c语言延时函数delay_一个delay函数库的诞生

前言

真是太惊喜了,本人竟然获得了电子芯吧客社区和松果派社区提供的松果派ONE开发板试用机会。为了不辜负大家的期望,我一定要为电子芯吧客社区和松果派社区多发几篇文章。

松果派ONE开发板用的是一款新单片机(SWM320)。相对于STM32的成熟方案,SWM320的教程还比较少,因此试用的过程也是个探索性的学习过程。试用期间,我们陆续遭遇了一些有趣的问题,比如MicroPython下Pin配置无法控制GPIOC后面的端口,比如定时微秒触发中断时候会卡死,比如控制舵机时候无法产生合适的PWM波形。摸着石头过河的过程非常有趣,我们就这样一步一跌地前行。感谢松果社区小伙伴们的陪伴,让我知道踩到坑的不止我一人,让我在挫折中还能与大伙儿互相勉励。当然,今天要说的重点不在踩到坑的挫折。诚然,有可能导致失败的原因会有很多,这些坑不可能一一列举出来让大家规避。况且,作为一名摸着石头过河的先行者,也不能只局限在找BUG然后提交的狭隘视角下;而是需要站在更高的层面上,主动地提出改进方案;要以主人翁的姿态,多为成功谋划出路。

最近,松果派社区的小伙伴们又遭遇了一个小小的问题,SWM320的函数库里似乎少了一个delay函数。于是,本人做了delay函数库的开发。今天,这个delay库被更新到PineconePi_ONE的github上了。也许,手快的小伙伴已经去围观并点过星了。本人微不足道的贡献无需挂齿。今天的这篇文章也不是为了吹牛,而是要聊聊真正的技术,谈谈这个delay库是怎么开发出来的。本文重点是在解决问题的方法和思维模式,希望大伙儿能get到本篇文章的精髓。

2 Delay的原理

让单片机实现延时功能的方法有很多,比如定时器方法,比如循环跑i++的方法等。其中,比较准确的方案是使用定时器计数来做延时。

该方案下,先要做一个计数器(也可以使用现成的定时器计数器),每隔固定时间就会触发一次定时器中断,把计数的数值加一。举个例子,一个变量a,它的初始值为0;如果把a++语句放在一个1毫秒触发一次的函数里,a的数值就会每隔1毫秒增加1;当a还没发生溢出时,它的值就代表了定时器开始后经过了多少毫秒。这个a变量的数值类型可以定义为32位无符号类型;也可以定义为64位无符号类型,这样可以在溢出前定时更长的时间。然后,我们只要比较两次读到的a的数值,对这两次数值求差,就可以知道相对的间隔了多少毫秒。对于延时函数的实现方式,可以循环检测间隔时间是否大于等于设定的延时时间,小于的话就继续循环,直到延时时间到了(间隔时间大于等于设定的延时时间)才跳出循环。

头文件编写

我们先来给这个延时函数库想个容易识别的名字,大家都知道延时的英文名叫delay,我们这个库是为SWM320写的。那么,叫swm320_delay.h就再合适不过啦;这个名字也保持了与Synwit原厂库的命名方式一致。
头文件内容:914a23b81ed5b06f248b6956e3f66b4a.png

定义头文件时候要防止递归包含,所以我们加入下面两句话,意思是当没有定义时候才定义。

#ifndef __SWM320_DELAY_H

#define __SWM320_DELAY_H

...

...

#endif

我们的这个库是用C写的,考虑到以后可能会有C++代码要链接这个库,为了防止出现链接错误,我们事先做一些兼容处理:

#ifdef __cplusplus

extern "C" {

#endif

...

...

#ifdef __cplusplus

}

#endif

其中,__cplusplus是c++的自定义宏,如果有这个宏,就表明是c++要链接我们这个库了,于是给它一个extern “C”语句。extern “C”可以告诉编译器这是一个用C语言写的库,请用C的方式链接它。重要提示:如果c库被以C++的方式链接会报找不到函数的错误。

然后,我们的这个库里面的函数又依赖于Synwit原厂库,所以要引用SWM320.h这个头文件,这招叫做——站在巨人的肩膀上。

#include "SWM320.h"

定义一个用来初始化计数数值并对定时器做一些初始设置的Delay_Init函数。这里,我计划用5号定时器来实现delay函数的功能。

void Delay_Init(void); //对计数值初始化0 设置并开启5号定时器

