一、TIM定时中断

定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

例:stm32中定时器的基准时钟一般是72MHZ,【周期是频率的倒数1T = 1/72us】,如果计数72个,就是1us,计数72000个,就是1ms

16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时

计数器:进行计数的寄存器,每来一个时钟,计数器加一

预分频器:对计数器时钟进行分频

自动重装寄存器:计数器的目标值,设定计多少数申请中断

2^16^=65536 预分频器和自动重装寄存器设置最大,定时器时间最大为59.65s【1/(72MHZ/65536*65536)】

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

二、定时器类型

类型 编号 总线 功能
高级定时器 TIM1、TIM8 APB2 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器 TIM6、TIM7 APB1 拥有定时中断、主模式触发DAC的功能

STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

2.1基本定时器

TIM6和TIM7定时器的主要功能包括:

  • 16位自动重装载累加计数器

  • 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频

  • 触发DAC的同步电路

  • 在更新事件(计数器溢出)时产生中断/DMA请求

image-20221126150307421

2.1.1分频的作用

便于计算,计时更加精确

  • 以12MHZ为例:

    1. 不分频

      一个时钟周期为 T = 1/12 us,如果我们需要1us,需要12T,这个很好理解吧,但是我们使用的时候经常是要以秒(s)微秒(ms)进行计时的,当我们需要1ms的时候,我们就需要计数12000T,这个数是已经很大了

    2. 12分频

      12分频后,12MHZ变为1MHZ,一个时钟周期为 T = 1/1 us = 1us,如果我们需要1us,需要1T;需要1ms时,只需要1000T

​ 我们很清楚的看到分频以后,计一次数就是1us,这不但利用我们去计算定时时间,而且计算的次数明显减少了,那么为什么说计数次数减少就可以提高精度呢?可以想一下平时使用的钟表,当我们使用的时间长了,表就会不太准,时钟也是一样,我们计数次数多了,难免会产生误差,一次两次小误差肯能影响不大,但是成千上百次误差的影响那可就大了

定时时间更长

  • 以72MHZ为例

    1. 不分频

      一个时钟周期为 T = 1/72 us,16位定时器的范围是0~65535,那么一个范围计数完成的时间大概是0.94ms

    2. 3分频

      3分频后,72MHZ变为24MHZ,一个时钟周期为 T = 1/24 us ,那么一个范围计数完成的时间大概是2.73ms

    3. 72分频

      72MHZ变为1MHZ,一个时钟周期为 T =1 us ,那么一个范围计数完成的时间大概是65.5ms

​ 由于分频以后,到达同一个计数时间的计数次数减少,所以在16位的定时器范围内,定时器的定时时间最大值增加【定时器计数最大值是不可改变的,2^16^,所以我们只能改变频率来改变计数最大时间】

2.1.2预分频器

​ 预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频。它是通过一个16位寄存器(TIMx_PSC)的计数实现分频。因为TIMx_PSC控制寄存器具有缓冲,可以在运行过程中改变它的数值,新的预分频数值将在下一个更新事件时起作用

实际分频数 = 分频器的值 + 1

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

image-20221126150902581

​ 缓冲器(也叫作影子寄存器),是实际起作用的寄存器。比如:在计数过程中突然改变分频系数,那么一个周期前半部分和后半部分的频率就会不一致,这可能会产生一些不好的影响。但是加上缓冲器后,改变分频系数并不会立即改变这一个周期的频率,它会等到这个周期结束后,产生更新事件,才会去改变,这样就保证了stm32的严谨性

2.1.3计数模式

image-20221126190730609

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

计数器使用影子寄存器 和 计数器不使用影子寄存器

image-20221126192339034

2.1.4主模式触发DAC功能

​ 当我们使用DAC时,需要每隔一段时间都要输出一段波形,按正常思路来说,我们要使用中断,每隔一段时间调用一次中断里面的代码,实现DAC转换。但是频繁调用中断会影响到主程序的进行,阻碍其他中断的进行

​ 如果我们使用主模式的话,就可以把定时器的更新事件映射到触发输出TRGO,然后将TRGO接到DAC触发转换引脚上,这样就不需要更新中断来实现DAC转换了。整个过程不需要软件的参与,实现了硬件的自动化

image-20221126153543448

2.2通用定时器

