马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
我们从前在开发产物的时间,肯定会碰到一些延时需求,比如常见的LED闪耀,按键消抖,控制IO口输出时序等等。
别鄙视延时,这个小标题,想做好,以致要思量到步伐架构层面。
在开发板上,大概你用delay死延时,很简朴。
但是有个致命的标题,就是CPU壅闭,须要等延时完,步伐才气往下实验,这种在实际产物大部门情况是不能用的,另有就是这种延时时间精度也不敷,大概你延时500ms,实测550ms~600ms随机跳动。
如果换个主频从12MHz改为24MHz的单片机,全部定时全乱了套,改到你抓狂。
背面工作了,我就通过定时器,以全局变量来计时,然后判定变量值来判定时间,时间精度的标题办理了,但是又陪伴着另一个标题,就是代码可扩展性和可移植性差,换一个项目,要增长新的延时时间,大概换一个单片机,代码又要大改。
本日带你彻底办理这个标题,分享我从前做产物不停在用的定时架构,已经颠末几十个项目批量验证,稳固、可扩展,可移植。
一、架构实现思绪图解
1.1 焦点数据结构体
- typedef struct {
- uint16_t Period; // 定时周期(50μs单位)
- uint16_t CurrentCount; // 当前计数值
- void (*func)(void); // 回调函数指针
- TIMER_STATE_TYPEDEF state; // 状态标示
- } Stu_TimerTypedef;
- volatile Stu_TimerTypedef Stu_Timer[T_SUM]; // T_SUM建议定义8
复制代码
1.2 三层架构计划
二、代码逐行剖析(焦点函数)
2.1 硬件初始化函数
- static void hal_timer4Config(void)
- {
- // TIM4时钟使能
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
-
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = {0};
- TIM_TimeBaseStructure.TIM_Period = 50 - 1; // 50us间隔自动重装载值
- TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock/1000000 - 1; // 1MHz时基
- //其它初始化代码
- }
复制代码
2.2 定时器管理API
2.2.1 创建定时器
- void hal_CreatTimer(TIMER_ID_TYPEDEF id, void (*proc)(void),
- uint16_t Period, TIMER_STATE_TYPEDEF state)
- {
- Stu_Timer[id].state = state;
- Stu_Timer[id].Period = Period; // 设置周期(50μs*Period)
- Stu_Timer[id].CurrentCount = 0; // 清空计数
- Stu_Timer[id].func = proc; // 绑定回调函数
- }
复制代码
3.2.2 定时器状态控制
- TIMER_RESULT_TYPEDEF hal_CtrlTimerAction(TIMER_ID_TYPEDEF id,
- TIMER_STATE_TYPEDEF sta)
- {
- if(Stu_Timer[id].func != NULL){
- Stu_Timer[id].state = sta; // 修改运行状态
- return T_SUCCESS;
- }
- return T_FAIL; // 定时器未创建
- }
复制代码
3.3 克制处置惩罚焦点
- void TIM4_IRQHandler(void)
- {
- if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET){
- // 全局中断处理函数
- for(uint8_t i=0; i<T_SUM; i++){
- if(Stu_Timer[i].state == T_STA_START){
- if(++Stu_Timer[i].CurrentCount >= Stu_Timer[i].Period){
- Stu_Timer[i].state = T_STA_STOP; // 单次触发模式
- Stu_Timer[i].func(); // 执行用户回调
- }
- }
- }
- TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
- }
- }
复制代码
三、底子用法示例
3.1 LED闪耀(1Hz)
- // 定义LED任务ID
- #define LED_TASK_ID 0
- // LED回调函数
- void LED_Task(void){
- GPIO_WriteBit(GPIOC, GPIO_Pin_13,
- (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
- }
- int main(void){
- // 硬件初始化
- hal_timerInit();
- GPIO_Init(GPIOC, GPIO_Pin_13, GPIO_Mode_Out_PP);
-
- // 创建定时器(10000*50μs=500ms)
- hal_CreatTimer(LED_TASK_ID, LED_Task, 10000, T_STA_START);
-
- while(1){
- // 主循环可添加其他任务
- if(需要重启定时器){
- hal_ResetTimer(LED_TASK_ID, T_STA_START);
- }
- }
- }
复制代码
3.2 按键消抖(进阶用法)
- #define KEY_TASK_ID 1
- uint8_t key_state = 0;
- void Key_Scan_Task(void){
- static uint16_t press_time = 0;
-
- if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)){
- if(++press_time > 10){ // 50μs*10=0.5ms
- key_state = 1;
- }
- }else{
- press_time = 0;
- key_state = 0;
- }
- }
- void Init_Key_Scan(void){
- hal_CreatTimer(KEY_TASK_ID, Key_Scan_Task, 10, T_STA_START); // 每0.5ms扫描
- }
复制代码 关于这个定时器架构,我在2018年也录了一套比力体系的教程,可滴滴我安排。
以上两种是比力常用了,除了这个,我们无际单片机项目里另有控制单口时序驱动外围芯片的用法,比如语音芯片等等,用起来极其机动。
这种是通过定时器的精准定时,定时使命在定时器克制内里实验,也是有缺点的,如果定时的使命多了,就会影响及时性。
以是,有些定时,不须要要求这么高的,我们一样平常是共同使命的Tick,然后每个使命里设置一个变量,通过递增和递减来延时。
之前有同砚问过我,怎么去验证这个定时器时间准禁绝?
我们在调试延时架构代码的阶段,会通过示波器,共同IO电平翻转去测试,比如10ms翻转一次,看下精度。
近来许多粉丝问我单片机怎么学,我根据本身从业十年履历,累积耗时一个月,经心整理一份「单
片机最佳学习路径+单片机入门到高级教程+工具包」,全部无偿分享给铁粉!!!
除此以外,再含泪分享我压箱底的22个热门开源项目,包罗源码+原理图+PCB+阐明文档,让你敏捷进阶成高手!
教程资料包和具体的学习路径可以看我下面这篇文章的开头。
《单片机入门到高级开挂学习路径(附教程+工具)》
《单片机入门到高级开挂学习路径(附教程+工具)》
《单片机入门到高级开挂学习路径(附教程+工具)》
|