Comms stacks :(
Comms stacks in plain C :((
This is a summary of how I do it, though it's surely not the only way:
The app starts by by creating a 'generalPool' array of buffer structs, (BS:), of a fixed size. No more buffers are ever allocated and no buffers are ever freed during the run. The BS has space for data, data len, next/prev index bytes and a 'command' enum that describes what the buffer is, (and other stuff, but that clouds the issue). Indexes to this array are used for all inter-thread and driver comms, (I use byte-size indexes, rather than pointers, because there are less than 256 BS and I have RAM constraints). The next/prev bytes are initialized to form a double-linked list, and the calls to get/put an index are protected by a mutex.
Inter-thread comms are performed by getting a BS index from the generalPool, loading it up as required, setting the enum and then pushing the index onto a producer-consumer queue. The thread at the other end dequeues the index and, typically, switches on the enum to handle the BS message. Once handled, the consumer thread can repool the BS or queue it on somewhere else for further handling, (logger, say).
Because the BS has those next/prev bytes, the producer-consumer queue class does not need any storage space of its own - it has first and last bytes and so can link together the BS in a similar manner to the pool.
OK, now drivers:
I have interrupt-nesting disabled so that only one interrupt can run at a time. This enables me to make a BS index 'DriverQueue'. The DriverQueue has actual storage space for the index bytes - it does not use the next/prev links. This allows BS indexes to be safely added at one end, and removed at the other, by any one interrupt and one thread.
I have one 'CommsPool' DriverQueue. This is pre-filled on startup with some BS extracted from the generalPool. These BS are used for received data.
I have one 'commsTx' DriverQueue for each tx interrupt. Outgoing data is queued on them.
I have one 'commsRx' DriverQueue for all rx interrupts. Incoming data is queued on it.
One 'commsThread' handles the higher-level comms by initializing and operating a state machine, similar to your idea. When idle, it waits on a 'CommsEvent' semaphore.
The rx interrupts get BS from the CommsPool, load them up with data from the hardware, set the command enum to 'RxX', (X is the comms channel/interrupt ID number), push the BS index onto the common commsRx queue and signal CommsEvent.
The tx interrupts get BS from their own, private commsTx, load the data into the hardware, set the command enum to 'TxUsed', push the BS index onto the common commsRx queue and signal CommsEvent.
The commsThread is responsible for managing all the I/O. It has a 'commsRq' input queue for comms request BS from other threads. This is not, however a blocking queue - just thread-safe. It is not blocking because the commsThread has to handle the commsEvent signals from the interrupt-handlers as well.
Any thread that wants to communicate stuff loads up a BS with appropriate data and command, queues it to commsRq and signals CommsEvent, so waking the commsThread.
The commsthread does not know why it has been woken, so it polls the commsRx queue first to see if there is a BS in it. If there is, it handles it - if an 'RxX', it processes it through its state-engine code/data, if a 'TxUsed', it checks the CommsPool first, to see if it needs 'topping up', and pushes it there if there is need, else it pushes it back onto the generalPool for re-use elsewhere.
Once the commsThread has handled the driver queues appropriately, it polls the commsRq queue to see if there are any new comms requests from other threads. If there are, it dequeues and handles the request thorough it's state-machine code/data.
After that, the commsThread checks again to see if any CommsPool 'topping up' is required and, if the CommsPool is not full, tops it off with more BS from the generalPool.
The commsThread then loops back to wait on the semaphore again. The semaphore ensures that the commsThread runs exactly as many times as are requried to handle all input from other threads and the interrupt-handlers, no more, no less. If the thread ever wakes up and finds nothing to do, it's an error.
That's how I do it, anyway:) It provides good throughput and efficient use of RAM. Inter-thread producer-consumer queues need no internal storage. Only one thread, (and so only one RAM-consuming stack:), is required for all interrupt-management and Tx/Rx data handling. No mallocs/frees required after initialization. There is no busy-waiting or any need for periodic checking of any flags. No copying of the data is required, (except in/out of hardware - unavoidable). Timeout actions can be handled by either a timed wait on the semaphore, (preferable, if your OS supports it), or by the periodic'injection' of a 'TimeTick' BS on the inputQueue from some other thread. Returned BS can easily be 'diverted' to, say, a logger or terminal, for debug display before returning them to the generalPool.
However you do this, you should consider moving to C++. C just gets messy for anything other than simple straight-line code. C++ allows, for instance the BS to be implemented as class instances with methods for streaming in data and for 'auto-extending' a BS by getting and linking another BS if one BS gets full, so generating a 'compound' data message.
I've left some stuff out. For example, perhaps you already know the misery of tx interrupts - after the tx has been idle, they often have to be 'primed' by having the first bytes loaded into a FIFO to get the TX interrupt to start again :(
Also hint: my UART debug terminal prompt looks like 'A:96>'. The number, (96 here), is the current count of BS in the general pool. If this number starts dropping, I know I have a leak:)
I've been reading the source code of both FreeRTOS and CoOS.
In FreeRTOS (the port I found first in Google), the xSemaphoreGiveFromISR()
function calls to xQueueGenericSendFromISR()
, and this function had this comment:
/* Similar to xQueueGenericSend, except we don't block if there is no room
in the queue. Also we don't directly wake a task that was blocked on a
queue read, instead we return a flag to say whether a context switch is
required or not (i.e. has a task with a higher priority than us been woken
by this post). */
As for CoOS, the function isr_PostSem
called from an ISR just enqueues a request to the so called "Service request queue", using the function InsertInSRQ
. I have no idea about what this queue is, probably is some system task to offload work from ISRs and thus let interrupts complete quickly.
As for mbed, which only has 1 version of the method, it is based on CMSIS RTOS, and there's a call to the function osSemaphoreRelease()
. There's a comment about this and similar functions:
The following CMSIS-RTOS functions can be called from threads and interrupt
service routines (ISR):
- osSignalSet
- osSemaphoreRelease
- osPoolAlloc, osPoolCAlloc, osPoolFree
- osMessagePut, osMessageGet
- osMailAlloc, osMailCAlloc, osMailGet, osMailPut, osMailFree
Functions that cannot be called from an ISR are verifying the interrupt
status and return in case that they are called from an ISR context the status
code osErrorISR. In some implementations this condition might be caught using
the HARD FAULT vector.
So it seems to be just kernel design, some designers chose to have separate functions and let the programmer make the distinction, and some others prefered to have just one method and internally check if the call was made from an ISR.
Best Answer
First of all, the semaphore can be given in "ADC_IRQHandler()" or "HAL_ADC_ConvCpltCallback()" (ADC) by adding:
At the end of the function it seems that "HAL_ADC_ConvCpltCallback" is also an ISR when I use
xSemaphoreGive( xBinarySemaphore);
. With it the program crashes.It is also important to set the priority of the IRQHandler numerically higher than configMAX_SYSCALL_INTERRUPT_PRIORITY, in my case '80', by adding (5 for example).
HAL_NVIC_SetPriority(ADC_IRQn, 5, 0);
, priority 5 ? 15 in my case. You can check this for more information: http://www.freertos.org/RTOS-Cortex-M3-M4.html.Anyway this code is working and takes the ADC init from https://visualgdb.com/tutorials/arm/stm32/adc/.
Code