datasheet

STM32 多路软定时器

2016-10-10来源: eefocus关键字:STM32  多路软定时器
不记得哪里听过这句话:一个产品的50%的代码用于实现功能,另外50%则用与于容错。可见容错的重要性。容错的方法有很多,其中超时机制是最常用的方法之一。超时机制,故名思议,需要使用到定时器,用定时器来产生定时节拍,然后检测对象是否在规定的时间内正常完成操作。当存在多个需要监控的对象时,如果只使用一个定时器来监控多个对象,则定时机制会产生紊乱;但如果使用多个定时器分别监控一个对象,则会导外设资源浪费或者匮乏。这个问题的解决方法是:用一个定时器外设,在它的基础上实现多路软定时器。这样的话,实际只是用了一个定时器外设,却可以获得多个定时器功能。
下面就来讲讲如何使用一个定时器外设来实现多路软定时器。还是基于我自己规范工程。
1、工程的修改
1)这里要用到定时器,必须使用到库文件stm32f10x_tim.c,所以将是stm32f10x_tim.c文件添加到STM32F10x_StdPeriod_Driver工程组中。
2)打开stm32f10x_conf.h文件,将原先屏蔽的:“#include stm32f10x_tim.h”语句的屏蔽去掉。
3)新建SoftTimer.c与SoftTimer.h两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将SofTimer.c文件添加到BSP工程组中。
 
2、SoftTimer.c与SoftTimer.h两个文件的编写
这里选择定时器2作为整个超时机制的“动力源”,设置TIM2的节拍为1000Hz,也就是1ms,代码如下:

#define MAX_SOFTTIMER 4

static Timer_typedef TimerList[10];

/*************************************************************
Function : SoftTimer_TimerInit
Description: 定时器配置
Input : none
return : none
*************************************************************/

static void SoftTimer_TimerInit(void)
{
u8 i = 0;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_TimeBaseStructure.TIM_Period = 2 - 1;//最大计数值为1
TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;//分频36000,时钟节拍为72M/36000/2=1000,即1ms
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟不分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//增计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_ARRPreloadConfig(TIM2, ENABLE);//自动重装

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//打开定时器更新中断
TIM_Cmd(TIM2, ENABLE);//打开定时器

for(i = 0; i < MAX_SOFTTIMER; i++) //初始化MAX_SOFTTIMER个软定时器
{
TimerList[i].Timeoutcnt = 1000001;//超时初值
TimerList[i].Timeout = 1000001; //超时的装载值
TimerList[i].Timeoutfuc = (void*)0;//超时的回调函数
TimerList[i].Parameter = (void*)0;//超时的参数
}
}

首先在这个函数之前,宏定义了一个最多软定时器的个数,可以根据情况而设置软定时器的个数。在这个函数中,除了配置了TIM2,还初始化了几个软定时器。我自定义了一个软定时器结构体Timer_typedef,用来新建软定时器用,这个定时器的定义在SoftTimer.h中给出,这里先提前看下它的结构:

typedef struct __TIMER
{
u32 Timeoutcnt; //超时计数值
u32 Timeout; //超时重装值
void (*Timeoutfuc)(void* parameter); //超时回调函数
void* Parameter; //回调传递的参数
u8 Timerflag; //超时方式
}Timer_typedef;

这个机构体是面向对象风格的, 就好像定义了一个对象:软定时器,它有着很多的属性:超时计数值、超时重装值、超时回调函数等等。
接下去再配置下TIM2的中断,代码如下:

/*************************************************************
Function : SoftTimer_Int_Config
Description: 定时器中断配置
Input : none
return : none
*************************************************************/
static void SoftTimer_Int_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //设置中断有优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

然后再编写一个总函数SoftTimer_Init()调用定时器配置代码,如下:

/*************************************************************
Function : SoftTimer_Init
Description: 软定时器初始化
Input : none
return : none
*************************************************************/
void SoftTimer_Init(void)
{
SoftTimer_TimerInit();
SoftTimer_Int_Config();
}

下面开始就是软定时器的实现方法。
首先需要编写SoftTimer_TimerStart()函数来打开某一路软定时器,代码如下:

/*************************************************************
Function : SoftTimer_TimerStart
Description: 软定时器开始计时
Input : TimerIdent-软定时器索引
TimeOut-超时计数值
Timeoutfuc(parameter)-超时回调函数
parameter-传递的参数
flag-定时方式:
-TIMER_ONESHOT-单次定时
-TIMER_PERIOD-周期定时
return : none
*************************************************************/
void SoftTimer_TimerStart(u8 TimerIdent, u32 TimeOut, void (*Timeoutfuc)(void* parameter), void* parameter, u8 flag)
{
if(TimerIdent >= MAX_SOFTTIMER)
{
return;
}
__disable_irq(); //关闭总中断,不可重入

TimerList[TimerIdent].Timeoutcnt = TimeOut; //设置超时计数初值
TimerList[TimerIdent].Timeout = TimeOut; //设置超时的转载值
TimerList[TimerIdent].Timeoutfuc = Timeoutfuc; //设置超时的回调函数
TimerList[TimerIdent].Parameter = parameter; //设置超时的参数
TimerList[TimerIdent].Timerflag = flag; //设置超时标志

__enable_irq(); //打开总中断
}

这段代码首先会判断定时器的索引是否正确,代码中创建了MAX_SOFTTIMER个软定时器,所以软定时器的索引不能超过MAX_SOFTTIMER。__disable_irq()关闭所有中断,这样的话,它下面的一些操作就不会被中断打断了,一般把这个函数成为不可重入函数。然后配置指定软定时器的超时时间,超时重装值,传递回调函数,传递回调参数以及计时的方式。尤其要注意的是:这里的给TimerList[TimerIdent].Timeoutfuc传递了一个实例回调函数,这种回调实现的方法,以后会很常见。
除了上面打开软定时器外,还可以编写许多人性化的函数。比如说,如果想要废除之前设置过的软定时器,我们可以编写一个SoftTimer_TimerReset()函数,将指定的软定时器重新设置为初始状态,代码如下:

/*************************************************************
Function : SoftTimer_TimerReset
Description: 软定时器复位
Input : none
return : none
*************************************************************/
void SoftTimer_TimerReset(u8 TimerIdent)
{
if(TimerIdent >= MAX_SOFTTIMER)
{
return;
}
TimerList[TimerIdent].Timeoutcnt = 1000001; //超时初值
TimerList[TimerIdent].Timeout = 1000001; //超时的装载值
TimerList[TimerIdent].Timeoutfuc = (void*)0; //超时的回调函数
TimerList[TimerIdent].Parameter = (void*)0; //超时的参数
}

当已经打开了一路软定时器,然后后想要改变他的超时时间,当然可以调用SoftTimer_TimerStart()函数重新配置,但是这种方法需要重新传递回调函数及回调函数参数,这样做显得十分繁琐,所以这里再编写一个SoftTimer_TimerRestart()函数,只改变超时时间,而不改变其他配置,代码如下:

/*************************************************************
Function : SoftTimer_TimerRestart
Description: 软定时器重启,只有之前启动过的软定时器才能重启
Input : none
return : none
*************************************************************/
void SoftTimer_TimerRestart(u8 TimerIdent, u32 TimeOut)
{
if(TimerIdent >= MAX_SOFTTIMER)
{
return;
}
if(TimerList[TimerIdent].Timeout != 1000001)//保证这路软定时器有效且已经曾经启动过
{
TimerList[TimerIdent].Timeoutcnt = TimeOut;//重新设置超时时间
TimerList[TimerIdent].Timeout = TimeOut; //重新设置超时转载值
}
}

当启动了一个定时任务,在它定时时间到之前如果想要取消掉定时的话,需要在编写一个函数:SoftTimer_TimerStop()来取消当前的定时,代码如下:

/*************************************************************
Function : SoftTimer_TimerStop
Description: 停止软定时器,停止正在计时的软定时器
Input : none
return : none
*************************************************************/
void SoftTimer_TimerStop(u8 TimerIdent)
{
if(TimerIdent >= MAX_SOFTTIMER)
{
return;
}
TimerList[TimerIdent].Timeoutcnt = 1000001;
}

接下去编写一个执行函数,让整个软定时器“活”起来,代码如下:

/*************************************************************
Function : SoftTimer_Execute
Description: 周期执行函数
Input : none
return : none
*************************************************************/
void SoftTimer_Execute(void)
{
u8 i = 0;
for(i = 0; i < MAX_SOFTTIMER; i++)
{
if((TimerList[i].Timeoutcnt != 0) && (TimerList[i].Timeoutcnt <= 1000000))
{
TimerList[i].Timeoutcnt--;
if(TimerList[i].Timeoutcnt == 0)
{
if(TimerList[i].Timerflag != TIMER_PERIOD)//单次计时
{
TimerList[i].Timeoutcnt = 1000001;//设置超时初值,超出1000000所以不会继续计数
}
else//周期计时
{
TimerList[i].Timeoutcnt = TimerList[i].Timeout;//重新设置超时计数值
}
TimerList[i].Timeoutfuc(TimerList[i].Parameter); //执行回调函数
}
}
}
}

