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
- 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_LENGTHmacro 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
- 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
MR1register is loaded with the timer value for when the stop bit is expected (nine bit lengths from receiving the start bit).
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
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
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 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).