avr128db48 USART_RXC_vect interrupt not working

397 Views Asked by At
#define F_CPU 4000000
#define BAUD_RATE(BAUD) ((64.0*F_CPU)/(BAUD*16.0))
#include <avr/io.h>
#include <avr/interrupt.h>


volatile uint8_t a;
volatile uint8_t b;

void UART_sw_write(char c)
{
    while(!(USART3.STATUS & USART_DREIF_bm))
    {
        ;
    }
    
    USART3.TXDATAL = c;
}

uint8_t UART_sw_read()
{
    while(!(USART3.STATUS & USART_RXCIF_bm)) //WAIT UNtil done reading
    {
        ;
    }
    a = USART3.RXDATAL;
    return a;
}

ISR(USART3_RXC_vect){
    cli();
    b = UART_sw_read();
    UART_sw_write(b);
    sei();
}

 
int main(void)
{
    USART3.BAUD = BAUD_RATE(9600);
    VPORTB_DIR |= PIN0_bm;  //TX as output RX as input
    USART3.CTRLA |= USART_RXCIE_bm | USART_TXCIE_bm | USART_RXSIE_bm | USART_DREIE_bm;
    USART3.CTRLB |= USART_RXEN_bm | USART_TXEN_bm;
    USART3.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_PMODE_DISABLED_gc | USART_SBMODE_1BIT_gc | USART_CHSIZE_8BIT_gc;
    sei();
    while (1) 
    {
        //b = UART_sw_read();
        //UART_sw_write(b);
        asm volatile("nop");
    }
}

In the USART3.STATUS RXCIF is set but this ISR(USART3_RXC_vect) interrupt never been called.

I tryed different set up for USART3 controls but none of it work. The code shoud sent a character back everytime it receive it by using interrupt.

3

There are 3 best solutions below

3
Grimmace_23 On

I think it's the sei() and cli() instructions within your handler. cli() clears interrupts, so I would try changing that first.

If anything, they should go where you're configuring the exception in main():

cli();
USART3.BAUD = BAUD_RATE(9600);
VPORTB_DIR |= PIN0_bm;  //TX as output RX as input
USART3.CTRLA |= USART_RXCIE_bm | USART_TXCIE_bm | USART_RXSIE_bm | USART_DREIE_bm;
USART3.CTRLB |= USART_RXEN_bm | USART_TXEN_bm;
USART3.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_PMODE_DISABLED_gc | USART_SBMODE_1BIT_gc | USART_CHSIZE_8BIT_gc;
sei();
1
sunriax On

You never should wait in an interrupt. An interrupt needs to be as short as possible. So this is weird:

void UART_sw_write(char c)
{
    while(!(USART3.STATUS & USART_DREIF_bm))
    {
        ;
    }
    
    USART3.TXDATAL = c;
}

uint8_t UART_sw_read()
{
    while(!(USART3.STATUS & USART_RXCIF_bm)) //WAIT UNtil done reading
    {
        ;
    }
    a = USART3.RXDATAL;
    return a;
}

ISR(USART3_RXC_vect){
    cli();
    b = UART_sw_read(); // Breaks interrupt concept
    UART_sw_write(b);   // Breaks interrupt concept
    sei();
}

This will break the interrupt concept. In case of that a better way to solve this would be e.g.:

ISR(USART3_RXC_vect)
{
    sei();  // Allow nested interrupts
    rx = USART0.RXDATAL;
}

ISR(USART3_TXC_vect)
{
    sei();  // Allow nested interrupts
    USART0.TXDATAL = tx;
}

AVR Controllers normally disable interrupts after entering into an ISR and enables them by jumping out of the ISR. If you want nested interrupts you only have to enable them with sei() (See -> this thread)

Receiving data is decoupled from transmitting data.

Important the first transmission has to be initiated manually by writing a dummy byte to USART3.TXDATAL e.g. USART3.TXDATAL = 0x00

0
Lundin On

You have several conceptually wrong things going on here. Basically the AVR is not a PC.

  • #define BAUD_RATE(BAUD) ((64.0*F_CPU)/(BAUD*16.0)). Using floating point on a lousy 8 bitter without FPU is a cardinal sin. It means that you force the compiler to load a whole software floating point library and produce extremely inefficient code.

    Worse yet, you should not calculate baudrate while allowing floating point inaccuracy. The register values are integers. There will be a baudrate formula in the UART part of the manual telling you how to calculate baudrate based on integer fixed point arithmetic. This means that you may not always get exact divisors given a certain system clock and a certain baudrate.

    Which in turn is a well-known problem since forever - microcontroller design always have to take serial buses in account when picking a clock, since these serial buses are usually the most picky hardware peripherals in terms of clocks. Some bare minimum allowed division error for UART is that the baudrate inaccuracy shouldn't differ more than 3%. I'd probably recommend to keep it below 1% however. This means:

    • You have to pick a suitable crystal and system clock for the standard UART baudrates, most commonly something divisible by 8 such as 8MHz.
    • The clock inaccuracy in itself has to be taken in account, which likely means forget about using internal RC oscillators and use external quartz.
  • You are mixing up the different concepts of polling vs interrupts. When setting up an interrupt, the interrupt will trigger on a certain flag or a group of flags. So there is no need to wait for that flag once inside the interrupt, those while loops inside the ISR doesn't make any sense.

    You may however have to check several flags to find out what was the interrupt source and you may have to clear the flag manually.

    If you wish to receive one character then send another, you should set one interrupt for the rx flag. It is quite unlikely that you have a pending TX transmission at that point since the normal setup for UART is semi duplex (RS232/RS485), so you can probably just send straight away without waiting for a flag. Optionally for a full duplex system (RS422) you could store the saved character and then upon next TX interrupt send it.

    For MCUs this old with limited RX buffers, you probably want to implement a software ring buffer where you store incoming characters to be processed later. It will have to be implemented in a "reentant" manner so that new RX interrupts don't cause race condition bugs.

  • The CLI and SEI instructions inside the ISR doesn't make any sense. AVR is a traditional MCU which doesn't allow nested interrupts by default. Upon interrupt, the condition code register is stacked by hardware and then the i bit is cleared, so that when you enter an ISR, interrupts are already automatically disabled by the MCU. And similarly when you leave, the i bit is restored to the previous value.

    This means that your cli() and sei() calls are pointless. I'd recommend to read up on how interrupts work in the CPU manual. For MCUs this old, the CPU manual is a relatively easy read.

  • int main(void) is senseless in "bare metal" embedded systems, so called freestanding systems. Various confused PC programmer nonsense about "main must always return int" does not apply. There is none to return to, so main() should be using the implementation-defined form void main (void) which is fine and explicitly allowed by the C standard. For the gcc compiler it means that you have to tell it to compile for an embedded system by passing the -ffreestanding compiler option.

  • Regarding VPORTB_DIR |= PIN0_bm; //TX as output RX as input, the RX line will need a pull-up resistor. If the MCU supports this internally, then you can use that. Otherwise, make sure there's an external one.