| ??? 06/03/03 09:32 Read: times |
#47348 - RE: same old war Responding to: ???'s previous message |
Well Waqar.....maybe it is time for a formal tutorial on how a state machine could be coded. I thought for a while and came up with an interesting problem that is not the same as your project, but similar enough that you should be able to learn how to do a state machine.
The project is based upon having eight switches connected to a port of an 80C51 / 80C52 controller. This same controller also has eight LEDs connected to it on another port. The schematic of the connections is shown below. ![]() What we want to happen in the program is the following: Whenever any switch is pressed its active press to a low level should be de-bounced and then the corresponding LED should light for 5 seconds. Such as if SW4 is pressed then LED4 would turn on for 5 seconds. While an LED is lit its corresponding switch should be ignored. After the 5 second period has expired the program should go back to looking for the high to low transition of the switch being pressed. The program should be coded so that each switch is handled independant of the the others. Thus if SW2 was detected pressed and its LED was turned on it should appear to the user that he can also press SW3 at the same time or close to the same time and have its LED3 light as well. It should even be possible that any combination of the switches could be active at any one time. I designed a concept to handle this problem using a state machine. The state handling for each switch is the same except that different port bits are applicable to each one. Thus I decided that it should be possible to write one set of state machine code that could be re-used for each of the switch inputs. The states I defined for the state machine are as follows: State 0: Waiting for the switch input to be detected low indicating that the switch has been pressed. If the switch input is seen low then the state changes to State 1. State 1: Validation state to check if switch input is still low. If input bounced then the state should go back to State 0. If the switch input is validated as a low for this state then the LED for this switch should be turned on and a 5 second timer started that controls how long the LED should stay lit. The state number is then set to State 2. State 2: This waits for the 5 second LED flash interval to be completed. The 5 second timer/counter is decremented and if still non-zero then the State 2 should not change. However if the 5 second timer expires then the LED should be turned off and the state number changed to State 3. State 3. This waits for the switch input to go back to the inactive high state indicating that the switch has been released. It is possible that the switch was released during the 5 second LED flash time and if so this state catches this condition on the first pass into this state. The state machine is designed to be called on a periodic basis. I decided that 10 milliseconds was a good time period to debounce the switches and so I designed the state machine logic such that if it was called each 10 milliseconds, for a particular switch, then the status of that switch could be monitored and the LED could be managed. Since there are 8 separate switches to manage I designed a timer interrupt that runs eight times faster than 10 milliseconds or equivalently at 1.25 milliseconds period. The idea is to maintain a global variable that will count from 0 -> 1 -> 2 ..... -> 7 and back to 0 again in a cyclic manner. Each occurrance or the 1.25 millisecond interrupt will call the state machine logic for the switch number in this counter. After the state subroutine is complete the counter is incremented up to the next switch number. The net effect of this is that the state machine for a particular switch is called once each 10 milliseconds. The code needs to maintain separate state variables for each switch and a separate 5 second timer counters for each LED flash time because each is to operate independantly. I decided to organize the code with three tables that are accessed with indexing. One table is 8 bytes long and holds the current state number for each switch. A second table is 16 bytes long and holds the 8 LED flash time counters. For this program these needed to be two bytes each because the counters, which get processed every 10 milliseconds (100 times a second) need to count from 500 down to zero to arrive at a 5 second interval. A third table is a look-up-table in the code space that is used to convert the current 0 -> 7 switch number into a bit mask for the SWITCH input port and the LED output port. I decided to have the timer interrupt be a very short function that just sets a bit flag at each interrupt time. The main line code then runs the state machine process and polls on the bit flag from the timer interrupt to obtain the net 1.25 msec rate to dispatch the state machine calls. Here is the code I made to implement the state machine. Please note that I just designed this code and typed it into a source file and assembled it till there were no assembly errors. The code has just been "bench checked" by reading over the source code but it has not been formally debugged. As such there may be some minor mistakes or an instruction left out uninterntionally. Note that if anyone else here has a proto type board and has a desire to build up this circuit and test the code please do. Then let me know the results and code corrections that may be necessary. If that is done and the code becomes "known good" then I propose that we add this program as tutorial to the appropriate section of this web site as "How to write a state machine". Paritial credit can go to those that debug the code. Please note that this code is written in a manner to maximize modularity and clarity. There are a number of things that could be done to reduce the size of the compiled object code and / or make the code faster. However I tend not to write the tightest possible code in favor of clarity and modularity. And at the same time it will make this program easier for the learner to follow and understand how it works. Any questions about the code and how it works should be posted to this thread and not emailed to me. When posted here there is an opportunity for many people to learn and not just one.
$NOMOD51
$INCLUDE(REG51FX.INC) ; register definitions compatible
; with standard Intel architecture FX cpu
USING 0 ; use register block 0
SWTPORT EQU P2 ; switches connected to Port 2
LEDPORT EQU P3 ; LEDs connected to Port 3
;***************************************************
; INTERNAL DATA BIT DEFINITIONS, locations 20H-2FH
;---------------------------------------------------
BSEG AT 00
Tick:
DBIT 1 ; 1 = 1.25 msec tick has come
;***************************************************
; INTERNAL DATA BYTE DEFINITIONS
;---------------------------------------------------
;internal data area variables.
;
DSEG AT 30H ; word locations are HIGH:LOW
SwitchNo:
DS 1 ; current switch number to process (0->7)
SwitchStates:
DS 1 ; current state of Switch 0
DS 1 ; current state of Switch 1
DS 1 ; current state of Switch 2
DS 1 ; current state of Switch 3
DS 1 ; current state of Switch 4
DS 1 ; current state of Switch 5
DS 1 ; current state of Switch 6
DS 1 ; current state of Switch 7
SwitchCounters:
DS 2 ; counter for Switch 0
DS 2 ; counter for Switch 1
DS 2 ; counter for Switch 2
DS 2 ; counter for Switch 3
DS 2 ; counter for Switch 4
DS 2 ; counter for Switch 5
DS 2 ; counter for Switch 6
DS 2 ; counter for Switch 7
STACK:
DS 20 ; reserve 20 bytes of space
RAM_END:
DS 1 ; end of allocated RAM marker
;***************************************************
; INTERRUPT VECTOR DEFINITIONS
;---------------------------------------------------
CSEG AT 0000H ; reset vector
JMP PWR_UP ; power-on entry point
CSEG AT 0003H ; intr 0 vector
CLR EX0 ; external int 0 not used
RETI
CSEG AT 000BH ; timer 0 vector
RETI ; not used
CSEG AT 0013H ; intr 1 vector
CLR EX1 ; external int 1 not used
RETI
CSEG AT 001BH ; timer 1 vector
JMP T1_ISR ; timer 1 int isr
CSEG AT 0023H ; UART vector
CLR ES ; serial port int not used
RETI
;***************************************************
; BASE OF CODE AREA
;---------------------------------------------------
CSEG AT 040H
DB 'Copyright (C) Carousel Design 2003',0
;***************************************************
; NAME: INIT_T1
; Initialize a 1.25 mSec period interrupt for a
; state machine dispatcher on timer 1. This is same
; as a frequency of 800 Hz.
; Mode 1 is 16-bit
; TMOD is not bit addressable.
;
; Timer clocks are 11.0952 mHz / 12 = 924600 Hz
; Divisor for 800 Hz = 924600 / 800 = 1155.75 (approx 1156)
;---------------------------------------------------
T1_RELOAD EQU (65536 - 1156)
INIT_T1:
MOV TMOD, #10H ; timer 1 - mode 1
;
MOV TH1, #HIGH(T1_RELOAD) ;to setup 800 Hz rate
MOV TL1, #LOW(T1_RELOAD) ;timer 1 for 1.25 ms
CLR Tick ; clear the tick indicator flag
SETB TR1 ; start the timer 1
RET
;***************************************************
;NAME: T1_ISR
; Used to time an 800 Hz ticker bit
;---------------------------------------------------
T1_ISR:
PUSH PSW ; save entry state
SETB Tick ; show tick time
;
CLR ET1 ; stop it for a moment
MOV TH1, #HIGH(T1_RELOAD) ; to setup 800 Hz rate
MOV TL1, #LOW(T1_RELOAD) ; timer 1 for 1.25 ms
SETB ET1 ; restart the timer
;
POP PSW ; restore state
RETI
;***************************************************
;NAME: SWT_MSK
; Used to convert a switch number to a port mask
; Entry A is the 0-7 switch mask
;---------------------------------------------------
SWT_MSK:
INC A
MOVC A, @A+PC
RET
;
DB 00000001B ; Bit 0 <- 0
DB 00000010B ; Bit 1 <- 1
DB 00000100B ; Bit 2 <- 2
DB 00001000B ; Bit 3 <- 3
DB 00010000B ; Bit 4 <- 4
DB 00100000B ; Bit 5 <- 5
DB 01000000B ; Bit 6 <- 6
DB 10000000B ; Bit 7 <- 7
;***************************************************
; MAIN PROGRAM INITIALIZATION
;---------------------------------------------------
PWR_UP:
MOV SP, #STACK ; set stack pointer
;
MOV SWTPORT, 0FFH ; set to make the switch ports inputs
MOV LEDPORT, 0FFH ; fix so all LEDs are off
CALL INIT_T1 ; initialize timer 1 interrupt
CLR A ; initialize switch number variable
MOV SwitchNo, A
MOV R0, #SwitchStates ; clear all switch states to 0
MOV R2, #8
SwStInit:
MOV @R0, #0
INC R0
DJNZ R2, SwStInit
MOV R0, #SwitchCounters ; clear all switch counters
MOV R2, #8
SwCtInit:
MOV @R0, #0
INC R0
MOV @R0, #0
INC R0
DJNZ R2, SwCtInit
;
SETB EA ; enable interrupts
JMP MAIN_LOOP
;
;
; here is the main loop state table for the processing
; of the states for a specific switch.
;
; States are defined as:
; State 0: Waiting for a switch input to show it was
; detected going low.
; State 1: Waiting for second verification of a switch
; input in the low state as a debounce verification.
; State 2: Waiting for 5 second counter time to expire while LED
; is kept on for 5 seconds
; State 3: Waiting for switch input to show high again
;
STATE_TABLE:
DW MAIN_STATE_0 ; pointer to State 0 routine
DW MAIN_STATE_1 ; pointer to State 1 routine
DW MAIN_STATE_2 ; pointer to State 2 routine
DW MAIN_STATE_3 ; pointer to State 3 routine
STATE_CNT EQU ($ - STATE_TABLE)/2 ;number of states
;
;
;***************************************************
; main loop process
;
; This processes the current state each switch in a round robin manner.
; Each state is dispatched whenever the timer 1 interrupt indicates that
; 1.25 milliseconds has gone by. Then the state routine for one switch is called
; followed by an increment of the switch number variable. The main loop
; then goes back to the top to wait for another 1.25 msec period to expire.
; Since we process each of 8 switches in turn the effective processing rate
; for each switch input is 1,25 msec * 8 = 10 mSec (or 100 Hz).
;---------------------------------------------------
;
MAIN_LOOP:
JNB Tick, MAIN_LOOP ; wait till a tick has gone by
;
CLR Tick ; clear bit once seen
;
MOV A, SwitchNo ; fetch the state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV A, @R0
CJNE A, #STATE_CNT,ML_A ; check for legal state number
ML_A:
JC ML_B ; state number OK
CLR A ; reset to 0 if invalid
MOV @R0, A
ML_B:
MOV DPTR, #STATE_TABLE ; point to state branch table
CALL CALL_TABLE ; call to state routine
;
MOV A, SwitchNo ; increment to next switch number
INC A
ANL A, #7 ; limit to 3 bits of switch number
MOV SwitchNo, A
;
JMP MAIN_LOOP ; go wait for next tick time
;***************************************************
;NAME: MAIN_STATE_0
; This is state processing routine to wait for a
; switch to be in the pressed state where the input
; for the switch comes from the SWTPORT. If the input
; is still high then just stay in State 0 otherwise
; transition to State 1.
;
; Variable SwitchNo has the current switch number.
;---------------------------------------------------
MAIN_STATE_0:
MOV A, SwitchNo ; get the switch number
CALL SWT_MSK ; get mask for this switch number
ANL A, SWTPORT ; look at switch state
JNZ MAIN_STATE_0X ; exit no change if switch still high
;
;switch is pressed so change to State 1.
;
MOV A, SwitchNo ; set new state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV @R0, #1 ; force next state number to State 1
;
MAIN_STATE_0X:
RET
;***************************************************
;NAME: MAIN_STATE_1
; This is state processing routine to validate that
; switch is still in the pressed state where the input
; for the switch comes from the SWTPORT. If the input
; is still low then transfer to State 2 with LED on
; for 5 seconds. Otherwise if input is high we have
; bounce so return back to state 0.
;
; Variable SwitchNo has the current switch number.
;---------------------------------------------------
FIVE_SEC_CNT EQU 5 * 100 ; 5 seconds is 5 * 100 Hz state 2 rate (i,e, 10 msec)
MAIN_STATE_1:
MOV A, SwitchNo ; get the switch number
CALL SWT_MSK ; get mask for this switch number
ANL A, SWTPORT ; look at switch state
JZ MAIN_STATE_1B ; input low so switch still pressed
;
MAIN_STATE_1A: ; here if switch input bounced back high
MOV A, SwitchNo ; set new state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV @R0, #0 ; force next state number back to State 0
JMP MAIN_STATE_1X
;
MAIN_STATE_1B: ;here if switch input valid low for 10 mSec
MOV A, SwitchNo ; get bit to set on the LED for this switch
CALL SWT_MSK ; get mask for this switch number
XRL A, 0FFH ; invert mask
ANL LEDPORT, A ; drive the LED bit low to turn on the LED
;
MOV A, SwitchNo ; set the counter for this switch to 5 seconds
CLR C
RLC A ; make *2 word type index to counter table
ADD A, #SwitchCounters
MOV R0, A ; make pointer to counter word
MOV @R0, #HIGH(FIVE_SEC_CNT) ;set counter for 5 seconds
INC R0
MOV @R0, #LOW(FIVE_SEC_CNT)
;
MOV A, SwitchNo ; set new state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV @R0, #2 ; force next state number to State 2
;
MAIN_STATE_1X:
RET
;***************************************************
;NAME: MAIN_STATE_2
; This is state processing routine to wait out the
; five second time that the LED is activated. The
; switch input status is just ignored. If the count
; is still active then the state stays as State 2.
; When the 5 seconds has expired then the LED for
; this switch is shut off and the state is changed
; to state 3.
;
; Variable SwitchNo has the current switch number.
;---------------------------------------------------
MAIN_STATE_2:
MOV A, SwitchNo ; decrement the counter for this switch
CLR C
RLC A ; make *2 word type index to counter table
ADD A, #SwitchCounters+1 ;offset to access the low byte first.
MOV R0, A
MOV A, @R0 ; fetch low byte and decrement it
DEC A
CJNE A, #0FFH, MAIN_STATE_2A ;low byte did not underflow
DEC R0
DEC @R0 ; decrement the high byte
INC R0
MAIN_STATE_2A:
MOV @R0, A ; save the low byte
;
DEC R0 ; check for count having gone to zero
ORL A, @R0
JNZ MAIN_STATE_2X ; exit staying in state 2 if counter not expired
;
MAIN_STATE_2B: ;here when the 5 second timer has expired
MOV A, SwitchNo ; get bit to set on the LED for this switch
CALL SWT_MSK ; get mask for this switch number
ORL LEDPORT, A ; drive the LED bit high to turn off the LED
;
MOV A, SwitchNo ; set new state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV @R0, #3 ; force next state number to State 3
;
MAIN_STATE_2X:
RET
;***************************************************
;NAME: MAIN_STATE_3
; This is state processing routine to wait for the
; switch input to go back high, It may have already
; gone high during the 5 second LED period in
; which case we catch it high here on the first check.
; If the switch input is still low then stay in
; State 3. If the switch has gone high then transfer
; back to State 0.
;
; Variable SwitchNo has the current switch number.
;---------------------------------------------------
MAIN_STATE_3:
MOV A, SwitchNo ; get the switch number
CALL SWT_MSK ; get mask for this switch number
ANL A, SWTPORT ; look at switch state
JZ MAIN_STATE_3X ; exit no change if switch still low
;
;switch is released so change to State 0.
;
MOV A, SwitchNo ; set new state for the current
ADD A, #SwitchStates ; switch
MOV R0, A
MOV @R0, #0 ; force next state number to State 0
;
MAIN_STATE_3X:
RET
;***************************************************
;NAME: CALL_TABLE
; Routine to call a routine through a table.
; Come here with A as the 0-n index into the
; table and DPTR pointing to the base of the
; table. The return at end of called routine
; takes execution back to where this routine
; was called from. This routine also uses R0.
;---------------------------------------------------
CALL_TABLE:
CLR C ;
RLC A ; multiply * 2 for word access
MOV R0, A ; save a copy of index
INC A ; increment index to the hig byte
MOVC A, @A+DPTR ; low byte
PUSH ACC ; onto stack
MOV A, R0
MOVC A, @A+DPTR ; high byte
PUSH ACC ; onto stack
RET ; direct branch to the subroutine
END
Michael Karas |




