0%

ADC简介

•ADC(Analog-Digital Converter)模拟-数字转换器
•ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁(目前DAC主要应用在波形生成的领域,比如信号发生器、音频解码芯片等)
•12位($ 0-2^{12} $)逐次逼近型ADC,1us转换时间(位数越高,量化结果越精细,分辨率越高)
•输入电压范围:0-3.3V,转换结果范围:0-4095(中间都是一一对应的线性关系)
•18个输入通道,可测量16个外部(16个GPIO口)和2个内部信号源(内部温度传感器、内部参考电压)
•规则组和注入组两个转换单元
•模拟看门狗自动监测输入电压范围(高于或低于某个阈值,执行某些操作,由模拟看门狗自动执行)

•STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(10个外部引脚)

逐次逼近型ADC

逐次逼近型ADC

此图描述ADC的内部结构。

左侧IN0-IN7为8路输入通道,通过通道选择开关,选中一路,进行转换。

地址锁存和译码,就是把想选中的通道号,放在ADDA、ADDB、ADDC这三个引脚上,然后用ALE给锁存信号,上面的通道选择开关就自动拨好了。

ADC的转换很快,只需要几us就可以转换完成,如果想转换多路信号,不需要设计多个AD转换器,只需要一个AD转换器,然后加上一个多路选择开关,想转换哪一路,只需要选中对应的开关即可。

比较器可以比较两个电压的大小关系,输入端在左侧,一个是待测电压,一个是DAC的电压输出端(DAC是数模转换器,给它一个数据,可以输出对应的电压)DAC输出的是已知的电压,同时输入到比较器进行比较,如果DAC输出的比较大,就调小DAC数据,直到DAC输出的电压和外部通道电压近似相等,这样DAC输入的数据就是外部电压的编码数据。通过SAR完成。

通过三态锁存缓冲器进行输出,EOC是转换结束信号,START是开始信号,CLOCK为时钟。
VREF+和VREF-为DAC的参考电压。

ADC框图

ADC框图
左侧中间为ADC输入通道,对应16个GPIO口,IN0-IN15,还有2个内部通道,一个是温度传感器,另一个是VREFINT(内部参考电压)

到达模拟多种开关,可以指定想要选择的通道,右侧是多路开关输出,进入模数转换器,执行逐次比较的过程,转换结果放在数据寄存器中,读取寄存器,就知道AD转换结果。

对于普通的ADC,多路开关一般只选中一个,但是这里可以同时选择多个,在转换时还可以分成两个组,规则通道组和注入通道组。其中规则组一次最多选中16个通道,注入组最多选中4个通道。

对于规则组转换,最好配合DMA使用,可以防止覆盖。

左下角为触发转换的部分,开始信号。

对于STM32 ,触发ADC转换的方式有两种
1.软件触发:在程序中调用一段代码,启动转换
2.硬件触发:定时器的通道、TRGO定时器主模式输出等

VREF+和VREF-是ADC的参考电压,决定了ADC输入电压的范围。
VDDA和VSSA是ADC的供电引脚。
一般VREF+接VDDA,VREF-接VSSA。(在STM32内部已经接在一起)

ADC预分频器来自于RCC。ADCCLK最大是14MHZ,只能选择6分频、8分频,结果是12M和9M

DMA请求用于触发DMA进行数据转运。

注入通道数据寄存器(4x16位)和规则通道数据寄存器(16位),用于存放转换结果。

模拟看门狗,存放一个阈值高限和阈值低限,启用看门狗并指定通道,一旦这个通道超过阈值范围,会申请模拟看门狗中断,通向NVIC。

对于规则组和注入组,转换完成之后,也会有EOC转换完成的信号。

ADC基本结构

ADC基本结构

输入通道

