之前是完成了bsp的移植和导入,接下来就要尝试移植foc算法了,开源的foc算法也比较多,我这里打算利用simplefoc进行移植。本身的simplefoc是基于c++的,这里要移植成c代码。另外,simplefoc的sdk其中已经适配了很多种类的传感器,驱动器以及无刷电机。如果硬件使用的是它已经适配的方案,则只需要简单配置一下就可以驱动了。
而我这里是要在rtthread下移植foc,更倾向于使用rtthread的框架,所以各种传感器和驱动器的适配计划加到rtthread的驱动这边来做。foc那边只移植simplefoc的核心算法即可。所以在正式移植foc算法之前,还需要先搭建用到的底层驱动。
今天就先整理一下读取磁编码器pwm信号的输入捕获驱动的移植记录。其实某些适配更好的bsp内的rtthread驱动库里面已经有了输入捕获驱动,但只是捕获了输入脉宽的时间,而我这里需要的是捕获pwm信号的占空比,也就对应了磁编码器探测到的电机位置。但大体功能类似,所以随便找一个类似的底层驱动进行一下修改和移植即可。
磁编码器简介
我这里用的是赛卓电子的国产磁编码器芯片sc60228,详情请看其数据手册,主要特性如下:
移植rtt驱动
这个比较简单,因为rtt驱动库内已经有了“rt_inputcapture.c”的驱动文件,在sdk的“rt-thread/components/drivers/misc”目录下。只不过大多数的bsp没有做对应的适配而已。那先不管bsp那边的适配问题,先把这个c文件和对应的头文件拷贝一份,比如我重命名为“pwm_input_capture.c”和“pwm_input_capture.h”。
然后代码内容改动不大,主要改的是返回的数据除了脉宽时间还有一个周期时间,这样就可以计算输入pwm信号的占空比了。另外,原有的驱动上使用的是ringbuffer做了一个数据缓存,这样数据处理可以异步话,什么时候需要什么时候把缓存内的数据全部读走即可。但各人考虑,我应用的场合是用这个信号来驱动无刷电机,这个pwm信号的输入频率也才1khz,市面上大多数的无刷电机驱动,底层控制频率基本都达到了10khz以上。
所以我这里肯定不需要异步处理的,会直接用这个信号触发底层控制。而且控制效果还需要测试,如果转速上不去或者抖动厉害的话,可能还需要想办法插值细化或者改用spi读取编码器数据(这也是为什么硬件上做了两种接口的原因,就是想去测试探索一些好玩的东西)。所以我这里是直接去掉了ringbuffer,加入了信号量。到时候上层开一个线程去等待这个信号量去跑foc算法。头文件修改如下:
struct pwm_inputcapture_data
{
rt_uint32_t pulsewidth_us; //脉宽
rt_uint32_t pulsecycle_us; //周期
};
struct pwm_inputcapture_device
{
struct rt_device parent;
const struct pwm_inputcapture_ops ops;
rt_sem_t sem;
struct pwm_inputcapture_data pulse_param;
};
/
capture operators
*/
struct pwm_inputcapture_ops
{
rt_err_t (*init)(struct pwm_inputcapture_device *inputcapture);
rt_err_t (*open)(struct pwm_inputcapture_device *inputcapture);
rt_err_t (*close)(struct pwm_inputcapture_device *inputcapture);
};
void pwm_hw_inputcapture_isr(struct pwm_inputcapture_device *inputcapture);
rt_err_t rt_device_pwm_inputcapture_register(struct pwm_inputcapture_device *inputcapture,
const char *name,
void *data);
c文件主要修改的是回调函数,把之前的数据加入ringbuffer的操作改成了释放信号量,其它地方的修改都是一些简单的适配,由于c代码较多,我这里就不都贴出来了,相信大家肯定会自己完成适配,甚至比我的还要适配的好。而我的代码,等我第一期的功能开发完了,会整体开源出来。c代码主要修改的回调函数如下:
void rt_hw_pwm_inputcapture_isr(struct pwm_inputcapture_device *inputcapture)
{
rt_sem_release(inputcapture->sem);
if (inputcapture->parent.rx_indicate != rt_null)
inputcapture->parent.rx_indicate(&inputcapture->parent, 1);
}
适配bsp驱动
bsp驱动的适配稍微麻烦一点,如果大家能找到其它类似bsp内的相似驱动可以进行移植,那我这里简单找了一下并没有找到,所以仿照rtt的驱动适配方式,自己适配了一下。主要实现要点就是开启每个timer的ch0和ch1双通道对ci0或者ci1输入的pwm信号进行采样,一个捕获脉宽,一个捕获周期,从而得到占空比。剩下的就是一些向下调用gd32的驱动库api,向上适配rtt的驱动接口。同样,下面只给出主要的初始化代码和中断处理代码,其它的可自行实现或者关注我后续开源的代码。
rt_err_t pwm_inputcap_init(struct pwm_inputcapture_device *pwm_incap)
{
uint32_t sys_clk_freq;
uint32_t timer_prescaler = 1;
uint32_t trigger_ch;
timer_parameter_struct timerconfig;
timer_ic_parameter_struct timericconfig;
struct gd32_pwm_inputcapture_device pwm_incap_device;
pwm_incap_device = (struct gd32_pwm_inputcapture_device )pwm_incap;
rcu_periph_clock_enable(pwm_incap_device->timer_rcu);
rcu_periph_clock_enable(pwm_incap_device->gpio_rcu);
gpio_init(pwm_incap_device->gpiox, gpio_mode_in_floating, gpio_ospeed_50mhz, pwm_incap_device->pinx);
sys_clk_freq = rcu_clock_freq_get(ck_sys);
log_i(system clock frequency:%d, sys_clk_freq);
timerconfig.alignedmode = timer_counter_edge;
timerconfig.clockdivision = timer_ckdiv_div1;
timerconfig.counterdirection = timer_counter_up;
timerconfig.period = 65535u;
do{
if(sys_clk_freq / timer_prescaler / timerconfig.period input_freq_min)
break;
if(timer_prescaler == 65536)
{
rt_kprintf(can not configure the prescaler for input signal frequency:%dhzn, pwm_incap_device->input_freq_min);
return rt_error;
}
timer_prescaler++;
}while(1);
timerconfig.prescaler = timer_prescaler-1;
timerconfig.repetitioncounter = 0;
timer_init(pwm_incap_device->timerx, &timerconfig);
log_i(%s timer prescaler:%d, pwm_incap_device->name, timer_prescaler);
timericconfig.icfilter = 10;
timericconfig.icpolarity = timer_ic_polarity_rising;
timericconfig.icprescaler = timer_ic_psc_div1;
timericconfig.icselection = timer_ic_selection_directti;
timer_input_pwm_capture_config(pwm_incap_device->timerx, pwm_incap_device->input_ch, &timericconfig);
timer_interrupt_flag_clear(pwm_incap_device->timerx, timer_int_flag_up);
timer_interrupt_enable(pwm_incap_device->timerx,timer_int_up);
trigger_ch = ((pwm_incap_device->input_ch == timer_ch_0) ? timer_smcfg_trgsel_ci0fe0 : timer_smcfg_trgsel_ci1fe1);
timer_input_trigger_source_select(pwm_incap_device->timerx, trigger_ch);
timer_slave_mode_select(pwm_incap_device->timerx, timer_slave_mode_restart);
timer_external_trigger_config(pwm_incap_device->timerx,timer_ext_tri_psc_off,timer_etp_rising,10);
nvic_setpriority(pwm_incap_device->timerx_irqn, 3);
nvic_enableirq(pwm_incap_device->timerx_irqn);
timer_enable(pwm_incap_device->timerx);
return rt_eok;
}
void pwm_inputcapture_update_isr(struct gd32_pwm_inputcapture_device device)
{
uint32_t width_ch;
/ tim update event */
if (timer_interrupt_flag_get(device->timerx, timer_int_flag_up) != reset)
{
timer_interrupt_flag_clear(device->timerx, timer_int_flag_up);
device->pwm_inputcap.pulse_param.pulsecycle_us = timer_channel_capture_value_register_read(device->timerx, device->input_ch);
width_ch = ((device->input_ch == timer_ch_0) ? (timer_ch_1) : (timer_ch_0));
device->pwm_inputcap.pulse_param.pulsewidth_us = timer_channel_capture_value_register_read(device->timerx, width_ch);
rt_hw_pwm_inputcapture_isr(device);
}
}
修改工程构建文件
修改相关sconscript文件
在“libraries/gd32_drivers/sconscript”文件内的适当位置加入如下代码:
if getdepend(['rt_using_pwm_input_capture']):
src += ['drv_pwm_inputcapture.c']
在“rt-thread/components/drivers/misc/sconscript”文件内的适当位置加入如下代码:
if getdepend(['rt_using_input_capture']):
src = src + ['rt_inputcapture.c']
意思很简单,就是当rtconfig.h内定义了”rt_using_pwm_input_capture”宏,则把“drv_pwm_inputcapture.c”和“rt_inputcapture.c”驱动文件加入工程,更准确的是加入编译。
修改相关kconfig文件
在“rt-thread/components/drivers/kconfig”文件内的适当位置加入如下代码:
config rt_using_input_capture
bool using input capture device drivers
default n
管理bsp驱动代码的kconfig文件不再librares目录下,而是在board目录下。于是在“board/kconfig”文件内的适当位置,仿照其它驱动加入如下代码:
menuconfig bsp_using_pwm_inputcapture
bool enable pwm input capture
default n
select rt_using_pwm_input_capture
if bsp_using_pwm_inputcapture
config bsp_using_pwm_inputcapture1
bool enable pwm input capture 1
default n
config bsp_using_pwm_inputcapture2
bool enable pwm input capture 2
default n
config bsp_using_pwm_inputcapture3
bool enable pwm input capture 3
default n
config bsp_using_pwm_inputcapture4
bool enable pwm input capture 4
default n
config bsp_using_pwm_inputcapture5
bool enable pwm input capture 5
default n
config bsp_using_pwm_inputcapture6
bool enable pwm input capture 6
default n
endif
意思也比较简单,我这里适配了6个pwm的输入捕获驱动,并且利用“select”语句,在bsp的驱动管理里面自动开启了rtt驱动里面的“rt_using_pwm_input_capture”选项。修改完上述代码后,就可以用menuconfig命令或者rtthreadide的rt-thread settings图形配置界面内进行配置了:
测试
驱动有了,再在顶层逻辑内创建并实现两个测试线程:
int main(void)
{
...
rt_thread_t motorl_encoder_thread;
motorl_encoder_thread = rt_thread_create(motorlencoder, motorlencoder_thread_entry, rt_null, 1024, 4, 20);
rt_thread_startup(motorl_encoder_thread);
rt_thread_t motorr_encoder_thread;
motorr_encoder_thread = rt_thread_create(motorrencoder, motorrencoder_thread_entry, rt_null, 1024, 4, 20);
rt_thread_startup(motorr_encoder_thread);
...
}
void motorlencoder_thread_entry(void *parameter)
{
rt_device_t lpwm_input_dev;
rt_uint16_t wait_i;
struct pwm_inputcapture_device *inputcap_dev;
lpwm_input_dev = rt_device_find(pwm_inputcap1);
if(lpwm_input_dev == rt_null)
return;
inputcap_dev = (struct pwm_inputcapture_device *)lpwm_input_dev;
rt_device_open(lpwm_input_dev, rt_device_oflag_rdonly);
while(1)
{
if(inputcap_dev->sem != rt_null)
{
rt_sem_take(inputcap_dev->sem, rt_waiting_forever);
if(wait_i++ >= 1000)
{
rt_kprintf(motorl encoder:tt%dn,inputcap_dev->pulse_param.pulsewidth_us * 10000 / inputcap_dev->pulse_param.pulsecycle_us);
wait_i = 0;
}
}
}
}
void motorrencoder_thread_entry(void *parameter)
{
rt_device_t rpwm_input_dev;
rt_uint16_t wait_i;
struct pwm_inputcapture_device *inputcap_dev;
rpwm_input_dev = rt_device_find(pwm_inputcap3);
if(rpwm_input_dev == rt_null)
return;
inputcap_dev = (struct pwm_inputcapture_device *)rpwm_input_dev;
rt_device_open(rpwm_input_dev, rt_device_oflag_rdonly);
while(1)
{
if(inputcap_dev->sem != rt_null)
{
rt_sem_take(inputcap_dev->sem, rt_waiting_forever);
if(wait_i++ >= 1000)
{
rt_kprintf(motorr encoder:%dn,inputcap_dev->pulse_param.pulsewidth_us * 10000 / inputcap_dev->pulse_param.pulsecycle_us);
wait_i = 0;
}
}
}
}
目前只是实现了大概1s钟打印一次编码器位置,一圈的机械角度范围扩大到了0~10000(我用的是12位磁编码器,分辨率是4096,我这里统一归一化到了10000),终端输出如下:
可以看到,慢慢向一个方向推动小车,两个编码器的变化规律是相反的,和实际的两个电机对向安装相匹配,实际使用的时候按照其中一个为基准,把另外一个编码器数据反向即可。
只看其中一个轮子,输出频率改为原有的1khz,输出值转换为浮点的角度值,可得到如下的测试曲线:
静止不动,暂时也没有驱动电机,也就没有电机的电磁干扰,在次条件下测得的静态数据如下,静态稳定度在0.2度左右,比12位的最小测量精度0.088度大了二倍多一点:
华芯通公司将于4月30日关闭,预示着更大的风暴将要席卷半导体界
虚电路的概念及采用虚电路进行数据传输的过程
PLM、ERP、APS和MES几个信息系统的数据集成以及各自基本功能
企业可以怎样投资区块链
索尼准备和美国卡内基梅隆大学携手,染指家用机器人领域
无刷电机小车开发记录—PWM信号输入捕获驱动
FSH18频谱分析仪18GHz
apple watch2上市时间出来了!同时还支持防水和GPS
红米Pro2要来了,联发科双摄后置指纹
【节能学院】安科瑞电能管理系统在郑大体院的设计与应用
人工智能发展近70年来背后的故事
荣耀9、小米6区别对比评测:双摄、价格、现货之战,小米6性价比高,华为荣耀9有现货啊
美信发布业内尺寸最小集成度最高的IO-Link收发器
医疗传感器的介绍与应用
程序员加班的害处有哪些
功率放大器的特点及其分类
隧道智能巡检机器人前景可期
特斯拉改变的不止汽车,锂电池行业会被革新
变压器差动保护回路中的不平衡电流
矢量控制是什么意思 矢量控制的特点