模块化编程的分层设计经验Word文档下载推荐.docx
- 文档编号:19916258
- 上传时间:2023-01-12
- 格式:DOCX
- 页数:56
- 大小:417.33KB
模块化编程的分层设计经验Word文档下载推荐.docx
《模块化编程的分层设计经验Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《模块化编程的分层设计经验Word文档下载推荐.docx(56页珍藏版)》请在冰豆网上搜索。
lcd162x.c;
lcd856x.c或者lcd1602.c;
lcd12864.c;
lcd320240.c等,引出基本的初始化、定位、写点、写字节函数
动---物理层--文件lcd61202_io.c;
lcd61202_bus.c;
引出器件的基本读写函数
对应文件应用举例2:
如EEPROM的块写统一化
件|---器件层--文件ee24xx.c;
ee93xx.c;
ee_sdcard.c;
ee29xx.c;
ee28f.c;
ee39xx.c;
等
动---物理层--文件bus_i2c.c;
bus_spi.c等
一个大的单片机程序往往包含很多模块,我是这样组织的
1。
每一个C源文件都要建立一个与之名字一样的H文件,里面仅仅包括该C文件的函数的声明,其他的什么也不会有,比如变量的定义啊等等不应该有。
2。
建立一个所有的文件都要共同使用的头文件,里面当然就是单片机的管脚使用的定义,还有里面放那些需要的KEIL系统的头文件,比如#include<
reg52.h>
#include<
absacc.h>
等等,把这个文件命名为common.h,
或者干脆就叫main.h
3,每个C源文件应该包含自己的头文件以及那个共同的使用的头文件,里面还放自己本文件内部使用的全局变量或者以extern定义的全局变量
4。
主文件main.c里面包含所有的头文件包括那个共同使用的文件,main.c里面的函数可以再做一个头文件,也可以直接放在文件的开头部分声明就可以了,里面一般还有中断服务程序也放在main.c里面
5。
对于那些贯穿整个工程的变量,可以放在那个共同的使用的头文件里面,也可以用extern关键字在某个C源文件里面定义,哪个文件要使用就重复定义一下
6.建立工程的时候,只要把C源文件加到工程中,把H文件直接放到相应的目录下面就可以了,不需要加到工程里。
单片机系统模块化编程的一些想法
51核类型单片机是目前应用较为广泛的一款MCU,编写单片机程序常常成为嵌入式软件开发软件入门级的训练。
一般而言,51程序代码量少,考虑模块化程序相对较少。
一种常规做法就是主程序采用while循环,再者通过中断中设置一些标志位;
笔者在51单片机程序开发过程,发现公司的单片机程序更新很快,基本每个人都要修改一点,一段时间后原有代码想法都很难找到。
还有一种是移植操作系统后,然后进行代码规范化,比如移植UCOS-ii等嵌入式操作系统,但是往往代码量增加很快,对存储容量本来就少的51单片机有较大的压力。
51模块化程序设计的最重要问题,笔者认为就是找到一种合理的程序结构,而且它能胜任实际的51单片机程序开发。
考虑到文中前面提到的问题,笔者主要针对第一种主程序while循环结构进行修改。
首先增加任务结构体定义,其中函数指针pFun指向实际的任务函数,nDelay表示延时时间,以ms为单位,以下涉及时间都一样的。
而nRunme表示运行的次数,nPeriod表示运行的时间周期。
…
struct_TASK_DEFINE
{
void(code*pTask)(void);
//指向实际任务函数
UINT16nDelay;
//延时时间
UBYTE8nRunme;
//运行次数
UINT16nPeriod;
//运行周期
}S_TASK,*pS_TASK;
系统中设定全局的任务列表变量S_TASKSCH_TASK_G[TASK_TOTAL_NUM];
其中TASK_TOTAL_NUM为系统设定的任务最大数量。
在进入while主循环前,需要添加相应的任务,采用函数SCH_ADD_TASK(…),后面再详细阐述。
系统的主循环采用遍历的方法,实现实际任务的运行。
while
(1)
{
SCH_DISPATCH_TASK();
/*遍历实现任务切换*/
/*51系统进入空闲模式,定时中断能唤醒*/
SCH_Goto_Sleep();
}
…
SCH_DISPATCH_TASK遍历函数的主要实现代码如下,
UBYTEnIndex;
for(nIndex=0;
nIndex<
TASK_TOTAL_NUM;
nIndex++)
if((SCH_TASK_G+nIndex)->
nRunme>
0)
(*(SCH_TASK_G+nIndex)->
pTask)();
//运行实际的任务
(SCH_TASK_G+nIndex)->
nRunme--;
//运行次数减1
if((SCH_TASK_G+nIndex)->
nPeriod==0)/*执行一次型任务后删除之*/
{SCH_DEL_TASK(nIndex);
}
定时器实现1ms定时中断,在中断中进行任务刷新(SCH_UPDATE_TASK函数),这也是实现系统结构关键一步。
定时器可以采用定时Timer0,有的52型单片机也可以采用定时器Timer2,总之实现1ms时间的定时中断。
SCH_UPDATE_TASK()的主要实现代码如下
UBYTE8nIndex;
if((SCH_TASK_G+nIndex)->
nDelay==0)
nRunme++;
//运行次数加1
/*获得实际的时间周期*/
if((SCH_TASK_G+nIndex)->
nPeriod>
nDelay=(SCH_TASK_G+nIndex)->
nPeriod;
else
{(SCH_TASK_G+nIndex)->
nDelay--;
在进行主程序while循环前,必须要添加相应的任务函数SCH_ADD_TASK,其主要的实现代码如下:
UBYTE8SCH_ADD_TASK(void(code*pFun)(void),
constUINT16tDelay,
constUINT16tPeriod)
UBYTE8nIndex;
nIndex=0;
while((SCH_TASK_G+nIndex)->
pTask!
=NULL)&
&
(nIndex<
TASK_TOTAL_NUM))
{nIndex++;
if(nIndex==TASK_TOTAL_NUM)
{returnTASK_OVER_FLOW;
/*增加任务到列表中*/
SCH_TASK_G[nIndex]->
pTask=pFun;
SCH_TASK_G[nIndex]->
nDelay=tDelay;
nRunme=0;
SCH_TASK_G[nIndex]->
nPeriod=tPeriod;
returnnIndex;
主体代码基本实现,51系统开发主要的工作就是增加一个任务函数,在实际的任务实现相应的功能,这样构成的单片机系统就比较好控制,维护代码也很简单。
当然还有就是任务函数的执行时间必须控制1ms以内,即是每1ms时间标度中要执行的任务时间不超过1ms。
如果执行时间较长的任务,总有一些办法对其划分为较小的若干个小任务。
笔者在实际开发中也设想过一个问题,假定A任务每2ms执行一次,执行需要的时间为0.5ms;
而B任务每4ms执行一次,执行所需的时间为0.5ms。
如果两个任务运行在同一个时标(1ms)中,就可能导致运行单个时标运行任务超过1ms的限制;
由于4ms的间隔也是2ms延时的整数倍关系,执行完全有可能。
一种常见的解决方法是加大定时中断时间的时标,把1ms修改成2ms,但是同时系统的时间敏感度减少了。
笔者想到一种方法,设置
两个任务结构体中延时nDelay不同,A任务延时初值0,而B任务延时初值为1;
这样实际A任务执行时间标度为2*n,而B为4*m+1(n,m为正整数),而2*n!
=4*m+1(奇偶数),从而使A与B任务永远不可能同时在一个时标(1ms)中执行。
以上是在51单片机开发模块化设计的一些想法,在几个实际项目开发也得到较好的应用,当然可能还会有一些没有考虑的问题,欢迎各位提出更好的建议!
模块化设计原则:
高内聚
第一步:
创建头文件(源文件与头文件同名如delay.c与delay.h)
第二步:
防止重复包含处理
在.h文件里加入
#ifndefXXXX
#defineXXXX
.......
#endif
例如:
#ifndef_DELAY_H__
#define_DELAY_H__
第三步:
代码封装(内部调用【.h封装外部调用的部分】)
封装成函数或者宏定义以便提高可读性和可修改文件,尽量少用或者不用全局变量
第四步:
使用源文件
.c文件添加到文件中
模块化编程实例:
delay.h文件
#ifndef__DELAY_H__
#define__DELAY_H__
#defineucharunsingnedchar
#defineuintunsignedint
voiddelay50us(uintt);
voiddelay50ms(uintt);
delay.c文件
#include<
#include"
模块化编程实例.h"
voiddelayus(uintt)//延时函数
{uintj;
for(;
t>
0;
t--)
for(j=6245;
j>
j--);
voiddelayms(uintt)//延时函数
数码管.h文件
#define"
voiddispaytable(uchar*p);
voiddispayt(ucharnum0,ucharnum1,ucharnum2,ucharnum3,ucharnum4,ucharnum5,ucharnum6,ucharnum7,);
数码管.c文件
数码管.h"
unsignedcharcodesmg_du[]={0xfe,0xfd,0xfb,0xf7,0xef,
0xbf,0x7f};
unsignedcharcodesmg_we[]={0x00,0x00,0x3e,0x41,
0x41,0x41,0x3e,0x00};
voiddisplay_table(uchar*p)
uchari;
foe(i=0;
i<
8;
i++)
P1=smg_du[*p];
P2=smg_we[i];
delay_50us(20);
}
voiddisplay(ucharnum0,ucharnum1,ucharnum2,ucharnum3,ucharnum4,ucharnum5,ucharnum6,ucharnum7,)
P1=smg_du[mun0];
P2=smg_we[0];
P1=smg_du[mun1];
P2=smg_we[1];
P1=smg_du[mun2];
P2=smg_we[2];
P1=smg_du[mun3];
P2=smg_we[3];
P1=smg_du[mun4];
P2=smg_we[4];
P1=smg_du[mun5];
P2=smg_we[5];
P1=smg_du[mun6];
P2=smg_we[6];
P1=smg_du[mun7];
P2=smg_we[7];
mian.c文件
sbitrst=P3^6;
unsignedchartable[]={2,3,4,5,6,7,8,9};
voidmain()
rst=0;
while
(1)
{display_tale(table);
C语言高效编程
编写高效简洁的C语言代码,是许多软件工程师追求的目标。
本文就工作中的一些体会和经验做相关的阐述,不对的地方请各位指教。
第1招:
以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招——以空间换时间。
字符串的赋值。
方法A,通常的办法:
#defineLEN32
charstring1[LEN];
memset(string1,0,LEN);
strcpy(string1,“Thisisaexample!
!
”);
方法B:
constcharstring2[LEN]=“Thisisaexample!
”;
char*cp;
cp=string2;
(使用的时候可以直接用指针来操作。
)
从上面的例子可以看出,A和B的效率是不能比的。
在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。
B的缺点在于灵活性没有A好。
在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;
如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
该招数的变招——使用宏函数而不是函数。
举例如下:
方法C:
#definebwMCDR2_ADDRESS4
#definebsMCDR2_ADDRESS17
intBIT_MASK(int__bf)
return((1U<
<
(bw##__bf))-1)<
(bs##__bf);
voidSET_BITS(int__dst,int__bf,int__val)
__dst=((__dst)&
~(BIT_MASK(__bf)))|\
(((__val)<
(bs##__bf))&
(BIT_MASK(__bf))))
SET_BITS(MCDR2,MCDR2_ADDRESS,RegisterNumber);
方法D:
#definebmMCDR2_ADDRESSBIT_MASK(MCDR2_ADDRESS)
#defineBIT_MASK(__bf)(((1U<
(bs##__bf))
#defineSET_BITS(__dst,__bf,__val)\
((__dst)=((__dst)&
函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。
大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;
同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。
而宏函数不存在这个问题。
宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。
D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。
C方法是其变体,其中滋味还需大家仔细体会。
第2招:
数学方法解决问题
现在我们演绎高效C语言编写的第二招——采用数学方法来解决问题。
数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方法会对程序的执行效率有数量级的提高。
举例如下,求1~100的和。
方法E
intI,j;
for(I=1;
I<
=100;
I++){
j+=I;
方法F
intI;
I=(100*(1+100))/2
这个例子是我印象最深的一个数学用例,是我的计算机启蒙老师考我的。
当时我只有小学三年级,可惜我当时不知道用公式N×
(N+1)/2来解决这个问题。
方法E循环了100次才解决问题,也就是说最少用了100个赋值,100个判断,200个加法(I和j);
而方法F仅仅用了1个加法,1次乘法,1次除法。
效果自然不言而喻。
所以,现在我在编程序的时候,更多的是动脑筋找规律,最大限度地发挥数学的威力来提高程序运行的效率。
第3招:
使用位操作
实现高效的C语言编写的第三招——使用位操作,减少除法和取模的运算。
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。
一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。
方法G
intI,J;
I=257/8;
J=456%32;
方法H
I=257>
>
3;
J=456-(456>
4<
4);
在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;
而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。
当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的MSC,ARMC来看,效率的差距还是不小。
相关汇编代码就不在这里列举了。
运用这招需要注意的是,因为CPU的不同而产生的问题。
比如说,在PC上用这招编写的程序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。
所以只有在一定技术进阶的基础下才可以使用这招。
第4招:
汇编嵌入
高效C语言编程的必杀技,第四招——嵌入汇编。
“在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾”。
这种说法虽然偏激了一些,但是却有它的道理。
汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?
所以,为了获得程序的高效率,我们只好采用变通的方法——嵌入汇编,混合编程。
举例如下,将数组一赋值给数组二,要求每一字节都相符。
charstring1[1024],string2[1024];
方法I
for(I=0;
1024;
I++)
*(string2+I)=*(string1+I)
方法J
#ifdef_PC_
*(string2+I)=*(string1+I);
#else
#ifdef_ARM_
__asm
MOVR0,string1
MOVR1,string2
MOVR2,#0
loop:
LDMIAR0!
[R3-R11]
STMIAR1!
ADDR2,R2,#8
CMPR2,#400
BNEloop
#endi
方法I是最常见的方法,使用了1024次循环;
方法J则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。
这里有朋友会说,为什么不用标准的内存拷贝函数呢
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 模块化 编程 分层 设计 经验