通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:

  • 16位向上、向下、向上/向下自动装载计数器

  • 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值

  • 4个独立通道: ─ 输入捕获 ─ 输出比较 ─ PWM生成(边缘或中间对齐模式) ─ 单脉冲模式输出

  • 使用外部信号控制定时器和定时器互连的同步电路

  • 如下事件发生时产生中断/DMA:

    • ─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
    • ─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    • ─ 输入捕获
    • ─ 输出比较
  • 支持针对定位的增量(正交)编码器和霍尔传感器电路

  • 触发输入作为外部时钟或者按周期的电流管理

image-20221126180204480 image-20221126180323300

2.2.1计数模式

向上计数模式、向下计数模式、中央对齐模式(向上/向下计数)

image-20221126181637041

向上计数模式:每次+1,加到目标值,申请中断,归零

向下计数模式:每次-1,减到目标值,申请中断,归起始值

中央对齐模式(向上/向下计数):每次+1,加到目标值,申请中断;之后每次-1,减到起始值,申请中断

2.3高级定时器

image-20221126182703940

三、定时中断基本结构

image-20221126183112649

使用中断输出控制的原因:

定时器模块中很多地方都要申请中断,定时器图中的向上折的箭头都表示要申请中断,所以需要控制这些中断,如果需要就允许,不需要就不允许

时钟树

image-20221126193508331

如果不改变SystemInit里面的配置,三种定时器中内部基准时钟均为72MHZ

实例一、定时中断和内外部时钟代码实现

1.1、定时中断

**功能实现:**每隔1S计数一次

**注意:**需要手动清除中断初始化时产生的中断标记,不然默认其实值为1,而不是0

timer.c

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
#include "stm32f10x.h"                  // Device header

void Timer_Init(void){
//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//手动清除中断标志位,避免刚初始化完就进入中断
TIM_ClearFlag(TIM2,TIM_IT_Update);

//使能中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}

//void TIM2_IRQHandler(void){
// //判断中断
// if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
//
// //清除中断标志位
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}

timer.h

1
2
3
4
5
6
7
#ifndef __TIMER_H__
#define __TIMER_H__

void Timer_Init(void);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Timer.h"

uint16_t num = 0;

int main(){
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num:");
while(1){
OLED_ShowNum(1,5,num,5);
//OLED_ShowNum(2,1,TIM_GetCounter(TIM2),5);
}
}

void TIM2_IRQHandler(void){
//判断中断
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
num++;
//清除中断标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}



OLED.c

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#include "stm32f10x.h"
#include "OLED_Font.h"

/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);

OLED_W_SCL(1);
OLED_W_SDA(1);
}

/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}

/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}

/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}

/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}

/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}

/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}

/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}

/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}

/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}

/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}

/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}

/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}

/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}

/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}

/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;

for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}

OLED_I2C_Init(); //端口初始化

OLED_WriteCommand(0xAE); //关闭显示

OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);

OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);

OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);

OLED_WriteCommand(0x40); //设置显示开始行

OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置

OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置

OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);

OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);

OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);

OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);

OLED_WriteCommand(0xA4); //设置整个显示打开/关闭

OLED_WriteCommand(0xA6); //设置正常/倒转显示

OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);

OLED_WriteCommand(0xAF); //开启显示

OLED_Clear(); //OLED清屏
}

OLED.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

1.2、外部时钟

**功能实现:**每遮挡一次计数器+1

**注意:**如果出现遮挡一次跳跃好多次,需要添加滤波

timer.c

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
#include "stm32f10x.h"                  // Device header

void Timer_Init(void){
//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择外部时钟
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0f);//这里要添加滤波,不然会跳好多次

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//手动清除中断标志位,避免刚初始化完就进入中断
TIM_ClearFlag(TIM2,TIM_IT_Update);

//使能中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}

uint16_t Timer_GetCount(void){
return TIM_GetCounter(TIM2);
}

//void TIM2_IRQHandler(void){
// //判断中断
// if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
//
// //清除中断标志位
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}

timer.h

1
2
3
4
5
6
7
8
#ifndef __TIMER_H__
#define __TIMER_H__

void Timer_Init(void);
uint16_t Timer_GetCount(void);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Timer.h"

uint16_t num = 0;

int main(){
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num:");
OLED_ShowString(2,1,"CNT:");
while(1){
OLED_ShowNum(1,5,num,5);
OLED_ShowNum(2,5,Timer_GetCount(),5);
}
}

