我们从2011年坚守至今,只想做存粹的技术论坛。  由于网站在外面,点击附件后要很长世间才弹出下载,请耐心等待,勿重复点击不要用Edge和IE浏览器下载,否则提示不安全下载不了

 找回密码
 立即注册
搜索
查看: 2396|回复: 8

[产品新知] 单片机处理多个任务程序框架设计

[复制链接]
  • TA的每日心情

    昨天 20:55
  • 签到天数: 1 天

    [LV.1]初来乍到

    478

    主题

    247

    回帖

    1391

    积分

    二级逆天

    积分
    1391

    终身成就奖特殊贡献奖

    发表于 2020-9-4 09:02:44 | 显示全部楼层 |阅读模式
    [paragraph]单片机裸机开发时,单片机要处理多个任务,此时你的程序框架是怎样的呢?
    这其实是个经典面试问题,我以前面试也被问过。
    答案一:轮询系统
    代码结构如:
    左右滑动查看全部代码>>>
    int main(void)
    { init_something();  while(1) {  do_something1();        do_something2();        do_something3(); }}这种结构大概是我们初学单片机的时候的代码结构。在没有外部事件驱动时,可以较好使用。
    只答出了这种情况,印象分估计会比较低,多半凉凉。
    答案二:前后台系统
    代码结构如(该代码来自 《RT-Thread内核实现与应用开发实践指南》 ):
    左右滑动查看全部代码>>>
    int flag1 = 0;
    int flag2 = 0;int flag3 = 0;int main(void){ /* 硬件相关初始化 */ HardWareInit(); /* 无限循环 */ for (;;) {   if (flag1) {     /* 处理事情 1 */     DoSomething1();   }   if (flag2) {     /* 处理事情 2 */     DoSomethingg2();   }   if (flag3) {     /* 处理事情 3 */     DoSomethingg3();   } }}void ISR1(void){ /* 置位标志位 */ flag1 = 1; /* 如果事件处理时间很短,则在中断里面处理 如果事件处理时间比较长,在回到后台处理 */ DoSomething1();}void ISR2(void){ /* 置位标志位 */ flag2 = 2; /* 如果事件处理时间很短,则在中断里面处理 如果事件处理时间比较长,在回到后台处理 */ DoSomething2();}void ISR3(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"
    struct Timer timer1;struct Timer timer2;void timer1_callback(){    printf("timer1 timeout!");}void timer2_callback(){    printf("timer2 timeout!");}int main(){    timer_init(&timer1, timer1_callback, 1000, 1000); //1s loop    timer_start(&timer1);        timer_init(&timer2, timer2_callback, 50, 0); //50ms delay    timer_start(&timer2);        while(1) {                timer_loop();    }}void HAL_SYSTICK_Callback(void){    timer_ticks(); //1ms ticks}3、MultiTimer 的移植、剖析
    想要对MultiTimer 进行深入学习可阅读项目源码及如下这篇文章:
    第6期 | MultiTimer,一款可无限扩展的软件定时器
    自己动手,丰衣足食
    1、代码模板
    准备一个定时器,可以是系统滴答定时器,也可以是TIM定时器,使用这个定时器拓展出多个软件定时器。
    比如我们系统中有三个任务:LED翻转、温度采集、温度显示。此时我们可以使用一个硬件定时器拓展出3个软件定时器,定义如下宏定义:
    左右滑动查看全部代码>>>
    #define  MAX_TIMER            3            // 最大定时器个数
    EXT volatile unsigned long    g_Timer1[MAX_TIMER]; #define  LedTimer             g_Timer1[0]  // LED翻转定时器#define  GetTemperatureTimer  g_Timer1[1]  // 温度采集定时器#define  SendToLcdTimer       g_Timer1[2]  // 温度显示定时器#define  TIMER1_SEC        (1)              // 秒#define  TIMER1_MIN        (TIMER1_SEC*60)  // 分在定时器初始化的时候也顺便给三个软件定时器进行初始化操作:
    左右滑动查看全部代码>>>
    /********************************************************************************************************
    ** 函数: TIM1_Init, 通用定时器1初始化**------------------------------------------------------------------------------------------------------** 参数: arr:自动重装值 psc:时钟预分频数** 说明: 定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft** 返回: void ********************************************************************************************************/void TIM1_Init(uint16_t arr, uint16_t psc){    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_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(int i = 0; i < MAX_TIMER; i++) {  g_Timer1 = 0;    }}在定时器中断中对这些软件定时器进行定时值做递减操作:
    左右滑动查看全部代码>>>
    /********************************************************************************************************
    ** 函数: TIM1_IRQHandler,  定时器1中断服务程序**------------------------------------------------------------------------------------------------------** 参数: 无** 返回: 无 ********************************************************************************************************/void TIM1_UP_IRQHandler(void)   //TIM1中断{ uint8 i;  if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)  // 检查TIM1更新中断发生与否 {  //-------------------------------------------------------------------------------  // 各种定时间器计时  for (i = 0; i < MAX_TIMER; i++)     // 定时时间递减        if( g_Timer1 ) g_Timer1-- ;  TIM_ClearITPendingBit(TIM1, TIM_IT_Update);     //清除TIMx更新中断标志  }} 我们在各个定时任务中给这些软件定时器赋予定时值,这些定时值递减到0则该任务会被触发执行,比如:
    左右滑动查看全部代码>>>
    void Task_Led(void)
    { //---------------------------------------------------------------- // 等待定时时间 if(LedTimer) return; LedTimer = 1 * TIMER1_SEC; //---------------------------------------------------------------- // LED任务主体 LedToggle();}void Task_GetTemperature(void){ //---------------------------------------------------------------- // 等待定时时间 if(LedTimer) return; LedTimer = 2 * TIMER1_SEC; //---------------------------------------------------------------- // 温度采集任务主体 GetTemperature();}void Task_SendToLcd(void){ //---------------------------------------------------------------- // 等待定时时间 if(LedTimer) return; LedTimer = 2 * TIMER1_SEC; //---------------------------------------------------------------- // 温度显示任务主体 LcdDisplay();}如此一来,每过1、2、4秒则分别触发LED翻转任务、温度采集任务、温度显示任务。
    这里配置的最小定时单位为1秒,当然根据实际需要进行配置(定时器初始化),定时器初始化可以放在系统统一初始化函数里:
    左右滑动查看全部代码>>>
    /********************************************************************************************************
    ** 函数: SysInit, 系统上电初始化**------------------------------------------------------------------------------------------------------** 参数: ** 说明: ** 返回: ********************************************************************************************************/void SysInit(void){ CpuInit();                  // 配置系统信息函数 SysTickInit();              // 系统滴答定时器初始化函数 UsartInit(115200);          // 串口初始化函数,波特率115200 TIM1_Init(2000-1, 36000-1); // 定时周期1s LedInit();                  // Led初始化 TemperatureInit();          // 温度传感器初始化 LcdInit();                  // LCD初始化}此时我们的main函数就可以设计为:
    int main(void)
    { //-----------------------------------------------------------------------------------------------  // 上电初始化函数 SysInit();   //-----------------------------------------------------------------------------------------------  // 主程序 while (1) {  //-----------------------------------------------------------------------------------------------   // 定时任务  Task_Led();  Task_GetTemperature();   Task_SendToLcd(); }}主函数主要是进行系统上电的一些初始化操作,接着是调用各定时任务函数。
    本demo使用定时器1来扩展出3个软件定时器,如果TIM资源不够用,可以换用系统滴答定时器来做。如:
    其中,时间基数可以根据实际需要进行调整。
    2、实践(代入法)
    套用以上模板,分享我的一个实例:
    需要思考及注意的问题是给每个任务的定时值设置多大合适?这也是一些朋友有疑问的,这只能是自己对自己的任务做考虑,具体情况具体分析,给经验值、调试调整。
    就如同常常有人问定义多大的数组合适?在使用RTOS时每个线程的线程栈大小设置多大合适、优先级设置为多少合适?这些都是需要我们自己进行思考的。
    有模板/轮子套用是好事,但有些问题不能单单依靠模板,否则有可能把自己给套进去。
    以上是以STM32为例的,其它单片机也是可以用这样子的思想的,包括51单片机。
    面对文首提到的面试问题,若是可以提到使用软件定时器来处理,进一步能清楚地表达出来,再进一步能写出一些伪代码,那这场面试多半是稳了。
    不仅仅是为了面试,本文的方法是很经典的,小编曾经接触的产品项目中就有用到,很实用,值得学习掌握。方法掌握多了,实际应用的时候想用屠龙刀还是倚天剑根据实际情况选择使用即可。
    回复

    使用道具 举报

    该用户从未签到

    30

    主题

    4174

    回帖

    0

    积分

    百元学习allegro

    积分
    0

    终身成就奖优秀斑竹奖

    发表于 2020-9-4 09:08:36 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    1052

    回帖

    0

    积分

    二级逆天

    积分
    0

    社区居民终身成就奖优秀斑竹奖

    QQ
    发表于 2020-9-4 09:22:36 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    23

    主题

    1832

    回帖

    2332

    积分

    二级逆天

    积分
    2332

    终身成就奖

    QQ
    发表于 2020-9-4 09:31:38 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    6740

    回帖

    3

    积分

    二级逆天

    积分
    3

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    发表于 2020-9-4 11:08:38 | 显示全部楼层
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    2024-9-27 09:10
  • 签到天数: 29 天

    [LV.4]偶尔看看III

    4

    主题

    2235

    回帖

    1760

    积分

    二级逆天

    积分
    1760

    终身成就奖特殊贡献奖优秀斑竹奖

    QQ
    发表于 2020-9-4 12:40:42 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    1541

    回帖

    0

    积分

    二级逆天

    积分
    0

    终身成就奖优秀斑竹奖

    发表于 2020-9-4 14:33:07 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    6218

    回帖

    8730

    积分

    二级逆天

    积分
    8730

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    QQ
    发表于 2020-9-7 07:42:25 | 显示全部楼层
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-8-6 09:13
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    44

    主题

    5757

    回帖

    1万

    积分

    三级逆天

    积分
    10058

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    QQ
    发表于 2020-9-7 08:07:30 | 显示全部楼层
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    论坛开启做任务可以
    额外奖励金币快速赚
    积分升级了


    Copyright ©2011-2024 NTpcb.com All Right Reserved.  Powered by Discuz! (NTpcb)

    本站信息均由会员发表,不代表NTpcb立场,如侵犯了您的权利请发帖投诉

    平平安安
    TOP
    快速回复 返回顶部 返回列表