A need for protecting a buffer which is only accessed within an ISR

So I have a ring buffer, and currently, according to the plan, there’d be two writers writing to the buffer within a UART ISR.
There’s no task per se for each writer; the UART ISR is fired as soon as the data is received from either source (terminal and BLE). Assuming the interrupts caused by each writer are of the same priority, there’s no need to have mutex protection for the write access for the buffer given there wouldn’t be any way a currently executing IRQ would be preempted?

And once the writing is done inside the ISR, a callback is invoked where the data is then parsed, and a corresponding message is sent to the main task via a xQueueSend() to perform a certain action based on what’s passed. ISR exits and the main task continues…

Are there be things I should be wary of?

yes, among other things that it may not work…

I tried something very similar many moons ago, can’t remember the platform, but I tried very hard to get it to work… until I realized that the MCU wouldn’t allow me to initiate a UART transfer from another UARTs Rx ISR. The processor wasn’t in a valid state to do that. If I remember correctly, I couldn’t for the life of me get the Transmit ISR to fire after pushing the first character to the UARTs DR. In a task the same thing worked like a charm. It all depends on how the UART module is coded on the Microcode level.

MCU wouldn’t allow me to initiate a UART transfer from another UARTs Rx ISR.

so you had multiple sources firing the same ISR, and you’re saying UART transfer won’t happen for one of them?

I couldn’t for the life of me get the Transmit ISR to fire after pushing the first character to the UARTs DR. In a task the same thing worked like a charm. It all depends on how the UART module is coded on the Microcode level.

Not quite sure if I understand. You weren’t able to fire a UART interrupt with a TX flag after pushing stuff to UART’s DR? What did you do in a task that worked like a charm? When you to DR, the ISR would be fired regardless, no?

One issue to watch out for is getting messages from both sources at once. That will get a merged message to your task.

A second is to watch out for any interactions between the read side and the write side, in particular make sure the read side has actually removed the character before advancing the read pointer so the write side doesn’t think it has room it doesn’t have in the case of a race.

One issue to watch out for is getting messages from both sources at once. That will get a merged message to your task

not sure if I understood the last part of your response. But yeah this would be one edge-case I haven’t figured out of handling appropriately. What I thought was if both the interrupts are of the same priority, one would run first while the second would wait till the first one is fully done but there would be a better way around it perhaps?

A second is to watch out for any interactions between the read side and the write side, in particular make sure the read side has actually removed the character before advancing the read pointer so the write side doesn’t think it has room it doesn’t have in the case of a race.

if the read/write indices are only accessed during the ISR cause of the equal-priority interrupts, do you still foresee it getting corrupted?

For the two inputs, same priority avoids races so you don’t need to worry about that, but more serial ports don’t buffer a whole message, so you might get some characters from one input, then some from the other. You COULD have the ISR mark that one device is receiving data, and if something comes from the other just throw it away. If you do need to accept from two ports at once, you really need to separate buffers.

I was commenting that the task that READS out of the buffer will be updating things that the ISR will be reading, and you need to watch out for races. If you have anything that both ends update, then you need to use a critical section.

more serial ports don’t buffer a whole message, so you might get some characters from one input, then some from the other.

whole message? the ISR is fired for each character inputted, isnt it?

If you do need to accept from two ports at once, you really need to separate buffers.

Please note that Im not receiving inside a task, rather inside an ISR which is fired for each character inputted. So if two sources have IRQ enabled and or the same priority, the ISR would be invoked for each source accordingly unless both are sent at the same time, which then would follow sequential ISR runs I thinkso like you mentioned, there wouldnt be any possibility of races

I was commenting that the task that READS out of the buffer will be updating things that the ISR will be reading, and you need to watch out for races. If you have anything that both ends update, then you need to use a critical section.

but in my case, task isn’t really reading from the buffer, rather it’s read/parsed inside the ISR and only the relevant message is sent to the task via FreeRTOS Queue i.e signalling its idling otherwise.

Right, I am saying that SINCE you don’t have the UARTS buffering a whole message before interrupting, the ISR will somewhat alternate which one adds the characters it has gotten into the buffer, so the buffer gets filled with a scramble of the two messages.

When you trigger the callback, it will see the jumble of characters. If the parsing of data is minimal then doing it in the ISR might be ok, But I find it normally best to have the ISR just buffer the data, and if they can simply determine the end of a message, signal the task when a full message is in the buffer, otherwise let the task pull out the characters as they come in and let it figure out where the message breaks are.

SINCE you don’t have the UARTS buffering a whole message before interrupting, the ISR will somewhat alternate which one adds the characters it has gotten into the buffer, so the buffer gets filled with a scramble of the two messages.

by buffering a whole message before interrupting, you mean the ISR isn’t fired for each character entered, and rather once it’s done entering?

And by alternating, I guess you mean in the context of both interrupts happening at the same time or in general? Because now I’m curious as to how will I distinguish the data from one source from another provided they’re both writing to the same buffer.

When you trigger the callback, it will see the jumble of characters. If the parsing of data is minimal then doing it in the ISR might be ok, But I find it normally best to have the ISR just buffer the data, and if they can simply determine the end of a message, signal the task when a full message is in the buffer, otherwise let the task pull out the characters as they come in and let it figure out where the message breaks are

my parsing function hopefully isn’t super complex; just determining the start and end index, and running memcpy.
How could you have the task pull out the character as it’s buffered in? Are you sending a received byte via RTOS Queues etc from ISR back to the task directly?

