SPWM can mean anything based on who you ask, but I basically modulate the PWM value based on a sine lookup table. This is because even though I'm now just skipping the values through in 6 steps, I originally planned to interpolate inbetween and will do it soon... (I had the sine interpolation in place first, it works very well at modest and high speeds, extremely smooth and quiet running; but in it's most simple form, it was problematic when running extremely slowly (think about 1Hz), so I just removed it.)
Right now, I simply do it in 6 steps based on the current hall position, so the field direction wiggles around 60 and 120 degrees instead of the "optimum" 90 degrees. This keeps the motor running automatically, and the applied amplitude basically configures the current and hence torque (and that way, acceleration). So I have a PID loop around the speed, outputting the amplitude. Speed is measured by the time between the hall state changes.
Works so well that I haven't revised my code for a long time, just running it in the application doing gazillion of other things, but now I happen to need sub-hall-step-precision positioning so need to revise it soon...
Sorry, this code doesn't look very nice, was done in hurry. I stripped down some unimportant details, but just to give the idea what performs well even though very simple.
const int hall_loc[8] =
{
-1, // 000 ERROR
1, // 001
3, // 010
2, // 011
5, // 100
0, // 101 Middle hall active - we'll call this zero position
4, // 110
-1 // 111 ERROR
};
#define PH120SHIFT (1431655765UL) // 120 deg shift between the phases in the 32-bit range.
#define PH90SHIFT (1073741824UL) // 90 deg shift between the phases in the 32-bit range.
#define PH45SHIFT (PH90SHIFT/2)
const int base_hall_aims[6] =
{
0,
PH120SHIFT/2,
PH120SHIFT,
PH120SHIFT+PH120SHIFT/2,
PH120SHIFT*2,
PH120SHIFT*2+PH120SHIFT/2
};
#define HALL_ABC() ((GPIOB->IDR & (0b111<<6))>>6)
#define HALL_LOC() (hall_loc[HALL_ABC()])
/*
The sine table is indexed with 8 MSBs of uint32; this way, a huge resolution is implemented for the frequency,
since the frequency is a term added to the indexing each round. <-- this comment was written when interpolating between hall updates :)
*/
const int sine[256] =
{
0,804,1607,2410,3211,4011,4807,5601,6392,7179,7961,8739,9511,10278,11038,11792,
12539,13278,14009,14732,15446,16150,16845,17530,18204,18867,19519,20159,20787,21402,22004,22594,
23169,23731,24278,24811,25329,25831,26318,26789,27244,27683,28105,28510,28897,29268,29621,29955,
30272,30571,30851,31113,31356,31580,31785,31970,32137,32284,32412,32520,32609,32678,32727,32757,
32767,32757,32727,32678,32609,32520,32412,32284,32137,31970,31785,31580,31356,31113,30851,30571,
30272,29955,29621,29268,28897,28510,28105,27683,27244,26789,26318,25831,25329,24811,24278,23731,
23169,22594,22004,21402,20787,20159,19519,18867,18204,17530,16845,16150,15446,14732,14009,13278,
12539,11792,11038,10278,9511,8739,7961,7179,6392,5601,4807,4011,3211,2410,1607,804,
0,-804,-1607,-2410,-3211,-4011,-4807,-5601,-6392,-7179,-7961,-8739,-9511,-10278,-11038,-11792,
-12539,-13278,-14009,-14732,-15446,-16150,-16845,-17530,-18204,-18867,-19519,-20159,-20787,-21402,-22004,-22594,
-23169,-23731,-24278,-24811,-25329,-25831,-26318,-26789,-27244,-27683,-28105,-28510,-28897,-29268,-29621,-29955,
-30272,-30571,-30851,-31113,-31356,-31580,-31785,-31970,-32137,-32284,-32412,-32520,-32609,-32678,-32727,-32757,
-32767,-32757,-32727,-32678,-32609,-32520,-32412,-32284,-32137,-31970,-31785,-31580,-31356,-31113,-30851,-30571,
-30272,-29955,-29621,-29268,-28897,-28510,-28105,-27683,-27244,-26789,-26318,-25831,-25329,-24811,-24278,-23731,
-23169,-22594,-22004,-21402,-20787,-20159,-19519,-18867,-18204,-17530,-16845,-16150,-15446,-14732,-14009,-13278,
-12539,-11792,-11038,-10278,-9511,-8739,-7961,-7179,-6392,-5601,-4807,-4011,-3211,-2410,-1607,-804
};
#define PID_I_MAX 2000000000LL
#define PID_I_MIN -PID_I_MAX
#define MIN_MULT_OFFSET 10
void tim1_inthandler()
{
static int mult = 0;
static int currlim_mult = 255;
static int32_t cnt = 0;
static int expected_next_hall_pos;
static int cnt_at_prev_hall_valid = 0;
static int cnt_at_prev_hall;
static int prev_hall_pos;
static int prev_ferr = 0;
static int f = 0;
static int pid_f_set;
static int64_t pid_integral = 0;
TIM1->SR = 0; // Clear interrupt flags
int hall_pos = hall_loc[HALL_ABC()];
if(hall_pos == -1) hall_pos = prev_hall_pos; // glitchy value -> assume previous
int reverse = (pid_f_set<0)?1:0;
if(hall_pos == expected_next_hall_pos)
{
// We have advanced one step in the right dirction
// calculate the frequency from the delta time.
if(cnt_at_prev_hall_valid)
{
f = (PH120SHIFT) / ((cnt-cnt_at_prev_hall));
}
else
{
f = 0;
}
cnt_at_prev_hall_valid = 1;
cnt_at_prev_hall = cnt;
}
else // We are lost
{
lost_count++;
cnt_at_prev_hall_valid = 0;
f = 0;
}
prev_hall_pos = hall_pos;
expected_next_hall_pos = hall_pos;
if(!reverse) { expected_next_hall_pos++; if(expected_next_hall_pos > 5) expected_next_hall_pos = 0; }
else { expected_next_hall_pos--; if(expected_next_hall_pos < 0) expected_next_hall_pos = 5; }
// Ramp the speed setpoint.
int next_pid_f_set = (spi_rx_data.speed*100);
if(next_pid_f_set > pid_f_set) pid_f_set+=256;
else if(next_pid_f_set < pid_f_set) pid_f_set-=256;
if((next_pid_f_set - pid_f_set) > -256 && (next_pid_f_set - pid_f_set) < 256) next_pid_f_set = pid_f_set;
// Run speed PID
int pid_f_meas = f>>3;
if(reverse) pid_f_meas *= -1;
int ferr = pid_f_set - pid_f_meas;
spi_tx_data.speed = pid_f_meas>>8;
if(!(cnt & 15)) // don't run the PID every round
{
int dferr = (ferr - prev_ferr);
prev_ferr = ferr;
pid_integral += ferr;
if(pid_integral > (PID_I_MAX)) pid_integral = PID_I_MAX;
else if(pid_integral < (PID_I_MIN)) pid_integral = PID_I_MIN;
mult = ((30*(int64_t)pid_f_set)>>10) /* feedforward */
+ ((50*(int64_t)ferr)>>12) /* P */
+ ((50*(int64_t)pid_integral)>>19) /* I */
+ ((50*(int64_t)dferr)>>12); /*D*/
}
// Generate the sine
// Hall magic: get the magnetic position of the rotor right now.
int loc = (base_hall_aims[hall_pos] + timing_shift + (reverse?(-PH90SHIFT):(PH90SHIFT)));
// Indexes to the sine table (arithmetic wrap-around utilized):
int idxa = (((uint32_t)loc)&0xff000000)>>24;
int idxb = (((uint32_t)loc+PH120SHIFT)&0xff000000)>>24;
int idxc = (((uint32_t)loc+2*PH120SHIFT)&0xff000000)>>24;
const int max_mult = 150*256;
const int min_mult = -150*256;
if(mult > max_mult) mult = max_mult;
if(mult < min_mult) mult = min_mult;
int sin_mult = mult>>8;
if(reverse) sin_mult *= -1;
if(sin_mult < 0) sin_mult = 0;
if(sin_mult != 0)
sin_mult += MIN_MULT_OFFSET;
// Calculate the final multiplier, which is the combination of the PID loop output and current limit
uint8_t m = (sin_mult * currlim_mult)>>8; // 254 max
TIM1->CCR1 = (PWM_MID) + ((m*sine[idxa])>>14); // 4 to 1019.
TIM1->CCR2 = (PWM_MID) + ((m*sine[idxb])>>14);
TIM1->CCR3 = (PWM_MID) + ((m*sine[idxc])>>14);
cnt++;
int current = latest_adc[1].cur_b-dccal_b; // Temporary solution only looking current in phase B, adc timing must be improved
spi_tx_data.current = current;
if(OVERCURR()) // DRV8302 configurable cycle-by-cycle current limit signal: the gate driver has already terminated the pulse, this is information about it.
{
LED_ON(); led_short = 0; // give long blink
currlim_mult-=50;
}
else if(current < neg_curr_lim || current > pos_curr_lim) // soft current limit (by shunt & amp & adc)
{
LED_ON(); led_short = 1; // give short blink
currlim_mult-=2;
}
else if(currlim_mult < 255)
currlim_mult++;
if(currlim_mult < 5) currlim_mult = 5;
}