2

I am developing a rather complex microcontroller application in C, and I have some doubts about how to "link" my shared data between the different tasks/threads without coupling them.

Until now I have used a time-sliced scheduler for running my application, and therefore there has been no need for data protection. But I want to make the application right, and I want to make it ready for an multi-threaded OS later on.

I have tried to simplify my question by using a completely different system than the actual system i am working on. I couldn't add a picture because i am a new user, but ill try and explain instead:

We got 4 tasks/threads: 3 input threads which reads some sensor data from different sensors through Hardware Abstraction Layers (HAL). The collected sensor data is stored within the task domain (ie: They wont be global!!). Now we also got 1 output task, lets call it "Regulator". Regulator has to use (read) sensor data collected from all 3 sensors in order to generate a proper output.

Question: How will Regulator read the collected data stored in the different input tasks without coupling with other tasks?

Regulator must only know of the inputs tasks and their data by reference (ie: no #includes, no coupling).

Until now Regulator have had a pointer to each of the needed sensor data, and this pointer is set up at initialization time. This wont work in a multi-threaded application due to data protection.

I could make some getSensorValue() functions, which make use of semaphores, for each sensor value and then link these to Regulator with function pointers. But this would take up a lot of memory!! Is there a more elegant way of doing this? I am just searching for inputs.

I hope all this is understandable :)

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Jolle
  • 1,336
  • 5
  • 24
  • 36
  • What hardware are you running on? The family of microcontrollers makes a big difference to what can be done. For example on many microcontrolelr there is no virtual memory. In other controllers 'threads' are not actually threads but simply watchdog swapped stack pointers. – Pyrce Apr 03 '13 at 17:41
  • At the moment i am running on a PSoC5 later on i am planning to port/convert to a STM32? – Jolle Apr 03 '13 at 17:44
  • Those families look to have true threading/virtual memory tables. When you say that data protection will prevent the application from reading the thread specific data, do you mean that these 'threads' are being launched as processes instead of threads? Because threads can read other thread's memory unless you explicitly restrict that memory by thread local keywords like `__thread`. What do you consider a lot of memory, 3 Semaphores should only take a few bytes of memory. Or can the threads not be blocked for extended times due to sensor read rates? – Pyrce Apr 03 '13 at 18:01
  • Thanks for your reply! I'll try to elaborate: Say I have only 2 threads defined in: Sensor.h and Regulator.h. Sensor will collect some data and save it in some variables defined in Sensor.c. Now Regulator.c has to access these data WITHOUT coupling to sensor.c (I mean without making a #include "Sensor.h"). – Jolle Apr 03 '13 at 18:12
  • Out of curiosity, why can't you include Sensor.h? For this situation it looks like you need one master file that includes both headers, starts all the threads, and links them together so they can access each other's data. It seems like from your description Regulator.c can't know about data in Sensor.c without including Sensor.c (not recomended) or Sensor.h? – Pyrce Apr 03 '13 at 18:19
  • It's because I want the application to be so generic as possible. I'll make an other example explaining this better. I got 2 threads again: Motionsensor1.h and Regulator.h. Motionsensor1 collects data from various sensors like gyroscopes and accelerometers and combine the data using one type of sensorfusion (for example a complementary filter). Regulator uses the output data from Motionsensor1 to generate some kind of output. Now I make a new thread: Motionsensor2 which generates the same kind of output as Motionsensor1 but uses a different sensorfusion (for example Kalman filter). Continued.. – Jolle Apr 03 '13 at 19:00
  • > Now Motionsensor1 and Motionsensor2 isn't necessarily implemented by the same party therefore the variables containing data may not be named the same. Therefore I can't just change the #include "..". Here it would be smart for me just to give the right reference in the initiation of Regulator. – Jolle Apr 03 '13 at 19:05
  • So I dont have to change any code in the regulator to make it use another data as input. I find this elegant :) – Jolle Apr 03 '13 at 19:06
  • What you're describing is just a common interface between sensors and regulators, which doesn't preclude that Motionsensor2.h can't be included. In fact, Motionsensor2.h has to be included by Regulator (directly or indirectly) or Motionsensor2.h must include Regulator or another file must include both for these two files to interact. The "right reference in the initiation of Regulator" indicates you have a main file which is passing a Sensor reference to Regulator. I'll post an answer in a bit to expand on what I mean so we don't extend this comment section much further. – Pyrce Apr 03 '13 at 19:16
  • I agree! And what you are descriping is how it is running today! Main passes pointers, which point to the direct sensor data, to Regulator. Problem is: There is a lot of sensor data, so it gets a little messy. AND because Regulator is given a direct data pointer it is not possible to implement data protection. Then ill have to make getFunctions() to all sensordata and then pass these function pointers to Regulator, but then it will take up a lot of space :) – Jolle Apr 03 '13 at 19:32
  • the project you are working on is a good candidate for time slice scheduler. I really don't see the value to migrate to threads. – Kamyar Souri Apr 03 '13 at 21:09

