STM32有五个时钟源:HSI,HSE,LSI,LSE,PLL。
HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟,WDG。
LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC
PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2,HSE或者HSE/2。
倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
系统时钟SYSCLK可来源于三个时钟源:
HSI振荡器时钟
HSE振荡器时钟
PLL时钟
STM32的片上外设都挂载在AHB,APB这两天总线上,APB1的总线频率最高是36MHz,APB2的时钟频率最高是72MHz,AHB总线最快。
SysTick定时器就是系统滴答定时器,一个24位的倒计时定时器,计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15)
SysTick中断的优先级也可以设置。
4个SysTick寄存器
CTRL SysTick控制和状态寄存器
LOAD SysTick自动重装载除值寄存器
VAL SysTick当前值寄存器
CALIB SysTick校准值寄存器
关于SysTick相关代码:
初始化函数 bsp_InitTimer
主要配置SysTick中断相关,并初始化软件定时器变量
启动定时器
bsp_StartTimer 用于启动一次软件定时器
bsp_StartAutoTimer用于周期性定时
递减计数器bsp_SoftTimerDec
检测定时器超时bsp_CheckTimer主程序里调用
停止软件定时器bsp_StopTimer
SysTick中断服务函数SysTick_ISR
现在我们开始书写滴答定时器
这个滴答定时器代码在最开始学习的时候就连带着给我们了,这里先把代码奉上
bsp_timer.c:
/*
*********************************************************************************************************
*
* 模块名称 : 定时器模块
* 文件名称 : bsp_timer.c
* 版 本 : V1.0
* 说 明 : 配置systick定时器作为系统滴答定时器。缺省定时周期为1ms。
*
* 实现了多个软件定时器供主程序使用(精度1ms), 可以通过修改 TMR_COUNT 增减定时器个数
* 实现了ms级别延迟函数(精度1ms) 和us级延迟函数
* 实现了系统运行时间函数(1ms单位)
*
*********************************************************************************************************
*/
#include "bsp.h"
/* 这2个全局变量转用于 bsp_DelayMS() 函数 */
static volatile uint32_t s_uiDelayCount = 0;
static volatile uint8_t s_ucTimeOutFlag = 0;
/* 定于软件定时器结构体变量 */
static SOFT_TMR s_tTmr[TMR_COUNT];
/*
全局运行时间,单位1ms
最长可以表示 24.85天,如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
*/
__IO int32_t g_iRunTime = 0;
static void bsp_SoftTimerDec(SOFT_TMR *_tmr);
/*
*********************************************************************************************************
* 函 数 名: bsp_InitTimer
* 功能说明: 配置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次性工作模式 */
}
/*
配置systic中断周期为1ms,并启动systick中断。
SystemCoreClock 是固件中定义的系统内核时钟,对于STM32F1XX,一般为 72MHz
SysTick_Config() 函数的形参表示内核时钟多少个周期后触发一次Systick定时中断.
-- SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms
-- SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms
-- SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us
对于常规的应用,我们一般取定时周期1ms。对于低速CPU或者低功耗应用,可以设置定时周期为 10ms
*/
SysTick_Config(SystemCoreClock / 1000);
}
/*
*********************************************************************************************************
* 函 数 名: SysTick_ISR
* 功能说明: SysTick中断服务程序,每隔1ms进入1次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
extern void bsp_RunPer1ms(void);
extern void bsp_RunPer10ms(void);
void SysTick_ISR(void)
{
static uint8_t s_count = 0;
uint8_t i;
/* 每隔1ms进来1次 (仅用于 bsp_DelayMS) */
if (s_uiDelayCount > 0)
{
if (--s_uiDelayCount == 0)
{
s_ucTimeOutFlag = 1;
}
}
/* 每隔1ms,对软件定时器的计数器进行减一操作 */
for (i = 0; i < TMR_COUNT; i++)
{
bsp_SoftTimerDec(&s_tTmr[i]);
}
/* 全局运行时间每1ms增1 */
g_iRunTime++;
if (g_iRunTime == 0x7FFFFFFF) /* 这个变量是 int32_t 类型,最大数为 0x7FFFFFFF */
{
g_iRunTime = 0;
}
bsp_RunPer1ms(); /* 每隔1ms调用一次此函数,此函数在 bsp.c */
if (++s_count >= 10)
{
s_count = 0;
bsp_RunPer10ms(); /* 每隔10ms调用一次此函数,此函数在 bsp.c */
}
}
/*
*********************************************************************************************************
* 函 数 名: 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;
}
}
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_DelayMS
* 功能说明: ms级延迟,延迟精度为正负1ms
* 形 参: n : 延迟长度,单位1 ms。 n 应大于2
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_DelayMS(uint32_t n)
{
if (n == 0)
{
return;
}
else if (n == 1)
{
n = 2;
}
DISABLE_INT(); /* 关中断 */
s_uiDelayCount = n;
s_ucTimeOutFlag = 0;
ENABLE_INT(); /* 开中断 */
while (1)
{
bsp_Idle(); /* CPU空闲执行的操作, 见 bsp.c 和 bsp.h 文件 */
/*
等待延迟时间到
注意:编译器认为 s_ucTimeOutFlag = 0,所以可能优化错误,因此 s_ucTimeOutFlag 变量必须申明为 volatile
*/
if (s_ucTimeOutFlag == 1)
{
break;
}
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_DelayUS
* 功能说明: us级延迟。 必须在systick定时器启动后才能调用此函数。
* 形 参: n : 延迟长度,单位1 us
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_DelayUS(uint32_t n)
{
uint32_t ticks;
uint32_t told;
uint32_t tnow;
uint32_t tcnt = 0;
uint32_t reload;
reload = SysTick->LOAD;
ticks = n * (SystemCoreClock / 1000000); /* 需要的节拍数 */
tcnt = 0;
told = SysTick->VAL; /* 刚进入时的计数器值 */
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
/* SYSTICK是一个递减的计数器 */
if (tnow < told)
{
tcnt += told - tnow;
}
/* 重新装载递减 */
else
{
tcnt += reload - tnow + told;
}
told = tnow;
/* 时间超过/等于要延迟的时间,则退出 */
if (tcnt >= ticks)
{
break;
}
}
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_StartTimer
* 功能说明: 启动一个定时器,并设置定时周期。
* 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。
* _period : 定时周期,单位1ms
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_StartTimer(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_ONCE_MODE; /* 1次性工作模式 */
ENABLE_INT(); /* 开中断 */
}
/*
*********************************************************************************************************
* 函 数 名: bsp_StartAutoTimer
* 功能说明: 启动一个自动定时器,并设置定时周期。
* 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。
* _period : 定时周期,单位10ms
* 返 回 值: 无
*********************************************************************************************************
*/
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_StopTimer
* 功能说明: 停止一个定时器
* 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。
* 返 回 值: 无
*********************************************************************************************************
*/
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(); /* 开中断 */
}
/*
*********************************************************************************************************
* 函 数 名: bsp_CheckTimer
* 功能说明: 检测定时器是否超时
* 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。
* _period : 定时周期,单位1ms
* 返 回 值: 返回 0 表示定时未到, 1表示定时到
*********************************************************************************************************
*/
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;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetRunTime
* 功能说明: 获取CPU运行时间,单位1ms。最长可以表示 24.85天,如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
* 形 参: 无
* 返 回 值: CPU运行时间,单位1ms
*********************************************************************************************************
*/
int32_t bsp_GetRunTime(void)
{
int32_t runtime;
DISABLE_INT(); /* 关中断 */
runtime = g_iRunTime; /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */
ENABLE_INT(); /* 开中断 */
return runtime;
}
/*
*********************************************************************************************************
* 函 数 名: bsp_CheckRunTime
* 功能说明: 计算当前运行时间和给定时刻之间的差值。处理了计数器循环。
* 形 参: _LastTime 上个时刻
* 返 回 值: 当前时间和过去时间的差值,单位1ms
*********************************************************************************************************
*/
int32_t bsp_CheckRunTime(int32_t _LastTime)
{
int32_t now_time;
int32_t time_diff;
DISABLE_INT(); /* 关中断 */
now_time = g_iRunTime; /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */
ENABLE_INT(); /* 开中断 */
if (now_time >= _LastTime)
{
time_diff = now_time - _LastTime;
}
else
{
time_diff = 0x7FFFFFFF - _LastTime + now_time;
}
return time_diff;
}
/*
*********************************************************************************************************
* 函 数 名: SysTick_Handler
* 功能说明: 系统嘀嗒定时器中断服务程序。启动文件中引用了该函数。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void SysTick_Handler(void)
{
SysTick_ISR();
}
/***************************** 德致伦电子 DeZLinc (END OF FILE) *********************************/
还有bsp_timer.h:
/*
*********************************************************************************************************
*
* 模块名称 : 定时器模块
* 文件名称 : bsp_timer.h
* 版 本 : V1.0
* 说 明 : 头文件
*
* Copyright (C), 2014-2020, 德致伦电子
*
*********************************************************************************************************
*/
#ifndef __BSP_TIMER_H
#define __BSP_TIMER_H
#include "sys.h"
/*
在此定义若干个软件定时器全局变量
注意,必须增加__IO 即 volatile,因为这个变量在中断和主程序中同时被访问,有可能造成编译器错误优化。
*/
#define TMR_COUNT 4 /* 软件定时器的个数 (定时器ID范围 0 - 3) */
/* 定时器结构体,成员变量必须是 volatile, 否则C编译器优化时可能有问题 */
typedef enum
{
TMR_ONCE_MODE = 0, /* 一次工作模式 */
TMR_AUTO_MODE = 1 /* 自动定时工作模式 */
}TMR_MODE_E;
/* 定时器结构体,成员变量必须是 volatile, 否则C编译器优化时可能有问题 */
typedef struct
{
volatile uint8_t Mode; /* 计数器模式,1次性 */
volatile uint8_t Flag; /* 定时到达标志 */
volatile uint32_t Count; /* 计数器 */
volatile uint32_t PreLoad; /* 计数器预装值 */
}SOFT_TMR;
/* 提供给其他C文件调用的函数 */
void bsp_InitTimer(void);
void bsp_DelayMS(uint32_t n);
void bsp_DelayUS(uint32_t n);
void bsp_StartTimer(uint8_t _id, uint32_t _period);
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period);
void bsp_StopTimer(uint8_t _id);
uint8_t bsp_CheckTimer(uint8_t _id);
int32_t bsp_GetRunTime(void);
int32_t bsp_CheckRunTime(int32_t _LastTime);
void bsp_InitHardTimer(void);
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack);
#endif
/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/
代码解释:
在.h文件里先宏定义了4个软件定时器,每个软件定时器在使用的过程中都会进行调度,调度就需要延时,所以为了准确度高,就只设置了四个。
后面是定时器的工作模式,有一个单次工作模式和自动定时工作模式,这个可以解释为闹钟,像周一和周五早上每天都响,这就叫周期性时钟,只定了个下午两点响一次,那就是一次性时钟。定时器工作模式也是这个逻辑。
然后是定时器的结构体,分为模式(就是上面那个),定时到达标志,计数器,计数器预装值。
再下面就是.c文件里的函数声明。
void bsp_StartTimer(uint8_t _id, uint32_t _period);
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period);
分别是定时器单次启动和周期启动
void bsp_StopTimer(uint8_t _id);
是关闭定时器,
uint8_t bsp_CheckTimer(uint8_t _id);
是检查定时器是否到时,是否溢出了
然后介绍.c文件
定义了一个软件定时器结构体变量
static SOFT_TMR s_tTmr[TMR_COUNT];
其中的TMR_COUNT相当于是一个数组,
往下就是Timer的初始化
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_Config(SystemCoreClock / 1000)配置定时器的周期;
SystemCoreClock是系统的72MHz,/1000相当于是慢了1000倍,相当于1m除以1000,是1ms
即周期是1ms
再往下是SysTick中断服务程序SysTick_ISR(void)
里面的注释写的也很清楚
/* 每隔1ms进来1次 (仅用于 bsp_DelayMS) */
if (s_uiDelayCount > 0)
{
if (--s_uiDelayCount == 0)
{
s_ucTimeOutFlag = 1;
}
}从进去就开始减1,减到0就有一个标志位变1的操作。
然后每隔1ms,对软件定时器的计数器进行减一操作
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;
}
}
}
}
这里会先检查_tmr->Count是否大于0,即是否减到0了,要是没减到0就会进去函数去减一
再往下的一个MS的函数里面存在关中断和开中断,这个存在的意义就是为了防止时间因为中断发生被打断从而出现误差。
再往下就是一些我们上面讲过的函数了。
接下来在main.c里写一下周期性的软件定时器0 100ms工作一次,10s后由定时器1关闭,定时器1只工作一次,10s关闭自己和定时器0的实现
打开timer.h复制bsp_StartTimer()到主函数我们应用的是定时器0,所以是bsp_StartTimer(1,10000);转回去复制bsp_StartAutoTimer(uint8_t _id, uint32_t _period);到主函数改定时器0:bsp_StartAutoTimer(0,100);
然后在While(1)里写操作
找到bsp_CheckTimer去查看是否到了时间,然后去执行操作
即主函数这样写:
int main(void)
{
bsp_Init(); /* 硬件初始化 */
//周期性的软件定时器0 100ms工作一次,10s后由定时器1关闭,定时器1只工作一次,10s关闭自己和定时器0
bsp_StartAutoTimer(0,100);
//10s=10000ms
bsp_StartTimer(1,10000);
while(1)
{
//定时器1定时到
if(bsp_CheckTimer(1) == 1)
{
bsp_StopTimer(1);
bsp_StopTimer(0);
}
}
}