定义一个计数数值加1的IncTick函数

void IncTick(void);//用5号定时器把计数值+1

定义一个获取计数数值的GetTick函数,这个函数在延时函数里用来获取计数数值,它要返回一个32位无符号整型数值。

uint32_t GetTick(void);//返回计数值

定义一个实现延时功能的delay函数,输入的延时数是一个32位无符号数。

void delay(__IO uint32_t Delay);//毫秒延时

其中,“两个下划线IO”在Cortex_M中是定义为volatile类型的。计时中断里修改变量需要加“两个下划线IO”, 即volatie,以防止程序因为优化提高访问速度而从cache中读取不是最新的数据。

把计数值定义为volatie类型,保证程序每次都从该变量的地址读取最新的数据,这一点很重要

源文件编写

源文件的命名与头文件一致,即swm320_delay.c。源文件的内容如下:779f78b55898ae2fd157eae9c53f8348.png

常规操作,开头要引用一下这个库对应的头文件swm320_delay.h。

#include "SWM320.h"

定义毫秒计数数值的32位无符号整型变量。一定把计数值定义为volatie类型。注意“两个下划线IO”不可省略,原因前面已经讲过了。

__IO uint32_t uwTick;//毫秒计数值

第一个是Delay_Init函数,它把定时器5用掉了,要注意功能冲突。

void Delay_Init(void)

{

uwTick=0;//从0开始计数

TIMR_Init(TIMR5, TIMR_MODE_TIMER, SystemCoreClock/1000, 1);

TIMR_Start(TIMR5);

}

这个函数会先定义uwTick的初始值为0;再设置5号定时器的中断每1毫秒触发一次;接着把定时器5开启。其中,TIMR_Init函数用于定时器初始化,它的第一个输入变量是要设置的定时器,有效值包括TIMR0、TIMR1、TIMR2、TIMR3、TIMR4、TIMR5,这里我们用TIMR5。一般来说,按人类正常的逻辑,会按顺序使用各个定时器。相信这个5号定时器是最少被用到的,可以最大限度避免功能冲突。先说TIMR_Init函数的第三个输入变量,定时的计数周期数。其中,SystemCoreClock是系统时钟频率的数值(这里,系统时钟定义为高速内部时钟,频率20000000,即一秒有20000000个周期)。我们为了设置1毫秒的计数周期数,可以把SystemCoreClock的数值除以1000来获得。TIMR_Init函数的第四个输入变量是中断使能,1启用,0不启用。这里我们就是要用中断触发来实现每隔1毫秒加一的功能的;所以设置1,把它启用。

回过头来再说TIMR_Init函数的第二个输入变量,它用于定时器的模式设置,有效值包括TIMR_MODE_TIMER(定时器模式),和TIMR_MODE_COUNTER计数器模式。当使用计数器模式时,可以通过TIMR_GetCurValue函数返回32位无符号整型的计数值。

既然已经有了计数器模式,为什么要多此一举地使用定时器模式来实现计数呢?
如果你想让计数更长,比如uint64_t类型,这时候就不能用TIMR_GetCurValue函数了。

于是,我为您提供了更多可能。

我提供了一种代码逻辑框架,只需要:

把 uint32_t uwTick 改为 uint64_t uwTick

把 uint32_t GetTick(void) 改为 uint64_t GetTick(void)

把 void delay(__IO uint32_t Delay) 改为 void delay(__IO uint64_t Delay)

然后,您就可以获得一个64位的计数器和延时函数了。

第二个IncTick函数,它把计数值加1。

void IncTick(void)

{

uwTick++;

}

第三个GetTick函数,它可以返回毫秒计数的数值

uint32_t GetTick(void)

{

return uwTick;

}

第四个TIMR5_Handler函数每隔1毫秒就会触发一次。触发以后,我们先通过TIMR_INTClr函数,清除5号定时器的中断标志。然后通过IncTick函数,把计数数值加1.

void TIMR5_Handler(void)

{

TIMR_INTClr(TIMR5);

IncTick();

}

第五个delay函数就是延时函数,输入变量是毫秒延时数,这里是32位无符号整型。它先获取当前计数值,然后在一个while循环里比较时间间隔是否大于等于设定的毫秒延时数。如果是小于,就是时间还没到,继续跑循环。如果是大于等于,就说明延时时间够了,则跳出循环......

b487eea5e65822456c9d2cd3530daaa7.gif

点“阅读原文”,查阅完整文章!