4

I am here asking how does one transfer a difference equation into a MCU? I have never done it personally and looking into this topic I was never able to find a good answer. Its usually used with Matlab but never a MCU.

I have a continuous transfer function:

$$LPF_A = \frac{1}{6.416\cdot 10^{-11}s^2+1.133\cdot 10^{-5}s+1}$$

Its a 2nd Order Butterworth filter with a \$F_c = 20\text{kHz}\$.

Using the Tustin method I have the discrete equation:

$$LPF_D=\frac{0.1441z^2+0.2881z+0.1441}{z^2-0.6777z+0.254}$$ With a sample of \$T_s = \$ 8e-6 seconds, in scientific E notation.

The difference equation:

\$Y\$ = Output

\$U\$ = Input

$$Y_i =0.1441U_{i}+0.2281U_{i-1}+0.1441U_{i-2}+0.6777Y_{i-1}-0.254Y_{i-2}$$

I know I have to make my ADC output = \$U_i\$ and initialize \$U_{i-1}\$, \$U_{i-2}\$, \$Y_{i-1}\$, \$Y_{i-2} = 0\$, But after that no idea.

I have no idea what to do with this after. I have always used Matlab's embedded ways of doing it but never have transferred a difference equation into a MCU before. Any help would be appreciated.

Edit: Here's the code based on what I think

  unsigned short x;
double Y_i[10] = {0,0,0,0,0,0,0,0,0,0};
double U_i[10] = {0,0,0,0,0,0,0,0,0,0};
double Y_i_1 = 0;
double Y_i_2 = 0;
double U_i_1 = 0;
double U_i_2 = 0;

void setup() {

  ADMUX |= 0xC0;
  ADCSRA |= 0x8F;
  ADCSRA |= 0x40;
  Serial.begin(9600);
}

ISR(ADC_vect) {
 x = ADCL << 2;
 x |= ADCH;
 ADCSRA |= 0x40;
}

void loop() {
  //Yi = 0.1441Ui + 0.2281Ui-1 + 0.1441Ui-2 + 0.6777Yi-1 - 0.254Yi-2 
  for (int i = 1; i < 4; i++){

    if (i == 1) {
       Y_i[i] = 0.144*x + 0.2281*U_i_1 + 0.1441*U_i_2 + 0.6777*Y_i_1 - 0.254*Y_i_2;
       U_i[i] = x;
    } else if (i == 2) {
      Y_i[i] = 0.144*x + 0.2281*U_i[i-1] + 0.1441*U_i[i-2] + 0.6777*Y_i[i-1] - 0.254*Y_i[i-2];
      U_i[i] = x;
    } else {
      Y_i[i] = 0.144*x + 0.2281*U_i[i-1] + 0.1441*U_i[i-2] + 0.6777*Y_i[i-1] - 0.254*Y_i[i-2];
      U_i[i] = x;
    }

  }

}

What happens after the iteration is done? It resets? thats it?

Leoc
  • 1,393
  • 1
  • 9
  • 21

2 Answers2

2

