0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单片机处理多个任务程序框架设计

Q4MP_gh_c472c21 来源:嵌入式大杂烩 作者:ZhengNL 2020-09-03 17:52 次阅读

前言

前不久,我有位做测试的朋友转去做开发的工作,面试遇到了一个问题,他没明白,打电话问了我。题目大概就是:

单片机裸机开发时,单片机要处理多个任务,此时你的程序框架是怎样的呢?

这其实是个经典面试问题,我以前面试也被问过。

答案一:轮询系统

代码结构如:

左右滑动查看全部代码>>>

intmain(void)

{
init_something();

while(1)
{
do_something1();
do_something2();
do_something3();
}
}

这种结构大概是我们初学单片机的时候的代码结构。在没有外部事件驱动时,可以较好使用。

只答出了这种情况,印象分估计会比较低,多半凉凉。

答案二:前后台系统

代码结构如(该代码来自 《RT-Thread内核实现与应用开发实践指南》 ):

左右滑动查看全部代码>>>

intflag1=0;

intflag2=0;
intflag3=0;

intmain(void)
{
/*硬件相关初始化*/
HardWareInit();

/*无限循环*/
for(;;){
if(flag1){
/*处理事情1*/
DoSomething1();
}

if(flag2){
/*处理事情2*/
DoSomethingg2();
}

if(flag3){
/*处理事情3*/
DoSomethingg3();
}
}
}

voidISR1(void)
{
/*置位标志位*/
flag1=1;
/*如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到后台处理*/
DoSomething1();
}

voidISR2(void)
{
/*置位标志位*/
flag2=2;

/*如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到后台处理*/
DoSomething2();
}

voidISR3(void)
{
/*置位标志位*/
flag3=1;
/*如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到后台处理*/
DoSomething3();
}

此处,中断称为前台,main中的while循环称为后台。相比于循环系统,这种方式相对可以提高外部事件的实时响应能力。

可以回答出这种情况,印象分大概一半以上,会再细问。

答案三:升级版前后台系统(软件定时器法)

以前,学C语言时,常常听到有人说:指针是C语言的灵魂,没学会指针就是没学会C语言。。

后来,学单片机时,又听到有人说:中断和定时器是单片机的灵魂,没掌握中断与定时器就没学会单片机。。

大佬们都那么说了,那就拿定时器来搞点事情。定时器浑身都是宝,本篇笔记我们来介绍使用定时器(系统滴答定时器或者其它定时器)来做的裸机框架。软件定时器法也有另一种说法:时间片轮询法。

可以回答出这种情况,这场面试多半稳了。

下面以STM32单片机为例看看这种方法的使用。

站在巨人的肩膀上

开源项目—— MultiTimer ,项目仓库地址:

https://github.com/0x1abin/MultiTimer

1、MultiTimer 简介

MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

2、MultiTimer 的demo

左右滑动查看全部代码>>>

#include"multi_timer.h"

structTimertimer1;
structTimertimer2;

voidtimer1_callback()
{
printf("timer1timeout!
");
}

voidtimer2_callback()
{
printf("timer2timeout!
");
}

intmain()
{
timer_init(&timer1,timer1_callback,1000,1000);//1sloop
timer_start(&timer1);

timer_init(&timer2,timer2_callback,50,0);//50msdelay
timer_start(&timer2);

while(1){

timer_loop();
}
}

voidHAL_SYSTICK_Callback(void)
{
timer_ticks();//1msticks
}

3、MultiTimer 的移植、剖析

想要对MultiTimer 进行深入学习可阅读项目源码及如下这篇文章:

第6期 | MultiTimer,一款可无限扩展的软件定时器

自己动手,丰衣足食

1、代码模板

准备一个定时器,可以是系统滴答定时器,也可以是TIM定时器,使用这个定时器拓展出多个软件定时器。

比如我们系统中有三个任务:LED翻转、温度采集、温度显示。此时我们可以使用一个硬件定时器拓展出3个软件定时器,定义如下宏定义:

左右滑动查看全部代码>>>

#defineMAX_TIMER3//最大定时器个数

EXTvolatileunsignedlongg_Timer1[MAX_TIMER];
#defineLedTimerg_Timer1[0]//LED翻转定时器
#defineGetTemperatureTimerg_Timer1[1]//温度采集定时器
#defineSendToLcdTimerg_Timer1[2]//温度显示定时器

#defineTIMER1_SEC(1)//秒
#defineTIMER1_MIN(TIMER1_SEC*60)//分

在定时器初始化的时候也顺便给三个软件定时器进行初始化操作:

左右滑动查看全部代码>>>

/********************************************************************************************************

**函数:TIM1_Init,通用定时器1初始化
**------------------------------------------------------------------------------------------------------
**参数: arr:自动重装值 psc:时钟预分频数
**说明:定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft
**返回:void
********************************************************************************************************/
voidTIM1_Init(uint16_tarr,uint16_tpsc)
{
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
NVIC_InitTypeDefNVIC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

/*定时器TIM1初始化*/
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);
TIM_ClearFlag(TIM1,TIM_FLAG_Update);

/*中断使能*/
TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);

/*中断优先级NVIC设置*/
NVIC_InitStructure.NVIC_IRQChannel=TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM1,ENABLE);

