4.1定时器基础知识
1.简单来说定时器就是用来定时的机器,是STM32单片机上的一个外设,STM32共有八个定时器,分别是两个高级定时器(TIM1、TIM8),四个通用定时器(TIM2、TIM3、TIM4、TIM5)和两个基本定时器(TIM6、TIM7)
2.三种定时器的区别
4.2基本定时器
1.基本定时器结构框图
1.时钟源
2.控制器
3.时基单元
时钟源
·时钟源来自RCC的TIMx_CLK(属于内部的CK_INIT)
控制器
·控制器用于控制定时器:复位、使能、计数、触发ADC
·涉及到的寄存器:CR1/2,DIER,EGR,SR
时基(定时器的心脏)
·定时器最重要的就是时基部分:包括预分频器、计数器、自动重装载寄存器
·预分频器:1-16位预分频器PSC对内部时钟CK_PSC进行分频之后,得到计数器时钟CK_INT=CK_PSC/(PSC+1)
CNT在计数器时钟的驱动下开始计数,计数一次的时间为1/CK_INT
·计数器、重装载寄存器:定时器使能(CEN置1)后,计数器CNT在CK_CNT驱动下计数,当CNT值与ARR的设定值相等时就自动生成事件并CNT自动清零,然后自动重新开始计数,如此重复以上过程。
影子寄存器
1.PSC和ARR都有影子寄存器,功能框图上有个影子
2.影子寄存器的存在起到一个缓冲的作用,用户值->寄存器->影子寄存器->起作用,如果不使用影子寄存器则用户值在写到寄存器之后则里面起作用,ARR影子,TIMx_CR1:APRE位控制。
2.定时时间的计算
定时器时间=(PSC +1)*(ARR+1) /72M
3.基本定时器的功能
1.计数器16bit,只能向上计数,只有TIM6和TIM7
2.没有外部的GPIO,是内部资源,只能用来定时
3.时钟来自于PCKL1,为72M,可实现1~65536分频
4.3通用定时器
1.STM32的众多定时器中我们使用最多的是高级定时器和通用定时器,而高级定时器一般也是用作通用定时器的功能,下面我们就以通用定时器为例进行讲解,其功能和特点包括:
·位于低速的APB1总线上(APB1)
·16位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)
·16位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意数值。
·4个独立通道(TIMx_CH1~4),这些通道可以用来作为:
输入捕获
输出比较
PWM 生成(边缘或中间对齐模式)
单脉冲模式输出
·可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用1个定时器控制另外一个定时器)的同步电路。
·如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
更新: 计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)。
输入捕获
输出比较
支持针对定位的增量(正交)编码器和霍尔传感器电路。
触发输入作为外部时钟或者按周期的电流管理
·STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等
·使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。
2.计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式
·向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件
·向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件
·中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数
3.通用定时器框图
框图可以分为四个大部分,分别是:
①时钟产生器部分
②时基单元部分
③输入捕获部分
④辅出比较部分
时钟产生部分
①内部时钟(CK_INT)
②外部时钟模式:外部触发输入(ETR)
③内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
④外部时钟模式:外部输入脚(TIx)
时基单元
时基单元就是定时器框图的第二部分,它包括三个寄存器,分别是:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)和自动装载寄存器(TIMx_ARR)。对这三个寄存器的介绍如下:
·计数器寄存器(TIMx_CNT)
向上计数、向下计数或者中心对齐计数;
·预分频器寄存器(TIMx_PSC)
可将时钟频率按1到65535之间的任意值进行分频,可在运行时改变其设置值;
·自动装载寄存器(TIMx_ARR)
如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;如果ARPE为1,ARR寄存器的那日同将在每次的更新时间UEV发生时,传送到影子寄存器;如果TIM1_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件
输入捕获通道
1.IC1、2和IC3、4可以分别通过软件设置将其映射到TI1、TI2和TI3、TI4;
2.4个16位捕捉比较寄存器可以编程用于存放检测到对应的每一次输入捕捉时计数器的值;
3.当产生一次捕捉,相应的CCxIF标志位被置1;同时如果中断或DMA请求使能,则产生中断或DMA请求。
4.如果当CCxIF标志位已经为1,当又产生一个捕捉,则捕捉溢出标志位CCxOF将被置1。
输出比较通道
·PWM模式运行产生:
定时器2、3和4可以产生独立的信号:频率和占空比可以进行如下设定一个自动重载寄存器用于设定PWM的周期;每个PWM通道有一个捕捉比较寄存器用于设定占空时间
·两种可设置PWM模式:
边沿对齐模式
中心对齐模式
通用定时器输出PWM
以TIM3为例,STM32的通用定时器TIM2,TIM3,TIM4,TIM5,每个定时器都有独立的四个通道可以用来作为:输入捕获,输出比较,PWM输出,单脉冲模式输出等。
STM32的定时器除了TIM6和TIM7(基本定时器)之外,其他的定时器都可以产生PWM波输出,高级定时器TIM1,TIM8可以同时产生7路PWM输出,而通用定时器可以同时产生4路PWM输出,这样STM32可以最多同时输出30路PWM输出
以向上计数为例,讲述PWM原理:
①在PWM输出模式下除了CNT(计数器当前值),ARR(自动重装载值),CCRx(捕获/比较寄存器值)
②当CNT小于CCRx时,TIMx__CHx通道输出低电平
③当CNT等于或大于CCRx时,TIMx_CHx通道输出高电平
所谓脉宽调制信号(PWM波),就是一个TIMx_ARR自动重装载寄存器确定频率(由它决定PWM周期).TIM_CCRx寄存器确定占空比信号
自动加载的预加载寄存器
如果对ARR寄存器的值进行修改,当APER=1,ARR立即生效,当APER=0,ARR下个周期生效
系统定时器SysTick
SysTick : 24位系统定时器,只能递减,存在于内核嵌套在NVIC中。所有的Cortex-M中都有这个系统定时器。
重装载值reload 递减,当递减到0会触发中断并且会有 置位countflag标志,VAL表示当前值。 然后reload继续从预设值开始递减,周而复始。
常用寄存器
SysTick定时时间的计算
①T : 一个计数循环的时间,跟reload和 CLK有关
②CLK : 72M 或者9 M, 由CTRL寄存器配置
③reload : 24位,用户自己配置
T = reload * (1/ CLK)
CLK= 72M, 1us = (72 )* (1/ 72 000 000)
CLK= 72M, 1ms = (72 000 )* (1/ 72 000 000)
时间单位换算: 1s = 1000ms = 1000 000 us = 1000 000 000 ns
SysTick中断优先级
systick 中断优先级配置的是scb->shprx寄存器 外设中断优先级配置的是nvic-> iprx寄存器 有优先级分组
①STM32的外设(内核还是片上)都是使用4个二进制来表示中断优先级
②中断优先级的分组对内核和外设同样适用,只需要将中断优先级的四个位按外设优先级来分组即可,人为的进行分出抢占优先级和子优先级。
例举: 1<<4 -1=16 -1= 15 (1 1 1 1 ) 前面两位表示抢占优先级 后面两位表示子优先级 3 3
编写一个us延时的函数和一个ms延时的函数:
systick.c
#include "stm32f10x.h"
#include "systick.h"
void ms_delay(uint32_t ms)
{
uint32_t i;
SysTick_Config(72000);//设置reload值
for(i=0;i<ms;i++)
{
while( !((SysTick->CTRL)&(1<<16)));//CTRL寄存器16位为计数完成标志,计数器减到0此位自动置1,读取后自动清零
}
SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;//失能内核时钟
}
void us_delay(uint32_t us)
{
uint32_t i;
SysTick_Config(72);
for(i=0;i<us;i++)
{
while( !((SysTick->CTRL)&(1<<16)));
}
SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}
systick.h
#include "stm32f10x.h"
void ms_delay(uint32_t ms);
void us_delay(uint32_t us);
硬件:
1.SG90电机
接线:
红线:3.3v/5v
黑线:GND
黄线:信号线
标准的PWM信号周期为20ms,脉宽在0.5ms至2.5ms之间变化,对应0到180度的旋转角度
2.超声波模块HC-SR04
超声波传感器模块上面通常有两个超声波元器件,一个用于发射,一个用于接收。电路板上有四个引脚:VCC GND Trig(触发),Echo(回应)主要参数:
工作电压与电流:5V.15mA感应距离:2~400cm感测角度:不小于15°
被测物的面积不要小于50cm2并且尽量平整具备温度补偿电路
超声波模块的触发脚(Trig) 输入10us 以上的高电位,即可发射超声波,发射超声波后,与接收到传回的超声波之前,"响应”脚(Echo)位呈现高电平。因此,程序可以从"响应"脚位(Echo)的高电平脉冲持续时间,换算出被测物的距离。
小项目:超声波模块控制舵机(垃圾桶)
1.舵机模块
void Motor_Config(void)
{
GPIO_InitTypeDef GPIOMotor;
TIM_TimeBaseInitTypeDef TIM_Motor;
TIM_OCInitTypeDef TIMPWM_Motor;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //APB2下的GPIOB时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //重映射引脚复用使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //APB1下的TIM3时钟使能
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);//TIM3部分重映射
GPIOMotor.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOMotor.GPIO_Pin = GPIO_Pin_5;
GPIOMotor.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIOMotor);
TIM_Motor.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_Motor.TIM_CounterMode = TIM_CounterMode_Up; //设置为向上计数模式
TIM_Motor.TIM_Period = 200 - 1; //设置自动重装载值
TIM_Motor.TIM_Prescaler = 7200 - 1; //设置时钟分频预分频值
TIM_TimeBaseInit(TIM3, &TIM_Motor); //初始化定时器结构体
TIMPWM_Motor.TIM_OCMode = TIM_OCMode_PWM1; //选择模式为PWM1
TIMPWM_Motor.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
TIMPWM_Motor.TIM_OCPolarity = TIM_OCPolarity_Low; //选择有效输出极性
TIM_OC2Init(TIM3, &TIMPWM_Motor); //初始化输出PWM结构体
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //预装载寄存器的使能
TIM_Cmd(TIM3, ENABLE); //定时器3使能
}
2.超声波测距模块
uint16_t mscount = 0;//定义mscount
//us延时函数
uint16_t mscount = 0;
void delay_us(uint32_t us)
{
us *= 8;
while(us--);
}
//ms延时函数
void delay_ms(uint32_t ms)
{
while(ms--)
{
delay_us(1000);
}
}
//定时器2中断初始化
void Base_Time_Init(void)
{
TIM_TimeBaseInitTypeDef Time_InitStruct; //定时器结构体
NVIC_InitTypeDef NVIC_InitStruct; //NVIC结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC优先级分组
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //APB1下的TIM2时钟使能
Time_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //外部输入时钟分频因子(基本定时器没有)
Time_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;//设置计数模式
Time_InitStruct.TIM_Period = 1000-1; //自动重装载值(配置为1000-1相当于中断一次为1ms)
Time_InitStruct.TIM_Prescaler = 72-1; //分频因子
Time_InitStruct.TIM_RepetitionCounter = 0; //重复计数值(高级定时器专属)
TIM_TimeBaseInit(TIM2, &Time_InitStruct); //初始化结构体
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE); //定时器2中断使能
TIM_Cmd(TIM2,DISABLE); //定时器2失能
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; //选择定时器2中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
void HC04_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruction;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
//配置Trig为B11引脚
GPIO_InitStruction.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruction.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruction.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_InitStruction);
//配置Echo为B10引脚
GPIO_InitStruction.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruction.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOB, &GPIO_InitStruction);
}
//打开定时器2
void Open_Tim()
{
TIM_SetCounter(TIM2,0);//清空计数值
mscount = 0; //初始化mscount
TIM_Cmd(TIM2, ENABLE );//使能TIM2
}
//关闭定时器2
void Close_Tim()
{
TIM_Cmd(TIM2, DISABLE );//让定时器2失能
}
//获取定时器中断次数(也就是多少ms)
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)//中断发生
{
mscount++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断
}
}
//获取总的高电平时间
int Get_Echo_Time()
{
uint16_t t=0; //定义总时间t并初始化为0
t = mscount * 1000; //换算成us
t += TIM_GetCounter(TIM2);//加上计数器里不足1ms的值
TIM2->CNT = 0; //清零计数器
delay_ms(50); //延时50ms
return t; //函数返回值为总时间
}
//获取距离函数
float Get_Length()
{
int i = 0;
float sum = 0;
uint16_t t = 0;
float length = 0;
while(i != 5) //计算距离循环5次
{ //让HC04工作
GPIO_SetBits(GPIOB, GPIO_Pin_11);
delay_us(20);
GPIO_ResetBits(GPIOB, GPIO_Pin_11);
//定时器计算Echo引脚输出高电平时间
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0);//等待Echo引脚从低电平到高电平
Open_Tim(); //打开定时器
i = i+1; //循环五次
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1);//等待Echo引脚从高电平到低电平
Close_Tim(); //关闭定时器
t = Get_Echo_Time(); //获取总时间
//时间换算成距离
length = (float)t/58.0;
sum = sum+length; //五次距离的和
}
length = sum/5; //计算距离平均值提高准确度
return length; //函数返回值为距离
}
3.在主函数中编写控制舵机函数
int main()
{
int pwmval = 195;
float length = 0;
Base_Time_Init();
myusart_init();
HC04_Init();
Motor_Config();
while(1)
{ pwmval = 155;
length = Get_Length();
printf("%1f\r\n",length);//打印出距离
delay(200);
if(length<5)
{
for(pwmval = 195;pwmval >= 155;pwmval-=15) //改变占空比
{
TIM_SetCompare2(TIM3,pwmval);
delay(500);
}
}
else if(length>5)
{
TIM_SetCompare2(TIM3, pwmval-20 );
}
}
}
实现结果:
如果超声波检测距离小于5cm,则舵机控制垃圾桶盖打开
如果超声波检测距离大于5cm,则舵机控制垃圾桶盖关闭