SysTick滴答定时器
- 滴答定时器介绍
SysTick滴答定时器是一个24 位的递减计数器,支持中断,SysTick 定时器被捆绑在 NVIC 中,用于产生 SYSTICK 异常(异常号:15) - 滴答定时器时钟源的选择函数
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
- 滴答定时器的配置
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value impossible */
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = ticks - 1; /* set reload register */
/* set Priority for Systick Interrupt */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);//优先级设置为15(系统最低优先级)
/* Load the SysTick Counter Value */
SysTick->VAL = 0;
/* Enable SysTick IRQ and SysTick Timer */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
/* Function successful */
return (0);
}
- 配置SysTick中断,并初始化软件定时器变量
void bsp_InitTimer(void)
{
uint8_t i;
/*清零所有的软件定时器 */
for (i = 0; i < TMR_COUNT; i++)
{
s_tTmr[i].Count = 0;
s_tTmr[i].PreLoad = 0;
s_tTmr[i].Flag = 0;
s_tTmr[i].Mode = TMR_ONCE_MODE; /* ȱʡÊÇ1´ÎÐÔ¹¤×÷ģʽ */
}
/*配置systick中断周期为1ms,并启动systick中断
SystemCoreClock 是固件库定义的系统内核时钟,对于STM32F4XX,一般为 168MHz
SysTick_Config()函数的形参表示内核时钟多少个周期后触发一次Systick定时中断
-- SystemCoreClock / 1000 表示定时频率为1000HZ,定时周期为1ms
-- SystemCoreClock / 500 表示定时频率为500HZ,定时周期为2ms
-- SystemCoreClock / 2000 表示定时频率为2000HZ,定时周期为500us */
SysTick_Config(SystemCoreClock / 1000);
// bsp_InitHardTimer(); /*开启硬件定时中断 */
}
软件定时器
- 启动自动定时器
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period)
{
if (_id >= TMR_COUNT)
{
/* 打印出错的源代码文件名、函数名称 */
BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
while(1); /* 参数异常,死机等待看门狗复位 */
}
DISABLE_INT(); /* 关中断 */
s_tTmr[_id].Count = _period; /* 实时计数器初值 */
s_tTmr[_id].PreLoad = _period; /* 计数器自动重装值,仅自动模式起作用 */
s_tTmr[_id].Flag = 0; /* 定时时间到标志 */
s_tTmr[_id].Mode = TMR_AUTO_MODE; /* 自动工作模式 */
ENABLE_INT(); /* 开中断 */
}
- 函数递减计数器
/******************************************************************************
* 函 数 名: bsp_SoftTimerDec
* 功能说明: 每隔1ms对所有定时器变量减1。必须被SysTick_ISR周期性调用。
* 形 参: _tmr : 定时器变量指针
* 返 回 值: 无
******************************************************************************/
static void bsp_SoftTimerDec(SOFT_TMR *_tmr)
{
if (_tmr->Count > 0)
{
/* 如果定时器变量减到1则设置定时器到达标志 */
if (--_tmr->Count == 0)
{
_tmr->Flag = 1;
/* 如果是自动模式,则自动重装计数器 */
if(_tmr->Mode == TMR_AUTO_MODE)
{
_tmr->Count = _tmr->PreLoad;
}
}
}
}
- 超时检测函数
uint8_t bsp_CheckTimer(uint8_t _id)
{
if (_id >= TMR_COUNT)
{
return 0;
}
if (s_tTmr[_id].Flag == 1)
{
s_tTmr[_id].Flag = 0;
return 1;
}
else
{
return 0;
}
}
- 停止软件定时器
void bsp_StopTimer(uint8_t _id)
{
if (_id >= TMR_COUNT)
{
/* 打印出错的源代码文件名、函数名称 */
BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
while(1); /* 参数异常,死机等待看门狗复位 */
}
DISABLE_INT(); /* 关中断 */
s_tTmr[_id].Count = 0; /* 实时计数器初值 */
s_tTmr[_id].Flag = 0; /* 定时时间到标志 */
s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 自动工作模式 */
ENABLE_INT(); /* 开中断 */
}
定时器中断
-
控制寄存器1(TIMx_CR1)
-
自动重装载寄存器(TIMx_ARR)
该寄存器在物理上实际对应着 2 个寄存器:
当TIMx_CR1中APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2 个寄存器是连通的;
当TIMx_CR1中APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。 -
DMA/中断使能寄存器(TIMx_DIER)
-
预分频寄存器(TIMx_PSC)—该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
-
状态寄存器(TIMx_SR)—该寄存器用来标记当前与定时器相关的各种事件/中断是否发生
-
初始化
void TIM3_Int_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<1; //TIM3 时钟使能
TIM3->ARR=arr; //设定计数器自动重装值//刚好 1ms
TIM3->PSC=psc; //预分频器 7200,得到 10Khz 的计数时钟
TIM3->DIER|=1<<0; //允许更新中断
TIM3->CR1|=0x01; //使能定时器 3
MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占 1,子优先级 3,组 2
}
- 定时器中断服务
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001) //溢出中断
{
}
TIM3->SR&=~(1<<0); //清除中断标志位
}
PWM
- 简介
STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出。 - 捕获/比较模式寄存器(TIMx_CCMR1/2)
TIMx_CCMR1 控制 CH1(低八位) 和 2(高八位),而 TIMx_CCMR2 控制 CH3 和 4
位OCxM可配置成7种模式,PWM模式对应的设置为 110/111。
位CCxS 用于设置通道的方向(输入/输出)。 - 捕获/比较使能寄存器(TIMx_CCER)—控制着各个输入输出通道的开关
- TIMx_BDTR
高级定时器要想输出 PWM,必须还要设置一个MOE 位(TIMx_BDTR 的第 15 位),以使能主输出,否则不会输出 PWM。 - 初始化
void TIM3_PWM_Init(u16 arr,u16 psc)
{
//arr:自动重装值 psc:时钟预分频数
RCC->APB1ENR|=1<<1; //TIM3 时钟使能
RCC->APB2ENR|=1<<3; //使能 PORTB 时钟
GPIOB->CRL&=0XFF0FFFFF; //PB5 输出
GPIOB->CRL|=0X00B00000; //复用功能输出
RCC->APB2ENR|=1<<0; //开启辅助时钟
AFIO->MAPR&=0XFFFFF3FF; //清除 MAPR 的[11:10]
AFIO->MAPR|=1<<11; //部分重映像,TIM3_CH2->PB5
TIM3->ARR=arr; //设定计数器自动重装值
TIM3->PSC=psc; //预分频器不分频
TIM3->CCMR1|=7<<12;//CH2 PWM2 模式
TIM3->CCMR1|=1<<11;//CH2 预装载使能
TIM3->CCER|=1<<4; //OC2 输出使能
TIM3->CR1=0x0080; //ARPE 使能
TIM3->CR1|=0x01; //使能定时器 3
}
输入捕获
STM32F1 的定时器除了 TIM6 和 TIM7都具有输入捕获功能。
STM32F1 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。
- TIMx_CCMR1 [7:0]位详细描述
- TIMx_CCER 最低 2 位描述
- 捕获/比较寄存器(TIMx_CCR1~4)。
该寄存器用来存储捕获发生时,TIMx_CNT的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度。 - 捕获高电平脉宽的思路
首先,设置 TIM5_CH1 捕获上升沿,这在TIM5_Cap_Init 函数执行的时候就设置好了,然后等待上升沿中断到来,当捕获到上升沿中断,此时如果 TIM5CH1_CAPTURE_STA 的第 6 位为 0,则表示还没有捕获到新的上升沿,就先把TIM5CH1_CAPTURE_STA、TIM5CH1_CAPTURE_VAL 和 TIM5->CNT 等清零,然后再设置IM5CH1_CAPTURE_STA 的第 6 位为 1,标记捕获到高平,最后设置为下降沿捕获,等待下降沿到来。如果等待下降沿到来期间,定时器发生了溢出,就在 TIM5CH1_CAPTURE_STA里面对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成(虽然此时还没有捕获到下降沿)。当下降沿到来的时候,先设置 TIM5CH1_CAPTURE_STA 的第 7 位为 1,标记成功捕获一次高电平,然后读取此时的定时器值到 TIM5CH1_CAPTURE_VAL 里面,最后设置为上升沿捕获,回到初始状态。这样就完成了一次高电平捕获。 - 定时器 5 通道 1 输入捕获库函数配置
TIM_ICInitTypeDef TIM5_ICInitStructure;
void TIM5_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //①使能 TIM5 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //①使能 GPIOA 时钟
//初始化 GPIOA.0 ①
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
//②初始化 TIM5 参数
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //初始化 TIMx
//③初始化 TIM5 输入捕获通道 1
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure); //初始化 TIM5 输入捕获通道 1
//⑤初始化 NVIC 中断优先级分组
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级 2 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级 0 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化 NVIC
TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//④允许更新中断捕获中断
TIM_Cmd(TIM5,ENABLE ); //⑥使能定时器 5
}
u8 TIM5CH1_CAPTURE_STA=0; //自定义输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//⑤定时器 5 中断服务程序
void TIM5_IRQHandler(void)
{
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获(修改输入捕获通道1的极性)
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0); //设置计数器寄存器值
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
Note:TIM5CH1_CAPTURE_STA,是用来记录捕获状态,本身就是个变量,只是我们把它当成一个寄存器那样来使用)。TIM5CH1_CAPTURE_STA 各位描述如图所示:
- 定时器 5 通道 1 输入捕获寄存器操作配置
void TIM5_Cap_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<3; //TIM5 时钟使能
RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
GPIOA->CRL&=0XFFFFFFF0; //PA0 清除之前设置
GPIOA->CRL|=0X00000008; //PA0 输入
GPIOA->ODR|=0<<0; //PA0 下拉
TIM5->ARR=arr; //设定计数器自动重装值
TIM5->PSC=psc; //预分频器
TIM5->CCMR1|=1<<0; //CC1S=01 选择输入端 IC1 映射到 TI1 上
TIM5->CCMR1|=0<<4; //IC1F=0000 配置输入滤波器 不滤波
TIM5->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频
TIM5->CCER|=0<<1; //CC1P=0 上升沿捕获
TIM5->CCER|=1<<0; //CC1E=1 允许捕获计数器的值到捕获寄存器中
TIM5->DIER|=1<<1; //允许捕获中断
TIM5->DIER|=1<<0; //允许更新中断
TIM5->CR1|=0x01; //使能定时器 2
MY_NVIC_Init(2,0,TIM5_IRQn,2);//抢占 2,子优先级 0,组 2
}
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//定时器 5 中断服务程序
void TIM5_IRQHandler(void)
{
u16 tsr;
tsr=TIM5->SR;
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if(tsr&0x02)//捕获 1 发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL=TIM5->CCR1;//获取当前的捕获值.
TIM5->CCER&=~(1<<1); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM5->CNT=0; //计数器清空
TIM5->CCER|=1<<1; //CC1P=1 设置为下降沿捕获
}
}
}
TIM5->SR=0;//清除中断标志位
}
- 编码器转动速度
定时器有些通道具有AB正交解码功能
static void Encoder_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_TIM4,ENABLE );//重映射
/*
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 正交编码器输入引脚 PB->6 PB->7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
// 正交编码器映射引脚 PD->12 PD->13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/*- TIM4编码器模式配置 -*/
TIM_DeInit(TIM4);
TIM_TimeBaseStructure.TIM_Period = 60000; //设定计数器重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 0; //50Hz采样编码
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI1, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); //配置编码器模式触发源和极性
TIM_ICStructInit(&TIM_ICInitStructure); //配置滤波器
TIM_ICInitStructure.TIM_ICFilter = 6;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM4->CNT = 0;
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_Cmd(TIM4, ENABLE); //启动TIM4定时器
}
encoder_dcar = TIM4->CNT;
TIM4->CNT = 0;
SENSOR_DATA.encoder_delta_car += (encoder_dcar > 30000)?(encoder_dcar-60000):encoder_dcar; //正交解码