通道ADC1ADC2ADC3
通道0PA0PA0PA0
通道1PA1PA1PA1
通道2PA2PA2PA2
通道3PA3PA3PA3
通道4PA4PA4PF6
通道5PA5PA5PF7
通道6PA6PA6PF8
通道7PA7PA7PF9
通道8PB0PB0PF10
通道9PB1PB1
通道10PC0PC0PC0
通道11PC1PC1PC1
通道12PC2PC2PC2
通道13PC3PC3PC3
通道14PC4PC4
通道15PC5PC5
通道16温度传感器
通道17内部参考电压
ADC通道和引脚复用的关系。

规则组的四种转换模式

在ADC初始化的结构体中,有2个参数,转换方式和扫描方式,组成4种组合。

单次转换,非扫描模式

单次转换,非扫描模式
写入要转换的通道,只有序列1的位置有效。
在序列1的位置指定选中的通道,比如通道2,然后触发转换,ADC会对通道2进行模数转换,转换完成后放在数据寄存器里,并且EOC标志位置1。判断EOC标志位,转换完了可以在数据寄存器中读取结果。
想再次转换,需要再次触发。想换一个通道转换,在转换之前,把第一个位置通道2改为其他通道,再启动。

连续转换,非扫描模式

连续转换,非扫描模式
只有序列1的位置有效,在一次转换后不会停止,而是立刻开始下一轮,持续下去。
不需要手动转换,也不需要判断EOC值,想要读AD值的时候,直接从数据寄存器读取。

单次转换,扫描模式

单次转换,扫描模式
单词转换,每触发一次,转换结束后会停下来。
每个位置的通道可以任意选定,且可以重复。
初始化时要给通道数目参数,比如指定通道数目为7,只转换前7个。

为了防止数据被覆盖,需要应用DMA及时把数据移走。

7个通道转换完成后,产生EOC信号,转换结束。

连续转换,扫描模式

连续转换,扫描模式
在上一个模式的基础上,一次转换完成后,立刻开始下一次转换。

触发控制

触发控制
规则组的触发源。

数据对其

数据右对齐
数据右对齐
数据左对齐
数据左对齐

STM32的ADC是12位的,但是数据寄存器是16位的,存在数据对其的问题。

一般使用:右对齐,就是12位的数据向右靠,高位多出补0。得到的数据和实际的一样。
左对齐,就是12位的数据向左靠,低位多出补0。得到的数据比实际的大。要除以16。可以舍弃精度。

转换时间

AD转换的步骤:采样,保持,量化,编码

STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5个ADC周期

例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期:TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs

校准

•ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

•建议在每次上电后执行一次校准

•启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期

硬件电路

硬件电路

常用函数

1
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

定时器编码器接口配置

编码器接口测速

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

void Encoder_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使用TIM3的通道1,引脚是PA6
//配置GPIO
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_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,这个值决定测周法的标准频率fc,根据信号频率分布范围调整
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
//初始化输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性选择,代表不反向
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性选择,代表不反向
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;
}


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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
uint16_t Num;
int16_t Speed;
int main(void)
{
OLED_Init();
Timer_Init();
Encoder_Init();

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

while(1)
{
OLED_ShowSignedNum(1,5,Speed,5);
}
}

void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Speed = Encoder_Get();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}

编码器接口简介

•Encoder Interface 编码器接口
•编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
•每个高级定时器和通用定时器都拥有1个编码器接口
•两个输入引脚(定时器的CH1、CH2引脚,CH3、CH4不能接编码器)借用了输入捕获的通道1和通道2

正交编码器

正交编码器
编码器旋转轴旋转,A相和B相会输出方波信号。
转的越快,方波信号的频率越高,因此方波频率代表速度
取任意一相的信号,可以得到旋转速度,但是只有一相,无法得知正转还是反转。
正转时:A相提前B相90度。
反转时,B相提前A相90度。

使用正交信号,相比于单独的方向引脚的好处:

  1. 正交信号精度更高:因为A、B相都可以计次,相当于计次频率提高了一倍。
  2. 正交信号可以抗噪声:正交信号,两个信号是交替跳变的,可以设计一个抗噪声电路,例如一个信号一直跳变,但另一个信号不变,认为是产生了噪声,计次忽略。

编码器接口基本结构

