51单片机程序优化

51单片机程序优化
51单片机程序优化

程序优化

由于单片机的性能同电脑的性能是天渊之别的,无论从空间资源上、内存资源、工作频率,都是无法

与之比较的。PC 机编程基本上不用考虑空间的占用、内存的占用的问题,最终目的就是实现功能就可以了。对于单片机来说就截然不同了,一般的单片机的Flash 和Ram 的资源是以KB 来衡量的,可想而知,单片机的资源是少得可怜,为此我们必须想法设法榨尽其所有资源,将它的性能发挥到最佳,程序设计时必须遵循以下几点进行优化:

1. 使用尽量小的数据类型

能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变

量就不要用长整型(long int),能不使用浮点型(float)变量就不要使用浮点型变量。当然,在定义变

量后不要超过变量的作用范围,如果超过变量的范围赋值,C 编译器并不报错,但程序运行结果却错了,而且这样的错误很难发现。

2. 使用自加、自减指令

通常使用自加、自减指令和复合赋值表达式(如a-=1 及a+=1 等)都能够生成高质量的

程序代码,编译器通常都能够生成inc 和dec 之类的指令,而使用a=a+1 或a=a-1 之类

的指令,有很多C 编译器都会生成二到三个字节的指令。

3. 减少运算的强度

可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。

(1)求余运算

N= N %8 可以改为N = N &7

说明:位操作只需一个指令周期即可完成,而大部分的C 编译器的“%”运算均是调用子程序来

完成,代码长、执行速度慢。通常,只要求是求2n 方的余数,均可使用位操作的方法来代替。

(2)平方运算

N=Pow(3,2) 可以改为N=3*3

说明:在有内置硬件乘法器的单片机中(如51 系列),乘法运算比求平方运算快得多, 因为浮点数

的求平方是通过调用子程序来实现的,乘法运算的子程序比平方运算的子程序代码短,执行速度快。(3)用位移代替乘法除法

N=M*8 可以改为N=M<<3

N=M/8 可以改为N=M>>3

说明:通常如果需要乘以或除以2n,都可以用移位的方法代替。如果乘以2n,都可以生成左移

的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果。如N=M*9 可以改为N=(M<<3)+M;

(4)自加自减的区别

例如我们平时使用的延时函数都是通过采用自加的方式来实现。

void DelayNms(UINT16 t)

{

UINT16 i,j;

for(i=0;i

for(j=0;i<1000;j++)

}

可以改为

void DelayNms(UINT16 t)

{

UINT16 i,j;

for(i=t;i>=0;i--)

for(j=1000;i>=0;j--)

}

说明:两个函数的延时效果相似,但几乎所有的C 编译对后一种函数生成的代码均比前一种代码少1~3 个字节,因为几乎所有的MCU 均有为0 转移的指令,采用后一种方式能够生成这类指令。

4. while 与do...while 的区别

void DelayNus(UINT16 t)

{

while(t--)

{

NOP();

}

}

可以改为

void DelayNus(UINT16 t)

{

do

{

NOP();

}while(--t)

}

说明:使用do…while循环编译后生成的代码的长度短于while 循环。

5. register 关键字

void UARTPrintfString(INT8 *str)

{

while(*str && str)

{

UARTSendByte(*str++)

}

}

可以改为

void UARTPrintfString(INT8 *str)

{

register INT8 *pstr=str;

while(*pstr && pstr)

{

UARTSendByte(*pstr++)

}

}

说明:在声明局部变量的时候可以使用register 关键字。这就使得编译器把变量放入一个多用途的寄存

器中,而不是在堆栈中,合理使用这种方法可以提高执行速度。函数调用越是频繁,越是可能提高代码的速度,注意register 关键字只是建议编译器而已。

6. volatile 关键字

volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在

哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。一般来说,volatile 关键字只用在以下三种情况:

a) 中断服务函数中修改的供其它程序检测的变量需要加volatile(参考本书高级实验程序)

b) 多任务环境下各任务间共享的标志应该加volatile

c) 存储器映射的硬件寄存器通常也要加volatile 说明,因为每次对它的读写都可能由不同意义

总之,volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素

更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

7. 以空间换时间

在数据校验实战当中,CRC16 循环冗余校验其实还有一种方法是查表法,通过查表可以更加快获得

校验值,效率更高,当校验数据量大的时候,使用查表法优势更加明显,不过唯一的缺点是占用大量的空间。

//查表法:

code UINT16 szCRC16Tbl[256] = {

0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,

0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,

0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,

0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,

0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,

0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,

0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,

0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,

0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,

0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,

0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,

0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,

0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,

0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,

0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,

0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,

0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,

0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,

0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,

0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,

0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,

0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,

0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,

0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,

0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,

0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,

0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,

0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,

0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,

0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,

0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,

0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0

};

UINT16 CRC16CheckFromTbl(UINT8 *buf,UINT8 len)

{

UINT16 i;

UINT16 uncrcReg = 0, uncrcConst = 0xffff;

for(i = 0;i < len;i ++)

{

uncrcReg = (uncrcReg << 8) ^ szCRC16Tbl[(((uncrcConst ^ uncrcReg) >> 8)

^ *buf++) & 0xFF];

uncrcConst <<= 8;

}

return uncrcReg;

}

如果系统要求实时性比较强,在CRC16 循环冗余校验当中,推荐使用查表法,以空间换时间。

8. 宏函数取代函数

首先不推荐所有函数改为宏函数,以免出现不必要的错误。但是一些基本功能的函数很有必要使用宏

函数来代替。

UINT8 Max(UINT8 A,UINT8 B)

{

return (A>B?A:B)

}

可以改为

#define MAX(A,B) {(A)>(B)?(A):(B)}

说明:函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,cpu 也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些cpu 时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。

9. 适当地使用算法

假如有一道算术题,求1~100 的和。

作为程序员的我们会毫不犹豫地点击键盘写出以下的计算方法:

UINT16 Sum(void)

{

UINT8 i,s;

for(i=1;i<=100;i++)

{

s+=i;

}

return s;

}

很明显大家都会想到这种方法,但是效率方面并不如意,我们需要动脑筋,就是采用数学算法解决问题,使计算效率提升一个级别。

UINT16 Sum(void)

{

UINT16 s;

s=(100 *(100+1))>>1;

return s;

}

结果很明显,同样的结果不同的计算方法,运行效率会有大大不同,所以我们需要最大限度地通过数

学的方法提高程序的执行效率。

10. 用指针代替数组

在许多种情况下,可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相

比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。下面的代码作用是相同的,但是效率不一样。

UINT8 szArrayA[64];

UINT8 szArrayB[64];

UINT8 i;

UINT8 *p=szArray;

for(i=0;i<64;i++)szArrayB[i]=szArrayA[i];

for(i=0;i<64;i++)szArrayB[i]=*p++;

指针方法的优点是,szArrayA 的地址装入指针p 后,在每次循环中只需对p 增量操作。在数组索引

方法中,每次循环中都必须进行基于i 值求数组下标的复杂运算。

11. 强制转换

C 语言精髓第一精髓就是指针的使用,第二精髓就是强制转换的使用,恰当地利用指针和强制转换不但

可以提供程序效率,而且使程序更加之简洁,由于强制转换在C 语言编程中占有重要的地位,下面将已五个比较典型的例子作为讲解。