//全局定时器初始化
for(inti=0;i< MAX_TIMER; i++)
 {
  g_Timer1[i] = 0;   
 }
}

在定时器中断中对这些软件定时器进行定时值做递减操作:

左右滑动查看全部代码>>>

/********************************************************************************************************

**函数:TIM1_IRQHandler,定时器1中断服务程序
**------------------------------------------------------------------------------------------------------
**参数:无
**返回:无
********************************************************************************************************/
voidTIM1_UP_IRQHandler(void)//TIM1中断
{
uint8i;

if(TIM_GetITStatus(TIM1,TIM_IT_Update)!=RESET)//检查TIM1更新中断发生与否
{
//-------------------------------------------------------------------------------
//各种定时间器计时
for(i=0;i< MAX_TIMER; i++)     // 定时时间递减     
   if( g_Timer1[i] ) g_Timer1[i]-- ;
  TIM_ClearITPendingBit(TIM1, TIM_IT_Update);     //清除TIMx更新中断标志 
 }
} 

我们在各个定时任务中给这些软件定时器赋予定时值,这些定时值递减到0则该任务会被触发执行,比如:

左右滑动查看全部代码>>>

voidTask_Led(void)

{
//----------------------------------------------------------------
//等待定时时间
if(LedTimer)return;
LedTimer=1*TIMER1_SEC;
//----------------------------------------------------------------
//LED任务主体
LedToggle();
}

voidTask_GetTemperature(void)
{
//----------------------------------------------------------------
//等待定时时间
if(LedTimer)return;
LedTimer=2*TIMER1_SEC;
//----------------------------------------------------------------
//温度采集任务主体
GetTemperature();
}

voidTask_SendToLcd(void)
{
//----------------------------------------------------------------
//等待定时时间
if(LedTimer)return;
LedTimer=2*TIMER1_SEC;
//----------------------------------------------------------------
//温度显示任务主体
LcdDisplay();
}

如此一来,每过1、2、4秒则分别触发LED翻转任务、温度采集任务、温度显示任务。

这里配置的最小定时单位为1秒,当然根据实际需要进行配置(定时器初始化),定时器初始化可以放在系统统一初始化函数里:

左右滑动查看全部代码>>>

/********************************************************************************************************

**函数:SysInit,系统上电初始化
**------------------------------------------------------------------------------------------------------
**参数:
**说明:
**返回:
********************************************************************************************************/
voidSysInit(void)
{
CpuInit();//配置系统信息函数
SysTickInit();//系统滴答定时器初始化函数
UsartInit(115200);//串口初始化函数,波特率115200
TIM1_Init(2000-1,36000-1);//定时周期1s
LedInit();//Led初始化
TemperatureInit();//温度传感器初始化
LcdInit();//LCD初始化
}

此时我们的main函数就可以设计为:

intmain(void)

{
//-----------------------------------------------------------------------------------------------
//上电初始化函数
SysInit();

//-----------------------------------------------------------------------------------------------
//主程序
while(1)
{
//-----------------------------------------------------------------------------------------------
//定时任务
Task_Led();
Task_GetTemperature();
Task_SendToLcd();
}
}

主函数主要是进行系统上电的一些初始化操作,接着是调用各定时任务函数。

本demo使用定时器1来扩展出3个软件定时器,如果TIM资源不够用,可以换用系统滴答定时器来做。如:

其中,时间基数可以根据实际需要进行调整。

2、实践(代入法)

套用以上模板,分享我的一个实例:

需要思考及注意的问题是给每个任务的定时值设置多大合适?这也是一些朋友有疑问的,这只能是自己对自己的任务做考虑,具体情况具体分析,给经验值、调试调整。

就如同常常有人问定义多大的数组合适?在使用RTOS时每个线程的线程栈大小设置多大合适、优先级设置为多少合适?这些都是需要我们自己进行思考的。

有模板/轮子套用是好事,但有些问题不能单单依靠模板,否则有可能把自己给套进去。

以上是以STM32为例的,其它单片机也是可以用这样子的思想的,包括51单片机

面对文首提到的面试问题,若是可以提到使用软件定时器来处理,进一步能清楚地表达出来,再进一步能写出一些伪代码,那这场面试多半是稳了。

不仅仅是为了面试,本文的方法是很经典的,小编曾经接触的产品项目中就有用到,很实用,值得学习掌握。方法掌握多了,实际应用的时候想用屠龙刀还是倚天剑根据实际情况选择使用即可。