void TIM2_IRQHandler(void){
//判断中断
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
num++;
//清除中断标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}

四、TIM输出比较

OC(Output Compare)输出比较

输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形

CCR是输出比较寄存器

image-20221128195422185

每个高级定时器和通用定时器都拥有4个输出比较通道

高级定时器的前3个通道额外拥有死区生成和互补输出的功能

4.1、PWM简介

PWM(Pulse Width Modulation)脉冲宽度调制

在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域

PWM参数:频率 = 1 / TS **占空比 **= TON / TS 分辨率 = 占空比变化步距

占空比越大,模拟出的电压越趋近于高电平,占空比越小,模拟出的电压越趋近于低电平

image-20221128195700005

4.2、输出比较通道

4.2.1输出比较通道(通用定时器)

image-20221128201040691

4.2.2输出比较通道(高级定时器)

image-20221129103943044

4.3 输出比较模式

image-20221128203052660

4.4PWM基本结构

image-20221128203914556

PWM频率: Freq = { CK_PSC / (PSC + 1) } / (ARR + 1)

PWM的频率=计数器更新频率

PWM占空比: Duty = CCR / (ARR + 1)

PWM分辨率: Reso = 1 / (ARR + 1)

分辨率定义为占空比最小的变化步距,占空比变化的越细腻越好

五、舵机

舵机是一种根据输入PWM信号占空比来控制输出角度的装置

输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

image-20221128210052386

在这里,PWM当做通讯协议使用,不是PWM等效一个模拟输出

image-20221128210413737

六、直流电机

直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转

直流电机属于大功率器件GPIO口无法直接驱动,需要配合电机驱动电路来操作

TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

image-20221128210854526 image-20221128211613707

实例二、PWM驱动呼吸灯&舵机&直流电机代码实现

2.1、PWM驱动LED呼吸灯

**功能实现:**LED实现呼吸效果

**注意:**这里用过改变CCR的值来改变占空,比达到呼吸效果;但是占空比的值是有CCR和ARR+1共同决定的

PWM.c

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
#include "stm32f10x.h"                  // Device header

void PWM_Init(void){

//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
//不论是否使用,都先赋一个初值,避免出现奇奇怪怪的错误。赋值后再更改需要的参数即可
TIM_OCStructInit(&TIM_OCInitStructure);//结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H__
#define __PWM_H__

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);

#endif

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "PWM.h"
#include "OLED.h"

uint8_t i;

int main(){
OLED_Init();
PWM_Init();
while(1){
for(i = 0; i< 100; i++){
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i = 0; i< 100; i++){
PWM_SetCompare1(100-i);
Delay_ms(10);
}
}
}



Delay.c

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
#include "stm32f10x.h"

/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}

/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}

/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}

Delay.h

1
2
3
4
5
6
7
8
9
#ifndef __DELAY_H
#define __DELAY_H

void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

拓展:keil5自带示波器使用

1.点击魔术棒

image-20221128235705370

2.进入Debug

先设置为Use Simulator,然后去设置Dialog DLL和其后面的Parameter.

其中将Dialog DLL设置为:DARMSTM.DLL

Parameter设置为-p单片机型号

image-20221128235745302

3.进入调试模式,调出示波器

image-20221129000242745

4.点击Setup设置仿真端口

在里面加入要查看的端口,格式为PORTX.Y【X为A,B,C…;Y为1,2,3,…】

image-20221129000321043

image-20221129000506238 image-20221129000615129

5.查看仿真效果

image-20221129000647017

2.2、PWM驱动舵机

**功能实现:**通过按键控制舵机旋转角度

PWM.c

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
#include "stm32f10x.h"                  // Device header

void PWM_Init(void){

//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
//不论是否使用,都先赋一个初值,避免出现奇奇怪怪的错误。赋值后再更改需要的参数即可
TIM_OCStructInit(&TIM_OCInitStructure);//结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H__
#define __PWM_H__

void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);

#endif

Servo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void)
{
PWM_Init();
}

void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}

Servo.h

1
2
3
4
5
6
7
8
#ifndef __SERVO_H
#define __SERVO_H

void Servo_Init(void);
void Servo_SetAngle(float Angle);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Servo.h"
#include "OLED.h"
#include "Key.h"


uint8_t KeyNum;
float Angle;

int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();

OLED_ShowString(1, 1, "Angle:");