1 Answers1

2

From what you described in the question and comments it seems like you're most worried about the interfacing between Sensors and Regulators being low-memory with minimal implementation details and without knowing the explicit details of each Sensor implementation.

Since you're in C and don't have some of the C++ class features that would make encapsulation easier via inheritance, I'd suggest you make a common datapackage from each Sensor thread which is passed to Regulators rather than pass a function pointer. A struct of the form

struct SensorDataWrap {
    DataType *data;
    LockType *lock;
    ... other attributes such as newData or sensorName ...
};

would allow you to pass data to Regulators, where you could lock before reading. Similarly the Sensors would need to lock before writing. If you changed data to be a double pointer DataType **data you could make the write command only need to lock for the time it takes to swap the underlying pointer. The Regulator then just needs a single SensorDataWrap struct from each thread to process that thread's information regardless of the Sensor implementation details.

The LockType could be a semaphore, or any higher level lock object which enables single-access acquisition. The memory footprint for any such lock should only be a couple bytes. Furthermore you're not duplicating data here, so you shouldn't have any multiplicative effects on your memory size relative to sensor read-outs. The hardware you're using should have more than enough space for holding a single copy of the data from the sensors you described as well as enough flash space to accommodate the semaphore or lock objects.

The implementation details for communication are now restricted to lock, do operation, unlock and doesn't need complicated function pointers or SensorN specific header includes. It should take close to the minimal logic needed for any threaded shared data program. The program should also be transferable to other microcontrollers without major changes -- the communication only really restricted by the pressence/absence of threading and locks.

Another option is to pass a triple buffer object and do buffer flipping in order to avoid semaphores and locks. This approach needs atomic integer/bool support to be created (which you most likely have exposed by the compiler if you have semaphores). A guide to using triple buffers for concurrency can be found on this blog. This approach will use a little more active memory, but is a very slick way of avoiding most concurrency problems.

Pyrce
  • 8,296
  • 3
  • 31
  • 46
  • Ok! This sounds (a little) like Message Queues, and I have had that thought but concluded (without any good arguments actually) that it was an overkill. Though this isn't exactly what you are talking about right? If I understand this right the Regulator should have a reference to an instans of SensorDataWrap (which typedef should be declared for both Sensor and Regulator btw), and inside SensorDataWrap there is BOTH data and some kind of protection mechanism?. I like the idea! The double pointer part I didn't quite understand though. – Jolle Apr 03 '13 at 21:00
  • Exactly, the implementation I wrote out is a simplified message queue in which it's the regulators responsibility to read data before it's overwritten by the sensor. A message queue implementation would work just as well and isn't overkill. The double pointer is a minor note. A single pointer version requires sensors to hold the lock while they write all of their readings into data. A double pointer would let them write into a new 'data' object and only lock while they changed the pointer to address the new read-outs. This require memory management though, so it's not explicitly better. – Pyrce Apr 03 '13 at 21:11
  • OK, I actually think that this is better than message queues because the regulator is real time, and therefore it will not necessarily consume the data as fast as the Sensor produces it. Regulator should just access the newest data at all times. But thank you very much, that was just the kind of input I was looking for! – Jolle Apr 04 '13 at 06:19