0%

DMA简介

•DMA(Direct Memory Access)直接存储器存取(DMA可以直接读取STM32内部存储器,包括运行内存SRAM、程序存储器Flash、寄存器等)
•DMA可以提供外设(外设的数据寄存器)和存储器(运行内容SRAM和程序存储器Flash)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
•12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
•每个通道都支持软件触发和特定的硬件触发(存储器到存储器的转运,一般使用软件触发;外设到存储器之间的数据转运,一般使用硬件触发)
•STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

类型起始地址存储器用途
ROM0x0800 0000程序存储器Flash存储C语言编译后的程序代码
0x1FFF F000系统存储器存储BootLoader,用于串口下载
0x1FFF F800选项字节存储一些独立于程序代码的配置参数
RAM0x2000 0000运行内存SRAM存储运行过程中的临时变量
0x4000 0000外设寄存器存储各个外设的配置参数
0xE000 0000内核外设寄存器存储内核各个外设的配置参数

DMA框图

DMA框图

上图,左上角魏Cortex-M3内核,包含CPU和内核外设。
剩下的所有东西,都可以看作是存储器。
Flash是主闪存,SRAM是运行内存,各个外设,都可以看作是寄存器,也是SRAM寄存器。

DMA基本结构

DMA基本结构
DMA的数据转运,可以从外设到存储器,也可以从存储器到外设,具体的方向,使用方向参数进行控制。
还有一直存储器到存储器的转运方式,比如Flash到SRAM或者SRAM到SRAM。由于Flash只读,因此不能进行Flash到Flash或者SRAM到Flash的操作。

传输计数器,用来指定要转运几次。是自减计数器。

自动重装器的作用:当传输计数器减到0之后,是否恢复到最初的值。

DMA的触发源:包括硬件触发和软件触发。具体选择哪个,由M2M这个参数决定。
软件触发:代表连续触发。不能和循环模式一起使用。
硬件触发:触发源可以选择ADC、串口、定时器等等,一般都是与外设有关的转运,这种转运一般需要特定的时机,比如ADC转换完成、串口收到数据、定时时间到等等。

DMA转运的几个条件:
1.开关控制,DMA_Cmd必须使能
2.传输计数器必须大于0
3.触发源,必须有触发信号。触发一次,转运一次,传输计数器自减一次,当传输计数器等于0,且没有自动重装时,这时无论是否触发,DMA都不在转运了。这时需要把DMA_Cmd给Disable,关闭DMA,再给传输计数器写大于0的数字,再DMA_Cmd给Enable,开启DMA。(这里注意:写传输计数器时,必须关闭DMA,再写)

DMA请求

DMA请求
上图是DMA1的请求映像,包括DMA1的7个通道。
每路都有一个选择器,可以选择硬件触发或者软件触发。
EN控制数据选择器是否工作。0不工作,1工作。
左侧是硬件触发源,是外设请求信号,可以看到每个信号的硬件出发源都是不同的。例如:需要ADC1触发,必须选择通道1。(硬件触发是这样,如果是软件触发,就任意选择了)

这7个触发源,进入仲裁器,进行优先级判断,产生最终的DMA1请求。
优先级判断,默认是通道号越小,优先级越高,也可在程序中自行设置。

数据宽度与对齐

数据宽度与对齐
如果外设和存储器的数据宽度一致,那就是正常的一个个转运。
如果数据宽度不一样,根据上表处理。

数据转运+DMA

数据转运+DMA
将SRAM里的数组A转运到另一个数组B中。

配置外设站点和存储器站点的起始地址、数据宽度、地址是否自增三个参数。
本例中:外设地址写数组A的首地址,存储器地址写数组B的首地址。
数据宽度,都设为8。
地址是否自增,由于需要DataA[0]转运到DataB[0]、DataA[1]转运到DataB[1]等,一一对应,因此都需要自增。

方向参数,设置为外设站点到存储器站点。(如果想从DataB到DataA,需要反过来)

需要转运7次,因此传输计数器给7,自动重装暂时不设置。

触发选择部分,选择软件触发。

最后,调用DMA_Cmd,给DMA使能。

ADC扫描模式+DMA