while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Angle += 30;
if (Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(180);
OLED_ShowNum(1, 7, Angle, 3);
}
}

Delay.c

Delay.h

2.3、PWM驱动直流电机

**功能实现:**通过按键控制舵机旋转速度

PWM.c

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
#include "stm32f10x.h"                  // Device header

void PWM_Init(void){

//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
//不论是否使用,都先赋一个初值,避免出现奇奇怪怪的错误。赋值后再更改需要的参数即可
TIM_OCStructInit(&TIM_OCInitStructure);//结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H__
#define __PWM_H__

void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);

#endif

Motor.c

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
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

PWM_Init();
}

void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
//设置速度
PWM_SetCompare3(Speed);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
//设置速度
PWM_SetCompare3(-Speed);
}
}

Motor.h

1
2
3
4
5
6
7
8
#ifndef __MOTOR_H
#define __MOTOR_H

void Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Motor.h"
#include "OLED.h"
#include "Key.h"


uint8_t KeyNum;
int8_t Speed;

int main(void)
{
OLED_Init();
Motor_Init();
Key_Init();

OLED_ShowString(1, 1, "Speed:");

while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Speed += 20;
if (Speed > 100)
{
Speed = -100;
}
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(1, 7, Speed, 3);
}
}

Delay.c

Delay.h

七、TIM输入捕获

IC(Input Capture)输入捕获

输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数

每个高级定时器和通用定时器都拥有4个输入捕获通道

可配置为PWMI模式,同时测量频率和占空比

可配合主从触发模式,实现硬件全自动测量

7.1频率测量

测频法:在闸门时间T内,对上升沿计次,得到N,则频率【f~x~=N / T】

测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率【f~x~=f~c~/ N】

中界频率:测频法与测周法误差相等的频率点【f~m~=√(f~c~/ T)】

image-20221201160008905

7.2输入捕获通道

image-20221201161146355 image-20221201164341495

7.3主从触发模式

image-20221201164809908

7.4两种捕获方式基本结构

7.4.1输入捕获基本结构

触发源选择只有TI1和TI2,没有TI3和TI4,如果使用从模式自动清零CNT只能使用通道1和通道2

如果使用通道3和通道4只能开启捕获中断,手动清零

image-20221201165935357

7.4.2PWMI基本结构

image-20221201170120062

实例三、输入捕获模式测频率&PWMI模式测频率占空比

3.1、输入捕获模式测频率

功能实现:PA0口输出,PA6口测量

IC.c

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
#include "stm32f10x.h"                  // Device header

void IC_Init(void)
{
//开启时钟,TIM3是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM3);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
//配置捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择分频器
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStructure);

//配置TRGI的触发源
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

//配置从模式
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

//启动定时器
TIM_Cmd(TIM3,ENABLE);
}

uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

IC.h

1
2
3
4
5
6
7
#ifndef __IC_H__
#define __IC_H__

void IC_Init(void);
uint32_t IC_GetFreq(void);
#endif

PWM.c

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
#include "stm32f10x.h"                  // Device header

void PWM_Init(void){

//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
//不论是否使用,都先赋一个初值,避免出现奇奇怪怪的错误。赋值后再更改需要的参数即可
TIM_OCStructInit(&TIM_OCInitStructure);//结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}

void PWM_SetCompare1(uint16_t Compare)//改变占空比
{
TIM_SetCompare1(TIM2, Compare);
}

void PWM_SetPrescaler(uint16_t Prescaler)//改变频率
{
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Update);
}


PWM.h

1
2
3
4
5
6
7
8
9
#ifndef __PWM_H__
#define __PWM_H__

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);

#endif

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "PWM.h"
#include "OLED.h"
#include "IC.h"

uint8_t i;

int main(){
OLED_Init();
PWM_Init();
IC_Init();

OLED_ShowString(1,1,"Freq:00000HZ");
PWM_SetCompare1(50);
PWM_SetPrescaler(720-1);

while(1){
OLED_ShowNum(1,6,IC_GetFreq(),5);
}
}

OLED.c

OLED.h

3.2、PWMI模式测量占空比

功能实现:PA0口输出,PA6口测量,双通道分别测量频率和占空比

注意:可以使用TIM_PWMIConfig函数直接配置另一个通道

IC.c

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
#include "stm32f10x.h"                  // Device header

