Buffered UART routines

Coding and general discussion relating to user created compiler modules

Moderators: David Barker, Jerry Messina

Post Reply
SHughes_Fusion
Posts: 219
Joined: Wed Sep 11, 2013 1:27 pm
Location: Chesterfield

Buffered UART routines

Post by SHughes_Fusion » Wed Jan 13, 2016 10:59 am

Does anyone have any insight in to how to do a buffered serial transmit routine?

I thought I'd done one but I've just realised that it doesn't in fact work! The crux is the ISR code - I was wondering why my RS485 bus was going back to 0V after each byte. This is my ISR transmit code:

Code: Select all

        // Transmit interrupt. Output next character from transmit buffer
   If TXIF = true And TXIE = true Then
     If TXReadPtr = TXWritePtr Then                                     // No more data waiting to be sent, wait for transmission to complete then enable RS485 receiver
       If TXComplete Then                                               // TRMT is clear - transmission is complete
         TXIE = false                                                   // Disable interrupts
         RS485REDE = 0                                                  // Enable RS485 receiver
       EndIf                                                            // Note that we will be stuck in the ISR until TRMT clears but at least other interrupts will be getting serviced
     Else
       RS485REDE = 1                                                    // Enable transmitter
       TXREG = TXBuf(TXReadPtr)                                         // Write the byte at the read pointer to the transmit register
       TXBufFull = false                                                // Can't be full as we've just removed a byte
       Inc(TXReadPtr)                                                   // Increment the pointer
       TXReadPtr = TXReadPtr And TXBufMask                              // Mask pointer so it will loop.
     EndIf             
   EndIf
The intention was that I have a PutChar command (and various compound commands using that to write strings etc) which would fill the TXBuf array and the contents of this would be output one byte at a time by the ISR until the buffer was empty. At this stage the ISR would wait for the transmit shift register to empty and would re-enable the receiver.

The PutChar command sets TXIE so as soon as the first character is put in the buffer transmission starts.

Of course the issue is the double-buffering of the PIC UART. As soon as TXIE gets set the ISR code will execute, writing the top byte from the buffer to TXREG. This will get copied in to the transmit shift register at which point TXIF will be set again. The ISR will complete, exit and be called again before the main code has the chance to add anything else to the buffer. In effect you get stuck in the ISR until transmission has completed and TXIE gets cleared.

I guess one way to do this is to only set TXIE once writing to the buffer has completed but it is hard to know when this is the case if you are assembling a message from several bits of information. It also delays transmission.

Any ideas as to a neat way to do this?

Jerry Messina
Swordfish Developer
Posts: 1469
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: Buffered UART routines

Post by Jerry Messina » Wed Jan 13, 2016 4:00 pm

With RS485 I've handled this two different ways before:

1). instead of checking for TRMT in the TX ISR, when you load a byte into TXREG start a timer
set for the byte TX time (minus a half bit or so). In the timer ISR, check for TRMT and set RS485REDE
as appropriate there. You can use the same trick of leaving the timer IF flag set until you see TRMT = 1
so that you'll keep getting an interrupt until the byte is transmitted.

2). separate the RS485 RE and TE signals and just enable RE so that the receiver is always active.
That way you'll see what you transmit and can disable the TE in the RX ISR when you're done sending
a message. This has the addtl benefit of providing a "poor man's collision detection" in case anyone
stomps on your transmissions. It's not 100% effective since it doesn't account for the difference in time
of the signals travelling down the wire, but it's usually better than nothing.


Most times I don't even bother with using TX interrupts since I usually end up blocking until I've sent the data
anyway. The little extra time I'd gain is normally offset by having to deal with potential TX buffer overflows,
sync issues, etc.

SHughes_Fusion
Posts: 219
Joined: Wed Sep 11, 2013 1:27 pm
Location: Chesterfield

Re: Buffered UART routines

Post by SHughes_Fusion » Thu Jan 14, 2016 9:34 am

Good idea to use a timer to control direction change. I wish Microchip had included an interrupt for transmit shift register empty as it would make life much easier here!

I originally polled TRMT in my main loop but I was finding that on occasion it wasn't being picked up fast enough and the transmitting device was hold the bus too long, corrupting messages from other devices. Or at least that is what I thought - maybe it was also a factor of this bug in the transmit routine.

I'm not so keen on using a loopback, I can see it would complicate the receive routines... Although I do get your point about it also allowing collision detection. Not as easy one to do at the moment though without hacking up my hardware.

The solution I'd come up with was to remove the interrupt enable from the PutChar routine and add it in to the Write routines, with a 'catch all' in PutChar where if the buffer is full it will enable the interrupt to ensure things get transmitted. In theory if the first write is a single character it will still stick in the interrupt as the buffer will be empty as soon as it gets transmitted so maybe I still need to modify things to use your timer-based solution.

I do need to use transmit interrupts here unfortunately. One end is an RS485 to SPI bridge so I need to be able to respond to SPI traffic at any time so I can't afford to be stuck in the ISR for any length of time. I've found since I fixed this issue that SPI performance is much improved so it seems to be a win/win in that regard. The other end of the link is a heater controller so probably not the end of the world if it blocks for a few microseconds (longest message takes about 12 microseconds to transmit, 64 bytes at 57600 baud) but as I've implemented the code for one end I might as well use it at the other.

Post Reply