Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
智能小车_平衡车
设备驱动
无刷电机小车开发记录03——PWM信号输入捕获驱动
发布于 2023-08-20 14:36:09 浏览:1001
订阅该版
##前言 好吧,转眼一看,距离上一篇文章已经过去了两个月了。只能说工作和家庭的各种事情确实太多了,人到中年属于自己的时间确实越来越少了,再不过来调一下这个小车估计都要生锈了。所以今天过来接着做下一步工作。之前是完成了BSP的移植和导入,接下来就要尝试移植FOC算法了,开源的FOC算法也比较多,我这里打算利用SimpleFOC进行移植。本身的SimpleFOC是基于C++的,这里要移植成C代码。另外,SimpleFOC的SDK其中已经适配了很多种类的传感器,驱动器以及无刷电机。如果硬件使用的是它已经适配的方案,则只需要简单配置一下就可以驱动了。而我这里是要在RTThread下移植FOC,更倾向于使用RTThread的框架,所以各种传感器和驱动器的适配计划加到RTThread的驱动这边来做。FOC那边只移植SimpleFOC的核心算法即可。所以在正式移植FOC算法之前,还需要先搭建用到的底层驱动。今天就先整理一下读取磁编码器PWM信号的输入捕获驱动的移植记录。其实某些适配更好的BSP内的RTThread驱动库里面已经有了输入捕获驱动,但只是捕获了输入脉宽的时间,而我这里需要的是捕获PWM信号的占空比,也就对应了磁编码器探测到的电机位置。但大体功能类似,所以随便找一个类似的底层驱动进行一下修改和移植即可。 ##磁编码器简介 我这里用的是赛卓电子的国产磁编码器芯片[SC60228](https://atta.szlcsc.com/upload/public/pdf/source/20200313/C496752_1840082083502A76F84629E8B586B7EC.pdf "SC60228"),详情请看其数据手册,主要特性如下: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230826/8db323492816be80bf33a1fc2ffd98f0.png.webp) ##移植RTT驱动 这个比较简单,因为RTT驱动库内已经有了“rt_inputcapture.c”的驱动文件,在SDK的“rt-thread/components/drivers/misc”目录下。只不过大多数的BSP没有做对应的适配而已。那先不管BSP那边的适配问题,先把这个C文件和对应的头文件拷贝一份,比如我重命名为“PWM_input_capture.c”和“PWM_input_capture.h”。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230820/33a1899043da02a32c5ef55337fe6ac0.png) 然后代码内容改动不大,主要改的是返回的数据除了脉宽时间还有一个周期时间,这样就可以计算输入PWM信号的占空比了。另外,原有的驱动上使用的是ringbuffer做了一个数据缓存,这样数据处理可以异步话,什么时候需要什么时候把缓存内的数据全部读走即可。但各人考虑,我应用的场合是用这个信号来驱动无刷电机,这个PWM信号的输入频率也才1Khz,市面上大多数的无刷电机驱动,底层控制频率基本都达到了10Khz以上。所以我这里肯定不需要异步处理的,会直接用这个信号触发底层控制。而且控制效果还需要测试,如果转速上不去或者抖动厉害的话,可能还需要想办法插值细化或者改用SPI读取编码器数据(这也是为什么硬件上做了两种接口的原因,就是想去测试探索一些好玩的东西)。所以我这里是直接去掉了ringbuffer,加入了信号量。到时候上层开一个线程去等待这个信号量去跑FOC算法。头文件修改如下: ```c 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代码主要修改的回调函数如下: ```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的驱动接口。同样,下面只给出主要的初始化代码和中断处理代码,其它的可自行实现或者关注我后续开源的代码。 ```c 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 < pwm_incap_device->input_freq_min) break; if(timer_prescaler == 65536) { rt_kprintf("can not configure the prescaler for input signal frequency:%dhz\n", 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”文件内的适当位置加入如下代码: ```python if GetDepend(['RT_USING_PWM_INPUT_CAPTURE']): src += ['drv_pwm_inputcapture.c'] ``` 在“rt-thread/components/drivers/misc/SConscript”文件内的适当位置加入如下代码: ```python 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”文件内的适当位置加入如下代码: ```python config RT_USING_INPUT_CAPTURE bool "Using INPUT CAPTURE device drivers" default n ``` 管理BSP驱动代码的Kconfig文件不再librares目录下,而是在board目录下。于是在“board/Kconfig”文件内的适当位置,仿照其它驱动加入如下代码: ```python 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图形配置界面内进行配置了: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230820/95eac2879ca4dfe29d7ecaafad36c9c8.png) ##测试 驱动有了,再在顶层逻辑内创建并实现两个测试线程: ```c 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:\t\t%d\n",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:%d\n",inputcap_dev->pulse_param.pulsewidth_us * 10000 / inputcap_dev->pulse_param.pulsecycle_us); wait_i = 0; } } } } ``` 目前只是实现了大概1S钟打印一次编码器位置,一圈的机械角度范围扩大到了0~10000(我用的是12位磁编码器,分辨率是4096,我这里统一归一化到了10000),终端输出如下: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230820/5adecc4dc535c9e00e0d8815543bd26b.png) 可以看到,慢慢向一个方向推动小车,两个编码器的变化规律是相反的,和实际的两个电机对向安装相匹配,实际使用的时候按照其中一个为基准,把另外一个编码器数据反向即可。 只看其中一个轮子,输出频率改为原有的1Khz,输出值转换为浮点的角度值,可得到如下的测试曲线: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230820/ba73786ff3df6aa283e65d6772df8dc1.png.webp) 静止不动,暂时也没有驱动电机,也就没有电机的电磁干扰,在次条件下测得的静态数据如下,静态稳定度在0.2度左右,比12位的最小测量精度0.088度大了二倍多一点: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230820/775949356be1f62f839eaa35a47c6244.png.webp) ##相关连接 [本系列首篇文章连接:https://club.rt-thread.org/ask/article/5c0c4ba7eb4ab1ad.html](https://club.rt-thread.org/ask/article/5c0c4ba7eb4ab1ad.html "本系列首篇文章连接:")
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
吉利咕噜2022
国防科大-军品研发
文章
18
回答
3
被采纳
2
关注TA
发私信
相关文章
1
RTT软件包做的平衡小车,请教各位大神看看
2
三轮差速智能小车 开发记录
3
[一起DIY智能战车]硬件选择
4
rt-thread智能小车软件环境搭建
5
狂暴战车 使用 rt-robots 软件包 “组装” car
6
还在为做平衡小车而烦恼吗? EV来了
7
还在为做平衡小车而烦恼吗? EV 来了
8
狂暴战车 直流电机转速闭环,pid调试过程
9
狂暴战车 开发环境搭建
10
汇总:狂暴战车 开发记录
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
11
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部