PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Fixed point math for dsPIC33F
Hello all, I am trying to wrap my head around fixed point math as I came to know that the dsPIC does not have a socalled 'FPU' on it ([edit] or does it? I am not sure about that). I have a few basic questions regarding this. Question#1:So, I am using an ADC value to reconstruct a sensed signal. It turns out that I will have to do the following to get the actual signal value float v_mes, v_act; v_mes = (adc_val*3.3)/4096; v_act = (2.239*v_mes + 3.1465)*1000; //using curve fit to calculate the actual voltage
I understand that the above computations cannot be processed on my dsPIC. So, I am considering to go towards fixedpoint computation. Is my understanding, right? Question#2 Now the next question is regarding variable overflow issues. I am trying to reconstruct the signal which is in kilovolts and would like to use it to compute errors, which eventually make their way to a PI controller. I have read a few documents and understood that the place where we choose our decimal point is crucial to ensure that there aren't any variable over range errors. If I am measuring a 3000 volts signal(using a very large voltage divider) to compute the error from a set reference, which goes through a PI implementation, to generate a duty signal. I will not be able to take Q12.4 or Q13.3 formats as they affect the integer part overflow and decimal part precision (a real pickle). So, do I switch to Q16.16 formats or start using a percentage value(per unit values), to compute all the variables? Thank you in advance.
post edited by PICrukie  2019/06/12 11:57:56

