cancel
Showing results for 
Search instead for 
Did you mean: 

A general question about serial data (usart) and an RTOS

Harvey White
Senior III

I have a specific problem, I'm trying to read data from a serial source, length unknown, transmission length known, in an RTOS environment (FreeRTOS to be exact). I'm using the HAL drivers because at least, they set things up for me.

I have several problems with this.

My operational problem is that using HAL transmit (no DMA, no IRQ because it works well, I can survive if the transmit task is interrupted) to an ESP8266 sends well.

Receiving works mostly, misses some characters, and messes up some pointers and a data structure attached to the receive.

In an operating system environment, any task (and this is one) can be interrupted at any time for any reason. So my receive has to survive being interrupted. The interrupt time can be a millisecond or more and at a 115200 baud input rate, that's about 10 characters. The hardware USART in the chip has a buffer of one character.

We won't go into how inconvenient that is, whether or not the decision even makes sense, but it is what it is and we can't expect the hardware to be redesigned.

So we have to fix it in software....

I'm currently using DMA receive, CubeMX generated code (and no, I"d prefer not to use LL drivers, not yet, they don't get along well with HAL drivers). Every once and a while, the DMA HAL driver does something, or the system does something, and the RX buffer is messed up. Remember that this is an RTOS driven system, and any task (receive, get character, put in buffer) can be interrupted. Several characters are missed, and earlier characters in the string are missing. I am also getting the RX buffer (fed by DMA) filled with a lot of zeros. You can argue that this is a bad pointer, but it's set up by code, one way or another. The transmit, but not receive, is protected by a semaphore.

While the buffer is filled with zeros to start with, that the buffer has lots of leading zeros says that the DMA routine is putting them in once a timed receive is executed. I've looked at the serial stream coming in, it has no omissions.

I have a few observations, and then some questions:

Firstly, that the HAL routines for serial transmission are flawed, and flawed critically.

THIS IS A BUG.

NOBODY who wrote the HAL routines ever considered the situation where a serial peripheral would return a variable number of bytes. EVER.....

Sorry, but reality wins here. ESP8266 is one case, and so (apparently) is a GPS peripheral.

Indedd, while the LL drivers enable the IDLE LINE flag, the HAL drivers do not.

Haven't seen that fixed yet.

It may be possible (in this particular situation) to designate a particular character as a "stop" character. No facility for this exists in the HAL drivers.

it may be possible to designate a time by which all replies should be generated. No facility for this exists in the HAL drivers.

This list is a sart.

6 REPLIES 6
S.Ma
Principal

Some implements a DMA for TX and DMA Cyclical for RX with 1 msec time buffer check sweeps.

Some mix cyclic buffer with DMA block transfer.

Coming from 8 bit MCU experience, I try to keep DMA for 1+ mbit data transfers...

To implement a bridge USART to SPI, used LL with SW per byte RX and TX FIFOs, under interrupt ISR.

As it is buried in the interrupt, it does not know if a RTOS or bare metal is above.

What remains to tune are the interrupt priorities and core frequency depending on ISR max tolerable latencies.

Jack Peacock_2
Senior III

Perhaps the HAL doesn't work in this application but the STM32 does support what you describe in hardware. Assuming you have a newer STM32 with programmable RX IDLE time the USART RX DMA can handle variable length messages. This situation also occurs in Modbus protocol serial applications, where message framing is defined with an inter-frame gap time guaranteed to be greater than inter-character delays (i.e. Modbus RTU mode). Message is binary data so an address match for end/start of frame character can't be used.

If this fits your application then set up the USART to interrupt on RX IDLE with a timeout that meets the framing gap time. In the ISR handling USART RX stop the DMA at the timeout, post the completed message event to your blocked task, along with the receive count, and then restart RX DMA and USART to continue receiving data. This assumes you use a large circular buffer with sufficient space to process messages before the buffer overruns. If the DMA completes before IDLE timeout you have detected a protocol error from the host.

For older STM32s without programmable IDLE (like the F1) you have to connect a timer input to the USART RXD line. ST has an app note on how to do this. Effectively the hardware timer acts as a programmable IDLE detector.

I've used this technique with F4s and RS-485 up to 256Kbaud and with FreeRTOS without dropping variable length packets, so there is no hardware limitation on what you're trying to accomplish. I don't use the HAL. As you have discovered, it has some serious limitations for more advanced applications. The HAL is best used for training, not commercial products.

STM32s don't use USART FIFOs because DMA is much more flexible, albeit more complicated to use. Of the two methods I much prefer RX DMA when using machine to machine burst transfers. FIFOs are fine for small messages but aren't sufficient for 100+ byte packets.

There's no reason USART TX DMA won't work either, unless your remote receiving end can't handle the burst rate, or if there's too much of a mismatch in baud rates. TX DMA is easy to use and very reliable. I never use TX interrupt mode on USARTs for data transfers, way too much overhead compared to block transfers.

Jack Peacock

The problem I think I'm seeing is that RTOS turns off interrupts to change tasks. While characters are not coming in that fast (11.52 chars/sec roughly), apparently something is messing up the DMA pointers, and therefore, killing a reply.

I'm looking for a way around that.

I am using an LK432, F445, and an F767. They likely have the idle time interrupt built into hardware. I don't use the LL drivers because there's nothing other than a setup. Just didn't want to go back and write all the intermediate level drivers at all. Had enough of that on an XMEGA, thank you.

