嵌入式系统软件架构:
模块划分:
1> 模块划分既是一个.c文件和一个.h文件的结合,头问及爱你.h中是对于该模块接口的声明;
2> 某模块提供给其他模块调用的外部函数及数据需在.h文件中冠以extern关键字声明.
3> 模块内的函数和全局变量需在.c文件中冠以static关键字声明;
4> 永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇
编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量
嵌入式系统包括两类模块:
1> 硬件驱动模块,一种特定硬件对应的一个模块
2> 软件功能模块,其模块的划分应满足低偶合,高内聚的要求
多任务还是单任务:
单任务程序典型架构:
1> 从CPU复位时的指定地址开始执行
2> 跳转至汇编代码startup处执行
3> 跳转至用户主程序main执行,在main中完成
a: 初始化各硬件设备
b: 初始化各软件模块
c: 进入死循环,调用个模块的处理函数(操作系统是死循环,多线程程序的线
程处理函数是死循环,嵌入式系统软件是死循环,win32程序是死循环)
中断服务程序:(建立一个中断队列)
(1)不能返回值;
(2)不能向ISR传递参数;
(3) ISR应该尽可能的短小精悍;
(4) printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
硬件驱动模块:(一个硬件驱动模块应包含如下函数)
1> 中断服务程序ISR
2> 硬件初始化:
a: 修改寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率
等)
b: 将中断服务程序入口地址写入中断向量表
/* 设置中断向量表 */
m_myPtr = make_far_pointer(0l); /* 返回void far型指针void far *
*/
m_myPtr += ITYPE_UART; /* ITYPE_UART: uart中断服务程
序 */
/* 相对于中断向量表首地址的偏移 */
*m_myPtr = &UART _Isr; /* UART _Isr:UART的中断服务程序
*/
3> 设置CPU针对该硬件的控制线:
a. 如果控制线可作PIO(可编程I/O)和控制信号用,则设置CPU内部对应寄存器使其作
为控制信号;
b. 设置CPU内部的针对该设备的中断屏蔽位,设置中断方式(电平触发还是边缘触发)
4> 提供一系列针对该设备的操作接口函数。
例如,对于LCD,其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数;
而对于实时钟,其驱动模块则需提供获取时间、设置时间等函数。
C的面向对象化:
C语言嵌入式系统编程修炼之屏幕操作:
汉字处理:(对于包含较完整汉字库的系统而言,我们可以以上述规则计算字模的位置。但是如果仅仅是提供少量
汉字呢?譬如几十至几百个?最好的做法是:)
定义宏:
# define EX_FONT_CHAR(value)
# define EX_FONT_UNICODE_VAL(value) (value)
# define EX_FONT_ANSI_VAL(value) (value)
定义结构体:
typedef struct _wide_unicode_font16x16
{
WORD value; /* 内码 */
BYTE data[32]; /* 字模点阵 */
}Unicode; #define CHINESE_CHAR_NUM … /* 汉字数量 */
字模的存储用数组:
Unicode chinese[CHINESE_CHAR_NUM] = {
{
EX_FONT_CHAR("业")
EX_FONT_UNICODE_VAL(0x4e1a)
{0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50,
0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00} },
{ EX_FONT_CHAR("中")
EXFONTUNICODEVAL(0x4e2d)
{0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,
0x21, 0x08, 0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00} },
{ EX_FONT_CHAR("云")
EX_FONT_UNICODE_VAL(0x4e91)
{0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07,
0x00, 0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00} }, { EX_FONT_CHAR("件")
EX_FONT_UNICODE_VAL(0x4ef6) {0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40,
0x28, 0x40, 0x2f, 0xfe, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40} }
}
系统时间显示:(从NVRAM中可以读取系统的时间)
关于时间的显示,有一个效率问题。因为时间有其特殊性,那就是60秒才有一次分钟的变化,60分钟才有一次小
时变化,如果我们每次都将读取的时间在屏幕上完全重新刷新一次,则浪费了大量的系统时间。 一个较好的办法是我们在时
间显示函数中以静态变量分别存储小时、分钟、秒,只有在其内容发生变化的时候才更新其显示。
extern void DisplayTime(…)
{
static BYTE byHour,byMinute,bySecond;
BYTE byNewHour, byNewMinute, byNewSecond;
byNewHour = GetSysHour();
byNewMinute = GetSysMinute();
byNewSecond = GetSysSecond();
if(byNewHour!= byHour) { … /* 显示小时 */
byHour = byNewHour;
} if(byNewMinute!= byMinute) { … /* 显示分钟 */
byMinute = byNewMinute;
} if(byNewSecond!= bySecond) { … /* 显示秒钟 */
bySecond = byNewSecond;
}
}
动画显示:
菜单操作:
/* 将菜单的属性和操作"封装"在一起 */
typedef struct tagSysMenu { char *text; /* 菜单的文本 */
BYTE xPos; /* 菜单在LCD上的x坐标 */
BYTE yPos; /* 菜单在LCD上的y坐标 */
void (*onOkFun)(); /* 在该菜单上按下ok键的处理函数指针 */
void (*onCancelFun)(); /* 在该菜单上按下cancel键的处理函数指针 */
}SysMenu, *LPSysMenu;
static SysMenu menu[MENU_NUM] = {
{ "menu1", 0, 48, menu1OnOk, menu1OnCancel } ,
{ " menu2", 7, 48, menu2OnOk, menu2OnCancel } ,
{ " menu3", 7, 48, menu3OnOk, menu3OnCancel } ,
{ " menu4", 7, 48, menu4OnOk, menu4OnCancel} … };
/* 按下OK键 */
void onOkKey() {
menu[currentFocusMenu].onOkFun(); }
/* 按下Cancel键 */
void onCancelKey() {
menu[currentFocusMenu].onCancelFun(); }
模拟MessageBox函数:
/******************************************
/* 函数名称: MessageBox
/* 功能说明: 弹出式对话框,显示提醒用户的信息
/* 参数说明: lpStr --- 提醒用户的字符串输出信息
/* TYPE --- 输出格式(ID_OK = 0, ID_OKCANCEL = 1)
/* 返回值: 返回对话框接收的键值,只有两种 KEY_OK, KEY_CANCEL
/******************************************
typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE;
extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE)
{ BYTE keyValue = -1; ClearScreen(); /* 清除屏幕 */
DisplayString(xPos,yPos,lpStr,TRUE); /* 显示字符串 */
/* 根据对话框类型决定是否显示确定、取消 */
switch (TYPE)
{ case ID_OK:
DisplayString(13,yPos+High+1, " 确定 ", 0);
break;
case ID_OKCANCEL:
DisplayString(8, yPos+High+1, " 确定 ", 0);
DisplayString(17,yPos+High+1, " 取消 ", 0);
break; default: break;
}
DrawRect(0, 0, 239, yPos+High+16+4); /* 绘制外框 */
/* MessageBox是模式对话框,阻塞运行,等待按键 */
while( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) )
{
keyValue = getSysKey();
} /* 返回按键类型 */
if(keyValue== KEY_OK)
{ return ID_OK; }
else { return ID_CANCEL; }
}
嵌入式系统修炼之内存操作:
数据指针:
p++(或++p)的结果等同于:p = p+sizeof(int),而p-(或-p)的结果是p = p-sizeof(int)
unsigned char *p = (unsigned char *)0xF000FF00; *p=11;
以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写
入11
函数指针:
typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 */
/* 函数指针类型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*/
/* CPU启动后所执行第一条指令的位置 */
lpReset(); /* 调用函数 */
数组 vs 动态申请:(在调用处申请内存)
char *p=malloc(…);
if(p==NULL) …;
function(p);
… free(p);
p=NULL;
而函数function则接收参数p,如下:
void function(char *p)
{ … /* 一系列针对p的操作 */ }
给出原则:
(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组
越过界限就光荣地成全了一个混乱的嵌入式系统);
(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc
和free应成对出现!
关键字const:
const意味着"只读"。区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们
的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀:
const int SIZE = 10;
char a[SIZE]; /* 非法:编译阶段不能用到变量 */
关键字volatile:
C语言编译器会对用户书写的代码进行优化,譬如如下代码:
int a,b,c;
a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/
b = a;
a = inWord (0x100); /*再次读取I/O空间0x100端口的内容存入a变量*/
c = a;
很可能被编译器优化为:
int a,b,c;
a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/
b = a;
c = a;
但是这样的优化结果可能导致错误,如果I/O空间0x100端口的内容在执行第一次读操
作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,b和c的值应该不同。在变量a的定义前加上volatile
关键字可以防止编译器的类似优化,正确的做法是:
volatile int a;
volatile变量可能用于如下几种情况:
(1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);
(2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量);
(3) 多线程应用中被几个任务共享的变量。
阅读(810) | 评论(1) | 转发(0) |