du00000001
Just Some Member
 Total Posts : 2764
 Reward points : 0
 Joined: 2016/05/03 13:52:42
 Location: Germany
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 13:19:06
(permalink)
Your equations would fit a 3.1465 .. 10.535 kV signal. Is this really the range you want to measure? BTW: any PIC is capable of float math  it's just slow without an FPU. But I have some doubt that your current equations would yield a correct result.
PEBKAC / EBKAC / POBCAK / PICNIC (eventually see en.wikipedia.org)

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 14:15:49
(permalink)
☄ Helpfulby PICrukie 2019/06/12 15:13:11
The dsPIC will process fixed point math a ton faster than floating point math in general and especially so if you use the DSP functionality. The last time I did this, I used the builtin compiler functions to access the 40bit accumulators (excerpt of a PI regulator): volatile register int16_t aReg asm("A"); volatile register int16_t bReg asm("B"); // Grab the Ui and shove it in the 'A' accumulator aReg = __builtin_lac(pireg>Ui, 0); // Use the built in MAC to compute the integral output // aReg = aReg + (Ki * error) aReg = __builtin_mac(aReg, pireg>Ki, pireg>error, 0, 0, 0, 0, 0, 0, 0, 0);
// We are using a Q11 multiplier for the Kp to allow a larger value. // This gets us a Q26 number when we multiply, so we left // shift it 4 bits to scale the value bReg = __builtin_mpy(pireg>error, pireg>Kp, 0, 0, 0, 0, 0, 0); bReg = __builtin_sftac(bReg, 4); // Add the Ui and Up. Do it in the accumulator for automatic saturation bReg = __builtin_addab(aReg, bReg); // Pull the values out of the accumulators, scale, and round them using the // system round setting (convergent) pireg>Ui = __builtin_sacr(aReg, 0); pireg>output = __builtin_sacr(bReg, 0);
I have always found the easiest way to do anything in fixed point math is to normalize all the inputs to per unit values. The only time I convert anything to regular (nonnormalized) values is when the values are sent out or displayed. The dsPIC ADC is actually set up to make it easy by offering fractional modes.

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 14:37:12
(permalink)
Thank you, @du00000001 for pointing out the scaling issue. I totally missed it. The correct scaling factor is obtained by dropping the 3.1465. [edit] Although this leads to a 7.3KV max voltage, the application circuit does not actually go that high. It is limited to around 3.5 KV.
post edited by PICrukie  2019/06/12 14:54:21

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 14:46:04
(permalink)
@bhave, thank you for the valuable reply. I have a few questions here. If I am converting the actual value to a per unit value, I will have to do the following: PU = actual/base. But if I do the division, uint16_t actual_value, base_value, pu_value;
uint16_t *rem;
pu_value = __builtin_divmodud(actual_value, base_value,*rem); Will the 'pu_value' not be zero? As I am dividing a smaller quantity by larger quantity (assuming base value to be greater than the actual value?). Also, is the above code snippet a recommended way to convert a value to its per unit equivalent? Thanks.
post edited by PICrukie  2019/06/12 15:00:24

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 16:55:10
(permalink)
☄ Helpfulby PICrukie 2019/06/12 17:58:54
Before normalizing any number using fixed point, you need to determine your fixed point number format. I generally use Q1.15 since this is natively supported by the dsPIC. This means that your base number is scaled by 2^15 and gives you a range of 1 to 0.99996948. Normalizing a number is easy otherwise. Just multiply it by your fixed point multiplier divided by your normalizing term. For example, normalizing a value of 20 to 60 would be: 20 * (2^15 / 60).

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/12 22:27:30
(permalink)
@bhave, that makes a ton of sense now! Some questions however remain. To best explain my question, let me use an example. In my first message, I wrote the following: float v_mes, v_act; v_mes = (adc_val*3.3)/4096; v_act = (2.239*v_mes)*1000; //using curve fit to calculate the actual voltage I now understood form your suggestion that the above code is not particularly efficient. Hence if I want to convert everything to say Q1.15 format, //Q1.15 example uint32_t actual_voltage; uint16_t temp,adc_value,adc_voltage; temp = (adc_value<<3); //since adc_val*(2^15/2^12), where 2^12 is adc max value in 12bit mode //multiplying with 108134 which is the 3.3 volt equivalent in Q1.15 adc_voltage = __builtin_mpy(temp,108134,0,0,0,0,0,0); //but 108134 is a 32bit number!! //multiplying with 73367 which is the 2.239 equivalent in Q1.15 actual_voltage = __builtin_mpy(adc_voltage,73367) //But __builtin_mpy needs both values in int (which is 16 bits long)
So, I won't actually be able to perform this computation due to one operand being 32bits long. Even if I switch to a Q4.12 format it doesnot help //Q4.12 example uint32_t actual_voltage; uint16_t temp,adc_value,adc_voltage; temp = (adc_value); //since adc_val*(2^12/2^12), where 2^12 is adc max value in 12bit mode. So no shifts needed? //multiplying with 13516 which is the 3.3 volt equivalent in Q4.12 adc_voltage = __builtin_mpy(temp,13516,0,0,0,0,0,0); //13516 is a 16bit number. so ok? //multiplying with 9170 which is the 2.239 equivalent in Q4.12 actual_voltage = __builtin_mpy(adc_voltage, 9170, 0, 0, 0, 0, 0, 0); //But __builtin_mpy needs both values in int (which is 16 bits long) but adc_voltage is 32bit
I am assuming I am doing something very wrong and my understanding of fixed point is not sound. Please comment. My problem is that I will not be able to perform the above multiplication as the numbers go into 32bit regimes and I won't be able to use multiplication. Or, is my math wrong? Thank you.
post edited by PICrukie  2019/06/12 22:47:32

du00000001
Just Some Member
 Total Posts : 2764
 Reward points : 0
 Joined: 2016/05/03 13:52:42
 Location: Germany
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 00:21:39
(permalink)
@ PICrukie For the moment, try to forget anything you know about Qx.y! Just answer the following questions:  What's the division ratio of your resistive divider ?
 Your ADC's reference voltage is 3.3 V. Correct?
 What resolution do you expect from v_act? Whole Volts? Fractions of Volts? If the latter: which fractions: binary (1/2, 1/4 etc.) or decimal (max. resolution maybe 0.1 V) ?
