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

前言

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

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

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

答案一:轮询系统

代码结构如:

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

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!\r\n");
}void timer2_callback()
{printf("timer2 timeout!\r\n");
}int main()
{timer_init(&timer1, timer1_callback, 1000, 1000); //1s looptimer_start(&timer1);timer_init(&timer2, timer2_callback, 50, 0); //50ms delaytimer_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[i] = 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[i] ) g_Timer1[i]-- ;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);          // 串口初始化函数,波特率115200TIM1_Init(2000-1, 36000-1); // 定时周期1sLedInit();                  // 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单片机。

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

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

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

-END-

推荐阅读:

    专辑|Linux文章汇总

    专辑|程序人生

    专辑|C语言

嵌入式Linux

微信扫描二维码,关注我的公众号 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467374.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c 语言指针教学视频,[C语言] 猎豹网校 C和指针视频教程

资源介绍21days_Cpp_二进制和十六进制.mp4xa0xa080x86_内联汇编.mp4xa0xa0ABG2C_for_循环.mp4xa0xa0ABG2C_关系运算符.mp4xa0xa0ABG2C_字符串.mp4xa0xa0ABG2C_循环.mp4xa0xa0ABG2C_数学运算.mp4xa0xa0ABG2C_更高级的运算符.mp4xa0xa0ABG2C_测试多个值.mp4xa0xa0ABG2C_终止循环.…

图解http

今天是周五&#xff0c;按照正常的情况&#xff0c;今天下班后将会经历一周中最愉快的时光&#xff0c;愉快的时间总是会有那么一些短暂&#xff0c;所以我就送给大家一个资料 「图解http」这是一个微信好友发给我的&#xff0c;他一直给我发送了很多很多消息&#xff0c;比如什…

为什么执行自己的程序要在前面加./

前言在Linux中&#xff0c;我们执行内置命令时&#xff0c;直接输入命令名称即可&#xff0c;如&#xff1a;$ mv a b #将a重命名为b而在执行自己写好的程序时&#xff0c;却要带上./&#xff0c;例如&#xff1a;$ hello hello: command not found $ ./hello hello world这是为…

C语言怎么计算数据类型范围?

之前在网上看到的一个讨论&#xff0c;是谁决定了数据类型的范围&#xff1f;比如说&#xff0c;怎么确定 char 就是 -128~127 &#xff0c;而不是 -127~128 呢&#xff1f;说下规定signed 的取值范围是 -(2N-1) to 2N-1 - 1unsigned 的取值范围是 0 to (2N-1) (2N-1 - 1)只要…

双十一为何规则复杂,套路多多

为啥不直接打5折?为了让你把“穷人”俩字写到自己脸上啊。 双十一快到了&#xff0c;今年我又一次有了不太想参加的感觉。作为一个阅读理解不太灵光的人&#xff0c;去年的活动我就整得不太明白——优惠券都是十块十块的&#xff0c;也不知道该咋用;还有预付款、整点秒杀之类的…

团建是什么鬼?

2012年我入职TCL&#xff0c;在TCL我体会到了团队建设的文化&#xff0c;也正是经历了那段职场&#xff0c;我明白了很多「可意味不可言传」。那时候我们经常五十成群出去吃饭、喝酒、唱歌&#xff1b;上学那会我很不喜欢团队活动&#xff0c;特别是室内的活动&#xff0c;总感…

Alpha 答辩总结

【Alpha展示评审表格】 小组序号小组名称格式&#xff08;20%&#xff09;内容&#xff08;20%&#xff09;PPT&#xff08;20%&#xff09;演讲&#xff08;20%&#xff09;答辩&#xff08;20%&#xff09;总分1天机组1515151516762PMS1617171616824“像我这么能打的还有五个…

一粒沙子变成芯片的全过程

推荐阅读&#xff1a;专辑|Linux文章汇总专辑|程序人生专辑|C语言嵌入式Linux微信扫描二维码&#xff0c;关注我的公众号

要学会拒绝

Img「聊天截图」这是今晚上跟一个朋友聊天的截图&#xff0c;之后、突然想到这个话题&#xff0c;今年因为疫情&#xff0c;很多人抱怨工作不好找&#xff0c;我这个朋友&#xff0c;也是在疫情之下&#xff0c;可能是干得不开心&#xff0c;也可能还是干得不开心&#xff0c;可…

linux设备模型之kset/kobj/ktype分析

1. 概述今天来聊一下Linux设备模型的基石&#xff1a;kset/kobject/ktype。sysfs文件系统提供了一种用户与内核数据结构进行交互的方式&#xff0c;可以通过mount -t sysfs sysfs /sys来进行挂载&#xff1b;Linux设备模型中&#xff0c;设备、驱动、总线组织成拓扑结构&#x…

VLAN 路由实验图解

VLAN 路由实验图解(交换机为cisco1900系列)注&#xff1a;PC1&#xff1a;192.168.1.2 255.255.255.0PC2&#xff1a;192.168.2.2 255.255.255.0PC3&#xff1a;192.168.1.3 255.255.255.0PC4&#xff1a;192.168.2.3 255.255.255.0Fa0/0.1&#xff1a;192.168.1.1 255.255.255…

第 120 场双周赛 解题报告 | 珂学家 | 前后缀拆解 启发式合并

前言 忘名可以再记&#xff0c;回忆永不再来 整体评价 好像有一段时间没写周赛题解了&#xff0c;_. 感觉今天手感特别好&#xff0c;下午的几场比赛&#xff0c;包括传智杯都能打出超神战绩。 T3这题属于前后缀拆解&#xff0c;然后单调栈上二分(可以引入哨兵机制)&#xf…

音频系统,Alsa 里面的buff 是怎么计算的?

相关文章(干货)Ai音箱和Linux音频驱动小谈Linux ALSA 图解我在MTK平台下调试音频ALSA我们知道声音是模拟信号&#xff0c;模拟信号转成数字信号就一定有大小&#xff0c;既然有大小&#xff0c;那我们就需要开辟内存来保存这些数据。---- 我们知道&#xff0c;视频流的一帧就是…

用一句话证明你是程序员,你会怎么说

这个文章没有正文突然想到的一个话题&#xff0c;感觉很有意思如果用一句话透露出你是一个程序员你会怎么表露自己了&#xff1f;留言偷偷告诉我&#xff1f;

空间换时间,查表法的经典例子

前言 上一篇分享了&#xff1a;C语言精华知识&#xff1a;表驱动法编程实践这一篇再分享一个查表法经典的例子。我们怎么衡量一个函数/代码块/算法的优劣呢&#xff1f;这需要从多个角度看待。本篇笔记我们先不考虑代码可读性、规范性、可移植性那些角度。在我们嵌入式中&#…

Linux内核系统架构介绍

28年前(1991年8月26日)Linus公开Linux的代码&#xff0c;开启了一个伟大的时代。这篇文章从进程调度&#xff0c;内存管理&#xff0c;设备驱动&#xff0c;文件系统&#xff0c;网络等方面讲解Linux内核系统架构。Linux的系统架构是一个经典的设计&#xff0c;它优秀的分层和模…

这道笔试题竟然运行不出错

#读者提供的面试题下面这张截图是一个读者在面试的时候遇到的题目&#xff0c;是哪个公司的我就不说出来了&#xff0c;我在微信朋友圈发了这个题目后&#xff0c;有几个好友给我留言说自己也写了这道题。题目&#xff1a;下面这段代码有什么问题&#xff1f;#后续然后我就用这…

android加号底部导航栏,EasyNavigation Android 底部导航栏████几行代码实现 Tab 导航(随意定制加号,带红点消息提示) @codeKK Android开源站...

几行代码轻松实现底部导航栏(Tab 文字图片高度随意更改)&#xff1b;中间可添加加号按钮&#xff0c;也可添加文字&#xff1b;(足够的属性满足你需要实现的加号样式)如果还不能满足、中间可添加自定义 View&#xff1b;Tab 中随意添加小红点提示、数字消息提示&#xff1b;点击…

在工厂的这几天

上周在工厂呆了6天支持我们产品量产&#xff0c;说下自己在工厂看到的一些事情&#xff0c;可能对大家对嵌入式行业有一定的认识&#xff0c;这样大家在选择方向会有些借鉴作用。产品由研发到生产是一个过程&#xff0c;只有经受过量产考验的产品&#xff0c;才能说这个产品真正…

Arm华为NXP睿赛德大咖云集!2020中国嵌入式技术大会嘉宾揭晓

展会即将开幕▲扫码领门票从电子信息产业趋势看&#xff0c;以自动驾驶、智能机器人和智慧安防为代表的AIOT&#xff08;智联网&#xff09;正在成为新一轮科技创新制高点。5G 布置将大大提升端侧智能&#xff0c;助力AIOT 发展。从技术层面看&#xff0c;AIOT 是AI 技术嵌入到…