Recommended best practice for a multithreaded sensor

Hi Nordic.

This is a general question and could as easily have been posted to stack overflow but I have found the members and staff of this community so helpful and adept in explaining things such that even I understand them and thus I hope this is a legit question for this forum.

Currently, I am using the nRF9160 to try out some accelerometers. They record data to a sd-card and send the data to a server via FTP.
The way things context switch is by using a large amount of primitive locks (simple 0 or 1 integers) and a total of 3 threads.

The threads control ftp transmission, memory writing and the last one deals with reading the sensors and calculating trigger values continuously.

A pseudo example of thread usage is this:

accThread()

    while(1)
        if(sensorvalue != 0)
            write_lock = 0
            thread_resume(memThread)

        else
            someother_lock = 0
            thread_resume(memThread)

memThread()
    while(1)
      if(write_lock == 0)
         SPI_write(...)
      if(someother_lock == 0)
        dosometingelse()
        .
        .
    thread_sleep(memThread)


Using integer locks to choose which function to run in a thread and resuming/sleeping threads based on conditions does not seem effective nor safe.

My question is: What kind of architecture should be used to implement this? Surely it is interrupt driven but I cant figure out the schematics of what kind of locks and functions should be implemented for best practice. Is there a name for the structure I need?

I hope this made somewhat sense, english is not my first language. Please ask if you are as confused as I am.

Best of regards


  • Hi, In my case I would you Queue to communicate between Tasks. In Task 1 you can read the values from senzor, then send it to the queue and in Task2 you can read it. It is safe, maybe it will not be fast enought - but it deppends on your needs. Queues are safe.

    Do you understand? Just read it from senzor and send info aout it to the another task..

  • I think I understand if you mean tasks are threads(?). Or maybe you can show me a pseudo example, however it sounds interesting. I am at the limits of the nrf9160 doing this, it is just barely fast enough to keep up (when an event is triggered, ftp-thread is sleeped as to give more cpu to sd-card-thread and sensor-thread)

  • I mean threads (I do not know terminology - I am used to FreeRTOS). 

    Task1:

    Common data structure for all queues in the system:
    typedef struct {
    	uint32_t cmd;
    	uint32_t data;
    	uint8_t temp8[20];
    }dataQueue;
    
    /* cmds constant table*/
    #define NEW_DATA_FROM_SENZOR  1
    #define NEW_DATA_FROM_ADC     2
    ...
    
    /* Create queue */
    char __aligned(4) queueCore[10 * sizeof(dataQueue)];
    struct k_msgq my_msgq;
    K_MSGQ_DEFINE(my_msgq, sizeof(dataQueue), 16, 4);
    
    /*Read data from senzor */
    dataXYZ[3] = getNewSenzorValue();
    /*Send data to the queue */
    dataQueue sendData;
    sendData.cmd=NEW_DATA_FROM_SENZOR;
    memcpy(sendData.temp8,dataXYZ,sizeof(sendData.temp8));
    while (k_msgq_put(&my_msgq, &sd, K_NO_WAIT) != 0) {
              /* message queue is full: purge old data & try again */
              // k_msgq_purge(&my_msgq);
            }

    and reading data in different task:

    dataQueue rxd;
    k_msgq_get(&my_msgq, &rxd, K_FOREVER);
    if(rxd.cmd == NEW_DATA_FROM_SENZOR)
    {
        /* new data are in rxd.temp8 */
    }

  • Thanks for the example! I will try it out and get back to you. Very much appreciated!

  • Using k_msgq_get() and k_msgq_get() like witc recommended seems like a good approach.

    Down below I'll copy in a reply I provided in a private ticket a month ago, which explains different approaches to manage different threads in Zephyr/NCS:

    "I would recommend you to look at the applications under nrf/applications, they are more complex than the simple samples under nrf/samples, and demonstrate how to handle states/program flow in NCS/Zephyr.

    For example the examples nrf5340_audio and asset_tracker_v2 uses k_msgq_get inside a while(true) loop. For example, the audio app calls it through nrf5340_audio\src\main.c-->main()-->streamctrl_event_handler()-->ctrl_events_get()-->k_msgq_get().

    So it will wait at k_msgq_get() until k_msgq_put() is called from somewhere else, and handle it accordingly. While waiting at k_msgq_get() I think the thread where it's called from (main thread in this case) is suspended and the idle thread can run (similar to what happens when k_sleep() is called).

    The applications nrf_desktop, machine_learning and connectivity_bridge uses the Application Event Manager. In this library, events are submitted by modules and other modules can subscribe and react to them. I'm not sure how it works in details, but it seems like whenever an event is submitted, the Work queue (see _event_submit()) is used to execute the appropriate function calls. So whenever the thread that processes the work items in the queue has done it's work, it will get suspended and the idle thread will run and put the chip to sleep.

    There are many more ways of going about this, for example you can use work queues directly and whenever an event happens, like a button press, you can put the response off to the work queue (for example blink an LED). Here is an example made by a colleague of mine where this approach is used: https://github.com/too1/ncs-peripheral-uart-adc/blob/d575cdb9a2e0d7e5f0984a6a228bcb0d3a6f1a7c/src/main.c#L564. So when the adc_sample_event() is called it will run the adc_sample() function. In this sample, I just used k_sem_take() inside a while true loop, and the chip will sleep while waiting for the semaphore."

    Best regards,

    Simon

Related