编码器接口基本结构
输入捕获通道,通过GPIO口接入编码器A、B相。
通过滤波器和边沿检测极性选择,产生TI1FP1和TI2FP2,通向编码器接口。
编码器接口通过预分频器控制CNT计数器的时钟,同时,编码器接口还根据编码器的旋转方向,控制CNT计数器的计数方向。编码器正转时,CNT自增。反转时,CNT自减。
一般设置ARR为最大值65535,利用补码的特性,容易得到复数。

工作模式

工作模式

实例(均不反相)

实例(均不反相)

实例(TI1反相)

实例(TI1反相)

常用函数

1
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

用结构体配置输入捕获单元的函数。
ICInit的4个通道,共用一个函数,这一点和OCInit不同。

1
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

初始化输入捕获单元,可以配置两个通道。
把外设电路结构配置成PWMI模式。

1
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

给输入捕获结构体赋一个初始值。

1
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

选择输入触发源TRGI。从模式的触发源选择。

1
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

选择输出触发源TRGO,选择主模式输出的触发源。

1
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

选择从模式。

1
2
3
4
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);

分别单独配置通道1、2、3、4的分频器

1
2
3
4
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

分别读取4个通道的CCR。输入捕获模式下,CCR是只读的,必须使用GetCapture读出。

输入捕获模式测频率

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
#include "stm32f10x.h"                  // Device header
void IC_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使用TIM3的通道1,引脚是PA6
//配置GPIO
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);//内部时钟,计时器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,这个值决定测周法的标准频率fc,根据信号频率分布范围调整
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);

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);
}


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

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

OLED_ShowString(1,1,"Freq:00000Hz");

PWM_SetPrescaler(720 - 1); //FREQ = 72M / (PSC + 1) / 100
PWM_SetCompare1(50);//Duty = CCR/100
while(1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);
}
}


PWMI模式测占空比

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 IC_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
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);

TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//这个函数,只要传入一个通道的参数,会自动把剩下的通道初始化成相反的配置

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);
}

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

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

OLED_ShowString(1,1,"Freq:00000Hz");
OLED_ShowString(2,1,"Duty:00%");

PWM_SetPrescaler(720 - 1); //FREQ = 72M / (PSC + 1) / 100
PWM_SetCompare1(50);//Duty = CCR/100
while(1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);
OLED_ShowNum(2,6,IC_GetDuty(),2);
}
}

输入捕获简介

•IC(Input Capture)输入捕获
•输入捕获模式下,当通道输入引脚出现指定电平跳变时 (上升沿、下降沿) ,当前CNT的值将被锁存到CCR中 (把CNT的值到CCR写入) ,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
•每个高级定时器和通用定时器都拥有4个输入捕获通道(基本定时器没有输入捕获功能)
•可配置为PWMI模式(PWM输入模式),同时测量频率和占空比
•可配合主从触发模式,实现硬件全自动测量

频率测量

频率测量

•测频法:图左侧,在闸门时间T内(通常为1S),对上升沿计次,得到N,则频率$f_x=\frac{N}{T}$
即1S内出现了多少个周期。

•测周法:图右侧,两个上升沿内,以标准频率fc计次,得到N ,则频率$f_x=\frac{f_c}{N}$
一个周期用了多长时间。

测频法要求,信号频率最好高一些;测轴法的信号频率最好低些。这样更准确。
使用什么方法取决于中界频率。

•中界频率:图中间,测频法与测周法误差相等的频率点$f_m=\sqrt{(\frac{f_c}{T})}$
频率大于中界频率,选择测频法。小于中界频率,选择测周法。

输入捕获通道

输入捕获通道

主从触发模式

主从触发模式
主模式:将定时器内部信号,映射到TRGO引脚,触发别的外设。
从模式:接受其他外设或者自身外设的信号,控制自身定时器的运行,被别的信号控制。
触发源选择:选择从模式的触发信号源。

输入捕获基本结构

输入捕获基本结构

PWMI基本结构

PWMI基本结构

1
2
3
4
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

配置输出比较模块,OC意为Output Compare。
有四个输出比较单元,对应四个函数。

