电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
一个delay函数库的开发过程
分 享
扫描二维码分享
一个delay函数库的开发过程
松果派ONE
延时函数库
开发
xukejing
关注
发布时间: 2019-07-08
丨
阅读: 1984
# 1 前言 真是太惊喜了,本人竟然获得了电子芯吧客社区和松果派社区提供的松果派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原厂库的命名方式一致。 头文件内容: ![头文件](https://cf04.ickimg.com/bbsimages/201907/33b926e69d57c5ee997da88982e373a8.jpg "头文件") 定义头文件时候要防止递归包含,所以我们加入下面两句话,意思是当没有定义时候才定义。 ```c #ifndef __SWM320_DELAY_H #define __SWM320_DELAY_H ... ... #endif ``` 我们的这个库是用C写的,考虑到以后可能会有C++代码要链接这个库,为了防止出现链接错误,我们事先做一些兼容处理: ```c #ifdef __cplusplus extern "C" { #endif ... ... #ifdef __cplusplus } #endif ``` 其中,__cplusplus是c++的自定义宏,如果有这个宏,就表明是c++要链接我们这个库了,于是给它一个extern "C"语句。extern "C"可以告诉编译器这是一个用C语言写的库,请用C的方式链接它。 **重要提示:**如果c库被以C++的方式链接会报找不到函数的错误。 然后,我们的这个库里面的函数又依赖于Synwit原厂库,所以要引用SWM320.h这个头文件,这招叫做——站在巨人的肩膀上。 ```c #include "SWM320.h" ``` 定义一个用来初始化计数数值并对定时器做一些初始设置的Delay_Init函数。这里,我计划用5号定时器来实现delay函数的功能。 ```c void Delay_Init(void); //对计数值初始化0 设置并开启5号定时器 ``` 定义一个计数数值加1的IncTick函数 ```c void IncTick(void);//用5号定时器把计数值+1 ``` 定义一个获取计数数值的GetTick函数,这个函数在延时函数里用来获取计数数值,它要返回一个32位无符号整型数值。 ```c uint32_t GetTick(void);//返回计数值 ``` 定义一个实现延时功能的delay函数,输入的延时数是一个32位无符号数。 ```c void delay(__IO uint32_t Delay);//毫秒延时 ``` 其中,“两个下划线IO”在Cortex_M中是定义为volatile类型的。计时中断里修改变量需要加“两个下划线IO”, 即volatie,以防止程序因为优化提高访问速度而从cache中读取不是最新的数据。 把计数值定义为volatie类型,保证程序每次都从该变量的地址读取最新的数据,**这一点很重要**。 # 源文件编写 源文件的命名与头文件一致,即swm320_delay.c。源文件的内容如下: ![源文件](https://cf04.ickimg.com/bbsimages/201907/8f6888e68cdf12616a865e5c01e9f00e.jpg "源文件") 常规操作,开头要引用一下这个库对应的头文件swm320_delay.h。 ```c #include "SWM320.h" ``` 定义毫秒计数数值的32位无符号整型变量。一定把计数值定义为volatie类型。 **注意**“两个下划线IO”不可省略,原因前面已经讲过了。 ```c __IO uint32_t uwTick;//毫秒计数值 ``` 第一个是Delay_Init函数,它把定时器5用掉了,要注意功能冲突。 ```c 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函数了。 于是,我为您提供了更多可能。 我提供了一种代码逻辑框架,只需要: ```c 把 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。 ```c void IncTick(void) { uwTick++; } ``` 第三个GetTick函数,它可以返回毫秒计数的数值 ```c uint32_t GetTick(void) { return uwTick; } ``` 第四个TIMR5_Handler函数每隔1毫秒就会触发一次。触发以后,我们先通过TIMR_INTClr函数,清除5号定时器的中断标志。然后通过IncTick函数,把计数数值加1. ```c void TIMR5_Handler(void) { TIMR_INTClr(TIMR5); IncTick(); } ``` 第五个delay函数就是延时函数,输入变量是毫秒延时数,这里是32位无符号整型。它先获取当前计数值,然后在一个while循环里比较时间间隔是否大于等于设定的毫秒延时数。如果是小于,就是时间还没到,继续跑循环。如果是大于等于,就说明延时时间够了,则跳出循环。 ```c void delay(__IO uint32_t Delay) { uint32_t tickstart = GetTick(); uint32_t wait = Delay; while((GetTick() - tickstart) < wait) { } } ``` # Delay库的用法 前面已经写好了延时函数库,那么怎么在main文件里面使用它呢? 第一步要在main文件开头添加delay库头文件的引用。。。。。原谅我说了句大家都懂的废话. ```c #include "swm320_delay.h" ``` 如果因为粗心没有加这个库的引用也不要紧,因为编译器也会提示你找不到相关函数的,然后你就会恍然大悟,并仰天长叹“哎呀我的天啊,头文件忘引啦!!!!”。 你看,多有画面感。 在main函数里使用delay函数前,先要用Delay_Init函数做一下必要的初始化。逻辑上,Delay_Init里面包含了定时器操作,要放在SystemInit的后面。 然后,你就可以在你需要延时的地方使用delay函数了。举例: ```c delay(10); //延时10毫秒 delay(100); //延时100毫秒 delay(1000); //延时1秒 delay(10000); //延时10秒 ``` 比如,下图演示了在while循环里使用delay(100)来延时100毫秒,防止while里面的程序跑得太快。 ![一个delay函数用法的例子](https://cf04.ickimg.com/bbsimages/201907/a23a053afe4d24c666c7d6e2557b6550.jpg "一个delay函数用法的例子") # 小结 这个delay库已经添加到PineconePi的Github上,大伙儿无需重复造车轮,只要git clone下来就可以快乐地玩耍了。 再次强调,作者是在传授大家解决问题的方法和思维模式。当您学会这种方法后,您也能成为我们电子芯吧客社区的达人。最后,希望大伙儿都能get到本篇文章的精髓。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
1
)
xukejing
擅长:其他应用
关注
评论
(2)
登录后可评论,请
登录
或
注册
神马姐
284
天前...
写得不错,值得学习
1
回复
发布
阿紫
237
天前...
写的很好,也学习了很多
0
回复
发布
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交