4

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.

datahaki
  • 600
  • 7
  • 23
  • If my answer helps, I would appreciate if you accept it. – Jeff Sep 06 '12 at 03:22
  • Dear Jeff, I have code for ATmega128CAN and other ATMEL chips that solve this problem (you can find the code on my website unter Satellites/Cansat). Unfortunately the "translation" of the code in your answer to my situation leads to no other behaviour that I get from my code: Characters are sent out too quickly. Do you know how to fix my sample code? You just say "biggest flaw", but as long as I disable interrupts, it's perfectly safe to share variables... I will spend another weekend trying to figure out what's wrong with my code: count interrupts calls etc... – datahaki Sep 06 '12 at 05:30
  • Tell me more about your problem....the characters can only go as fast as the baud rate or do you need to add an intercharacter delay? – Jeff Sep 10 '12 at 20:42
  • the problem was the locations of the updates of UART0_tx_available – datahaki Sep 21 '12 at 03:52
  • In my world, disabling interrupts is a no no. – Jeff Feb 19 '14 at 17:02
  • btw, later we replaced the EA (=global interrupt) flag with the ES0 (=UART0 interrupt flag) to minimize the effect on other peripherals. At the time the above code was posted, we were just desperate and took drastic measures. – datahaki Feb 20 '14 at 16:53

2 Answers2

3

The biggest flaw I see is that you should not have any variable (for example: UART0_tx_available) being modified by the main code and the interrupt code.

Usually I implement an interrupt driven UART using a circular buffer and two pointers.

Here is a simple C example for the AVR micro. My 8051 code is all assembly.

/* size of RX/TX buffers */
#define UART_RX_BUFFER_SIZE  16
#define UART_TX_BUFFER_SIZE  16
#define UART_RX_BUFFER_MASK ( UART_RX_BUFFER_SIZE - 1)
#define UART_TX_BUFFER_MASK ( UART_TX_BUFFER_SIZE - 1)

#if ( UART_RX_BUFFER_SIZE & UART_RX_BUFFER_MASK )
#error RX buffer size is not a power of 2   
#endif  
#if ( UART_TX_BUFFER_SIZE & UART_TX_BUFFER_MASK )
#error TX buffer size is not a power of 2
#endif

/* 
 *  module global variables
 */
static volatile unsigned char UART_TxBuf[UART_TX_BUFFER_SIZE];
static volatile unsigned char UART_RxBuf[UART_RX_BUFFER_SIZE];
static volatile unsigned char UART_TxHead;
static volatile unsigned char UART_TxTail;
static volatile unsigned char UART_RxHead;
static volatile unsigned char UART_RxTail;
static volatile unsigned char UART_LastRxError;

SIGNAL(UART0_TRANSMIT_INTERRUPT)
/*************************************************************************
Function: UART Data Register Empty interrupt
Purpose:  called when the UART is ready to transmit the next byte
**************************************************************************/
{
    unsigned char tmptail;

    if ( UART_TxHead != UART_TxTail) {
        /* calculate and store new buffer index */
        tmptail = (UART_TxTail + 1) & UART_TX_BUFFER_MASK;

        /* get one byte from buffer and write it to UART */
        UART0_DATA = UART_TxBuf[tmptail];  /* start transmission */
        UART_TxTail = tmptail;
    }else{
        /* tx buffer empty, disable UDRE interrupt */
        UART0_CONTROL &= ~_BV(UART0_UDRIE);
    }
}

/*************************************************************************
Function: uart_putc()
Purpose:  write byte to ringbuffer for transmitting via UART
Input:    byte to be transmitted
Returns:  none
**************************************************************************/
void uart_putc(unsigned char data)
{
    unsigned char tmphead;

    tmphead  = (UART_TxHead + 1) & UART_TX_BUFFER_MASK;

    while ( tmphead == UART_TxTail ){
        ;/* wait for free space in buffer */
    }

    UART_TxBuf[tmphead] = data;
    UART_TxHead = tmphead;

    /* enable UDRE interrupt */
    UART0_CONTROL |= _BV(UART0_UDRIE);

}/* uart_putc */

A special thanks to Peter Fleury http://jump.to/fleury for the library these routines came from.

Jeff
  • 1,364
  • 1
  • 8
  • 17
  • thank you very much! Looks very promising. I will lookup the meaning behind some of the lines such as UART0_CONTROL |= _BV(UART0_UDRIE); Previously, I had thought that enabling the UartTx interrupt bit, will also cause entering the interrupt function/vector... that's why I still don't understand your solution... – datahaki Sep 03 '12 at 09:52
  • @datahaki I figured it would be easier to convert AVR C to 8051 C then 8051 assembly to 8051 C. UART0_CONTROL |= _BV(UART0_UDRIE); is the portable method used to set the UART Receive Enable Bit. I believe the corresponding 8051 code would be EA=1; or maybe ES0=1;? – Jeff Sep 03 '12 at 19:55
  • @datahaki oops, UART0_CONTROL |= _BV(UART0_UDRIE); is the portable technique used to set the UART Interrupt Enable Bit – Jeff Sep 03 '12 at 20:12
  • @datahaki Concerning enabling the UartTX and entering the interrupt, that is the designed operation so it will start transmitting the characters. You need to verify how the bits will work in your variety of 8051. – Jeff Sep 04 '12 at 02:30
  • if UART0_CONTROL |= _BV(UART0_UDRIE); only enables the uart interrupt, i.e. similar to ES0=1; then how does the first byte get sent at all. After all, the interrupt is only triggered after a byte was sent, or by setting TI0=1; – datahaki Sep 04 '12 at 02:41
  • This example was created for the AVR processor. You need to review the datasheet for your specific device. On the 8051 I believe you will need to enable the interrupt and trigger it as you stated by setting TI0=1; – Jeff Sep 04 '12 at 03:19
0

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;
    }    
  }
}
datahaki
  • 600
  • 7
  • 23
  • If a tx interrupt occurs anytime inside your if (UART0_tx_available) block and the value of UART0_tx_available == 1, the interrupt routine will dec the value to zero and turn off the interrupt, causing the transmissions to stop. This will be the type of random failure that will take days(weeks) to resolve. – Jeff Feb 19 '14 at 16:29
  • Just realized you are disabling interrupts, so your while will block indefinitely if that condition ever occurs, since your interrupts are turned off. – Jeff Feb 19 '14 at 17:07
  • "If a tx interrupt occurs anytime inside your if (UART0_tx_available) block (of the UART0_putChar() function(?))..." that cannot happen because interrupts are disabled. The code has been working perfectly fine. – datahaki Feb 19 '14 at 17:24
  • "you are disabling interrupts, your while will block indefinitely...". the interrupts are enabled until the while loop finishes. – datahaki Feb 19 '14 at 17:29
  • Excellent! I over analyzed it. – Jeff Feb 19 '14 at 17:46
  • I have used the same methodoloy on an AT90CAN128 and other ATmegas. It's always a joy to have the serial communication non-blocking, "running in the background". – datahaki Feb 20 '14 at 02:50