Email: Password: Remember Me | Create Account (Free)

Back to Subject List

Old thread has been locked -- no new posts accepted in this thread
???
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


List of 26 messages in thread
TopicAuthorDate
removing from check list            01/01/70 00:00      
   RE: removing from check list            01/01/70 00:00      
      RE: removing from check list            01/01/70 00:00      
      RE: removing from check list            01/01/70 00:00      
      RE: removing from check list            01/01/70 00:00      
         RE: removing from check list            01/01/70 00:00      
   RE: removing from check list            01/01/70 00:00      
   RE: removing from check list            01/01/70 00:00      
      RE: removing from check list            01/01/70 00:00      
         RE: removing from check list            01/01/70 00:00      
            mahmood Elnasser            01/01/70 00:00      
         RE: removing from check list            01/01/70 00:00      
   same old war            01/01/70 00:00      
      RE: same old waqar            01/01/70 00:00      
      RE: same old war            01/01/70 00:00      
         Tutorials addition            01/01/70 00:00      
         RE: same old war            01/01/70 00:00      
            RE: same old war            01/01/70 00:00      
         RE: same old war            01/01/70 00:00      
         Stepper Motor Control            01/01/70 00:00      
            RE: Stepper Motor Control            01/01/70 00:00      
               RE: Stepper Motor Control            01/01/70 00:00      
                  RE: Stepper Motor Control            01/01/70 00:00      
   i will make the circuit            01/01/70 00:00      
      Congratulations - Mike            01/01/70 00:00      
         RE: Congratulations - Mike            01/01/70 00:00      

Back to Subject List