EDN首页   博客首页

日志档案

发表于 2007-2-15 17:04:20

6

标签: 无标签

单片机实现交通灯的一些感想

       上午别人拿给我一个程序,关于单片机模拟实现城市交通灯的,也可以称的上是MCU中一个很经典的程序吧。朋友写了很久终于搞定的,而且用KEIL生成了.HEX文件,但当烧到片子里的时候出向了问题:有时候无缘无故地不能执行!下面就把他的程序贴出来,大伙可以讨论讨论。芯片是AT89S52,22.1184MHz,整体图片:,也就是32个I/O口都接了一个LED,并利用它们来模拟交通灯。具体构思为:

P1口的低四位             东西方向的红灯

P1口的高四位             东西方向的绿灯

P3                     东西方向的黄灯

P0口的低四位             南北方向的红灯

P0口的高四位             南北方向的绿灯

P2                     南北方向的黄灯

 

他的程序是:

 

ORG 0000H                ;从地址0开始执行程序
LJMP START               ;跳转到START处
START: MOV P1,#0FFH      ;关闭P1口的所有LED
MOV P2,#0FFH             ;关闭P2口的所有LED
MOV P3,#0FFH             ;关闭P3口的所有LED
MOV P0,#0FFH             ;关闭P0口的所有LED
LOOP:
LCALL STATUS_1           ;调用STATUS-1子程序
LCALL STATUS_2           ;调用STATUS-2子程序
LCALL STATUS_3           ;调用STATUS-3子程序
LCALL STATUS_4           ;调用STATUS-4子程序
LJMP LOOP                ;跳转到LOOP处形成一个循环
//**************以下为STATUS-1的模块***************//
STATUS_1:
MOV R0,#5                ;将5赋给R0
LOOP_1:MOV A,#00H        ;将0赋给A
MOV P3,A                 ;将A中的数值赋给P3口
MOV P2,A                 ;将A中的数值赋给P2口
LCALL DELAY_5            ;调用DELAY-5延迟子程序
CPL A                    ;将A中的数值取反
MOV P3,A                 ;将A中的数值赋给P3口
MOV P2,A                 ;将A中的数值赋给P2口
LCALL DELAY_5            ;调用DELAY-5延迟子程序
DJNZ R0,LOOP_1           ;跳回LOOP-1处,循环5次
RET                      ;返回
//***************以下是STATUS-2的模块***************//
STATUS_2:
MOV P1,#0FH              ;P1口的高4位亮,即绿灯亮
MOV P0,#0F0H             ;P0口的低4位亮,即红灯亮
LCALL DELAY_30           ;调用DELAY-30程序
RET                      ;返回
//***************以下是STATUS-3的模块*******************//
STATUS_3:
MOV R1,#5                ;将5赋给R1
LOOP_2:MOV A,#00H        ;将0赋给A
MOV P3,A                 ;将A中的数值赋给P3口
MOV P2,A                 ;将A中的数值赋给P2口
LCALL DELAY_5            ;调用DELAY-5子程序
CPL A                    ;将A中的数值取反
MOV P3,A                 ;将A中的数值赋给P3口
MOV P2,A                 ;将A中的数值赋给P2口
LCALL DELAY_5            ;调用DELAY-5子程序
DJNZ R1,LOOP_2           ;跳回LOOP-2处,循环5次
RET                      ;返回
//****************以下是STSTUS-4的模块****************//
STATUS_4:
MOV P1,#0F0H             ;P1口的低4位亮,即红灯亮
MOV P0,#0FH              ;P0口的高4位亮,即绿灯亮
LCALL DELAY_30           ;调用DELAY-30子程序
RET                      ;返回
//****************以下是DELAY-5模块******************//
DELAY_5:
MOV R2,#5                ;
LOOP_3:MOV R3,#184       ;
LOOP_4:MOV R4,#200       ;
DJNZ R4,$                ;
DJNZ R3,LOOP_4           ;
DJNZ R2,LOOP_3           ;
RET                      ;
//****************以下是DELAY-30模块****************** //
DELAY_30:
MOV R5,#6                ;
LOOP_6:MOV R6,#247       ;
LOOP_5:MOV R7,#249       ;
DJNZ R7,$                ;
DJNZ R6,LOOP_5           ;
DJNZ R5,LOOP_6           ;
RET                      ;
//**************结束****************  //
END

 