Maybe you are having trouble with the "advance time" step. It helps to separate the logic as follows:

  1. Declare your variables: \$Y_{i}\$, \$Y_{i-1}\$, \$Y_{i-2}\$, \$U_{i}\$, \$U_{i-1}\$ and \$U_{i-2}\$. Let's also initialize a counter \$i = 0\$, which represents the current value of the subscript in your variables (this counter is completely unnecessary, but helps getting across what we are doing). Here is how \$i\$ works: if \$i = 10\$, this means the variable for \$Y_{i-2}\$ currently holds \$Y_{8}\$. Initialize everyone to zero, but keep in mind \$Y_{i}\$ and \$U_{i}\$ are not valid yet - we'll deal with them in the next two steps.
  2. Acquire new data. That is, acquire \$U_{i}\$ from the ADC.
  3. Recalculate your output. You know how to do it. Store it in \$Y_i\$.
  4. Profit. Now all your variables are correct and reflect the system at the timestep \$i\$ (if this is your first pass, now \$i = 0\$). Do whatever you want with them.
  5. Now you have to advance time. This means updating your variables as if time passed. This is necessary for calculating the next timestep. To do this, have \$Y_{i}\$ become \$Y_{i-1}\$, and so on (I won't spoil everything you have to do). You'll notice old values of \$U\$ and \$Y\$ will be lost as time advances. Also increment \$i\$ to keep track of which sample we are talking about.
  6. Now, all your variables except \$Y_i\$ and \$U_i\$ reflect the state of the sistem at the current \$i\$ (if this is your first pass, now \$i = 1\$). \$Y_i\$ and \$U_i\$ are still unknown because you need another sample to calculate them.
  7. Wait for the time to sample again and go to step 2.
FrancoVS
  • 1,513
  • 14
  • 28
  • Let me know if I am wrong here but: Initialize all the variables to 0 Set Ui = ADC First Iteration: Yi = a Constant. Second Iteration: Did the Yi before becomes Yi-1 now? After I I iterate to Yi-2 what now? Is it done? can I not sample "Continuously" now? – Leoc Mar 31 '20 at 19:41
  • "Did the Yi before becomes Yi-1 now?" Yes. I did not understand your other question – FrancoVS Mar 31 '20 at 21:04
  • Sorry, the other question was stating what do you have you solve the second iteration. To me it looks like you need to solve the two iterations and thats it, what happens after that? – Leoc Mar 31 '20 at 21:45
  • When solving for the two iterations do I need to put this in a loop or can I first solve them then once I do put the equation in a loop? – Leoc Mar 31 '20 at 21:48
  • every iteration is the same: you acquire Ui, calculate a new Yi, and advance time. – FrancoVS Mar 31 '20 at 21:48
  • Edit main post with arduino code – Leoc Mar 31 '20 at 22:21
  • Is the code I have there correct ish? So essentially to solve the output we need to do solve the past 2 input and output values essentially and rinse and repeat? – Leoc Mar 31 '20 at 23:06
  • To me, `Y_i` and `U_i` shoudn't be arrays, and there should be no `for` and no `if/else` inside `loop()`: you just need to implement steps 2~7 in order. That being said, " to solve the output we need to do solve the past 2 input and output values essentially and rinse and repeat" is exactly the spirit. – FrancoVS Apr 01 '20 at 17:18
  • I see. The problem I am having is the rinse and repeat part. Do you go back to your initial conditions or is your Y_i_2 your new Y_i now? – Leoc Apr 01 '20 at 17:21
  • I'd just throw `Y_i_2` away: it became `Y_i_3`, which we will never need – FrancoVS Apr 01 '20 at 17:47
  • So essentially start from the beginning again. You dont calculate U_i at time zero right (i = 0)? You would want to start it at i =i ? – Leoc Apr 01 '20 at 18:01
  • in my example, the first sample is `U_0`, and the first output is `Y_0`, but you could change that, of course. Also, right now I think jDAQ's answer might be cleared than mine – FrancoVS Apr 01 '20 at 18:41
  • Appreciate the patience you have with me. I am starting to get it more now. If i == 0 to current value then when i == 1 how do you evaluate it when it has 2 delays like U_i_2 and Y_i_2, I know Y_i_1 = Y_i same with the input but how do you go about that? – Leoc Apr 01 '20 at 18:45
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/106205/discussion-between-francovs-and-pllsz). – FrancoVS Apr 01 '20 at 19:00
1

Well, every iteration update your variables like this:

$$ U_{i-2} \leftarrow U_{i-1}$$ $$ U_{i-1} \leftarrow U_{i}$$ $$ U_{i} \leftarrow \text{curent ADC reading}$$ $$Y_{i-2} \leftarrow Y_{i-1}$$ $$Y_{i-1} \leftarrow Y_i$$ $$Y_i \leftarrow \text{calculate current output}$$

In you code you are using double variables for precision, but, for the Arduino Uno and other that use AVR architecture microcontrollers double is the same as float. If you intend on keep using double, notice you will not have the desired precision.

