|
|
View previous topic :: View next topic |
Author |
Message |
coldrestart
Joined: 04 May 2021 Posts: 3
|
dspic30F4011 - MPY instruction fractional math for motor pwm |
Posted: Thu May 06, 2021 11:33 am |
|
|
Hello,
I programmed a dspic30F4011 based on the application note AN984.
To explain this more in detail, I have a sinus table, with 256 int16 values, from -32768 as the lowest point of the sinus and 32767 as the highest point.
The range of my pwm setup is 0-1048, so the int16 value in the sinus table for 100% must be scaled to +624/-624, afterwards I add an offset of 625.
All is working fine, except I want to change the amplitude. As I am in an interrupt service routine, I cannot make use of float math.
I tried to use standard integers to scale the amplitude value, still it will take too much time. The idea was to multiply the int16 of the sinus table with 1000, so we get 32767000, and the divide by 62000 = to get 528.
Then I could play with the "1000" value to scale the input.
But this doesn't work.
In the application note, they do the explanation about fractional mathematics, where an int of 0x7FFF is to be considered as 0.999
By using the "MPY" function in assembly, you can scale down a value quite fast.
I cannot find the MPY function in the manual of CCS, and I dont know how I can write this function in inline-assembly code.
Does anyone has done this? Or has an example function?
Code: | //78µs interrupt
#INT_TIMER4
void timer4_isr(VOID)
{
i_pwmLoc += FreqOffset; // 409 = 50Hz - now fixed 256 with a standard timer
//update de pwm modules
PwmTemp1 = i_pwmLoc;
PwmTemp2 = i_pwmLoc + PHASE_SHIFT_120;
PwmTemp3 = i_pwmLoc + PHASE_SHIFT_240;
Phase1 = (PwmTemp1)>>8; //index are 256 steps
Phase2 = (PwmTemp2)>>8;
Phase3 = (PwmTemp3)>>8;
//Here the lookup is done, my value of 62 is fixed, so always 100% duty
Scaled1 = (SineTable[Phase1] / 62) + 625;
Scaled2 = (SineTable[Phase2] / 62 ) + 625;
Scaled3 = (SineTable[Phase3] / 62 ) + 625;
//pwm, group, time
//PWM: PWM module - > STEEDS 1
//Group: PWM GENERATOR 1, 2 or3
//TIME: The value set in the duty cycle REGISTER
set_motor_pwm_duty (1, 1, Scaled1) ;
set_motor_pwm_duty (1, 2, Scaled2 ) ;
set_motor_pwm_duty (1, 3, Scaled3 ) ;
} |
Thanks in advance,
Coldrestart. |
|
|
alan
Joined: 12 Nov 2012 Posts: 357 Location: South Africa
|
|
Posted: Thu May 06, 2021 11:36 pm |
|
|
Just something that I use extensively to do just this.
If you want to multply by 1000, instead use 1024, uses just a shift
Then instead of dividing by 62000, divide by 65535, again a shift
Value are then 512, instead of your 528.
This is best done with a union where you just take the high word of the int32
You could then add another term where you multiply by 16 and divide by 32768, to give 527.
Much faster than divide and if you don't need the accuracy, you get within 1 or 2 counts of your intended value. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Fri May 07, 2021 12:00 am |
|
|
You get MUL used automatically if you multiply. Just as you are already
using the DIV instruction to perform your /62.
However I'd suggest reducing the number of instructions by using a
scale 'value'.
Currently you /62 to get the required output value from your look up
table. If you want to have the output reduced by (say) a couple of percent,
then the answer would be to use /63, instead of /62. Now if you stored the
'62' in a variable, and had the code outside in the main that want to set the
'output' scale, change what is stored in this, then no more operations are
required in the interrupt, and you just change this factor to change the
output scale.
So if you had a lookup array, something like:
Code: |
int scales[] = {12000,6200,3100,2066,1550,1240,1033,885,775, \
688,620,563,516,476,442,413,387,364,344,326,310,295,281,269 \
258,248,238,229,221,213,206,200,193,187,182,177,172,167,163 \
158,155,151,147,144,140,137,134,131,129,126,124,121,119,116 \
114,112,110,108,106,105,103,101,100,98,96,95,93,92,91,89,88, \
87,86,84,83,82,81,80,79,78,77,76,75,74,73,72,72,71,70,69,68, \
68,67,66,65,65,64,63,63,62,62,61,60,60,59,59,58,57,57,56 };
|
Then in your main when the factor to be used changes, you just load
the new scale to be used form this into a variable called something like
div_factor. So if you want 100%, you would load the 100th entry from
this which is 62. If you wanted 50%, then you would be loading the
fiftieth entry which is 124. You maths in the interrupt remains as it is
except it uses 'div_factor' instead of '62'.
Now there is an issue, since your existing table cannot support going above
100%. Think about it. You would get a value over 1250 for this. So if you
need to be able to generate outputs above 100%, you actually would need
to reduce the values stored in your lookup table by (say) 10%, and
alter the hardware so that 100% out occurs at an output of +/-1120. Then
the output would be able to go above 100%. Currently it can't.
There seems to be a discrepancy in what you are saying and doing. You say
the sine table has values from -32767 to 32767, and that the PWM can
range from 0 to 1048, and that you add 625. However to get 1048 from
+/-32767, would require division by 64, not 62. The offset would also need
to be 524 rather than your current 625.
Your use larger values solution requires switching to int32 arithmetic
instead of int16. A lot more cycles, since the carries have to then be
handled and propagated. The processor does not have an int32 'mul' as
a single instruction. You'd also need to store and load int32 values. result
a lot more time involved. |
|
|
coldrestart
Joined: 04 May 2021 Posts: 3
|
|
Posted: Fri May 07, 2021 10:00 am |
|
|
The lookup table with the scaling value pre-calculated is indeed the fastest way. I tested it and it works perfect.
Also the idea of shifting the bit's to divide is a good solution, but I will use the lookup table, as this method requires the least amount of calculations in my isr.
I need to admit that in my first post, I wasn't consistent in the values that I mentioned.
The full pwm input range is 0-1250, I kept 1048, as a window, to limit my full output range.
As I create the "middlepoint" in the center, I use 625 (center of 1250), with an offset of +/- 524. Resulting in a min value of 101 and a max value of 1149.
Thanks for the help!
Code: | //78us interrupt
#INT_TIMER4
void timer4_isr(VOID)
{
i_pwmLoc += FreqOffset; // 409 = 50Hz - now fixed 256
//update de pwm modules
PwmTemp1 = i_pwmLoc;
PwmTemp2 = i_pwmLoc + PHASE_SHIFT_120;
PwmTemp3 = i_pwmLoc + PHASE_SHIFT_240;
Phase1 = (PwmTemp1)>>8; //index
Phase2 = (PwmTemp2)>>8;
Phase3 = (PwmTemp3)>>8;
Scaled1 = (SineTable[Phase1] / ScaleSineTable[ScaleLookupPntr]) + 625;
Scaled2 = (SineTable[Phase2] / ScaleSineTable[ScaleLookupPntr]) + 625;
Scaled3 = (SineTable[Phase3] / ScaleSineTable[ScaleLookupPntr]) + 625;
//pwm, group, time
//PWM: PWM module - > STEEDS 1
//Group: PWM GENERATOR 1, 2 or3
//TIME: The value set in the duty cycle REGISTER
set_motor_pwm_duty (1, 1, Scaled1) ;
set_motor_pwm_duty (1, 2, Scaled2 ) ;
set_motor_pwm_duty (1, 3, Scaled3 ) ;
} |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Fri May 07, 2021 11:05 am |
|
|
The numbers didn't quite make sense...
Good. Glad it works. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|