其实,说真的,朋友做事很认真,程序写的很工整,让人看起来很舒服。但美中不足的是,这个程序是不能用的。

 

我修改后的程序:

 

ORG 0000H                ;从地址0开始执行程序

LJMP START               ;跳转到START

START: MOV P1,#0FFH      ;关闭P1口的所有LED

MOV P2,#0FFH             ;关闭P2口的所有LED

MOV P3,#0FFH             ;关闭P3口的所有LED

MOV P0,#0FFH             ;关闭P0口的所有LED

NOP

NOP

LCALL DELAY_5           ;调用延迟子程序,准备主程序的运行

LOOP:

NOP

NOP

LCALL STATUS_1           ;调用STATUS-1子程序

NOP

NOP

LCALL STATUS_2           ;调用STATUS-2子程序

NOP

NOP

LCALL STATUS_3           ;调用STATUS-3子程序

NOP

NOP

LCALL STATUS_4           ;调用STATUS-4子程序

NOP

NOP

LJMP LOOP                ;跳转到LOOP处形成一个循环

//**************以下为STATUS-1的模块***************//

STATUS_1:

MOV P0,  #0FFH           ;关闭P0

MOV P1, #0FFH             ;关闭P1

MOV R0,#5                ;5赋给R0

LOOP_1:MOV A,#00H        ;0赋给A

MOV P3,A                 ;A中的数值赋给P3

MOV P2,A                 ;A中的数值赋给P2

NOP

NOP

LCALL DELAY_5            ;调用DELAY-5延迟子程序

CPL A                    ;A中的数值取反

MOV P3,A                 ;A中的数值赋给P3

MOV P2,A                 ;A中的数值赋给P2

NOP

NOP

LCALL DELAY_5            ;调用DELAY-5延迟子程序

DJNZ R0,LOOP_1           ;跳回LOOP-1处,循环5

RET                      ;返回

//***************以下是STATUS-2的模块***************//

STATUS_2:

MOV P1,#0FH              ;P1口的高4位亮,即绿灯亮

MOV P0,#0F0H             ;P0口的低4位亮,即红灯亮

NOP

NOP

LCALL DELAY_30           ;调用DELAY-30程序

RET                      ;返回

//***************以下是STATUS-3的模块*******************//

STATUS_3:

MOV P0, #0FFH              ;关闭P0

MOV P1, #0FFH             ;关闭P1

MOV R1,#5                ;5赋给R1

LOOP_2:MOV A,#00H        ;0赋给A

MOV P3,A                 ;A中的数值赋给P3

MOV P2,A                 ;A中的数值赋给P2

NOP

NOP

LCALL DELAY_5            ;调用DELAY-5子程序

CPL A                    ;A中的数值取反

MOV P3,A                 ;A中的数值赋给P3

MOV P2,A                 ;A中的数值赋给P2

NOP

NOP

LCALL DELAY_5            ;调用DELAY-5子程序

DJNZ R1,LOOP_2           ;跳回LOOP-2处,循环5

RET                      ;返回

//****************以下是STSTUS-4的模块****************//

STATUS_4:

MOV P1,#0F0H             ;P1口的低4位亮,即红灯亮

MOV P0,#0FH              ;P0口的高4位亮,即绿灯亮

NOP

NOP

LCALL DELAY_30           ;调用DELAY-30子程序

RET                      ;返回

//****************以下是DELAY-5模块******************//

DELAY_5:

MOV R2,#12                ;

LOOP_3:MOV R3,#191       ;

LOOP_4:MOV R4,#200       ;

DJNZ R4,$                ;

DJNZ R3,LOOP_4           ;

DJNZ R2,LOOP_3           ;

RET                      ;

//****************以下是DELAY-30模块****************** //

DELAY_30:

MOV R5,#75                ;

LOOP_6:MOV R6,#247       ;

LOOP_5:MOV R7,#249       ;

DJNZ R7,$                ;

DJNZ R6,LOOP_5           ;

DJNZ R5,LOOP_6           ;

RET                      ;

//**************结束****************  //

END

 

