我是靠谱客的博主 清新夏天,这篇文章主要介绍笔记:STM32——PWM波形生成以及控制电机,现在分享给大家,希望可以做个参考。

简单了解一下PWM波形就是利用定时器TIM比较器生成,就是根据CNT计时,然后PWM模式的CCR设置一个位置在0~RCC的位置,当CNT计时到CCR持平的位置就值0或者置1。

PWM波形

频率=1/Ts 置高电平的占空比=Ton/Ts 分辨率=占空比变化差距,Ts等于周期,

特别声明一次下:只有惯性系统才可以使用

PWM频率=单片机频率/psc+1/ARR+1

占空比=CCR/ARR+1

PWM分频率=1/ARR+1

下面是配置PWM波形的基本结构图

配置PWM波形是要利用定时器的,所以也要配置定时器的基本结构,这里定时器可以去这个了解一

https://blog.csdn.net/m0_64478952/article/details/123734824?spm=1001.2014.3001.5501

这里还有要知道的是输出比较通到配置的模式分几种。这里标注红色的就是用到的 PWM模式了,

可以看出来PWM1模式和PWM2模式其实就反过来的意思

              输出比较模式                                                 REF意思是参考信号

模式

描述

冻结

CNT=CCR时,REF保持为原状态

配置时为有效电平

CNT=CCR时,REF保持为有效电平

配置时为无效电平

CNT=CCR时,REF保持为无效电平

配置时为电平翻转

CNT=CCR时,REF保持为电平反转

强制时为有效电平

CNT与CCR无效,REF强制为无效电平

强制时为有效电平

CNT与CCR无效,REF强制为有效电平

PWM1模式

向上计数:CNT<CCR时,REF置有效电平,CNT>=CCR时,REF置无效电平

向下计数:CNT>CCR时,REF置无效电平,CNT<=CCR时,REF置有效电平

PWM2模式

向上计数:CNT<CCR时,REF置无效电平,CNT>=CCR时,REF置有效电平

向下计数:CNT>CCR时,REF置有效电平,CNT<=CCR时,REF置无效电平

其实输出比较的意思可以看这个文件可以看出来就是进入psc分频,然后CNT开始计数,设定一个重装载得置,在这个置得中间产生一个事件信号,就是设置CCR得值得信号,因为PWM波形一定是要在这个值之内才有意义,不然超过了或者等于的时候,那么占空比是100%就没有意义了。所以这个产生的事件一定是要在这个值的范围内。也就是设置停止位。

 接下来就是了解TIM定时器的引脚和比较值通道了。这个通道就是设置PWM输出比较模式的。

可以看一下这个引脚图,PA0对应着TIM2——CH1意思就是定时器2,输出通道1的意思,也就是说用到了PA0的引脚作为PWM生成的话就要配置定时器2和通道1这个功能,否则是不会有效的,这里注意就是只有引脚有标明TIM和CH这些标出才可以使用PWM输出,否则是不行的,往下面看PA1 PA2这些引脚其实都是一样的看定时器看通道,配置相应的就行了,这些都是大同小异的。

这些了解之后就开始配置引脚代码了。

首先配置通常第一步都是打开引脚时钟。

这里用到了定时器以及PA0作为PWM输出引脚所以就对应打开这两个时钟。

 这里有注意的时候配置引脚的定时器是复用的功能,所以也要打开AFIO复用功能的时钟

复制代码
1
2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE); //打开GPIOA组的时钟

然后配置引脚结构体初始化,这里可以看到这个GPIO_Mode模式配置跟以往的不一样,这里也是用到AF复用的推挽模式,只有这样这个引脚输出PWM的控制权才会复用到定时器的手中。不然是没有PWM的效果的。

复制代码
1
2
3
4
5
GPIO_InitTypeDef GPIO_Initstreuture; //初始化GPIO,同时对应着下面的TIM2和CH1通道 GPIO_Initstreuture.GPIO_Mode=GPIO_Mode_AF_PP; //如果用定时器控制引脚的需要利复用推挽输出,这样控制权才能让定时器控制,才可以有PWM引脚输出 GPIO_Initstreuture.GPIO_Pin=GPIO_Pin_0; //设置GPIO——0引脚 GPIO_Initstreuture.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_Initstreuture); //结构体A组写入寄存器

接下来就是配置定时器的时基单元了。这里就没有什么可以讲的,就是根据正常的配置定时器一样。定义结构体初始化,然后设置计数模式,和重装载值以及预分频的值,这里为什么设置频率要设置100-1和36-1呢下面有一个小知识可以讲到。

复制代码
1
2
3
4
5
6
7
8
9
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //初始化时基单元结构体 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分屏参数,选择哪一个没有很大的问题 TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up; //时钟计数模式,分别由向上计数,向下计数,和三种中央对齐模式 TIM_TimeBaseStructure.TIM_Period=100-1; //频率计数器的值,就是一个周期记多少个数重装载的值 TIM_TimeBaseStructure.TIM_Prescaler=36-1; //预分频器的值 TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //这个是高级定时器,基本通用定时器不用开启 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //时基单元结构体写入初始化寄存器