这个函数会被TIM2中断服务程序调用,轮流将软定时器计数值递减,当计数值减到为0时,判断定时器的工作方式是单次定时还是周期定时,如果是周期定时,则重新转载定时计数值,否则设置一个不工作的初值,最后再执行回调函数。这样的话,软定时器功能就差不多实现了。
接下去给出SoftTimer.h的代码,这这个文件中,声明有些函数:

#ifndef __SOFTTIMER_H__
#define __SOFTTIMER_H__
#include "stm32f10x.h"

#define TIMER_ONESHOT 0 //单次定时
#define TIMER_PERIOD 1 //周期定时

typedef struct __TIMER
{
u32 Timeoutcnt; //超时计数值
u32 Timeout; //超时重装值
void (*Timeoutfuc)(void* parameter); //超时回调函数
void* Parameter; //回调传递的参数
u8 Timerflag; //超时方式
}Timer_typedef;

void SoftTimer_Init(void);
void SoftTimer_TimerStart(u8 TimerIdent, u32 TimeOut, void (*Timeoutfuc)(void* parameter), void* parameter, u8 flag);

void SoftTimer_TimerReset(u8 TimerIdent);
void SoftTimer_TimerRestart(u8 TimerIdent, u32 TimeOut);
void SoftTimer_TimerStop(u8 TimerIdent);
void SoftTimer_Execute(void);

#endif

3、stm32f10x_it.c的修改
这个文件需要添加一个TIM2的中断服务程序,并在这个函数中调用软定时器的执行函数SoftTimer_Execute(),代码如下:

/*************************************************************
Function : TIM2_IRQHandler
Description: 定时器2中断服务程序
Input : none
return : none
*************************************************************/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
SoftTimer_Execute();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}

4、main的编写
main函数需要初始化下定时器,然后在创建两个软定时器,并设置它们的参数:

/*************************************************************
Function : main
Description: main入口
Input : none
return : none
*************************************************************/
int main(void)
{
BSP_Init();
PRINTF("\nmain() is running!\r\n");
SoftTimer_Init();
SoftTimer_TimerStart(0, 2000, SoftTimer0Callback, (void*)"SoftTimer0 timeout", TIMER_ONESHOT);//打开软定时器0,超时时间:1s
SoftTimer_TimerStart(1, 1000, SoftTimer1Callback, (void*)"SoftTimer1 timeout", TIMER_PERIOD); //打开软定时器1,超时时间:2s

Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
SoftTimer_TimerStop(1);//关闭软定时器1
PRINTF("SoftTimer stop!\r\n");
Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
SoftTimer_TimerRestart(1, 500);//软定时器1重启,设置新的定时时间
PRINTF("SoftTimer restart, timeout:500ms!\r\n");
Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
Delay_ms(1000);
SoftTimer_TimerReset(1); //软定时器1复位
PRINTF("SoftTimer reset!\r\n");
while(1)
{

}
}

调用SoftTimer_TimerStart()打开了2路软定时器,然后再对软定时进行一系列的操作。
既然提交了回调函数,所以下面还要编写两个超时回调函数,如下:

/*************************************************************
Function : SoftTimer0Callback
Description: 软定时器0超时回调函数
Input : parameter-传递的参数
return : none
*************************************************************/
void SoftTimer0Callback(void *parameter)
{
PRINTF("%s\r\n", (char *)parameter);
}

/*************************************************************
Function : SoftTimer1Callback
Description: 软定时器1超时回调函数
Input : parameter-传递的参数
return : none
*************************************************************/
void SoftTimer1Callback(void *parameter)
{
LED1_Toggle();
PRINTF("%s\r\n", (char *)parameter);
}

在这两个回调函数中,串口打印出传递的信息,并在周期定时的回调函数中每回调一次就然LED闪烁一次。
 