1
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

用来给输出比较结构体赋默认值。可以不用手动设置所有的TIM结构体参数。

1
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

使用高级定时器输出PWM时,需要使能主输出,否则PWM将不能正常输出(仅高级定时器)

1
2
3
4
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);

配置强制输出模式,暂停输出波形并且强制输出高电平或者低电平。

1
2
3
4
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

配置CCR寄存器的预装功能(影子寄存器),即写入的值不会立即生效,而是在更新事件后生效。

1
2
3
4
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);

配置快速使能。

1
2
3
4
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);

外部事件时清除REF信号。

1
2
3
4
5
6
7
8
9
10
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);

void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);

void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);

void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);

单独设置输出比较极性,NPolarity是高级定时器里互补通道的配置。OC4没有互补通道,所以没有OC4N的函数

1
2
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);

单独修改输出使能的。

1
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

选择输出比较模式,单独修改输出比较模式。

1
2
3
4
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

单独更改CCR寄存器值,在运行时修改占空比需要用到。

PWM驱动LED呼吸灯

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

void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使用引脚重映射,需要先使能AFIO
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//引脚重映射,把PA0映射到PA15
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,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;//设置OC比较的模式
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)//运行时修改CCR的值
{
TIM_SetCompare1(TIM2,Compare);
}

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();//PWM初始化
while (1)
{
for(i = 1 ;i <= 100 ; i++)//CCR的值从1到100变化,再从100到1,LED的占空比不断变化,亮度也不断变化。
{
PWM_SetCompare1(i);
Delay_ms(20);
}
for(i = 1 ;i <= 100 ; i++)
{
PWM_SetCompare1(100 - i);
Delay_ms(20);
}
}
}

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

void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
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;//设置OC比较的模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//设置输出比较的极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0;//设置CCR,默认先设置成0,之后再改
TIM_OC2Init(TIM2,&TIM_OCInitStructure);

TIM_Cmd(TIM2,ENABLE);

}

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

Servo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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);
}

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "key.h"
#include "Servo.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(Angle);
OLED_ShowNum(1,7,Angle,3);
}
}

PWM驱动直流电机

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
#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)//输入的Speed>=0
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);//置位PA4,方向
GPIO_ResetBits(GPIOA,GPIO_Pin_5);//复位PA5,对应方向
PWM_SetCompare3(Speed);//修改CCR
}
else//Speed<0
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
PWM_SetCompare3(-Speed);//Speed为负数,取反
}
}

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

void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
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 = 36 - 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;
TIM_OC3Init(TIM2,&TIM_OCInitStructure);


TIM_Cmd(TIM2,ENABLE);

}

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

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

uint8_t KeyNum;
int8_t Speed;
int main(void)
{
OLED_Init();
Motor_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);
}
}
}


主要用来输出PWM波形。

输出比较简介

•OC(Output Compare)输出比较
•输出比较可以通过比较CNT(计数器)与CCR(捕获/比较寄存器,输入捕获和输出比较共用)寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
•每个高级定时器和通用定时器都拥有4个输出比较通道
•高级定时器的前3个通道额外拥有死区生成和互补输出的功能

PWM简介

•PWM(Pulse Width Modulation)脉冲宽度调制
•在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
•PWM参数:
频率 = 1 / TS           
占空比 = TON / TS          
分辨率 = 占空比变化步距
PWM
占空比越大,等效电压越趋近于高电平;占空比越小,等效电压越趋近于低电平。

输出比较通道(通用)

输出比较通道(通用)

左侧是CNT计数器和CCR1第一路捕获/比较寄存器,进行比较。
当CNT>CCR1或者CNT=CCR1时,就会给输出模式控制器传一个信号,然后输出模式控制器会改变它的输出OC1REF的高低电平(参考信号的高低电平)。
REF信号可以前往主模式控制器,可以映射到主模式的TRGO输出上去。
也可以到达极性选择,选择是否把高低电平反转,到输出使能电路,选择要不要输出。

输出比较通道(高级)

