One-to-many semaphore vs. mutex

Hi,

I have a class which contains multiple tasks. The purpose of this class is to package and transmit data to a remote processor; the transmission is via SPI using DMA (to minimise CPU interaction as much as possible).

The tasks receive data (via pointer) via queue from other classes. Each task appends the received data to the current transmit buffer (which is one of several within a pool). I have a timer which calls a function every 10ms, which then initiates the SPI DMA transfer of the current transmit buffer.

I’m trying to ensure that when the timer function is called, no further appends to the current transmit buffer are made. I’d also appreciate any input as to considerations I need to make/might be missing regarding ensuring one task isn’t interrupted by another while appending its received data to the transmit buffer.

Some code:

The below is representative of one of the several (~5) tasks which receives data and appends it to the current transmit buffer.

static void task1(void *pvParameters)
{
    (void) pvParameters;

    //A BaseType_t variable for capturing the xQueueReceive() result
    BaseType_t queueStatus;

    //Instantiate a variable for receiving the byte array pointer
    uint8_t (*taskAData)[TASK_A_DATA_SIZE];

    for (;;) {

        //Receive the data
        queueStatus = xQueueReceive(taskAQueue, &taskAData, pdMS_TO_TICKS(portMAX_DELAY);

        if (queueStatus == pdPASS) {

            //Check if the received data will fit in the current transmit buffer
            if (transmitBufferPoolByteIndex[currentTransmitBufferIndex] + TASK_A_DATA_SIZE <= (TRANSMIT_BUFFER_SIZE)) {

                //Data does fit into buffer; append data
                memcpy(&transmitBuffer[currentTransmitBufferIndex][transmitBufferPoolByteIndex[currentTransmitBufferIndex]], *taskAData, TASK_A_DATA_SIZE);

                //Update the byte index of the current transmit buffer
                transmitBufferPoolByteIndex[currentTransmitBufferIndex] += TASK_A_DATA_SIZE;
            }
        }
    }
}

In the above task, if the received data doesn’t fit in the current transmit buffer, the timer function is called directly (to transmit the full buffer), the timer is reset, and the received data is written to the start of the next transmit buffer in the pool. I’ve excluded that code from the above excerpt for simplicity.

The below is representative of the timer function which finalises the current transmit buffer and initiates the SPI DMA transfer.

void timerFunction(void)
{
    //Add some metadata to the current transmit buffer
    //E.g. buffer length, termination characters, etc.

    //Initiate SPI DMA transfer of the current transmit buffer

    //Increment the transmit buffer pool index variable
    currentTransmitBufferIndex++;
    if (currentTransmitBufferIndex == NUMBER_OF_TRANSMIT_BUFFERS) {
        currentTransmitBufferIndex = 0x00;
    }

    //Set the byte index position of the new/current transmit buffer
    transmitBufferPoolByteIndex[currentTransmitBufferIndex] = 0x00;
}

What I’d like to understand is what is the optimal way to allow the timer function to perform the final processing on the current transmit buffer (and then increment the transmit buffer pool index as shown above), ensuring that the other ~5 tasks aren’t interacting with the buffer at the ‘same’ time. Would a semaphore, mutex, task notification, etc. be the ‘right’ way to go here?

Any input would be greatly appreciated as always!

Hi jars121,

in your case you would need to mimick an asymmetrical (reader-writer) lock as a simple mutual exclusion will not guarantee coherence of the currentTransmitBufferIndex variable.

The design looks a little strange to me anyways. Why do you trigger the Xmits from inside a timer?

1 Like

is incorrect. portMAX_DELAY is already a dedicated constant to be used directly as ‘blocking time’

1 Like

My first thought on your problem is you want first, a mutex to ‘claim’ the buffer while adding material to it, and then wishin that mutex guarded section, you have a pointer to indicate which buffer you are using. Each sending task takes the mutex, then sends the data over the buffer pointed to, and then gives the mutex back. The master SPI transmit also takes the mutex, starts the SPI send, and then updates the pointer to the next buffer before giving the mutex back.

1 Like

Thanks all for your input.

I don’t have to use a timer, and I’d be open to suggestions as to how better manage the transmission. For additional context, the MCU (on which FreeRTOS is running) is the SPI slave, with the remote CPU being the master. The MCU asserts a GPIO which tells the CPU it’s ready to send data, and the CPU initiates an SPI transfer. At the moment I’m using a 10ms timer as part of my development and testing, but the transfer could definitely be initiated by other means.

Thanks for picking this up!

Thanks Richard. Just to make sure I understand correctly, is the below what you had in mind (for each of the appending tasks):

static void task1(void *pvParameters)
{
    (void) pvParameters;

    //A BaseType_t variable for capturing the xQueueReceive() result
    BaseType_t queueStatus;

    //Instantiate a variable for receiving the byte array pointer
    uint8_t (*taskAData)[TASK_A_DATA_SIZE];

    for (;;) {

        //Receive the data
        queueStatus = xQueueReceive(taskAQueue, &taskAData, pdMS_TO_TICKS(portMAX_DELAY);

        if (queueStatus == pdPASS) {

            //Block until taskA can take the mutex
            if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {

                //Check if the received data will fit in the current transmit buffer
                if (transmitBufferPoolByteIndex[currentTransmitBufferIndex] + TASK_A_DATA_SIZE <= (TRANSMIT_BUFFER_SIZE)) {

                    //Data does fit into buffer; append data
                    memcpy(&transmitBuffer[currentTransmitBufferIndex][transmitBufferPoolByteIndex[currentTransmitBufferIndex]], *taskAData, TASK_A_DATA_SIZE);

                    //Update the byte index of the current transmit buffer
                    transmitBufferPoolByteIndex[currentTransmitBufferIndex] += TASK_A_DATA_SIZE;

                } else {

                    //Call the timer function directly
                    //Reset the timer
                    //Write the current received data to the new buffer
                }

                //Give the semaphore
                xSemaphoreGive(xSemaphore);
            }
        }
    }
}

yes, that is sort of what I was thinking of, though you could probably optimize a bit by reversing the test.

First, test if there is room in the buffer, and if not call the routine that sends the buffer and updates the buffer pointer, and then you can have just a single copy of the buffer add code.

Since you are calling the send buffer routine with the semaphore taken, it says the timer routine would similarly take the semaphore (so it can’t send a buffer that is currently actively being added to) and then call that same routine. That timer routine would need to check if the buffer was empty, as it could be that the timer expired as another task was just starting to send a block that would overflow the buffer.