单片机裸机编程注意事项总结

单片机编程和PC端编程有所不同,笔者根据已有的粗浅的编程经验,列写几个认为比较重要的点。

首先对文中的名词作适当的约定。文章的“事件”可理解为“中断”,每一个“中断”发生时,ISR将发送对应于该“事件”的“消息”;“事件”通常理解为“异步事件”,为了方便,即使是固定周期的定时中断,亦称为“异步事件”。

中断服务程序(ISR)内不处理事务(或数据),将程序流程转移到主程序

ISR仅负责发送事件标志,以通知主程序(不称其为“应用程序”的原因是,文章的总结基于不使用RTOS的假定)发生了异步事件。但这并非绝对,在一些需要极精准的定时控制的或者数据必须在事件发生时马上被采集或处理的场合中,我们仍难以避免在ISR中作数据采集、处理或者执行控制任务,或者兼有之,但通常我们应保证在这种情况下ISR的执行时间仍然非常短。

如何通知主程序异步事件发生了呢?这里列出三种方案。

利用一组标志变量(或者为了节省存储空间,使用标志位),通过对标志变量的置位、复位来实现事件的通知和撤销(表明事件已被处理)。该方案伪代码如下

int main()

{

Init();

while(1)

{

if(event_flag)

{

event_flag=0;

Proc();

}

...

}

return 0;

}

void ISR()

{

清中断标志;

event_flag=1;

}

这一方案的伪代码仅表示了单个事件的情况,事实上事件(数量)可以任意定义。

方案二是上一方案的改进,考虑事件连续发生导致单片机来不及处理之的情况,我们利用队列来暂存未被处理的事件信息(若没有存储未被处理的事件的缓冲区, 那么单片机将丢失这些事件的信息,从而导致有些事件不被处理)。在这一方案中,我们依然在ISR中发送时间通知,但此时不再是置位某一标志,而是往事件队列 添加事件信息。在主函数的死循环中,则不断地检查事件队列是否为空,一旦非空,取出其中的事件信息,交由事件判断和事件处理函数处理。这个方案保证了即 使在极短的时间内连续发生两次或者数次同样的事件,依然不会丢失信息。

在嵌入式裸机程序中,队列通常用循环数组实现,而非链表。

这一方案的灵感源于我学习MFC的时候了解的“消息(队列)”机制。

方案二的伪代码如下

int main()

{

Init();

while(1)

{

if(事件队列非空)

{

从队列中取出一则消息;

判断事件类别;

处理之(调用事件处理函数);

}

}

return 0;

}

void ISR0()

{

清中断标志;

QInsert(事件0的信息);

}

void ISR1()

{

清中断标志;

QInsert(事件1的信息);

}

...

方案三是使用实时操作系统(RTOS)。对于RTOS的讲述,三言两语难以清楚明白,省略之。


除非使用了低速外设(如低端字符液晶),拒绝一切阻塞性延时

当你是新手,阻塞性延时意指单片机在执行延迟程序时,不再处理任何事务,直到延时完成。相应地,非阻塞性延时则是指在该种延时操作进行时,CPU并非原地踏步等待时间到,而是将程序流程切换至其它位置。非阻塞性延时有赖于任务切换,通常需要RTOS的支持,在裸机程序难以实现。


用宏定义和条件编译实现易修改、配置和移植性能

敏锐察觉程序中任何可能需要修改的参数/变量,用宏定义代替立即数。在具规模的程序中,易修改性能尤为重要,因此对此不可等闲视之。利用宏定义和条件编译结合实现便捷地裁剪和配置程序/功能。

合理地将程序结构设计成模块化、层次化的架构,对未来实现代码重用和移植大有裨益。


注意计算方式对执行效率的影响

举个例子,如果我们要用一个无符号整形对2整除,请使用移位操作而非"/"操作符,即使编译器也会优化。


ISR中不发送数据块、不延时

利用串口发送数据是常用的调试/监控方式,但是请勿在ISR中发送大量数据,以低波特率更加不可取。


统一接口

当我们熟悉数个型号的单片机时,我们经常会考虑在不同的单片机之间移植同一个程序框架,从而提高开发效率。如果统一底层驱动的接口、串口调试用的通信协议等等,将更加得心应手。


实不相瞒,第一次写博客,文笔与逻辑实在不怎么样,让您见笑了。

你可能感兴趣的:(总结)