PEBKAC / EBKAC / POBCAK / PICNIC (eventually see en.wikipedia.org)

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 05:55:47
(permalink)
If your ADC reference is 3.3V, then temp = (adc_value<<3);
normalized it to 3.3V. Now, what does 3.3V mean to your system? For example, if my ADC input is a voltage divider and it was set up to divide 75V max down to 3.3V for the ADC, then the value above is also scaled to 75V since 3.3V on the ADC means 75V outside the resistor divider. Also remember that the dsPIC has two 40bit accumulators for working with normalized numbers with lots of extra headroom.

NorthGuy
Super Member
 Total Posts : 5479
 Reward points : 0
 Joined: 2014/02/23 14:23:23
 Location: Northern Canada
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 06:23:29
(permalink)
Using builtin DSP instructions is only beneficial if you use them in a loop (such as for FIR filter). Otherwise, regular multiplication is just as fast, but doesn't require any dancing around as DSP instructions do, thus regular multiplication is likely to be faster.

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 09:17:36
(permalink)
@du00000001, here are the answers to your questions: 1) The resistive divider has 1/1000 division ratio. (100kOhm and 100MOhm ~ 9.99e4 ~ 1e3) 2)Yes, the ADC voltage is 3.3 volts. 3)I'd expect whole volts for v_act. For example, if the ADC value reads 1500, it would mean that, the v_act = (1500/4096)*3.3*2.239*1000 = 2705 volts. This is what I am trying to implement. This value is to be compared with the set point to get the error and the rest of the PI implementation follows.

Jim Nickerson
User 452
 Total Posts : 6016
 Reward points : 0
 Joined: 2003/11/07 12:35:10
 Location: San Diego, CA
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 09:33:17
(permalink)

