无刷电机小车开发记录04——互补PWM驱动移植

09/18 20:17
阅读数 59

前言


  上篇文章记录了为GD32的BSP添加了PWM信号输入捕获的驱动,并实现了对SC60228DC磁编码器数据的读取(PWM接口),最后还做了一点简单的测试。今天过来继续修轮子,适配一下PWM驱动。这里不得不提一下,造轮子或者修轮子可能是比较枯燥的,如果有也想搞一搞这个小车但懒得造轮子的小伙伴可以等我都弄完了直接用完适配好的程序,可能大多数同学都更喜欢玩上层实现具体功能的部分。不过对于我来说,在这之前还有很多轮子要修。


相关硬件电路


  介于上篇文章有小伙伴产生了一些疑问,所以后面的文章我尽量把相关的一些东西提前罗列一下,比如今天要调试的是驱动无刷电机的PWM信号。那下面就是其中一路电机的驱动电路原理图,无刷电机的三项引线分别由三个NCE6005AS的双N沟道MOS管驱动。其中G2拉高,G1拉低时,高侧MOS管导通,该项被接入VDD,相反则低测MOS导通,该项被接入GND。而Mos管由EG2134的三半桥栅极驱动器驱动,最初的介绍视频里面也提过,我这里用的是全国产化的方案,所以相对国外的一些集成IC来说,器件要零散一些,但主要功能一致。而栅极驱动器的驱动信号则是三路互补PWM,其中HINx和LINx为一组。

如下是EG2134的输入输出逻辑真值表:

想了解更多器件信息,可参阅其芯片手册:EG2134芯片手册

PWM驱动移植


源文件适配

  目前我用的这块GD32E503器件还没有适配PWM驱动,所以还是有两个选择,如果能找到一个类似的驱动移植就会简单一点,否则就要走第二条路,完全自己适配,就要麻烦很多。首先对于RTT的PWM驱动有现成的,只要打开“RT_USING_PWM”宏即可把“rt_drv_pwm.c”添加到工程里。

而对于BSP的底层驱动,我用的这块器件并没有适配,但好在RTT对于arm内核的GD32器件的适配度还是挺高的,可以在arm内核的BSP内找到相关驱动,那闲话少说,先拷贝过来再说。

  接下来的工作就是要看一下驱动是否匹配,把不匹配的地方适配一下就可以了。首先来说,RTT层的PWM功能还是比较完善的,已经支持了互补PWM模式的配置,也有配置死区的接口。但看了一下GD32的BSP层的驱动,只适配了普通的PWM功能,没有互补模式。所以着重的工作就是适配互补模式的PWM。首先看”drv_pwm.c”下的第一个结构体TIMER_PORT_CHANNEL_MAP_S,用来定义PWM用到的timer以及输出通道和输出引脚。原本的定义首先没有互补通道的IO配置,其次直接用GD32固件库的Port和pin定义的,所以配置只能固化到代码里。

1typedef struct
2{

3    rt_int8_t TimerIndex; // timer index:0~13
4    rt_uint32_t Port;     // gpio port:GPIOA/GPIOB/GPIOC/...
5    rt_uint32_t pin;      // gpio pin:GPIO_PIN_0~GPIO_PIN_15
6    rt_uint16_t channel;  // timer channel
7    char *name;
8} TIMER_PORT_CHANNEL_MAP_S;

这里我想实现更多的使用配置文件配置,而配置文件配置如果也采用uint32类型的Port的寄存器地址去定义是很不直观的。所以添加了互补通道的同时,也修改了一下定义方式,具体怎么用且往后看。

1typedef struct
2{

3    rt_int8_t TimerIndex; // timer index:0~13
4    char        *OP_Port;    //A,B,C,D...
5    rt_uint16_t OP_pin;     // GPIO_pin:0~15
6    char        *ON_Port;    //A,B,C,D...
7    rt_base_t ON_pin;      // GPIO_pin:0~15
8    rt_uint16_t channel;  // timer channel
9    char *name;
10} TIMER_PORT_CHANNEL_MAP_S;

再往下看就是原本驱动里面对PWM引脚等信息的固化配置,比如PWM配置的是Timer3的ch2,输出引脚是GPIOB_8:

1static struct gd32_pwm gd32_pwm_obj[] = {
2#ifdef RT_USING_PWM1
3    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2"pwm1"}},
4#endif
5#ifdef RT_USING_PWM2
6    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2"pwm2"}},
7#endif
8...
9...
10};

修改后的代码如下,其中所以配置都由宏定义实现,而具体的宏定义后面可以用Kconfig实现图形化配置:

1static struct gd32_pwm gd32_pwm_obj[] = {
2#ifdef RT_USING_PWM1
3    {.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}},
4#endif
5#ifdef RT_USING_PWM2
6    {.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}},
7#endif
8    ...
9    ...
10};