Also, if you are using the Arduino Uno you will not be able to do many floating point operations per cycle, someone got a figure of 9 us per operation (except division), so just the part where you calculate your output \$Y_i\$ would use 5 multiplications and 5 additions, taking 90 us. Your filter is designed for a sampling time Ts= 8e-6 seconds, so you would not be able to finish the first floating point multiplication before getting your second reading from the ADC, and would have done 10 more readings before calculating the first output \$Y_i\$, long story short, the microcontroller will not be able to keep up and run that filter at that speed.

So you are way over your budget in frequency, if you where doing only that calculation, you would be barely able to achieve a 10 kHz frequency.

Finally, you could improve your code in two ways, first

//you do not need all those values, 
//from your expression for the filter, you could use just this
//double Y_i[10] = {0,0,0,0,0,0,0,0,0,0};
//double U_i[10] = {0,0,0,0,0,0,0,0,0,0};
double Y_i[3] = {0,0,0};
double U_i[3] = {0,0,0};



//not needed, just load the values you want as initialization in the array above 
//double Y_i_1 = 0;
//double Y_i_2 = 0;
//double U_i_1 = 0;
//double U_i_2 = 0;

notice that

  • \$ U_{i-2}\$ = U_i[2]
  • \$ U_{i-1}\$ = U_i[1]
  • \$ U_{i}\$ = U_i[0]
  • \$Y_{i-2}\$ = Y_i[2]
  • \$Y_{i-1}\$ = Y_i[1]
  • \$Y_{i}\$ = Y_i[0]

And that filter expression with a bunch of if could be just

void loop() {

    U_i[2] = U_i[1];
    U_i[1] = U_i[0];
    U_i[i] = x;
    Y_i[2] = Y_i[1];
    Y_i[1] = Y_i[0];
    Y_i[0] = 0.144*U_i[0] + 0.2281*U_i[1] + 0.1441*U_i[2] + 0.6777*Y_i[1] - 0.254*Y_i[2];
    //send or store any of the values that you need, 
    //Y_i[0] will always have the latest update of the filter, and so on...


    //either put this filter in some non-critical interruption to execute it with the desired periodicity (best way) 
    //or use some precise delay here right after calculating the filter inside the loop
    delayMicroseconds(50);

}


jDAQ
  • 2,517
  • 1
  • 9
  • 19
  • Why did you make this a community wiki? – pipe Mar 31 '20 at 19:01
  • So people can make it more complete with information regarding how to deal with the real life problems of implementing the algorithm, specially regarding the very limited FPU capability of the Arduino the OP is going to use and the errors due to quantization. – jDAQ Mar 31 '20 at 19:47
  • For people downvoting, either point out the problem or feel free to add/correct things in the answer... – jDAQ Mar 31 '20 at 20:51
  • I didn't see this edit and it was extremely helpful thank you. I have a few questions. When you say I am over budget for frequency, I understand why based on what you said, then how would one find out if a microcontroller is capable of needed frequency then? The way I did it was Arduino nano has a clock speed of 16MHz in the ADC register prescale it with 128, so 16MHz/128 = 125KHz, I would imagine the ADC is now sampling at Ts = 1/125KHz. If you are saying the loop() function is operating at a different speed then would using a timer ISR and shoving those equations in it would fix it? – Leoc Apr 01 '20 at 17:59
  • "When you say I am over budget for frequency, I understand why based on what you said, then how would one find out if a microcontroller is capable of needed frequency then?' this can be a question itself, some similar ones are https://electronics.stackexchange.com/questions/151550/what-is-capacity-in-the-context-of-scheduling-algorithms-in-real-time-systems/158184#158184 https://electronics.stackexchange.com/questions/137742/need-verification-of-real-time-concepts – jDAQ Apr 01 '20 at 23:02
  • Regarding the second part, that was not what I said, I said the CPU of an Arduino Uno takes 9 us to calculate a * or + using floating points. It does not matter at what frequency your ADC is running, the minimum amount of time to calculate `Y_i[0]` will already be close to 100 us, and if that was the only code being executed (which is not) the maximum frequency at which your code would run is $$ f = \frac{1}{100 \mu \text{s}} = 10 \text{kHz}$$. But yeah, even if you change your sampling time there is no way to make this filter work on a Arduino Uno. – jDAQ Apr 01 '20 at 23:09