《51单片机应用开发从入门到精通》——2.9 交通灯实例

本节书摘来自异步社区《51单片机应用开发从入门到精通》一书中的第2章,第2.9节,作者 张华杰,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.9 交通灯实例

定时器/计数器是单片机中最常用、最重要的功能模块之一,本节通过交通灯控制器实例来演示定时器的使用方法,并复习如何使用上节学习的散转程序。

本节首先介绍交通灯的基础知识以及定时器/计数器的基础知识,接着介绍本实例的硬件电路构成,然后逐步分析定时器的编程以及程序的全貌,最后将总结一下本实例的技巧与注意点。

2.9.1 基础知识

交通灯控制器实例主要使用了8051单片机的定时器/计数器,本实例的基础知识主要包括:交通灯的变化规律、定时器/计数器的概念、定时器/计数器的相关寄存器、定时器/计数器的4种工作方式以及定时器/计数器的编程。下面就从这几个方面进行 介绍。

1.交通灯的变化规律

本实例是交通灯控制器,所以先介绍交通灯的变化规律。

假设一个十字路口为东西南北走向。初始状态为状态1南北方向绿灯通车,东西方向红灯。经过过一段时间(20秒)转换到状态2,南北方向绿灯闪几次转亮黄灯,延时3秒,东西方向仍然红灯。再转换到状态3,东西方向绿灯通车,南北方向红灯。过一段时间(20秒)转换到状态4,东西方向绿灯闪几次转亮黄灯,延时3秒,南北方向仍然红灯。最后循环至状态1。

2.定时器/计数器的概念

8051单片机内有两个可编程的定时器/计数器T0、T1。

当定时器/计数器用作“定时器”功能时,每经过1个机器周期(12个时钟周期),计数器加1。

当定时器/计数器用作“计数器”功能时,计数器在对应的外部输入管脚(T0为P3.4引脚,T1为P3.5引脚)上每发生一次1到0的跳变时加1。使用“计数器”功能时,外部输入每个机器周期被采样一次。当某一周期管脚状态采样为高而下一周期采样为低时,计数器加1。由于检测下降沿跳变需要两个机器周期(24个时钟周期)的时间,所以计数频率最大值只能为时钟周期的1/24。计数器对外部输入信号的占空比并无限制,但为了保证给定的电平信号在其改变之前至少被采样一次,外部输入信号必须至少保持一个完整的机器周期。

3.定时器/计数器的相关寄存器

与定时器/计数器相关的寄存器有定时器/计数器工作方式寄存器(TMOD)、定时器/计数器控制寄存器(TCON)。TCON已经在2.5节受控输出实例中介绍过,在本例中主要介绍TMOD寄存器。

定时器/计数器工作方式寄存器(TMOD),字节地址89H,不可进行位寻址。TMOD的格式如图2-13所示。


《51单片机应用开发从入门到精通》——2.9 交通灯实例

定时器/计数器工作方式寄存器(TMOD)的8位分为两组,高4位控制T1,低4位控制T0。TMOD每一位的功能如下:

  • GATE:门控位。

GATE = 0,仅由运行控制位TRx(x = 0,1) = 1来启动定时器/计数器运行;
GATE = 1,由运行控制位TRx(x = 0,1) = 1和外部中断引脚上的高电平共同来启动定时器/计数器运行。 - C/T:定时器模式和计数器模式选择位。

C/T = 0,为定时器模式;
C/T = 1,为计数器模式。 - M1、M0:工作方式选择位。M1、M0的4中编码对应4种工作方式,对应关系见表2-11。


《51单片机应用开发从入门到精通》——2.9 交通灯实例

4.定时器/计数器的4种工作方式

定时器/计数器的4种工作方式下的逻辑结构如表2-12所示。


《51单片机应用开发从入门到精通》——2.9 交通灯实例


《51单片机应用开发从入门到精通》——2.9 交通灯实例


《51单片机应用开发从入门到精通》——2.9 交通灯实例


《51单片机应用开发从入门到精通》——2.9 交通灯实例

(1)方式0

定时器/计数器的工作方式0称为13位定时器/计数器。它是由TLx的低5位和THx的8位构成13位的计数器,此时TLx的高3位未使用。该工作方式是为了和48系列单片机兼容而设计的一种工作方式,一般情况下一般不使用方式0进行定时/计数。方式0的控制方式与方式1完全相同,下面重点介绍方式1的控制方式。

(2)方式1

定时器/计数器的工作方式1称为16位定时器/计数器。它由TLx和THx构成,TLx计数溢出向THx进位,THx计数溢出置位TCON中的溢出标志位TFx。