例子1:将带符号字节整型转换为无符号字节整型

UINT8 a=0;

INT8 b=-3;

a=(UINT8)b;

例子2:在大端模式下(8051 系列单片机是大端模式),将数组a[2]转化为无符号16 位整型值。

方法1:采用位移方法。

UINT8 a[2]={0x12,0x34};

UINT16 b=0;

b=(a[0]<<8)|a[1];

结果:b=0x1234

方法2:强制类型转换。

UINT8 a[2]={0x12,0x34};

UINT16 b=0;

b= *(UINT16 *)a; //强制转换

结果:b=0x1234

例子3:保存结构体数据内容。

方法1:逐个保存。

typedef struct _ST

{

UINT8 a;

UINT8 b;

UINT8 c;

UINT8 d;

UINT8 e;

}ST;

ST s;

UINT8 a[5]={0};

s.a=1;

s.b=2;

s.c=3;

s.d=4;

s.e=5;

a[0]=s.a;

a[1]=s.b;

a[2]=s.c;

a[3]=s.d;

a[4]=s.e;

结果:数组a 存储的内容是1、2、3、4、5。方法2:强制类型转换。

typedef struct _ST

{

UINT8 a;

UINT8 b;

UINT8 c;

UINT8 d;

UINT8 e;

}ST;

ST s;

UINT8 a[5]={0};

UINT8 *p=(UINT8 *)&s;//强制转换

UINT8 i=0;

s.a=1;

s.b=2;

s.c=3;

s.d=4;

s.e=5;

for(i=0;i

{

a[i]=*p++;

}

结果:数组a 存储的内容是1、2、3、4、5。

例子4:在大端模式下(8051 系列单片机是大端模式)将含有位域的结构体赋给无符号字节整型值方法1:逐位赋值。

typedef struct __BYTE2BITS

{

UINT8 _bit7:1;

UINT8 _bit6:1;

UINT8 _bit5:1;

UINT8 _bit4:1;

UINT8 _bit3:1;

UINT8 _bit2:1;

UINT8 _bit1:1;

UINT8 _bit0:1;

}BYTE2BITS;

BYTE2BITS Byte2Bits;

Byte2Bits._bit7=0;

Byte2Bits._bit6=0;

Byte2Bits._bit5=1;

Byte2Bits._bit4=1;

Byte2Bits._bit3=1;

Byte2Bits._bit2=1;

Byte2Bits._bit1=0;

Byte2Bits._bit0=0;

UINT8 a=0;

a|= Byte2Bits._bit7<<7;

a|= Byte2Bits._bit6<<6;

a|= Byte2Bits._bit5<<5;

a|= Byte2Bits._bit4<<4;

a|= Byte2Bits._bit3<<3;

a|= Byte2Bits._bit2<<2;

a|= Byte2Bits._bit1<<1;

a|= Byte2Bits._bit0<<0;

结果:a=0x3C

方法2:强制转换。

typedef struct __BYTE2BITS

{

UINT8 _bit7:1;

UINT8 _bit6:1;

UINT8 _bit5:1;

UINT8 _bit4:1;

UINT8 _bit3:1;

UINT8 _bit2:1;

UINT8 _bit1:1;

UINT8 _bit0:1;

}BYTE2BITS;

BYTE2BITS Byte2Bits;

Byte2Bits._bit7=0;

Byte2Bits._bit6=0;

Byte2Bits._bit5=1;

Byte2Bits._bit4=1;

Byte2Bits._bit3=1;

Byte2Bits._bit2=1;

Byte2Bits._bit1=0;

Byte2Bits._bit0=0;

UINT8 a=0;

a = *(UINT8 *)&Byte2Bits

结果:a=0x3C

例子5:在大端模式下(8051 系列单片机是大端模式)将无符号字节整型值赋给含有位域的结构体。方法1:逐位赋值。

typedef struct __BYTE2BITS

{

UINT8 _bit7:1;

UINT8 _bit6:1;

UINT8 _bit5:1;

UINT8 _bit4:1;

UINT8 _bit3:1;

UINT8 _bit2:1;

UINT8 _bit1:1;

UINT8 _bit0:1;

}BYTE2BITS;

BYTE2BITS Byte2Bits;

UINT8 a=0x3C;

Byte2Bits._bit7=a&0x80;

Byte2Bits._bit6=a&0x40;

Byte2Bits._bit5=a&0x20;

Byte2Bits._bit4=a&0x10;

Byte2Bits._bit3=a&0x08;

Byte2Bits._bit2=a&0x04;

Byte2Bits._bit1=a&0x02;

Byte2Bits._bit0=a&0x01;

方法2:强制转换。

typedef struct __BYTE2BITS

{

UINT8 _bit7:1;

UINT8 _bit6:1;

UINT8 _bit5:1;

UINT8 _bit4:1;

UINT8 _bit3:1;

UINT8 _bit2:1;

UINT8 _bit1:1;

UINT8 _bit0:1;

}BYTE2BITS;

BYTE2BITS Byte2Bits;

UINT8 a=0x3C;

Byte2Bits= *(BYTE2BITS *)&a;

12. 减少函数调用参数

使用全局变量比函数传递参数更加有效率。这样做去除了函数调用参数入栈和函数完成后参数出栈所

需要的时间。然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。

13. switch 语句中根据发生频率来进行case 排序

switch 语句是一个普通的编程技术,编译器会产生if-else-if 的嵌套代码,并按照顺序进行比较,

发现匹配时,就跳转到满足条件的语句执行。使用时需要注意。每一个由机器语言实现的测试和跳转仅仅是为了决定下一步要做什么,就把宝贵的处理器时间耗尽。为了提高速度,没法把具体的情况按照它们发生的相对频率排序。换句话说,把最可能发生的情况放在第一位,最不可能的情况放在最后。

14. 将大的switch 语句转为嵌套switch 语句

当switch 语句中的case 标号很多时,为了减少比较的次数,明智的做法是把大switch 语句转为嵌

套switch 语句。把发生频率高的case 标号放在一个switch 语句中,并且是嵌套switch 语句的最外

层,发生相对频率相对低的case 标号放在另一个switch 语句中。比如,下面的程序段把相对发生频率

低的情况放在缺省的case 标号内。

UINT8 ucCurTask=1;

void Task1(void);

void Task2(void);

void Task3(void);

void Task4(void);

……………

void Task16(void);

switch(ucCurTask)

{

case 1: Task1();break;

case 2: Task2();break;

case 3: Task3();break;

case 4: Task4();break;

………………………

case 16: Task16();break;

default:break;

}

可以改为

UINT8 ucCurTask=1;

void Task1(void);

void Task2(void);

void Task3(void);

void Task4(void);

……………

void Task16(void);

switch(ucCurTask)

{

case 1: Task1();break;

case 2: Task2();break;

default:

switch(ucCurTask)

{

case 3: Task3();break;

case 4: Task4();break;

………………………

case 16: Task16();break;

default:break;

}

Break;

}

由于switch 语句等同于if-else-if 的嵌套代码,如果大的if 语句同样要转换为嵌套的if 语句。UINT8 ucCurTask=1;

void Task1(void);

void Task2(void);

void Task3(void);

void Task4(void);

……………

void Task16(void);

if (ucCurTask==1) Task1();

else if(ucCurTask==2) Task2();

else

{

if (ucCurTask==3) Task3();

else if(ucCurTask==4) Task4();

………………

else Task16();

}

15. 函数指针妙用

当switch 语句中的case 标号很多时,或者if 语句的比较次数过多时,为了提高程序执行速度,

可以运用函数指针来取代switch 或if 语句的用法,这些用法可以参考电子菜单实验代码、USB 实验代码和网络实验代码。

UINT8 ucCurTask=1;

void Task1(void);

void Task2(void);

void Task3(void);

void Task4(void);

……………

void Task16(void);

switch(ucCurTask)

{

case 1: Task1();break;

case 2: Task2();break;

case 3: Task3();break;

case 4: Task4();break;

………………………

case 16: Task16();break;

default:break;

}

可以改为

UINT8 ucCurTask=1;

void Task1(void);

void Task2(void);

void Task3(void);

void Task4(void);

……………

void Task16(void);

void (*szTaskTbl)[16])(void)={Task1,Task2,Task3,Task4,…,Task16};

