jenswilly.dk


Soft UART on LPC13xx

For the ROV project I need two UART connections (one for RS485 to the onboard controller and one for receiving input from the VNC2 USB host controller).

And since the LPC1347 only has one UART I needed a soft UART for one of the connections. Fortunately for me, NXP provides an implementation in AN10955. There was only one problem with it: it doesn't work. Or rather, it works for a while and then stops receiving anything (scoping the UART line confirmed that the VNC2 chip continued to send data so the problem had to be in the soft UART code).

Yea, intermittent errors – what's not to like… (Read on for the problem and the solution.)

How the soft UART works

In order to find out the problem I first had to figure out how the soft UART actually works. Here's a brief recap – of the RX part only:

  • One of the 32 bit timers is used to keep track of everything. I use CT32B0.
  • The timer is configured to count from 0 to 0x3FFFFFFF after which it resets to 0 and continues. No interrupt is generated when reset occurs and no prescaler is used.
  • Two values are pre-calculated: a "bit length" which is the number of cycles between each bit (in my case: 72.000.000 clock cycles/sec and 9600 bits/secs yields 7500 timer ticks per bit which is defined in the BIT_LENGTH macro in the header file – and the length for all nine bits (8 data bits and one stop bit) which is simply 9 * bit length – defined in the STOP_BIT_SAMPLE macro.
  • The timer uses a capture pin to capture the value of the timer counter when the RX signal switches from high to low or vice versa and generate an interrupt when a transition occurs. I use CT32B0_CAP0 on pin PIO1.27.
  • Whenever a timer interrupt occurs the method swu_isr_rx() (and the TX counterpart swu_isr_tx() but we'll ignore that for now) is called. This is where all the processing occurs.
  • Basically, the method measure PCLKs between signal transitions and use that to construct the received byte. Also, when the first high-to-low event occurs, the timer's MR1 register is loaded with the timer value for when the stop bit is expected (nine bit lengths from receiving the start bit).

The problem

The problem is in the code that calculates when the stop bit is expected. This is what it looks like:

edge_last = (signed long int) RX_ISR_TIMER->CR0; //initialize the last edge
  edge_sample = edge_last + (BIT_LENGTH >> 1); //initialize the sample edge
  if (edge_sample < edge_last) //adjust the sample edge...
    edge_sample |= ADJUST; //... if needed
  swu_bit = 0; //rx bit is 0 (a start bit)
  RX_ISR_TIMER->IR = 0x02; //clear MAT1 int flag
  edge_stop = edge_sample + STOP_BIT_SAMPLE; //estimate the end of the byte
  if (edge_stop < edge_last) //adjust the end of byte...
    edge_stop |= ADJUST; //... if needed

edge_last is the timer value when the start bit is received. edge_sample is half a bit length later. That is, in the middle of the bit instead of right at the start where the transition happens.
edge_stop is calculated as STOP_BIT_SAMPLE PCLKs later (which is (9*BIT_LENGTH)).
Then it is checked if edge_stop has gotten so high that it has overflowed and is now less then the time of the start bit. If that happens, ADJUST is added. Which is 0x40000000. Remember, the timer only counts up to 0x3FFFFFFF.

However! That last if-statement will never be true. And that is the bug!
If the timer counted the entire 32 bits it would work, but it doesn't – it only counts up to 0x3FFFFFFF. And therefore the edge_stop will never wrap around and be less than edge_last.
But it can, however, become greater than 0x3FFFFFFF (if, for example, the start bit is received when the timer is at 0x3FFFFDFF, in which case edge_stop would be set to 0x400105AB). And the timer will never reach that value because it resets to zero when it reaches 0x3FFFFFFF. So the "stop bit time reached – grab the byte and put it into the buffer" code will never run.

The solution

The solution is simple. What we really wanted to do in the last two lines of the code above was, "If the calculated stop bit time is greater than the timer's maximum, wrap it around". And that looks like this:

if( edge_stop >= ADJUST )   // Adjust the stop bit time if necessary
    edge_stop -= ADJUST;

And hey presto – everything works!
I also removed a couple of other if statements that would never come true (same reason as above).


2 comments on "Soft UART on LPC13xx"
Boris says:

Show the full code without bugs, please.

Bob Belov says:

I also removed the bug statement:
swu_rbr = swu_rbr << cnt; //... make space for the rest...

Leave a Reply