IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 嵌入式基础--毫秒级定时模块 -> 正文阅读

[嵌入式]嵌入式基础--毫秒级定时模块

大家好,我是惊觉。失踪了三个月,我回来了。给大家带来一个好消息和一个坏消息。坏消息是,我尚未满血复活,Ardupilot第四篇将继续延期。好消息是,公众号恢复更新,先出一系列提升编码能力的文章。

全国电赛在即,昨天母校老师联系我,想让我给学弟们做下赛前培训。我做过很多年的培训,很早就发现了一个问题:同学们在为比赛做准备时,往往只注重去学习使用各种各样的传感器,自动控制算法,各种驱动。同学们只关注如何去实现功能,而忽视了如何把代码写得更好,更健壮,更易扩展和维护。如果在比赛之前,先准备好高质量的代码框架,基础模块,熟练掌握调度技巧,将极大提高赛时的开发和调试效率。

所谓高质量,涉及到很多方面,比如:

  • 函数、变量的命名有统一的规则
  • 基础接口要简单易用
  • 设计模块时需层次分明,高内聚低耦合
  • 尽量避免重复代码

笔者不打算一一讲解这些设计原则,而是介绍一些实际的基础模块,讲解它们的设计思路,注意事项和编程技巧,并在此过程中让大家理解相关的设计原则。

毫秒级定时模块

作为系列开篇,本文先介绍一个非常基础的模块:毫秒级定时模块。

友情提醒,本模块比较基础,可能有的同学对此非常熟悉,不过文末还有一个重要的小技巧噢。有基础的同学,可直接往后翻,跳到“再看comm_delay”一节

此模块提供基础的定时功能,可细分为两种:

  • 延时功能:毫秒极延时。
  • 定时功能:过一段时间后执行一项操作。

可能有的同学会想,直接用单片机的定时器嘛,一个任务用一个定时器,有啥好讲的。其实不然。定时模块肯定要依赖于硬件定时器,但是一个任务用一个定时器的话,会有如下问题:

  • 浪费资源。硬件定时器不仅仅只有定时功能,还有捕获输入信号,输出PWM,编码器等功能。如果滥用基础定时功能的话,等用到上述功能时,将无资源可用。
  • 某个任务与某个定时器绑定在一起,提高了系统耦合程度,可移植性差。

因此,我们需要一个统一的,可移植性强的定时模块。

我们再回头看下两个基础的功能,延时和定时。

延时示例,1秒打印1次hello。comm_delay实现毫秒极延时。

void comm_delay(uint32_t ms);

while (1)
{
    printf("hello\r\n");
    comm_mdelay(1000);
}

定时示例,1秒打印1次hello。comm_get_ms返回当前系统时间,即系统从启动到现在经过了多少毫秒。

uint32_t comm_get_ms(void);

while (1)
{
    cur_time = comm_get_ms();
    if (cur_time >= timeout)
    {
        printf("hello\r\n");
        timeout = cur_time + 1000;
    }
}

可能有的同学觉得上述两项功能差不多,而定时比延时的代码要复杂。定时的代码确实多一些,不过它具有并发能力,即支持多个定时任务同时进行。

定时示例,1秒打印1次you,2秒打印1次me。

static void show_you(void)
{
    static uint32_t timeout = 0;
    
    uint32_t cur_time = 0;
    
    cur_time = comm_get_ms();
    if(cur_time < timeout)
    {
        return;
    }
    
    printf("you\r\n");
    timeout = cur_time + 1000;
}

static void show_me(void)
{
    static uint32_t timeout = 0;
    
    uint32_t cur_time = 0;
    
    cur_time = comm_get_ms();
    if(cur_time < timeout)
    {
        return;
    }
    
    printf("me\r\n");
    timeout = cur_time + 2000;
}

int main(int argc, char **argv)
{
    while (1)
    {
        show_you();
        show_me();
    }
    
    return 0;
}

小结:

  • 延时功能需要提供一个延时函数。
  • 定时功能需要提供获取系统时间的函数。

即:

void comm_delay(uint32_t ms);
uint32_t comm_get_ms(void);

其实延时函数很简单,因为它也可以看成是一个定时任务:

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() < timeout);
}

系统时间

实现comm_get_ms,即记录系统时间,自然要靠硬件定时器啦。大家用的单片机,无论是TI,STM32,NXP等,大部分都是cortex-m的内核,该内核有一个专门干这事情的定时器:SysTick timer。其名称为系统滴答定时器,只有简单的定时功能。配置好reload计数并使能后,其由reload值递减至0,触发中断,再从reload递减。如果根据其时钟配置相应的reload值,实现每1ms触发1次中断,那就可以记录毫秒 级的系统时间

其配置函数位于单片机驱动库的CMSIS组件中,一般的工程都包含了这个组件,比如笔者使用TRUEStudio创建的工程:
在这里插入图片描述

