1
votes

Trying to get an ATmega162 USART up and running. This code does exactly what I expect it to:

#define F_CPU 14745600UL
#define UBRR_1 F_CPU / 16 / 9600 - 1
#define UBRR_2 F_CPU / 16 / 31250 - 1

#include <inttypes.h>

#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>

int main(){
  uint16_t ubrr1 = UBRR_1;

  UBRR0H = (uint8_t)(ubrr1 >> 8);
  UBRR0L = (uint8_t)ubrr1;
  UCSR0B = _BV(TXEN0);
  UCSR0C = _BV(URSEL0) | _BV(UCSZ00) | _BV(UCSZ01);

  uint16_t ubrr2 = UBRR_2;

  UBRR1H = (uint8_t)(ubrr2 >> 8);
  UBRR1L = (uint8_t)ubrr2;
  UCSR1B = _BV(RXEN1);
  UCSR1C = _BV(URSEL1) | _BV(UCSZ10) | _BV(UCSZ11);

  DDRB = _BV(PB0) | _BV(PB1);

  PORTB |= _BV(PB0);

  while (1){
    PORTB ^= _BV(PB0);
    _delay_ms(50);

    // byte received on usart 1
    if ((UCSR1A & _BV(RXC1)) != 0){

      // usart 0 ready to write
      if ((UCSR0A & _BV(UDRE0)) != 0){
        uint8_t b = UDR1;
        UDR0 = b;
      }
    }
  }

  return 0;
}

That is, initializes the two USARTs at different baud rates, reads from USART1 and writes to USART0. Works great. Yes, I know that _delay_ms() is messing with the timing, but it works fine for this example. Now, as soon as I enable the RX interrupt on USART1 and add the appropriate vector, the main loop stops running (the LED isn't blinking, at least):

#define F_CPU 14745600UL
#define UBRR_1 F_CPU / 16 / 9600 - 1
#define UBRR_2 F_CPU / 16 / 31250 - 1

#include <inttypes.h>

#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>

int main(){
  uint16_t ubrr1 = UBRR_1;

  UBRR0H = (uint8_t)(ubrr1 >> 8);
  UBRR0L = (uint8_t)ubrr1;
  UCSR0B = _BV(TXEN0);
  UCSR0C = _BV(URSEL0) | _BV(UCSZ00) | _BV(UCSZ01);

  uint16_t ubrr2 = UBRR_2;

  UBRR1H = (uint8_t)(ubrr2 >> 8);
  UBRR1L = (uint8_t)ubrr2;
  UCSR1B = _BV(RXEN1);
  UCSR1C = _BV(URSEL1) | _BV(UCSZ10) | _BV(UCSZ11);

  DDRB = _BV(PB0) | _BV(PB1);

  // enable usart1 rx interrupt
  UCSR1B |= _BV(RXCIE1);

  PORTB |= _BV(PB0);

  // enable interrupts
  sei();

  while (1){
    PORTB ^= _BV(PB0);
    _delay_ms(50);
  }

  return 0;
}

ISR(USART1_RXC_vect){
  uint8_t byte = UDR1;

  if ((UCSR0A & _BV(UDRE0)) != 0){
    UDR0 = byte;
  }
}

The weirdest part is that it's not the sei(); and UCSR1B |= _BV(RXCIE1); lines that make the program stop working -- it's the existence of the ISR. As soon as I comment out that function, the main loop executes normally. Did I miss a flag somewhere?

1
try ISR(USART1_RX_vect) instead of RXC - Gossamer
Thanks, but I'm quite sure that USART1_RXC_vect is the proper vector definition as USART1_RX_vect isn't defined at all in avr/iom162.h. - Andrew
Well, im using UCSR1B = (1<<RXEN1) with ISR(USART1_RX_vect) and that works like a charm. - Gossamer
For an ATmega162? What version of avr-libc are you using? I'm on 1.8.0 (on Arch Linux). - Andrew
Ok, that could explain it. Im using WinAVR with libc v1.6.7 - Gossamer

1 Answers

0
votes

It's possible this has been caused by the M161C fuse bit (in the extended fuse byte) becoming programmed. This puts the ATmega162 into ATmega161 compatibility mode which causes the device to have a different layout of the interrupt vector table (which the compiler won't know about.) See enter link description here Page 57 for the details. You could test this by compiling with -mmcu=atmega161 and seeing if it fixes the problem.

The other thing which would cause similar behaviour is if this code is run on an (almost identical-looking) ATmega16 instead of an ATmega162 as the UDR1 register is in a different place in the IO register map, meaning that the RXC interrupt flag would never get cleared and the handler would re-enter forever. You can check the register values the compiler is using by disassembling with avr-objdump.