Skip to content

Commit

Permalink
Functions reorg
Browse files Browse the repository at this point in the history
  • Loading branch information
dzid26 committed May 9, 2024
1 parent 3e0c036 commit 57b3d13
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 161 deletions.
59 changes: 18 additions & 41 deletions firmware/src/BSP/A4950.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
#include "stepper_controller.h"
#include "nonvolatile.h"
#include "board.h"
#include "sine.h"
#include "encoder.h"
#include "actuator_config.h"
#include "utils.h"

#define I_RS_A4950_div (1000U/10U) //mOhm to Ohm and 10x multiplier
Expand Down Expand Up @@ -180,7 +177,7 @@ static void setPWM_bridgeA(uint16_t duty, bool quadrant1or2)
//Make sure the PIN_A4950_INs have running timer
TIM_SetAutoreload(PWM_TIM, PWM_TIM_MAX);

uint16_t pwm_count = min(duty >> PWM_SCALER, PWM_TIM_MAX);
uint16_t pwm_count = min(duty, PWM_TIM_MAX);

//duty_A,B between 0 and PWM_MAX corresponds to 0 and v_mot
//quadrant1or2,3or4 - determines which phase is used
Expand Down Expand Up @@ -218,7 +215,7 @@ static void setPWM_bridgeB(uint16_t duty, bool quadrant3or4)
//Make sure the PIN_A4950_INs are configured as PWM
TIM_SetAutoreload(PWM_TIM, PWM_TIM_MAX);

uint16_t pwm_count = min(duty >> PWM_SCALER, PWM_TIM_MAX);
uint16_t pwm_count = min(duty, PWM_TIM_MAX);

if (slow_decay)
{ //electric field quadrant
Expand Down Expand Up @@ -258,15 +255,14 @@ void A4950_enable(bool enable)
}


// this is precise move and modulo of A4950_STEP_MICROSTEPS is a full step.
// stepAngle is in A4950_STEP_MICROSTEPS units..
// The A4950 has no idea where the motor is, so the calling function has to
// tell the A4950 what phase to drive motor coils.
// A4950_STEP_MICROSTEPS is 256 by default so stepAngle of 1024 is 360 degrees
// Note you can only move up to +/-A4950_STEP_MICROSTEPS from where you
// currently are.

