switch case with enum (LED state machine)

Thread Starter

Kittu20

Joined Oct 12, 2022
511
You guys realize the pin output buffer reflects the current state? No need for the superfluous state variable.

Just sayin'.
How program would handle the different timing. If we want to maintain the correct timing for each traffic light color, I think we would indeed need to use some form of state transition logic.
 

Thread Starter

Kittu20

Joined Oct 12, 2022
511
No difference. Just use (read) the pin buffer as the state variable.
Thank you for your patience and for pointing out the one way. The current state is directly determined by checking the status of the pin output buffers. If a pin is HIGH (1), it corresponds to the active state for that color. The state transitions and timing are handled based on these pin states. This approach simplifies the code by removing the need for a separate enumeration or state variable.
C:
#define RED_PIN LATB0
#define YELLOW_PIN LATB2
#define GREEN_PIN LATB1

// ... (definitions and initialization functions remain the same)

void main(void) {
    initializeHardware();
    configureTimer0();

    unsigned int previousTime = 0;

    while (1) {
        unsigned int currentTime = readElapsedTime();
        unsigned int elapsedTimeSinceLastLoop = currentTime - previousTime;
        previousTime = currentTime;

        if (RED_PIN) {
            if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                RED_PIN = 0;
                GREEN_PIN = 1;
            }
        } else if (GREEN_PIN) {
            if (elapsedTimeSinceLastLoop > 3000) { // 3 seconds
                GREEN_PIN = 0;
                YELLOW_PIN = 1;
            }
        } else if (YELLOW_PIN) {
            if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                YELLOW_PIN = 0;
                RED_PIN = 1;
            }
        }
    }
}
 

joeyd999

Joined Jun 6, 2011
6,309
Thank you for your patience and for pointing out the one way. The current state is directly determined by checking the status of the pin output buffers. If a pin is HIGH (1), it corresponds to the active state for that color. The state transitions and timing are handled based on these pin states. This approach simplifies the code by removing the need for a separate enumeration or state variable.
C:
#define RED_PIN LATB0
#define YELLOW_PIN LATB2
#define GREEN_PIN LATB1

// ... (definitions and initialization functions remain the same)

void main(void) {
    initializeHardware();
    configureTimer0();

    unsigned int previousTime = 0;

    while (1) {
        unsigned int currentTime = readElapsedTime();
        unsigned int elapsedTimeSinceLastLoop = currentTime - previousTime;
        previousTime = currentTime;

        if (RED_PIN) {
            if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                RED_PIN = 0;
                GREEN_PIN = 1;
            }
        } else if (GREEN_PIN) {
            if (elapsedTimeSinceLastLoop > 3000) { // 3 seconds
                GREEN_PIN = 0;
                YELLOW_PIN = 1;
            }
        } else if (YELLOW_PIN) {
            if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                YELLOW_PIN = 0;
                RED_PIN = 1;
            }
        }
    }
}
Yup. Be careful, though. You should always have a default case to reset to a known state if things get out of whack -- which may happen for a whole host of reasons.
 

Thread Starter

Kittu20

Joined Oct 12, 2022
511
@joeyd999

My actual plan is to make two way traffic lights control system. What do you think which approach would be best suitable for two way or four way traffic control system?

I think, the direct pin manipulation approach may be suitable for simpler systems like one way traffic lights , the state transition approach with an enumeration is likely more suitable and maintainable for a two-way traffic light system
 

joeyd999

Joined Jun 6, 2011
6,309
@joeyd999

My actual plan is to make two way traffic lights control system. What do you think which approach would be best suitable for two way or four way traffic control system?

I think, the direct pin manipulation approach may be suitable for simpler systems like one way traffic lights , the state transition approach with an enumeration is likely more suitable and maintainable for a two-way traffic light system
If you treat the port as just another memory register (assuming all the outputs are on the same port), there is no difference. You can even enumerate the states just like you did originally. They won't be sequential, but a case statement (or if-then-else) structure won't care.

Just remember to have a default case that gets your code out of trouble (regardless of what you use for a state variable).

Edit: my main issue with having two variables (a state counter and an output register) that essentially mirror each other is that you won't know if they get out of sync without adding extra sanity-testing code.
 

Thread Starter

Kittu20

Joined Oct 12, 2022
511
Just remember to have a default case that gets your code out of trouble (regardless of what you use for a state variable).
yes, totally agree including a default case in switch statement to handle unexpected situations is a good practice to ensure code behaves as expected.
 

joeyd999

Joined Jun 6, 2011
6,309
For your traffic light controller, I suggest you create a table structure that has entries for current state, next state, and timing information. Then your state machine reads the table when the state timer expires, sets up the new state and resets the time (to the time value of the new state).

If you do it right, there's no need to process each state individually, and changes to sequence and timing can be made to the table at the top of your code (or even better: non-volatile storage outside of your code that can be updated externally!)

In this way, the code will be far more generic, readable, maintainable, and smaller.
 

JohnInTX