输出比较通道(高级)

输出比较模式

模式描述
冻结CNT=CCR时,REF保持为原状态
匹配时置有效电平CNT=CCR时,REF置有效电平(置高电平)
匹配时置无效电平CNT=CCR时,REF置无效电平(置低电平)
匹配时电平翻转CNT=CCR时,REF电平翻转,可以输出频率可调,占空比为50%的PWM
强制为无效电平CNT与CCR无效,REF强制为无效电平(置低电平),暂停波形输出,暂停期间保持低电平
强制为有效电平CNT与CCR无效,REF强制为有效电平(置高电平),暂停波形输出,暂停期间保持高电平
PWM模式1向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平

向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平
PWM模式2向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平

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

PWM的基本结构

PWM的基本结构

参数计算

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

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

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

常用TIM函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void TIM_DeInit(TIM_TypeDef* TIMx);//恢复缺省配置
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//时基单元初始化,配置时基单元
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//把结构体变量赋默认值
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//使能计数器
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//使能中断输出信号
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);//选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择ITRx其他定时器的时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择TIX捕获通道的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//配置ETR引脚的预分频器、极性、滤波器参数
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);//单独写预分频值
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//改变计数器的计数模式
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);//自动重装器预装功能配置
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//给计数器写一个值,手动给一个计数值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//给自动重装器一个值,手动给一个自动重装值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);//获取当前计数器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//获取当前预分频器的值
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);//获取标志位
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);//清除标志位
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//获取标志位,中断程序使用
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//清除标志位,中断程序使用

定时器定时中断

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
#include "stm32f10x.h"                  // Device header
extern uint16_t Num;//声明对应main里的Num
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启时钟,TIM2是APB1总线的外设
TIM_InternalClockConfig(TIM2);//选择为内部时钟,选择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预分频器的值,要参考6-1TIM定时中断计算
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//结构体成员:重复计数器的值
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//完成配置时基单元

TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断,更新中断

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组,选择分组2
NVIC_InitTypeDef NVIC_InitStructure;//NVIC结构体
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);//完成配置NVIC

TIM_Cmd(TIM2,ENABLE);//使能通道
}

void TIM2_IRQHandler(void)//TIM2的中断函数
{
if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//检查标志位
{
Num ++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
}
}

main.c

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

uint16_t Num;//定义一个16位的全局变量Num
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num:");

while(1)
{
OLED_ShowNum(1,5,Num,5);
OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);//获取TIM2当前计数器的值
}

}


定时器外部时钟

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
extern uint16_t Num;
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA
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);//通过ETR引脚的外部时钟模式2配置

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_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

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)
{
Num ++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}

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

main.c

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

uint16_t Num;
int main(void)
{
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_GetCounter(),5);//计数器的值
}
}

TIM简介

•TIM(Timer)定时器
•定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
•16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
•不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
•根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

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

•STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
不同的型号,定时器的数量一般是不同的。操作一个外设之前,要查询是否有这个外设。

基本定时器

基本定时器结构图
预分频器、自动重装载寄存器、CNT计数器共同构成了基本的计数计时单元,这块电路叫时基单元。
预分频器之前,连接的是基准计数时钟的输入,来到控制器。
由于基本定时器只能选择内部时钟,所以可以认为直接连到内部时钟CK_INT,内部时钟来自RCC的TIMxCLK,这里的频率值一般是系统的主频72MHz。
因此通向时基单元的计数基准频率为72MHz

预分频器对72MHz的计数时钟进行分频,比如预分频器寄存器写0,就是不分频,或者是1分频。此时输入频率=输出频率=72MHz。
如果预分频器写1,那就是2分频,输出频率=输入频率/2=36MHz。
写2,为3分频,输出频率=输入频率/3=24MHz。
(预分频器的值和实际的分频系数差1,实际分频系数=预分频器+1,预分频器是16位的,最大值为65535,也就是65536分频)

计数器对分频后的计数时钟进行计数,计数时钟每有一个上升沿,计数器的值+1,最大值是65535。超过65535,变为0。