GATE位的状态决定定时器/计数器运行控制取决于TRx一个条件还是TRx和INTx引脚这两个条件。当GATE = 0时,则只要TRx被置为1,定时器/计数器即被允许计数(定时器/计数器的计数控制仅由TRx的状态确定,TRx = 1计数,TRx = 0停止计数)。当GATE = 1时,定时器/计数器是否计数由INTx输入的电平和TRx的状态共同确定:当TRx = 1,且INTx = 1时,才允许定时器/计数器计数(定时器/计数器的计数控制由TRx和INTx两个条件控制)。

(3)方式2

定时器/计数器的工作方式0和方式1在计数溢出后,计数器的值为0,需要通过程序重新装入计数初值。

定时器/计数器的工作方式1称为初值自动重装的8位定时器/计数器。在该工作方式下,TLx作为计数器,当TLx计数溢出时,在置1溢出标志TFx的同时,还自动地将THx中的常数送至TLx,使TLx从该常数开始重新计数。这种工作方式可以省去用户软件中重装常数的程序,简化定时常数的计算方法(确定计数初值),可以相当精确地确定定时时间。

(4)方式3

工作方式3仅对定时器/计数器0有效,在该工作方式下,定时器/计数器0被拆成2个独立的定时/计数器:TL0、TH0。TL0使用T0的状态控制位C/T、GATE、TR0、INT0,而TH0被固定为一个8位定时器(不能用作外部计数方式),并使用定时器/计数器1的状态控制位TR1和TF1,同时占用定时器T1的中断源。此时,定时器/计数器1可设定为方式0、方式1和方式2,作为串行口的波特率发生器。

注意:此时,定时器/计数器1也可作为定时器,用于不需要中断的场合。

5.定时器/计数器的编程

(1)初始化

定时器/计数器的初始化编程包括以下几个部分:

  • 根据要求给定时器/计数器方式寄存器(TMOD)送一个方式控制字,以设定定时器/计数器的工作方式。
  • 根据需要给TH和TL寄存器送初值,以确定需要的定时时间或计数的初值。
  • 根据需要给中断允许寄存器(IE)送中断控制字,以开放相应的中断和设定中断优先级。

注意:也可用查询方式来响应定时器。

  • 给TCON寄存器送命令字以启动或禁止定时/计数器的运行。

(2)定时器/计数器初值的计算

  • 计数器初值:

设计数器的模值为M,所需的计数值为C,计数初值设定为TC,则
TC = M-C(M = 213、216或28)。 - 定时器初值:

设定时器的模值为M,需要的定时时间为T,定时器的初值设定为TC,则
TC = M-T/t 机器周期(M = 213、216或28)。

2.9.2 硬件电路图

本实例硬件电路如图2-14所示,使用6只LED模拟两组红绿灯:VD1、VD2、VD3分别模拟南北方向的红灯、黄灯、绿灯,VD4、VD5、VD6分别模拟东西方向的红灯、黄灯、绿灯。


《51单片机应用开发从入门到精通》——2.9 交通灯实例

2.9.3 软件程序设计

交通灯控制器实例使用了8051单片机的定时器/计数器,软件程序设计部分首先分定时器初始化、定时器中断服务程序两个部分介绍定时器/计数器的软件编程。然后介绍如何在画出程序流程图的基础上编写软件程序,并给出了完整的交通灯控制器程序实例。

1.定时器初始化

为了使定时时间准确,不出现因为定时器重装而引起的累计误差,将定时器设置为初值自动重装的8位定时器/计数器,即定时器工作在工作方式2。在12MHz晶振条件下,8位定时器的最长定时时间为0.256毫秒,为了方便计算取定时时间为0.25毫秒,这样,定时0.5秒钟需要定时器中断2000次。

下面计算定时器的初值。定时器初值TC = M-T/t_机器周期_=__28-250/1 = 6,因此TH0 = 06H,TL0 = 06H。

以下为定时器初始化程序实例,定时器T0设定为工作方式2,初始值为06H,自动重装入值为06H。

T0_INIT:
;---------------------------------
;     定时器T0的初始化
;---------------------------------
   MOV TMOD,#00000010B ;定时器T0工作在方式2
   MOV TL0,#06H   ;设定定时器T0的初始值
   MOV TH0,#06H   ;设定定时器T0的自动重装入值
   MOV TCON,#00010000B ;定时器T0使能
   SETB EA    ;中断允许总控制位使能
   SETB ET0    ;T0中断使能
   RET

2.定时器中断服务程序

定时器中断服务程序实例如下:

T0_INT:
;---------------------------------
;     T0中断服务程序
;每0.5秒置一SECOND_FLAG
;---------------------------------
   DJNZ TIME_COUNT0,T0_INT_EXIT
   MOV TIME_COUNT0,#250
   DJNZ TIME_COUNT1,T0_INT_EXIT
   MOV TIME_COUNT1,#8
   SETB SECOND_FLAG  ;定时0.5秒到,置一SECOND_FLAG
T0_INT_EXIT:
   RETI

