无刷电机小车开发记录—移植SimpleFOC流程

前情提要
今天过来继续撸我的无刷电机小车。驱动无刷电机底层需要实现三大部分:功率驱动,位置反馈以及电流反馈。前面我已经适配了功率驱动(6线互补pwm),和位置反馈(pwm接口)的底层驱动代码。
而电流反馈我电路使用的是线内探测方案,输出模拟电压值。驱动程序可暂时使用rtt的adc底层驱动。
相关硬件电路
功率驱动电路和位置反馈的磁编码器芯片,前面文章给出过简介。这里我给出最后一块电路检测相关的电路:
这里我只检测了a,b两相的相电流,第三项电流可由电流和为零(基尔霍夫第一定律)计算得到。
其中r45和r49为线内采样电阻,压差经过lt199g1运放放大后输出。lt199g1的放大倍数是50,由于后面mcu的adc只能检测0~3.3v的正电压信号,所以这里加了个1.5v偏置。
可检测最大电流i=1.5v/50/0.01r=3a。对于我的这个小电机来说足够了,甚至后面可能还需要根据测试结果适当增大采样电阻阻值。
移植simplefoc
万事俱备只欠东风,到了驱动无刷电机最关键的时刻了,就是移植foc算法。目前网络上可以看到的foc算法很多,虽然我也没有过多的接触,但感觉核心算法基本一致,无非就是外围做了一些优化,变形。所以我这里选择了比较简单的simplefoc进行移植。后面可以根据自己的测试自行优化。
foc的原理我这里暂时不提及,网络上可以找到包括稚晖君在内的很多大佬的科普文章,也肯定比我讲解的好,可自行查阅。如果有需要,我这里后面再另外补充一篇算法讲解篇。这里只简单提及移植过程。
下面给出simplefoc的官网链接,更多详情可参考官网教程。如果完全按照官网提供的电路搭建平台,甚至可以不用管任何算法相关的内容,直接驱动无刷电机。如果完全以实现功能为目的,建议采用此途径。
找到arduino-foc仓库,克隆或者直接点击“download zip”按钮下载源代码。
目录结构
解压源代码,可先简单了解一下目录结构,根目录下主要有两个文件夹,一个examples,内部是基于arduino的例程。另外一个是最主要的src源代码目录。
例程文件后面用到的时候再说,这里先看src下的目录文件:
没错,这里可以看到,simplefoc是基于c++的。基于c++面向对象的特性,可以把整个结构封装的更好,也可以使用一些c++的高级语法。
其中“common”目录下除了公用的pid和低通滤波相关代码外,还包括了驱动,电流传感器,位置传感器的抽象类,包含了各传感器的公共属性。而外面的”drivers”,”current_sense”,”sensors”目录下封装了面向各实际方案的不同子类。
common目录内容:
common/base_classes目录内容:
比如src/drivers目录下就包含了3线pwm的bldc电机驱动和6线pwm的bldc电机驱动,以及几个步进电机的驱动类,他们都继承与bldcdriver类,然后各自实现各自独有的属性功能。
而子类目录下的hardware_specific文件夹内,包含的是面向不同硬件平台的驱动接口,也是直接适配新的mcu硬件平台的时候需要适配的代码。
为什么要说直接适配呢?是因为如果只是做电机驱动板,完全可以使用simplefoc的软件框架,把mcu等其它的相关驱动代码通过接口封装进来。而我这里显然不属于这种直接适配。我这里是想把simplefoc移植到rtthread的框架下,后面还想借助rtthread做其它功能。
simplefoc源码加入rtt目录
最后这个simplefoc想以一个功能包的形式嵌入到rtthread系统内,可以像rtthread已有的功能包一样,通过ui或者menuconfig进行配置。所以,在./packages目录下创建simplefoc目录,然后把simplefoc源码包内的src文件夹拷贝进来:
添加sconscript文件
上面的操作只是在文件系统内加入了代码包,要想让rtt编译加入的代码,还需要添加并修改sconscript构建文件。./packages/simplefoc目录下的sconscript文件比较简单,直接把./packages目录下的拷贝一份即可:
拷贝到./packages/simplefoc目录下的sconscript文件内容如下,主要是起到承接作用,让scons构建工具去子目录内继续查找sconscript文件。
import os
from building import *
objs = []
cwd = getcurrentdir()
list = os.listdir(cwd)
for item in list:
if os.path.isfile(os.path.join(cwd, item, 'sconscript')):
objs = objs + sconscript(os.path.join(item, 'sconscript'))
return('objs')
/packages/simplefoc/src目录内的sconscript文件稍复杂。我这里使用的这级sconscript文件管理整个源码包的构建。
最主要的是sources的源码添加和cpppath的头文件目录的定义。语法说明请参见python以及rtt官网相关教程文档。
...
sources = [bldcmotor.c,common/foc_utils.c,common/base_classes/focmotor.c]
sources += [common/base_classes/currentsense.c]
sources += [common/base_classes/sensor.c]
sources += [common/lowpass_filter.c]
sources += [common/pid.c]
sources += [common/time_utils.c]
sources += [sensors/magneticsensorpwm.c]
if getdepend(['rt_using_simplefoc_drv_6pwm']):
sources += ['drivers/bldcdriver6pwm.c']
if getdepend(['rt_using_simplefoc_current_sense_inline']):
sources += ['current_sense/inlinecurrentsense.c']
...
...
cpppath = [cwd, os.path.join(getcurrentdir(), 'inc'), cwd+'/common', cwd+'/common/base_classes', cwd+'/sensors', cwd+'/drivers']
cpppath += [cwd+'/current_sense']
...
修改kconfig文件
功能包的添加在/board/kconfig文件内。如下是我在”board extended module drivers”菜单下加入的simplefoc的相关配置:
menu board extended module drivers
menuconfig pkg_using_simplefoc
bool enable simple_foc module
default n
select rt_using_simplefoc
if pkg_using_simplefoc
config simple_foc_voltage_power_supply
int voltage power supply
default 12
choice
prompt select driver stype
default rt_using_simplefoc_drv_6pwm
config rt_using_simplefoc_drv_6pwm
bool using 6pwm mode driver
config rt_using_simplefoc_drv_3pwm
bool using 3pwm mode driver
endchoice
choice
prompt select current sense stype
default rt_using_simplefoc_current_sense_inline
config rt_using_simplefoc_current_sense_inline
bool using inline mode current sense
config rt_using_simplefoc_current_sense_lowside
bool using lowside mode current sense
endchoice
endif
endmenu
把相关的.cpp文件修改为.c文件
这里只是先修改文件类型,让构建文件能找到相关文件,具体内容后面在修改。比如./packages/simplefoc/src/common目录内的相关文件:
使用ui工具配置rtt
完成了以上修改既可以配置rtthread系统,把需要的代码真正添加进来了。
我这里添加进来的所有文件列表如下:
用c代码实现原有c++代码的功能
这里是比较耗费时间的,要用c语言的语法,实现类似c++的继承以及函数重载的功能。大体思路就是继承使用结构体包含的方式实现,函数重载就是用函数指针的方式链接子类(只是类的概念)的实现函数到父类内。具体的可参加我开源的源代码。
功能测试
开环控制
源码包的/examples/motion_control目录下有一些控制例程,我这里暂时测试了开环控制和速度闭环控制,开环控制不用关联位置传感器和电流传感器,只实现驱动器相关的代码即可:
测试代码如下,如果上面的流程没有出错,这里即可看到电机可以正常转起来了,只不过是开环的旋转效果不是很好,尽量不要测试太久,当心电机发热严重。
bldcmotor motor_left;
bldcdriver6pwm motor_left_driver;
float target_velocity = 6.28;
void motor_test_timeout(void *parameter)
{
motor_left.foc_motor.ops.move(&(motor_left.foc_motor), (float )(parameter));
}
int main(void)
{
rt_timer_t motor_tm;
...
bldcmotor_set_default(&motor_left, 7, 8, 100, not_set);
bldcdriver6pwm_set_default(&motor_left_driver, rt_using_pwm1_name, rt_using_pwm2_name, rt_using_pwm3_name, rt_using_pwm1_ch, rt_using_pwm2_ch, rt_using_pwm3_ch, 10000);
motor_left_driver.bldc_driver.voltage_power_supply = 8;
motor_left_driver.bldc_driver.voltage_limit = 8;
motor_left_driver.bldc_driver.ops.init(&(motor_left_driver.bldc_driver));
motor_left.linkdriver(&motor_left, &(motor_left_driver.bldc_driver));
motor_left.foc_motor.voltage_limit = 4;
motor_left.foc_motor.velocity_limit = 50;
motor_left.foc_motor.controller = velocity_openloop;
motor_left.foc_motor.ops.init(&(motor_left.foc_motor));
motor_tm = rt_timer_create(motor_test_tm, motor_test_timeout, &target_velocity, 10, rt_timer_flag_soft_timer | rt_timer_flag_periodic);
if(motor_tm != rt_null)
rt_timer_start(motor_tm);
...
}
闭环控制
上面的开环测试可以正常旋转就说明电路没有问题,就可以测试闭环控制效果了,代码如下:
bldcmotor motor_left;
bldcdriver6pwm motor_left_driver;
magneticsensorpwm sensor_pwm;
inlinecurrentsense current_sense;
float target_position = 0;
float target_velocity = 6.28;
void magneticsensorpwm_callback()
{
motor_left.foc_motor.ops.loopfoc(&(motor_left.foc_motor));
motor_left.foc_motor.ops.move(&(motor_left.foc_motor), target_velocity);
}
int main(void)
{
...
bldcmotor_set_default(&motor_left, 7, 8, 100, not_set);
bldcdriver6pwm_set_default(&motor_left_driver, rt_using_pwm1_name, rt_using_pwm2_name, rt_using_pwm3_name, rt_using_pwm1_ch, rt_using_pwm2_ch, rt_using_pwm3_ch, 10000);
magneticsensorpwm_set_default(&sensor_pwm, motorl_sensor,pwm_inputcap1);
inlinecurrentsense_set_default_by_gain(¤t_sense, 0.01f, 50, adc0, 10, 11, not_set);
current_sense.current_sense.ops.init(&(current_sense.current_sense));
sensor_pwm.sensor.ops.init(&(sensor_pwm.sensor));
focmotor_linksensor(&(motor_left.foc_motor), &(sensor_pwm.sensor));
focmotor_linkcurrentsense(&(motor_left.foc_motor), &(current_sense.current_sense));
motor_left_driver.bldc_driver.voltage_power_supply = 8;
motor_left_driver.bldc_driver.voltage_limit = 8;
motor_left_driver.bldc_driver.ops.init(&(motor_left_driver.bldc_driver));
motor_left.linkdriver(&motor_left, &(motor_left_driver.bldc_driver));
currentsense_linkdriver(&(current_sense.current_sense), &(motor_left_driver.bldc_driver));
motor_left.foc_motor.voltage_limit = 4;
motor_left.foc_motor.controller = velocity;
motor_left.foc_motor.pid_velocity.output_ramp = 1000;
motor_left.foc_motor.pid_velocity.p = 0.04;
motor_left.foc_motor.pid_velocity.i = 0.4;
motor_left.foc_motor.pid_velocity.d = 0;
motor_left.foc_motor.lpf_velocity.tf = 0.01f;
motor_left.foc_motor.ops.init(&(motor_left.foc_motor));
motor_left.foc_motor.ops.initfoc(&(motor_left.foc_motor), not_set, cw);
magneticsensorpwm_enableinterrupt(&sensor_pwm, &magneticsensorpwm_callback);
...
}
static int motorl_v_pi(int argc, char **argv)
{
rt_err_t result = rt_eok;
float p, i;
char buf[64];
if(argc >= 3)
{
p = atof(argv[1]);
i = atof(argv[2]);
motor_left.foc_motor.pid_velocity.p = p;
motor_left.foc_motor.pid_velocity.i = i;
sprintf(buf, set p:%.3f,i%.3fn, p, i);
rt_kprintf(buf);
}
else {
rt_kprintf(usage: n);
rt_kprintf(motorl_v_pi - set the left motor velocity pi valuen);
rt_kprintf(eg:motorl_v_pi 0.2 5 is set left motor velocity p to 0.2, i to 5n);
result = -rt_error;
}
return rt_eok;
}
msh_cmd_export(motorl_v_pi, motorl_v_pi );
static int motorl_v(int argc, char **argv)
{
rt_err_t result = rt_eok;
float velocity;
char buf[64];
if(argc >= 2)
{
velocity = atof(argv[1]);
target_velocity = velocity;
sprintf(buf, set velocity:%.3fn, velocity);
rt_kprintf(buf);
}
else {
rt_kprintf(usage: n);
rt_kprintf(motorl_v - set the left motor velocityn);
rt_kprintf(eg:motorl_v 6 is set left motor velocity to 6 rad/sn);
result = -rt_error;
}
return rt_eok;
}
msh_cmd_export(motorl_v, motorl_v );
可以通过motorl_v_pi命令修改pid的参数,通过motorl_v命令设置转速。
结束语
这里的具体测试效果,我就暂时先不附加视频展示了,等后面忙完risc-v应用创新大赛的事,再过来详细测试优化,到时候再给出测试的视频效果。
由于还没有完全测试完毕,所以之前的硬件电路图纸和代码也一直没开源出来,到目前为止,至少测试了无刷电机部分的电路没什么大问题。所以后面我会先把目前阶段的相关文件开源出来,地址会加到首篇文章内。后面再测试优化再进行更新。

AGMX2手机怎么样?AGMX2还值得纪念吗?
!!供应/罗SHP54601A模拟示波器HP 54601A小
英特尔推出体积极小并可支持虚拟实境的NUC
罗德与施瓦茨发布新一代微波测量接收机FSMR3000
H桥电路的原理和应用
无刷电机小车开发记录—移植SimpleFOC流程
半导体划片刀原理、特点以及刀片磨损原因等知识分享!
RNN在FPGA的应用及测试分析
协作机器人为个人防护设备生产提速、创造新的就业机会
检测PCB的9个常识及方法
新华三的承载网解决方案,赋能5G时代下的数字未来
明基TK800评测 4K家用机里的高亮典型
针对高精密电子领域而推出的耐高温小型轻触开关
IHS分析师JimmyKim:OLED电视制造需要8代线以上生产线
发展数字经济,释放电力大数据新活力
SSD扛鼎新型数据中心
一部在发布之前就备受瞩目的手机,它到底符不符合我们的期待呢?
苹果14上市时间已定 苹果14手机图片及价格
跨屏翻译成焦点,讯飞双屏翻译机亮相第五届世界智能大会
联想拯救者即将推出搭载骁龙888电竞手机