自动重装寄存器存的是写入的计数目标。
在运行时,计数值不断自增,自动重装值是固定的目标,当计数值和自动重装值相等时,代表时间到了。
会产生中断信号,并且清零计数器。

主模式触发DAC模式,可以让内部硬件在不受到程序控制的情况下自动运行(可以减轻CPU负担)。
在使用DAC时,会用DAC输出一段波形,每隔一段时间触发依次DAC,让它输出下一个电压点。(定时器的更新不需要通过中断来触发DAC转换,只需要把更新时间通过主模式映射到TRGO,再通过TRGO触发DAC。无需软件参与)

基本定时器计时,只能向上计数。

通用定时器

基本定时器
通用定时器框图中,中间的部分和基本定时器是一样的。

通用定时器和高级定时器除了向上计数,还支持向下计数和中央对其模式。
(向下计数模式,就是从重装值开始,向下自减,直到0,回到重装值并且申请中断)
(中央对其模式,就是从0开始,向上自增,计数到重装值,申请中断,然后再向下自减,减到0,再申请中断)
通常使用向上计数模式,其他模式使用少。

上面部分是内外时钟选择和主从触发模式的结构。
对于基本定时器,定时只能选择系统内部时钟,即系统频率72MHz。
通用定时器还可以选择外部时钟。
第一个外部时钟是来自TIMx_ETR引脚上的外部时钟,即在PA0上接一个外部方波时钟,然后配置内部的极性选择、边沿检测和预分频电路,在配置输入滤波电路,可以对外部时钟进行整形。
滤波后的信号,分为2路,一路去ETRF进入触发控制器,可以选择作为时基单元的时钟。这一路叫外部时钟模式2。另一路去TRGI,用作触发输入来使用,可以触发定时器的从模式,叫做外部时钟模式1。

ITR信号的时钟信号来自其他定时器,实现定时器级联。

TI1F_ED,连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,上升沿和下降沿都有效,这个时钟还能通过TI1FP1和TI2FP2来获得。

对于时钟,通常使用内部的72MHz时钟,如果要使用外部时钟,首选ETR引脚外部时钟模式2的输入。

右下角可以用于输出PWM波形,驱动电机,左侧是输入捕获电路,有4个通道,对应CH1到CH4引脚。可以用于测输入方波的频率。
中间的捕获/比较寄存器,是输入捕获和输出比较电路公用的,输入捕获和输出比较不能同时使用,因此寄存器共用,引脚共用。

高级定时器

高级定时器
增加重复次数计数器,可以实现每隔几个计数周期,才发生一次更新时间和更新中断。(相当于对输出的更新信号做一次分频)

定时中断基本结构

定时中断基本结构

预分频器时序

预分频器时序
•计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计数器时序

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

计数器无预装时序

计数器无预装时序

计数器有预装时序

计数器有预装时序

对射式红外传感器计次

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

uint16_t CountSensor_Count;

void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO外设
//EXTI和NVIC外设为默认开启,无需手动开启
//配置GPIO外设
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入,默认高电平
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置AFIO外设
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//整个芯片只执行一次,即整个工程只运行一次此代码
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}

uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}

void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
CountSensor_Count++;//触发中断,加1
EXTI_ClearITPendingBit(EXTI_Line14);//清除中断
}
}

mian.c

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


int main(void)
{
OLED_Init();//初始化OLED
CountSensor_Init();//初始化传感器
OLED_ShowString(1,1,"Count:");

while(1)
{
OLED_ShowNum(1,7,CountSensor_Get(),5);//调用CountSensor_Get()函数,得到计次值
}
}

旋转编码器计次

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
65
66
67
68
69
70
71
72
73
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count;

void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO外设
//EXTI和NVIC外设为默认开启
//配置GPIO外设
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入,默认高电平
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置AFIO外设
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);

EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//整个芯片只执行一次,即整个工程只运行一次此代码
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}

int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}

void EXTI0_IRQHandler(void)//反转
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}

void EXTI1_IRQHandler(void)//正传
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}