每0.25毫秒定时器中断发生,程序跳转到中断服务程序T0_INT开始执行。中断服务程序每次将定时器中断计数变量减1,当定时器中断计数变量为0时,0.5秒定时时间到,将位变量SECOND_FLAG置1。定时器中断服务程序通过RETI指令返回,程序将跳转到进入中断前的断点继续执行。

3.程序流程图

在前面几节中,程序较为简单,可以直接进行程序的编写,但本实例的程序流程比较复杂,在编写程序之前,应当先画出程序流程图。在编写复杂的程序之前画出程序流程图,有助于理清思路,方便编程,应当养成编写程序前画程序流程图的良好 习惯。

程序流程图是描述程序运行流程的一种图表。它不仅描绘程序从头至尾的运行顺序,也描述了程序运行过程中的所有可能的状况。一般程序流程图包含的基本元素如表2-13所示。


《51单片机应用开发从入门到精通》——2.9 交通灯实例

本实例的程序流程图如图2-15所示。


《51单片机应用开发从入门到精通》——2.9 交通灯实例

4.程序全貌

;-----------------------------------
;                   交通灯控制器实例
;功能:使用6只LED模拟交通灯控制器
;-----------------------------------
SOUTH_RED  EQU  P1.0
SOUTH_YELLOW  EQU  P1.1
SOUTH_GREEN  EQU  P1.2
EAST_RED   EQU  P1.3
EAST_YELLOW  EQU  P1.4
EAST_GREEN  EQU  P1.5
SECOND_FLAG  BIT  00H
TIME_COUNT0  DATA 30H
TIME_COUNT1  DATA 31H
STATUS_FLAG  DATA 32H
SECOND_COUNT  DATA 33H

ORG 0000H      ;伪指令,指定程序从0000H开始存放
LJMP MAIN     ;跳转指令,程序跳转到MAIN处
ORG 000BH      ;伪指令,指定程序从T0入口地址000BH开始存放
LJMP  T0_INT   ;跳转指令,程序跳转到T0_INT处

ORG 0100H
MAIN:
   MOV SP,#60H     ;给堆栈指针赋初值
   LCALL INIT      ;调用状态初始化子程序
   LCALL T0_INIT   ;调用定时器中断初始化子程序
LOOP:
   JNB  SECOND_FLAG,LOOP  ;根据SECOND_FLAG值判断0.5秒是否到
   LCALL STATUS_CHANGE    ;每0.5秒调用交通灯状态转换子程序
   CLR  SECOND_FLAG       ;清零SECOND_FLAG
   SJMP  LOOP             ;跳转,程序继续

STATUS_CHANGE:
;---------------------------------
;     交通灯状态转换子程序
;---------------------------------
   MOV  A,STATUS_FLAG  ;根据STATUS_FLAG值进行散转
   ADD  A,STATUS_FLAG
   MOV  DPTR,#STATUS_PROC_TABLE
   JMP  @A + DPTR
STATUS_PROC_TABLE:
   AJMP STATUS1
   AJMP STATUS2
   AJMP STATUS3
   AJMP STATUS4
   AJMP STATUS5
   AJMP STATUS6

STATUS1:
;---------------------------------
;     散转子程序1
;状态1:南北绿灯,东西红灯,持续20秒
;然后转到状态2
;---------------------------------
   DJNZ SECOND_COUNT,STATUS1_EXIT
   MOV SECOND_COUNT,#6  ;20秒定时到,转换到状态2
   MOV STATUS_FLAG,#01H
STATUS1_EXIT:
   RET

STATUS2:
;---------------------------------
;     散转子程序2
;状态2:南北绿灯闪,东西红灯,持续3秒
;然后转到状态3
;---------------------------------
   DJNZ SECOND_COUNT,STATUS2_EXIT
   MOV SECOND_COUNT,#4  ;3秒定时到,转换到状态3
   MOV STATUS_FLAG,#02H
   SETB SOUTH_RED   ;南北红灯灭
   CLR  SOUTH_YELLOW  ;南北黄灯亮
   SETB SOUTH_GREEN   ;南北绿灯灭
   CLR  EAST_RED   ;东西红灯亮
   SETB EAST_YELLOW   ;东西黄灯灭
   SETB EAST_GREEN   ;东西绿灯灭
   RET
STATUS2_EXIT:
   CPL  SOUTH_GREEN  ;南北绿灯闪
   RET

STATUS3:
;---------------------------------
;     散转子程序3
;状态3:南北黄灯,东西红灯,持续2秒
;然后转到状态4
;---------------------------------
   DJNZ SECOND_COUNT,STATUS3_EXIT
   MOV SECOND_COUNT,#40  ;2秒定时到,转换到状态4
    MOV STATUS_FLAG,#03H
   CLR  SOUTH_RED  ;南北红灯亮
   SETB SOUTH_YELLOW   ;南北黄灯灭
   SETB SOUTH_GREEN   ;南北绿灯灭
   SETB EAST_RED    ;东西红灯灭
   SETB EAST_YELLOW   ;东西黄灯灭
   CLR  EAST_GREEN  ;东西绿灯亮