以上就是本次的分享,如有错误,欢迎指出,谢谢。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6032

    文章

    44516

    浏览量

    633026
  • C语言
    +关注

    关注

    180

    文章

    7598

    浏览量

    136197
  • 定时器
    +关注

    关注

    23

    文章

    3237

    浏览量

    114471

原文标题:工程师实战:单片机裸机程序框架是怎样炼成的?

文章出处:【微信号:gh_c472c2199c88,微信公众号:嵌入式微处理器】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    单片机裸机开发时要处理几个任务程序

    单片机裸机程序框架##单片机裸机开发时,要处理多个任务
    发表于 11-22 07:05

    单片机裸机开发时程序框架是怎样的?

    前言 前不久,我有位做测试的朋友转去做开发的工作,面试遇到了一个问题,他没明白,打电话问了我。题目大概就是:在单片机裸机开发时,单片机处理多个任务
    发表于 12-09 07:31

    单片机裸机开发的程序框架是怎样的呢

    前言 前不久,我有位做测试的朋友转去做开发的工作,面试遇到了一个问题,他没明白,打电话问了我。题目大概就是:在单片机裸机开发时,单片机处理多个任务
    发表于 12-09 06:44

    聊聊我对单片机程序的整体框架设计的一些思路体会

    RTOS。或者使用RTOS,在整体思路上比较迷茫,不知从何入手,所以本文来聊聊我对单片机程序的整体框架设计的一些思路体会。为啥要讨论架构单片机系统开发人员的目标之一是在编程环境中创建固
    发表于 02-23 07:30

    单片机系统多任务实现方法

    考虑到成本等因素,大多数单片机系统的开发还是基于处理器直接编写,但开发者经常面临同时处理多个任务的要求,提出了一种
    发表于 11-21 16:47 750次下载

    PIC单片机程序结构框架

    为了快速掌握PIC单片机程序的基本结构,这里给出一个典型的程序结构框架
    发表于 06-27 13:41 5562次阅读

    单片机可以同时处理多个任务

    单片机可以按分时、顺序、中断的方法处理多个任务
    的头像 发表于 04-06 15:43 4786次阅读

    浅述单片机程序的整体框架设计的思路体会

    上比较迷茫,不知从何入手,所以本文来聊聊我对单片机程序的整体框架设计的一些思路体会。 为啥要讨论架构 单片机系统开发人员的目标之一是在编程环境中创建固件,以实现低成本系统、软件可靠性以
    的头像 发表于 06-27 11:34 4365次阅读
    浅述<b class='flag-5'>单片机</b><b class='flag-5'>程序</b>的整体<b class='flag-5'>框架设</b>计的思路体会

    单片机程序框架设计与实现

    前言什么是框架程序框架其实就类似一个文件大纲或者模板。因为写程序就和类似于写文章,如果没有大纲或者模板那么你写起来就会比较费劲。而。为什么要有框架
    发表于 11-04 12:36 2次下载
    <b class='flag-5'>单片机</b><b class='flag-5'>程序</b><b class='flag-5'>框架设</b>计与实现

    单片机程序框架

    单片机程序框架
    发表于 11-13 14:21 15次下载
    <b class='flag-5'>单片机</b><b class='flag-5'>程序</b><b class='flag-5'>框架</b>

    单片机裸机程序框架

    单片机裸机程序框架##单片机裸机开发时,要处理多个任务
    发表于 11-13 19:21 12次下载
    <b class='flag-5'>单片机</b>裸机<b class='flag-5'>程序</b><b class='flag-5'>框架</b>

    浅谈单片机裸机系统程序框架

    浅谈单片机裸机系统程序框架
    发表于 11-23 17:51 12次下载
    浅谈<b class='flag-5'>单片机</b>裸机系统<b class='flag-5'>程序</b><b class='flag-5'>框架</b>

    工程师实战:单片机裸机程序框架是怎样炼成的?

    前言 前不久,我有位做测试的朋友转去做开发的工作,面试遇到了一个问题,他没明白,打电话问了我。题目大概就是:在单片机裸机开发时,单片机处理多个任务
    发表于 11-26 11:06 16次下载
    工程师实战:<b class='flag-5'>单片机</b>裸机<b class='flag-5'>程序</b><b class='flag-5'>框架</b>是怎样炼成的?

    你的单片机裸机程序框架是怎样的?

    前言前不久,我有位做测试的朋友转去做开发的工作,面试遇到了一个问题,他没明白,打电话问了我。题目大概就是:在单片机裸机开发时,单片机处理多个任务
    发表于 11-26 13:21 13次下载
    你的<b class='flag-5'>单片机</b>裸机<b class='flag-5'>程序</b><b class='flag-5'>框架</b>是怎样的?

    处理特定任务单片机可简化复杂设计

    处理特定任务单片机可减轻主单片机或微处理器的任务和工作负荷,从而有助于简化各 种应用的设计流
    发表于 05-17 11:04 2次下载