调用方法1:(*szTaskTbl[ucCurTask])();

调用方法2: szTaskTbl[ucCurTask]();

16. 循环嵌套

循环在编程中经常用到的,往往会出现循环嵌套。现在就已for 循环为例。

UINT8 i,j;

for(i=0;i<255;i++)

{

for(j=0;j<25;j++)

{

………………

}

}

较大的循环嵌套较小的循环编译器会浪费更加多的时间,推荐的做法就是较小的循环嵌套较大的循环。

UINT8 i,j;

for(j=0;j<25;j++)

{

for(i=0;i<255;i++)

{

………………

}

}

17. 内联函数

在C++中,关键字inline 可以被加入到任何函数的声明中。这个关键字请求编译器用函数内部的代

码替换所有对于指出的函数的调用。这样做在两个方面快于函数调用。这样做在两个方面快于函数调用:第一,省去了调用指令需要的执行时间;第二,省去了传递变元和传递过程需要的时间。但是使用这种方法在优化程序速度的同时,程序长度变大了,因此需要更多的ROM。使用这种优化在inline 函数频繁调用并且只包含几行代码的时候是最有效的。

如果编译器允许在C 语言编程中能够支持inline 关键字,注意不是C++语言编程,而且单片机的ROM

足够大,就可以考虑加上inline 关键字。支持inline 关键字的编译器如ADS1.2,RealView MDK 等。

18. 从编译器着手

很多编译器都具有偏向于代码执行速度上的优化、代码占用空闲太小的优化。例如Keil 开发环境编

译时可以选择偏向于代码执行速度上的优化(Favor Speed)还是代码占用空间太小的优化(Favor Size)。还有其他基于GCC 的开发环境一般都会提供-O0、-O1、-O2、—O3、-Os 的优化选项,而使用

-O2 的优化代码执行速度上最理想,使用-Os 优化代码占用空间大小最小。

19. 嵌入汇编---杀手锏

汇编语言是效率最高的计算机语言,在一般项目开发当中一般都采用C 语言来开发的,因为嵌入汇编

之后会影响平台的移植性和可读性,不同平台的汇编指令是不兼容的。但是对于一些执着的程序员要求程序获得极致的运行的效率,他们都在C 语言中嵌入汇编,即“混合编程”。

注意:如果想嵌入汇编,一定要对汇编有深刻的了解。不到万不得已的情况,不要使用嵌入汇编。

51单片机实例程100讲全集

目录 目录 (1) 函数的使用和熟悉 (4) 实例3:用单片机控制第一个灯亮 (4) 实例4:用单片机控制一个灯闪烁:认识单片机的工作频率 (4) 实例5:将P1口状态分别送入P0、P2、P3口:认识I/O口的引脚功能 (5) 实例6:使用P3口流水点亮8位LED (5) 实例7:通过对P3口地址的操作流水点亮8位LED (6) 实例8:用不同数据类型控制灯闪烁时间 (7) 实例9:用P0口、P1 口分别显示加法和减法运算结果 (8) 实例10:用P0、P1口显示乘法运算结果 (9) 实例11:用P1、P0口显示除法运算结果 (9) 实例12:用自增运算控制P0口8位LED流水花样 (10) 实例13:用P0口显示逻辑"与"运算结果 (10) 实例14:用P0口显示条件运算结果 (11) 实例15:用P0口显示按位"异或"运算结果 (11) 实例16:用P0显示左移运算结果 (11) 实例17:"万能逻辑电路"实验 (11) 实例18:用右移运算流水点亮P1口8位LED (12) 实例19:用if语句控制P0口8位LED的流水方向 (13) 实例20:用swtich语句的控制P0口8位LED的点亮状态 (13) 实例21:用for语句控制蜂鸣器鸣笛次数 (14) 实例22:用while语句控制LED (15) 实例23:用do-while语句控制P0口8位LED流水点亮 (16) 实例24:用字符型数组控制P0口8位LED流水点亮 (17) 实例25:用P0口显示字符串常量 (18) 实例26:用P0 口显示指针运算结果 (19) 实例27:用指针数组控制P0口8位LED流水点亮 (19) 实例28:用数组的指针控制P0 口8 位LED流水点亮 (20) 实例29:用P0 、P1口显示整型函数返回值 (21) 实例30:用有参函数控制P0口8位LED流水速度 (22) 实例31:用数组作函数参数控制流水花样 (22) 实例32:用指针作函数参数控制P0口8位LED流水点亮 (23) 实例33:用函数型指针控制P1口灯花样 (25) 实例34:用指针数组作为函数的参数显示多个字符串 (26) 实例35:字符函数ctype.h应用举例 (27) 实例36:内部函数intrins.h应用举例 (27) 实例37:标准函数stdlib.h应用举例 (28) 实例38:字符串函数string.h应用举例 (29) 实例39:宏定义应用举例2 (29) 实例40:宏定义应用举例2 (29) 实例41:宏定义应用举例3 (30)

AT89C51单片机简易计算器的设计

AT89C51单片机简易计算器的设计 单片机的出现是计算机制造技术高速发展的产物,它是嵌入式控制系统的核心,如今,它已广泛的应用到我们生活的各个领域,电子、科技、通信、汽车、工业等。本设计是基于51系列单片机来进行的数字计算器系统设计,可以完成计算器的键盘输入,进行加、减、乘、除六位数范围内的基本四则运算,并在LCD上显示相应的结果。设计电路采用AT89C51单片机为主要控制电路,利用MM74C922作为计算器4*4键盘的扫描IC读取键盘上的输入。显示采用字符LCD静态显示。软件方面使用C语言编程,并用PROTUES仿真。 一、总体设计 根据功能和指标要求,本系统选用MCS-51系列单片机为主控机。通过扩展必要的外围接口电路,实现对计算器的设计。具体设计如下:(1)由于要设计的是简单的计算器,可以进行四则运算,为了得到较好的显示效果,采用LCD 显示数据和结果。 (2)另外键盘包括数字键(0~9)、符号键(+、-、×、÷)、清除键和等号键,故只需要16 个按键即可,设计中采用集成的计算键盘。 (3)执行过程:开机显示零,等待键入数值,当键入数字,通过LCD显示出来,当键入+、-、*、/运算符,计算器在内部执行数值转换和存储,并等待再次键入数值,当再键入数值后将显示键入的数