The application is transmit, wait for characters, (apparently completely buffered by the ESP8266), then wait until done. I can easily set the gap for 300 us or so, which is a 3 character gap. So it'll have to be some register manipulation, I'd guess.

I can use a large circular buffer (either in RAM memory or perhaps in SPI memory) if I go this way, so that's not the problem. I'm suspicious of why I get periodic message errors (coming back as seen by the processor, not as transmitted.)

Not sure where they are coming from, and not sure where the software error is that's causing them. I need to narrow that down, since the protocol never overwrites a message send/receive session unless RTOS is ignoring its own semaphores.

Task makes command, accesses semaphore, semaphore blocks further access to hardware, sends message, receives message, processes same, releases semaphore. So not another task, I think.

not useful to do DMA on transmit, unless I know exactly when it's finished (I know, transmit count interrupt when done, but simplifying for the moment is a goal.

I think that I could easily implement a 1000 byte FIFO if I needed it, either in RAM or by throwing hardware at it. Now how well that would work, that's another matter. Needed? Not sure yet

Are you using RTOS at all? The whole system is too complicated to *not* have an RTOS.

Until I clear up the suspect list, I'm still considering RTOS.

Right now, the system is building a buffer using DMA on RX, with that being shut down and the buffer cleared before the transmit starts. RX DMA (to a sufficiently large buffer) is then started, the message transmitted, and then a dumb but adequate timeout is done (vTaskDelay) in RTOS.

Comparing the transmitted message command in the TX buffer and the RX buffer indicates an error (RXbuffer command is written at the same time TX buffer is built), so something is messing up the RX buffer upon occasion. Not sure what's going on there, not yet.

If I throw hardware at it (very possible) the FPGA can't miss anything, and reads from the FPGA are essentially monotonic, and *really* hard to screw up.....

Rather not do that, though. (FPGA is already on the board, so that's not a problem).

Thanks, I'll at least look into programming the idle interrupt, even if I have to do it the hard way.

Harvey White
Senior III

Just as a further comment:

I've spent a bit of time investigating various solutions.

One of the better ones is (and I don't remember his name, sorry) to adjust the interrupt handler to dump into a buffer then after that, reset the buffer pointer, get the character, and then reset the count so that the interrupts go on forever.

In anything that weren't the FreeRTOS environment, that would work wonderfully. Remember that an RTOS turns off interrupts for changing context (tasks), and *could* miss a character. I've found, with the code so far, that it can miss quite a few.

On the interrupts, I can compare the put pointer on the FIFO (software, sorry) and since the FIFO is directly fed by the interrupt, if it stays constant for long enough (no, can't depend on the ESP8266 to continuously send characters until done. It might, but I don't want to depend on that), then transmission is ended.

Still misses characters, but a longer delay waiting for them does help. Problem *not* solved.

I looked at DMA, but because of the shadow registers, I can't read the DMA put pointer, so I don't know where it is and what it did... Not helping. Does DMA continue even when you're debugging and paused for a breakpoint? Never saw anything that told me about that.

Right now, the solution seems to be to throw hardware at it. Easiest hardware to throw at it is a UART to I2C bridge (I have I2C implemented, and the same chip could do SPI). It has a 64 byte transmit and receive FIFO. Since at 100 Khz clock I2C is transmitting about 9-10K chars/sec and so is 115200 baud (within various definitions of "same"), I should be good enough. Right now, I think it would work, so the next phase is to throw hardware at it.

Yep, understand why FIFOs were not in the USARTS. Makes sense, but is awkward at best when messing around with an RTOS. Could roll the FIFO and USART into an FPGA, that's plan B.....

*did* try other software solutions, but mixing LL drivers and HAL drivers is a mess. Yes, know that HAL drivers are the pits... However, as I've metioned, LL drivers are a bit too simplistic, and I'd have to come up with SPI, I2C, timer, SDIO, RS-232, LL drivers. This is a bit of a complicated project.

So hardware it is.....

Harvey White
Senior III

Thought that I'd revisit this in case someone gives this a try.

I threw hardware at it, but very specific hardware.

First: The ESP8266 by itself, the 8 pin board, is ill suited to an ARM processor. The real reason is that there is no flow control. Firstly, get a package if you're going to use the family that has flow control. The chip itself has it, but it's not brought out on those 8 pins. You need two more.

Second: When you do use hardware flow control, the chip and USART have to react fast enough that no characters are lost. I've put in two solutions, neither of which are completely implemented at the moment. Waiting for boards to be made.

Solution 1, use a SC16IS750 chip. Programmable USART, I2C interface, flow control, and a 64 byte buffer. The I2C drivers can be made to be reasonably well behaved. Set the threshold to 32 bytes, and you have enough room for a bit of lack of synchronization.

Solution 2, If you already have an FPGA, put a USART and a FIFO in it, making the FIFO bigger than 64 bytes. 1K by 8 ought to be decent enough.

The problem with variable length I2C messages can be solved, perhaps elegantly, perhaps not, by going to fixed length packets and having a packet sending queue loading structure. Instant fixed length messages. Oh, and on the I2C, programmed receive in the slave processor plays badly with FreeRTOS, I'd suggest interrupt driven routines. Master seems to be happy with programmed I2C for send, still working on something in the slave back to master route.

and yes.... I had been doing other things, too.