下面是stm32的示例。SystemCoreClock为单片机的主频,这也是SysTick的输入时钟。SystemCoreClock / 1000即为1ms的定时计数,将reload配置为此值即可实现每1ms触发1次定时中断。其他单片机方法类似。

#include "stm32l1xx.h"
#include <stdint.h>

static uint32_t  sys_tick = 0;

void sys_tick_init(void)
{
    if(SysTick_Config(SystemCoreClock / 1000));
    NVIC_SetPriority(SysTick_IRQn, 0);
}

uint32_t sys_tick_get(void)
{
    return sys_tick;
}

void SysTick_Handler(void)
{
    sys_tick++;
}

用法非常简单,单片机启动时调用sys_tick_init配置并使能SysTick,每1ms触发1次SysTick_Handler,其内对当前时间sys_tick进行加1操作。应用层通过sys_tick_get获取当前时间。

SysTick_Handler在中断向量表中指定,大家根据具体的MCU对号入座。
在这里插入图片描述

comm_get_ms只需对sys_tick_get进行简单的封装:

uint32_t comm_get_ms(void)
{
    return sys_tick_get();
}

再看comm_delay

我们再看一下毫秒极延时的实现,大家觉得它有问题吗?

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() < timeout);
}

对于只需要进行几分钟演示的电赛来说,它没有问题。不过,电赛只是同学们实践所学的一条途径。正儿八经的产品,需要具有足够的健壮性,可长期稳定地运行。上述代码能长期运行吗?

static uint32_t  sys_tick = 0;

comm_get_ms是对sys_tick_get的简单封装,而sys_tick_get返回的是一个32位无符号整型变量,它记录的是系统从启动到现在所经过的毫秒数。32位无符号整型变量最大能表示多长的时间呢?

2^32 / 1000 / 3600 / 24 = 49.71

其可记录49天。在49天后,sys_tick将会溢出,从零重新开始累加。为了方便描述,要使用到一个宏UINT32_MAX。UINT32_MAX表示32位无符号整型变量的最大值,即0xffffffff。

假设我们要延时1分钟,即60000ms。当前时间为UINT32_MAX - 59999,下面计算timeout。

uint32_t timeout = comm_get_ms() + ms;

timeout发生溢出,计算结果为0。

UINT32_MAX - 59999 + 60000 = UINT32_MAX + 1 = 0

那么下面的等待循环将立刻退出,而不需要等待1分钟。

while(comm_get_ms() < timeout);

在接下来的1分钟内,comm_get_ms(60000)都是失效的,每1分钟执行1次的任务将不停地执行。还有其他溢出的场景,这里不再一一描述。我们只要明确一点就好:comm_delay不能长期运行。

健壮的comm_delay

怎么修改comm_delay以解决溢出问题呢?其实很简单,直接给出答案:

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() - timeout > UINT32_MAX / 2);
}

我们简单地验证几个场景:

当前时间为10ms,延时2ms。

先计算timeout:
timeout = 10 + 2 = 12

下面看看从现在开始,什么时候while(comm_get_ms() - timeout > UINT32_MAX / 2);会退出。

  • 第0ms秒时,当前时间为10,10 - 12 = -2,请注意,comm_get_ms() - timeout中的操作数都是uint32_t类型,即32位无符号整型,它们相减的结果还是无符号整型。所以-2 --> UINT32_MAX - 1 > UINT32_MAX / 2,循环继续。
  • 第2秒时,当前时间为12,12 - 12 = 0 < UINT32_MAX / 2,循环等待结束。

此种场景,成功实现延时2ms。

当前时间为(UINT32_MAX - 1)ms,延时2ms。

之所以定成UINT32_MAX - 1,是想测试时间溢出的场景,2ms后时间溢出。

先计算timeout,timeout在加2时溢出,最终结果为0。
timeout = (UINT32_MAX - 1) + 2 = UINT32_MAX + 1 = 0

下面看看从现在开始,什么时候while(comm_get_ms() - timeout > UINT32_MAX / 2);会退出。

  • 第0秒时,当前时间为(UINT32_MAX - 1),(UINT32_MAX - 1) - 0 = (UINT32_MAX - 1) > UINT32_MAX / 2,循环继续。
  • 第2秒时,当前时间如timeout一样加2溢出,最终为0。0 - 0 = 0 < UINT32_MAX / 2,循环等待结束。

此种场景,成功实现延时2ms。

总结

经过两种情况的测试,我们发现,无论计算过程中时间有无溢出,改进后的comm_delay都圆满完成延时。

这种实现的原理是什么呢?原理很重要噢,否则大家在使用时,可能把大于和小于关系搞反,或者是把被减数与减数的关系搞反。至于原理是什么呢,今天来不及讲了,请待下回分解。剧透一下,下篇的名字叫:张三与李四谁跑的快。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-07 00:04:22  更:2021-07-07 00:04:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 9:58:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计