值,按等号就会在LCD上输出运算结果。 (4)错误提示:当计算器执行过程中有错误时,会在LCD上显示相应的提示,如:当输入的数值或计算得到的结果大于计算器的表示范围时,计算器会在LCD上提示溢出;当除数为0时,计算器会在LCD 上提示错误。 系统模块图: 二、硬件设计 (一)、总体硬件设计 本设计选用AT89C51单片机为主控单元。显示部分:采用LCD 静态显示。按键部分:采用4*4键盘;利用MM74C922为4*4的键盘扫描IC,读取输入的键值。 总体设计效果如下图:

基于51单片机的计算器设计程序代码汇编

DBUF EQU 30H TEMP EQU 40H YJ EQU 50H ;结果存放 YJ1 EQU 51H ;中间结果存放GONG EQU 52H ;功能键存放 ORG 00H START: MOV R3,#0 ;初始化显示为空MOV GONG,#0 MOV 30H,#10H MOV 31H,#10H MOV 32H,#10H MOV 33H,#10H MOV 34H,#10H MLOOP: CALL DISP ;PAN调显示子程序WAIT: CALL TESTKEY ; 判断有无按键JZ WAIT CALL GETKEY ;读键 INC R3 ;按键个数 CJNE A,#0,NEXT1 ; 判断就是否数字键 LJMP E1 ; 转数字键处理NEXT1: CJNE A,#1,NEXT2 LJMP E1 NEXT2: CJNE A,#2,NEXT3 LJMP E1 NEXT3: CJNE A,#3,NEXT4 LJMP E1 NEXT4: CJNE A,#4,NEXT5 LJMP E1 NEXT5: CJNE A,#5,NEXT6 LJMP E1 NEXT6: CJNE A,#6,NEXT7 LJMP E1 NEXT7: CJNE A,#7,NEXT8 LJMP E1 NEXT8: CJNE A,#8,NEXT9 LJMP E1 NEXT9: CJNE A,#9,NEXT10 LJMP E1 NEXT10: CJNE A,#10,NEXT11 ;判断就是否功能键LJMP E2 ;转功能键处理NEXT11: CJNE A,#11,NEXT12 LJMP E2 NEXT12: CJNE A,#12, NEXT13 LJMP E2

最全最好的课程设计-51单片机电子日历时钟( 含源程序)

LED日历时钟课程设计 院系: 班级: 姓名: 学号: 指导教师: 2012 年06 月16 日

目录

摘要 单片机自20世纪70年代问世以来,以其极高的性能价格比,受到人们的重视和关注,应用很广、发展很快。单片机体积小、重量轻、抗干扰能力强、环境要求不高、价格低廉、可靠性高、灵活性好、开发较为容易。由于具有上述优点,在我国,单片机已广泛地应用在工业自动化控制、自动检测、智能仪器仪表、家用电器、电力电子、机电一体化设备等各个方面,而51单片机是各单片机中最为典型和最有代表性的一种。这次毕业设计通过对它的学习、应用,以AT89S51芯片为核心,辅以必要的电路,设计了一个简易的电子时钟,它由4.5V直流电源供电,通过数码管能够准确显示时间,调整时间,从而到达学习、设计、开发软、硬件的能力。 第一章前言 数字电子钟具有走时准确,一钟多用等特点,在生活中已经得到广泛的应用。虽然现在市场上已有现成的电子钟集成电路芯片,价格便宜、使用也方便,但是人们对电子产品的应用要求越来越高,数字钟不但可以显示当前的时间,而且可以显示期、农历、以及星期等,给人们的生活带来了方便。另外数字钟还具备秒表和闹钟的功能,且闹钟铃声可自选,使一款电子钟具备了多媒体的色彩。单片机具有体积小、功能强可靠性高、价格低廉等一系列优点,不仅已成为工业测控领域普遍采用的智能化控制工具,而且已渗入到人们工作和和生活的各个角落,有力地推动了各行业的技术改造和产品的更新换代,应用前景广阔。 时钟电路在计算机系统中起着非常重要的作用,是保证系统正常工作的基础。在一个单片机应用系统中,时钟有两方面的含义:一是指为保障系统正常工作的基准振荡定时信号,主要由晶振和外围电路组成,晶振频率的大小决定了单片机系统工作的快慢;二是指系统的标准定时时钟,即定时时间,它通常有两种实现方法:一是用软件实现,即用单片机内部的可编程定时/计数器来实现,但误差很大,主要用在对时间精度要求不高的场合;二是用专门的时钟芯片实现,在对时间精度要求很高的情况下,通常采用这种方法,典型的时钟芯片有:DS1302,DS12887,X1203等都可以满足高精度的要求。 AT89S51是一个低功耗,高性能CMOS 8位单片机,片内含4k B ytes ISP(In-system programmable)的可反复擦写1000次的Flash只读程序存储器,器件采用ATMEL公司的高密度、非易失性存储技术制造,兼容标准MCS-51指令系统及80C51引脚结构,芯片内集成了通用8位中央处理器和ISP Flash存储单元,功能强大的微型计算机的AT89S51可为许多嵌入式控制应用系统提供高性价比的解决方案。 AT89S51具有如下特点:40个引脚,4k Bytes Flash片内程序存储器,128 bytes的随机存取数据存储器(RAM),32个外部双向输入/输出(I/O)口,5个中断优先级2层中断嵌套中断,2个16位可编程定时计数器,2个全双工串行通信口,看门狗(WDT)电路,片内时钟振荡器。

51单片机简易计算器程序

#include <reg51.h>#include <intrins.h> #include <ctype.h> #include <stdlib.h> #define uchar unsigned char #define uint unsigned int uchar operand1[9], operand2[9]; uchar operator; void delay(uint); uchar keyscan(); void disp(void); void buf(uint value); uint compute(uint va1,uint va2,uchar optor); uchar code table[] = {0xc0,0xf9,0xa4,0xb0,0x99, 0x92,0x82,0xf8,0x80,0x90,0xff}; uchar dbuf[8] = {10,10,10,10,10,10,10,10}; void delay(uint z) { uint x,y; for(x=z;x>0;x--)

