Search code examples
cmicrocontrollerfreertosrtosespressif-idf

FreeRTOS: Is semaphore appropriate for a proxy MCU node communication?


I am currently trying to develop a proxy program that takes data from a SPI bus to tcp and vice versa. I would like to know if the method i intend to do is a good/intended way of utilising freertos library. The program is running as a SPI master with GPIO pin trigger from slave if slave wants to send data over as SPI can only be initiated from master.


char buffer1 [128];
char buffer2[128];
static QueueHandle_t rdySem1 //semaphore
static QueueHandle_t rdySem2 //semaphore
volatile atomic int GPIO_interrupt_pin;

void SPI_task(void* arg)
{
 while(1)
 {
  if (GPIO_interrupt_pin)
   {
   //TODO:read data from SPI bus and place in buffer1
   xSemaphoreGive(rdySem1);
   GPIO_interrupt_pin = 0;
   } 
 xSemaphoreTake(rdySem2, portMAX_DELAY);
 //TODO:send data from buffer2[] to SPI bus
 }
}

void tcp_task(void* arg)
{
 while(1)
 {
  int len; 
  char rx_buffer[128];
  len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
  if (len>0)
  {
   //TODO:process data from TCP socket and place in buffer2
   xSemaphoreGive(rdySem2);
  } 
 xSemaphoreTake(rdySem1, portMAX_DELAY);
 //TODO:send data from buffer1[] to TCP
 }
}

//only run when GPIO pin interrupt triggers
static void isr_handler(void* arg)
{
 GPIO_interrupt_pin = 1;
}

Also, i am not very familiar with how freeRTOS work but i believe xSemaphoreTake is a blocking call and it would not work in this context unless i use a non-blocking call version if xSemaphoreTake(). Any kind soul that can point me in the right direction? Much appreciate.


Solution

  • Your current pattern has a few problems but the fundamental idea of using semaphores can solve part of this problem. However, I think it's worth looking at restructuring your code such that each thread only waits on it's respective receive and does the complementary transmit upon reception instead of trying to pass it off to the other thread. Trying to pass, for example, both TCP-recv waiting and SPI-packet-to-TCP-send waiting, which unless you are guaranteed that first you get data over TCP to send to SPI and then you get data back, doesn't really work very well; truly asynchronous communication involves being ready to wake on either event (ie, tcp_task can't be waiting on recv when a SPI packet comes in or it may never TCP send that SPI packet until something is TCP recieved).

    Instead, let the tasks only wait on their respective receiving functions and send the data to the other side immediately. If there are mutual exclusion concerns, use a mutex to guard the actual transactions. Also note that even though it's atomic, there is a risk without using test and set that GPIO_interrupt_pin might be set to 0 incorrectly if an interrupt comes between the test and the clearing of the variable. Fortunately, FreeRTOS provides nicer mechanisms in the form of task notifications to do the same thing (and the API I am using here is very much like a semaphore).

    void SPI_task(void *arg)
    {
      while (1) {
         // Wait on SPI data ready pin
         ulTaskNotifyTake(0, portMAX_DELAY);
         
         // There is spi data, can grab a mutex to avoid conflicting SPI transactions
         xSemaphoreTake(spi_mutex, portMAX_DELAY);
        char data[128];
        spi_recv(data, sizeof(data)); //whatever the spi function is, maybe get length out of it
        xSemaphoreGive(spi_mutex);
    
        // Send the data from this thread, no need to have the other thread handle (probably)
        send(sock, data, sizeof(data));
      }
    }
    
    void tcp_task(void *arg)
    {
      while (1) {
        char data[128];
        int len = recv(sock, data, sizeof(data));
        if (len > 0) {
          // Just grab the spi mutex and do the transfer here
          xSemaphoreTake(spi_mutex, portMAX_DELAY);
          spi_send(data, len);
          xSemaphoreGive(spi_mutex);
        }
      }
    }
    
    static void isr_handler(void *arg)
    {
      vTaskNotifyGiveFromISR(SPI_task_handle, NULL);
    }
    

    The above is a simplified example, and there's a bit more depth to go into for task notifications which you can read about here: https://www.freertos.org/RTOS-task-notifications.html