1、什么要学习程序设计规范?
1、程序不仅要被计算机执行,还要给程序员阅读,以及被今后重复使用。
2、一个风格清爽而严谨、规范化的程序更容易被读懂,更容易被修改、排错和移植。
3、规范化编程、高度一致的风格和正确的习惯还有助于保持思维清晰,写出正确无误的代码。特别是一个开发团队共同工作时,规范一致化的编程尤其重要
4、每个初学者在项目初期都会因为不良编程习惯浪费大量时间,因此若能在开始写程序时就重视规范化问题,对顺利渡过提高阶段有很大帮助。
2、程序规范的基本原则?
原则:
(1)一致的代码风格,统一的变量名、函数名命名规则
(2)符合英语语法、可阅读的代码
(3)关键代码100%注释
(4)硬件有关、硬件无关代码分离,可移植性强
(5)每个对象都具有完善的封装,接口形式简洁、易用
(6)对所有资源严格要求硬件隔离层,软件中不允许直接访问硬件,不允许跨层调用。
(一)编程风格
(二)可移植性
1)消除CPU差异
若希望自己写的程序在不同处理器上运行,首先需要了解这些处理器的不同之处。其中包括硬件的不同以及特殊语法的差异。例如MSP430单片机没有位操作指令,8051单片机有,若两者之间程序需相互移植,首先需要消灭这个差异。
2)消除硬件差异
更改数码管硬件布线,无需重写段码表
3)封装
封装是指将软/硬件对象的属性与行为绑定在一起,并放置在一个模块单元内。该单元负责将所描述的属性隐藏起来,外界对其内部属性的所有访问只能通过提供的应用程序接口实现。
初学者常见问题:
4)应用程序接口(API)
接口是对软/硬件对象的抽象,对外提供的服务和访问功能,
即:对象封装后对外呈现成什么样?
原则:(1)使用方便(2)功能丰富(3)但不要过多过滥
【例】为液晶显示模块规划应用程序接口:
5)软件层次
例:菜单程序
(1)杜绝跨层调用
(2)功能模块可以任意拼接、组合
(3)要有硬件隔离层(HAL)
(4)更换任意层代码,上下层建筑不变
(三)版本管理、可配置
(1)尽可能减少软件副本
(2)类似的产品/设计尽可能公用同一代码
(3)利用宏定义配置功能
(4)永远在最新的代码上开发,保留最新版
三 前后台多任务程序设计
(一)前后台程序的基本概念
任务(Task) :指完成某一单一功能的程序
后台程序:对时间要求不严格的任务,通常在主循环内执行
前台程序:要求快速响应或者时间严格的任务,通常中断内
队列/缓冲:用于暂存后台来不及处理的前台事件
(二)前后台程序的编写基本原则
(1)任何一个任务都不能阻塞CPU。
每个任务都应主动结束,让出CPU。不能有等待、死循环、长延时等环节(对初学者来说是难点!)
(2)关注函数重入问题
可重入(Reentrancy):函数在自己调用自己的时候,不必担心数据被破坏。
从软件工程角度对函数可重入的作用可以解释为:具有可重入性的函数能够被多个任务同时调用。在后台程序中,任务都是顺序执行的,不存在多个任务同时调用一个函数的情况。但可能出现前台中断服务程序与后台任务同时调用某个函数。
因此对于前后台公用函数,必须是可重入的。
【例】:
(3)临界代码保护(Critical Code Protection)
临界代码:运行时不可分割的代码,这部分代码不允许被中断打断。
1) 依靠软件产生时间严格时序的程序段。
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*名称: Pulse 10us()
\*功能: 产生10us脉冲
\*入门参数: 无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void Pulse\_10us()
{
\_DINT(); // -------以下是临界代码区,不允许被中断
IO=1; // 置高
\_delay\_cycles(8); // 高电平持续10us (O赋值身耗2us)
IO=0; //置低
_EINTO0; // -------以上是临界代码区-----------
}
2)共享资源互斥性造成的临界代码
程序中任何可被占用的实体都称为“资源”。资源可以是硬件设备,如定时器、串口、打印机、键盘、LCD显示器等,也可以是变量、数组、队列、等数据。
被一个以上任务所占用的资源叫做“共享资源”。共享资源若不能同时被多任务占用,则具有“互斥性”(Mutual Exclusive)。
【例】:前后台同时调用液晶显示函数造成花屏
(4)临界代码的保护方法
- 关中断、开中断
隐患: 如果在A函数的临界代码区调用了另一个函数B,B函数内也有临界代码区,从B函数返回时,中断被打开了,这将造成A函数后续代码失去临界保护。所以,使用该方法时,不能在临界代码区调用任何具其他有临界代码的函数! - 利用硬件栈保存中断使能状态
优点:不影响函数调用
缺点:很多单片机的C语言不支持操作硬件栈 - 利用变量保存中断使能状态
缺点:每一段临界代码要消耗2-4字节内存 - 利用模拟栈保存中断使能状态
缺点:速度慢
典型错误1:在临界代码区调用其他临界代码
A()
{
int\_Disable();
B(); // 这期间中断是开的,所执行的代码不被临界保护
int\_Enable();
}
B()
{
int\_Disable();
// ...
int\_Enable();
}
典型错误2:在主函数和中断中都调用LCD操作函数
- LCD操作耗时长,不应放在中断中。
- LCD操作是临界代码,LCD是互斥性共享资源,LCD操作函数都是不可重入的,如果主函数里没有做临界代码保护,结果就是没多久屏幕就花了或呈死机效果。
讨论与总结
- 后台中任务顺序执行。每个后台任务中的内存(局部变量)在任务结束后可以全部释放,让给下一个任务使用。即使在RAM很少的处理器上也能同时执行众多任务。
- 后台中任务顺序执行。天然避免了后台任务资源互斥问题,但仍需考虑前后台之间的资源互斥问题。
- 前后台程序的结构灵活,实现形式与实现手段多样,是使用最广的程序结构,但缺乏架构标准,维护、升级、排错都很困难。
- 必须要程序员自己来判断和处理临界代码的隐患。
- 程序多任务的执行依靠每个任务的非阻塞性来保证,是编程最大的难点,下一节将介绍的FSM将是解决这一问题的利器!
四 FSM:状态机建模
1、什么是状态机?
有限状态自动机(Finite-State Machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
2、为什么要引入状态机?
流程图的缺陷:
1、不能用于描述“任何时候”发生的事件
2、无法描述“由外部事件决定流程”的程序
3、不适合描述带有阻塞的并发过程
4、难以描述大量的独立事件。
需要:能够描述并发结构的软件,能从行为的角度来描述软件,且能够根据模型生成代码,也能够对软件进行完整测试的新手段—状态机模型。
3、流程图无法描述的软件行为举例
(1)不能用于描述“任何时候”发生的事件
(2)不能用于描述由外部事件决定流程的事件
【例】:电子表有两个按键,但按键顺序完全由外部决定
(3)不适合描述带有阻塞的并发过程
【例】:一个单片机同时控制2台电动机完成运转时序
(4)不适合描述大量的独立事件
【例】:投币式自动咖啡机程序
流程图:投币->选咖啡->出咖啡->结束
然 而…
- 如果没有放咖啡杯就选择咖啡,会发生什么情况?
- 如果咖啡杯未满之前,用户取走了咖啡杯,会发生什么情况?
- 如果咖啡灌了一半时,用户取走咖啡杯,再放回,能否把剩下一半继续加满?
- 如果咖啡灌了一半时,用户取走咖啡杯,不再放回,剩下一半是否会加到下一用户杯中?
- 如果咖啡杯未满之前,用户取走了咖啡杯,然后按退币扭,该如何处理?
- 如果还没有完成一次点单之前,用户再次投币,会发生什么情况?
- 如果一杯咖啡尚未加满时,用户又点了另一种咖啡,会发生什么情况?
- 如果用户一次投入两杯咖啡的钱,该怎样处理?
- 如果咖啡存储容器已空,用户点单时能否正确提示?
- 如果咖啡杯未满之前,咖啡机空了,能否给用户退款?
…
4、引入“状态机” 的优势
上面这些例子中,软件下一步要执行的功能不仅与外界入信息有关,还与系统的“当前状态”有关。状态机就是一种基于“状态”与“事件”的软件描述手段!其优势在于:
(1) 能够处理并发。由于系统在每一时刻只能有唯一的状态,在每一个状态下,可能发生的事件也是有限的。因此系统中即使存在有大量的独立事件,软件描述也会简单得多。
(2) 能够消除阻寒。真正需要CPU处理的只有系统状态发生改变的那一刻,在系统等待事件到来的期间,是不需要CPU处理的。如果能够用状态与事件的形式来描述软件,能够将CPU从等待事件发生的过程中解放出来,从而生成无阻塞的代码。
(3) 能够降低系统的复杂度,提高可测性
5、状态机的表示方法:状态转移图
圆圈:状态 (有限个)
有向箭头:状态转移
Event事件:事件发生时将触发状态转移
Action动作:状态转移的同时执行动作
6、由状态转移图生成代码
方法1、在状态中判断事件(事件查询)
switch(State) //根据当前状态决定程序分支
{
case S0: //在s0状态
if(Event0)
{ //如果查询到Event0事件
Action0();
}
else if(Event1)
{ //如果查询到Event1事件
Action1();
State=S1;
}
else if(Event2)
{ //如果查询到Event2事件
Action3();
State=S2;
}
break;
case S1: //在s1状态
if(Event2)
{ //如果查询到Event2事件
Action2();
State=S2;
}
break;
......
方法2、在事件中判断状态(事件触发)
7、状态机建模举例
例1 电子表程序
====A键中断====
switch(Status) /\*根据当前状态处理A键所引发的状态跳跳转\*/
{
case DISP_TIMME: Status = DISP_DATE;break://时间显示时按A键,显示日期
case DISP_DATE: Status = DISP_SEC; break;
case DISP_SEC: Status= DISP_TIME; break;
case SET_HOUR: if(++Hour>23) Hour=0; break; //小时设置时按A键调整小时case SET\_MINUTE: if(++Min>59) Min=0; break;
case SET_MONTH: if(++Month>12) Month=1; break;
case SET_DATE: if(++Date>31) Date=0; break;
}
====B键中断=====
switch (Status) /\*/根据当前状态处理B键所引发的状态跳转/\*/
{
case DISP_TIME; Status= SET_HOUR; break; //时间显示时按B键,设置小时
case DISP_DATH; Status= SET_HOUR: break; //日期显示时按B键,设置小时
case DISP_SEC; Second 0; break; //秒钟显示时按B键,秒归零
case SET_HOUR; Status= SET_MINUTE; break; //小时设置时按B键,分钟设置
case SET_MINUTE; Status= SET_MONTH; break; //分钟设置时按B键月设置
case SET_MONTH; Status= SET_DATE; break; //月设置时按B键,日设置
case SET_DATE; Status= DISP_TIME; break; //日设置时按B键,显示时间
}
例2 长短按键识别
例3 事件序列匹配
五 模块化程序设计
1、模块化程序的原则
- 非阻寒性 不能长时间占用CPU,更不死等待某个事件的发生,尽快地让出CPU,供后续任务执行。
- 硬件隔离 屏蔽硬件特征,起到硬件隔离层的作用。上层软件将不涉及直接硬件操作。
- 模块独立性 各个程序库之间没有代码关联性(没有互相调用和隶属关系),在使用时,各个模块文件可以任意拼接、组合、拆分。
- 时间独立性 使用缓冲区、事件队列等手段,对时间关联性强的事件进行斩存和缓冲,使之可以被异步处理。
- 可移植性 代码都按照标准C的规范,很容易的移植到其他处理器上,或者其它硬件平台上。
- 100%注释 注释关键代码的功能或设计意图,以及每个函数的功能.入口出口参数、说明及注意事项,使用范例等。
- 开放性 方便日后修改、添加新的功能函数。
2、键盘模块化程序
3、带长短键识别的键盘模块化程序
4、串口收发模块化程序
5、数码管显示模块化程序
6、时钟和日历程序
六 事件触发多任务程序设计
1、什么是事件触发程序?
任务全部在中断内完成,主程序休眠。
可以看做前后台程序中,只有前台任务。
特点:1)实时性较好,事件响应较快;2)低功耗
2、事件触发程序的程序架构
中断:获取原始的事件信息。
事件引擎:判断何种事件发生,并调用相应处理程序。
(一个中断有多个中断源时)
事件处理程序:只负责某个事件的处理,要求非阻塞。
3、事件触发程序设计范例
有3个按键 (S1、S2、S3),按下为低电平。外部输入电压从ADC输入。设计一款超低功耗的电压表,要求用事件触发结构实现下列功能:
- 实现电压测量功能,每秒刷新显示2次电压测量值。
每次测量值由ADC连续采样4次求平均得到。 - 按下S1键时,斩停采样与显示刷新(保持功能)
- 按下S2键时,恢复采样与刷新。
- 按下S3键时,将采样数据从串口发出。
第3步:编写5个事件处理程序,此时并不关心事件如何发生如何被检测到,以及按什么顺序发生
第4步:测试。按照预先设定的顺序,依次调用5个事件处理程序(并不需要硬件上真的发生事件),模拟事件发生,即可进行代码测试。
第5步:编写事件引擎,在中断内检测和判判断事件,并分别调用相应的事件处理程序。
七 时间触发系统
时间触发系统可以理解为一种特殊的事件触发系统,但它另有自己的特点。
例子:一个医生,必须在一些护理人员的帮助下,通宵照顾十个危重病人,方案可以是
● 安排护理人员在某个病人出现严重问题时唤醒他
——事件触发
● 每小时闹铃,闹铃响时起床去探访每个病人
——时间触发
以每小时的间隔探访病人,医生能在严重并发症出现之前探访每个病人并安排合理的治疗。另外,工作量在整个晚上平摊。于是,所有病人都能平安地度过这个夜晚。
而前一种方式可能会出现护理人员在唤醒医生时,有多个病人都出现了严重并发症,需要依次做手术,后面的病人有可能来不及得到手术治疗。
这个医院的例子在事件驱动的系统中,即可能同时发生几个事件。需要处理同时发生的多个事件不但增加了系统复杂性,而且降低了对事件触发系统在所有情况下的行为做出预计的能力。
相比而言,在时间触发的嵌入式系统中,设计人员能够通过仔细安排可控的顺序,保证一次只处理一个事件。
许多嵌入式系统并不需要睡眠,采用时间触发方法有很多好处,有助于改善可靠性和安全性(如广泛应用于航空航天工业和汽车工业),主要原因在于系统的行为可以预计。
时间触发方法还能降低CPU的负荷,并减少存储器的使用量。
嵌入式系统需要执行的任务分为两种:
● **周期性任务**,比如每100ms执行一次
● **单次任务**,如在50ms的延迟后执行一次
考虑方案1:
void main(void)
{
Init\_System();
while(1)
{
X(); // 执行任务(耗时10ms)
Delay\_90ms(); // 延迟90ms
}
}
需要知道任务X的精确运行时间
这个运行时间永不变化—— 不可行
考虑方案2:
基于定时器中断,在一定的时刻调用函数
如果要支持多个任务(这些任务一般具有不同的运行时间并以不同的时间间隔运行),可以每个任务都使用一个定时器。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-CCOuvjW1-1715626479519)]
[外链图片转存中…(img-T1uRavlb-1715626479520)]
[外链图片转存中…(img-pCdGPei2-1715626479521)]
[外链图片转存中…(img-84CKyaAw-1715626479521)]
[外链图片转存中…(img-MQEmaZGC-1715626479522)]
[外链图片转存中…(img-omQ53IOm-1715626479523)]
[外链图片转存中…(img-VEXXMiln-1715626479524)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!