Tuesday 11 December 2012

NXP WTF?

Well in another WTF moment I've been tackling the problem of detecting when a UART has transmitted ALL of the bytes you sent.

Trivial right? Well not as trivial as you may think.

Why do you care? Well maybe you have to turn around an RS-485 transceiver and you do that after the last byte, not the second-last. Or maybe you are sending a command to another processor and timing the response, it's a bit unfair to start timing before the last byte has gone.

The LPC UART (or at least the one on the 1227) has no explicit flag to read to tell you that the last byte has left the TSR (Transmit Shift Register). Actually that's not true, there is the TEMT flag.
Transmitter Empty. TEMT is set when both THR and TSR are empty;
Yep, that's clear enough. But there are two issues here, one is that you don't get an interrupt so you have to poll the TEMT flag. Usable but not good. The second issue is worse though, IT DOESN'T WORK.

You can poll the TEMT bit until the cows come home but it gets set when the FIFO is empty, not when the TSR is. (YMMV but that's what I'm seeing)

So just use a timer. Well that was the non-answer provided by an NXP support person on the forum. Use an entire hardware timer for this simple function? I think not, heck you only have 4 and he wants me to tie up two of them to fix their crap design.

Back to square one. So what do you get.

You get a THRE flag and interrupt, but this only tells you that the FIFO is empty, at this point however there is still a single byte in the TSR and that may not be gone for quite some time as is shown in this trace



Here we see two bytes being sent from the UART, 'A' and 'B'. The small pulse is the time at which the THRE interrupt is fired. Note that at this point 'B' has still not been transmitted.

Fortunately there is a mechanism that is clearly and succinctly described in the data sheet.
The UARTn THRE interrupt (UnIIR[3:1] = 001) is a third level interrupt and is activated when the UARTn THR FIFO is empty provided certain initialization conditions have been met. These initialization conditions are intended to give the UARTn THR FIFO a chance to fill up with data to eliminate many THRE interrupts from occurring at system start-up. The initialization conditions implement a one character delay minus the stop bit whenever THRE = 1 and there have not been at least two characters in the UnTHR at one time since the last THRE = 1 event. This delay is provided to give the CPU time to write data to UnTHR without a THRE interrupt to decode and service. A THRE interrupt is set immediately if the UARTn THR FIFO has held two or more characters at one time and currently, the UnTHR is empty. The THRE interrupt is reset when a UnTHR write occurs or a read of the UnIIR occurs and the THRE is the highest interrupt (UnIIR[3:1] = 001). 

Got that? No, I didn't either despite reading it maybe 10 times.

Luckily one of the guys on the LPC forum is smarter than me and he explained it,
So you have only write one byte in the fifo and the THRE interrupt will occur after this byte was sent.  
Still a bit unclear so let me slightly reword it.
If you only place a single byte in the FIFO the THRE interrupt will occur after this byte was sent. 
That's right and worth repeating and rephrasing again in the hope that one of the explainations will make sense, if you only place one byte in the FIFO you get the THRE interrupt after that byte has gone. Yay, that's exactly what we need, and here it is in action


Note that I have only sent a single byte and that the interrupt pulse now occurs after that byte has completely left the TSR (not counting the stop bit).

We're getting there, trouble is you normally send more than one byte. What happens if we send 10? Well in that case unless you take extra steps you are back where you started. If you just blat 10 bytes into the FIFO the interrupt will fire after the 9th byte has been transmitted, not after the 10th.

You have to get clever and hold off with the last byte. You send 9 bytes straight away and when the last of those is in the TSR you write the 10th byte into the FIFO.


Here we have written 9 bytes ('A' thru 'I') into the FIFO, when 'I' goes into the TSR the interrupt is fired. At this point the FIFO is empty and we write the 10th byte ('J') into it, thus satisfying the "only one byte in the FIFO" criteria.

The next time we see the THRE interrupt is after the 'J' has gone. We can now set a global flag somewhere to tell the rest of the program that the data has been completely transmitted.

And here is the pseudo code for the interrupt function (actually this is my real code with a lot of unrelated stuff deleted for clarity)


Note the hwFifoCount variable, this is my workaround for the FIFOLVL bug in the hardware as documented over the last couple of days, it keeps track of the number of bytes in the FIFO.

Phew, what a marathon, it probably took longer to document than to do :)



6 comments:

  1. what a $@^%*&! calamity.
    The wording for the described 'mechanism that is clearly and succinctly described in the data sheet' is confusing and possibly needing a degree in language to decode it.

    So, I gather from what you are doing is to send out all bytes less the last byte and then send the last byte out as a final byte so you get the interrupt to tell you all has been sent and the transmit register is empty.

    Serial comms has been around now for a while, so you would think the designers of hardware would be fully conversant and such a no brainer for them to have a fully functional SIOC, remember the Z8530 from all those years back? Ok, let's not talk Zilog at this point, this is too depressing

    Paul

    ReplyDelete
    Replies
    1. Yeah it seems like the UART is a real cock up. I hope the other peripherals aren't as bad :)

      "remember the Z8530 from all those years back?"
      Fondly, as I do all the other Z80 chips, from the days when men were men and PCBs needed 30 chips to be useful.

      I worked with them for years.

      "send out all bytes less the last byte and then send the last byte out as a final byte so you get the interrupt to tell you all has been sent and the transmit register is empty."
      Correct.

      Delete
  2. Chip designers at one time had contact with real world programming. Evidently hiring people that actually know something has become too expensive.

    And yeah. The Zilog Chip was THE STANDARD for quite some time. If you ever played with the Intel 8251 you would know why. The 8251 was a dog.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Reading this is 2016, How do I make use of the U0FIFOLVL register in this case? The lpc2378 manual doesn't have it but mentions it as a feature.How do I receive 4 or 8 bytes at a time from the U0RBR.

    ReplyDelete
  5. It appears that the UART has been fixed. I work with the LPC1788 and recently I found that the last interrupt isn't occurring after the last byte anymore. It's occurring after the last byte has exited the FIFO. And the TEMT flag is being set when the transmitter is truly empty, e.g. after the last byte has been shifted out completely, or at the same point the last interrupt previously asserted.

    I run a UART at 921,600 baud and in Normal Multidrop Mode with Auto Address Detect. But I have to use a GPIO line for OE since the normal OE line is used by other things. So I transmit in 16-byte chunks until I reach the end of my buffer, not worrying about keeping the last byte by itself. On the last interrupt when my transmit buffer is empty, I do a loop to wait until the TEMT flag sets then switch the direction to receive. The wait at 921,600 baud is only about 5 uS.

    ReplyDelete