单片机软件系统
设计教程
基础
要求
对教师党员的评价套管和固井爆破片与爆破装置仓库管理基本要求三甲医院都需要复审吗
• 熟练运用C语言编程。
• 熟练掌握和运用一款单片机。包括硬件
结构、寄存器、外设、了解基本汇编指
令等。
• 熟悉一款集成开发环境及编译器。
• 独立编写并调通2000行以上的小型程
序。
一、如何编写高质量软件
• 合理划分
函
关于工期滞后的函关于工程严重滞后的函关于工程进度滞后的回复函关于征求同志党风廉政意见的函关于征求廉洁自律情况的复函
数功能,合并常用函数
• 严格的函数、变量命名方法
• 合理划分软件层次。如硬件隔离层、驱
动程序层、功能函数层、应用层。
• 合理运用宏定义,提高程序可移植性
• 经常查看反汇编代码,练习优化指令。
函数划分
• 常用的功能集合,可以合理合并成一个函数
• 利用参数传递,将多个函数合并。
• 例如:某菜单需要将选中的菜单条反色显示,因此需要2个
中文显示函数:
DisplayString(char *ptr,char x,char y);
DisplayStringInvert(char *ptr,char x,char y);
可以合并成一个函数
DisplayString (char *ptr,char x,char y,bool
InvertFlag);
注意函数过分合并会造成运算性能下降。
函数与变量命名
• 严格按照 模块名_功能名的方法命名,按正
确的英文(甚至拼音也可)命名
• 单词首字母大写
• 例如:ADC_CailbrateZero()
CPU_SleepDelay(int Time_ms)
Flash_WriteWord(int Addr,int Word)
软件层次划分
• 合理划分软件层次,将使程序结构清晰
• 层次良好的软件,移植性强
• 对软件开发者来说,便于管理和维护,
甚至可实现“代码免维护”
• 大大提高开发速度。
• 例:在某液晶上需要做中文菜单、数字
显示、波形显示、滚动条等。为该应用
编写液晶驱动函数。
LCD驱动层划分范例
• 每一层含义明确,功
能完整独立
• 每一层只调用其下一
层的函数,禁止跨层
调用函数。
• 所有变量对于非其它
层函数不透明。禁止
跨层使用全局变量
• 层次划分越多,执行
效率越低
• 更改或更换任何一层
的函数,不影响其它
层函数的执行。
合理运用宏定义
• 1.用宏定义作硬件隔离
#define LED1_POUT P1OUT
#define LED1_PDIR P1DIR
#define LED1_BIT (BIT2) /*更换IO只需改以上3行*/
#define LED_ON LED1_POUT |= LED1_BIT /*亮*/
#define LED_OFF LED1_POUT &= ~LED1_BIT /*灭*/
/*更换单片机只需修改以上2行*/
用宏定义自动运算
#define OSC (1000)
#define BAUD (9600)
...
UBR00 = ((long)OSC*1000/8)/BAUD;
UBR10 = 0x00;
...
#define TIMER_PERIOD 12000
...
TH1=(unsigned int)(65536-TIMER_PERIOD)/256;/*51单片机*/
TL1=(unsigned int)(65536-TIMER_PERIOD)%256;
...
用宏定义作编译开关
#define AVE 0
#define RMS 1
#define PEK 2
#define UMODE AVE /AVE:平均值 RMS真有效值 PEK 峰值*/
...
#if (UMODE==RMS)
VAC_Ch1=(long int)TempIntV1*TempIntV1;
VAC_Ch2=(long int)TempIntV2*TempIntV2;
#endif
#if (UMODE==AVE)
VAC_Ch1=absv(TempIntV1);
VAC_Ch2=absv(TempIntV2);
#endif
...
用宏定义作功能开关
• #define ON 1
• #define OFF 0
• #define HI_LO 2
• #define LEV_2 1
• #define NONE 0
• #define MINORCUT OFF /*是否打开小值切除功能*/
• #define RS485 ON /*是否打开RS-485通讯功能*/
• #define DAC ON /*是否打开变送功能*/
• #define DEFAULT OFF /*是否打开参数备份功能*/
• #define ALARM_MODE NONE /*报警模式 NONE=无报警
LEV_2=双限 HI_LO=高低限 */
• #define OFFSET ON /*是否打开偏移补偿功能*/
• #define KEYTONE ON /*按键音*/
程序优化
• 打开编译器自带的优化选项。注意高级
别优化会删除部分“冗余”代码,或改动
程序结构,应注意挥发性变量
(volatile)
• 查看反汇编代码,看汇编代码是否有冗
余操作,合理修改。
二、软件结构
• 如何让CPU尽可能多地执行多项任务
• 如何让CPU尽可能快地响应各个事件
• 如何让程序尽可能少地占用RAM
• 如何让程序具有尽可能强的可扩展性
• 如何让编出的程序尽可能通用
• 如何减少编程工作量?如何减少错误概
率和调试工作量?
• 好的程序结构,可以解决上述问题
实时性
• 实时性最高的任务,需要零延迟立即响
应。应考虑硬件实现。如用
CPLD/FPGA、捕获等实现。
• 实时性较高任务,允许微秒级延迟的事
件,可用中断实现。但背景程序中不允
许长时间关闭中断。且中断处理时间不
宜过长。
• 实时性较低事件,可以查询。
事件查询
• 1.事件出现时间较长,允许响应延迟也较
长。如某个按键,可以在主循环内直接
查询
• 2.事件出现时间较短,小于主循环时间,
但允许响应延迟,可以中断置标志位,
主程序处理,完毕后清除标志。比如串
口数据桢判别。
• 3.情况2中,若事件无法产生中断,可以
定时中断查询,置标志位。主程序处理
顺序多任务程序
void main (void)
{
int counter = 0;
while (1) /* repeat forever */
{
SleepDelay_ms(10);
check_serial_io (); /* check for serial input */
process_serial_cmds (); /* process serial input */
check_kbd_io (); /* check for keyboard input */
process_kbd_cmds (); /* process keyboard input */
adjust_ctrlr_parms (); /* adjust the controller */
counter++; /* increment counter */
if(counter%100==0) {…}
}
}
并发多任务程序
void main()
{
while(1) {LPM0;}
}
串口中断
{
…
}
键盘中断
{
…
}
定时中断
{
…
}
低功耗程序首选结构
每个事件不宜执行时间过长
注意中断随机嵌套,注意保护临界代码
全部信息通过全局变量传递和保存
休眠
事件
B
事件
C
事件
A 事件
D
前后台程序
Void main()
{
while(1)
{
等待1秒延迟
读取电压
计算温度
显示温度
报警处理
}
}
定时中断
串口中断
最常见的程序结构
背景程序与前台程序之间通过
全局变量传递
注意中断用到全局变量的保护
注意临界代码的保护
主程序不能长时间关闭中断
中断程序尽量简短,尽快返
回。
注意避免函数递归调用
RTOS
• 嵌入一个微型实时操作系统(RTOS),可
以轻松实现多任务(进程)并发执行。
• 商品化或免费的的RTOS,如uC-OSII,
RTX-TINY,国产Small-RTOS等。
• 允许每个任务都是死循环,由RTOS负责
调度CPU程序指针分时执行每个任务。
• CPU、RAM开销较大,8位CPU或简单程
序不建议使用。
避免函数重入
• 重入(reentrant):指函数直接或间接调
用函数自身。
• 和PC不同,PC采用堆栈传递允许函数重
入。单片机采用静态变量传递,不允许
函数重入。
• 造成重入的原因:
1.递归调用。例如A函数调用B函数,B函
数调用C函数,C函数调用A函数。
避免函数重入
• 2.中断重入
假设主程序循环会调用A函数,中断也使
用了A函数,那么有可能在主循环A函数
执行一半的时候,中断,又执行A函数,
造成A函数原来的变量被更改。
3.硬件重入
假设主程序操作液晶显示,中断也操作
液晶显示,有可能在液晶时序一半的时
候,中断,时序重新开始,造成液晶收
到错误指令,发生不可恢复的错误。
临界代码保护
• 临界代码(Critical Code):指运行过程中
不允许被中断打断的代码。
• 几种产生临界代码的原因:
1.执行时间严格,不允许被延长。如软件
模拟串口,软件循环产生精确延迟,软
件模拟时间严格的异步时序(如1Wire总
线),不允许被打断。
临界代码保护
• 2.中断时间很长,某函数不允许长时间
暂停。比如液晶。
• 3.因为避免函数重入产生的临界代码。
a. 再写一个一样的函数,专供中断用。
b. 用reentrant关键字定义函数(堆栈传递
参数)
c. 进入临界代码前关中断,后开中断。
临界代码保护
• 2. 中断变量传递造成的临界代码:
例如,主程序要得到存有周期计数值的长整形变量 PeriodCnt。该值
在中断内被更新。
中断:
{
PeriodCnt= TAOF_Cnt*65536+TAR;
}
Main
{
…
Freq= 1.0/PeriodCnt;//错误!
_DINT();
TempL=PeriodCnt;
_EINT();
Freq= 1.0/ TempL; //正确!
}
对象化编程
• 单片机系统的任何一个独立单元/部件/外
设/功能集合,都可以抽象成一个对象
(Object)。
• 对象都有其属性、方法、事件。
• 以IO为例。
属性:方向、电平。
方法:置1,置0,取反,读取输入值,读
取方向…
事件:跳变,中断…
对象化编程
• IO1_DIR IO1_IN 作为其属性,可以被设
置。
• 方法 IO1_Set(Bool), IO1_Neg();
• 事件 IO1_Interrupt();
基于状态机编程
• 例:当某对象处于A状态时,假设发生了
B事件,就执行C代码,并且跳转到D状
态。
• 例:电子
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
。键A 键B
1.当显示时间时(状态1),按下键A,就
显示日期。
2.当显示日期时,按下A键,就显示秒
3.当显示秒时,按下A键,回到显示时间。
例:电子表
• 4.当显示时间时,按下B键,时闪烁,提示
设置“时”
• 5.当提示提示设置“时”,按B键,分闪烁
• 6.当分闪烁时按B键,月闪烁
• 7.当月闪烁时按B键,日闪烁
• 8.当日闪烁时按B键,恢复时间显示
• 9.时闪烁时,按A键,时+1,若超过12归0
• 10.分闪烁时,按A键,分+1,若超过59归0
例:电子表
• 11.月闪烁时,按A键,月+1,若超过12归0
• 12.日闪烁时,按A键,日+1,若超过31归0
• 13.显示秒时,按B键,秒归0
状态跳转图
正常显示
日期显示
A
秒显示
AA
设置时 设置分
设置月设置日
B
B
B
B
B
秒=0
B
分++
A
月++
A
日++ A
时++
A
系统抽象
• 属性可以为7种值
• 方法有5种
• 事件有2种
单片机软件系统设计教程
基础要求
一、如何编写高质量软件
函数划分
函数与变量命名
软件层次划分
LCD驱动层划分范例
合理运用宏定义
用宏定义自动运算
用宏定义作编译开关
用宏定义作功能开关
程序优化
二、软件结构
实时性
事件查询
顺序多任务程序
并发多任务程序
前后台程序
RTOS
避免函数重入
避免函数重入
临界代码保护
临界代码保护
临界代码保护
对象化编程
对象化编程
基于状态机编程
例:电子表
例:电子表
状态跳转图
系统抽象