6
\$\begingroup\$

I am currently able to read byte by byte from USART with this code

ISR(USART_RX_vect)
{
    cli();
    while(!(UCSR0A&(1<<RXC0))){};
    // clear the USART interrupt  
    received = UDR0;

    if(pinState == 0)
    {
        OCR2A = received;
        pinState = 1;
    }
    else if(pinState == 1)
    {
        OCR2B = received;
        pinState = 2;
    }
    else
    {
        OCR0A = received;
        pinState = 0;
    } 
    sei();
}

But now I will send 4 bytes of data which is solely necessary for my application. I could not figure out how to read 4 bytes at once since the interrupt is triggered for every byte. Thank you for any effort in advance.

To make it clearer I will briefly explain what that pinState is. I am sending 3 bytes of data and I want the 1. byte to go pin 3 pwm 2. byte pin 11 pwm and 3. byte to pin6 pwm. As you see in every interrupt I am not gettin 3 bytes, but 1 byte instead. So that pinState thing is only standing for that purpose.

\$\endgroup\$

3 Answers 3

10
\$\begingroup\$

You did not mention which microcontroller you are using, but it probably doesn't matter. USART peripherals generally operate exactly as you have discovered: one byte at a time. This, however, is not a limitation.

Based on the code snippet you posted in your question, you're trying to execute some functionality with every received byte. That limits you to one-byte operations. What if, instead, you used the ISR just to populate an array of bytes? Then, after a certain number of bytes had arrived, only then do you read the bytes and interpret their meaning - preferably in the main loop of your code, not the ISR.

Be careful of doing too much work in an ISR. Especially calling functions from within the ISR function. Every silicon manufacturer and compiler is different, of course, but calling functions within ISRs can often lead to extremely slow, inefficient code. The majority of your computations and data handling should happen in your main loop. ISRs should be used for setting flags and quick operations. You generally want to exit the ISR as quickly as you can.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Also, I dont think you need cli(); in your ISR, since it is automaticly done by the MCU. \$\endgroup\$
    – Golaž
    Feb 27, 2015 at 11:39
  • \$\begingroup\$ I am not writing the code for a specific processor. Thus I wanted to be cautious about differences. It is like considering if int is 32bits or 16bits in any processor, so to speak. \$\endgroup\$
    – Zgrkpnr__
    Feb 27, 2015 at 14:13
  • 3
    \$\begingroup\$ @Zgrkpnr__ Interrupt handlers are an area where you cannot write portable code. The details matter, and explicitly clearing a flag that you should allow the hardware to clear for you can have wild unexpected side effects. Similarly, not clearing a flag that hardware expects you to handle also will cause problems. \$\endgroup\$
    – RBerteig
    Feb 27, 2015 at 19:28
  • \$\begingroup\$ It's clearly AVR anyway. This code is already far from portable :) \$\endgroup\$
    – hobbs
    Feb 28, 2015 at 1:37
12
\$\begingroup\$

The conventional method for dealing with multi-byte receives in an interrupt routine is to setup a circular queue. As each byte is received it is placed into the next available slot in the queue. This would replace your single byte save code that you have now.

The output side of the circular queue is polled for available content by your mainline code that wants to use the received data. When you start dealing with multi byte streams on a serial interface it is often desirable to make a "protocol" that allows the receiver end to be able to track where the starting of each batch of data starts. There are many ways to do this. One way it to select a unique byte value for the start of the batch. Another way is to set the MSB of the first byte of the batch and then follow with all of the rest of the bytes in the batch with the MSB cleared.

** Some comments about your ISR **

  1. It is generally a bad practice to place polling loops inside an interrupt service routine. You want to keep the ISR execution time as short as possible. I do not know what the intention of the polling loop is but you should study about removing that.

  2. It is usually not necessary to be putting interrupt disable and re-enable inside an ISR. Most MCUs will automatically disable the interrupts when the ISR is entered and then restore the previous state when the return from interrupt is executed.

  3. It is not clear what the pin state logic is doing inside the ISR. That does not look at all applicable to servicing a USART interrupt.

\$\endgroup\$
2
  • \$\begingroup\$ Updated my question with a little more detail. \$\endgroup\$
    – Zgrkpnr__
    Feb 27, 2015 at 14:10
  • \$\begingroup\$ Both answers led me to the same solution which was helpful and solved my problem. Thank you. I up voted you answer, as well \$\endgroup\$
    – Zgrkpnr__
    Feb 27, 2015 at 14:20
6
\$\begingroup\$

Just today I happened to be in the exact same position, and I wrote a program along the lines of what Michael Karas suggests in his answer, using a circular buffer. I used a PIC18, so some code may not compile, but it shows the idea clearly and it should be easy to port this code to AVR, ...

I declared some global variables:

#define EUSART_BUFFER_SIZE 2048
char eusart_rx_buffer[EUSART_BUFFER_SIZE];   // the actual buffer, now 2048 bytes long
uint16_t eusart_rx_buffer_rd = 0;            // the current read position
uint16_t eusart_rx_buffer_wr = 0;            // the current write position

This assumes stdint.h is included for the uint16_t type.

The idea is:

  • In the ISR, when we receive a byte, store it at eusart_rx_buffer[eusart_rx_buffer_wr] and increment the write position.
  • When we want to read the data, you can read from eusart_rx_buffer_rd until eusart_rx_buffer_wr.

Of course, when more than 2048 bytes are stored at the same time, the buffer will be overwritten and you'll lose data. There are some tricks you can use to work around that though. You can change EUSART_BUFFER_SIZE to suit your needs. A lower value, of course, takes less data memory.

Now, in my ISR, I have:

if (PIR1bits.RCIF) {                                  // EUSART data received
    eusart_rx_buffer[eusart_rx_buffer_wr++] = RCREG;  // Store the received data
    if (eusart_rx_buffer_wr >= EUSART_BUFFER_SIZE)    // Increment write pointer
        eusart_rx_buffer_wr = 0;
    PIR1bits.RCIF = 0;                                // Clear interrupt flag
}

Of course, on an AVR this code will look slightly different, but the idea is the same.

Then, where you want to read the data, you can do something like:

while (eusart_rx_buffer_rd != eusart_rx_buffer_wr) {   // While there's data in the buffer
    do_sth(eusart_rx_buffer[eusart_rx_buffer_rd++]);   // Do something with it
    if (eusart_rx_buffer_rd >= EUSART_BUFFER_SIZE)     // Increase read pointer
        eusart_rx_buffer_rd = 0;
}

This code was written for PIC18 using the XC8 compiler, but most of it is standard C and can be copied directly or ported easily.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.