for(y=110;y>0;y--); } uchar keyscan() { uchar skey; P1 = 0xfe; while((P1 & 0xf0) != 0xf0) { delay(3); while((P1 & 0xf0) != 0xf0) { switch(P1) { case 0xee: skey = '7'; break; case 0xde: skey = '8'; break; case 0xbe: skey = '9'; break; case 0x7e: skey = '/'; break; default: skey = '#'; }

单片机编程全集(含源代码)

前言 (2) 基础知识:单片机编程基础 (2) 第一节:单数码管按键显示 (4) 第二节:双数码管可调秒表 (6) 第三节:十字路口交通灯 (7) 第四节:数码管驱动 (9) 第五节:键盘驱动 (10) 第六节:低频频率计 (15) 第七节:电子表 (18) 第八节:串行口应用 (19)

前言 本文是本人上课的一个补充,完全自写,难免有错,请读者给予指正,可发邮件到ZYZ@https://www.360docs.net/doc/b66506723.html,,或郑郁正@中国;以便相互学习。结合课堂的内容,课堂上的部分口述内容,没有写下来;有些具体内容与课堂不相同,但方法是相通的。https://www.360docs.net/doc/b66506723.html, 针对当前的学生情况,尽可能考虑到学生水平的两端,希望通过本文都学会单片机应用。如果有不懂的内容,不管是不是本课的内容,都可以提出来,这些知识往往代表一大部分同学的情况,但本人通常认为大家对这些知识已精通,而在本文中没有给予描述,由此影响大家的学习。对于这些提出问题的读者,本人在此深表谢意。 想深入详细学习单片机的同学,可以参考其它有关单片机的书籍和资料,尤其是外文资料。如果有什么问题,我们可以相互探讨和研究,共同学习。 本文根据教学的情况,随时进行修改和完善,所以欢迎同学随时注意本文档在课件中的更新情况。 基础知识:单片机编程基础 单片机的外部结构: 1、DIP40双列直插; 2、P0,P1,P2,P3四个8位准双向I/O引脚;(作为I/O输入时,要先输出高电平) 3、电源VCC(PIN40)和地线GND(PIN20); 4、高电平复位RESET(PIN9);(10uF电容接VCC与RESET,即可实现上电复位) 5、内置振荡电路,外部只要接晶体至X1(PIN18)和X0(PIN19);(频率为主频的12倍) 6、程序配置EA(PIN31)接高电平VCC;(运行单片机内部ROM中的程序) 7、P3支持第二功能:RXD、TXD、INT0、INT1、T0、T1 单片机内部I/O部件:(所为学习单片机,实际上就是编程控制以下I/O部件,完成指定任务) 1、四个8位通用I/O端口,对应引脚P0、P1、P2和P3; 2、两个16位定时计数器;(TMOD,TCON,TL0,TH0,TL1,TH1) 3、一个串行通信接口;(SCON,SBUF) 4、一个中断控制器;(IE,IP) https://www.360docs.net/doc/b66506723.html, 针对AT89C52单片机,头文件AT89x52.h给出了SFR特殊功能寄存器所有端口的定义。教科书的160页给出了针对MCS51系列单片机的C语言扩展变量类型。 C语言编程基础: 1、十六进制表示字节0x5a:二进制为01011010B;0x6E为01101110。 2、如果将一个16位二进数赋给一个8位的字节变量,则自动截断为低8位,而丢掉高8位。 3、++var表示对变量var先增一;var—表示对变量后减一。 4、x |= 0x0f;表示为 x = x | 0x0f; 5、TMOD = ( TMOD & 0xf0 ) | 0x05;表示给变量TMOD的低四位赋值0x5,而不改变TMOD的高四位。 6、While( 1 ); 表示无限执行该语句,即死循环。语句后的分号表示空循环体,也就是{;} 在某引脚输出高电平的编程方法:(比如P1.3(PIN4)引脚)

51单片机课程设计

课程设计说明书
课程设计名称






学生姓名
指导教师
单片机原理及应用课程设计 电子信息工程 140405 20141329 李延琦 胡黄水
2016 年 12 月 26 日

课程设计任务书
课程设计 题目
酒精测试仪
起止日期
2016 年 12 月 26 日— 2017 年 1 月 6 日
设计地点
计算机科学与工程学 院单片机实验室 3409
设计任务及日程安排: 设计任务:分两部分: (一)、设计实现类:进行软、硬件设计,并上机编程、联线、调试、 实现; 1.电子钟的设计 2.交通灯的设计 3.温度计的设计 4.点阵显示 5.电机调速 6.电子音乐发声(自己选曲) 7.键盘液晶显示系统 (二)、应用系统设计类:不须上机,查资料完成软、硬件设计画图。 查资料选定题目。 说明:第 1--7 题任选其二即可。(二)里题目自拟。 日程安排: 本次设计共二周时间,日程安排如下: 第 1 天:查阅资料,确定题目。 第 2--4 天:进实验室做实验,连接硬件并编写程序作相关的模块实验。 第 5--7 天:编写程序,并调试通过。观察及总结硬件实验现象和结果。 第 8--9 天:整理资料,撰写课程设计报告,准备答辩。 第 10 天:上交课程设计报告,答辩。 设计报告要求:
1. 设计报告里有两个内容,自选题目内容+附录(实验内容),每 位同学独立完成。 2. 自选题目不须上机实现,要求能正确完成硬件电路和软件程序 设计。内容包括: 1) 设计题目、任务与要求 2)硬件框图与电路图 3) 软件及流程图 (a)主要模块流程图 (b)源程序清单与注释 4) 总结 5) 参考资料 6)附录 实验上机调试内容
注:此任务书由指导教师在课程设计前填写,发给学生做为本门课程设计 的依据。

AT89C51单片机C实现简易计算器

AT89C51单片机简易计算器的设计 一、总体设计 根据功能和指标要求,本系统选用MCS-51系列单片机为主控机。通过扩展必要的外围接口电路,实现对计算器的设计。具体设计如下:(1)由于要设计的是简单的计算器,可以进行四则运算,为了得到较好的显示效果,采用LCD 显示数据和结果。 (2)另外键盘包括数字键(0~9)、符号键(+、-、×、÷)、清除键和等号键,故只需要16 个按键即可,设计中采用集成的计算键盘。 (3)执行过程:开机显示零,等待键入数值,当键入数字,通过LCD显示出来,当键入+、-、*、/运算符,计算器在内部执行数值转换和存储,并等待再次键入数值,当再键入数值后将显示键入的数值,按等号就会在LCD上输出运算结果。 (4)错误提示:当计算器执行过程中有错误时,会在LCD上显示相应的提示,如:当输入的数值或计算得到的结果大于计算器的表示范围时,计算器会在LCD上提示溢出;当除数为0时,计算器会在LCD 上提示错误。 系统模块图:

二、硬件设计 (一)、总体硬件设计 本设计选用AT89C51单片机为主控单元。显示部分:采用LCD 静态显示。按键部分:采用4*4键盘;利用MM74C922为4*4的键盘扫描IC,读取输入的键值。 总体设计效果如下图:

(二)、键盘接口电路 计算器输入数字和其他功能按键要用到很多按键,如果采用独立按键的方式,在这种情况下,编程会很简单,但是会占用大量的I/O 口资源,因此在很多情况下都不采用这种方式,而是采用矩阵键盘的方案。矩阵键盘采用四条I/O 线作为行线,四条I/O 线作为列线组成键盘,在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为4×4个。这种行列式键盘结构能有效地提高单片机系统中I/O 口的利用率。 矩阵键盘的工作原理: 计算器的键盘布局如图2所示:一般有16个键组成,在单片机中正好可以用一个P口实现16个按键功能,这种形式在单片机系统中也最常用。 图 2 矩阵键盘布局图 矩阵键盘内部电路图如图3所示:

51单片机控制LED灯程序设计

51单片机:LED灯亮灯灭程序设计 1.功能说明:控制单片机P1端口输出,使P1.0位所接的LED点亮,其他7只灯熄灭。 程序: 01: MOV A , #11111110B ; 存入欲显示灯的位置数据 02: MOV P1,A ; 点亮第一只灯 03: JMP $ ; 保持当前的输出状态 04: END ; 程序结束 2.功能说明:单片机P1端口接8只LED,点亮第1、3、4、6、7、8只灯。 程序:

01:START: MOV A , #00010010B ; 存入欲显示灯的位置数据 02:MOV P1,A ; 点亮灯 03:JMP START ; 重新设定显示值 04:END ; 程序结束 3.功能说明:单片机P1端口接8只LED,每次点亮一只,向左移动点亮,重复循环。 程序: 01:START: MOV R0, #8 ;设左移8次 02:MOV A, #11111110B ;存入开始点亮灯位置