接下来就是这个PWM结构图的基本配置了。首先也是定义Oc通道的结构体,OC通道其实就是CH通道,然后写入结构体成员。有一段注释的是高级定时器的,这里用不到 ,现在配置的是通用定时器的。然后在下就是配置PWM模式,这里配置的是模式1,然后配置极性,再就是打开时使能,这里Pulse其实就是CCR的值,就是这一个位置,让CNT计数到这里的位置,就是占空比的值。这里设置0,是因为等下需要重新修改这个值,因为PWM其实就是一个占空比不断变化的过程,如果只设置一个值,就没有很好的利用PWM的功能。再就写入结构体写入寄存器这里寄存器其实也是有好几个的需要根据配置的引脚打开相应的通道。

复制代码
1
2
3
4
5
6
7
8
TIM_OCInitTypeDef TIMOC1_Initstruture; //定义PWM通道 TIM_OCStructInit(&TIMOC1_Initstruture); //结构体成员写入OCS通道 //高级定时器用的 //TIMOC1_Initstruture.TIM_OCIdleState=; //TIMOC1_Initstruture.TIM_OCNIdleState=;//TIMOC1_Initstruture.TIM_OCNPolarity=;//TIMOC1_Initstruture.TIM_OutputNState=; TIMOC1_Initstruture.TIM_OCMode=TIM_OCMode_PWM1; //模式 TIMOC1_Initstruture.TIM_OCPolarity=TIM_OCNPolarity_High; //极性 TIMOC1_Initstruture.TIM_OutputState= TIM_OutputState_Enable; //使能 TIMOC1_Initstruture.TIM_Pulse=0; //脉冲 CCR //运行时控制CCR需要TIM_SetCompare1这个函数,形成不同的占空比,达到PWM效果 TIM_OC1Init(TIM2,&TIMOC1_Initstruture); //通道1

这里就是四个OC通道对应着CH四个了。

 到这里完整的PWM就是差不多了,最后就是打开定时器通道开始工作

复制代码
1
TIM_Cmd(TIM2,ENABLE);

接下来就是改变CCR的值也就是Pulse的值,这里也是每个通道对应着一个改变的函数

这里定义一个新的函数方便调用

复制代码
1
2
3
4
void PWM_SetCompare3(uint16_t Compare2) { TIM_SetCompare1(TIM2,Compare2); }

这里也是每个通道对应一个改变比较值,这个函数意思就是设置比较值,就是CCR的值,这里意思四个通道。参数1很明了了就是设置TIM几定时器几的意思,这里是TIM2。参数2就是一个比较值,这个值可以是参数值。uint16_t,这个数据类型的范围都可以。

 

上面的大致初始化就我完成了,接下来就是要看看这个电机怎么操作了,电机利用TB16612这个芯片很好用也很方便,驱动电机效果也很好,但是唯一的缺点就是,最近这个热度高,芯片哄抬价格比较高。有点下贵。

用起来很方便,下面附一张接线图,这个可以接两路电机的。分别看下面对应就是了,这个其实就是一个双路H桥直流电机驱动芯片刚好就可以控制正反转。STBY接电源正常工作,接地就是待机状态。