void IC_Init(void)
{
//开启时钟,TIM3是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM3);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择分频器
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

//配置另一个通道的参数
TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);

TIM_ICInit(TIM3,&TIM_ICInitStructure);

//配置TRGI的触发源
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

//配置从模式
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

//启动定时器
TIM_Cmd(TIM3,ENABLE);
}

uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

IC.h

1
2
3
4
5
6
7
8
#ifndef __IC_H__
#define __IC_H__

void IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);
#endif

PWM.c

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
#include "stm32f10x.h"                  // Device header

void PWM_Init(void){

//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
//不论是否使用,都先赋一个初值,避免出现奇奇怪怪的错误。赋值后再更改需要的参数即可
TIM_OCStructInit(&TIM_OCInitStructure);//结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}

void PWM_SetCompare1(uint16_t Compare)//改变占空比
{
TIM_SetCompare1(TIM2, Compare);
}

void PWM_SetPrescaler(uint16_t Prescaler)//改变频率
{
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Update);
}


PWM.h

1
2
3
4
5
6
7
8
9
#ifndef __PWM_H__
#define __PWM_H__

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "PWM.h"
#include "OLED.h"
#include "IC.h"

uint8_t i;

int main(){
OLED_Init();
PWM_Init();
IC_Init();

OLED_ShowString(1,1,"Freq:00000HZ");
OLED_ShowString(2,1,"Duty:00%");
PWM_SetPrescaler(7200-1);
PWM_SetCompare1(90);


while(1){
OLED_ShowNum(1,6,IC_GetFreq(),5);
OLED_ShowNum(2,6,IC_GetDuty(),2);
}
}

OLED.c

OLED.h

八、TIM编码器接口

Encoder Interface 编码器接口

编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度

每个高级定时器和通用定时器都拥有1个编码器接口

两个输入引脚借用了输入捕获的通道1和通道2

8.1正交编码器

正交信号:正转和反转的位相相差90°

正交信号的优势:精度高、可以抗噪声

image-20221205152215829

8.2编码器接口基本结构

参考手册框图:

image-20221205152823861

简化原理图:

image-20221205154038636

8.3工作模式

image-20221205154147591

实例四编码器接口测速

Encoder.c

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
#include "stm32f10x.h"                  // Device header

void Encoder_Init(void){
//开启时钟,TIM3是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//编码器接口是一个带方向控制的外部时钟,所以内部时钟不需要

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
//TIM_InternalClockConfig(TIM3);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//ARR自动重装器的值,满量程,容易转换成负数
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//PSC预分频器的值,不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);

//配置捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;

//结构体配置不完整,所以需要默认初始化
//通道1
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获滤波器

//后面仍会配置极性,后面的会覆盖前面的,可以删去
//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,上升沿,不反向
//这两项编码器用不到,可以删去,但是删去后结构体配置不完整,需要初始化一下
//TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择分频器
//TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

TIM_ICInit(TIM3,&TIM_ICInitStructure);

//配置通道2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获滤波器
TIM_ICInit(TIM3,&TIM_ICInitStructure);

//配置编码器接口
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

TIM_Cmd(TIM3,ENABLE);
}

int16_t Encoder_Get(void){
int16_t temp;
temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return temp;
}

Encoder.h

1
2
3
4
5
6
7
8
#ifndef __ENCODER_H__
#define __ENCODER_H__

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

Timer.c

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
#include "stm32f10x.h"                  // Device header

void Timer_Init(void){
//开启时钟,TIM2是APB1的时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
TIM_InternalClockConfig(TIM2);

//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

//手动清除中断标志位,避免刚初始化完就进入中断
TIM_ClearFlag(TIM2,TIM_IT_Update);

//使能中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

//启动定时器
TIM_Cmd(TIM2,ENABLE);

}

Timer.h

1
2
3
4
5
6
7
#ifndef __TIMER_H__
#define __TIMER_H__

void Timer_Init(void);

#endif

main.c

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
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

int16_t speed;

int main(){
OLED_Init();
Timer_Init();
Encoder_Init();

OLED_ShowString(1,1,"SPEED:");
while(1){
//OLED_ShowNum(1,5,num,5);
OLED_ShowSignedNum(2,1,speed,5);
}
}

void TIM2_IRQHandler(void){
//判断中断
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
speed = Encoder_Get();
//清除中断标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}

OLED.c

OLED.h