03:LOOP: MOV P1, A ;传送到P1并输出 04:RL A ;左移一位 05:DJNZ R0, LOOP ;判断移动次数 06:JMP START ;重新设定显示值 07:END ;程序结束 4.功能说明:单片机P1端口接8只LED,每次点亮一只,向右移动点亮,重复循环。 程序: 01:START: MOV R0, #8 ;设右移8次

02:MOV A, #01111111B ;存入开始点亮灯位置03: LOOP: MOV P1, A ;传送到P1并输出 04: ACALL DELAY ;调延时子程序05: RR A ;右移一位 06: DJNZ R0, LOOP ;判断移动次数07: JMP START ;重新设定显示值08: DELAY: MOV R5,#50 ; 09:DLY1: MOV R6,#100 ; 10: DLY2: MOV R7,#100 ;

针对常用51单片机下载程序问题做下详解

针对常用51单片机下载程序问题做下详解 目前为止,接触单片机已有不少,从选择元器件、原理图、PCB、电路硬件调试、软件开发也算小有心得。 单片机软件开发里面第一步当属下载程序了,如果这一步都有问题,那么后面的一切便无从谈起,记得当初刚接触单片机时,对于下载电路方法及原理也是一头雾水。好在随着经验的积累以及自己的努力探求,现在对此问题算是有了点点自己的经验理解。故今天在此针对常用51单片机下载程序问题做下详解,以求新手们少走弯路。 原理 单片机的TXD、RXD是TTL电平,所以你得万变不离其宗的将其它信号转成TTL电平,只有这样给单片机下载程序才有可能成功!其中CH340、PL2303等芯片是直接将USB信号转换为TTL电平,而MAX232等芯片是将TTL转换为RS232信号或者将RS232信号转换为TTL.下面请看利用这种原理的两种常用方法: 方法一: 请看图一,这是我们最常见的单片机下载电路了,其中从②到⑥属于大家常用的USB转串口线,用这种方案的好处是,如果自己的PC带有串口(可能很老的机器没有USB接口),那么就可以直接给单片机开发板下载程序,因为采用这种方法的开发板必定带有串口接口嘛。当然,如果PC仅有USB接口而不带串口,那么只能找根USB转串口线了(其电路原理就是图中②到⑥),这里我推荐大家使用采用CH340芯片的USB转串口线,而不要采用PL2303的USB转串口线,因为PL2303价钱便宜所以山寨的水货较多,这会导致下载电路不稳定,甚至无法正常下载。同时此方案的坏处是电路板上必定要做一个9针串口接口(太巨大了),这必将增加了电路PCB的面积,当然也就增加了成本啦!(顺便发表下个人见解:那个9针接口实在太丑,又大又重,无形中便降低了自己的设计档次,哈哈。所以不推荐这种方法!) 请看图二,这是我经常采用的单片机下载电路。实践证明效果非常好,几乎没出过任何问

基于51单片机FAT32文件系统程序

基于51单片机FAT32文件系统程序 #ifndef __ZNFAT_H__ #define __ZNFAT_H__ #include "mytype.h" //类型重定义 /*******************************************************/ //znFAT的裁减宏--------------------------------------------------------- //#define ZNFAT_ENTER_DIR //有此宏,函数 znFAT_Enter_Dir() 参与编译 #define ZNFAT_OPEN_FILE //有此宏,函数 znFAT_Open_File() 参与编译 //#define ZNFAT_SEEK_FILE //有此宏,函数 znFAT_Seek_File() 参与编译 //#define ZNFAT_READ_FILE //有此宏,函数 znFAT_Read_File() 参与编译 //#define ZNFAT_READ_FILEX //有此宏,函数 znFAT_Read_FileX() 参与编译 //#define ZNFAT_ADD_DAT //有此宏,函数 znFAT_Add_Dat() 参与编译 //#define ZNFAT_CREATE_DIR //有此宏,函数 znFAT_Create_Dir() 参与编译 //#define ZNFAT_CREATE_FILE //有此宏,函数 znFAT_Create_File() 参与编译 //#define ZNFAT_DEL_FILE //有此宏,函数 znFAT_Del_File() 参与编译 //#define ZNFAT_XCOPY_FILE //有此宏,函数 znFAT_XCopy_File() 参与编译 //#define ZNFAT_RENAME_FILE //有此宏,函数 znFAT_Rename_File() 参与编译 //#define ZNFAT_GET_TOTAL_SIZE //有此宏,函数 znFAT_Get_Total_Size() 参与编译 //#define znFAT_GET_REMAIN_CAP //有此宏,函数 znFAT_Get_Remain_Cap() 参与编译 #include "cj.h" #include "cj.h" //---------------------------------------------------------------------- #define SOC(c) (((c-pArg->FirstDirClust)*(pArg->SectorsPerClust))+pArg->FirstDirSector) // 用于计算簇的开始扇区#define CONST const //设备表 #define SDCARD 0 //SD卡 #define UDISK 1 //U盘 #define CFCARD 2 //CF卡 #define OTHER 3 //其它 //这里的存储设备表,可以灵活扩充,以实现对更多存储设备的支持 //------------------------------------------- #define MAKE_FILE_TIME(h,m,s) ((((unsigned int)h)<<11)+(((unsigned int)m)<<5)+(((unsigned int)s)>>1)) /* 生成指定时分秒的文件时间数据 */ #define MAKE_FILE_DATE(y,m,d) (((((unsigned int)y)+20)<<9)+(((unsigned int)m)<<5)+((unsigned int)d)) /* 生成指定年月日的文件日期数据 */ //DPT:分区记录结构如下 struct PartRecord { UINT8 Active; //0x80表示此分区有效 UINT8 StartHead; //分区的开始磁头 UINT8 StartCylSect[2];//开始柱面与扇区 UINT8 PartType; //分区类型 UINT8 EndHead; //分区的结束头 UINT8 EndCylSect[2]; //结束柱面与扇区 UINT8 StartLBA[4]; //分区的第一个扇区 UINT8 Size[4]; //分区的大小

51单片机简易计算器代码

#include"reg52.h" #define uchar unsigned char #define uint unsigned int sbit busy=P0^7; void delay(uint z) { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } char i,j,temp,num; long a,b,c; //a,第一个数b,第二个数c,得数 uchar flag,fuhao;//flag表示是否有符号键按下,fuhao表征按下的是哪个符号uchar code table[]={7,8,9,0,4,5,6,0,1,2,3,0,0,0,0,0}; uchar code table1[]={7,8,9,0x2f-0x30,4,5,6,0x2a-0x30,1,2,3,0x2d-0x30,0x01-0x30,0,0x3d-0x30,0 x2b-0x30}; //按键显示编码表 sbit lcden=P2^2; sbit lcdwrite=P2^1; sbit lcdrs=P2^0; //lcd的写指令 void write_com(uchar com) { lcdrs=0; lcden=0; P0=com; delay(1); lcden=1; delay(1); lcden=0; } //lcd的写数据 void write_date(uchar da) { lcdrs=1; lcden=0; P0=da; delay(1); lcden=1; delay(1); lcden=0; } //初始化