其中的红色部分是我修改的地方,可能有朋友要问了,你不就写了三个字母么?有什么了不起!呵呵,好像也对,但如果拿到那三个字母你再看看?程序还能正常运行么?其实,当初我在学习单片机的时候也对NOP很不以为然,但又来慢慢才意识到INTRL不是吃白饭的!人家总共就造了111个指令,怎么可能弄一个没用的上去呢?从指令的运行来讲:NOP这条指令是没有任何含义的,它唯一的作用是消耗时间,但从保护程序的正确执行来讲,它却是一个大功臣!去搜一搜很多别人写好的程序,总会发现在LCALL,JB,JNB,JC,JNC等指令的前后有它们的身影。为什么呢?

 

首先需要明确的是:在单片机中,无论是程序还是数字,反映到物理设备上,他们仅仅是一串高低电平而已!

其次还得从指令的格式说起,一般来说汇编语言是单字节指令的,换个话说:有的指令是双字节指令,甚至三字节指令的。当执行单字节指令的时候,指令的格式为:操作码,;而双字节指令则为:操作码,操作数;三字节指令的格式为:操作码,操作数,操作数,操作数;当程序执行单字节指令的时候,PC(program counter)可以保证程序正确地执行,即:先取操作码,后取操作数。而当程序执行到双字节或三字节指令的时候,有时候就会误将第二个操作数当成操作码(注意前面红色部分),从而就出现了程序跑飞的发生。而单字节指令NOP的使用恰好可以保证程序从错误的“轨道”上“偏”回来。

呵呵,具体的做法是在一些对程序的流向起控制作用的重要指令前插入两条NOP指令。这类指令有:RET,RETI,ACALL,LCALL,LJMP,JZ,JNZ,JC,JNC,DJNZ,CJNE,SETB等...

 

呵呵,闲暇时间,略微唠叨唠叨,期待与朋友们的交流和探讨.

 

 

系统分类: 单片机   |   用户分类: 无分类   |   来源: 无分类   |   【推荐给朋友】   |   【添加到收藏夹】

    阅读(1956)    回复(7)  

投一票您将和博主都有获奖机会!

最新评论

  • flightbird@gmail.com

    2007-3-1 15:17:36

    "误将第二个操作数当成操作码", 为什么? 而且是"有时".

  • carson2005

    2007-3-2 18:57:28

    建议你看看汇编语言的格式,尤其是程序计数器(program counter,简称PC)的工作方式,在各个指令中PC的值是不一样的,有的是PC<------PC+1,有的是PC<-----PC+2,而有的是PC<-------PC+3……

  • maxking

    2007-3-14 17:21:50

    受益了!不过写程序一直很少用NOP,除非是延时。但也从未发生过问题。

  • wangge6666@126.com

    2007-4-7 21:51:06

    作交通灯的这个实例,要是单片机差2个I/O口,请问有什么廉价的解决办法么?

  • 陶生

    2007-4-24 12:37:24

    你好!我是一名大学生.我现在要做一个交通灯的设计.板我已经做好了.就是汇编语言不会.特向你请教.帮忙编写一个汇编语言程序.我问过老师了.只需要编写一个循环的主程序.再加一个延时程序和显示程序.要求是东西绿灯持续亮30秒.最后五秒黄灯和绿灯一起闪烁.然后变为红灯.南北方向由红灯变为绿灯.持续20秒.最后五秒黄灯和绿灯一起闪烁.然后变为红灯.这样周而复始的循环.我用的单片机是AT89C2051 接数码显示管的是74LS164.数码显示管是共阴的.晶震是6M的.麻烦帮忙编写一下.不胜感激.我的邮箱是tstony@126.com  非常感谢!!!!!!

  • carson2005

    2007-4-11 22:03:13

    差两个I/O口,什么意思?

    其实我的设计仅仅是提出一种实现方法,在模拟试验的时候只要有6个LED就完全可以用了。

  • carson2005

    2007-5-1 17:00:50

    你的要求并不高,只要你仔细看了我的这个程序,稍微修改一下你的程序完全可以实现了.程序不是写出来的,而是修改出来的,写程序可能只需要10%的时间,但修改程序却需要90%的时间,但今天你花90%的时间去修改是为了明天只需要花10%就可以完成了.

    刚开始我学汇编的时候写个10行的程序也感觉很痛苦,但到现在,随便200,300行完全没有问题,而且几乎不用修改.我个人感觉做电子行业是从理论---->错误的实践------>理论------>正确的实践.

    你可以考虑一下