STATUS3_EXIT: 
   RET

STATUS4:
;---------------------------------
;     散转子程序4
;状态4:南北红灯,东西绿灯,持续20秒
;然后转到状态5
;---------------------------------                        
   DJNZ SECOND_COUNT,STATUS4_EXIT
   MOV SECOND_COUNT,#6  ;20秒定时到,转换到状态5
   MOV STATUS_FLAG,#04H
STATUS4_EXIT:
   RET

STATUS5:
;---------------------------------
;     散转子程序5
;状态5:南北红灯,东西绿灯闪,持续3秒
;然后转到状态6
;---------------------------------
   DJNZ SECOND_COUNT,STATUS5_EXIT
   MOV SECOND_COUNT,#4  ;3秒定时到,转换到状态6
   MOV STATUS_FLAG,#05H
   CLR  SOUTH_RED  ;南北红灯亮
   SETB SOUTH_YELLOW   ;南北黄灯灭
   SETB SOUTH_GREEN   ;南北绿灯灭
   SETB EAST_RED    ;东西红灯灭
   CLR  EAST_YELLOW  ;东西黄灯亮
   SETB EAST_GREEN   ;东西绿灯灭
   RET
STATUS5_EXIT:
   CPL  EAST_GREEN  ;东西绿灯闪
   RET

STATUS6:
;---------------------------------
;     散转子程序6
;状态6:南北红灯,东西黄灯,持续2秒
;然后转到状态1
;---------------------------------
   DJNZ SECOND_COUNT,STATUS6_EXIT
   MOV SECOND_COUNT,#40  ;2秒定时到,转换到状态1
   MOV STATUS_FLAG,#00H
   SETB SOUTH_RED   ;南北红灯灭
   SETB SOUTH_YELLOW   ;南北黄灯灭
   CLR  SOUTH_GREEN  ;南北绿灯亮
   CLR  EAST_RED   ;东西红灯亮
   SETB EAST_YELLOW   ;东西黄灯灭
   SETB EAST_GREEN   ;东西绿灯灭
STATUS6_EXIT:
   RET

INIT:
;---------------------------------
;     状态初始化子程序
;---------------------------------
   SETB SOUTH_RED   ;南北红灯灭
   SETB SOUTH_YELLOW   ;南北黄灯灭
   CLR  SOUTH_GREEN  ;南北绿灯亮
   CLR  EAST_RED   ;东西红灯亮
   SETB EAST_YELLOW   ;东西黄灯灭
   SETB EAST_GREEN   ;东西绿灯灭
   MOV TIME_COUNT0,#250  ;变量TIME_COUNT0赋初值250
   MOV TIME_COUNT1,#8  ;变量TIME_COUNT1赋初值8
   MOV SECOND_COUNT,#40  ;变量SECOND_COUNT赋初值40
   MOV STATUS_FLAG,#00H  ;变量STATUS_FLAG赋初值00H
   CLR  SECOND_FLAG  ;清零位变量SECOND_FLAG
   RET

T0_INT:
;---------------------------------
;     T0中断服务程序
;每0.5秒置一SECOND_FLAG
;---------------------------------
   DJNZ TIME_COUNT0,T0_INT_EXIT
   MOV TIME_COUNT0,#250
    DJNZ TIME_COUNT1,T0_INT_EXIT
   MOV TIME_COUNT1,#8
   SETB SECOND_FLAG  ;定时0.5秒到,置一SECOND_FLAG
T0_INT_EXIT:
   RETI

T0_INIT:
;---------------------------------
;     定时器T0的初始化
;---------------------------------
   MOV TMOD,#00000010B  ;定时器T0工作在方式2
   MOV TL0,#06H    ;设定定时器T0的初始值
   MOV TH0,#06H    ;设定定时器T0的自动重装入值
   MOV TCON,#00010000B  ;定时器T0使能
   SETB EA     ;中断允许总控制位使能
   SETB ET0     ;T0中断使能
   RET

END

2.9.4 技巧总结

本实例以交通灯控制器为例介绍了8051单片机定时器/计数器的使用方法,通过本实例应注意以下的几个技巧。

  • 定时器的初始化以及定时时间的计算。
  • 定时器的中断服务程序的编写应注意现场保护和现场恢复的操作;中断服务程序通过RETI指令返回;中断服务程序应当尽量简短。
  • 在编写复杂的程序前,画程序流程图可以理清思路,使编程事半功倍。
上一篇:从《2015年中国互联网安全报告》看安全趋势发展


下一篇:微信支付自带的简易log