Joined Jun 26, 2012
4,787
You guys realize the pin output buffer reflects the current state? No need for the superfluous state variable.

Just sayin'.
Sure, if you don't mind giving up a level of hardware abstraction but mainly I did not bring it up because we were working on the concept of needing critical sections when using interrupts. But by all means continue.
 

Thread Starter

Kittu20

Joined Oct 12, 2022
511
@JohnInTX

I've been working on the points mentioned in post 38 and I wanted to reach out to get your feedback. Could you please take a look at post #39 and let me know if I've adequately covered all the points that were suggested in post 39? Your input would be greatly appreciated!
 

JohnInTX

Joined Jun 26, 2012
4,787
@JohnInTX

I've been working on the points mentioned in post 38 and I wanted to reach out to get your feedback. Could you please take a look at post #39 and let me know if I've adequately covered all the points that were suggested in post 39? Your input would be greatly appreciated!
The point I was trying to make in #38 about clearing elapsedTimer is that, just like when you read it, you have to disable the interrupt when you clear it. I showed an example that you should read through and understand. The point is that the variable takes more than one instruction to clear it, just as it takes more than one instruction to read it. In between those instructions the interrupt can make changes to the variable that can corrupt the clearing or reading operations. That means that you have to temporarily disable the interrupt while performing those operations. Since you clear elapsedTimer at more than one point in the program, write a function that does the job. Fix that first.
 

Thread Starter

Kittu20

Joined Oct 12, 2022
511
The point I was trying to make in #38 about clearing elapsedTimer is that, just like when you read it, you have to disable the interrupt when you clear it. I showed an example that you should read through and understand. The point is that the variable takes more than one instruction to clear it, just as it takes more than one instruction to read it. In between those instructions the interrupt can make changes to the variable that can corrupt the clearing or reading operations. That means that you have to temporarily disable the interrupt while performing those operations. Since you clear elapsedTimer at more than one point in the program, write a function that does the job. Fix that first.
Thank you for the valuable feedback and guidance. Based on your advice, I've revisited the code and made the necessary adjustments to ensure proper synchronization and handling of the elapsedTime variable.

Here's the updated code:

C:
#include <xc.h>
#include <stdio.h>
#include "config.h"

#define RED_PIN LATB0
#define YELLOW_PIN LATB2
#define GREEN_PIN LATB1

enum LightState {
    RED_STATE,
    YELLOW_STATE,
    GREEN_STATE
};

enum LightState currentLightState = RED_STATE;
unsigned int elapsedTime = 0;

// ... (initializeHardware(), configureTimer0(), and ISR definitions remain the same)

void clearElapsedTime() {
    INTCONbits.GIE = 0; // Disable global interrupts
    elapsedTime = 0;
    INTCONbits.GIE = 1; // Enable global interrupts
}

void main(void) {
    initializeHardware();
    configureTimer0();

    unsigned int previousTime = 0;

    while (1) {
        unsigned int currentTime = readElapsedTime();
        unsigned int elapsedTimeSinceLastLoop = currentTime - previousTime;
        previousTime = currentTime;

        // Check light state based on elapsed time
        switch (currentLightState) {
            case RED_STATE:
                if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                    clearElapsedTime();
                    currentLightState = GREEN_STATE;
                    RED_PIN = 0;
                    YELLOW_PIN = 0;
                    GREEN_PIN = 1;
                }
                break;
            case GREEN_STATE:
                if (elapsedTimeSinceLastLoop > 3000) { // 3 seconds
                    clearElapsedTime();
                    currentLightState = YELLOW_STATE;
                    RED_PIN = 0;
                    YELLOW_PIN = 1;
                    GREEN_PIN = 0;
                }
                break;
            case YELLOW_STATE:
                if (elapsedTimeSinceLastLoop > 20000) { // 20 seconds
                    clearElapsedTime();
                    currentLightState = RED_STATE;
                    RED_PIN = 1;
                    YELLOW_PIN = 0;
                    GREEN_PIN = 0;
                }
                break;
        }
    }
}
If there are any further suggestions or comments, please feel free to share. Thanks again for your expertise! @JohnInTX
 

JohnInTX

Joined Jun 26, 2012
4,787
clearElapsedTime() looks OK finally.

C:
       unsigned int currentTime = readElapsedTime();
        unsigned int elapsedTimeSinceLastLoop = currentTime - previousTime;
        previousTime = currentTime;
I don't think elapsedTimeSinceLastLoop is ever going to get very big. You had it right earlier where you just clear the timer then let it count up to the values in your states.

Also, I am pretty sure that declaring a variable with an initializer only gets executed once during startup. I don't use that construct so I may be wrong or XC8 might handle it differently. I am too lazy to look it up myself but you can or another member can chime in. Why not just declare the variables then use them in the loop on a separate line to eliminate any confusion?

This would be a good time to get familiar with the simulator provided in MPLABX. You can step the code and set breakpoints at various points in the code to see exactly what is happening.

Good luck!
 
Top