ADC扫描模式+DMA
左侧是ADC扫描模式的执行流程,在这里有7个通道,触发一次后,7个通道依次进行AD转换,转换结果都放在ADC_DR数据寄存器里面。
在每个单独的通道转换完成之后,应该进行DMA数据转运,且对目的地址进行自增,这样数据不会被覆盖。
因此,DMA配置为:
1.外设地址,写ADC_DR这个寄存器的地址。
2.存储器的地址,可以在SRAM中定义一个数组ADValue
3.数据宽度,16位
4.地址自增,外设地址不自增,存储器地址自增。
5.传输方向,外设站点到存储器站点
6.传输计数器,通道有7个,因此计数7次。
7.计数器是否自动重装,看ADC的配置,ADC如果是单次扫描,DMA的传输计数器可以不自动重装,转换一轮就停止。如果ADC是连续扫描的,那DMA可以使用自动重装,ADC启动下一轮时,DMA也启动下一轮转运,ADC和DMA同步工作。
8.触发选择,ADC硬件触发。

常用函数

1
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

配置ADCCLK分频器,可以对APB2的72MHz选择2、4、6、8分频,输入到ADCCLK。

1
void ADC_DeInit(ADC_TypeDef* ADCx);

恢复缺省配置

1
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

初始化

1
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);

结构体初始化

1
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC上电,开关控制。

1
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

开启DMA输出信号,如果使用DMA转运数据,调用此函数。

1
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

中断输出控制,控制某个中断,通往NVIC。

1
2
3
4
void ADC_ResetCalibration(ADC_TypeDef* ADCx);//复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);//获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);//开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);//获取开始校准状态

在ADC初始化完成后,依次调用完成校准。

1
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC软件开始转换控制,用于软件触发函数,软件触发转换

1
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

ADC获取软件开始转换状态,给SWSTART位置1,开始转换,返回SWSTART状态。(一般不用)

1
2
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

配置间断模式,第一个函数是每个几个通道间断一次,第二个函数是是否启用间断模式。

1
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

ADC规则组通道配置,给序列的每个位置填写指定的通道。

1
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC外部触发转换控制,是否允许外部触发转换。

1
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

ADC获取转换值,获取AD转换数据寄存器,读取结果。

1
uint32_t ADC_GetDualModeConversionValue(void);

ADC获取双模式转换器,双ADC模式读取转换结果。

1
2
3
4
5
6
7
8
9
10
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);

对ADC注入组进行配置。

1
2
3
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);//是否启用模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);//配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);//配置看门通道

对模拟看门狗进行配置

1
void ADC_TempSensorVrefintCmd(FunctionalState NewState);

开启内部通道:ADC温度传感器、内部参考电压控制

1
2
3
4
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//获取标志位状态
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//清除标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);//获取中断状态
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);//清除中断挂起位

AD单通道

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

void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟,ADC是APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK,分频时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//选择模拟输入模式,是ADC的专属模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC规则组通道配置,给序列的每个位置填写指定的通道。通道0。

//配置ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发,使用软件触发
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC工作模式,独立模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换模式还是单次转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描转换模式,多通道还是单通道
ADC_InitStructure.ADC_NbrOfChannel = 1;//扫描模式下的通道数目,
ADC_Init(ADC1,&ADC_InitStructure);

ADC_Cmd(ADC1,ENABLE);

//对ADC进行校准
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位校准状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}

uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//获取标志位状态
return ADC_GetConversionValue(ADC1);//ADC获取转换值

}

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 "OLED.h"
#include "AD.h"
uint16_t ADValue;
int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1,1,"ADValue:");
OLED_ShowString(2,1,"Voltage:0.00V");
while(1)
{
ADValue = AD_GetValue();
OLED_ShowNum(1,9,ADValue,4);
OLED_ShowNum(2,9,((float)ADValue/4095*3.3),1);
OLED_ShowNum(2,11,((uint16_t)(((float)ADValue/4095*3.3)*100)%100),2);
Delay_ms(100);
}
}

AD多通道

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

void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟,ADC是APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK,分频时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//选择模拟输入模式,是ADC的专属模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);


//配置ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发,使用软件触发
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC工作模式,独立模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换模式还是单次转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描转换模式,多通道还是单通道
ADC_InitStructure.ADC_NbrOfChannel = 1;//扫描模式下的通道数目,
ADC_Init(ADC1,&ADC_InitStructure);

ADC_Cmd(ADC1,ENABLE);

//对ADC进行校准
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位校准状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}

uint16_t AD_GetValue(uint8_t ADC_Channel)//传入AD通道
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//AD通道放在第二个参数,是传进来的。
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//获取标志位状态
return ADC_GetConversionValue(ADC1);//ADC获取转换值

}

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);

OLED_ShowNum(1,5,AD0,4);
OLED_ShowNum(2,5,AD1,4);
OLED_ShowNum(3,5,AD2,4);
OLED_ShowNum(4,5,AD3,4);
Delay_ms(100);
}
}

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);//计数器的值
}
}