只要电源线接好,然后PWMA线接PWM信号引脚AIN1,AIN2普通的IO口电平控制正反转就行了。

 那下面就是配置一下IO口吧,就是配置一下A4和A5作为AIN1和2的两个输入引脚就好了。然后

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Motor_Init(void) { GPIO_InitTypeDef led_Init; //定义初始化结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); led_Init.GPIO_Mode=GPIO_Mode_Out_PP; //模式,带上拉电阻的推挽输出 led_Init.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5; //引脚 led_Init.GPIO_Speed=GPIO_Speed_50MHz; //速度 GPIO_Init(GPIOA,&led_Init); }

下面就配置一下控制电机正反转和改变CCR的值就好了

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void MOtor_Setspeed(int8_t speed) { if(speed>=0) { GPIO_SetBits(GPIOA,GPIO_Pin_4); GPIO_ResetBits(GPIOA,GPIO_Pin_5); PWM_SetCompare1(speed); } else { GPIO_ResetBits(GPIOA,GPIO_Pin_4); GPIO_SetBits(GPIOA,GPIO_Pin_5); PWM_SetCompare1(-speed); } }

然后全部代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include "stm32f10x.h" uint6_t num=0,num1; void MOtor_Setspeed(int8_t speed); void PWM_Init(void); void PWM_SetCompare1(uint16_t Compare); void Motor_Init(void); void key_Init(void); unsigned key_scanf(void); int main(void) { PWM_Init(); Motor_Init(); key_Init(); while(1) { num=key_scanf(); //这个就是一个按键,按一次调一下速度 if(num == 1) { num1+=20; if(num1>100) { num1=-100;} } MOtor_Setspeed(num1); } } void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA组的时钟 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //从映射引脚 // GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2 ,ENABLE); //引脚重映射分配部分重映射,先打开重映射TIM2的功能 // GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//因为重映射刚好也JTD端口,所以也需要去掉。看到数据手册,TIM2,通道一部分重映射是PA15.所以初始化就可以把端口改位15,在引脚定刚好是PA15 PB3,PB4 GPIO_InitTypeDef GPIO_Initstreuture; //初始化GPIO,同时对应着下面的TIM2和CH1通道 GPIO_Initstreuture.GPIO_Mode=GPIO_Mode_AF_PP; //如果用定时器控制引脚的需要利复用推挽输出,这样控制权才能让定时器控制,才可以有PWM引脚输出 GPIO_Initstreuture.GPIO_Pin=GPIO_Pin_0; //设置GPIO——0引脚 GPIO_Initstreuture.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_Initstreuture); //结构体A组写入寄存器 TIM_InternalClockConfig(TIM2); //内部时钟,但是不配置也可以,因为是一直开启的 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //初始化时基单元结构体 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分屏参数,选择哪一个没有很大的问题 TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up; //时钟计数模式,分别由向上计数,向下计数,和三种中央对齐模式 TIM_TimeBaseStructure.TIM_Period=100-1; //频率计数器的值,就是一个周期记多少个数重装载的值 TIM_TimeBaseStructure.TIM_Prescaler=36-1; //预分频器的值 TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //这个是高级定时器,基本通用定时器不用开启 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //时基单元结构体写入初始化寄存器 //PWM频率 =72Mhz/(psc+1)/(arr+1)=1kz。PWM占空比=CRR/(arr+1)=50%。PWM分辨率=1/(arr+1)=1%。这是公式,需要根据不同需求改变不同的参数 TIM_OCInitTypeDef TIMOC1_Initstruture; //定义PWM通道 TIM_OCStructInit(&TIMOC1_Initstruture); //结构体成员写入OCS通道 //高级定时器用的 //TIMOC1_Initstruture.TIM_OCIdleState=; //TIMOC1_Initstruture.TIM_OCNIdleState=;//TIMOC1_Initstruture.TIM_OCNPolarity=;//TIMOC1_Initstruture.TIM_OutputNState=; TIMOC1_Initstruture.TIM_OCMode=TIM_OCMode_PWM1; //模式 TIMOC1_Initstruture.TIM_OCPolarity=TIM_OCNPolarity_High; //极性 TIMOC1_Initstruture.TIM_OutputState= TIM_OutputState_Enable; //使能 TIMOC1_Initstruture.TIM_Pulse=0; //脉冲 CCR //运行时控制CCR需要TIM_SetCompare1这个函数,形成不同的占空比,达到PWM效果 TIM_OC1Init(TIM2,&TIMOC1_Initstruture); //通道1 TIM_Cmd(TIM2,ENABLE); //定时器开始工作 } void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2,Compare); } void Motor_Init(void) { GPIO_InitTypeDef led_Init; //定义初始化结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); led_Init.GPIO_Mode=GPIO_Mode_Out_PP; //模式,带上拉电阻的推挽输出 led_Init.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5; //引脚 led_Init.GPIO_Speed=GPIO_Speed_50MHz; //速度 GPIO_Init(GPIOA,&led_Init); } void MOtor_Setspeed(int8_t speed) { if(speed>=0) { GPIO_SetBits(GPIOA,GPIO_Pin_4); GPIO_ResetBits(GPIOA,GPIO_Pin_5); PWM_SetCompare1(speed); } else { GPIO_ResetBits(GPIOA,GPIO_Pin_4); GPIO_SetBits(GPIOA,GPIO_Pin_5); PWM_SetCompare1(-speed); } } void key_Init(void) { GPIO_InitTypeDef key_Init; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); key_Init.GPIO_Mode=GPIO_Mode_IPU; key_Init.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_13; key_Init.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOC,&key_Init); } /* 函数名:按键扫面函数 * 参数 :无 * 返回值:num 按键值当按键按下时返回值1按键1,返回2值按键2,没有按键按下的时候返回值0 */ unsigned key_scanf(void) { unsigned char num=0; if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1) == 0) //判断按键是否按下 { Delay_ms(20); while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1) == 0);//判断按键是否按下 Delay_ms(20); num=1; } if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13) == 0) { Delay_ms(20); while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13) == 0); Delay_ms(20); num=2; } return num; }

上面arr 设置100-1   arr设置36-1就是因为驱动电机,电机是一个线圈磁铁,如果频率太低,会产生刺激耳朵的声音,可以计算一下的  单片机的频率72000000/100-1/36-1=20khz,是一个高频来的,正常人的耳朵频率范围是20~20khz。其实20khz已近是听不到的了。所以设置为20khz就是避免电机产生刺激的声音,这只是一个小知识,如果不在意的话。完成可以随别设置。

而且arr设置100刚好CCR设置为0加到100刚好就是PWM的占空比就是CCR/ARR+1,可以看出20/100=0.2,40/100=0.4,60/100=0.6 80/100=0.8 100/100=1 这个分辨率就是20%,等于1的时候就是满转速,-负的时候就是电机反转

最后

以上就是清新夏天最近收集整理的关于笔记:STM32——PWM波形生成以及控制电机的全部内容,更多相关笔记内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(91)

评论列表共有 0 条评论

立即
投稿
返回
顶部