上面提到过为了配置的时候更直观,没有直接使用寄存器地址值,而是用的字符’A’,’B’等去代表GPIOA,GPIOB。对于PIN的定义也类似,所以这里需要添加一个配置参数到具体的Port和pin的转换接口:

1static rt_uint32_t get_gpio_periph_port(char pot)
2
{
3    rt_uint32_t Port=0;
4    switch(pot)
5    {
6    case 'A':
7        Port = GPIOA;
8        break;
9    case 'B':
10        Port = GPIOB;
11        break;
12    case 'C':
13        Port = GPIOC;
14        break;
15    case 'D':
16        Port = GPIOD;
17        break;
18    case 'E':
19        Port = GPIOE;
20        break;
21    case 'F':
22        Port = GPIOF;
23        break;
24    case 'G':
25        Port = GPIOG;
26        break;
27    default:
28        Port = 0;
29        break;
30    }
31    return Port;
32}
33static rt_uint32_t get_gpio_periph_pin(rt_uint16_t pn)
34
{
35    rt_uint32_t Pin=0;
36    if(pn < 16)
37    {
38        Pin = GPIO_PIN_0 << (pn);
39    }
40    else {
41        LOG_E("Unsport gpio pin!\n");
42    }
43    return Pin;
44}

有了上面的对应接口,原驱动里的一些配置代码跟随做一下调整即可。我下面只给出改动稍大的一些地方,比如对于gpio的初始化代码,要添加一路互补IO的初始化,如果不适用互补PWM,配置文件里面不进行配置即可,这里就会跳过互补IO的初始化:

1static void gpio_config(void)
2
{
3    rt_int16_t i;
4    rt_uint32_t port;
5    rt_uint32_t pin;
6    /* config the GPIO as analog mode */
7    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i)
8    {
9        port = get_gpio_periph_port(*(gd32_pwm_obj[i].tim_handle.OP_Port));
10        pin = get_gpio_periph_pin(gd32_pwm_obj[i].tim_handle.OP_pin);
11        if(port)
12            gpio_init(port, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, pin);
13        port = get_gpio_periph_port(*(gd32_pwm_obj[i].tim_handle.ON_Port));
14        pin = get_gpio_periph_pin(gd32_pwm_obj[i].tim_handle.ON_pin);
15        if(port)
16            gpio_init(port, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, pin);
17    }
18}

源文件方面最后一个要修改的地方就是enable接口,主要是看configuration内的complementary互补模式是否被开启,如果开启则同时使能互补通道即可。

1static rt_err_t drv_pwm_enable(TIMER_PORT_CHANNEL_MAP_S *pstTimerMap, struct rt_pwm_configuration *configuration,
2                               rt_bool_t enable)
3{
4    int channel;
5    if(configuration->channel == 0 || configuration->channel > 4)
6        return RT_ERROR;
7    channel = configuration->channel-1;
8    if (!enable)
9    {
10        timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel,
11                                          TIMER_CCX_DISABLE);
12        if(configuration->complementary == RT_TRUE)
13        {
14            timer_channel_complementary_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel,
15                                              TIMER_CCXN_DISABLE);
16        }
17    }
18    else
19    {
20        timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel,
21                                          TIMER_CCX_ENABLE);
22        if(configuration->complementary == RT_TRUE)
23        {
24            timer_channel_complementary_output_state_config(index_to_timer(pstTimerMap->TimerIndex), channel,
25                    TIMER_CCXN_ENABLE);
26        }
27    }
28    LOG_I("pwm[%d][%d] enable:%d!", pstTimerMap->TimerIndex, channel, enable);
29    return RT_EOK;
30}

构建管理文件适配

接下来修改对应的SConscript文件和Kconfig文件。先在”libraries\gd32_drivers\”下的SConscript文件内添加如下代码,即开启RT_USING_PWM宏后,就把上面移植过来的“drv_pwm.c”添加到工程内。

1# add pwm drivers.
2if GetDepend('RT_USING_PWM'):
3    src +
= ['drv_pwm.c']

最后在“board\”目录下的Kconfig文件内,添加如下代码。利用select语句联动了RT_USING_PWM定义,我这里开启了6路PWM的配置:

