0
\$\begingroup\$

I have been developing a C language software for software oscilloscope which is part of a control software. From timing point of view the software consists of two execution loops.

The first execution loop (let´s say "fast loop"- FL) is interrupt service routine (ISR) for the "end of A/D conversion" interrupt. This ISR is invoked with \$100 \mu\mathrm{s}\$ period and is responsible for recording samples of given physical quantities into associated circular buffers which creat core of the oscilloscope. It is also responsible for pushing samples into another circular buffers for FIR filter calculations.

The second execution loop (let´s say "slow loop" - SL) is a FreeRTOS task which is executed after 8 executions of FL. This loop is responsible for dividing the oscilloscope circular buffers into the "pre-trigger" and "post-trigger" parts based on commands comming from software running on the PC, storing the osciloscope records into the external flash memory and FIR filters calculations based on values in the associated circular buffers.

enter image description here

I have been thinking about robust solution for exchanging data between the FL and SL and vice versa. I need to ensure:

a) as soon as the SL starts calculation of the FIR filters these calculations has to be done with 8 values gathered in the last 8 executions of the FL i.e. it has to be ensured that despite the FL is still running and periodically interrupting execution of the SL and has new samples the SL still uses the same "snapshot" of the 8 samples (which was valid at the beginnig of SL execution) for whole SL execution time.

b) as soon as the SL decides to change the division between "pre-trigger" and "post-trigger" (based on command comming from the PC) by changing position of a dividing point the dividing point position is allowed to change at the beginning of next FL execution despite the fact that the request could come during FL execution.

Can anybody give me an advice how to solve that? Thanks in advance for any suggestions.

\$\endgroup\$
1
  • \$\begingroup\$ it is unclear what you need help with ... the point b) needs some punctuation to make it understandable \$\endgroup\$
    – jsotola
    Jun 12, 2020 at 19:39

1 Answer 1

1
\$\begingroup\$

The a) problem is trivial, I can't understand the b) problem.

Add a):

As you already mention a circular buffer, you will need a wr_pointer and rd_ponter. Now, when you update new sample, the wr_pointer is incremented in the ISR routine (FL): wr_pointer = (wr_pointer + 1) AND mask

buffer[wr_pointer] = ADC_value
wr_pointer = (wr_pointer + 1) AND mask 

When the (wr_pointer - rd_pointer) AND mask >= 8 then you execute the SL, this can be done in main cycle, not need to have an additional ISR. You then read from buffer and increment the rd_pointer - only here. rd_pointer = (rd_pointer + 1) AND mask. The wr_pointer can change its value meantime, but it doesn't care.

if ((wr_pointer - rd_pointer) AND mask) >=8{
    for .... 8 x read 8 sample
    {
       value[i] = buffer[rd_pointer]
      rd_pointer = (rd_pointer + 1) AND mask
    }
}

The mask is a size of buffer-1 , it's a power of 2. For example 8k bufer if 8192 cells, hence mask is 8191. At least that's how do I implement circular bufer.

The only caveat is that a wr_pointer shall be a variable type that is written all in one piece, for example a 8-bit MCU can split this into more values. It shall write the whole value in a single write.

EDIT:

ad 2)

You need to have a single whole buffer that has size of 2^N cells (power of two) to simplify the relative positions calculation with masking. You can calculate anything you want by simply using an "AND mask" operation after the arithmetic operation.

So let's assume that you have a SL thread that initially computes a sub-buffer of size 8, then in the middle of computation this is changed to 10:

 divisions_temp = divisions
 if ((wr_pointer - rd_pointer) AND mask) >=divisions_temp{
    for 1 to divisions_temp  // read divisions_temp samples
    {
       value[i] = buffer[rd_pointer]
      rd_pointer = (rd_pointer + 1) AND mask
    }
}

The same rule applies here. You may need to put some semaphore if the variable is not written all in one piece - this may happen if this variable is received though a communication directly. But if it is received consistently, let's say the communication routine receives bytes and then fills a structure of variables, you can copy the whole struct to another one, when the comm. is complete and without errors - let the comm. routine is a different thread. You can even place a semaphore when you copy the struct and you clear it when the copy process is finished. You look for this semaphore in FL thread, before using divisions variable.

EDIT2:

But let' assume your processor is 32-bit and the array indexes as well the divisions variable is also 32-bit, then the CPU will write all these variables in a single instruction, no need to worry and use semaphores.

\$\endgroup\$
6
  • \$\begingroup\$ thank you for your reaction. As far as I understand correctly your idea you suggest to read in the 8 samples from the circular buffer in the FL into another temporary array in the SL as soon as the FL collects them i.e. SL execution is started. The requirement that SL uses the last 8 samples gathered by the FL will be fulfilled by the fact that SL reads in the samples before the FL overwrites them (that is 100 % feasible because the SL has to in the worst case read in at least one sample during \$100\mu\mathrm{s}\$). Is my understanding correct? \$\endgroup\$
    – Steve
    Jun 12, 2020 at 11:34
  • \$\begingroup\$ As far as the b) problem I have attempted to reformulate my statement. \$\endgroup\$
    – Steve
    Jun 12, 2020 at 11:43
  • \$\begingroup\$ @Steve You can do whatever you want, no need to copy into another one, it's just an example how to read values from circular buffer, you can even read and overwrite the filtered value. I will edit the answer for the B part. \$\endgroup\$ Jun 12, 2020 at 12:34
  • \$\begingroup\$ I am sorry. I have probably explained the b) problem in wrong manner. I have been thinking about how to ensure synchronization of data exchange between SL and FL. The problem here is that I need to ensure that the change of dividing point is allowed to be applied only at the beginning of FL execution despite the fact that the new value has been sent by the SL e.g. in the middle of execution of the FL. My problem is not in implementation of circular buffer with variable dividing point. \$\endgroup\$
    – Steve
    Jun 12, 2020 at 13:16
  • \$\begingroup\$ @Steve divisions_temp2=divisions, you put this in FL ISR and divisons_temp = divisons_temp2 in SL thread. The SL thread will finish with old divisons_temp settings, then it will start new cycle with divisons_temp2 settings. \$\endgroup\$ Jun 12, 2020 at 13:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.