Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
BSP
PWM
智能小车_平衡车
无刷电机小车开发记录04——互补PWM驱动移植
发布于 2023-08-23 21:26:40 浏览:717
订阅该版
[tocm] ##前言 上篇文章记录了为GD32的BSP添加了PWM信号输入捕获的驱动,并实现了对[SC60228DC](https://atta.szlcsc.com/upload/public/pdf/source/20200313/C496752_1840082083502A76F84629E8B586B7EC.pdf "SC60228DC")磁编码器数据的读取(PWM接口),最后还做了一点简单的测试。今天过来继续修轮子,适配一下PWM驱动。这里不得不提一下,造轮子或者修轮子可能是比较枯燥的,如果有也想搞一搞这个小车但懒得造轮子的小伙伴可以等我都弄完了直接用完适配好的程序,可能大多数同学都更喜欢玩上层实现具体功能的部分。不过对于我来说,在这之前还有很多轮子要修。 ##相关硬件电路 介于上篇文章有小伙伴产生了一些疑问,所以后面的文章我尽量把相关的一些东西提前罗列一下,比如今天要调试的是驱动无刷电机的PWM信号。那下面就是其中一路电机的驱动电路原理图,无刷电机的三项引线分别由三个NCE6005AS的双N沟道MOS管驱动。其中G2拉高,G1拉低时,高侧MOS管导通,该项被接入VDD,相反则低测MOS导通,该项被接入GND。而Mos管由EG2134的三半桥栅极驱动器驱动,最初的介绍视频里面也提过,我这里用的是全国产化的方案,所以相对国外的一些集成IC来说,器件要零散一些,但主要功能一致。而栅极驱动器的驱动信号则是三路互补PWM,其中HINx和LINx为一组。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/64d58bdf868263e62905871af7a2db76.png.webp) 如下是EG2134的输入输出逻辑真值表: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/e5a1aaf2fdd4fcad54d52c2a0364ec86.png) 想了解更多器件信息,可参阅其芯片手册:[EG2134芯片手册](https://atta.szlcsc.com/upload/public/pdf/source/20200107/C480661_235F21EC73D073F149882C12A613FF28.pdf "EG2134芯片手册") ##PWM驱动移植 ###源文件适配 目前我用的这块GD32E503器件还没有适配PWM驱动,所以还是有两个选择,如果能找到一个类似的驱动移植就会简单一点,否则就要走第二条路,完全自己适配,就要麻烦很多。首先对于RTT的PWM驱动有现成的,只要打开“RT_USING_PWM”宏即可把“rt_drv_pwm.c”添加到工程里。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/81000d6e95f20225506d67dcc731678b.png) 而对于BSP的底层驱动,我用的这块器件并没有适配,但好在RTT对于arm内核的GD32器件的适配度还是挺高的,可以在arm内核的BSP内找到相关驱动,那闲话少说,先拷贝过来再说。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/ceb55fb2946ab56bf509296ba43fc9b3.png.webp) 接下来的工作就是要看一下驱动是否匹配,把不匹配的地方适配一下就可以了。首先来说,RTT层的PWM功能还是比较完善的,已经支持了互补PWM模式的配置,也有配置死区的接口。但看了一下GD32的BSP层的驱动,只适配了普通的PWM功能,没有互补模式。所以着重的工作就是适配互补模式的PWM。首先看"drv_pwm.c"下的第一个结构体*TIMER_PORT_CHANNEL_MAP_S*,用来定义PWM用到的timer以及输出通道和输出引脚。原本的定义首先没有互补通道的IO配置,其次直接用GD32固件库的Port和pin定义的,所以配置只能固化到代码里。 ```c typedef struct { rt_int8_t TimerIndex; // timer index:0~13 rt_uint32_t Port; // gpio port:GPIOA/GPIOB/GPIOC/... rt_uint32_t pin; // gpio pin:GPIO_PIN_0~GPIO_PIN_15 rt_uint16_t channel; // timer channel char *name; } TIMER_PORT_CHANNEL_MAP_S; ``` 这里我想实现更多的使用配置文件配置,而配置文件配置如果也采用uint32类型的Port的寄存器地址去定义是很不直观的。所以添加了互补通道的同时,也修改了一下定义方式,具体怎么用且往后看。 ```c typedef struct { rt_int8_t TimerIndex; // timer index:0~13 char *OP_Port; //A,B,C,D... rt_uint16_t OP_pin; // GPIO_pin:0~15 char *ON_Port; //A,B,C,D... rt_base_t ON_pin; // GPIO_pin:0~15 rt_uint16_t channel; // timer channel char *name; } TIMER_PORT_CHANNEL_MAP_S; ``` 再往下看就是原本驱动里面对PWM引脚等信息的固化配置,比如PWM配置的是Timer3的ch2,输出引脚是GPIOB_8: ```c static struct gd32_pwm gd32_pwm_obj[] = { #ifdef RT_USING_PWM1 {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm1"}}, #endif #ifdef RT_USING_PWM2 {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm2"}}, #endif ... ... }; ``` 修改后的代码如下,其中所以配置都由宏定义实现,而具体的宏定义后面可以用Kconfig实现图形化配置: ```c static struct gd32_pwm gd32_pwm_obj[] = { #ifdef RT_USING_PWM1 {.tim_handle = {RT_USING_PWM1_TIMER_INDEX, RT_USING_PWM1_OP_PORT, RT_USING_PWM1_OP_PIN, RT_USING_PWM1_ON_PORT, RT_USING_PWM1_ON_PIN, RT_USING_PWM1_CH, RT_USING_PWM1_NAME}}, #endif #ifdef RT_USING_PWM2 {.tim_handle = {RT_USING_PWM2_TIMER_INDEX, RT_USING_PWM2_OP_PORT, RT_USING_PWM2_OP_PIN, RT_USING_PWM2_ON_PORT, RT_USING_PWM2_ON_PIN, RT_USING_PWM2_CH, RT_USING_PWM2_NAME}}, #endif ... ... }; ``` 上面提到过为了配置的时候更直观,没有直接使用寄存器地址值,而是用的字符'A','B'等去代表GPIOA,GPIOB。对于PIN的定义也类似,所以这里需要添加一个配置参数到具体的Port和pin的转换接口: ```c static rt_uint32_t get_gpio_periph_port(char pot) { rt_uint32_t Port=0; switch(pot) { case 'A': Port = GPIOA; break; case 'B': Port = GPIOB; break; case 'C': Port = GPIOC; break; case 'D': Port = GPIOD; break; case 'E': Port = GPIOE; break; case 'F': Port = GPIOF; break; case 'G': Port = GPIOG; break; default: Port = 0; break; } return Port; } static rt_uint32_t get_gpio_periph_pin(rt_uint16_t pn) { rt_uint32_t Pin=0; if(pn < 16) { Pin = GPIO_PIN_0 << (pn); } else { LOG_E("Unsport gpio pin!\n"); } return Pin; } ``` 有了上面的对应接口,原驱动里的一些配置代码跟随做一下调整即可。我下面只给出改动稍大的一些地方,比如对于gpio的初始化代码,要添加一路互补IO的初始化,如果不适用互补PWM,配置文件里面不进行配置即可,这里就会跳过互补IO的初始化: ```c static void gpio_config(void) { rt_int16_t i; rt_uint32_t port; rt_uint32_t pin; /* config the GPIO as analog mode */ for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i) { port = get_gpio_periph_port(*(gd32_pwm_obj[i].tim_handle.OP_Port)); pin = get_gpio_periph_pin(gd32_pwm_obj[i].tim_handle.OP_pin); if(port) gpio_init(port, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, pin); port = get_gpio_periph_port(*(gd32_pwm_obj[i].tim_handle.ON_Port)); pin = get_gpio_periph_pin(gd32_pwm_obj[i].tim_handle.ON_pin); if(port) gpio_init(port, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, pin); } } ``` 源文件方面最后一个要修改的地方就是*enable*接口,主要是看configuration内的complementary互补模式是否被开启,如果开启则同时使能互补通道即可。 ```c static rt_err_t drv_pwm_enable(TIMER_PORT_CHANNEL_MAP_S *pstTimerMap, struct rt_pwm_configuration *configuration, rt_bool_t enable) { int channel; if(configuration->channel == 0 || configuration->channel > 4) return RT_ERROR; channel = configuration->channel-1; if (!enable) { timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel, TIMER_CCX_DISABLE); if(configuration->complementary == RT_TRUE) { timer_channel_complementary_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel, TIMER_CCXN_DISABLE); } } else { timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel, TIMER_CCX_ENABLE); if(configuration->complementary == RT_TRUE) { timer_channel_complementary_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel, TIMER_CCXN_ENABLE); } } LOG_I("pwm[%d][%d] enable:%d!", pstTimerMap->TimerIndex, channel, enable); return RT_EOK; } ``` ###构建管理文件适配 接下来修改对应的SConscript文件和Kconfig文件。先在"libraries\gd32_drivers\"下的SConscript文件内添加如下代码,即开启*RT_USING_PWM*宏后,就把上面移植过来的“drv_pwm.c”添加到工程内。 ```python # add pwm drivers. if GetDepend('RT_USING_PWM'): src += ['drv_pwm.c'] ``` 最后在“board\”目录下的Kconfig文件内,添加如下代码。利用select语句联动了RT_USING_PWM定义,我这里开启了6路PWM的配置: ```python menuconfig BSP_USING_PWM bool "Enable PWM" default n select RT_USING_PWM if BSP_USING_PWM config RT_USING_PWM1 bool "Enable PWM1" default n if RT_USING_PWM1 config RT_USING_PWM1_NAME string "PWM DEV NAME" default PWM1 config RT_USING_PWM1_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM1_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM1_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM1_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM1_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM1_CH int "PWM output channel" default 0 endif config RT_USING_PWM2 bool "Enable PWM2" default n if RT_USING_PWM2 config RT_USING_PWM2_NAME string "PWM DEV NAME" default PWM2 config RT_USING_PWM2_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM2_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM2_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM2_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM2_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM2_CH int "PWM output channel" default 0 endif config RT_USING_PWM3 bool "Enable PWM3" default n if RT_USING_PWM3 config RT_USING_PWM3_NAME string "PWM DEV NAME" default PWM3 config RT_USING_PWM3_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM3_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM3_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM3_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM3_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM3_CH int "PWM output channel" default 0 endif config RT_USING_PWM4 bool "Enable PWM4" default n if RT_USING_PWM4 config RT_USING_PWM4_NAME string "PWM DEV NAME" default PWM4 config RT_USING_PWM4_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM4_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM4_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM4_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM4_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM4_CH int "PWM output channel" default 0 endif config RT_USING_PWM5 bool "Enable PWM5" default n if RT_USING_PWM5 config RT_USING_PWM5_NAME string "PWM DEV NAME" default PWM5 config RT_USING_PWM5_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM5_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM5_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM5_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM5_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM5_CH int "PWM output channel" default 0 endif config RT_USING_PWM6 bool "Enable PWM6" default n if RT_USING_PWM6 config RT_USING_PWM6_NAME string "PWM DEV NAME" default PWM6 config RT_USING_PWM6_TIMER_INDEX int "Timer Index" default 0 config RT_USING_PWM6_OP_PORT string "PWM output_P Port (A,B,C...)" default A config RT_USING_PWM6_OP_PIN int "PWM output_P pin (0~15)" default 0 config RT_USING_PWM6_ON_PORT string "PWM output_N Port (A,B,C...)" default A config RT_USING_PWM6_ON_PIN int "PWM output_N pin (0~15)" default 0 config RT_USING_PWM6_CH int "PWM output channel" default 0 endif endif ``` 到此,就可以利用menuconfig或者IDE的图形界面进行配置了: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/be88c1fe9ab76a2c6dd01f00c4c0c59f.png.webp) ##实现效果 为了测试效果,我这里在main函数内对6路PWM进行了初始化: ```c rt_device_t pwm1_LA=RT_NULL, pwm2_LB=RT_NULL, pwm3_LC=RT_NULL, pwm4_RA=RT_NULL, pwm5_RB=RT_NULL, pwm6_RC=RT_NULL; int main(void) { ... ... pwm1_LA = rt_device_find(RT_USING_PWM1_NAME); if(pwm1_LA != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm1_LA; rt_pwm_set(pwm_dev,RT_USING_PWM1_CH+1,10000,5000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM1_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } pwm2_LB = rt_device_find(RT_USING_PWM2_NAME); if(pwm2_LB != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm2_LB; rt_pwm_set(pwm_dev,RT_USING_PWM2_CH+1,10000,1000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM2_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } pwm3_LC = rt_device_find(RT_USING_PWM3_NAME); if(pwm3_LC != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm3_LC; rt_pwm_set(pwm_dev,RT_USING_PWM3_CH+1,10000,8000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM3_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } pwm4_RA = rt_device_find(RT_USING_PWM4_NAME); if(pwm4_RA != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm4_RA; rt_pwm_set(pwm_dev,RT_USING_PWM4_CH+1,10000,5000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM4_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } pwm5_RB = rt_device_find(RT_USING_PWM5_NAME); if(pwm5_RB != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm5_RB; rt_pwm_set(pwm_dev,RT_USING_PWM5_CH+1,10000,1000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM5_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } pwm6_RC = rt_device_find(RT_USING_PWM6_NAME); if(pwm6_RC != RT_NULL) { struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm6_RC; rt_pwm_set(pwm_dev,RT_USING_PWM6_CH+1,10000,8000); rt_pwm_enable(pwm_dev,-(RT_USING_PWM6_CH+1)); rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name); } ... ... } ``` 还实现了一个如下的测试命令,这样除了可以使用驱动里实现的PWM命令外,也可以使用自己的测试命令测试,更方便一些: ```c static int motorLA_pwm(int argc, char **argv) { rt_err_t result = RT_EOK; rt_uint32_t period, pulse; struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm1_LA; if(argc >= 3) { period = atoi(argv[1]); pulse = atoi(argv[2]); rt_pwm_set(pwm_dev,RT_USING_PWM1_CH+1,period,pulse); rt_kprintf("set %s:[period]%d-[pulse]%d!\n",pwm_dev->parent.parent.name, period, pulse); } else { rt_kprintf("Usage: \n"); rt_kprintf("motorXN_pwm
- set the pwm's period and pulse\n"); rt_kprintf("eg:motorLA_pwm 10000 5000 is set motorLA pwm's period to 10us, pulse to 5us\n"); result = -RT_ERROR; } return RT_EOK; } MSH_CMD_EXPORT(motorLA_pwm, motorLA_pwm
); ``` 下图是开机上电终端打印的信息,由于我开启了PWM驱动的调试,所以打印了很多相关的调试信息: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230823/f2107bb0499c94691c7d4ef555b54359.png.webp) 如下是测试的PWM1的波形输出,黄色的通道一测试的正向PWM信号,蓝色的通道二测试的是互补的反向PWM信号: ![screenshot_RigolDS18.png](https://oss-club.rt-thread.org/uploads/20230823/0a2db9980c813053c3c2c7188dd47d04.png.webp) 下图是设置PWM1的占空比为1/10后的效果: ![screenshot_RigolDS26.png](https://oss-club.rt-thread.org/uploads/20230823/6260a209ffacda334c95e768703e4095.png.webp) [本系列首篇文章连接:https://club.rt-thread.org/ask/article/5c0c4ba7eb4ab1ad.html](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
STM32 407 串口接收数据 系统卡死
2
RTT nrf24l01 设备驱动程序
3
stm32f10x串口只能发送数据,无法接收
4
第一次尝试移植rt-thread 到stm32F103系列问题
5
有人把stm32L07xx的bsp移到rtt上来了吗?求一个
6
rt-thread线程调度异常在stm32f103芯片上
7
RTT是否支持STM32F429
8
请问谁有 STM32F40x HAL + RT-THREAD 模板
9
rt-thread在stm32f411下的移植问题
10
针对STM32F7系列平台的MPU,Cache特性,需要注意哪些问题?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
机器人操作系统 (ROS2) 和 RT-Thread 通信
4
国产MCU移植系列教程汇总,欢迎查看!
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总线
ART-Pi
FinSH
USB
文件系统
DMA
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
ota在线升级
WIZnet_W5500
UART
flash
packages_软件包
cubemx
PWM
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
中断
flashDB
socket
Debug
GD32
编译报错
msh
keil_MDK
at_device
rt_mq_消息队列_msg_queue
ulog
C++_cpp
SFUD
本月问答贡献
出出啊
1512
个答案
341
次被采纳
小小李sunny
1437
个答案
289
次被采纳
张世争
774
个答案
167
次被采纳
crystal266
546
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
2
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
2
次点赞
crystal266
2
篇文章
1
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部