5、测量
用串口线将开发板与电脑连接,然后打开串口调试软件。下载程序到开发板,运行。可以根据main函数里操作就可以得出现象了。
(1)第1s时,软定时器1的单次计时ONESHOT超时,则打印:SoftTimer1 timeout,在第2s时,软定时器0与软定时器1都超时,在分别打印:SoftTimer0 timeout和SoftTimer1 timeoutSoft,如下图所示:
STM32 多路软定时器 - ziye334 - ziye334的博客
(2)因为软定时器0是单次计时,所以后面它就不在工作了。软定时器1则是周期计时,在延时4s后再将它关闭,期间,它会打印出3次SoftTimer1 timeout,,有人可能会问,不是延时4s吗,怎么值打印了3次,原因是这里的SoftTimer_TimerStop()在赶在了第4次超时之前把软定时器1停止掉了,所以值打印了3次信息,如下图所示:
STM32 多路软定时器 - ziye334 - ziye334的博客
(3)在SoftTimer_TimerStop()后有延时了4s,所以在4s期间不会有任何信息。
(4)延时完4s后再调用了SoftTimer_TimerRestart(1, 500);重启软定时器1,并设置超时时间为500ms,之后在延时4s后,在回收了这个软定时器0,所以在这4s期间会打印出7个信息,每0.5s打印一次,如下图所示:
STM32 多路软定时器 - ziye334 - ziye334的博客

关键字:STM32  多路软定时器

编辑:什么鱼 引用地址:http://www.eeworld.com.cn/mcu/article_2016101030311.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32串口IAP
下一篇:STM32 串口DMA接收

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

STM32堆栈设置

1.堆和栈大小 定义大小在startup_stm32f2xx.sStack_Size      EQU     0x00000400                AREA    STACK, NOINIT, READWRITE, ALIGN=3Stack_Mem      
发表于 2019-04-16
STM32堆栈设置

STM32堆和栈(Heap & Stack)的资料理解

源起:在移植cjson的过程中,解析json包的时候发现动态内存分配不足而导致解析失败,为解决这一问题,而深入了解stm32的堆和栈。stm32的存储器结构。Flash,SRAM寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。可访问的存储器空间被分成8个主要块,每个块为512MB。FLASH存储下载的程序。SRAM是存储运行程序中的数据。而SRAM一般分这几个部分:静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率
发表于 2019-04-16
STM32堆和栈(Heap & Stack)的资料理解

STM32定义堆栈地址到ram区顶部

本设置针对stm32f103rbt6的设置,该芯片RAM大小为20kB,故RAM区地址范围为0x20000000—0x20005000,芯片信息如下图所示;第一步:设置.sct文件;;*************************************************************; *** Scatter-Loading Description Filegenerated by uVision ***; *************************************************************LR_IROM1 0x08000000 0x00020000  
发表于 2019-04-16
STM32定义堆栈地址到ram区顶部

STM32之程序如何防止堆栈溢出

近日为某个项目写了个草稿程序,即非正式程序,后来发现老是进入hardfaulthandler,原来是堆栈溢出,后仔细查看发现函数调用纵深太深,最多的时候可保持7个函数在堆栈中调用。因此有心得如下:一、函数调用不要纵深太深,即以下模式:main(){   fun1();}fun1(){  fun2();}fun2(){   fun3();}fun3(){  fun4();}fun4(){  fun5();}fun5(){  fun6();}fun6(){   fun7();}这样子main函数要调用fun1函数完成某个功能,则要一直调到
发表于 2019-04-16

stm32之堆栈

stm32中的堆栈设置keil编译完成时存储情况当编译成功时,会出现: BUILD://Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632Code:程序代码部分RO-data: 程序定义的常量const tempRW-data:已初始化的全局变量ZI-data:未初始化的全局变量片中的:flash=Code+RO-data+RW-dataRAM=RW-data+ZI-data通过上面的BUILD可以看出,这个程序已经用了1600多的RAM,为什么会出用到这么多的RAM呢?在startup_stm32f10x_md.s文件中存在:St
发表于 2019-04-16

说说STM32的堆栈与内存

1.概念这里所说的堆栈,是针对单片机所说的“堆”与“栈”,指的是内存中一片特殊用途的区域。而不是数据结构中的堆栈(虽然其实规则一样)。这里所说的内存,是指RAM,RAM包括SRAM,DRAM等。而不是什么手机内存卡之类。这里所说的flash,指的是用作为ROM的存储器,保存代码与常量数据。而不是动画制作。。。栈的生长方向:指的是入栈方向,从高地址向低地址生长叫做向下生长,或逆向生长;反过来就叫向上生长,或正向生长。STM32的栈是向下生长。2.内存中的堆栈安排确切地说,是keil mdk根据STM32的特性,对stm32的RAM甚至flash进行部署。编译工程后,在生成的.map文件里可以看到具体的安排。双击工程界面的工程根目录
发表于 2019-04-16
说说STM32的堆栈与内存

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved