Search code examples
microcontrollerinterrupt8051uart

UART transmission via interrupt on a 8051 microcontroller


My platform is a c8051F120 microcontroller. I would like to send (=tx) bytes via UART0 using interrupts. My design so far is the following:

#define UART0_TX_SIZE 16    
char UART0_tx[UART0_TX_SIZE];
short UART0_tx_uart = 0;
short UART0_tx_main = 0;
short UART0_tx_available = 0;

void UART0_putChar(char value) {
  char SAVE_SFRPAGE;
  bit EA_SAVE = EA;
  // potentially blocking code
  while (UART0_tx_available == UART0_TX_SIZE)
    ;
  // disable interrupts
  EA = 0;
  EA = 0;
  if (UART0_tx_available) {
    UART0_tx[UART0_tx_main] = value;
    ++UART0_tx_main;
    if (UART0_tx_main == UART0_TX_SIZE)
      UART0_tx_main = 0;
    ++UART0_tx_available;
  } else {
    SAVE_SFRPAGE = SFRPAGE;
    SFRPAGE = UART0_PAGE;
    SBUF0 = value;
    SFRPAGE = SAVE_SFRPAGE;
  }
  // reenable if necessary
  EA = EA_SAVE;
}

// (return void works for other interrupts)
void UART0_Interrupt() interrupt (4) {
  if (RI0 == 1) {
    RI0 = 0;
  }
  if (TI0 == 1) { // cause of interrupt: previous tx is finished
    TI0 = 0; // Q: Should this clear tx interrupt flag be further down?
    if (SSTA0 & 0x20) { // Errors tx collision
      SSTA0 &= 0xDF;
    }
    if (UART0_tx_available) { // If buffer not empty
      --UART0_tx_available; // Decrease array size
      SBUF0 = UART0_tx[UART0_tx_uart]; //Transmit
      ++UART0_tx_uart; //Update counter
      if (UART0_tx_uart == UART0_TX_SIZE)
        UART0_tx_uart = 0;
    }    
  }
}

I am pretty sure that the initialization regarding UART0 registers and timing via Timer2 (not part of the above code) is correct, because I am able to use the blocking function:

char putchar_Blocking(char value) {
  char SFRPAGE_SAVE = SFRPAGE;
  SFRPAGE = UART0_PAGE;
  while (!TI0) // while TI0 == 1 wait for transmit complete
    ;
  TI0 = 0;
  SBUF0 = value;
  SFRPAGE = SFRPAGE_SAVE;
  return value;
}

When I want to switch to the interrupt design, of course, I also set

ES0 = 1;

Does anybody find a flaw in my design that attempts to use the interupt? Or, does anybody have sample code for this? Thank you! And a big shout-out to jszakmeister, who answered my question regarding reading the TCNT register.


Solution

  • My colleague Guo Xiong found the mistake: The variable UART0_tx_available was not incremented and decremented at the right place. Below is the corrected and tested version:

    #define UART0_TX_SIZE 16    
    char UART0_tx[UART0_TX_SIZE];
    short UART0_tx_uart = 0;
    short UART0_tx_main = 0;
    short UART0_tx_available = 0;
    
    void UART0_putChar(char value) {
      char SAVE_SFRPAGE;
      bit EA_SAVE = EA;
      // potentially blocking code
      while (UART0_tx_available == UART0_TX_SIZE)
        ;
      // disable interrupts
      EA = 0;
      EA = 0;
      if (UART0_tx_available) {
        UART0_tx[UART0_tx_main] = value;
        ++UART0_tx_main;
        if (UART0_tx_main == UART0_TX_SIZE)
          UART0_tx_main = 0;
      } else {
        SAVE_SFRPAGE = SFRPAGE;
        SFRPAGE = UART0_PAGE;
        SBUF0 = value;
        SFRPAGE = SAVE_SFRPAGE;
      }
      ++UART0_tx_available;
      // reenable if necessary
      EA = EA_SAVE;
    }
    
    // (return void works for other interrupts)
    void UART0_Interrupt() interrupt (4) {
      if (RI0 == 1) {
        RI0 = 0;
      }
      if (TI0 == 1) { // cause of interrupt: previous tx is finished
        TI0 = 0; // Q: Should this clear tx interrupt flag be further down?
        if (SSTA0 & 0x20) { // Errors tx collision
          SSTA0 &= 0xDF;
        }
        --UART0_tx_available; // Decrease array size
        if (UART0_tx_available) { // If buffer not empty
          SBUF0 = UART0_tx[UART0_tx_uart]; //Transmit
          ++UART0_tx_uart; //Update counter
          if (UART0_tx_uart == UART0_TX_SIZE)
            UART0_tx_uart = 0;
        }    
      }
    }