Simple Interrupt Handling program

interruptsrs232thread

I'm new to Embedded programming and I'm trying to understand how Interrupt handlers work in different contexts/scenarios. I just want to know how a interrupt handler would work in the following scenario.

We have a data stream coming from a RS232 interface that is processed by some microcontroller. An interrupt handler(of void type) has a read() function which reads the incoming data bytes. If a character is detected then the interrupt handler invokes a function called detectString() which returns TRUE if the string matches the reference string which is "ON". If detectString() returns boolean TRUE it invokes a function called LED_ON() which should turn on an LED for 1 minute. If it returns false it should turn off the LED. Lets say the microcontroller has a clock frequency of 20MHz and an addition operation taken 5 clock cycles.

My questions are as follows

  1. How do we approach this problem with an FSM?
  2. The RS232 interface keeps transmitting data even after the LED is turned on. So am I correct in assuming that the interrupt handler should work with a one thread and the functions that it invokes should work from a different threads?
  3. How would a skeletal program implementing this FSM look like? (pseudocode might really help to understand the backbone of the design)

Best Answer

You are correct with #2: two "threads" will be required.

Typically, your interrupt routine will save characters into a buffer. Your main program will continuously read the buffer, call detectString and implement your logic.

You can use hardware flow control if you're concerned about missing characters. One the buffer is full, you can make the sender wait until you're ready again.

Your FSM might be a switch statement inside your main loop.

EDIT: Some code:

/* UART0 Receive Interrupt Vector */
ISR(USART0_RX_vect)
{
    static uint8_t tmp;

    // calc next insert index & store character
    tmp = (uart0_rx_insert_idx + 1) % UART0_RX_BUFFER_SIZE;
    uart0_rx_buffer[uart0_rx_insert_idx] = UDR0;

    // check for more room in queue
    if (tmp != uart0_rx_extract_idx)
        uart0_rx_insert_idx = tmp; // update insert index

}

enum fsm {
    IDLE,
    MAGIC,
};

int main(void) {

    // setup
    enum fsm state = IDLE;
    uint8_t wordbuf[10];
    uint8_t wordbuf_index = 0x00u;
    setLed(0);
    uartSetup();

    //activate interrupts
    sei();

    // mainloop
    while (1) {

        switch (state) {
        case IDLE:

            if (characterInBuffer()) {

                // if buffer is full; word still not found
                // shift all characters by one
                if (wordbuf_index == sizeof(wordbuf) - 1)
                    memcpy(&wordbuf[1], &wordbuf[0], sizeof(wordbuf));

                // save current character
                wordbuf[wordbuf_index] = readCharacterFromBuffer();

                // increment index if not at end of buffer
                if (wordbuf_index != sizeof(wordbuf) - 1)
                    wordbuf_index++;

                if (detectString(wordbuf))
                    state = MAGIC;
            }

            if (getCharacter())

            break;

        case MAGIC:

            setLed(1);

            if (buttonPressed()) {
                setLed(0);
                state = IDLE;
            }

            break;
        } /* switch */
    } /* while */

return 0;
} /* main */

This would be for an Atmel AVR ATmega microcontroller. Many microcontrollers have static vector tables that store the addresses of interrupt service routines (ISRs for short) that are present in your code.

When a peripheral interrupt is activatde, such as the UART Receive interrupt in the example above, and global interrupts are enabled (sei() call), the microcontroller will stop executing the main code and start executing the ISR code. When the ISR exits, execution will resume in the main code.

There are microcontrollers that have a programmable vectored interrupt controller (VIC). With these, you can freely assign an ISR at runtime instead of compile time (and even switch at runtime!). The idea remains the same though:

  • hardware notices interrupt condition,
  • main program flow stops
  • ISR is executed
  • main program flow resumes

You can have mutex locks and spin locks in your code. You just have to be careful about dead locks and use proper types, i.e. atomic types.

There are some flavors of microcontrollers that have a hiararchical interrupt system: one type of interrupt can interrupt another interrupt. With these, you have to be especially mindful of using mutual exclusion and proper typing.

In general, ISRs are supposed to be very slim and should not have long pieces of code or loops in them. The reason is simple: While your ISR is being executed, the main program is not. Since the main program usually does something very useful, you wouldn't want it to be interrupt for a prolonged amount of time.

Having an operating system (OS) on microcontrollers is usually optional. You can certainly write one-off C code and be done with it. If you care about re-usability of your code or being able to use pre-existing code, you might want to look into ChibiOS, FreeRTOS, etc.

OSes will typically encapsulate the idea of interrupts (or even vectored interrupts) and present a common interface for your higher-level program. The workings "under the hood" are the ones described above though.

If you're looking to do many things in parallel and use threaded applications, you should definitely look into popular code. They usually have mutexes, spin locks, etc. pre-made and ready-to-use.

Related Topic