void apply_current_command(uint16_t elecAngleStep, uint16_t curr_tar) //256 stepAngle is 90 electrical degrees, aka full step
/**
* @brief Current based phase activation
*
* @param I_a - phase A requested current
* @param I_b - phase B requested current
* @param curr_lim - current limit applied to each phase
*/
void phase_current_command(int16_t I_a, int16_t I_b)
{
if (driverEnabled == false)
{
Expand All @@ -276,16 +272,6 @@ void apply_current_command(uint16_t elecAngleStep, uint16_t curr_tar) //256 step
return;
}

//calculate modified sine and cosine of our elecAngleStep
int16_t sin = sine_ripple(elecAngleStep, anticogging_factor);
int16_t cos = cosine_ripple(elecAngleStep, anticogging_factor);

int32_t I_d = curr_tar;
// Corresponds to Park transform for Id current only
// If we want to produce torque, we add +/-90deg load angle to elecAngleStep in the function caller
int16_t I_a = (int16_t)(I_d * cos / (int32_t)SINE_MAX); //convert value with vref max corresponding to 3300mV
int16_t I_b = (int16_t)(I_d * sin / (int32_t)SINE_MAX); //convert value with vref max corresponding to 3300mV

set_curr(fastAbs(I_a), fastAbs(I_b));

bridgeA((I_a > 0) ? 1 : 0);
Expand All @@ -298,13 +284,13 @@ void apply_current_command(uint16_t elecAngleStep, uint16_t curr_tar) //256 step

//todo bridge is not fully opened when control is off?
/**
* @brief Voltage commutation scheme
* @brief Voltage based phase activation with current limit
*
* @param elecAngle - current angle in electrical degrees - 360 corresponds to SINE_STEPS
* @param U_q - quadrature voltage command - corresponds to torque generating current command + BEMF compensation
* @param U_d - direct voltage command - corresponds to flux generating current + current lag compesantion
* @param U_a - phase A requested voltage
* @param U_b - phase B requested voltage
* @param curr_lim - current limit applied to each phase
*/
void apply_volt_command(uint16_t elecAngle, int32_t U_q, int32_t U_d, uint16_t curr_lim) //256 stepAngle is 90 electrical degrees
void phase_voltage_command(int16_t U_a, int16_t U_b, uint16_t curr_lim)
{
if (driverEnabled == false)
{
Expand All @@ -313,20 +299,11 @@ void apply_volt_command(uint16_t elecAngle, int32_t U_q, int32_t U_d, uint16_t c
bridgeB(3); //tri state bridge outputs
return;
}
//calculate the sine and cosine of elecAngle
int16_t sin = sine_ripple(elecAngle, anticogging_factor);
int16_t cos = cosine_ripple(elecAngle, anticogging_factor);

set_curr(curr_lim, curr_lim);

//timer compare
uint16_t U_in = GetMotorVoltage_mV();

int32_t U_a = (cos * U_d) - (sin * U_q);
int32_t U_b = (sin * U_d) + (cos * U_q);

uint16_t duty_a = (uint16_t) ((uint32_t)fastAbs(U_a) / U_in);
uint16_t duty_b = (uint16_t) ((uint32_t)fastAbs(U_b) / U_in);
uint16_t U_in = GetMotorVoltage_mV();
uint16_t duty_a = (uint16_t) ((uint32_t)fastAbs(U_a) * PWM_TIM_MAX / U_in);
uint16_t duty_b = (uint16_t) ((uint32_t)fastAbs(U_b) * PWM_TIM_MAX / U_in);
setPWM_bridgeA(duty_a, (U_a > 0)); //PWM12
setPWM_bridgeB(duty_b, liveMotorParams.swapPhase ? (U_b < 0) : (U_b > 0)); //PWM34
}
9 changes: 3 additions & 6 deletions firmware/src/BSP/A4950.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
#include <stdint.h>
#include <stdbool.h>


#define A4950_STEP_MICROSTEPS (uint16_t) 256U //Full step electrical angle
//VREF_SCALER reduces PWM resolution by 2^VREF_SCALER but increases PWM freqency by 2^(VREF_SCALER-1)
#define VREF_SCALER 6U
#define PWM_SCALER 3U //low vibration
Expand All @@ -35,12 +33,11 @@
#define PHASE_LEAD_MAX_SPEED 250u //revs/s
extern const uint16_t dacPhaseLead[PHASE_LEAD_MAX_SPEED];

#define I_MAX_A4950 (3300) //mA
#define I_MAX_A4950 3300 //mA

void A4950_enable(bool enable);
void apply_current_command(uint16_t elecAngleStep, uint16_t curr_tar);
void apply_volt_command(uint16_t elecAngle, int32_t U_q, int32_t U_d, uint16_t curr_lim);
void A4954_begin(void);
void phase_current_command(int16_t I_a, int16_t I_b);
void phase_voltage_command(int16_t U_a, int16_t U_b, uint16_t curr_lim);

extern volatile bool driverEnabled;

Expand Down
1 change: 0 additions & 1 deletion firmware/src/BSP/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include "main.h"
#include "can.h"
#include "A4950.h"
#include "sine.h"
#include "stepper_controller.h"
#include "utils.h"

Expand Down
6 changes: 6 additions & 0 deletions firmware/src/BSP/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#include <assert.h>
#include "stm32f10x.h"

#include "sine.h"


#define VOLT_DIV_RATIO(R1, R2) (((float) R2 / ((float)R1 + (float)R2)))
#define ADC_12bit 4096
//MCU power supply
Expand Down Expand Up @@ -142,6 +145,9 @@ float Get_PhaseA_Current(void);
float Get_PhaseB_Current(void);

#define MHz_to_Hz (uint32_t)(1000000)
#define s_to_ms (1000U)
#define s_to_us (1000000U)
#define us_to_ns (1000U)
void Motion_task_init(uint16_t taskPeriod);
void Serivice_task_init(void);

Expand Down
22 changes: 11 additions & 11 deletions firmware/src/BSP/calibration.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include "calibration.h"
#include "nonvolatile.h"
#include "flash.h"
#include "A4950.h"
#include "motor.h"
#include "encoder.h"
#include "delay.h"
#include "actuator_config.h"
Expand Down Expand Up @@ -149,11 +149,11 @@ float StepperCtrl_measureStepSize(void){
uint16_t stepCurrent = CALIBRATION_STEPPING_CURRENT;
// Measure the full step size
// Note we assume machine can take one step without issue///
apply_current_command(0, stepCurrent); //fix the stepper
openloop_step(0, stepCurrent); //fix the stepper
delay_ms(200);
angle1 = OverSampleEncoderAngle(100U); //angle1 - (0-65535)

apply_current_command(A4950_STEP_MICROSTEPS, stepCurrent); //move one step 'forward'
openloop_step(FULLSTEP_ELECTRIC_ANGLE, stepCurrent); //move one step 'forward'
delay_ms(200);
angle2 = OverSampleEncoderAngle(100U); //angle2 - (0-65535)

Expand All @@ -163,7 +163,7 @@ float StepperCtrl_measureStepSize(void){
float deg_delta = ANGLERAW_T0_DEGREES(angle_delta);

//move back
apply_current_command(0,stepCurrent);
openloop_step(0,stepCurrent);
A4950_enable(false);

return deg_delta;
Expand Down Expand Up @@ -219,7 +219,7 @@ static uint16_t CalibrationMove(int8_t dir, bool verifyOnly, bool firstPass){
const uint16_t microStepDelay = 30U; //[uS] controls calibration speed
const uint16_t stabilizationDelay = 0U; //[uS] wait for taking measurements - some medium stopping time can cause resonance, long stopping time can cause oveheat
const uint16_t stepOversampling = 3U; //measurements to take per point, note large will take some time
const uint16_t microStep = A4950_STEP_MICROSTEPS; //microsteping resolution in between taking measurements
const uint16_t microStep = FULLSTEP_ELECTRIC_ANGLE; //microsteping resolution in between taking measurements

static int32_t electAngle;//electric angle - static carry value over between passes
if (firstPass){
Expand All @@ -233,7 +233,7 @@ static uint16_t CalibrationMove(int8_t dir, bool verifyOnly, bool firstPass){
bool preRun = (step < preRunSteps); //rotate some to stabilize hysteresis before starting actual calibration
if (!preRun) {
delay_us(stabilizationDelay);
volatile int16_t calcStep = (int16_t)(electAngle / (int16_t)A4950_STEP_MICROSTEPS);
volatile int16_t calcStep = (int16_t)(electAngle / (int16_t)FULLSTEP_ELECTRIC_ANGLE);
volatile uint16_t expectedAngle = (uint16_t)(int32_t)((int32_t)calcStep * (int32_t)ANGLE_STEPS / (int16_t)liveMotorParams.fullStepsPerRotation);//convert to shaft angle
volatile uint16_t cal = (CalibrationTable_getCal(expectedAngle)); //(0-65535) - this is necessary for the second pass

Expand Down Expand Up @@ -268,8 +268,8 @@ static uint16_t CalibrationMove(int8_t dir, bool verifyOnly, bool firstPass){
const uint8_t stepDivCal_q4 = (uint8_t)(((uint16_t)(CALIBRATION_TABLE_SIZE << 4U)) / liveMotorParams.fullStepsPerRotation);
const uint16_t microSteps = (((uint16_t)(microStep << 4U)) / stepDivCal_q4);
for(uint16_t i = 0; i<microSteps; ++i){ //move between measurements
electAngle += dir * (int32_t)(uint16_t)(A4950_STEP_MICROSTEPS/microStep);//dir can be negative on first pass depending on higher level settings
apply_current_command((uint16_t) electAngle, stepCurrent);
electAngle += dir * (int32_t)(uint16_t)(FULLSTEP_ELECTRIC_ANGLE/microStep);//dir can be negative on first pass depending on higher level settings
openloop_step((uint16_t) electAngle, stepCurrent);
delay_us(microStepDelay);
}
}
Expand Down Expand Up @@ -299,7 +299,7 @@ uint16_t StepperCtrl_calibrateEncoder(bool verifyOnly){

A4950_enable(true);

apply_current_command(0, CALIBRATION_STEPPING_CURRENT);
openloop_step(0, CALIBRATION_STEPPING_CURRENT);
delay_ms(50);

//determine first pass direction
Expand All @@ -312,7 +312,7 @@ uint16_t StepperCtrl_calibrateEncoder(bool verifyOnly){
}
maxError = CalibrationMove(dir, verifyOnly, true);
//wait holding two phases (half a step) for less heat generation before triggering second pass
apply_current_command(A4950_STEP_MICROSTEPS/2U, CALIBRATION_STEPPING_CURRENT); //first calibration pass finishes at electAngle = 0, so adding half a step wont't ruin next pass
openloop_step(FULLSTEP_ELECTRIC_ANGLE/2U, CALIBRATION_STEPPING_CURRENT); //first calibration pass finishes at electAngle = 0, so adding half a step wont't ruin next pass
delay_ms(1000); //give some time before motor starts to move the other direction
if(!verifyOnly){
//second calibration pass the other direction - reduces influence of magnetic hysteresis
Expand All @@ -323,7 +323,7 @@ uint16_t StepperCtrl_calibrateEncoder(bool verifyOnly){
}
}
//measure new starting point
apply_current_command(0, 0); //release motor - 0mA
openloop_step(0, 0); //release motor - 0mA

return maxError;
}
123 changes: 123 additions & 0 deletions firmware/src/BSP/motor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "motor.h"
#include "sine.h"
#include "actuator_config.h"
#include "stepper_controller.h"
#include "nonvolatile.h"
#include "encoder.h"
#include "utils.h"
#include "board.h"

static void inverse_park_transform(uint16_t elecAngle, int16_t Q, int16_t D, int16_t *A, int16_t *B){
//calculate sine and cosine with ripple compensation
int16_t sin = sine_ripple(elecAngle, anticogging_factor);
int16_t cos = cosine_ripple(elecAngle, anticogging_factor);


*A = (int16_t)((((int32_t)cos * D) - ((int32_t)sin * Q)) / (int32_t)SINE_MAX); //convert value with vref max corresponding to 3300mV
*B = (int16_t)((((int32_t)sin * D) + ((int32_t)cos * Q)) / (int32_t)SINE_MAX); //convert value with vref max corresponding to 3300mV
}

/**
* @brief Current commutation scheme
*
* @param elecAngle - current angle in electrical degrees - 360 corresponds to SINE_STEPS
* @param I_q - quadrature current command - corresponds to driving torque
* @param I_d - direct current command - positive value corresponds to "holding torque" for open-loop stepping. Negative values can be used for field weakening
*/
static void current_commutation(uint16_t elecAngle, int16_t I_q, int16_t I_d)
{
int16_t I_a = 0;
int16_t I_b = 0;
inverse_park_transform(elecAngle, I_q, I_d, &I_a, &I_b);

phase_current_command(I_a, I_b);
}

/**
* @brief Voltage commutation scheme
*
* @param elecAngle - current angle in electrical degrees - 360 corresponds to SINE_STEPS
* @param U_q - quadrature voltage command - corresponds to torque generating current command + BEMF compensation
* @param U_d - direct voltage command - corresponds to flux generating current + current lag compesantion
*/
static void voltage_commutation(uint16_t elecAngle, int16_t U_q, int16_t U_d, uint16_t curr_lim)
{
int16_t U_a = 0;
int16_t U_b = 0;
inverse_park_transform(elecAngle, U_q, U_d, &U_a, &U_b);

phase_voltage_command(U_a, U_b, curr_lim);
}

void openloop_step(uint16_t elecAngleStep, uint16_t curr_tar){
current_commutation(elecAngleStep, 0, (int16_t)curr_tar);
}


/**
* @brief Converts absolut eangle to electric angle and applies delay compensations
* @return multiples of electric angle
*/
static uint16_t calc_electric_angle(bool volt_control){

int16_t angleSensLatency = 64u; //angle sensor delay - bigger value can result in higher speed (because it fakes field weakening), but can be detrimental to motor power and efficiency

int16_t angleSpeedComp = (int16_t) (speed_slow * angleSensLatency / (int32_t) S_to_uS);

//convert load angle to electrical angle domain (0-1023 full turn)
uint16_t absoluteAngle = (uint16_t)(((uint32_t)(int32_t)(currentLocation + angleSpeedComp)) & ANGLE_MAX); //add load angle to current location
uint16_t electricAngle = absoluteAngle * liveMotorParams.fullStepsPerRotation * FULLSTEP_ELECTRIC_ANGLE / ANGLE_STEPS;

//calculate microsteps phase lead for current control
if (volt_control == false){
uint16_t stepPhaseLead = 0;
if (speed_slow > 0){
stepPhaseLead = dacPhaseLead[min(((uint32_t) ( speed_slow) / ANGLE_STEPS), PHASE_LEAD_MAX_SPEED - 1U)];
electricAngle += stepPhaseLead;
}else{
stepPhaseLead = dacPhaseLead[min(((uint32_t) (-speed_slow) / ANGLE_STEPS), PHASE_LEAD_MAX_SPEED - 1U)];
electricAngle -= stepPhaseLead;
}
}
return electricAngle % SINE_STEPS;
}

void field_oriented_control(int16_t current_target)
{
const bool volt_control = true;

uint16_t electricAngle = calc_electric_angle(volt_control);

int16_t I_q = current_target;
if(volt_control == true){
//Iq, Id, Uq, Ud per FOC nomencluture

//Qadrature axis
//U_q = U_IR + U_emf
int16_t U_IR = (int16_t)((int32_t)I_q * phase_R / Ohm_to_mOhm);
int32_t U_emf = (int32_t)((int64_t)motor_k_bemf * speed_slow / (int32_t)ANGLE_STEPS);
int32_t U_q = U_IR + U_emf;
int16_t U_lim = (int16_t)min(GetMotorVoltage_mV(), INT16_MAX);
int16_t U_emf_sat = (int16_t)clip(U_emf, -U_lim, U_lim);
int16_t U_IR_sat = (int16_t)clip(U_IR, -U_lim - U_emf_sat, U_lim - U_emf_sat);
U_IR_sat = (int16_t)clip(U_IR_sat, -U_lim, U_lim);
int16_t U_q_sat = U_IR_sat + U_emf_sat;
int16_t I_q_act = (int16_t)((int32_t)U_IR_sat * Ohm_to_mOhm / phase_R);
control_actual = I_q_act;

//Direct Axis
//U_d = I_q*ω*Rl
uint16_t motor_rev_to_elec_rad = (uint16_t)((uint32_t)TWO_PI_X1024 * liveMotorParams.fullStepsPerRotation / 4U / 1024U); //typically 314 (or 628 for 0.9deg motor)
int32_t e_rad_s = (int32_t)((int64_t)motor_rev_to_elec_rad * speed_slow / (int32_t)ANGLE_STEPS);
int32_t U_d = (int32_t)(-I_q_act) * phase_L / H_to_uH * e_rad_s; //Vd=Iq * ω*Rl

int16_t U_d_sat = (int16_t)(clip(U_d, -U_lim, U_lim));
uint16_t magnitude = (uint16_t)((current_target > 0) ? I_q_act : -I_q_act); //abs
voltage_commutation(electricAngle, U_q_sat, U_d_sat, magnitude);
}else{
current_commutation(electricAngle, I_q, 0);
control_actual = (int16_t)clip(control, -MAX_CURRENT, MAX_CURRENT); // simplification - close to truth for low speeds

}
}

14 changes: 14 additions & 0 deletions firmware/src/BSP/motor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef MOTOR_H
#define MOTOR_H

#include <stdint.h>
#include <stdbool.h>

#include "A4950.h"

#define FULLSTEP_ELECTRIC_ANGLE (uint16_t) 256U //Full step electrical angle
#define MAX_CURRENT I_MAX_A4950
void openloop_step(uint16_t elecAngleStep, uint16_t curr_tar);
void field_oriented_control(int16_t current_target);

#endif // MOTOR_H_
Loading

0 comments on commit 57b3d13

Please sign in to comment.