Click here to Skip to main content
16,022,122 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
In an embedded software project, need to read from 100 data channels (possibly more in the future).
Each channel is sampled in a task, with possible task cycles of 1, 5, 10, 20, 50, 100, 250, or 1000 ms. The application layer can call Set_Sample_Period(Channel, iSamplePeriod) to set the sample period for each channel.

Currently, I am considering a simple logic for a 5 ms task, as shown below:
Task(5ms):
  for (i = 0; i < 100; i++) {
    if (DataCh[i].SamplePeriod == 5) {
      ReadDataCh(i);
    }
  }


Each task would follow similar logic for the respective sample periods.

My question is: Is there a more efficient or optimized way to implement this logic?

Thanks

What I have tried:

Task(5ms):
  for (i = 0; i < 100; i++) {
    if (DataCh[i].SamplePeriod == 5) {
      ReadDataCh(i);
    }
  }
Posted
Comments
KarstenK 5 days ago    
top-tip: most time consuming will be the switching of tasks, so think what the longest acceptable time for ONE task is. Try different solutions.

Use only one task with a cycle of 1ms:
C++
cycle = 1;
do{
  wait(1); //non-busy waiting for 1ms
  for (i=0; i<100; ++i) {
    if (cycle % DataCh[i].SamplePeriod == 0)
     ReadDataCh(i);
  }
  if (++cycle > 1000)
    cycle = 1;
} while (1);

Rationale: in your proposal, the 1ms task goes anyway through the list of channels and reads any channels requiring service. In addition the 5ms, 10ms, etc. tasks do the same thing. Why not use only one task?
 
Share this answer
 
v2
Comments
Rick York 9-Oct-24 22:52pm    
This was exactly my first thought. As long as it can read and store data within 1ms that should work.
merano99 5 days ago    
Unnecessary waiting times are generated by wait. In addition, an overflow can occur with the if query. With the loop used here, 100 hardware query delays and an additional wait could lead to increased latencies and non-deterministic sampling. It would be better to avoid wait completely.
Mircea Neacsu 5 days ago    
The “wait” function is ment as a non-busy waiting. Call it “sleep” or whatever you want. Otherwise, with a non-preemptive scheduler, the whole system would grind to a halt.

Also please explain how the if statement could overflow.
merano99 4 days ago    
Thank you for the clarification regarding the "wait" function being non-busy. While a non-busy waiting approach, such as "sleep," might reduce CPU usage compared to busy-waiting, I still believe that in the context of an embedded system with limited resources, relying on any form of waiting (busy or non-busy) is fundamentally inefficient and could lead to further issues.

1. The main issue with using a "wait" (even a non-busy one) is that it introduces dependency on the accuracy of the underlying sleep mechanism. This can lead to timing drift, especially when combined with sampling multiple channels at different intervals.
For example, the system's ability to handle hardware access and sampling within the allotted time can be affected by non-deterministic delays caused by the wait function. As mentioned in my initial response, accurate timing is crucial, especially in embedded systems where real-time behavior is often required.

2. While a non-busy wait might yield CPU time to other tasks, it still introduces latency in checking the sampling intervals of each channel. When handling 100 channels, the timing needs to be precise. The scheduler may not wake up the task exactly on time, leading to delayed sampling of some channels, especially for those with shorter intervals. In real-time systems, this can result in violations of the required timing constraints.

3. By utilizing a timer interrupt as I suggested, you avoid wasting cycles or introducing unnecessary latency by repeatedly putting the task to sleep and waking it up. The ISR-based approach allows for more efficient use of CPU resources, as it ensures that channels are sampled precisely when needed without the overhead of periodic waking and sleeping.

The issue with the if condition is that when the cycle resets to 1, channels with sample periods that don't divide evenly into 1000 can miss their sampling times. This causes skipped samples. A continuous timer avoids this problem by keeping all periods aligned without resetting.
Mircea Neacsu 4 days ago    
First, please don't consider me stubborn or obstinate for continuing to answer. It's just that the discussion seems interesting and worth continuing.

Second, let's agree that the OP was vague. It is not clear in what environment the code would run and what are the restrictions.

Now getting to your points:

Both our solutions depend on scheduler behavior. In your solution, you increment the timer count in the ISR (presumably very accurately) but the loop reading code runs when the other tasks have finished or when scheduled by the task scheduler. IMO accuracy is the same.

For the if code, it so happens that intervals specified in the OP where divisors of 1000. I worked with what was given.

One more comment: in real world, nothing is instantaneous. The data reading function (ReadDataCh) probably takes some time. Moreover, in many systems you have to send a command to initiate the data acquisition and later on read the data when available. Imagine how different the code would be if data comes from and ADC where you have to select a channel and send a "start conversion" command and latter on read the sample value when available.
In embedded systems, limited hardware resources are to be expected. The previously suggested approaches would utilize the hardware inefficiently by using wait, causing high CPU load during the remaining time, which could result in insufficient processing time. This could lead to missed sampling times or significant fluctuations.

The following problems or drawbacks should be avoided:

1. The sampling accuracy heavily depends on the precision of the wait(1) call. When 100 channels are checked afterward, each with potentially different sampling periods and hardware accesses, the operations are not deterministic, and the overall timing may fall out of sync. The allowed timing deviations were not specified, but they would be difficult to predict and control.

2. A significant amount of time is wasted during the waiting period, where other tasks could be executed instead.

3. Periods that are not divisible by 1000 would be skipped when the cycle resets to 1, leading to potential sampling errors.

In a typical embedded system, only a limited number of hardware timers are available, so assigning a dedicated hardware timer to each of the 100 channels is not practical. It is necessary to multiplex tasks, while the system should remain flexible enough to support different sampling periods for each channel. At the same time, the CPU should be utilized optimally, minimizing CPU load.

No information is provided on the required or achievable sampling accuracy, and this would need to be defined and verified to ensure that the system is capable of meeting these requirements. While some deviations in sampling intervals may be acceptable for typical embedded applications, it’s important to assess this upfront.

One option is to use a timer interrupt as the clock base for managing multiple channels with different sampling intervals, instead of wasting time with wait. A hardware timer with the smallest required interval (e.g., 1 ms) could increment a counter within the timer ISR. This counter would then serve as a base for determining the different sampling intervals of the 100 channels. Each channel’s last sample time would be tracked, allowing the system to decide if a channel should be sampled again based on the timer value and the configured sampling period.

A concept with a central timer and multiple channels could be structured roughly as follows:
C
volatile unsigned long timerCount = 0;  // Global counter

// ISR for the timer
void Timer_ISR() 
{
    timerCount++;  // Increment timer by 1 ms
}

void main_loop() 
{
    const int numChannels = 100;
    int channelLastSample[numChannels];  // Last sample time for each channel

    while (1) {
        // Critical section 
        // disableInterrupts
        unsigned long currentTimerCount = timerCount;  // Store value locally
        // enableInterrupts

        for (int i = 0; i < numChannels; ++i) {
            int samplePeriod = DataCh[i].SamplePeriod;
            if (currentTimerCount - channelLastSample[i] >= samplePeriod) {
                ReadDataCh(i);  // Sample the channel
                channelLastSample[i] = currentTimerCount;  // Update last sample time
            }
        }

        // Other tasks...
    }
}

Since race conditions could occur within an ISR, additional measures like atomic operations or temporarily disabling interrupts might be necessary.
 
Share this answer
 
v3
Comments
0x01AA 4 days ago    
+5

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900