The standard way to initiate an interrupt driver UART Xfer is to deposit buffer and size, enable the UART’s TXE interrupt, then push the first char to the DR and let the ISRs handle each character in turn until the buffer is empty (or force an inintial XMit ISR and let the ISR do every char including the first one). That is standard off the shelf code and works in a bazillion installations. Yet on that platform I could not do the transfer initiation inside a receive ISR.

What I’m saying is this: If you have trouble getting it to work, first write it not from ISR to ISR but piggybacked on a task until it works, then try to relocate the XMIT init to your RX ISR. It may still work, but then again it may not.

the TXE bit is set right after the frame has been fully send in the TX line and is ready to be read into the DR, yes?

I didn’t mention but i’m using a nordic device which uses different set of registers though it’s still an arm cortex processor. And i’m afraid I’m still not too convinced yet about dealing with multiple interrupts firing from each producer

As I said, you probably AREN’T buffering the full message as that would require the UART to have a big buffer in its hardware. Some will buffer up multiple characters in a FIFO and interrupt when the fifo gets near full or it detects a pause in the character stream.

Yes, the issue is if the two sources send messages with an overlapping time, the message will be received overlapping. The only ways I know to handle this is to either:

  1. Have separate buffers for each device,
  2. KNOW that you can’t get messages from both devices at once, or
  3. When one device starts sending a message, set a flag to ignore data from the other.

Yes, I often will use a Queue or StreamBuffer from a serial device that is filled by the ISR and emptied by the Task. This works if the serial device isn’t so fast that this overhead is a problem, that is when I will got a buffer in the ISR that sends a full message at a time.

Absolutely. I’m quite amazed that programmers still have to work these scenarios into UART ISR handlers. It’s as if the designers of the UARTs have never actually had to write any code to use their UARTs.

I have a driver model I’ve used for years that abstracts all of this behind an API, allowing the app to configure a UART to fire “events” when certain criteria is met, such as RX_THRESHOLD, RX_MATCH_CHAR, RX_TIMEOUT, TX_THRESHOLD, etc… It saves a lot of headaches.

you probably AREN’T buffering the full message as that would require the UART to have a big buffer in its hardware. Some will buffer up multiple characters in a FIFO and interrupt when the fifo gets near full or it detects a pause in the character stream.

That’s what i’m trying to understand; what are you referring to by buffering the full message? is it that you would only fire an interrupt once a end-of-data byte is received as opposed to per each byte and till then, you’re only storing the data into FIFO (somehow)?

KNOW that you can’t get messages from both devices at once
Yes, I’m aware of that but you should have edge cases covered, right?

  1. Have separate buffers for each device
  2. When one device starts sending a message, set a flag to ignore data from the other.

This makes sense, but how would the IRQ handler know where the data is coming from? Once that’s known, the rest isn’t too bad

Yes, I often will use a Queue or StreamBuffer from a serial device that is filled by the ISR and emptied by the Task. This works if the serial device isn’t so fast that this overhead is a problem, that is when I will got a buffer in the ISR that sends a full message at a time .

Please not that the task isn’t emptying the Fifo;

uint8_t parsedData[50];
void UART_IRQ_Handler(void) {
    callback(fifo);
    xQueueSendFromISR(systemQueue, parsedData, 0);
}

void callback(uint8_t *fifo) {
   // parse the current input in Fifo -> "empties out/reads" the Fifo
     parse(fifo);
}

  • also to clarify: if the serial device is too fast, the overhead from queue/streambuffer may cause delays and won’t be able to empty out as fast as it’s being filled in if they were used to read straight from the fifo?

I think the idea is that the hardware can be kept simple and that level of stuff works well in the code, as there are so many different options for what you want to do.

NO hardware is going to be built with ‘enough’ memory for all needs.

I agree that building a common API that you design to means you only need to suffer the pain one per platform, then you have a serial port that works the way YOU want, which may well be different than what I want, at least today, tomorrow I may want it to work differently, so I build a different driver.

With hardware, I would have to change the chip to a different chip that implemented the different protocol.

Well alright, Furx, this would be the point where I’d stop talking in conjunctive and start coding. It’s simple, really: Code out your plan and see if it works. Please let us know the results!

1 Like

I believe what Richard tries to explain is the following:

Assume one of your serial sources sends the string “Hello World” and the other “It’s me talking.” You attempt to feed both strings into the same input buffer right?

Now if each character causes a discrete Rx ISR, how do you plan on ordering the 26 RX inputs such that your common buffer doesn’t end up with “HeIlt’slo mWe talkinWorldg” ? Or don’t you care about the contents of the individual messages?

At least that would be my question here.

Now if each character causes a discrete Rx ISR, how do you plan on ordering the 26 RX inputs such that your common buffer doesn’t end up with “HeIlt’slo mWe talkinWorldg” ?

well, that’s exactly my question. I am not sure if there’s a way to tell which source actually caused an interrupt inside an IRQ handler; each byte received causes an interrupt, so unless you know how to only consider the input from a specific source, there’s no way I could think of that would prevent the “corruption” of the data being stored into a Fifo.

Ideal situation would be to not let any source wait or corrupt the data by perhaps having two buffers where each buffer stores the stuff from a particular source only…I think

Well normally each device creates its own interrupt, and goes to its own ISR (some processors start in a common ISR, and you ask the interrupt controller which interrupt you are servicing), so at that level the two data sources will be distinct. It is only funneling them both into the same buffer that causes the possible confusion.

In my case, the same UART interrupt is triggered though.

  1. BLE UART service upon being triggered puts stuff into UART TX register → invoking UART IRQ handler
  2. Stuff sent from a COM port session → puts stuff into RX register → invoking the same UART IRQ handler