1menuconfig BSP_USING_PWM
2        bool "Enable PWM"
3        default n
4        select RT_USING_PWM
5        if BSP_USING_PWM
6            config RT_USING_PWM1
7                bool "Enable PWM1"
8                default n
9            if RT_USING_PWM1
10                config RT_USING_PWM1_NAME
11                    string "PWM DEV NAME"
12                    default PWM1
13                config RT_USING_PWM1_TIMER_INDEX
14                    int "Timer Index"
15                    default 0
16                config RT_USING_PWM1_OP_PORT
17                    string "PWM output_P Port (A,B,C...)"
18                    default A
19                config RT_USING_PWM1_OP_PIN
20                    int "PWM output_P pin (0~15)"
21                    default 0
22                config RT_USING_PWM1_ON_PORT
23                    string "PWM output_N Port (A,B,C...)"
24                    default A
25                config RT_USING_PWM1_ON_PIN
26                    int "PWM output_N pin (0~15)"
27                    default 0
28                config RT_USING_PWM1_CH
29                    int "PWM output channel"
30                    default 0
31            endif
32            config RT_USING_PWM2
33                bool "Enable PWM2"
34                default n
35            if RT_USING_PWM2
36                config RT_USING_PWM2_NAME
37                    string "PWM DEV NAME"
38                    default PWM2
39                config RT_USING_PWM2_TIMER_INDEX
40                    int "Timer Index"
41                    default 0
42                config RT_USING_PWM2_OP_PORT
43                    string "PWM output_P Port (A,B,C...)"
44                    default A
45                config RT_USING_PWM2_OP_PIN
46                    int "PWM output_P pin (0~15)"
47                    default 0
48                config RT_USING_PWM2_ON_PORT
49                    string "PWM output_N Port (A,B,C...)"
50                    default A
51                config RT_USING_PWM2_ON_PIN
52                    int "PWM output_N pin (0~15)"
53                    default 0
54                config RT_USING_PWM2_CH
55                    int "PWM output channel"
56                    default 0
57            endif
58            config RT_USING_PWM3
59                bool "Enable PWM3"
60                default n
61            if RT_USING_PWM3
62                config RT_USING_PWM3_NAME
63                    string "PWM DEV NAME"
64                    default PWM3
65                config RT_USING_PWM3_TIMER_INDEX
66                    int "Timer Index"
67                    default 0
68                config RT_USING_PWM3_OP_PORT
69                    string "PWM output_P Port (A,B,C...)"
70                    default A
71                config RT_USING_PWM3_OP_PIN
72                    int "PWM output_P pin (0~15)"
73                    default 0
74                config RT_USING_PWM3_ON_PORT
75                    string "PWM output_N Port (A,B,C...)"
76                    default A
77                config RT_USING_PWM3_ON_PIN
78                    int "PWM output_N pin (0~15)"
79                    default 0
80                config RT_USING_PWM3_CH
81                    int "PWM output channel"
82                    default 0
83            endif
84            config RT_USING_PWM4
85                bool "Enable PWM4"
86                default n
87            if RT_USING_PWM4
88                config RT_USING_PWM4_NAME
89                    string "PWM DEV NAME"
90                    default PWM4
91                config RT_USING_PWM4_TIMER_INDEX
92                    int "Timer Index"
93                    default 0
94                config RT_USING_PWM4_OP_PORT
95                    string "PWM output_P Port (A,B,C...)"
96                    default A
97                config RT_USING_PWM4_OP_PIN
98                    int "PWM output_P pin (0~15)"
99                    default 0
100                config RT_USING_PWM4_ON_PORT
101                    string "PWM output_N Port (A,B,C...)"
102                    default A
103                config RT_USING_PWM4_ON_PIN
104                    int "PWM output_N pin (0~15)"
105                    default 0
106                config RT_USING_PWM4_CH
107                    int "PWM output channel"
108                    default 0
109            endif
110            config RT_USING_PWM5
111                bool "Enable PWM5"
112                default n
113            if RT_USING_PWM5
114                config RT_USING_PWM5_NAME
115                    string "PWM DEV NAME"
116                    default PWM5
117                config RT_USING_PWM5_TIMER_INDEX
118                    int "Timer Index"
119                    default 0
120                config RT_USING_PWM5_OP_PORT
121                    string "PWM output_P Port (A,B,C...)"
122                    default A
123                config RT_USING_PWM5_OP_PIN
124                    int "PWM output_P pin (0~15)"
125                    default 0
126                config RT_USING_PWM5_ON_PORT
127                    string "PWM output_N Port (A,B,C...)"
128                    default A
129                config RT_USING_PWM5_ON_PIN
130                    int "PWM output_N pin (0~15)"
131                    default 0
132                config RT_USING_PWM5_CH
133                    int "PWM output channel"
134                    default 0
135            endif
136            config RT_USING_PWM6
137                bool "Enable PWM6"
138                default n
139            if RT_USING_PWM6
140                config RT_USING_PWM6_NAME
141                    string "PWM DEV NAME"
142                    default PWM6
143                config RT_USING_PWM6_TIMER_INDEX
144                    int "Timer Index"
145                    default 0
146                config RT_USING_PWM6_OP_PORT
147                    string "PWM output_P Port (A,B,C...)"
148                    default A
149                config RT_USING_PWM6_OP_PIN
150                    int "PWM output_P pin (0~15)"
151                    default 0
152                config RT_USING_PWM6_ON_PORT
153                    string "PWM output_N Port (A,B,C...)"
154                    default A
155                config RT_USING_PWM6_ON_PIN
156                    int "PWM output_N pin (0~15)"
157                    default 0
158                config RT_USING_PWM6_CH
159                    int "PWM output channel"
160                    default 0
161            endif
162        endif

到此,就可以利用menuconfig或者IDE的图形界面进行配置了:

实现效果


为了测试效果,我这里在main函数内对6路PWM进行了初始化:

1rt_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;
2int main(void)
3
{
4    ...
5    ...
6    pwm1_LA = rt_device_find(RT_USING_PWM1_NAME);
7    if(pwm1_LA != RT_NULL)
8    {
9        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm1_LA;
10        rt_pwm_set(pwm_dev,RT_USING_PWM1_CH+1,10000,5000);
11        rt_pwm_enable(pwm_dev,-(RT_USING_PWM1_CH+1));
12        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
13    }
14    pwm2_LB = rt_device_find(RT_USING_PWM2_NAME);
15    if(pwm2_LB != RT_NULL)
16    {
17        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm2_LB;
18        rt_pwm_set(pwm_dev,RT_USING_PWM2_CH+1,10000,1000);
19        rt_pwm_enable(pwm_dev,-(RT_USING_PWM2_CH+1));
20        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
21    }
22    pwm3_LC = rt_device_find(RT_USING_PWM3_NAME);
23    if(pwm3_LC != RT_NULL)
24    {
25        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm3_LC;
26        rt_pwm_set(pwm_dev,RT_USING_PWM3_CH+1,10000,8000);
27        rt_pwm_enable(pwm_dev,-(RT_USING_PWM3_CH+1));
28        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
29    }
30    pwm4_RA = rt_device_find(RT_USING_PWM4_NAME);
31    if(pwm4_RA != RT_NULL)
32    {
33        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm4_RA;
34        rt_pwm_set(pwm_dev,RT_USING_PWM4_CH+1,10000,5000);
35        rt_pwm_enable(pwm_dev,-(RT_USING_PWM4_CH+1));
36        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
37    }
38    pwm5_RB = rt_device_find(RT_USING_PWM5_NAME);
39    if(pwm5_RB != RT_NULL)
40    {
41        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm5_RB;
42        rt_pwm_set(pwm_dev,RT_USING_PWM5_CH+1,10000,1000);
43        rt_pwm_enable(pwm_dev,-(RT_USING_PWM5_CH+1));
44        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
45    }
46    pwm6_RC = rt_device_find(RT_USING_PWM6_NAME);
47    if(pwm6_RC != RT_NULL)
48    {
49        struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm6_RC;
50        rt_pwm_set(pwm_dev,RT_USING_PWM6_CH+1,10000,8000);
51        rt_pwm_enable(pwm_dev,-(RT_USING_PWM6_CH+1));
52        rt_kprintf("%s init OK!\n", pwm_dev->parent.parent.name);
53    }
54    ...
55    ...
56}

还实现了一个如下的测试命令,这样除了可以使用驱动里实现的PWM命令外,也可以使用自己的测试命令测试,更方便一些:

1static int motorLA_pwm(int argc, char **argv)
2
{
3    rt_err_t result = RT_EOK;
4    rt_uint32_t period, pulse;
5    struct rt_device_pwm *pwm_dev = (struct rt_device_pwm *)pwm1_LA;
6    if(argc >= 3)
7    {
8        period = atoi(argv[1]);
9        pulse = atoi(argv[2]);
10        rt_pwm_set(pwm_dev,RT_USING_PWM1_CH+1,period,pulse);
11        rt_kprintf("set %s:[period]%d-[pulse]%d!\n",pwm_dev->parent.parent.name, period, pulse);
12    }
13    else {
14        rt_kprintf("Usage: \n");
15        rt_kprintf("motorXN_pwm <period> <pulse> - set the pwm's period and pulse\n");
16        rt_kprintf("eg:motorLA_pwm 10000 5000 is set motorLA pwm's period to 10us, pulse to 5us\n");
17        result = -RT_ERROR;
18    }
19    return RT_EOK;
20}
21MSH_CMD_EXPORT(motorLA_pwm, motorLA_pwm <period> <pulse>);

下图是开机上电终端打印的信息,由于我开启了PWM驱动的调试,所以打印了很多相关的调试信息:

如下是测试的PWM1的波形输出,黄色的通道一测试的正向PWM信号,蓝色的通道二测试的是互补的反向PWM信号:


下图是设置PWM1的占空比为1/10后的效果:

相关链接


本系列首篇文章连接:

https://club.rt-thread.org/ask/article/5c0c4ba7eb4ab1ad.html


———————End——————




👇 点击阅读原文进入官网

本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部