void init() //初始化 { uchar num; num=-1; lcdwrite=0; lcden=0; write_com(0x38); write_com(0x0c); write_com(0x06); write_com(0x01); delay(500);//延时0.5s write_com(0x01); i=0; j=0; a=0; //第一个参与运算的数 b=0; //第二个参与运算的数 c=0; flag=0; //flag表示是否有符号键按下, fuhao=0; // fuhao表征按下的是哪个符号 } void keyscan() // 键盘扫描程序 { P3=0xfe; if(P3!=0xfe) { delay(10); //延迟20ms if(P3!=0xfe) { temp=P3&0xf0; switch(temp) { case 0xe0:num=0; break; case 0xd0:num=1; break; case 0xb0:num=2; break; case 0x70:num=3; break; } } while(P3!=0xfe); if(num==0||num==1||num==2)//如果按下的是'7','8'或'9 { if(j==1)//确认一次计算完毕,清屏 { write_com(0x01);

51单片机 实现计算器功能

51单片机多为计算器汇编程序 此程序并不仅仅局限于255以内操作 FIR0 EQU 30H FIR1 EQU 31H FIR2 EQU 32H FIR3 EQU 33H ;第一个操作数 SEC0 EQU 34H SEC1 EQU 35H SEC2 EQU 36H SEC3 EQU 37H ; 第二个操作数 LIN0 EQU 38H LIN1 EQU 39H LIN2 EQU 40H LIN3 EQU 41H ; 数据暂存 RES0 EQU 42H RES1 EQU 43H RES2 EQU 44H RES3 EQU 45H ;结果暂存区 XLINE EQU 46H YLINE EQU 47H ;记录按键按键位置 SYMBLE EQU 48H ;操作符存储 DDE0 EQU 49H DDE1 EQU 50H DDE2 EQU 51H ;用于延时 FLEL4 EQU 52H FLEL5 EQU 53H FLEL6 EQU 54H BEFOR EQU 55H HH BIT 01H ;比较大 EE BIT 02H ;比较相等 FIL BIT 03H ;溢出标记 FLAG BIT 04H ;有无按键标记 ERR BIT 05H ;错误标记 YESY BIT 06H ; 有无操作符按键标记

NUM BIT 07H ;按键个数标记 YESN BIT 08H ;有无数字按键标记 ORG 0000H LJMP MAIN ORG 0003H LJMP INTERUPT MAIN: MOV IE,#01H ;初始化 MOV SP,#6FH LCALL CLRI SETB IT0 SETB EA DISPLAY: ;数码管显示函数 CJNE R3,#00H,TT1 MOV R3,#0AH TT1: CJNE R3,#0AH,STARTD CJNE R2,#00H,TT2 MOV R2,#0AH TT2: CJNE R2,#0AH,STARTD CJNE R1,#00H,STARTD MOV R1,#0AH STARTD: MOV A,R0 LCALL TRANS ;将所要显示的值转化为数码管对应的数据 MOV P2,A MOV P1,#10H LCALL DELAY10ms MOV A,R1 LCALL TRANS MOV P2,A MOV P1,#20H LCALL DELAY10ms MOV A,R2 LCALL TRANS MOV P2,A MOV P1,#40H LCALL DELAY10ms

51单片机几大误区,向51单片机说再见!

51单片机几大误区,向51单片机说再见! 51单片机的辉煌过去 51单片机指MCS-51系列单片机,CICS指令集。由Intel公司开发,其结构增加了如乘(MUL)、除(DIV)、减(SUBB)、比较(CMP)、16位数据指针、布尔代数运算等指令,以及串行通信能力和5个中断源,内有128个RAM单元及4K的ROM。其代表型号是ATMEL公司的AT89系列,它广泛应用于工业测控系统之中。目前国内的51单片机市场主要为国产宏晶的产品STC系列其号称低功耗,稳定与廉价的特点。 学习51单片机的误区 误区1:51单片机是学习的基础 51单片机是学习的基础这句话本身并没有错。在我读本科的时候,当时它无疑是学习的基础毕竟那时没有更高级的单片机可以供使用,国内更没有更高级的教材供参考,老师的水平也是参差不齐,而51单片机正符合这样的需求,不仅有大量的成型的教材,广泛的示例,当时工作的实际项目也是51单片机为主,于是51单片机理所当然的成为当时的学习基础。要知道笔者读本科的时候是2004年,即10年前的东西。按照莫尔定律,电子行业每18个月更新换代,10年前的技术现在已经更新了6代了事实也确实如此。 目前32位Crtex-M系列单片机的各种教程已经普及,其学习的难度不断降低。以意法半导体公司的STM32F系列单片机来说,意法半导体在推广产品初期大量赠送了核心板。免费赠送的核心板不仅有流行的32位Cortex-M系列微处理器,更在板上集成在线调试器。随机附赠的光盘或者链接更是提供了大量的示例源代码。我们只需要安装开发环境即可直接编译与下载调试。这时,如果再说51单片机是学习嵌入式的基础肯定是不合时宜了。误区2:51单片机可以学习寄存器的操作 51单片机可以学习寄存器的操作,这一点儿是不容置疑的。我们分析一下其更深层的原因。在MSC-51单片机的编程环境中,最初是以汇编语言为主要编程语言。要知道汇编语言就是直接操作寄存器的,汇编语言是无法做到C语言的函数调用与封装的。如果说51单片机是以操作寄存器为优点,我觉得更应该说51单片机操作寄存器是一种无奈,是只有一

(完整版)51单片机汇编指令(全)

指令中常用符号说明 Rn当前寄存器区的8个工作寄存器R0~R7(n=0~7) Ri当前寄存器区可作为地址寄存器的2个工作寄存器R0和R1(i=0,1) Direct8位内部数据寄存器单元的地址及特殊功能寄存器的地址 #data表示8位常数(立即数) #data16表示16位常数 Add16表示16位地址 Addr11表示11位地址 Rel8位代符号的地址偏移量 Bit表示位地址 @间接寻址寄存器或基址寄存器的前缀 ( )表示括号中单元的内容 (( ))表示间接寻址的内容 指令系统 数据传送指令(8个助记符) 助记符中英文注释 MOV Move 移动 MOV A , Rn;Rn→A,寄存器Rn的内容送到累加器A MOV A , Direct;(direct)→A,直接地址的内容送A MOV A ,@ Ri;(Ri)→A,RI间址的内容送A MOV A , #data;data→A,立即数送A MOV Rn , A;A→Rn,累加器A的内容送寄存器Rn MOV Rn ,direct;(direct)→Rn,直接地址中的内容送Rn MOV Rn , #data;data→Rn,立即数送Rn MOV direct , A;A→(direct),累加器A中的内容送直接地址中 MOV direct , Rn;(Rn)→direct,寄存器的内容送到直接地址 MOV direct , direct;(direct)→direct,直接地址的内容送到直接地址 MOV direct , @Ri;((Ri))→direct,间址的内容送到直接地址 MOV direct , #data;8位立即数送到直接地址中 MOV @Ri , A;(A)→@Ri,累加器的内容送到间址中 MOV @Ri , direct;direct→@Ri,直接地址中的内容送到间址中 MOV @Ri , #data; data→@Ri ,8位立即数送到间址中 MOV DPTR , #data16;data16→DPTR,16位常数送入数据指针寄存器,高8位送入DPH,低8位送入DPL中(单片机中唯一一条16位数据传送指令) (MOV类指令共16条)

51单片机计算器设计

