bielu000
bielu000

Reputation: 2241

Isr is called all the time, even with no data sent to UART

I wanted to enable isr for IDLE detection on my USART1, but after I set appropriate flag in control register, I see in debugger that isr procedure is called all the time. I mean, I've set a breakpoint on that procedure, so I can see that it is called all the time, not just one, or even zero times. To be honest I expected that isr procedure won't be called until I sent some data to UART...

Below is my code. I use STM32F103 and libopencm3.

extern "C" {
  #include <libopencm3/stm32/rcc.h>
  #include <libopencm3/stm32/gpio.h>
  #include <libopencm3/cm3/systick.h>
  #include <libopencm3/stm32/timer.h>
  #include <libopencm3/cm3/nvic.h>
  #include <libopencm3/stm32/usart.h>
  #include <libopencm3/stm32/dma.h>
}

static void rcc_setup()
{
  //Peripherals clock
  rcc_periph_clock_enable(RCC_GPIOA);
  rcc_periph_clock_enable(RCC_GPIOB); 
}

int main()
{
  rcc_setup();
  
  rcc_clock_setup_in_hse_8mhz_out_72mhz();

  systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8);
  systick_set_reload(8999);

  systick_interrupt_enable();
  systick_counter_enable();

  rcc_periph_clock_enable(RCC_USART1);
  rcc_periph_clock_enable(RCC_DMA1);

  // -> GPIO
  gpio_set_mode(GPIO_BANK_USART1_TX, GPIO_MODE_OUTPUT_50_MHZ, 
    GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);

  gpio_set_mode(GPIO_BANK_USART1_RX, GPIO_MODE_INPUT, 
    GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_USART1_RX);

  // -> Configuration
  usart_set_mode(USART1, USART_MODE_TX_RX);
  usart_set_baudrate(USART1, 9600);
  usart_set_parity(USART1, USART_PARITY_NONE);
  usart_set_databits(USART1, 8);
  usart_set_stopbits(USART1, 1);
  usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);

  USART_CR1(USART1) |= USART_CR1_IDLEIE; //without this, the isr is not called

  nvic_enable_irq(NVIC_USART1_IRQ);

  // -> Enable 
  usart_enable(USART1);


  while(true)
  {
    [[maybe_unused]] int x = 10;
  }
}

void usart1_isr()
{
  [[maybe_unused]]auto x = (USART_CR1(USART1) & USART_CR1_IDLEIE);
  [[maybe_unused]]auto y = (USART_SR(USART1) & USART_FLAG_IDLE);

    if (((USART_CR1(USART1) & USART_CR1_IDLEIE) != 0) &&
        ((USART_SR(USART1) & USART_FLAG_IDLE) != 0))
  {
    [[maybe_unused]] auto x = USART1_SR;
    [[maybe_unused]] auto y = USART1_DR; 

    // process_rx();

  }
}

Upvotes: 0

Views: 1215

Answers (2)

Lundin
Lundin

Reputation: 213842

In case flags are to be cleared by reads, as is sometimes done in certain UART or SPI peripherals, you should do it like this:

void usart1_isr (void)
{
   // read registers in well-defined, clear order to ensure flags are cleared
   volatile uint32_t usart_sr = USART1_SR; // MUST be volatile 
   volatile uint32_t usart_dr = USART1_DR;

   ...
   if(usart_sr == USART_FLAG_IDLE) // check the local variable, not the register
   ....   
}

In particular, you should not use the brain-damaged C++11 auto keyword in embedded systems!. That one is discarding the volatile qualifier from the local variable, since (USART_CR1(USART1) & USART_CR1_IDLEIE) is a lvalue of type int. Which in turn can lead to the compiler failing to generate instructions corresponding to the actual register reads.

Simple example demonstrating one of the many dangers of C++11 auto:

volatile int a=1;
auto b = a & 1; // b is now int, not volatile int

int* pa = &a; // not OK since a is volatile
int* pb = &b; // OK since b is not volatile

I would strongly recommend to port this program to C as soon as possible. Or at least to C++03.


EDIT

Ok so now I RTFM for this part and apparently you can also clear the SR by writing 0 to it, in most cases. Which flags that causes interrupts depend on how you set up the CR register. You set USART_CR1(USART1) |= USART_CR1_IDLEIE; to generate interrupts when the line is idle and then you clear the idle bit by reading the SR from inside the ISR.

So you have told the program to generate endless interrupts when the line is idle and that's what it does. Ditch the IDLEIE bit, you should only get interrupts for TX or RX, and ideally also cover OR and FE.

Upvotes: 1

kkrambo
kkrambo

Reputation: 7057

The STM32 Reference Manual explains that the IDLE interrupt is generated whenever IDLE=1 in the USART_ISR register. The IDLE bit in USART_ISR is set by the hardware when an Idle Line is detected. It is cleared by software, writing 1 to the IDLECF in the USART_ICR register. I'm guessing that you need to clear this within the ISR to prevent the interrupt from being immediately reasserted. Try adding something like this line to usart1_isr().

USART1_ICR = USART_ICR_IDLECF;  // Clear Idle flag in the USART_ISR register.

Upvotes: 0

Related Questions