du00000001
Just Some Member
 Total Posts : 2764
 Reward points : 0
 Joined: 2016/05/03 13:52:42
 Location: Germany
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 10:09:52
(permalink)
☄ Helpfulby PICrukie 2019/06/13 10:19:03
OK  for a measurement range of 3.3 kV, 12 Bit ADC values and a resolution of 1 V for v_act, 1 LSB equals 3300/4096 V resp. 825/1024 V. Thus v_act = adc_val * 825 / 1024; // Or  with rounding v_act = (adc_val * 825 + 512) / 1024;
Some further optimizations could be done  gaining speed at the expense of some precision, but this is a somewhat different topic. BTW: The result would be Q16.0. You would still require an int32_t propagation for the intermediate result, but this would require a single multiplication 16x16, followed by a simple shifting operation. While the dsPIC won't require further optimization, I did similar things on a PIC16 which was much trickier as it's just an 8 bit controller and 16x16 operations already require some "effort". My recommendation: Don't stare at the magic Qsomething  attack the problem from the "transformation side": ou've got an input range that you want to transform to some output range. Provided the relation is linear, it's always just 1 multiplication and 1 division. Best to do with divisions by a power of 2  equal to some shifting right.
PEBKAC / EBKAC / POBCAK / PICNIC (eventually see en.wikipedia.org)

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/13 10:12:46
(permalink)
☄ Helpfulby PICrukie 2019/06/13 10:19:06
For example, if the ADC value reads 1500, it would mean that, the v_act = (1500/4096)*3.3*2.239*1000 = 2705 volts. If I understand this correctly, that means the ADC PU value is scaled by (3.3 * 2.239 * 1000). I.E. 4096 in the ADC register means 7388.7 volts. So, your PI regulator setpoint would be multiplied by (2^15 / (3.3 * 2.239 * 1000)) to normalize it the same as the normalized ADC output. Edit: Or do it the way du00000001 describes ... there are many ways to accomplish this
post edited by bhave  2019/06/13 10:16:28

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 00:30:00
(permalink)
Thank you @du000000001 and @bhave for your valuable insights. I used @bhave's code snippet and adapted it to work for the application I'm trying to work with. Below is the code snippet of what I did: volatile register int16_t aReg asm("A"); volatile register int16_t bReg asm("B"); uint16_t vact = 2800*(32768/3.3*2.239*1000); //normalizing the set point uint16_t adcval; uint16_t q4_12kp = 16384; //kp value in Q4.12 uint16_t q4_12ki = 20480; //ki value in Q4.12 uint16_t error; uint16_t integral_sum; uint16_t prop_effort; uint16_t integral_effort; uint16_t control_effort; uint16_t PWM_tmr_load = 200; uint16_t duty;
uint16_t duty_compensate(void) { adcval = get_adc_val(); adcval = (acdval<<3); //converting to Q15
error = vactadcval; aReg = __builtin_lac(integral_effort,0); //load integral effort to A acc aReg = __builtin_mac(aReg, q4_12ki, error); //aReg is a Q27 quantity aReg = __builtin_sftac(aReg, 3); //converting to Q30 bReg = __builtin_mpy(error,q4_12ki); //bReg is a Q27 quantity bReg = __builtin_sftac(bReg, 3); //converting to Q30
bReg = __builtin_addab(aReg, bReg); //Auto saturation included integral_effort = __builtin_sacr(aReg,0); control_effort = __builtin_sacr(bReg,15);//right shift by 15 to convert to Q15 //control_effort now has normalized control effort in Q15 //multiply this with PWM_tmr_load to get the duty load value; bReg = __builtin_mpy(control_effort,PWM_tmr_load,0,0,0,0,0,0); duty = __builtin_sacr(bReg,15);//right shift by 15 to convert to Q0 (normal units) return duty; }
Alas! This didn't work really well atleast in the debug mode (I was trying to track the bit changes and data propogation within registers), and it didn't look right. So, in order to test the math, I did a simple multiplication of 3.45 (Q4.12) with 0.37(Q15). The issue is that the result in accumulator was nowhere close to what I computed by hand calculations (please see: 'fixed point math' attachment and debug report). The code I implemented is shown below: #include <stdlib.h> #include <stdint.h> #include<xc.h> int main(void) {
volatile register int result asm("A"); uint16_t val1, val2; val1 = 3.45*4096; //3.45 in Q4.12 val2 = 0.37*32768; //0.37 in Q15 result = __builtin_mpy(val1, val2, 0,0,0,0,0,0); result = __builtin_sftac(result, 12); //converting to Q15 while(1); }
Can anyone point out the mistakes I'm making in this code. I greatly appreciate the insights being put forth on this thread. I would greatly appreciate if anyone could help me crack this nut. Thanks in advance :)
Attached Image(s)

T Yorky
Super (Thick) Member
 Total Posts : 521
 Reward points : 0
 Joined: 2012/08/28 02:07:35
 Location: UK
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 03:10:40
(permalink)
@ PICrukie, See you're battling with fixed point integers... an example that may help.. Lets say the adc result (RSLT), which is 0..1023 represents 0 .. 56.8V (through an external divider/isol circuit). This is: RSLT  x 56.8V 1023 We do not have Fl Pt facility so.. lets scale the result. ie 56.8 > 568 /10 (lets tuck the div by 10 away for the moment). RSLT  x 568 1023 Also these numbers are a bit too small in resolution so lets work in 32bit. Make all vars/constants 32 bit. Now we can apply an identity > 1. This can be 1/1 or 5/5 or 100/100. But most useful in this example is 65536/65536 (=1) So... RSLT 65536  x  x 568 1023 65536 Now playing with the order of calcs.... RSLT 65536 x 568 1 x  x  1023 65536 Working in 32bit the 'central multiplier' becomes 36387.53 > rounded to 36388UL (note the UL for a 32bit const). The last divisor is simply a 16 bit shift right. In other words only use the top 16 bit result. Just remember the result (as selected by my example) is x10 ie 0..568 > 0..56.8 , but you could convert to a Q format, or normalised format of some kind. And to condense this into code, this is achieved by the __builtin_mulsu( RSLT, 36388) . The shift may not always be 16bit. This depends on the scale chosen. But a power of 2 is used for the simple divide. Trust this assists. T Yorky.

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 06:00:01
(permalink)
I don't have any hardware around right now to check this on, but when using the compiler builtin functions I remember having better luck verifying the result after pulling it out of the accumulator. Try: volatile register int16_t result asm("A"); uint16_t test, val1, val2; val1 = 3.45 * 4096; val2 = 0.37 * 32768;
result = __builtin_mpy(val1, val2, 0, 0 ,0 ,0 ,0 ,0); result = __buitin_sftac(result, 3); // shift left 3 bits, DSP expects result of mpy to be Q30 // Pull the value out of the accumulator, scale, and round using the system round setting test = __builtin_sacr(result, 0);
Then look at the contents of the 'test' variable to see if they are correct.

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 13:32:02
(permalink)
// Pull the value out of the accumulator, scale, and round using the system round setting test = __builtin_sacr(result, 0); @bhave, isn't the acuumullator storing the result in Q30? If we transfer the result from A to test in this situation, it will not be in Q15. Will it?

bhave
Starting Member
 Total Posts : 27
 Reward points : 0
 Joined: 2018/03/15 05:33:32
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 13:51:20
(permalink)
I apologize, I left out an important configuration. My code assumes that the CORCON IF bit is set to zero. This configures the DSP multiply to treat numbers as Q1.15 (from the 16bit and DSC Programmers Reference Manual, page 81): ... However, multiplies performed by DSP operations are different. In these instructions, data format selection is made by the IF bit (CORCON), and it must be set accordingly (‘0’ for Fractional mode, ‘1’ for Integer mode). This is required because of the implied radix point used by dsPIC30F/33F/33E fractional numbers. In Integer mode, multiplying two 16bit integers produces a 32bit integer result. However, multiplying two 1.15 values generates a 2.30 result. Since the dsPIC30F, dsPIC33F, and dsPIC33E devices use a 1.31 format for the accumulators, a DSP multiply in Fractional mode also includes a left shift of one bit to keep the radix point properly aligned.
post edited by bhave  2019/06/14 13:52:56

PICrukie
New Member
 Total Posts : 25
 Reward points : 0
 Joined: 2019/04/09 14:11:53
 Location: 0
 Status: offline
Re: Fixed point math for dsPIC33F
2019/06/14 14:29:18
(permalink)
@bhave, It worked! Not in the way you mentioned, but it worked! Your suggestion definitely helped though. I changed the CORCON's IF bit to integer mode ( rather than fractional as you pointed out), and the dsPIC seems to spit out the correct values. This is the code that worked: #include <stdlib.h> #include <stdint.h> #include<xc.h> int main(void) {
volatile register int16_t result asm("A"); int16_t test, val1, val2; val1 = 3.45 * 4096;//1.35*16384;//0x6ccc;//3.45 * 4096; val2 = 0.37 * 32768;//1.35*16384;//0x55c2;//0.37 * 32768;
CORCONbits.IF = 1; //Changes the operation from fixed point to integer
result = __builtin_mpy(val1, val2, 0, 0 ,0 ,0 ,0 ,0); result = __builtin_sftac(result, 12); // shift right 12 bits, result is now Q15
test = __builtin_sacd(result, 0); //Observe! This is not SACR!
while(1); }
So, because we are dealing with 'integer' numbers, the multiplier is working properly. However, what we are actually dealing with is fixed point numbers. So how doesn't the following make sense?This fixed point computation code is still giving erroneous results #include <stdlib.h> #include <stdint.h> #include<xc.h> int main(void) {
volatile register int16_t result asm("A"); int16_t test, val1, val2; val1 = 3.45 * 4096; val2 = 0.37 * 32768;
CORCONbits.IF = 0;
result = __builtin_mpy(val1, val2, 0, 0 ,0 ,0 ,0 ,0); result = __builtin_sftac(result, 3); // shift left 3 bits, DSP expects result of mpy to be Q30 test = __builtin_sacr(result, 0);
while(1); } Do you think there are some other bits in the CORCON register that have to be set correctly for this to give out the right values?
post edited by PICrukie  2019/06/14 14:30:52