1引言 当今时代,是一个新技术层出不穷的时代。在电子领域,尤其是自动化智能控制领域,传统的分立元件或数字逻辑电路构成的控制系统正以前所未见的速度被单片机智能控制系统所取代。单片机具有体积小、功能强、成本低、应用面广等优点,可以说,智能控制与自动控制的核心就是单片机。目前,一个学习与应用单片机的高潮正在工厂、学校及企事业单位大规模地兴起。过去习惯于传统电子领域的工程师、技术员正面临着全新的挑战,如不能在较短时间内学会单片机,势必会被时代所遗弃,只有勇敢地面对现实,挑战自我,加强学习,争取在较短的时间内将单片机技术融会贯通,才能跟上时代的步伐。 它所给人带来的方便也是不可否定的,它在一块芯片内集成了计算机的各种功能部件,构成一种单片式的微型计算机。20世纪80年代以来,国际上单片机的发展迅速,其产品之多令人目不暇接,单片机应用不断深入,新技术层出不穷。20世纪末,电子技术获得了飞速的发展,在其推动下,现代电子产品几乎渗透了社会的各个领域,有力地推动了社会生产力的发展和社会信息化程度的提高,同时也使现代电子产品性能进一步提高,产品更新换代的节奏也越来越快。 本设计是由单片机实现的模拟计算器,它不仅能实现数据的加减乘除运算,而且还能使数据及其计算结果在数码管上显示出来,能够实现0-256的数字四则运算。本设计是用单片机AT89C51来控制,采用共阳极数码显示,软件部分是由C语言来编写的。设计任务利用键盘和数码管设计一个简单的数学计算器,可以完成简单的如加,减,乘,除的四则运算,并将运算结果在数码管上显示出来。 2.方案论证与设计 根据功能和指标要求,本系统选用MCS 51 单片机为主控机。通过扩展必要的外围接口 电路,实现对计算器的设计。具体设计考虑如下: ①由于要设计的是简单的计算器,可以进行四则运算,对数字的大小范围要求不高,故 我们采用可以进行四位数字的运算,选用8 个LED 数码管显示数据和结果。 ②另外键盘包括数字键(0~9)、符号键(+、-、×、÷)、清除键和等号键,故只需要16 个按键即可。系统模块图: 2.1 输入模块: 键盘扫描计算器输入数字和其他功能按键要用到很多按键,如果采用独立按键的方式,在这种情况下,编程会很简单,但是会占用大量的I/O 口资源,因此在很多情况下都不采用这种方式。为此,我们引入了矩阵键盘的应用,采用四条I/O

小只推荐:51单片机知识大汇总看你了解有多少

小只推荐:51单片机知识大汇总看你了解有多少 基于51单片机的函数信号发生器利用单片机AT89C52 采用程序设计方法产生锯齿波、三角波、正弦波、方波四种波形,再通过D/A 转换器DAC0832 将数字信号转换成模拟信号,滤波放大,最终由示波器显示出来,能产生10Hz—10kHz 的波形。下面就对51单片机进行详细论述。 ? ?1.取指译码执行模型:首先我们来看看单片机是如何工作的,我们拿电脑的主板来作为对比,我们买电脑时,总是追求处理器的主频。处理器的工作原理是从存储器上取出一条指令,然后对指令译码,译码完后执行。然后取下一条指令,译码,执行。它为什幺能这幺有序的工作?是因为它有一个工作时钟,在这个工作时钟的统一管理下,处理器有序的工作,这里的主频就是工作时钟的速度,当然,现在你可能也知道主频越高,取指译码速度越快,性能越好。那幺你可能会问,这跟51单片机有什幺关系,OK,切入正题,单片机的工作也是这个原理,从存储器上取指,译码,执行。但是单片机的存储器在哪?在单片机的内部,对于电脑来说,是把处理器,内存集中在一块主板上。而现在你应该很明确,对于单片机它就相当于一块电脑主板,把处理器,存储器集中到一块芯片内部。从这个宏观的角度看,所有的单片机是不是都是一个原理,服从于取指,译码,执行的基本模型。OK,继续…… 2. 51单片机的外设:我们在学习单片机时,所做的第一个实验都是一样的,点灯。也许你已经学会了用单片机做出好看的花样灯,或者用单片机驱动数码管显示出数字或字母。或者可以检测按键了。不错,我们仔细想想这些是什幺,无非,你就是控制那一排排引脚输出高低电平,我们称这些引脚叫I/O口,输入输出,按键是输入,点灯是输出。其实你以为你学了三样东

单片机C语言模块化编程初步资料全

下面让我们揭开模块化神秘面纱,一窥其真面目。 C语言源文件*.c 提到C语言源文件,大家都不会陌生。因为我们平常写的程序代码几乎都在这个XX.C文件里面。编译器也是以此文件来进行编译并生成相应的目标文件。作为模块化编程的组成基础,我们所要实现的所有功能的源代码均在这个文件里。理想的模块化应该可以看成是一个黑盒子。即我们只关心模块提供的功能,而不管模块内部的实现细节。好比我们买了一部手机,我们只需要会用手机提供的功能即可,不需要知晓它是如何把短信发出去的,如何响应我们按键的输入,这些过程对我们用户而言,就是是一个黑盒子。 在大规模程序开发中,一个程序由很多个模块组成,很可能,这些模块的编写任务被分配到不同的人。而你在编写这个模块的时候很可能就需要利用到别人写好的模块的借口,这个时候我们关心的是,它的模块实现了什么样的接口,我该如何去调用,至于模块内部是如何组织的,对于我而言,无需过多关注。而追求接口的单一性,把不需要的细节尽可能对外部屏蔽起来,正是我们所需要注意的地方。 C语言头文件*.h 谈及到模块化编程,必然会涉及到多文件编译,也就是工程编译。在这样的一个系统中,往往会有多个C文件,而且每个C文件的作用不尽相同。在我们的C文件中,由于需要对外提供接口,因此必须有一些函数或者是变量提供给外部其它文件进行调用。 假设我们有一个LCD.C文件,其提供最基本的LCD的驱动函数 LcdPutChar(char cNewValue) ; //在当前位置输出一个字符 而在我们的另外一个文件中需要调用此函数,那么我们该如何做呢? 头文件的作用正是在此。可以称其为一份接口描述文件。其文件内部不应该包含任何实质性的函数代码。我们可以把这个头文件理解成为一份说明书,说明的内容就是我们的模块对外提供的接口函数或者是接口变量。同时该文件也包含了一些很重要的宏定义以及一些结构体的信息,离开了这些信息,很可能就无法正常使用接口函数或者是接口变量。但是总的原则是:不该让外界知道的信息就不应该出现在头文件里,而外界调用模块内接口函数或者是接口变量所必须的信息就一定要出现在头文件里,否则,外界就无法正确的调用我们提供的接口功能。因而为了让外部函数或者文件调用我们提供的接口功能,就必须包含我们提供的这个接口描述文件----即头文件。同时,我们自身模块也需要包含这份模块头文件(因为其包含了模块源文件中所需要的宏定义或者是结构体),好比我们平常所用的文件都是一式三份一样,模块本身也需要包含这个头文件。 下面我们来定义这个头文件,一般来说,头文件的名字应该与源文件的名字保持一致,这样我们便可以清晰的知道哪个头文件是哪个源文件的描述。

相关文档
最新文档