2
\$\begingroup\$

Background

I'm using an ATmega328P to send data to another device at a high baud rate (230400). The device I'm sending data to supports flow control and raises the RTS signal when it needs to hold the transmission. The problem is that the device is expecting me to stop the transmission immediately after RTS is asserted.

The ATmega328P has a transmission buffer (UDR) and a shift register from which data is sent to the TX line. Since the ATmega328P does not support flow control in hardware, I'm implementing it in software. Before sending (before writing to UDR) I'm checking RTS and waiting until it goes low, but apparently this is not enough because data in the transmit buffer and shift register is still being sent and a full byte could still be sent after RTS is asserted when the ATmega328P flushes its buffers.

Apparently, disabling the transmitter when RTS is asserted (setting the TXEN to zero) does not help since it will not become effective until ongoing and pending transmissions are completed, according to the documentation section 19.6.5.

A workaround could be to wait until Transmit Complete (TXC) before sending each byte, but this would impact the data rate since I'm not sending the byte stream back-to-back even when RTS is not asserted (I'm starting to send a new byte only after the previous one was completely sent).

My question is:

With the ATmega328P, is there a way to support RTS flow control in the transmitter while still enjoying the pipelining provided by the transmit buffer and shift register?

\$\endgroup\$
  • \$\begingroup\$ What sort of device expects you to halt transmission in the middle of a frame? \$\endgroup\$ – Ignacio Vazquez-Abrams Nov 12 '13 at 21:37
  • \$\begingroup\$ I don't see whats so wrong about the transmit complete approach. Yes, it might cause a single bit delay, depending on your controller clock speed, but you have to check the RTS condition in any case. Even with a software implemented UART (which isn't that hard), you would have to do this check. Or do I miss something? I guess so because the question doesn't look like you haven't thought about it for a while. Does the other device raise the RTS line "instantly" after the last bit was received? \$\endgroup\$ – Rev1.0 Nov 12 '13 at 22:35
  • \$\begingroup\$ @IgnacioVazquez-Abrams not in a middle of a frame (a byte), but atmega will flush the transmission buffer before stopping, so exactly one byte can still be sent after RTS was asserted. \$\endgroup\$ – Amir Gonnen Nov 13 '13 at 5:56
  • \$\begingroup\$ So then pick the RTS interrupt such that it has priority over the TXE interrupt. \$\endgroup\$ – Ignacio Vazquez-Abrams Nov 13 '13 at 5:59
  • 1
    \$\begingroup\$ When you say "flush" I guess you talk about the next byte going to the transmit buffer due to the double buffering? Why not just put only one single byte in the transmission queue (UDR=data), wait for TX complete ISR, check if RTS is set, if 'yes' write next byte to UDR, if 'no' do nothing and check RTS condition in main loop. Regarding the datarate: If you run at 16MHz/8MHz you have about 70/35 instructions per bit (@230k baud). This should be enough to evaluate RTS and react. \$\endgroup\$ – Rev1.0 Nov 13 '13 at 7:13
3
\$\begingroup\$

Sounds like you have two basic problems, a badly designed external device and that you are trying to make inappropriate hardware do a job it is not intended for.

First, does this other device really require absolutely no more bytes after RTS is asserted? That would be very unusual. It is very common for transmitters to empty a small buffer before obeying RTS, so unless the other engineer didn't know what he was doing the device should be able to tolerate a few more bytes. Maybe it can't handle the "full featured" case of 16 more bytes, but not allowing 2-4 more bytes is just bad design.

Second, given that you need to stop transmitting faster than the size of the output buffer, you picked the wrong hardware. I am not familiar with that line of micros, but surely this is in the datasheet. I see three options:

  1. Get a micro that does have a RTS input and that stops on the next byte boundary when it is asserted. Most UARTs in micros don't have this extra feature, but there are certainly some that do. I know some PIC 33F can do this because I've chosen them in part for this feature. Look around the Atmel product line. They may have a few micros with this feature too.

  2. Use a external UART that does hardware flow control on the byte level.

  3. Use the existing UART with more software intervention. You won't be able to use the internal buffer, and will have to wait until one character is sent, check RTS, then write the next character to the UART. This will be facilitated if the UART can interrupt when empty, not just when there is room in the output buffer.

None of these tradeoffs may be what you want, but that's the cost of bad architecture. Fix it next rev, or respin now if it's really important. The world doesn't always have a magic answer just because your boss wants it now or because you painted yourself into a corner.

\$\endgroup\$
  • \$\begingroup\$ The thing of it is that AVRs don't have a UART output buffer; they only have the single byte they're transmitting or have received, so I'm not exactly sure where the problem is in the first place. \$\endgroup\$ – Ignacio Vazquez-Abrams Nov 13 '13 at 15:10
  • \$\begingroup\$ @Ignacio: Really? The UART isn't even double-buffered? That sounds strange unless this is a very low level cost-optimized processor. I expect there would be at least one register that you can write the next byte to while the previous byte is being clocked out. That means the byte in this holding register would be sent as soon as the previous byte is done, without software being able to check RTS and not send the byte. Even the dumbest cheapest PICs have this 1-deep FIFO, and I find it hard to believe none of the Atmel parts have that. \$\endgroup\$ – Olin Lathrop Nov 13 '13 at 15:44
  • \$\begingroup\$ The receive is double-buffered, the transmit is not. \$\endgroup\$ – Ignacio Vazquez-Abrams Nov 13 '13 at 15:45
  • \$\begingroup\$ @IgnacioVazquez-Abrams This is not correct - You can think of AVRs as having a "one byte buffer": They have a transmission data register UDRn plus a transmit shift register. The SW can write to UDRn the next value to be sent while the previous one is being sent from the shift register on the same time. The fact that the transmission cannot be stopped until both UDRn and the transmit shift register are flushed is the problem here. \$\endgroup\$ – Amir Gonnen Nov 13 '13 at 19:24
  • \$\begingroup\$ @OlinLathrop I like the way you put it "The world doesn't always have a magic answer etc'", and I agree with you about it. So a valid answer to my question could be "No, there is no way to do it" unless I replace HW, implement UART in SW or give up the pipelining provided by the transmit buffer and shift register. If this is the right answer it actually makes me feel better because it means that I did a good research before posting this question. I sent the question for the small chance that I missed something and there is another way to do it, otherwise - I already have a workaround. \$\endgroup\$ – Amir Gonnen Nov 13 '13 at 19:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.