STM32 多路软定时器

发布者:WanderlustHeart最新更新时间: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  多路软定时器 引用地址:STM32 多路软定时器

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

推荐阅读最新更新时间:2024-03-16 15:14

stm32 GPIO配置以及什么时候用 GPIO_InitStructure
问题一:配置GPIO的步骤 现在做一个最简单的GPIO控制LED的 GPIO初始化 (1)初始化结构体 GPIO_InitTypeDef GPIO_InitStructure; (2)使能相应的时钟(程序最初应该有#define RCC_GPIO_LED GPIOB 或其他组端口) RCC_APB2PeriphClockCmd(RCC_GPIO_LED , ENABLE); (3)对GPIO结构体初始化。 GPIO结构体: typedef struct { uint16_t GPIO_Pin; //选择管脚,是你想用到的管脚 GPIOSpeed_TypeDe
[单片机]
STM32 的优先级NVIC配置
1、NVIC优先级介绍 2、NVIC优先级比较 提条件1:组别优先顺序(第0组优先级最强,第4组优先级最弱):*强调内容***NVIC_PriorityGroup_0 NVIC_PriorityGroup_1 NVIC_PriorityGroup_2 NVIC_PriorityGroup_3 NVIC_PriorityGroup_4 前提条件2:“组”优先级别 “抢”占优先级别 “副”优先级别 前提条件3:同一组优先级别中,不同的抢占级别之间,其中一抢占级别正在做事,另外抢占级别不能打断他;(即”同一组优先级下的中断源间,没有中断嵌套“) 前提条件4:不同组优先级别间,依据优先级强弱,优先级别高的组的中断源可以打
[单片机]
<font color='red'>STM32</font> 的优先级NVIC配置
STM32学习札记--ADC的有关函数个人见解
STM32的ADC功能较为完善,个人理解的还不是很深入。一点点的吃透! 在学习ADC之前我们需要知道相关的ADC的配置 一:ADC的最关键的时钟需要使能,用的是HSI:RCC_HSICmd(ENABLE);//时钟源选择及配置,参见时钟树 二:既然是ADC采集,当然需要你设置需要采集的通道,需要利用的I/O口 1,使能相应IO口的时钟---- 配置 I/O的引脚,模式,速率,输出类型及是否上下拉! 2,使能相应ADC的时钟--- 配置ADC的工作方式等 三:基础工作做完后,当然是要准备去采集数据, 包含两种方式: 规则通道顺序配置和注入通道配置 个人理解两者的区别在于前者是连续转换已经设定的通道,后者是等
[单片机]
最简单的方式 创建STM32的工程(使用标准库)-2
前文已经介绍创建一个简单的F103RC芯片的MDK V4工程文件,下面介绍一些其它的设置项目。 1:设置Output 和List的输出目录。在上文中(如下图)STM32100E-EVAL这个文件夹就是原工程文件设置的Output和List输出目录。但是我已经将项目改为“MySTM32”, 显然我要搞个我自己命名的文件来用作Output和list的输出目录。先在MDK-ARM文件夹下新建一个文件夹叫:Output-List 注:下图中 MDK-ARM目录下的Project_STM3210B-EVAL.dep 以及另外4个.dep文件,都可以删了。 2:看图操作。点击魔术棒按钮,打开Options for Target &
[单片机]
最简单的方式 创建<font color='red'>STM32</font>的工程(使用标准库)-2
STM32定时器中断配置
1.使能时钟 定时器时钟 2.配置定时器结构体、使能 3.开启定时器中断,配置中断结构体 配置例子 #include tim.h #include stm32f10x.h void tim_config(void) { TIM_TimeBaseInitTypeDef TIMinitStructure;//定时器结构体 NVIC_InitTypeDef NVICinitStructure;//内核中断结构体 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//中断优先组函数 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM
[单片机]
<font color='red'>STM32</font><font color='red'>定时器</font>中断配置
STM32驱动ADC0809详解
开发环境与工具 Keil 5主芯片为 STM32F103RET6下载工具为 JLINKXCOM V2.0 串口助手PC 为 Win10 准备工作 购买 ADC0809 芯片 习惯购买元器件多买一个,方便替换验证。 因为做过一次验证之后,这个板子就没有用了,所以购买 DIP-28 宽体底座,让底座焊板子上,芯片插底座上,方便芯片的二次使用,节约成本。 PCB 打板 下图这种模块: STM32 要想驱动 ADC0809 这个芯片需要很多个引脚(不考虑复用的话,需要 16 个引脚),如果这些引脚都用杜邦线连接的话会很乱,如果哪个杜邦线再接触不好,那么对于程序的调试很不方便,所以我就采用核心板+底板的形式来实现,避
[单片机]
STM32 UART的使用过程
1、使用UART前必须启动相应的外设时钟,其主要用到固件库的RCC_APBnPeriphClockCmd函数。 使能UART1:使用RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE) 使能UART2:使用RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 , ENABLE) 2、使用中断进行UART操作的需要配置NVIC,设置中断优先级。如: /* Configure the NVIC Preemption Priority Bits */ NVIC_PriorityGroupConfig(NVIC_PriorityGr
[单片机]
关于STM32中RTC的校准方法
实现RTC 校准的核心之一是库文件Stm321f0x_bkp.c中的void BKP_SetRTCCalibrationValue (uint8_t CalibrationValue) 函数。谈到RTC校准的相关参考文档包括AN2604.pdf,AN2821.pdf和AN2821.zip。这三个文档都可以从STM32官方网站下载。 按照AN2604.pdf描述的原理,RTC 的校准值应在0-127之间。可实现的校准误差对应为0-121ppm。相当于每30天跑快的秒数为0-314s。 这里应注意的一个关键问题是,RTC只能对跑快进行校准,不能对跑慢进行校准。如果手表晶振的标称频率是32768Hz,设其可能的误差范围是 2Hz,则实
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved