前言
上篇文章记录了为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); - 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);
下图是开机上电终端打印的信息,由于我开启了PWM驱动的调试,所以打印了很多相关的调试信息:
如下是测试的PWM1的波形输出,黄色的通道一测试的正向PWM信号,蓝色的通道二测试的是互补的反向PWM信号:
下图是设置PWM1的占空比为1/10后的效果:
相关链接
本系列首篇文章连接:
https://club.rt-thread.org/ask/article/5c0c4ba7eb4ab1ad.html
———————End———————
👇 点击阅读原文进入官网