v1.3增加一个简单的越障模式

This commit is contained in:
myperworld committed 2025-11-22 22:52:53 +08:00
1 parent 9d56ba85f1
commit 7a1931cb72
1835 files changed
+475654 -1

No files matched your search

+670
View File
@@ -0,0 +1,670 @@
#include "BLDCMotor.h"
// BLDCMotor( int pp , float R)
// - pp - pole pair number
// - R - motor phase resistance
BLDCMotor::BLDCMotor(int pp, float _R)
: FOCMotor()
{
// save pole pairs number
pole_pairs = pp;
// save phase resistance number
phase_resistance = _R;
// torque control type is voltage by default
torque_controller = TorqueControlType::voltage;
}
/**
Link the driver which controls the motor
*/
void BLDCMotor::linkDriver(BLDCDriver* _driver) {
driver = _driver;
}
// init hardware pins
void BLDCMotor::init() {
if (!driver || !driver->initialized) {
motor_status = FOCMotorStatus::motor_init_failed;
if(monitor_port) monitor_port->println(F("MOT: Init not possible, driver not initialized"));
}
motor_status = FOCMotorStatus::motor_initializing;
if(monitor_port) monitor_port->println(F("MOT: Init"));
// if no current sensing and the user has set the phase resistance of the motor use current limit to calculate the voltage limit
if( !current_sense && _isset(phase_resistance)) {
float new_voltage_limit = current_limit * (phase_resistance); // v_lim = current_lim / (3/2 phase resistance) - worst case
// use it if it is less then voltage_limit set by the user
voltage_limit = new_voltage_limit < voltage_limit ? new_voltage_limit : voltage_limit;
}
// sanity check for the voltage limit configuration
if(voltage_limit > driver->voltage_limit) voltage_limit = driver->voltage_limit;
// constrain voltage for sensor alignment
if(voltage_sensor_align > voltage_limit) voltage_sensor_align = voltage_limit;
// update the controller limits
if(current_sense){
// current control loop controls voltage
PID_current_q.limit = voltage_limit;
PID_current_d.limit = voltage_limit;
}
if(_isset(phase_resistance) || torque_controller != TorqueControlType::voltage){
// velocity control loop controls current
PID_velocity.limit = current_limit;
}else{
// velocity control loop controls the voltage
PID_velocity.limit = voltage_limit;
}
P_angle.limit = velocity_limit;
_delay(500);
// enable motor
if(monitor_port) monitor_port->println(F("MOT: Enable driver."));
enable();
_delay(500);
motor_status = FOCMotorStatus::motor_uncalibrated;
}
// disable motor driver
void BLDCMotor::disable()
{
// set zero to PWM
driver->setPwm(0, 0, 0);
// disable the driver
driver->disable();
// motor status update
enabled = 0;
}
// enable motor driver
void BLDCMotor::enable()
{
// enable the driver
driver->enable();
// set zero to PWM
driver->setPwm(0, 0, 0);
// motor status update
enabled = 1;
}
/**
FOC functions
*/
// FOC initialization function
int BLDCMotor::initFOC( float zero_electric_offset, Direction _sensor_direction) {
int exit_flag = 1;
motor_status = FOCMotorStatus::motor_calibrating;
// align motor if necessary
// alignment necessary for encoders!
if(_isset(zero_electric_offset)){
// abosolute zero offset provided - no need to align
zero_electric_angle = zero_electric_offset;
// set the sensor direction - default CW
sensor_direction = _sensor_direction;
}
// sensor and motor alignment - can be skipped
// by setting motor.sensor_direction and motor.zero_electric_angle
_delay(500);
if(sensor){
exit_flag *= alignSensor();
// added the shaft_angle update
sensor->update();
shaft_angle = shaftAngle();
}else if(monitor_port) monitor_port->println(F("MOT: No sensor."));
// aligning the current sensor - can be skipped
// checks if driver phases are the same as current sense phases
// and checks the direction of measuremnt.
_delay(500);
if(exit_flag){
if(current_sense) exit_flag *= alignCurrentSense();
else if(monitor_port) monitor_port->println(F("MOT: No current sense."));
}
if(exit_flag){
if(monitor_port) monitor_port->println(F("MOT: Ready."));
motor_status = FOCMotorStatus::motor_ready;
}else{
if(monitor_port) monitor_port->println(F("MOT: Init FOC failed."));
disable();
motor_status = FOCMotorStatus::motor_calib_failed;
}
return exit_flag;
}
// Calibarthe the motor and current sense phases
int BLDCMotor::alignCurrentSense() {
int exit_flag = 1; // success
if(monitor_port) monitor_port->println(F("MOT: Align current sense."));
// align current sense and the driver
exit_flag = current_sense->driverAlign(driver, voltage_sensor_align);
if(!exit_flag){
// error in current sense - phase either not measured or bad connection
if(monitor_port) monitor_port->println(F("MOT: Align error!"));
exit_flag = 0;
}else{
// output the alignment status flag
if(monitor_port) monitor_port->print(F("MOT: Success: "));
if(monitor_port) monitor_port->println(exit_flag);
}
return exit_flag > 0;
}
// Encoder alignment to electrical 0 angle
int BLDCMotor::alignSensor() {
int exit_flag = 1; //success
if(monitor_port) monitor_port->println(F("MOT: Align sensor."));
// if unknown natural direction
if(!_isset(sensor_direction)){
// check if sensor needs zero search
if(sensor->needsSearch()) exit_flag = absoluteZeroSearch();
// stop init if not found index
if(!exit_flag) return exit_flag;
// find natural direction
// move one electrical revolution forward
for (int i = 0; i <=500; i++ ) {
float angle = _3PI_2 + _2PI * i / 500.0f;
setPhaseVoltage(voltage_sensor_align, 0, angle);
_delay(2);
}
// take and angle in the middle
sensor->update();
float mid_angle = sensor->getAngle();
// move one electrical revolution backwards
for (int i = 500; i >=0; i-- ) {
float angle = _3PI_2 + _2PI * i / 500.0f ;
setPhaseVoltage(voltage_sensor_align, 0, angle);
_delay(2);
}
sensor->update();
float end_angle = sensor->getAngle();
setPhaseVoltage(0, 0, 0);
_delay(200);
// determine the direction the sensor moved
if (mid_angle == end_angle) {
if(monitor_port) monitor_port->println(F("MOT: Failed to notice movement"));
return 0; // failed calibration
} else if (mid_angle < end_angle) {
if(monitor_port) monitor_port->println(F("MOT: sensor_direction==CCW"));
sensor_direction = Direction::CCW;
} else{
if(monitor_port) monitor_port->println(F("MOT: sensor_direction==CW"));
sensor_direction = Direction::CW;
}
// check pole pair number
if(monitor_port) monitor_port->print(F("MOT: PP check: "));
float moved = fabs(mid_angle - end_angle);
if( fabs(moved*pole_pairs - _2PI) > 0.5f ) { // 0.5f is arbitrary number it can be lower or higher!
if(monitor_port) monitor_port->print(F("fail - estimated pp:"));
if(monitor_port) monitor_port->println(_2PI/moved,4);
}else if(monitor_port) monitor_port->println(F("OK!"));
}else if(monitor_port) monitor_port->println(F("MOT: Skip dir calib."));
// zero electric angle not known
if(!_isset(zero_electric_angle)){
// align the electrical phases of the motor and sensor
// set angle -90(270 = 3PI/2) degrees
setPhaseVoltage(voltage_sensor_align, 0, _3PI_2);
_delay(700);
// read the sensor
sensor->update();
// get the current zero electric angle
zero_electric_angle = 0;
zero_electric_angle = electricalAngle();
//zero_electric_angle = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
_delay(20);
if(monitor_port){
monitor_port->print(F("MOT: Zero elec. angle: "));
monitor_port->println(zero_electric_angle);
}
// stop everything
setPhaseVoltage(0, 0, 0);
_delay(200);
}else if(monitor_port) monitor_port->println(F("MOT: Skip offset calib."));
return exit_flag;
}
// Encoder alignment the absolute zero angle
// - to the index
int BLDCMotor::absoluteZeroSearch() {
// sensor precision: this is all ok, as the search happens near the 0-angle, where the precision
// of float is sufficient.
if(monitor_port) monitor_port->println(F("MOT: Index search..."));
// search the absolute zero with small velocity
float limit_vel = velocity_limit;
float limit_volt = voltage_limit;
velocity_limit = velocity_index_search;
voltage_limit = voltage_sensor_align;
shaft_angle = 0;
while(sensor->needsSearch() && shaft_angle < _2PI){
angleOpenloop(1.5f*_2PI);
// call important for some sensors not to loose count
// not needed for the search
sensor->update();
}
// disable motor
setPhaseVoltage(0, 0, 0);
// reinit the limits
velocity_limit = limit_vel;
voltage_limit = limit_volt;
// check if the zero found
if(monitor_port){
if(sensor->needsSearch()) monitor_port->println(F("MOT: Error: Not found!"));
else monitor_port->println(F("MOT: Success!"));
}
return !sensor->needsSearch();
}
// Iterative function looping FOC algorithm, setting Uq on the Motor
// The faster it can be run the better
void BLDCMotor::loopFOC() {
// update sensor - do this even in open-loop mode, as user may be switching between modes and we could lose track
// of full rotations otherwise.
if (sensor) sensor->update();
// if open-loop do nothing
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop ) return;
// if disabled do nothing
if(!enabled) return;
// Needs the update() to be called first
// This function will not have numerical issues because it uses Sensor::getMechanicalAngle()
// which is in range 0-2PI
electrical_angle = electricalAngle();
switch (torque_controller) {
case TorqueControlType::voltage:
// no need to do anything really
break;
case TorqueControlType::dc_current:
if(!current_sense) return;
// read overall current magnitude
current.q = current_sense->getDCCurrent(electrical_angle);
// filter the value values
current.q = LPF_current_q(current.q);
// calculate the phase voltage
voltage.q = PID_current_q(current_sp - current.q);
voltage.d = 0;
break;
case TorqueControlType::foc_current:
if(!current_sense) return;
// read dq currents
current = current_sense->getFOCCurrents(electrical_angle);
// filter values
current.q = LPF_current_q(current.q);
current.d = LPF_current_d(current.d);
// calculate the phase voltages
voltage.q = PID_current_q(current_sp - current.q);
voltage.d = PID_current_d(-current.d);
break;
default:
// no torque control selected
if(monitor_port) monitor_port->println(F("MOT: no torque control selected!"));
break;
}
// set the phase voltage - FOC heart function :)
setPhaseVoltage(voltage.q, voltage.d, electrical_angle);
}
// Iterative function running outer loop of the FOC algorithm
// Behavior of this function is determined by the motor.controller variable
// It runs either angle, velocity or torque loop
// - needs to be called iteratively it is asynchronous function
// - if target is not set it uses motor.target value
void BLDCMotor::move(float new_target) {
// downsampling (optional)
if(motion_cnt++ < motion_downsample) return;
motion_cnt = 0;
// shaft angle/velocity need the update() to be called first
// get shaft angle
// TODO sensor precision: the shaft_angle actually stores the complete position, including full rotations, as a float
// For this reason it is NOT precise when the angles become large.
// Additionally, the way LPF works on angle is a precision issue, and the angle-LPF is a problem
// when switching to a 2-component representation.
if( controller!=MotionControlType::angle_openloop && controller!=MotionControlType::velocity_openloop )
shaft_angle = shaftAngle(); // read value even if motor is disabled to keep the monitoring updated but not in openloop mode
// get angular velocity
shaft_velocity = shaftVelocity(); // read value even if motor is disabled to keep the monitoring updated
// if disabled do nothing
if(!enabled) return;
// set internal target variable
if(_isset(new_target)) target = new_target;
switch (controller) {
case MotionControlType::torque:
if(torque_controller == TorqueControlType::voltage){ // if voltage torque control
if(!_isset(phase_resistance)) voltage.q = target;
else voltage.q = target*phase_resistance;
voltage.d = 0;
}else{
current_sp = target; // if current/foc_current torque control
}
break;
case MotionControlType::angle:
// TODO sensor precision: this calculation is not numerically precise. The target value cannot express precise positions when
// the angles are large. This results in not being able to command small changes at high position values.
// to solve this, the delta-angle has to be calculated in a numerically precise way.
// angle set point
shaft_angle_sp = target;
// calculate velocity set point
shaft_velocity_sp = P_angle( shaft_angle_sp - shaft_angle );
// calculate the torque command - sensor precision: this calculation is ok, but based on bad value from previous calculation
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
// if torque controlled through voltage
if(torque_controller == TorqueControlType::voltage){
// use voltage if phase-resistance not provided
if(!_isset(phase_resistance)) voltage.q = current_sp;
else voltage.q = current_sp*phase_resistance;
voltage.d = 0;
}
break;
case MotionControlType::velocity:
// velocity set point - sensor precision: this calculation is numerically precise.
shaft_velocity_sp = target;
// calculate the torque command
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if current/foc_current torque control
// if torque controlled through voltage control
if(torque_controller == TorqueControlType::voltage){
// use voltage if phase-resistance not provided
if(!_isset(phase_resistance)) voltage.q = current_sp;
else voltage.q = current_sp*phase_resistance;
voltage.d = 0;
}
break;
case MotionControlType::velocity_openloop:
// velocity control in open loop - sensor precision: this calculation is numerically precise.
shaft_velocity_sp = target;
voltage.q = velocityOpenloop(shaft_velocity_sp); // returns the voltage that is set to the motor
voltage.d = 0;
break;
case MotionControlType::angle_openloop:
// angle control in open loop -
// TODO sensor precision: this calculation NOT numerically precise, and subject
// to the same problems in small set-point changes at high angles
// as the closed loop version.
shaft_angle_sp = target;
voltage.q = angleOpenloop(shaft_angle_sp); // returns the voltage that is set to the motor
voltage.d = 0;
break;
}
}
// Method using FOC to set Uq and Ud to the motor at the optimal angle
// Function implementing Space Vector PWM and Sine PWM algorithms
//
// Function using sine approximation
// regular sin + cos ~300us (no memory usaage)
// approx _sin + _cos ~110us (400Byte ~ 20% of memory)
void BLDCMotor::setPhaseVoltage(float Uq, float Ud, float angle_el) {
float center;
int sector;
float _ca,_sa;
switch (foc_modulation)
{
case FOCModulationType::Trapezoid_120 :
// see https://www.youtube.com/watch?v=InzXA7mWBWE Slide 5
static int trap_120_map[6][3] = {
{_HIGH_IMPEDANCE,1,-1},{-1,1,_HIGH_IMPEDANCE},{-1,_HIGH_IMPEDANCE,1},{_HIGH_IMPEDANCE,-1,1},{1,-1,_HIGH_IMPEDANCE},{1,_HIGH_IMPEDANCE,-1} // each is 60 degrees with values for 3 phases of 1=positive -1=negative 0=high-z
};
// static int trap_120_state = 0;
sector = 6 * (_normalizeAngle(angle_el + _PI_6 ) / _2PI); // adding PI/6 to align with other modes
// centering the voltages around either
// modulation_centered == true > driver.volage_limit/2
// modulation_centered == false > or Adaptable centering, all phases drawn to 0 when Uq=0
center = modulation_centered ? (driver->voltage_limit)/2 : Uq;
if(trap_120_map[sector][0] == _HIGH_IMPEDANCE){
Ua= center;
Ub = trap_120_map[sector][1] * Uq + center;
Uc = trap_120_map[sector][2] * Uq + center;
driver->setPhaseState(_HIGH_IMPEDANCE, _ACTIVE, _ACTIVE); // disable phase if possible
}else if(trap_120_map[sector][1] == _HIGH_IMPEDANCE){
Ua = trap_120_map[sector][0] * Uq + center;
Ub = center;
Uc = trap_120_map[sector][2] * Uq + center;
driver->setPhaseState(_ACTIVE, _HIGH_IMPEDANCE, _ACTIVE);// disable phase if possible
}else{
Ua = trap_120_map[sector][0] * Uq + center;
Ub = trap_120_map[sector][1] * Uq + center;
Uc = center;
driver->setPhaseState(_ACTIVE,_ACTIVE, _HIGH_IMPEDANCE);// disable phase if possible
}
break;
case FOCModulationType::Trapezoid_150 :
// see https://www.youtube.com/watch?v=InzXA7mWBWE Slide 8
static int trap_150_map[12][3] = {
{_HIGH_IMPEDANCE,1,-1},{-1,1,-1},{-1,1,_HIGH_IMPEDANCE},{-1,1,1},{-1,_HIGH_IMPEDANCE,1},{-1,-1,1},{_HIGH_IMPEDANCE,-1,1},{1,-1,1},{1,-1,_HIGH_IMPEDANCE},{1,-1,-1},{1,_HIGH_IMPEDANCE,-1},{1,1,-1} // each is 30 degrees with values for 3 phases of 1=positive -1=negative 0=high-z
};
// static int trap_150_state = 0;
sector = 12 * (_normalizeAngle(angle_el + _PI_6 ) / _2PI); // adding PI/6 to align with other modes
// centering the voltages around either
// modulation_centered == true > driver.volage_limit/2
// modulation_centered == false > or Adaptable centering, all phases drawn to 0 when Uq=0
center = modulation_centered ? (driver->voltage_limit)/2 : Uq;
if(trap_150_map[sector][0] == _HIGH_IMPEDANCE){
Ua= center;
Ub = trap_150_map[sector][1] * Uq + center;
Uc = trap_150_map[sector][2] * Uq + center;
driver->setPhaseState(_HIGH_IMPEDANCE, _ACTIVE, _ACTIVE); // disable phase if possible
}else if(trap_150_map[sector][1] == _HIGH_IMPEDANCE){
Ua = trap_150_map[sector][0] * Uq + center;
Ub = center;
Uc = trap_150_map[sector][2] * Uq + center;
driver->setPhaseState(_ACTIVE, _HIGH_IMPEDANCE, _ACTIVE);// disable phase if possible
}else{
Ua = trap_150_map[sector][0] * Uq + center;
Ub = trap_150_map[sector][1] * Uq + center;
Uc = center;
driver->setPhaseState(_ACTIVE, _ACTIVE, _HIGH_IMPEDANCE);// disable phase if possible
}
break;
case FOCModulationType::SinePWM :
// Sinusoidal PWM modulation
// Inverse Park + Clarke transformation
// angle normalization in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el);
_ca = _cos(angle_el);
_sa = _sin(angle_el);
// Inverse park transform
Ualpha = _ca * Ud - _sa * Uq; // -sin(angle) * Uq;
Ubeta = _sa * Ud + _ca * Uq; // cos(angle) * Uq;
// center = modulation_centered ? (driver->voltage_limit)/2 : Uq;
center = driver->voltage_limit/2;
// Clarke transform
Ua = Ualpha + center;
Ub = -0.5f * Ualpha + _SQRT3_2 * Ubeta + center;
Uc = -0.5f * Ualpha - _SQRT3_2 * Ubeta + center;
if (!modulation_centered) {
float Umin = min(Ua, min(Ub, Uc));
Ua -= Umin;
Ub -= Umin;
Uc -= Umin;
}
break;
case FOCModulationType::SpaceVectorPWM :
// Nice video explaining the SpaceVectorModulation (SVPWM) algorithm
// https://www.youtube.com/watch?v=QMSWUMEAejg
// the algorithm goes
// 1) Ualpha, Ubeta
// 2) Uout = sqrt(Ualpha^2 + Ubeta^2)
// 3) angle_el = atan2(Ubeta, Ualpha)
//
// equivalent to 2) because the magnitude does not change is:
// Uout = sqrt(Ud^2 + Uq^2)
// equivalent to 3) is
// angle_el = angle_el + atan2(Uq,Ud)
float Uout;
// a bit of optitmisation
if(Ud){ // only if Ud and Uq set
// _sqrt is an approx of sqrt (3-4% error)
Uout = _sqrt(Ud*Ud + Uq*Uq) / driver->voltage_limit;
// angle normalisation in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el + atan2(Uq, Ud));
}else{// only Uq available - no need for atan2 and sqrt
Uout = Uq / driver->voltage_limit;
// angle normalisation in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el + _PI_2);
}
// find the sector we are in currently
sector = floor(angle_el / _PI_3) + 1;
// calculate the duty cycles
float T1 = _SQRT3*_sin(sector*_PI_3 - angle_el) * Uout;
float T2 = _SQRT3*_sin(angle_el - (sector-1.0f)*_PI_3) * Uout;
// two versions possible
float T0 = 0; // pulled to 0 - better for low power supply voltage
if (modulation_centered) {
T0 = 1 - T1 - T2; // modulation_centered around driver->voltage_limit/2
}
// calculate the duty cycles(times)
float Ta,Tb,Tc;
switch(sector){
case 1:
Ta = T1 + T2 + T0/2;
Tb = T2 + T0/2;
Tc = T0/2;
break;
case 2:
Ta = T1 + T0/2;
Tb = T1 + T2 + T0/2;
Tc = T0/2;
break;
case 3:
Ta = T0/2;
Tb = T1 + T2 + T0/2;
Tc = T2 + T0/2;
break;
case 4:
Ta = T0/2;
Tb = T1+ T0/2;
Tc = T1 + T2 + T0/2;
break;
case 5:
Ta = T2 + T0/2;
Tb = T0/2;
Tc = T1 + T2 + T0/2;
break;
case 6:
Ta = T1 + T2 + T0/2;
Tb = T0/2;
Tc = T1 + T0/2;
break;
default:
// possible error state
Ta = 0;
Tb = 0;
Tc = 0;
}
// calculate the phase voltages and center
Ua = Ta*driver->voltage_limit;
Ub = Tb*driver->voltage_limit;
Uc = Tc*driver->voltage_limit;
break;
}
// set the voltages in driver
driver->setPwm(Ua, Ub, Uc);
}
// Function (iterative) generating open loop movement for target velocity
// - target_velocity - rad/s
// it uses voltage_limit variable
float BLDCMotor::velocityOpenloop(float target_velocity){
// get current timestamp
unsigned long now_us = _micros();
// calculate the sample time from last call
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
// quick fix for strange cases (micros overflow + timestamp not defined)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// calculate the necessary angle to achieve target velocity
shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts);
// for display purposes
shaft_velocity = target_velocity;
// use voltage limit or current limit
float Uq = voltage_limit;
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
// set the maximal allowed voltage (voltage_limit) with the necessary angle
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs));
// save timestamp for next call
open_loop_timestamp = now_us;
return Uq;
}
// Function (iterative) generating open loop movement towards the target angle
// - target_angle - rad
// it uses voltage_limit and velocity_limit variables
float BLDCMotor::angleOpenloop(float target_angle){
// get current timestamp
unsigned long now_us = _micros();
// calculate the sample time from last call
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
// quick fix for strange cases (micros overflow + timestamp not defined)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// calculate the necessary angle to move from current position towards target angle
// with maximal velocity (velocity_limit)
// TODO sensor precision: this calculation is not numerically precise. The angle can grow to the point
// where small position changes are no longer captured by the precision of floats
// when the total position is large.
if(abs( target_angle - shaft_angle ) > abs(velocity_limit*Ts)){
shaft_angle += _sign(target_angle - shaft_angle) * abs( velocity_limit )*Ts;
shaft_velocity = velocity_limit;
}else{
shaft_angle = target_angle;
shaft_velocity = 0;
}
// use voltage limit or current limit
float Uq = voltage_limit;
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
// set the maximal allowed voltage (voltage_limit) with the necessary angle
// sensor precision: this calculation is OK due to the normalisation
setPhaseVoltage(Uq, 0, _electricalAngle(_normalizeAngle(shaft_angle), pole_pairs));
// save timestamp for next call
open_loop_timestamp = now_us;
return Uq;
}
+112
View File
@@ -0,0 +1,112 @@
#ifndef BLDCMotor_h
#define BLDCMotor_h
#include "Arduino.h"
#include "common/base_classes/FOCMotor.h"
#include "common/base_classes/Sensor.h"
#include "common/base_classes/BLDCDriver.h"
#include "common/foc_utils.h"
#include "common/time_utils.h"
#include "common/defaults.h"
/**
BLDC motor class
*/
class BLDCMotor: public FOCMotor
{
public:
/**
BLDCMotor class constructor
@param pp pole pairs number
@param R motor phase resistance
*/
BLDCMotor(int pp, float R = NOT_SET);
/**
* Function linking a motor and a foc driver
*
* @param driver BLDCDriver class implementing all the hardware specific functions necessary PWM setting
*/
void linkDriver(BLDCDriver* driver);
/**
* BLDCDriver link:
* - 3PWM
* - 6PWM
*/
BLDCDriver* driver;
/** Motor hardware init function */
void init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
/**
* Function initializing FOC algorithm
* and aligning sensor's and motors' zero position
*/
int initFOC( float zero_electric_offset = NOT_SET , Direction sensor_direction = Direction::CW) override;
/**
* Function running FOC algorithm in real-time
* it calculates the gets motor angle and sets the appropriate voltages
* to the phase pwm signals
* - the faster you can run it the better Arduino UNO ~1ms, Bluepill ~ 100us
*/
void loopFOC() override;
/**
* Function executing the control loops set by the controller parameter of the BLDCMotor.
*
* @param target Either voltage, angle or velocity based on the motor.controller
* If it is not set the motor will use the target set in its variable motor.target
*
* This function doesn't need to be run upon each loop execution - depends of the use case
*/
void move(float target = NOT_SET) override;
float Ua, Ub, Uc;//!< Current phase voltages Ua,Ub and Uc set to motor
float Ualpha, Ubeta; //!< Phase voltages U alpha and U beta used for inverse Park and Clarke transform
private:
// FOC methods
/**
* Method using FOC to set Uq to the motor at the optimal angle
* Heart of the FOC algorithm
*
* @param Uq Current voltage in q axis to set to the motor
* @param Ud Current voltage in d axis to set to the motor
* @param angle_el current electrical angle of the motor
*/
void setPhaseVoltage(float Uq, float Ud, float angle_el);
/** Sensor alignment to electrical 0 angle of the motor */
int alignSensor();
/** Current sense and motor phase alignment */
int alignCurrentSense();
/** Motor and sensor alignment to the sensors absolute 0 angle */
int absoluteZeroSearch();
// Open loop motion control
/**
* Function (iterative) generating open loop movement for target velocity
* it uses voltage_limit variable
*
* @param target_velocity - rad/s
*/
float velocityOpenloop(float target_velocity);
/**
* Function (iterative) generating open loop movement towards the target angle
* it uses voltage_limit and velocity_limit variables
*
* @param target_angle - rad
*/
float angleOpenloop(float target_angle);
// open loop variables
long open_loop_timestamp;
};
#endif
+118
View File
@@ -0,0 +1,118 @@
/*!
* @file SimpleFOC.h
*
* @mainpage Simple Field Oriented Control BLDC motor control library
*
* @section intro_sec Introduction
*
* Proper low-cost and low-power FOC supporting boards are very hard to find these days and even may not exist.<br> Even harder to find is a stable and simple FOC algorithm code capable of running on Arduino devices. Therefore this is an attempt to:
* - Demystify FOC algorithm and make a robust but simple Arduino library: Arduino SimpleFOC library
* - Develop a modular BLDC driver board: Arduino SimpleFOC shield.
*
* @section features Features
* - Arduino compatible: Arduino library code
* - Easy to setup and configure:
* - Easy hardware configuration
* - Easy tuning the control loops
* - Modular:
* - Supports as many sensors , BLDC motors and driver boards as possible
* - Supports as many application requirements as possible
* - Plug & play: Arduino SimpleFOC shield
*
* @section dependencies Supported Hardware
* - Motors
* - BLDC motors
* - Stepper motors
* - Drivers
* - BLDC drivers
* - Gimbal drivers
* - Stepper drivers
* - Position sensors
* - Encoders
* - Magnetic sensors
* - Hall sensors
* - Open-loop control
* - Microcontrollers
* - Arduino
* - STM32
* - ESP32
* - Teensy
*
* @section example_code Example code
* @code
#include <SimpleFOC.h>
// BLDCMotor( pole_pairs )
BLDCMotor motor = BLDCMotor(11);
// BLDCDriver( pin_pwmA, pin_pwmB, pin_pwmC, enable (optional) )
BLDCDriver3PWM motor = BLDCDriver3PWM(9, 10, 11, 8);
// Encoder(pin_A, pin_B, CPR)
Encoder encoder = Encoder(2, 3, 2048);
// channel A and B callbacks
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void setup() {
// initialize encoder hardware
encoder.init();
// hardware interrupt enable
encoder.enableInterrupts(doA, doB);
// link the motor to the sensor
motor.linkSensor(&encoder);
// power supply voltage [V]
driver.voltage_power_supply = 12;
// initialise driver hardware
driver.init();
// link driver
motor.linkDriver(&driver);
// set control loop type to be used
motor.controller = MotionControlType::velocity;
// initialize motor
motor.init();
// align encoder and start FOC
motor.initFOC();
}
void loop() {
// FOC algorithm function
motor.loopFOC();
// velocity control loop function
// setting the target velocity or 2rad/s
motor.move(2);
}
* @endcode
*
* @section license License
*
* MIT license, all text here must be included in any redistribution.
*
*/
#ifndef SIMPLEFOC_H
#define SIMPLEFOC_H
#include "BLDCMotor.h"
#include "StepperMotor.h"
#include "sensors/Encoder.h"
#include "sensors/MagneticSensorSPI.h"
#include "sensors/MagneticSensorI2C.h"
#include "sensors/MagneticSensorAnalog.h"
#include "sensors/MagneticSensorPWM.h"
#include "sensors/HallSensor.h"
#include "sensors/GenericSensor.h"
#include "drivers/BLDCDriver3PWM.h"
#include "drivers/BLDCDriver6PWM.h"
#include "drivers/StepperDriver4PWM.h"
#include "drivers/StepperDriver2PWM.h"
#include "current_sense/InlineCurrentSense.h"
#include "current_sense/LowsideCurrentSense.h"
#include "current_sense/GenericCurrentSense.h"
#include "communication/Commander.h"
#include "communication/StepDirListener.h"
#endif
+417
View File
@@ -0,0 +1,417 @@
#include "StepperMotor.h"
// StepperMotor(int pp)
// - pp - pole pair number
// - R - motor phase resistance
StepperMotor::StepperMotor(int pp, float _R)
: FOCMotor()
{
// number od pole pairs
pole_pairs = pp;
// save phase resistance number
phase_resistance = _R;
// torque control type is voltage by default
// current and foc_current not supported yet
torque_controller = TorqueControlType::voltage;
}
/**
Link the driver which controls the motor
*/
void StepperMotor::linkDriver(StepperDriver* _driver) {
driver = _driver;
}
// init hardware pins
void StepperMotor::init() {
if (!driver || !driver->initialized) {
motor_status = FOCMotorStatus::motor_init_failed;
if(monitor_port) monitor_port->println(F("MOT: Init not possible, driver not initialized"));
}
motor_status = FOCMotorStatus::motor_initializing;
if(monitor_port) monitor_port->println(F("MOT: Init"));
// if set the phase resistance of the motor use current limit to calculate the voltage limit
if(_isset(phase_resistance)) {
float new_voltage_limit = current_limit * (phase_resistance); // v_lim = current_lim / (3/2 phase resistance) - worst case
// use it if it is less then voltage_limit set by the user
voltage_limit = new_voltage_limit < voltage_limit ? new_voltage_limit : voltage_limit;
}
// sanity check for the voltage limit configuration
if(voltage_limit > driver->voltage_limit) voltage_limit = driver->voltage_limit;
// constrain voltage for sensor alignment
if(voltage_sensor_align > voltage_limit) voltage_sensor_align = voltage_limit;
// update the controller limits
if(_isset(phase_resistance)){
// velocity control loop controls current
PID_velocity.limit = current_limit;
}else{
PID_velocity.limit = voltage_limit;
}
P_angle.limit = velocity_limit;
_delay(500);
// enable motor
if(monitor_port) monitor_port->println(F("MOT: Enable driver."));
enable();
_delay(500);
motor_status = FOCMotorStatus::motor_uncalibrated;
}
// disable motor driver
void StepperMotor::disable()
{
// set zero to PWM
driver->setPwm(0, 0);
// disable driver
driver->disable();
// motor status update
enabled = 0;
}
// enable motor driver
void StepperMotor::enable()
{
// disable enable
driver->enable();
// set zero to PWM
driver->setPwm(0, 0);
// motor status update
enabled = 1;
}
/**
FOC functions
*/
// FOC initialization function
int StepperMotor::initFOC( float zero_electric_offset, Direction _sensor_direction ) {
int exit_flag = 1;
motor_status = FOCMotorStatus::motor_calibrating;
// align motor if necessary
// alignment necessary for encoders!
if(_isset(zero_electric_offset)){
// abosolute zero offset provided - no need to align
zero_electric_angle = zero_electric_offset;
// set the sensor direction - default CW
sensor_direction = _sensor_direction;
}
// sensor and motor alignment - can be skipped
// by setting motor.sensor_direction and motor.zero_electric_angle
_delay(500);
if(sensor){
exit_flag *= alignSensor();
// added the shaft_angle update
sensor->update();
shaft_angle = sensor->getAngle();
}else if(monitor_port) monitor_port->println(F("MOT: No sensor."));
if(exit_flag){
if(monitor_port) monitor_port->println(F("MOT: Ready."));
motor_status = FOCMotorStatus::motor_ready;
}else{
if(monitor_port) monitor_port->println(F("MOT: Init FOC failed."));
motor_status = FOCMotorStatus::motor_calib_failed;
disable();
}
return exit_flag;
}
// Encoder alignment to electrical 0 angle
int StepperMotor::alignSensor() {
int exit_flag = 1; //success
if(monitor_port) monitor_port->println(F("MOT: Align sensor."));
// if unknown natural direction
if(!_isset(sensor_direction)){
// check if sensor needs zero search
if(sensor->needsSearch()) exit_flag = absoluteZeroSearch();
// stop init if not found index
if(!exit_flag) return exit_flag;
// find natural direction
// move one electrical revolution forward
for (int i = 0; i <=500; i++ ) {
float angle = _3PI_2 + _2PI * i / 500.0f;
setPhaseVoltage(voltage_sensor_align, 0, angle);
_delay(2);
}
// take and angle in the middle
sensor->update();
float mid_angle = sensor->getAngle();
// move one electrical revolution backwards
for (int i = 500; i >=0; i-- ) {
float angle = _3PI_2 + _2PI * i / 500.0f ;
setPhaseVoltage(voltage_sensor_align, 0, angle);
_delay(2);
}
sensor->update();
float end_angle = sensor->getAngle();
setPhaseVoltage(0, 0, 0);
_delay(200);
// determine the direction the sensor moved
if (mid_angle == end_angle) {
if(monitor_port) monitor_port->println(F("MOT: Failed to notice movement"));
return 0; // failed calibration
} else if (mid_angle < end_angle) {
if(monitor_port) monitor_port->println(F("MOT: sensor_direction==CCW"));
sensor_direction = Direction::CCW;
} else{
if(monitor_port) monitor_port->println(F("MOT: sensor_direction==CW"));
sensor_direction = Direction::CW;
}
// check pole pair number
if(monitor_port) monitor_port->print(F("MOT: PP check: "));
float moved = fabs(mid_angle - end_angle);
if( fabs(moved*pole_pairs - _2PI) > 0.5f ) { // 0.5f is arbitrary number it can be lower or higher!
if(monitor_port) monitor_port->print(F("fail - estimated pp:"));
if(monitor_port) monitor_port->println(_2PI/moved,4);
}else if(monitor_port) monitor_port->println(F("OK!"));
}else if(monitor_port) monitor_port->println(F("MOT: Skip dir calib."));
// zero electric angle not known
if(!_isset(zero_electric_angle)){
// align the electrical phases of the motor and sensor
// set angle -90(270 = 3PI/2) degrees
setPhaseVoltage(voltage_sensor_align, 0, _3PI_2);
_delay(700);
// read the sensor
sensor->update();
// get the current zero electric angle
zero_electric_angle = 0;
zero_electric_angle = electricalAngle();
_delay(20);
if(monitor_port){
monitor_port->print(F("MOT: Zero elec. angle: "));
monitor_port->println(zero_electric_angle);
}
// stop everything
setPhaseVoltage(0, 0, 0);
_delay(200);
}else if(monitor_port) monitor_port->println(F("MOT: Skip offset calib."));
return exit_flag;
}
// Encoder alignment the absolute zero angle
// - to the index
int StepperMotor::absoluteZeroSearch() {
if(monitor_port) monitor_port->println(F("MOT: Index search..."));
// search the absolute zero with small velocity
float limit_vel = velocity_limit;
float limit_volt = voltage_limit;
velocity_limit = velocity_index_search;
voltage_limit = voltage_sensor_align;
shaft_angle = 0;
while(sensor->needsSearch() && shaft_angle < _2PI){
angleOpenloop(1.5f*_2PI);
// call important for some sensors not to loose count
// not needed for the search
sensor->update();
}
// disable motor
setPhaseVoltage(0, 0, 0);
// reinit the limits
velocity_limit = limit_vel;
voltage_limit = limit_volt;
// check if the zero found
if(monitor_port){
if(sensor->needsSearch()) monitor_port->println(F("MOT: Error: Not found!"));
else monitor_port->println(F("MOT: Success!"));
}
return !sensor->needsSearch();
}
// Iterative function looping FOC algorithm, setting Uq on the Motor
// The faster it can be run the better
void StepperMotor::loopFOC() {
// update sensor - do this even in open-loop mode, as user may be switching between modes and we could lose track
// of full rotations otherwise.
if (sensor) sensor->update();
// if open-loop do nothing
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop ) return;
// shaft angle
shaft_angle = shaftAngle();
// if disabled do nothing
if(!enabled) return;
// Needs the update() to be called first
// This function will not have numerical issues because it uses Sensor::getMechanicalAngle()
// which is in range 0-2PI
electrical_angle = electricalAngle();
// set the phase voltage - FOC heart function :)
setPhaseVoltage(voltage.q, voltage.d, electrical_angle);
}
// Iterative function running outer loop of the FOC algorithm
// Behavior of this function is determined by the motor.controller variable
// It runs either angle, velocity or voltage loop
// - needs to be called iteratively it is asynchronous function
// - if target is not set it uses motor.target value
void StepperMotor::move(float new_target) {
// downsampling (optional)
if(motion_cnt++ < motion_downsample) return;
motion_cnt = 0;
// shaft angle/velocity need the update() to be called first
// get shaft angle
// TODO sensor precision: the shaft_angle actually stores the complete position, including full rotations, as a float
// For this reason it is NOT precise when the angles become large.
// Additionally, the way LPF works on angle is a precision issue, and the angle-LPF is a problem
// when switching to a 2-component representation.
if( controller!=MotionControlType::angle_openloop && controller!=MotionControlType::velocity_openloop )
shaft_angle = shaftAngle(); // read value even if motor is disabled to keep the monitoring updated but not in openloop mode
// get angular velocity
shaft_velocity = shaftVelocity(); // read value even if motor is disabled to keep the monitoring updated
// if disabled do nothing
if(!enabled) return;
// set internal target variable
if(_isset(new_target) ) target = new_target;
// choose control loop
switch (controller) {
case MotionControlType::torque:
if(!_isset(phase_resistance)) voltage.q = target; // if voltage torque control
else voltage.q = target*phase_resistance;
voltage.d = 0;
break;
case MotionControlType::angle:
// angle set point
shaft_angle_sp = target;
// calculate velocity set point
shaft_velocity_sp = P_angle( shaft_angle_sp - shaft_angle );
// calculate the torque command
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
// if torque controlled through voltage
// use voltage if phase-resistance not provided
if(!_isset(phase_resistance)) voltage.q = current_sp;
else voltage.q = current_sp*phase_resistance;
voltage.d = 0;
break;
case MotionControlType::velocity:
// velocity set point
shaft_velocity_sp = target;
// calculate the torque command
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if current/foc_current torque control
// if torque controlled through voltage control
// use voltage if phase-resistance not provided
if(!_isset(phase_resistance)) voltage.q = current_sp;
else voltage.q = current_sp*phase_resistance;
voltage.d = 0;
break;
case MotionControlType::velocity_openloop:
// velocity control in open loop
shaft_velocity_sp = target;
voltage.q = velocityOpenloop(shaft_velocity_sp); // returns the voltage that is set to the motor
voltage.d = 0;
break;
case MotionControlType::angle_openloop:
// angle control in open loop
shaft_angle_sp = target;
voltage.q = angleOpenloop(shaft_angle_sp); // returns the voltage that is set to the motor
voltage.d = 0;
break;
}
}
// Method using FOC to set Uq and Ud to the motor at the optimal angle
// Function implementing Sine PWM algorithms
// - space vector not implemented yet
//
// Function using sine approximation
// regular sin + cos ~300us (no memory usaage)
// approx _sin + _cos ~110us (400Byte ~ 20% of memory)
void StepperMotor::setPhaseVoltage(float Uq, float Ud, float angle_el) {
// Sinusoidal PWM modulation
// Inverse Park transformation
// angle normalization in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el);
float _ca = _cos(angle_el);
float _sa = _sin(angle_el);
// Inverse park transform
Ualpha = _ca * Ud - _sa * Uq; // -sin(angle) * Uq;
Ubeta = _sa * Ud + _ca * Uq; // cos(angle) * Uq;
// set the voltages in hardware
driver->setPwm(Ualpha, Ubeta);
}
// Function (iterative) generating open loop movement for target velocity
// - target_velocity - rad/s
// it uses voltage_limit variable
float StepperMotor::velocityOpenloop(float target_velocity){
// get current timestamp
unsigned long now_us = _micros();
// calculate the sample time from last call
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
// quick fix for strange cases (micros overflow + timestamp not defined)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// calculate the necessary angle to achieve target velocity
shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts);
// for display purposes
shaft_velocity = target_velocity;
// use voltage limit or current limit
float Uq = voltage_limit;
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
// set the maximal allowed voltage (voltage_limit) with the necessary angle
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs));
// save timestamp for next call
open_loop_timestamp = now_us;
return Uq;
}
// Function (iterative) generating open loop movement towards the target angle
// - target_angle - rad
// it uses voltage_limit and velocity_limit variables
float StepperMotor::angleOpenloop(float target_angle){
// get current timestamp
unsigned long now_us = _micros();
// calculate the sample time from last call
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
// quick fix for strange cases (micros overflow + timestamp not defined)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// calculate the necessary angle to move from current position towards target angle
// with maximal velocity (velocity_limit)
if(abs( target_angle - shaft_angle ) > abs(velocity_limit*Ts)){
shaft_angle += _sign(target_angle - shaft_angle) * abs( velocity_limit )*Ts;
shaft_velocity = velocity_limit;
}else{
shaft_angle = target_angle;
shaft_velocity = 0;
}
// use voltage limit or current limit
float Uq = voltage_limit;
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
// set the maximal allowed voltage (voltage_limit) with the necessary angle
setPhaseVoltage(Uq, 0, _electricalAngle((shaft_angle), pole_pairs));
// save timestamp for next call
open_loop_timestamp = now_us;
return Uq;
}
+118
View File
@@ -0,0 +1,118 @@
/**
* @file StepperMotor.h
*
*/
#ifndef StepperMotor_h
#define StepperMotor_h
#include "Arduino.h"
#include "common/base_classes/FOCMotor.h"
#include "common/base_classes/StepperDriver.h"
#include "common/base_classes/Sensor.h"
#include "common/foc_utils.h"
#include "common/time_utils.h"
#include "common/defaults.h"
/**
Stepper Motor class
*/
class StepperMotor: public FOCMotor
{
public:
/**
StepperMotor class constructor
@param pp pole pair number
@param R motor phase resistance
*/
StepperMotor(int pp, float R = NOT_SET);
/**
* Function linking a motor and a foc driver
*
* @param driver StepperDriver class implementing all the hardware specific functions necessary PWM setting
*/
void linkDriver(StepperDriver* driver);
/**
* StepperDriver link:
* - 4PWM - L298N for example
*/
StepperDriver* driver;
/** Motor hardware init function */
void init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
/**
* Function initializing FOC algorithm
* and aligning sensor's and motors' zero position
*
* - If zero_electric_offset parameter is set the alignment procedure is skipped
*
* @param zero_electric_offset value of the sensors absolute position electrical offset in respect to motor's electrical 0 position.
* @param sensor_direction sensor natural direction - default is CW
*
*/
int initFOC( float zero_electric_offset = NOT_SET , Direction sensor_direction = Direction::CW) override;
/**
* Function running FOC algorithm in real-time
* it calculates the gets motor angle and sets the appropriate voltages
* to the phase pwm signals
* - the faster you can run it the better Arduino UNO ~1ms, Bluepill ~ 100us
*/
void loopFOC() override;
/**
* Function executing the control loops set by the controller parameter of the StepperMotor.
*
* @param target Either voltage, angle or velocity based on the motor.controller
* If it is not set the motor will use the target set in its variable motor.target
*
* This function doesn't need to be run upon each loop execution - depends of the use case
*/
void move(float target = NOT_SET) override;
float Ualpha,Ubeta; //!< Phase voltages U alpha and U beta used for inverse Park and Clarke transform
private:
// FOC methods
/**
* Method using FOC to set Uq to the motor at the optimal angle
* Heart of the FOC algorithm
*
* @param Uq Current voltage in q axis to set to the motor
* @param Ud Current voltage in d axis to set to the motor
* @param angle_el current electrical angle of the motor
*/
void setPhaseVoltage(float Uq, float Ud , float angle_el);
/** Sensor alignment to electrical 0 angle of the motor */
int alignSensor();
/** Motor and sensor alignment to the sensors absolute 0 angle */
int absoluteZeroSearch();
// Open loop motion control
/**
* Function (iterative) generating open loop movement for target velocity
* it uses voltage_limit variable
*
* @param target_velocity - rad/s
*/
float velocityOpenloop(float target_velocity);
/**
* Function (iterative) generating open loop movement towards the target angle
* it uses voltage_limit and velocity_limit variables
*
* @param target_angle - rad
*/
float angleOpenloop(float target_angle);
// open loop variables
long open_loop_timestamp;
};
#endif
@@ -0,0 +1,46 @@
#ifndef BLDCDRIVER_H
#define BLDCDRIVER_H
#include "Arduino.h"
class BLDCDriver{
public:
/** Initialise hardware */
virtual int init() = 0;
/** Enable hardware */
virtual void enable() = 0;
/** Disable hardware */
virtual void disable() = 0;
long pwm_frequency; //!< pwm frequency value in hertz
float voltage_power_supply; //!< power supply voltage
float voltage_limit; //!< limiting voltage set to the motor
float dc_a; //!< currently set duty cycle on phaseA
float dc_b; //!< currently set duty cycle on phaseB
float dc_c; //!< currently set duty cycle on phaseC
bool initialized = false; // true if driver was successfully initialized
/**
* Set phase voltages to the harware
*
* @param Ua - phase A voltage
* @param Ub - phase B voltage
* @param Uc - phase C voltage
*/
virtual void setPwm(float Ua, float Ub, float Uc) = 0;
/**
* Set phase state, enable/disable
*
* @param sc - phase A state : active / disabled ( high impedance )
* @param sb - phase B state : active / disabled ( high impedance )
* @param sa - phase C state : active / disabled ( high impedance )
*/
virtual void setPhaseState(int sa, int sb, int sc) = 0;
};
#endif
@@ -0,0 +1,67 @@
#include "CurrentSense.h"
// get current magnitude
// - absolute - if no electrical_angle provided
// - signed - if angle provided
float CurrentSense::getDCCurrent(float motor_electrical_angle){
// read current phase currents
PhaseCurrent_s current = getPhaseCurrents();
// currnet sign - if motor angle not provided the magnitude is always positive
float sign = 1;
// calculate clarke transform
float i_alpha, i_beta;
if(!current.c){
// if only two measured currents
i_alpha = current.a;
i_beta = _1_SQRT3 * current.a + _2_SQRT3 * current.b;
}else{
// signal filtering using identity a + b + c = 0. Assumes measurement error is normally distributed.
float mid = (1.f/3) * (current.a + current.b + current.c);
float a = current.a - mid;
float b = current.b - mid;
i_alpha = a;
i_beta = _1_SQRT3 * a + _2_SQRT3 * b;
}
// if motor angle provided function returns signed value of the current
// determine the sign of the current
// sign(atan2(current.q, current.d)) is the same as c.q > 0 ? 1 : -1
if(motor_electrical_angle)
sign = (i_beta * _cos(motor_electrical_angle) - i_alpha*_sin(motor_electrical_angle)) > 0 ? 1 : -1;
// return current magnitude
return sign*_sqrt(i_alpha*i_alpha + i_beta*i_beta);
}
// function used with the foc algorihtm
// calculating DQ currents from phase currents
// - function calculating park and clarke transform of the phase currents
// - using getPhaseCurrents internally
DQCurrent_s CurrentSense::getFOCCurrents(float angle_el){
// read current phase currents
PhaseCurrent_s current = getPhaseCurrents();
// calculate clarke transform
float i_alpha, i_beta;
if(!current.c){
// if only two measured currents
i_alpha = current.a;
i_beta = _1_SQRT3 * current.a + _2_SQRT3 * current.b;
} else {
// signal filtering using identity a + b + c = 0. Assumes measurement error is normally distributed.
float mid = (1.f/3) * (current.a + current.b + current.c);
float a = current.a - mid;
float b = current.b - mid;
i_alpha = a;
i_beta = _1_SQRT3 * a + _2_SQRT3 * b;
}
// calculate park transform
float ct = _cos(angle_el);
float st = _sin(angle_el);
DQCurrent_s return_current;
return_current.d = i_alpha * ct + i_beta * st;
return_current.q = i_beta * ct - i_alpha * st;
return return_current;
}
@@ -0,0 +1,67 @@
#ifndef CURRENTSENSE_H
#define CURRENTSENSE_H
#include "BLDCDriver.h"
#include "../foc_utils.h"
/**
* Current sensing abstract class defintion
* Each current sensoring implementation needs to extend this interface
*/
class CurrentSense{
public:
/**
* Function intialising the CurrentSense class
* All the necessary intialisations of adc and sync should be implemented here
*/
virtual void init() = 0;
/**
* Function intended to implement all that is needed to sync and current sensing with the driver.
* If no such thing is needed it can be left empty (return 1)
* @returns - 0 - for failure & 1 - for success
*/
virtual int driverSync(BLDCDriver *driver) = 0;
// calibration variables
bool skip_align = false; //!< variable signaling that the phase current direction should be verified during initFOC()
/**
* Function intended to verify if:
* - phase current are oriented properly
* - if their order is the same as driver phases
*
* This function corrects the alignment errors if possible ans if no such thing is needed it can be left empty (return 1)
* @returns - 0 - for failure & positive number (with status) - for success
*/
virtual int driverAlign(BLDCDriver *driver, float voltage) = 0;
/**
* Function rading the phase currents a, b and c
* This function will be used with the foc control throught the function
* CurrentSense::getFOCCurrents(electrical_angle)
* - it returns current c equal to 0 if only two phase measurements available
*
* @return PhaseCurrent_s current values
*/
virtual PhaseCurrent_s getPhaseCurrents() = 0;
/**
* Function reading the magnitude of the current set to the motor
* It returns the abosolute or signed magnitude if possible
* It can receive the motor electrical angle to help with calculation
* This function is used with the current control (not foc)
*
* @param angle_el - electrical angle of the motor (optional)
*/
virtual float getDCCurrent(float angle_el = 0);
/**
* Function used for FOC contorl, it reads the DQ currents of the motor
* It uses the function getPhaseCurrents internally
*
* @param angle_el - motor electrical angle
*/
DQCurrent_s getFOCCurrents(float angle_el);
};
#endif
@@ -0,0 +1,141 @@
#include "FOCMotor.h"
/**
* Default constructor - setting all variabels to default values
*/
FOCMotor::FOCMotor()
{
// maximum angular velocity to be used for positioning
velocity_limit = DEF_VEL_LIM;
// maximum voltage to be set to the motor
voltage_limit = DEF_POWER_SUPPLY;
// not set on the begining
current_limit = DEF_CURRENT_LIM;
// index search velocity
velocity_index_search = DEF_INDEX_SEARCH_TARGET_VELOCITY;
// sensor and motor align voltage
voltage_sensor_align = DEF_VOLTAGE_SENSOR_ALIGN;
// default modulation is SinePWM
foc_modulation = FOCModulationType::SinePWM;
// default target value
target = 0;
voltage.d = 0;
voltage.q = 0;
// current target values
current_sp = 0;
current.q = 0;
current.d = 0;
//monitor_port
monitor_port = nullptr;
//sensor
sensor = nullptr;
//current sensor
current_sense = nullptr;
}
/**
Sensor linking method
*/
void FOCMotor::linkSensor(Sensor* _sensor) {
sensor = _sensor;
}
/**
CurrentSense linking method
*/
void FOCMotor::linkCurrentSense(CurrentSense* _current_sense) {
current_sense = _current_sense;
}
// shaft angle calculation
float FOCMotor::shaftAngle() {
// if no sensor linked return previous value ( for open loop )
if(!sensor) return shaft_angle;
return sensor_direction*LPF_angle(sensor->getAngle()) - sensor_offset;
}
// shaft velocity calculation
float FOCMotor::shaftVelocity() {
// if no sensor linked return previous value ( for open loop )
if(!sensor) return shaft_velocity;
return sensor_direction*LPF_velocity(sensor->getVelocity());
}
float FOCMotor::electricalAngle(){
// if no sensor linked return previous value ( for open loop )
if(!sensor) return electrical_angle;
return _normalizeAngle( (float)(sensor_direction * pole_pairs) * sensor->getMechanicalAngle() - zero_electric_angle );
}
/**
* Monitoring functions
*/
// function implementing the monitor_port setter
void FOCMotor::useMonitoring(Print &print){
monitor_port = &print; //operate on the address of print
if(monitor_port ) monitor_port->println(F("MOT: Monitor enabled!"));
}
// utility function intended to be used with serial plotter to monitor motor variables
// significantly slowing the execution down!!!!
void FOCMotor::monitor() {
if( !monitor_downsample || monitor_cnt++ < monitor_downsample ) return;
monitor_cnt = 0;
if(!monitor_port) return;
bool printed = 0;
if(monitor_variables & _MON_TARGET){
monitor_port->print(target,4);
monitor_port->print("\t");
printed= true;
}
if(monitor_variables & _MON_VOLT_Q) {
monitor_port->print(voltage.q,4);
monitor_port->print("\t");
printed= true;
}
if(monitor_variables & _MON_VOLT_D) {
monitor_port->print(voltage.d,4);
monitor_port->print("\t");
printed= true;
}
// read currents if possible - even in voltage mode (if current_sense available)
if(monitor_variables & _MON_CURR_Q || monitor_variables & _MON_CURR_D) {
DQCurrent_s c{0,0};
if(current_sense){
if(torque_controller == TorqueControlType::foc_current) c = current;
else{
c = current_sense->getFOCCurrents(electrical_angle);
c.q = LPF_current_q(c.q);
c.d = LPF_current_d(c.d);
}
}
if(monitor_variables & _MON_CURR_Q) {
monitor_port->print(c.q*1000, 2); // mAmps
monitor_port->print("\t");
printed= true;
}
if(monitor_variables & _MON_CURR_D) {
monitor_port->print(c.d*1000, 2); // mAmps
monitor_port->print("\t");
printed= true;
}
}
if(monitor_variables & _MON_VEL) {
monitor_port->print(shaft_velocity,4);
monitor_port->print("\t");
printed= true;
}
if(monitor_variables & _MON_ANGLE) {
monitor_port->print(shaft_angle,4);
printed= true;
}
if(printed) monitor_port->println();
}
@@ -0,0 +1,236 @@
#ifndef FOCMOTOR_H
#define FOCMOTOR_H
#include "Arduino.h"
#include "Sensor.h"
#include "CurrentSense.h"
#include "../time_utils.h"
#include "../foc_utils.h"
#include "../defaults.h"
#include "../pid.h"
#include "../lowpass_filter.h"
// monitoring bitmap
#define _MON_TARGET 0b1000000 // monitor target value
#define _MON_VOLT_Q 0b0100000 // monitor voltage q value
#define _MON_VOLT_D 0b0010000 // monitor voltage d value
#define _MON_CURR_Q 0b0001000 // monitor current q value - if measured
#define _MON_CURR_D 0b0000100 // monitor current d value - if measured
#define _MON_VEL 0b0000010 // monitor velocity value
#define _MON_ANGLE 0b0000001 // monitor angle value
/**
* Motiron control type
*/
enum MotionControlType : uint8_t {
torque = 0x00, //!< Torque control
velocity = 0x01, //!< Velocity motion control
angle = 0x02, //!< Position/angle motion control
velocity_openloop = 0x03,
angle_openloop = 0x04
};
/**
* Motiron control type
*/
enum TorqueControlType : uint8_t {
voltage = 0x00, //!< Torque control using voltage
dc_current = 0x01, //!< Torque control using DC current (one current magnitude)
foc_current = 0x02, //!< torque control using dq currents
};
/**
* FOC modulation type
*/
enum FOCModulationType : uint8_t {
SinePWM = 0x00, //!< Sinusoidal PWM modulation
SpaceVectorPWM = 0x01, //!< Space vector modulation method
Trapezoid_120 = 0x02,
Trapezoid_150 = 0x03,
};
enum FOCMotorStatus : uint8_t {
motor_uninitialized = 0x00, //!< Motor is not yet initialized
motor_initializing = 0x01, //!< Motor intiialization is in progress
motor_uncalibrated = 0x02, //!< Motor is initialized, but not calibrated (open loop possible)
motor_calibrating = 0x03, //!< Motor calibration in progress
motor_ready = 0x04, //!< Motor is initialized and calibrated (closed loop possible)
motor_error = 0x08, //!< Motor is in error state (recoverable, e.g. overcurrent protection active)
motor_calib_failed = 0x0E, //!< Motor calibration failed (possibly recoverable)
motor_init_failed = 0x0F, //!< Motor initialization failed (not recoverable)
};
/**
Generic motor class
*/
class FOCMotor
{
public:
/**
* Default constructor - setting all variabels to default values
*/
FOCMotor();
/** Motor hardware init function */
virtual void init()=0;
/** Motor disable function */
virtual void disable()=0;
/** Motor enable function */
virtual void enable()=0;
/**
* Function linking a motor and a sensor
*
* @param sensor Sensor class wrapper for the FOC algorihtm to read the motor angle and velocity
*/
void linkSensor(Sensor* sensor);
/**
* Function linking a motor and current sensing
*
* @param current_sense CurrentSense class wrapper for the FOC algorihtm to read the motor current measurements
*/
void linkCurrentSense(CurrentSense* current_sense);
/**
* Function initializing FOC algorithm
* and aligning sensor's and motors' zero position
*
* - If zero_electric_offset parameter is set the alignment procedure is skipped
*
* @param zero_electric_offset value of the sensors absolute position electrical offset in respect to motor's electrical 0 position.
* @param sensor_direction sensor natural direction - default is CW
*
*/
virtual int initFOC( float zero_electric_offset = NOT_SET , Direction sensor_direction = Direction::CW)=0;
/**
* Function running FOC algorithm in real-time
* it calculates the gets motor angle and sets the appropriate voltages
* to the phase pwm signals
* - the faster you can run it the better Arduino UNO ~1ms, Bluepill ~ 100us
*/
virtual void loopFOC()=0;
/**
* Function executing the control loops set by the controller parameter of the BLDCMotor.
*
* @param target Either voltage, angle or velocity based on the motor.controller
* If it is not set the motor will use the target set in its variable motor.target
*
* This function doesn't need to be run upon each loop execution - depends of the use case
*/
virtual void move(float target = NOT_SET)=0;
// State calculation methods
/** Shaft angle calculation in radians [rad] */
float shaftAngle();
/**
* Shaft angle calculation function in radian per second [rad/s]
* It implements low pass filtering
*/
float shaftVelocity();
/**
* Electrical angle calculation
*/
float electricalAngle();
// state variables
float target; //!< current target value - depends of the controller
float shaft_angle;//!< current motor angle
float electrical_angle;//!< current electrical angle
float shaft_velocity;//!< current motor velocity
float current_sp;//!< target current ( q current )
float shaft_velocity_sp;//!< current target velocity
float shaft_angle_sp;//!< current target angle
DQVoltage_s voltage;//!< current d and q voltage set to the motor
DQCurrent_s current;//!< current d and q current measured
// motor configuration parameters
float voltage_sensor_align;//!< sensor and motor align voltage parameter
float velocity_index_search;//!< target velocity for index search
// motor physical parameters
float phase_resistance; //!< motor phase resistance
int pole_pairs;//!< motor pole pairs number
// limiting variables
float voltage_limit; //!< Voltage limitting variable - global limit
float current_limit; //!< Current limitting variable - global limit
float velocity_limit; //!< Velocity limitting variable - global limit
// motor status vairables
int8_t enabled = 0;//!< enabled or disabled motor flag
FOCMotorStatus motor_status = FOCMotorStatus::motor_uninitialized; //!< motor status
// pwm modulation related variables
FOCModulationType foc_modulation;//!< parameter derterniming modulation algorithm
int8_t modulation_centered = 1;//!< flag (1) centered modulation around driver limit /2 or (0) pulled to 0
// configuration structures
TorqueControlType torque_controller; //!< parameter determining the torque control type
MotionControlType controller; //!< parameter determining the control loop to be used
// controllers and low pass filters
PIDController PID_current_q{DEF_PID_CURR_P,DEF_PID_CURR_I,DEF_PID_CURR_D,DEF_PID_CURR_RAMP, DEF_POWER_SUPPLY};//!< parameter determining the q current PID config
PIDController PID_current_d{DEF_PID_CURR_P,DEF_PID_CURR_I,DEF_PID_CURR_D,DEF_PID_CURR_RAMP, DEF_POWER_SUPPLY};//!< parameter determining the d current PID config
LowPassFilter LPF_current_q{DEF_CURR_FILTER_Tf};//!< parameter determining the current Low pass filter configuration
LowPassFilter LPF_current_d{DEF_CURR_FILTER_Tf};//!< parameter determining the current Low pass filter configuration
PIDController PID_velocity{DEF_PID_VEL_P,DEF_PID_VEL_I,DEF_PID_VEL_D,DEF_PID_VEL_RAMP,DEF_PID_VEL_LIMIT};//!< parameter determining the velocity PID configuration
PIDController P_angle{DEF_P_ANGLE_P,0,0,0,DEF_VEL_LIM}; //!< parameter determining the position PID configuration
LowPassFilter LPF_velocity{DEF_VEL_FILTER_Tf};//!< parameter determining the velocity Low pass filter configuration
LowPassFilter LPF_angle{0.0};//!< parameter determining the angle low pass filter configuration
unsigned int motion_downsample = DEF_MOTION_DOWNSMAPLE; //!< parameter defining the ratio of downsampling for move commad
unsigned int motion_cnt = 0; //!< counting variable for downsampling for move commad
// sensor related variabels
float sensor_offset; //!< user defined sensor zero offset
float zero_electric_angle = NOT_SET;//!< absolute zero electric angle - if available
int sensor_direction = NOT_SET; //!< if sensor_direction == Direction::CCW then direction will be flipped to CW
/**
* Function providing BLDCMotor class with the
* Serial interface and enabling monitoring mode
*
* @param serial Monitoring Serial class reference
*/
void useMonitoring(Print &serial);
/**
* Utility function intended to be used with serial plotter to monitor motor variables
* significantly slowing the execution down!!!!
*/
void monitor();
unsigned int monitor_downsample = DEF_MON_DOWNSMAPLE; //!< show monitor outputs each monitor_downsample calls
// initial monitoring will display target, voltage, velocity and angle
uint8_t monitor_variables = _MON_TARGET | _MON_VOLT_Q | _MON_VEL | _MON_ANGLE; //!< Bit array holding the map of variables the user wants to monitor
/**
* Sensor link:
* - Encoder
* - MagneticSensor*
* - HallSensor
*/
Sensor* sensor;
/**
* CurrentSense link
*/
CurrentSense* current_sense;
// monitoring functions
Print* monitor_port; //!< Serial terminal variable if provided
private:
// monitor counting variable
unsigned int monitor_cnt = 0 ; //!< counting variable
};
#endif
@@ -0,0 +1,72 @@
#include "Sensor.h"
#include "../foc_utils.h"
#include "../time_utils.h"
// TODO add an init method to make the startup smoother by initializing internal variables to current values rather than 0
void Sensor::update() {
float val = getSensorAngle();
angle_prev_ts = _micros();
float d_angle = val - angle_prev;
// if overflow happened track it as full rotation
if(abs(d_angle) > (0.8f*_2PI) ) full_rotations += ( d_angle > 0 ) ? -1 : 1;
angle_prev = val;
}
/** get current angular velocity (rad/s) */
float Sensor::getVelocity() {
// calculate sample time
float Ts = (angle_prev_ts - vel_angle_prev_ts)*1e-6;
// quick fix for strange cases (micros overflow)
if(Ts <= 0) Ts = 1e-3f;
// velocity calculation
float vel = ( (float)(full_rotations - vel_full_rotations)*_2PI + (angle_prev - vel_angle_prev) ) / Ts;
// save variables for future pass
vel_angle_prev = angle_prev;
vel_full_rotations = full_rotations;
vel_angle_prev_ts = angle_prev_ts;
return vel;
}
void Sensor::init() {
// initialize all the internal variables of Sensor to ensure a "smooth" startup (without a 'jump' from zero)
getSensorAngle(); // call once
delayMicroseconds(1);
vel_angle_prev = getSensorAngle(); // call again
vel_angle_prev_ts = _micros();
delay(1);
getSensorAngle(); // call once
delayMicroseconds(1);
angle_prev = getSensorAngle(); // call again
angle_prev_ts = _micros();
}
float Sensor::getMechanicalAngle() {
return angle_prev;
}
float Sensor::getAngle(){
return (float)full_rotations * _2PI + angle_prev;
}
double Sensor::getPreciseAngle() {
return (double)full_rotations * (double)_2PI + (double)angle_prev;
}
int32_t Sensor::getFullRotations() {
return full_rotations;
}
int Sensor::needsSearch() {
return 0; // default false
}
@@ -0,0 +1,131 @@
#ifndef SENSOR_H
#define SENSOR_H
#include <inttypes.h>
/**
* Direction structure
*/
enum Direction : int8_t {
CW = 1, // clockwise
CCW = -1, // counter clockwise
UNKNOWN = 0 // not yet known or invalid state
};
/**
* Pullup configuration structure
*/
enum Pullup : uint8_t {
USE_INTERN = 0x00, //!< Use internal pullups
USE_EXTERN = 0x01 //!< Use external pullups
};
/**
* Sensor abstract class defintion
*
* This class is purposefully kept simple, as a base for all kinds of sensors. Currently we have
* Encoders, Magnetic Encoders and Hall Sensor implementations. This base class extracts the
* most basic common features so that a FOC driver can obtain the data it needs for operation.
*
* To implement your own sensors, create a sub-class of this class, and implement the getSensorAngle()
* method. getSensorAngle() returns a float value, in radians, representing the current shaft angle in the
* range 0 to 2*PI (one full turn).
*
* To function correctly, the sensor class update() method has to be called sufficiently quickly. Normally,
* the BLDCMotor's loopFOC() function calls it once per iteration, so you must ensure to call loopFOC() quickly
* enough, both for correct motor and sensor operation.
*
* The Sensor base class provides an implementation of getVelocity(), and takes care of counting full
* revolutions in a precise way, but if you wish you can additionally override these methods to provide more
* optimal implementations for your hardware.
*
*/
class Sensor{
public:
/**
* Get mechanical shaft angle in the range 0 to 2PI. This value will be as precise as possible with
* the hardware. Base implementation uses the values returned by update() so that
* the same values are returned until update() is called again.
*/
virtual float getMechanicalAngle();
/**
* Get current position (in rad) including full rotations and shaft angle.
* Base implementation uses the values returned by update() so that the same
* values are returned until update() is called again.
* Note that this value has limited precision as the number of rotations increases,
* because the limited precision of float can't capture the large angle of the full
* rotations and the small angle of the shaft angle at the same time.
*/
virtual float getAngle();
/**
* On architectures supporting it, this will return a double precision position value,
* which should have improved precision for large position values.
* Base implementation uses the values returned by update() so that the same
* values are returned until update() is called again.
*/
virtual double getPreciseAngle();
/**
* Get current angular velocity (rad/s)
* Can be overridden in subclasses. Base implementation uses the values
* returned by update() so that it only makes sense to call this if update()
* has been called in the meantime.
*/
virtual float getVelocity();
/**
* Get the number of full rotations
* Base implementation uses the values returned by update() so that the same
* values are returned until update() is called again.
*/
virtual int32_t getFullRotations();
/**
* Updates the sensor values by reading the hardware sensor.
* Some implementations may work with interrupts, and not need this.
* The base implementation calls getSensorAngle(), and updates internal
* fields for angle, timestamp and full rotations.
* This method must be called frequently enough to guarantee that full
* rotations are not "missed" due to infrequent polling.
* Override in subclasses if alternative behaviours are required for your
* sensor hardware.
*/
virtual void update();
/**
* returns 0 if it does need search for absolute zero
* 0 - magnetic sensor (& encoder with index which is found)
* 1 - ecoder with index (with index not found yet)
*/
virtual int needsSearch();
protected:
/**
* Get current shaft angle from the sensor hardware, and
* return it as a float in radians, in the range 0 to 2PI.
*
* This method is pure virtual and must be implemented in subclasses.
* Calling this method directly does not update the base-class internal fields.
* Use update() when calling from outside code.
*/
virtual float getSensorAngle()=0;
/**
* Call Sensor::init() from your sensor subclass's init method if you want smoother startup
* The base class init() method calls getSensorAngle() several times to initialize the internal fields
* to current values, ensuring there is no discontinuity ("jump from zero") during the first calls
* to sensor.getAngle() and sensor.getVelocity()
*/
virtual void init();
// velocity calculation variables
float angle_prev=0; // result of last call to getSensorAngle(), used for full rotations and velocity
long angle_prev_ts=0; // timestamp of last call to getAngle, used for velocity
float vel_angle_prev=0; // angle at last call to getVelocity, used for velocity
long vel_angle_prev_ts=0; // last velocity calculation timestamp
int32_t full_rotations=0; // full rotation tracking
int32_t vel_full_rotations=0; // previous full rotation value for velocity calculation
};
#endif
@@ -0,0 +1,29 @@
#ifndef STEPPERDRIVER_H
#define STEPPERDRIVER_H
class StepperDriver{
public:
/** Initialise hardware */
virtual int init() = 0;
/** Enable hardware */
virtual void enable() = 0;
/** Disable hardware */
virtual void disable() = 0;
long pwm_frequency; //!< pwm frequency value in hertz
float voltage_power_supply; //!< power supply voltage
float voltage_limit; //!< limiting voltage set to the motor
bool initialized = false; // true if driver was successfully initialized
/**
* Set phase voltages to the harware
*
* @param Ua phase A voltage
* @param Ub phase B voltage
*/
virtual void setPwm(float Ua, float Ub) = 0;
};
#endif
+49
View File
@@ -0,0 +1,49 @@
// default configuration values
// change this file to optimal values for your application
#define DEF_POWER_SUPPLY 12.0f //!< default power supply voltage
// velocity PI controller params
#define DEF_PID_VEL_P 0.5f //!< default PID controller P value
#define DEF_PID_VEL_I 10.0f //!< default PID controller I value
#define DEF_PID_VEL_D 0.0f //!< default PID controller D value
#define DEF_PID_VEL_RAMP 1000.0f //!< default PID controller voltage ramp value
#define DEF_PID_VEL_LIMIT (DEF_POWER_SUPPLY) //!< default PID controller voltage limit
// current sensing PID values
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328PB__) || defined(__AVR_ATmega2560__)
// for 16Mhz controllers like Arduino uno and mega
#define DEF_PID_CURR_P 2 //!< default PID controller P value
#define DEF_PID_CURR_I 100 //!< default PID controller I value
#define DEF_PID_CURR_D 0.0f //!< default PID controller D value
#define DEF_PID_CURR_RAMP 1000.0f //!< default PID controller voltage ramp value
#define DEF_PID_CURR_LIMIT (DEF_POWER_SUPPLY) //!< default PID controller voltage limit
#define DEF_CURR_FILTER_Tf 0.01f //!< default velocity filter time constant
#else
// for stm32, due, teensy, esp32 and similar
#define DEF_PID_CURR_P 3 //!< default PID controller P value
#define DEF_PID_CURR_I 300.0f //!< default PID controller I value
#define DEF_PID_CURR_D 0.0f //!< default PID controller D value
#define DEF_PID_CURR_RAMP 0 //!< default PID controller voltage ramp value
#define DEF_PID_CURR_LIMIT (DEF_POWER_SUPPLY) //!< default PID controller voltage limit
#define DEF_CURR_FILTER_Tf 0.005f //!< default currnet filter time constant
#endif
// default current limit values
#define DEF_CURRENT_LIM 2.0f //!< 2Amps current limit by default
// default monitor downsample
#define DEF_MON_DOWNSMAPLE 100 //!< default monitor downsample
#define DEF_MOTION_DOWNSMAPLE 0 //!< default motion downsample - disable
// angle P params
#define DEF_P_ANGLE_P 20.0f //!< default P controller P value
#define DEF_VEL_LIM 20.0f //!< angle velocity limit default
// index search
#define DEF_INDEX_SEARCH_TARGET_VELOCITY 1.0f //!< default index search velocity
// align voltage
#define DEF_VOLTAGE_SENSOR_ALIGN 3.0f //!< default voltage for sensor and motor zero alignemt
// low pass filter velocity
#define DEF_VEL_FILTER_Tf 0.005 //!< default velocity filter time constant
// current sense default parameters
#define DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf 0.0f //!< default currnet sense per phase low pass filter time constant
@@ -0,0 +1,73 @@
#include "foc_utils.h"
// int array instead of float array
// 4x200 points per 360 deg
// 2x storage save (int 2Byte float 4 Byte )
// sin*10000
const int sine_array[200] = {0,79,158,237,316,395,473,552,631,710,789,867,946,1024,1103,1181,1260,1338,1416,1494,1572,1650,1728,1806,1883,1961,2038,2115,2192,2269,2346,2423,2499,2575,2652,2728,2804,2879,2955,3030,3105,3180,3255,3329,3404,3478,3552,3625,3699,3772,3845,3918,3990,4063,4135,4206,4278,4349,4420,4491,4561,4631,4701,4770,4840,4909,4977,5046,5113,5181,5249,5316,5382,5449,5515,5580,5646,5711,5775,5839,5903,5967,6030,6093,6155,6217,6279,6340,6401,6461,6521,6581,6640,6699,6758,6815,6873,6930,6987,7043,7099,7154,7209,7264,7318,7371,7424,7477,7529,7581,7632,7683,7733,7783,7832,7881,7930,7977,8025,8072,8118,8164,8209,8254,8298,8342,8385,8428,8470,8512,8553,8594,8634,8673,8712,8751,8789,8826,8863,8899,8935,8970,9005,9039,9072,9105,9138,9169,9201,9231,9261,9291,9320,9348,9376,9403,9429,9455,9481,9506,9530,9554,9577,9599,9621,9642,9663,9683,9702,9721,9739,9757,9774,9790,9806,9821,9836,9850,9863,9876,9888,9899,9910,9920,9930,9939,9947,9955,9962,9969,9975,9980,9985,9989,9992,9995,9997,9999,10000,10000};
// function approximating the sine calculation by using fixed size array
// ~40us (float array)
// ~50us (int array)
// precision +-0.005
// it has to receive an angle in between 0 and 2PI
float _sin(float a){
if(a < _PI_2){
//return sine_array[(int)(199.0f*( a / (_PI/2.0)))];
//return sine_array[(int)(126.6873f* a)]; // float array optimized
return 0.0001f*sine_array[_round(126.6873f* a)]; // int array optimized
}else if(a < _PI){
// return sine_array[(int)(199.0f*(1.0f - (a-_PI/2.0) / (_PI/2.0)))];
//return sine_array[398 - (int)(126.6873f*a)]; // float array optimized
return 0.0001f*sine_array[398 - _round(126.6873f*a)]; // int array optimized
}else if(a < _3PI_2){
// return -sine_array[(int)(199.0f*((a - _PI) / (_PI/2.0)))];
//return -sine_array[-398 + (int)(126.6873f*a)]; // float array optimized
return -0.0001f*sine_array[-398 + _round(126.6873f*a)]; // int array optimized
} else {
// return -sine_array[(int)(199.0f*(1.0f - (a - 3*_PI/2) / (_PI/2.0)))];
//return -sine_array[796 - (int)(126.6873f*a)]; // float array optimized
return -0.0001f*sine_array[796 - _round(126.6873f*a)]; // int array optimized
}
}
// function approximating cosine calculation by using fixed size array
// ~55us (float array)
// ~56us (int array)
// precision +-0.005
// it has to receive an angle in between 0 and 2PI
float _cos(float a){
float a_sin = a + _PI_2;
a_sin = a_sin > _2PI ? a_sin - _2PI : a_sin;
return _sin(a_sin);
}
// normalizing radian angle to [0,2PI]
float _normalizeAngle(float angle){
float a = fmod(angle, _2PI);
return a >= 0 ? a : (a + _2PI);
}
// Electrical angle calculation
float _electricalAngle(float shaft_angle, int pole_pairs) {
return (shaft_angle * pole_pairs);
}
// square root approximation function using
// https://reprap.org/forum/read.php?147,219210
// https://en.wikipedia.org/wiki/Fast_inverse_square_root
float _sqrtApprox(float number) {//low in fat
long i;
float y;
// float x;
// const float f = 1.5F; // better precision
// x = number * 0.5F;
y = number;
i = * ( long * ) &y;
i = 0x5f375a86 - ( i >> 1 );
y = * ( float * ) &i;
// y = y * ( f - ( x * y * y ) ); // better precision
return number * y;
}
@@ -0,0 +1,92 @@
#ifndef FOCUTILS_LIB_H
#define FOCUTILS_LIB_H
#include "Arduino.h"
// sign function
#define _sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
#define _round(x) ((x)>=0?(long)((x)+0.5f):(long)((x)-0.5f))
#define _constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
#define _sqrt(a) (_sqrtApprox(a))
#define _isset(a) ( (a) != (NOT_SET) )
#define _UNUSED(v) (void) (v)
// utility defines
#define _2_SQRT3 1.15470053838f
#define _SQRT3 1.73205080757f
#define _1_SQRT3 0.57735026919f
#define _SQRT3_2 0.86602540378f
#define _SQRT2 1.41421356237f
#define _120_D2R 2.09439510239f
#define _PI 3.14159265359f
#define _PI_2 1.57079632679f
#define _PI_3 1.0471975512f
#define _2PI 6.28318530718f
#define _3PI_2 4.71238898038f
#define _PI_6 0.52359877559f
#define NOT_SET -12345.0
#define _HIGH_IMPEDANCE 0
#define _HIGH_Z _HIGH_IMPEDANCE
#define _ACTIVE 1
// dq current structure
struct DQCurrent_s
{
float d;
float q;
};
// phase current structure
struct PhaseCurrent_s
{
float a;
float b;
float c;
};
// dq voltage structs
struct DQVoltage_s
{
float d;
float q;
};
/**
* Function approximating the sine calculation by using fixed size array
* - execution time ~40us (Arduino UNO)
*
* @param a angle in between 0 and 2PI
*/
float _sin(float a);
/**
* Function approximating cosine calculation by using fixed size array
* - execution time ~50us (Arduino UNO)
*
* @param a angle in between 0 and 2PI
*/
float _cos(float a);
/**
* normalizing radian angle to [0,2PI]
* @param angle - angle to be normalized
*/
float _normalizeAngle(float angle);
/**
* Electrical angle calculation
*
* @param shaft_angle - shaft angle of the motor
* @param pole_pairs - number of pole pairs
*/
float _electricalAngle(float shaft_angle, int pole_pairs);
/**
* Function approximating square root function
* - using fast inverse square root
*
* @param value - number
*/
float _sqrtApprox(float value);
#endif
@@ -0,0 +1,28 @@
#include "lowpass_filter.h"
LowPassFilter::LowPassFilter(float time_constant)
: Tf(time_constant)
, y_prev(0.0f)
{
timestamp_prev = _micros();
}
float LowPassFilter::operator() (float x)
{
unsigned long timestamp = _micros();
float dt = (timestamp - timestamp_prev)*1e-6f;
if (dt < 0.0f ) dt = 1e-3f;
else if(dt > 0.3f) {
y_prev = x;
timestamp_prev = timestamp;
return x;
}
float alpha = Tf/(Tf + dt);
float y = alpha*y_prev + (1.0f - alpha)*x;
y_prev = y;
timestamp_prev = timestamp;
return y;
}
@@ -0,0 +1,28 @@
#ifndef LOWPASS_FILTER_H
#define LOWPASS_FILTER_H
#include "time_utils.h"
#include "foc_utils.h"
/**
* Low pass filter class
*/
class LowPassFilter
{
public:
/**
* @param Tf - Low pass filter time constant
*/
LowPassFilter(float Tf);
~LowPassFilter() = default;
float operator() (float x);
float Tf; //!< Low pass filter time constant
protected:
unsigned long timestamp_prev; //!< Last execution timestamp
float y_prev; //!< filtered value in previous execution step
};
#endif // LOWPASS_FILTER_H
+58
View File
@@ -0,0 +1,58 @@
#include "pid.h"
PIDController::PIDController(float P, float I, float D, float ramp, float limit)
: P(P)
, I(I)
, D(D)
, output_ramp(ramp) // output derivative limit [volts/second]
, limit(limit) // output supply limit [volts]
, error_prev(0.0f)
, output_prev(0.0f)
, integral_prev(0.0f)
{
timestamp_prev = _micros();
}
// PID controller function
float PIDController::operator() (float error){
// calculate the time from the last call
unsigned long timestamp_now = _micros();
float Ts = (timestamp_now - timestamp_prev) * 1e-6f;
// quick fix for strange cases (micros overflow)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// u(s) = (P + I/s + Ds)e(s)
// Discrete implementations
// proportional part
// u_p = P *e(k)
float proportional = P * error;
// Tustin transform of the integral part
// u_ik = u_ik_1 + I*Ts/2*(ek + ek_1)
float integral = integral_prev + I*Ts*0.5f*(error + error_prev);
// antiwindup - limit the output
integral = _constrain(integral, -limit, limit);
// Discrete derivation
// u_dk = D(ek - ek_1)/Ts
float derivative = D*(error - error_prev)/Ts;
// sum all the components
float output = proportional + integral + derivative;
// antiwindup - limit the output variable
output = _constrain(output, -limit, limit);
// if output ramp defined
if(output_ramp > 0){
// limit the acceleration by ramping the output
float output_rate = (output - output_prev)/Ts;
if (output_rate > output_ramp)
output = output_prev + output_ramp*Ts;
else if (output_rate < -output_ramp)
output = output_prev - output_ramp*Ts;
}
// saving for the next pass
integral_prev = integral;
output_prev = output;
error_prev = error;
timestamp_prev = timestamp_now;
return output;
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef PID_H
#define PID_H
#include "time_utils.h"
#include "foc_utils.h"
/**
* PID controller class
*/
class PIDController
{
public:
/**
*
* @param P - Proportional gain
* @param I - Integral gain
* @param D - Derivative gain
* @param ramp - Maximum speed of change of the output value
* @param limit - Maximum output value
*/
PIDController(float P, float I, float D, float ramp, float limit);
~PIDController() = default;
float operator() (float error);
float P; //!< Proportional gain
float I; //!< Integral gain
float D; //!< Derivative gain
float output_ramp; //!< Maximum speed of change of the output value
float limit; //!< Maximum output value
float error_prev; //!< last tracking error value
protected:
//float error_prev; //!< last tracking error value
float output_prev; //!< last pid output value
float integral_prev; //!< last integral component value
unsigned long timestamp_prev; //!< Last execution timestamp
};
#endif // PID_H
//Lyf对此进行了修改,将error_prev从protected移至public,便于清楚积分影响
@@ -0,0 +1,31 @@
#include "time_utils.h"
// function buffering delay()
// arduino uno function doesn't work well with interrupts
void _delay(unsigned long ms){
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328PB__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__)
// if arduino uno and other atmega328p chips
// use while instad of delay,
// due to wrong measurement based on changed timer0
unsigned long t = _micros() + ms*1000;
while( _micros() < t ){};
#else
// regular micros
delay(ms);
#endif
}
// function buffering _micros()
// arduino function doesn't work well with interrupts
unsigned long _micros(){
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328PB__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__)
// if arduino uno and other atmega328p chips
//return the value based on the prescaler
if((TCCR0B & 0b00000111) == 0x01) return (micros()/32);
else return (micros());
#else
// regular micros
return micros();
#endif
}
@@ -0,0 +1,22 @@
#ifndef TIME_UTILS_H
#define TIME_UTILS_H
#include "foc_utils.h"
/**
* Function implementing delay() function in milliseconds
* - blocking function
* - hardware specific
* @param ms number of milliseconds to wait
*/
void _delay(unsigned long ms);
/**
* Function implementing timestamp getting function in microseconds
* hardware specific
*/
unsigned long _micros();
#endif
@@ -0,0 +1,614 @@
#include "Commander.h"
Commander::Commander(Stream& serial, char eol, bool echo){
com_port = &serial;
this->eol = eol;
this->echo = echo;
}
Commander::Commander(char eol, bool echo){
this->eol = eol;
this->echo = echo;
}
void Commander::add(char id, CommandCallback onCommand, char* label ){
call_list[call_count] = onCommand;
call_ids[call_count] = id;
call_label[call_count] = label;
call_count++;
}
void Commander::run(){
if(!com_port) return;
run(*com_port, eol);
}
void Commander::run(Stream& serial, char eol){
Stream* tmp = com_port; // save the serial instance
char eol_tmp = this->eol;
this->eol = eol;
com_port = &serial;
// a string to hold incoming data
while (serial.available()) {
// get the new byte:
int ch = serial.read();
received_chars[rec_cnt++] = (char)ch;
// end of user input
if(echo)
print((char)ch);
if (isSentinel(ch)) {
// execute the user command
run(received_chars);
// reset the command buffer
received_chars[0] = 0;
rec_cnt=0;
}
if (rec_cnt>=MAX_COMMAND_LENGTH) { // prevent buffer overrun if message is too long
received_chars[0] = 0;
rec_cnt=0;
}
}
com_port = tmp; // reset the instance to the internal value
this->eol = eol_tmp;
}
void Commander::run(char* user_input){
// execute the user command
char id = user_input[0];
switch(id){
case CMD_SCAN:
for(int i=0; i < call_count; i++){
print(call_ids[i]);
print(":");
if(call_label[i]) println(call_label[i]);
else println("");
}
break;
case CMD_VERBOSE:
if(!isSentinel(user_input[1])) verbose = (VerboseMode)atoi(&user_input[1]);
printVerbose(F("Verb:"));
switch (verbose){
case VerboseMode::nothing:
println(F("off!"));
break;
case VerboseMode::on_request:
case VerboseMode::user_friendly:
println(F("on!"));
break;
}
break;
case CMD_DECIMAL:
if(!isSentinel(user_input[1])) decimal_places = atoi(&user_input[1]);
printVerbose(F("Decimal:"));
println(decimal_places);
break;
default:
for(int i=0; i < call_count; i++){
if(id == call_ids[i]){
call_list[i](&user_input[1]);
break;
}
}
break;
}
}
void Commander::motor(FOCMotor* motor, char* user_command) {
// if target setting
if(isDigit(user_command[0]) || user_command[0] == '-' || user_command[0] == '+'){
target(motor, user_command);
return;
}
// parse command letter
char cmd = user_command[0];
char sub_cmd = user_command[1];
// check if there is a subcommand or not
int value_index = (sub_cmd >= 'A' && sub_cmd <= 'Z') ? 2 : 1;
// check if get command
bool GET = isSentinel(user_command[value_index]);
// parse command values
float value = atof(&user_command[value_index]);
// a bit of optimisation of variable memory for Arduino UNO (atmega328)
switch(cmd){
case CMD_C_Q_PID: //
printVerbose(F("PID curr q| "));
if(sub_cmd == SCMD_LPF_TF) lpf(&motor->LPF_current_q, &user_command[1]);
else pid(&motor->PID_current_q,&user_command[1]);
break;
case CMD_C_D_PID: //
printVerbose(F("PID curr d| "));
if(sub_cmd == SCMD_LPF_TF) lpf(&motor->LPF_current_d, &user_command[1]);
else pid(&motor->PID_current_d, &user_command[1]);
break;
case CMD_V_PID: //
printVerbose(F("PID vel| "));
if(sub_cmd == SCMD_LPF_TF) lpf(&motor->LPF_velocity, &user_command[1]);
else pid(&motor->PID_velocity, &user_command[1]);
break;
case CMD_A_PID: //
printVerbose(F("PID angle| "));
if(sub_cmd == SCMD_LPF_TF) lpf(&motor->LPF_angle, &user_command[1]);
else pid(&motor->P_angle, &user_command[1]);
break;
case CMD_LIMITS: //
printVerbose(F("Limits| "));
switch (sub_cmd){
case SCMD_LIM_VOLT: // voltage limit change
printVerbose(F("volt: "));
if(!GET) {
motor->voltage_limit = value;
motor->PID_current_d.limit = value;
motor->PID_current_q.limit = value;
// change velocity pid limit if in voltage mode and no phase resistance set
if( !_isset(motor->phase_resistance) && motor->torque_controller==TorqueControlType::voltage) motor->PID_velocity.limit = value;
}
println(motor->voltage_limit);
break;
case SCMD_LIM_CURR: // current limit
printVerbose(F("curr: "));
if(!GET){
motor->current_limit = value;
// if phase resistance specified or the current control is on set the current limit to the velocity PID
if(_isset(motor->phase_resistance) || motor->torque_controller != TorqueControlType::voltage ) motor->PID_velocity.limit = value;
}
println(motor->current_limit);
break;
case SCMD_LIM_VEL: // velocity limit
printVerbose(F("vel: "));
if(!GET){
motor->velocity_limit = value;
motor->P_angle.limit = value;
}
println(motor->velocity_limit);
break;
default:
printError();
break;
}
break;
case CMD_MOTION_TYPE:
case CMD_TORQUE_TYPE:
case CMD_STATUS:
motion(motor, &user_command[0]);
break;
case CMD_PWMMOD:
// PWM modulation change
printVerbose(F("PWM Mod | "));
switch (sub_cmd){
case SCMD_PWMMOD_TYPE: // zero offset
printVerbose(F("type: "));
if(!GET) motor->foc_modulation = (FOCModulationType)value;
switch(motor->foc_modulation){
case FOCModulationType::SinePWM:
println(F("SinePWM"));
break;
case FOCModulationType::SpaceVectorPWM:
println(F("SVPWM"));
break;
case FOCModulationType::Trapezoid_120:
println(F("Trap 120"));
break;
case FOCModulationType::Trapezoid_150:
println(F("Trap 150"));
break;
}
break;
case SCMD_PWMMOD_CENTER: // centered modulation
printVerbose(F("center: "));
if(!GET) motor->modulation_centered = value;
println(motor->modulation_centered);
break;
default:
printError();
break;
}
break;
case CMD_RESIST:
// enable/disable
printVerbose(F("R phase: "));
if(!GET){
motor->phase_resistance = value;
if(motor->torque_controller==TorqueControlType::voltage){
motor->voltage_limit = motor->current_limit*value;
motor->PID_velocity.limit= motor->current_limit;
}
}
if(_isset(motor->phase_resistance)) println(motor->phase_resistance);
else println(0);
break;
case CMD_SENSOR:
// Sensor zero offset
printVerbose(F("Sensor | "));
switch (sub_cmd){
case SCMD_SENS_MECH_OFFSET: // zero offset
printVerbose(F("offset: "));
if(!GET) motor->sensor_offset = value;
println(motor->sensor_offset);
break;
case SCMD_SENS_ELEC_OFFSET: // electrical zero offset - not suggested to touch
printVerbose(F("el. offset: "));
if(!GET) motor->zero_electric_angle = value;
println(motor->zero_electric_angle);
break;
default:
printError();
break;
}
break;
case CMD_MONITOR: // get current values of the state variables
printVerbose(F("Monitor | "));
switch (sub_cmd){
case SCMD_GET: // get command
switch((uint8_t)value){
case 0: // get target
printVerbose(F("target: "));
println(motor->target);
break;
case 1: // get voltage q
printVerbose(F("Vq: "));
println(motor->voltage.q);
break;
case 2: // get voltage d
printVerbose(F("Vd: "));
println(motor->voltage.d);
break;
case 3: // get current q
printVerbose(F("Cq: "));
println(motor->current.q);
break;
case 4: // get current d
printVerbose(F("Cd: "));
println(motor->current.d);
break;
case 5: // get velocity
printVerbose(F("vel: "));
println(motor->shaft_velocity);
break;
case 6: // get angle
printVerbose(F("angle: "));
println(motor->shaft_angle);
break;
case 7: // get all states
printVerbose(F("all: "));
print(motor->target);
print(";");
print(motor->voltage.q);
print(";");
print(motor->voltage.d);
print(";");
print(motor->current.q);
print(";");
print(motor->current.d);
print(";");
print(motor->shaft_velocity);
print(";");
println(motor->shaft_angle);
break;
default:
printError();
break;
}
break;
case SCMD_DOWNSAMPLE:
printVerbose(F("downsample: "));
if(!GET) motor->monitor_downsample = value;
println((int)motor->monitor_downsample);
break;
case SCMD_CLEAR:
motor->monitor_variables = (uint8_t) 0;
println(F("clear"));
break;
case SCMD_SET:
if(!GET) motor->monitor_variables = (uint8_t) 0;
for(int i = 0; i < 7; i++){
if(isSentinel(user_command[value_index+i])) break;
if(!GET) motor->monitor_variables |= (user_command[value_index+i] - '0') << (6-i);
print( (user_command[value_index+i] - '0') );
}
println("");
break;
default:
printError();
break;
}
break;
default: // unknown cmd
printVerbose(F("unknown cmd "));
printError();
}
}
void Commander::motion(FOCMotor* motor, char* user_cmd, char* separator){
char cmd = user_cmd[0];
char sub_cmd = user_cmd[1];
bool GET = isSentinel(user_cmd[1]);
float value = atof(&user_cmd[(sub_cmd >= 'A' && sub_cmd <= 'Z') ? 2 : 1]);
switch(cmd){
case CMD_MOTION_TYPE:
printVerbose(F("Motion:"));
switch(sub_cmd){
case SCMD_DOWNSAMPLE:
printVerbose(F(" downsample: "));
if(!GET) motor->motion_downsample = value;
println((int)motor->motion_downsample);
break;
default:
// change control type
if(!GET && value >= 0 && (int)value < 5) // if set command
motor->controller = (MotionControlType)value;
switch(motor->controller){
case MotionControlType::torque:
println(F("torque"));
break;
case MotionControlType::velocity:
println(F("vel"));
break;
case MotionControlType::angle:
println(F("angle"));
break;
case MotionControlType::velocity_openloop:
println(F("vel open"));
break;
case MotionControlType::angle_openloop:
println(F("angle open"));
break;
}
break;
}
break;
case CMD_TORQUE_TYPE:
// change control type
printVerbose(F("Torque: "));
if(!GET && (int8_t)value >= 0 && (int8_t)value < 3)// if set command
motor->torque_controller = (TorqueControlType)value;
switch(motor->torque_controller){
case TorqueControlType::voltage:
println(F("volt"));
// change the velocity control limits if necessary
if( !_isset(motor->phase_resistance) ) motor->PID_velocity.limit = motor->voltage_limit;
break;
case TorqueControlType::dc_current:
println(F("dc curr"));
// change the velocity control limits if necessary
motor->PID_velocity.limit = motor->current_limit;
break;
case TorqueControlType::foc_current:
println(F("foc curr"));
// change the velocity control limits if necessary
motor->PID_velocity.limit = motor->current_limit;
break;
}
break;
case CMD_STATUS:
// enable/disable
printVerbose(F("Status: "));
if(!GET) (bool)value ? motor->enable() : motor->disable();
println(motor->enabled);
break;
default:
target(motor, user_cmd, separator);
break;
}
}
void Commander::pid(PIDController* pid, char* user_cmd){
char cmd = user_cmd[0];
bool GET = isSentinel(user_cmd[1]);
float value = atof(&user_cmd[1]);
switch (cmd){
case SCMD_PID_P: // P gain change
printVerbose("P: ");
if(!GET) pid->P = value;
println(pid->P);
break;
case SCMD_PID_I: // I gain change
printVerbose("I: ");
if(!GET) pid->I = value;
println(pid->I);
break;
case SCMD_PID_D: // D gain change
printVerbose("D: ");
if(!GET) pid->D = value;
println(pid->D);
break;
case SCMD_PID_RAMP: // ramp change
printVerbose("ramp: ");
if(!GET) pid->output_ramp = value;
println(pid->output_ramp);
break;
case SCMD_PID_LIM: // limit change
printVerbose("limit: ");
if(!GET) pid->limit = value;
println(pid->limit);
break;
default:
printError();
break;
}
}
void Commander::lpf(LowPassFilter* lpf, char* user_cmd){
char cmd = user_cmd[0];
bool GET = isSentinel(user_cmd[1]);
float value = atof(&user_cmd[1]);
switch (cmd){
case SCMD_LPF_TF: // Tf value change
printVerbose(F("Tf: "));
if(!GET) lpf->Tf = value;
println(lpf->Tf);
break;
default:
printError();
break;
}
}
void Commander::scalar(float* value, char* user_cmd){
bool GET = isSentinel(user_cmd[0]);
if(!GET) *value = atof(user_cmd);
println(*value);
}
void Commander::target(FOCMotor* motor, char* user_cmd, char* separator){
// if no values sent
if(isSentinel(user_cmd[0])) return;
float pos, vel, torque;
char* next_value;
switch(motor->controller){
case MotionControlType::torque: // setting torque target
torque = atof(strtok (user_cmd, separator));
motor->target = torque;
break;
case MotionControlType::velocity: // setting velocity target + torque limit
// set the target
vel= atof(strtok (user_cmd, separator));
motor->target = vel;
// allow for setting only the target velocity without chaning the torque limit
next_value = strtok (NULL, separator);
if (next_value){
torque = atof(next_value);
motor->PID_velocity.limit = torque;
// torque command can be voltage or current
if(!_isset(motor->phase_resistance) && motor->torque_controller == TorqueControlType::voltage) motor->voltage_limit = torque;
else motor->current_limit = torque;
}
break;
case MotionControlType::angle: // setting angle target + torque, velocity limit
// setting the target position
pos= atof(strtok (user_cmd, separator));
motor->target = pos;
// allow for setting only the target position without chaning the velocity/torque limits
next_value = strtok (NULL, separator);
if( next_value ){
vel = atof(next_value);
motor->velocity_limit = vel;
motor->P_angle.limit = vel;
// allow for setting only the target position and velocity limit without the torque limit
next_value = strtok (NULL, separator);
if( next_value ){
torque= atof(next_value);
motor->PID_velocity.limit = torque;
// torque command can be voltage or current
if(!_isset(motor->phase_resistance) && motor->torque_controller == TorqueControlType::voltage) motor->voltage_limit = torque;
else motor->current_limit = torque;
}
}
break;
case MotionControlType::velocity_openloop: // setting velocity target + torque limit
// set the target
vel= atof(strtok (user_cmd, separator));
motor->target = vel;
// allow for setting only the target velocity without chaning the torque limit
next_value = strtok (NULL, separator);
if (next_value ){
torque = atof(next_value);
// torque command can be voltage or current
if(!_isset(motor->phase_resistance)) motor->voltage_limit = torque;
else motor->current_limit = torque;
}
break;
case MotionControlType::angle_openloop: // setting angle target + torque, velocity limit
// set the target
pos= atof(strtok (user_cmd, separator));
motor->target = pos;
// allow for setting only the target position without chaning the velocity/torque limits
next_value = strtok (NULL, separator);
if( next_value ){
vel = atof(next_value);
motor->velocity_limit = vel;
// allow for setting only the target velocity without chaning the torque limit
next_value = strtok (NULL, separator);
if (next_value ){
torque = atof(next_value);
// torque command can be voltage or current
if(!_isset(motor->phase_resistance)) motor->voltage_limit = torque;
else motor->current_limit = torque;
}
}
break;
}
printVerbose(F("Target: "));
println(motor->target);
}
bool Commander::isSentinel(char ch)
{
if(ch == eol)
return true;
else if (ch == '\r')
{
printVerbose(F("Warn: \\r detected! \n"));
}
return false;
}
void Commander::print(const int number){
if( !com_port || verbose == VerboseMode::nothing ) return;
com_port->print(number);
}
void Commander::print(const float number){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->print((float)number,(int)decimal_places);
}
void Commander::print(const char* message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->print(message);
}
void Commander::print(const __FlashStringHelper *message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->print(message);
}
void Commander::print(const char message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->print(message);
}
void Commander::println(const int number){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->println(number);
}
void Commander::println(const float number){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->println((float)number, (int)decimal_places);
}
void Commander::println(const char* message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->println(message);
}
void Commander::println(const __FlashStringHelper *message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->println(message);
}
void Commander::println(const char message){
if(!com_port || verbose == VerboseMode::nothing ) return;
com_port->println(message);
}
void Commander::printVerbose(const char* message){
if(verbose == VerboseMode::user_friendly) print(message);
}
void Commander::printVerbose(const __FlashStringHelper *message){
if(verbose == VerboseMode::user_friendly) print(message);
}
void Commander::printError(){
println(F("err"));
}
@@ -0,0 +1,287 @@
#ifndef COMMANDS_H
#define COMMANDS_H
#include "Arduino.h"
#include "../common/base_classes/FOCMotor.h"
#include "../common/pid.h"
#include "../common/lowpass_filter.h"
#include "commands.h"
#define MAX_COMMAND_LENGTH 20
// Commander verbose display to the user type
enum VerboseMode : uint8_t {
nothing = 0x00, // display nothing - good for monitoring
on_request = 0x01, // display only on user request
user_friendly = 0x02 // display textual messages to the user
};
// callback function pointer definiton
typedef void (* CommandCallback)(char*); //!< command callback function pointer
/**
* Commander class implementing string communication protocol based on IDvalue (ex AB5.321 - command id `A`, sub-command id `B`,value `5.321`)
*
* - This class can be used in combination with HardwareSerial instance which it would read and write
* or it can be used to parse strings that have been received from the user outside this library
* - Commander class implements command protocol for few standard components of the SimpleFOC library
* - FOCMotor
* - PIDController
* - LowPassFilter
* - Commander also provides a very simple command > callback interface that enables user to
* attach a callback function to certain command id - see function add()
*/
class Commander
{
public:
/**
* Default constructor receiving a serial interface that it uses to output the values to
* Also if the function run() is used it uses this serial instance to read the serial for user commands
*
* @param serial - Serial com port instance
* @param eol - the end of line sentinel character
* @param echo - echo last typed character (for command line feedback)
*/
Commander(Stream &serial, char eol = '\n', bool echo = false);
Commander(char eol = '\n', bool echo = false);
/**
* Function reading the serial port and firing callbacks that have been added to the commander
* once the user has requested them - when he sends the command
*
* - It has default commands (the letters can be changed in the commands.h file)
* '@' - Verbose mode
* '#' - Number of decimal places
* '?' - Scan command - displays all the labels of attached nodes
*/
void run();
/**
* Function reading the string of user input and firing callbacks that have been added to the commander
* once the user has requested them - when he sends the command
*
* - It has default commands (the letters can be changed in the commands.h file)
* '@' - Verbose mode
* '#' - Number of decimal places
* '?' - Scan command - displays all the labels of attached nodes
*
* @param reader - temporary stream to read user input
* @param eol - temporary end of line sentinel
*/
void run(Stream &reader, char eol = '\n');
/**
* Function reading the string of user input and firing callbacks that have been added to the commander
* once the user has requested them - when he sends the command
*
* - It has default commands (the letters can be changed in the commands.h file)
* '@' - Verbose mode
* '#' - Number of decimal places
* '?' - Scan command - displays all the labels of attached nodes
*
* @param user_input - string of user inputs
*/
void run(char* user_input);
/**
* Function adding a callback to the coomander withe the command id
* @param id - char command letter
* @param onCommand - function pointer void function(char*)
* @param label - string label to be displayed when scan command sent
*/
void add(char id , CommandCallback onCommand, char* label = nullptr);
// printing variables
VerboseMode verbose = VerboseMode::user_friendly; //!< flag signaling that the commands should output user understanable text
uint8_t decimal_places = 3; //!< number of decimal places to be used when displaying numbers
// monitoring functions
Stream* com_port = nullptr; //!< Serial terminal variable if provided
char eol = '\n'; //!< end of line sentinel character
bool echo = false; //!< echo last typed character (for command line feedback)
/**
*
* FOC motor (StepperMotor and BLDCMotor) command interface
* @param motor - FOCMotor (BLDCMotor or StepperMotor) instance
* @param user_cmd - the string command
*
* - It has several paramters (the letters can be changed in the commands.h file)
* 'Q' - Q current PID controller & LPF (see function pid and lpf for commands)
* 'D' - D current PID controller & LPF (see function pid and lpf for commands)
* 'V' - Velocity PID controller & LPF (see function pid and lpf for commands)
* 'A' - Angle PID controller & LPF (see function pid and lpf for commands)
* 'L' - Limits
* sub-commands:
* 'C' - Current
* 'U' - Voltage
* 'V' - Velocity
* 'C' - Motion control type config
* sub-commands:
* 'D' - downsample motiron loop
* '0' - torque
* '1' - velocity
* '2' - angle
* 'T' - Torque control type
* sub-commands:
* '0' - voltage
* '1' - current
* '2' - foc_current
* 'E' - Motor status (enable/disable)
* sub-commands:
* '0' - enable
* '1' - disable
* 'R' - Motor resistance
* 'S' - Sensor offsets
* sub-commands:
* 'M' - sensor offset
* 'E' - sensor electrical zero
* 'M' - Monitoring control
* sub-commands:
* 'D' - downsample monitoring
* 'C' - clear monitor
* 'S' - set monitoring variables
* 'G' - get variable value
* '' - Target setting interface
* Depends of the motion control mode:
* - torque : torque (ex. M2.5)
* - velocity (open and closed loop) : velocity torque (ex.M10 2.5 or M10 to only chanage the target witout limits)
* - angle (open and closed loop) : angle velocity torque (ex.M3.5 10 2.5 or M3.5 to only chanage the target witout limits)
*
* - Each of them can be get by sening the command letter -(ex. 'R' - to get the phase resistance)
* - Each of them can be set by sending 'IdSubidValue' - (ex. SM1.5 for setting sensor zero offset to 1.5f)
*
*/
void motor(FOCMotor* motor, char* user_cmd);
/**
* Low pass fileter command interface
* @param lpf - LowPassFilter instance
* @param user_cmd - the string command
*
* - It only has one property - filtering time constant Tf
* - It can be get by sending 'F'
* - It can be set by sending 'Fvalue' - (ex. F0.01 for settin Tf=0.01)
*/
void lpf(LowPassFilter* lpf, char* user_cmd);
/**
* PID controller command interface
* @param pid - PIDController instance
* @param user_cmd - the string command
*
* - It has several paramters (the letters can be changed in the commands.h file)
* - P gain - 'P'
* - I gain - 'I'
* - D gain - 'D'
* - output ramp - 'R'
* - output limit - 'L'
* - Each of them can be get by sening the command letter -(ex. 'D' - to get the D gain)
* - Each of them can be set by sending 'IDvalue' - (ex. I1.5 for setting I=1.5)
*/
void pid(PIDController* pid, char* user_cmd);
/**
* Float variable scalar command interface
* @param value - float variable pointer
* @param user_cmd - the string command
*
* - It only has one property - one float value
* - It can be get by sending an empty string '\n'
* - It can be set by sending 'value' - (ex. 0.01f for settin *value=0.01)
*/
void scalar(float* value, char* user_cmd);
/**
* Target setting interface, enables setting the target and limiting variables at once.
* The values are sent separated by a separator specified as the third argument. The default separator is the space.
*
* @param motor - FOCMotor (BLDCMotor or StepperMotor) instance
* @param user_cmd - the string command
* @param separator - the string separator in between target and limit values, default is space - " "
*
* Example: P2.34 70 2
* `P` is the user defined command, `2.34` is the target angle `70` is the target
* velocity and `2` is the desired max current.
*
* It depends of the motion control mode:
* - torque : torque (ex. P2.5)
* - velocity : velocity torque (ex.P10 2.5 or P10 to only chanage the target witout limits)
* - angle : angle velocity torque (ex.P3.5 10 2.5 or P3.5 to only chanage the target witout limits)
*/
void target(FOCMotor* motor, char* user_cmd, char* separator = " ");
/**
* FOC motor (StepperMotor and BLDCMotor) motion control interfaces
* @param motor - FOCMotor (BLDCMotor or StepperMotor) instance
* @param user_cmd - the string command
* @param separator - the string separator in between target and limit values, default is space - " "
*
* Commands:
* 'C' - Motion control type config
* sub-commands:
* 'D' - downsample motiron loop
* '0' - torque
* '1' - velocity
* '2' - angle
* 'T' - Torque control type
* sub-commands:
* '0' - voltage
* '1' - current
* '2' - foc_current
* 'E' - Motor status (enable/disable)
* sub-commands:
* '0' - enable
* '1' - disable
* '' - Target setting interface
* Depends of the motion control mode:
* - torque : torque (ex. M2.5)
* - velocity (open and closed loop) : velocity torque (ex.M10 2.5 or M10 to only chanage the target witout limits)
* - angle (open and closed loop) : angle velocity torque (ex.M3.5 10 2.5 or M3.5 to only chanage the target witout limits)
*/
void motion(FOCMotor* motor, char* user_cmd, char* separator = " ");
private:
// Subscribed command callback variables
CommandCallback call_list[20];//!< array of command callback pointers - 20 is an arbitrary number
char call_ids[20]; //!< added callback commands
char* call_label[20]; //!< added callback labels
int call_count = 0;//!< number callbacks that are subscribed
// helping variable for serial communication reading
char received_chars[MAX_COMMAND_LENGTH] = {0}; //!< so far received user message - waiting for newline
int rec_cnt = 0; //!< number of characters receives
// serial printing functions
/**
* print the string message only if verbose mode on
* @param message - message to be printed
*/
void printVerbose(const char* message);
/**
* Print the string message only if verbose mode on
* - Function handling the case for strings defined by F macro
* @param message - message to be printed
*/
void printVerbose(const __FlashStringHelper *message);
/**
* print the numbers to the serial with desired decimal point number
* @param message - number to be printed
* @param newline - if needs lewline (1) otherwise (0)
*/
void print(const float number);
void print(const int number);
void print(const char* message);
void print(const __FlashStringHelper *message);
void print(const char message);
void println(const float number);
void println(const int number);
void println(const char* message);
void println(const __FlashStringHelper *message);
void println(const char message);
void printError();
bool isSentinel(char ch);
};
#endif
@@ -0,0 +1,40 @@
#include "StepDirListener.h"
StepDirListener::StepDirListener(int _pinStep, int _pinDir, float _counter_to_value){
pin_step = _pinStep;
pin_dir = _pinDir;
counter_to_value = _counter_to_value;
}
void StepDirListener::init(){
pinMode(pin_dir, INPUT);
pinMode(pin_step, INPUT_PULLUP);
count = 0;
}
void StepDirListener::enableInterrupt(void (*doA)()){
attachInterrupt(digitalPinToInterrupt(pin_step), doA, CHANGE);
}
void StepDirListener::attach(float* variable){
attached_variable = variable;
}
void StepDirListener::handle(){
// read step status
bool step = digitalRead(pin_step);
// update counter only on rising edge
if(step && step != step_active){
if(digitalRead(pin_dir))
count++;
else
count--;
}
step_active = step;
// if attached variable update it
if(attached_variable) *attached_variable = getValue();
}
// calculate the position from counter
float StepDirListener::getValue(){
return (float) count * counter_to_value;
}
@@ -0,0 +1,57 @@
#ifndef STEPDIR_H
#define STEPDIR_H
#include "Arduino.h"
#include "../common/foc_utils.h"
/**
* Step/Dir listenner class for easier interraction with this communication interface.
*/
class StepDirListener
{
public:
/**
* Constructor for step/direction interface
* @param step - pin
* @param direction - pin
* @param counter_to_value - step counter to value
*/
StepDirListener(int pinStep, int pinDir, float counter_to_value = 1);
/**
* Start listenning for step commands
*
* @param handleStep - on step received handler
*/
void enableInterrupt(void (*handleStep)());
/**
* Initialise dir and step commands
*/
void init();
/**
* step handler
*/
void handle();
/**
* Get so far received valued
*/
float getValue();
/**
* Attach the value to be updated on each step receive
* - no need to call getValue function
*/
void attach(float* variable);
// variables
int pin_step; //!< step pin
int pin_dir; //!< direction pin
long count; //!< current counter value - should be set to 0 for homing
private:
float* attached_variable = nullptr; //!< pointer to the attached variable
float counter_to_value; //!< step counter to value
bool step_active = 0; //!< current step pin status (HIGH/LOW) - debouncing variable
};
#endif
@@ -0,0 +1,50 @@
#ifndef COMMANDS_h
#define COMMANDS_h
// see docs.simplefoc.com for in depth explanation of the commands
// list of commands
#define CMD_C_D_PID 'D' //!< current d PID & LPF
#define CMD_C_Q_PID 'Q' //!< current d PID & LPF
#define CMD_V_PID 'V' //!< velocity PID & LPF
#define CMD_A_PID 'A' //!< angle PID & LPF
#define CMD_STATUS 'E' //!< motor status enable/disable
#define CMD_LIMITS 'L' //!< limits current/voltage/velocity
#define CMD_MOTION_TYPE 'C' //!< motion control type
#define CMD_TORQUE_TYPE 'T' //!< torque control type
#define CMD_SENSOR 'S' //!< sensor offsets
#define CMD_MONITOR 'M' //!< monitoring
#define CMD_RESIST 'R' //!< motor phase resistance
#define CMD_PWMMOD 'W' //!< pwm modulation
// commander configuration
#define CMD_SCAN '?' //!< command scaning the network - only for commander
#define CMD_VERBOSE '@' //!< command setting output mode - only for commander
#define CMD_DECIMAL '#' //!< command setting decimal places - only for commander
// subcomands
//pid - lpf
#define SCMD_PID_P 'P' //!< PID gain P
#define SCMD_PID_I 'I' //!< PID gain I
#define SCMD_PID_D 'D' //!< PID gain D
#define SCMD_PID_RAMP 'R' //!< PID ramp
#define SCMD_PID_LIM 'L' //!< PID limit
#define SCMD_LPF_TF 'F' //!< LPF time constant
// limits
#define SCMD_LIM_CURR 'C' //!< Limit current
#define SCMD_LIM_VOLT 'U' //!< Limit voltage
#define SCMD_LIM_VEL 'V' //!< Limit velocity
//sensor
#define SCMD_SENS_MECH_OFFSET 'M' //!< Sensor offset
#define SCMD_SENS_ELEC_OFFSET 'E' //!< Sensor electrical zero offset
// monitoring
#define SCMD_DOWNSAMPLE 'D' //!< Monitoring downsample value
#define SCMD_CLEAR 'C' //!< Clear all monitored variables
#define SCMD_GET 'G' //!< Get variable only one value
#define SCMD_SET 'S' //!< Set variables to be monitored
#define SCMD_PWMMOD_TYPE 'T' //!<< Pwm modulation type
#define SCMD_PWMMOD_CENTER 'C' //!<< Pwm modulation center flag
#endif
@@ -0,0 +1,162 @@
#include "GenericCurrentSense.h"
// GenericCurrentSense constructor
GenericCurrentSense::GenericCurrentSense(PhaseCurrent_s (*readCallback)(), void (*initCallback)()){
// if function provided add it to the
if(readCallback != nullptr) this->readCallback = readCallback;
if(initCallback != nullptr) this->initCallback = initCallback;
}
// Inline sensor init function
void GenericCurrentSense::init(){
// configure ADC variables
if(initCallback != nullptr) initCallback();
// calibrate zero offsets
calibrateOffsets();
}
// Function finding zero offsets of the ADC
void GenericCurrentSense::calibrateOffsets(){
const int calibration_rounds = 1000;
// find adc offset = zero current voltage
offset_ia = 0;
offset_ib = 0;
offset_ic = 0;
// read the adc voltage 1000 times ( arbitrary number )
for (int i = 0; i < calibration_rounds; i++) {
PhaseCurrent_s current = readCallback();
offset_ia += current.a;
offset_ib += current.b;
offset_ic += current.c;
_delay(1);
}
// calculate the mean offsets
offset_ia = offset_ia / calibration_rounds;
offset_ib = offset_ib / calibration_rounds;
offset_ic = offset_ic / calibration_rounds;
}
// read all three phase currents (if possible 2 or 3)
PhaseCurrent_s GenericCurrentSense::getPhaseCurrents(){
PhaseCurrent_s current = readCallback();
current.a = (current.a - offset_ia);// amps
current.b = (current.a - offset_ib);// amps
current.c = (current.a - offset_ic); // amps
return current;
}
// Function synchronizing current sense with motor driver.
// for in-line sensig no such thing is necessary
int GenericCurrentSense::driverSync(BLDCDriver *driver){
return 1;
}
// Function aligning the current sense with motor driver
// if all pins are connected well none of this is really necessary! - can be avoided
// returns flag
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
int GenericCurrentSense::driverAlign(BLDCDriver *driver, float voltage){
int exit_flag = 1;
if(skip_align) return exit_flag;
// // set phase A active and phases B and C down
// driver->setPwm(voltage, 0, 0);
// _delay(200);
// PhaseCurrent_s c = getPhaseCurrents();
// // read the current 100 times ( arbitrary number )
// for (int i = 0; i < 100; i++) {
// PhaseCurrent_s c1 = getPhaseCurrents();
// c.a = c.a*0.6f + 0.4f*c1.a;
// c.b = c.b*0.6f + 0.4f*c1.b;
// c.c = c.c*0.6f + 0.4f*c1.c;
// _delay(3);
// }
// driver->setPwm(0, 0, 0);
// // align phase A
// float ab_ratio = fabs(c.a / c.b);
// float ac_ratio = c.c ? fabs(c.a / c.c) : 0;
// if( ab_ratio > 1.5f ){ // should be ~2
// gain_a *= _sign(c.a);
// }else if( ab_ratio < 0.7f ){ // should be ~0.5
// // switch phase A and B
// int tmp_pinA = pinA;
// pinA = pinB;
// pinB = tmp_pinA;
// gain_a *= _sign(c.b);
// exit_flag = 2; // signal that pins have been switched
// }else if(_isset(pinC) && ac_ratio < 0.7f ){ // should be ~0.5
// // switch phase A and C
// int tmp_pinA = pinA;
// pinA = pinC;
// pinC= tmp_pinA;
// gain_a *= _sign(c.c);
// exit_flag = 2;// signal that pins have been switched
// }else{
// // error in current sense - phase either not measured or bad connection
// return 0;
// }
// // set phase B active and phases A and C down
// driver->setPwm(0, voltage, 0);
// _delay(200);
// c = getPhaseCurrents();
// // read the current 50 times
// for (int i = 0; i < 100; i++) {
// PhaseCurrent_s c1 = getPhaseCurrents();
// c.a = c.a*0.6f + 0.4f*c1.a;
// c.b = c.b*0.6f + 0.4f*c1.b;
// c.c = c.c*0.6f + 0.4f*c1.c;
// _delay(3);
// }
// driver->setPwm(0, 0, 0);
// float ba_ratio = fabs(c.b/c.a);
// float bc_ratio = c.c ? fabs(c.b / c.c) : 0;
// if( ba_ratio > 1.5f ){ // should be ~2
// gain_b *= _sign(c.b);
// }else if( ba_ratio < 0.7f ){ // it should be ~0.5
// // switch phase A and B
// int tmp_pinB = pinB;
// pinB = pinA;
// pinA = tmp_pinB;
// gain_b *= _sign(c.a);
// exit_flag = 2; // signal that pins have been switched
// }else if(_isset(pinC) && bc_ratio < 0.7f ){ // should be ~0.5
// // switch phase A and C
// int tmp_pinB = pinB;
// pinB = pinC;
// pinC = tmp_pinB;
// gain_b *= _sign(c.c);
// exit_flag = 2; // signal that pins have been switched
// }else{
// // error in current sense - phase either not measured or bad connection
// return 0;
// }
// // if phase C measured
// if(_isset(pinC)){
// // set phase B active and phases A and C down
// driver->setPwm(0, 0, voltage);
// _delay(200);
// c = getPhaseCurrents();
// // read the adc voltage 500 times ( arbitrary number )
// for (int i = 0; i < 50; i++) {
// PhaseCurrent_s c1 = getPhaseCurrents();
// c.c = (c.c+c1.c)/50.0f;
// }
// driver->setPwm(0, 0, 0);
// gain_c *= _sign(c.c);
// }
// if(gain_a < 0 || gain_b < 0 || gain_c < 0) exit_flag +=2;
// exit flag is either
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
return exit_flag;
}
@@ -0,0 +1,41 @@
#ifndef GENERIC_CS_LIB_H
#define GENERIC_CS_LIB_H
#include "Arduino.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "../common/base_classes/CurrentSense.h"
#include "../common/lowpass_filter.h"
#include "hardware_api.h"
class GenericCurrentSense: public CurrentSense{
public:
/**
GenericCurrentSense class constructor
*/
GenericCurrentSense(PhaseCurrent_s (*readCallback)() = nullptr, void (*initCallback)() = nullptr);
// CurrentSense interface implementing functions
void init() override;
PhaseCurrent_s getPhaseCurrents() override;
int driverSync(BLDCDriver *driver) override;
int driverAlign(BLDCDriver *driver, float voltage) override;
PhaseCurrent_s (*readCallback)() = nullptr; //!< function pointer to sensor reading
void (*initCallback)() = nullptr; //!< function pointer to sensor initialisation
private:
/**
* Function finding zero offsets of the ADC
*/
void calibrateOffsets();
float offset_ia; //!< zero current A voltage value (center of the adc reading)
float offset_ib; //!< zero current B voltage value (center of the adc reading)
float offset_ic; //!< zero current C voltage value (center of the adc reading)
};
#endif
@@ -0,0 +1,172 @@
#include "InlineCurrentSense.h"
// InlineCurrentSensor constructor
// - shunt_resistor - shunt resistor value
// - gain - current-sense op-amp gain
// - phA - A phase adc pin
// - phB - B phase adc pin
// - phC - C phase adc pin (optional)
InlineCurrentSense::InlineCurrentSense(float _shunt_resistor, float _gain, int _pinA, int _pinB, int _pinC){
pinA = _pinA;
pinB = _pinB;
pinC = _pinC;
shunt_resistor = _shunt_resistor;
amp_gain = _gain;
volts_to_amps_ratio = 1.0f /_shunt_resistor / _gain; // volts to amps
// gains for each phase
gain_a = volts_to_amps_ratio;
gain_b = volts_to_amps_ratio;
gain_c = volts_to_amps_ratio;
}
// Inline sensor init function
void InlineCurrentSense::init(){
// configure ADC variables
_configureADCInline(pinA,pinB,pinC);
// calibrate zero offsets
calibrateOffsets();
}
// Function finding zero offsets of the ADC
void InlineCurrentSense::calibrateOffsets(){
const int calibration_rounds = 1000;
// find adc offset = zero current voltage
offset_ia = 0;
offset_ib = 0;
offset_ic = 0;
// read the adc voltage 1000 times ( arbitrary number )
for (int i = 0; i < calibration_rounds; i++) {
offset_ia += _readADCVoltageInline(pinA);
offset_ib += _readADCVoltageInline(pinB);
if(_isset(pinC)) offset_ic += _readADCVoltageInline(pinC);
_delay(1);
}
// calculate the mean offsets
offset_ia = offset_ia / calibration_rounds;
offset_ib = offset_ib / calibration_rounds;
if(_isset(pinC)) offset_ic = offset_ic / calibration_rounds;
}
// read all three phase currents (if possible 2 or 3)
PhaseCurrent_s InlineCurrentSense::getPhaseCurrents(){
PhaseCurrent_s current;
current.a = (_readADCVoltageInline(pinA) - offset_ia)*gain_a;// amps
current.b = (_readADCVoltageInline(pinB) - offset_ib)*gain_b;// amps
current.c = (!_isset(pinC)) ? 0 : (_readADCVoltageInline(pinC) - offset_ic)*gain_c; // amps
return current;
}
// Function synchronizing current sense with motor driver.
// for in-line sensig no such thing is necessary
int InlineCurrentSense::driverSync(BLDCDriver *driver){
return 1;
}
// Function aligning the current sense with motor driver
// if all pins are connected well none of this is really necessary! - can be avoided
// returns flag
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
int InlineCurrentSense::driverAlign(BLDCDriver *driver, float voltage){
int exit_flag = 1;
if(skip_align) return exit_flag;
// set phase A active and phases B and C down
driver->setPwm(voltage, 0, 0);
_delay(200);
PhaseCurrent_s c = getPhaseCurrents();
// read the current 100 times ( arbitrary number )
for (int i = 0; i < 100; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.a = c.a*0.6f + 0.4f*c1.a;
c.b = c.b*0.6f + 0.4f*c1.b;
c.c = c.c*0.6f + 0.4f*c1.c;
_delay(3);
}
driver->setPwm(0, 0, 0);
// align phase A
float ab_ratio = fabs(c.a / c.b);
float ac_ratio = c.c ? fabs(c.a / c.c) : 0;
if( ab_ratio > 1.5f ){ // should be ~2
gain_a *= _sign(c.a);
}else if( ab_ratio < 0.7f ){ // should be ~0.5
// switch phase A and B
int tmp_pinA = pinA;
pinA = pinB;
pinB = tmp_pinA;
gain_a *= _sign(c.b);
exit_flag = 2; // signal that pins have been switched
}else if(_isset(pinC) && ac_ratio < 0.7f ){ // should be ~0.5
// switch phase A and C
int tmp_pinA = pinA;
pinA = pinC;
pinC= tmp_pinA;
gain_a *= _sign(c.c);
exit_flag = 2;// signal that pins have been switched
}else{
// error in current sense - phase either not measured or bad connection
return 0;
}
// set phase B active and phases A and C down
driver->setPwm(0, voltage, 0);
_delay(200);
c = getPhaseCurrents();
// read the current 50 times
for (int i = 0; i < 100; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.a = c.a*0.6f + 0.4f*c1.a;
c.b = c.b*0.6f + 0.4f*c1.b;
c.c = c.c*0.6f + 0.4f*c1.c;
_delay(3);
}
driver->setPwm(0, 0, 0);
float ba_ratio = fabs(c.b/c.a);
float bc_ratio = c.c ? fabs(c.b / c.c) : 0;
if( ba_ratio > 1.5f ){ // should be ~2
gain_b *= _sign(c.b);
}else if( ba_ratio < 0.7f ){ // it should be ~0.5
// switch phase A and B
int tmp_pinB = pinB;
pinB = pinA;
pinA = tmp_pinB;
gain_b *= _sign(c.a);
exit_flag = 2; // signal that pins have been switched
}else if(_isset(pinC) && bc_ratio < 0.7f ){ // should be ~0.5
// switch phase A and C
int tmp_pinB = pinB;
pinB = pinC;
pinC = tmp_pinB;
gain_b *= _sign(c.c);
exit_flag = 2; // signal that pins have been switched
}else{
// error in current sense - phase either not measured or bad connection
return 0;
}
// if phase C measured
if(_isset(pinC)){
// set phase B active and phases A and C down
driver->setPwm(0, 0, voltage);
_delay(200);
c = getPhaseCurrents();
// read the adc voltage 500 times ( arbitrary number )
for (int i = 0; i < 50; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.c = (c.c+c1.c)/50.0f;
}
driver->setPwm(0, 0, 0);
gain_c *= _sign(c.c);
}
if(gain_a < 0 || gain_b < 0 || gain_c < 0) exit_flag +=2;
// exit flag is either
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
return exit_flag;
}
@@ -0,0 +1,65 @@
#ifndef INLINE_CS_LIB_H
#define INLINE_CS_LIB_H
#include "Arduino.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "../common/base_classes/CurrentSense.h"
#include "../common/lowpass_filter.h"
#include "hardware_api.h"
class InlineCurrentSense: public CurrentSense{
public:
/**
InlineCurrentSense class constructor
@param shunt_resistor shunt resistor value
@param gain current-sense op-amp gain
@param phA A phase adc pin
@param phB B phase adc pin
@param phC C phase adc pin (optional)
*/
InlineCurrentSense(float shunt_resistor, float gain, int pinA, int pinB, int pinC = NOT_SET);
// CurrentSense interface implementing functions
void init() override;
PhaseCurrent_s getPhaseCurrents() override;
int driverSync(BLDCDriver *driver) override;
int driverAlign(BLDCDriver *driver, float voltage) override;
// ADC measuremnet gain for each phase
// support for different gains for different phases of more commonly - inverted phase currents
// this should be automated later
float gain_a; //!< phase A gain
float gain_b; //!< phase B gain
float gain_c; //!< phase C gain
// // per phase low pass fileters
// LowPassFilter lpf_a{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current A low pass filter
// LowPassFilter lpf_b{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current B low pass filter
// LowPassFilter lpf_c{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current C low pass filter
private:
// hardware variables
int pinA; //!< pin A analog pin for current measurement
int pinB; //!< pin B analog pin for current measurement
int pinC; //!< pin C analog pin for current measurement
// gain variables
float shunt_resistor; //!< Shunt resistor value
float amp_gain; //!< amp gain value
float volts_to_amps_ratio; //!< Volts to amps ratio
/**
* Function finding zero offsets of the ADC
*/
void calibrateOffsets();
float offset_ia; //!< zero current A voltage value (center of the adc reading)
float offset_ib; //!< zero current B voltage value (center of the adc reading)
float offset_ic; //!< zero current C voltage value (center of the adc reading)
};
#endif
@@ -0,0 +1,179 @@
#include "LowsideCurrentSense.h"
// LowsideCurrentSensor constructor
// - shunt_resistor - shunt resistor value
// - gain - current-sense op-amp gain
// - phA - A phase adc pin
// - phB - B phase adc pin
// - phC - C phase adc pin (optional)
LowsideCurrentSense::LowsideCurrentSense(float _shunt_resistor, float _gain, int _pinA, int _pinB, int _pinC){
pinA = _pinA;
pinB = _pinB;
pinC = _pinC;
shunt_resistor = _shunt_resistor;
amp_gain = _gain;
volts_to_amps_ratio = 1.0f /_shunt_resistor / _gain; // volts to amps
// gains for each phase
gain_a = volts_to_amps_ratio;
gain_b = volts_to_amps_ratio;
gain_c = volts_to_amps_ratio;
}
// Lowside sensor init function
void LowsideCurrentSense::init(){
// configure ADC variables
_configureADCLowSide(pinA,pinB,pinC);
// sync the driver
_driverSyncLowSide();
// calibrate zero offsets
calibrateOffsets();
}
// Function finding zero offsets of the ADC
void LowsideCurrentSense::calibrateOffsets(){
const int calibration_rounds = 1000;
// find adc offset = zero current voltage
offset_ia = 0;
offset_ib = 0;
offset_ic = 0;
// read the adc voltage 1000 times ( arbitrary number )
for (int i = 0; i < calibration_rounds; i++) {
_startADC3PinConversionLowSide();
offset_ia += _readADCVoltageLowSide(pinA);
offset_ib += _readADCVoltageLowSide(pinB);
if(_isset(pinC)) offset_ic += _readADCVoltageLowSide(pinC);
_delay(1);
}
// calculate the mean offsets
offset_ia = offset_ia / calibration_rounds;
offset_ib = offset_ib / calibration_rounds;
if(_isset(pinC)) offset_ic = offset_ic / calibration_rounds;
}
// read all three phase currents (if possible 2 or 3)
PhaseCurrent_s LowsideCurrentSense::getPhaseCurrents(){
PhaseCurrent_s current;
_startADC3PinConversionLowSide();
current.a = (_readADCVoltageLowSide(pinA) - offset_ia)*gain_a;// amps
current.b = (_readADCVoltageLowSide(pinB) - offset_ib)*gain_b;// amps
current.c = (!_isset(pinC)) ? 0 : (_readADCVoltageLowSide(pinC) - offset_ic)*gain_c; // amps
return current;
}
// Function synchronizing current sense with motor driver.
// for in-line sensig no such thing is necessary
int LowsideCurrentSense::driverSync(BLDCDriver *driver){
_driverSyncLowSide();
return 1;
}
// Function aligning the current sense with motor driver
// if all pins are connected well none of this is really necessary! - can be avoided
// returns flag
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
int LowsideCurrentSense::driverAlign(BLDCDriver *driver, float voltage){
int exit_flag = 1;
if(skip_align) return exit_flag;
// set phase A active and phases B and C down
driver->setPwm(voltage, 0, 0);
_delay(2000);
PhaseCurrent_s c = getPhaseCurrents();
// read the current 100 times ( arbitrary number )
for (int i = 0; i < 100; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.a = c.a*0.6f + 0.4f*c1.a;
c.b = c.b*0.6f + 0.4f*c1.b;
c.c = c.c*0.6f + 0.4f*c1.c;
_delay(3);
}
driver->setPwm(0, 0, 0);
// align phase A
float ab_ratio = fabs(c.a / c.b);
float ac_ratio = c.c ? fabs(c.a / c.c) : 0;
if( ab_ratio > 1.5f ){ // should be ~2
gain_a *= _sign(c.a);
}else if( ab_ratio < 0.7f ){ // should be ~0.5
// switch phase A and B
int tmp_pinA = pinA;
pinA = pinB;
pinB = tmp_pinA;
gain_a *= _sign(c.b);
exit_flag = 2; // signal that pins have been switched
}else if(_isset(pinC) && ac_ratio < 0.7f ){ // should be ~0.5
// switch phase A and C
int tmp_pinA = pinA;
pinA = pinC;
pinC= tmp_pinA;
gain_a *= _sign(c.c);
exit_flag = 2;// signal that pins have been switched
}else{
// error in current sense - phase either not measured or bad connection
return 0;
}
// set phase B active and phases A and C down
driver->setPwm(0, voltage, 0);
_delay(200);
c = getPhaseCurrents();
// read the current 50 times
for (int i = 0; i < 100; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.a = c.a*0.6 + 0.4f*c1.a;
c.b = c.b*0.6 + 0.4f*c1.b;
c.c = c.c*0.6 + 0.4f*c1.c;
_delay(3);
}
driver->setPwm(0, 0, 0);
float ba_ratio = fabs(c.b/c.a);
float bc_ratio = c.c ? fabs(c.b / c.c) : 0;
if( ba_ratio > 1.5f ){ // should be ~2
gain_b *= _sign(c.b);
}else if( ba_ratio < 0.7f ){ // it should be ~0.5
// switch phase A and B
int tmp_pinB = pinB;
pinB = pinA;
pinA = tmp_pinB;
gain_b *= _sign(c.a);
exit_flag = 2; // signal that pins have been switched
}else if(_isset(pinC) && bc_ratio < 0.7f ){ // should be ~0.5
// switch phase A and C
int tmp_pinB = pinB;
pinB = pinC;
pinC = tmp_pinB;
gain_b *= _sign(c.c);
exit_flag = 2; // signal that pins have been switched
}else{
// error in current sense - phase either not measured or bad connection
return 0;
}
// if phase C measured
if(_isset(pinC)){
// set phase B active and phases A and C down
driver->setPwm(0, 0, voltage);
_delay(200);
c = getPhaseCurrents();
// read the adc voltage 500 times ( arbitrary number )
for (int i = 0; i < 50; i++) {
PhaseCurrent_s c1 = getPhaseCurrents();
c.c = (c.c+c1.c)/50.0f;
}
driver->setPwm(0, 0, 0);
gain_c *= _sign(c.c);
}
if(gain_a < 0 || gain_b < 0 || gain_c < 0) exit_flag +=2;
// exit flag is either
// 0 - fail
// 1 - success and nothing changed
// 2 - success but pins reconfigured
// 3 - success but gains inverted
// 4 - success but pins reconfigured and gains inverted
return exit_flag;
}
@@ -0,0 +1,66 @@
#ifndef LOWSIDE_CS_LIB_H
#define LOWSIDE_CS_LIB_H
#include "Arduino.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "../common/base_classes/CurrentSense.h"
#include "../common/base_classes/FOCMotor.h"
#include "../common/lowpass_filter.h"
#include "hardware_api.h"
class LowsideCurrentSense: public CurrentSense{
public:
/**
LowsideCurrentSense class constructor
@param shunt_resistor shunt resistor value
@param gain current-sense op-amp gain
@param phA A phase adc pin
@param phB B phase adc pin
@param phC C phase adc pin (optional)
*/
LowsideCurrentSense(float shunt_resistor, float gain, int pinA, int pinB, int pinC = NOT_SET);
// CurrentSense interface implementing functions
void init() override;
PhaseCurrent_s getPhaseCurrents() override;
int driverSync(BLDCDriver *driver) override;
int driverAlign(BLDCDriver *driver, float voltage) override;
// ADC measuremnet gain for each phase
// support for different gains for different phases of more commonly - inverted phase currents
// this should be automated later
float gain_a; //!< phase A gain
float gain_b; //!< phase B gain
float gain_c; //!< phase C gain
// // per phase low pass fileters
// LowPassFilter lpf_a{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current A low pass filter
// LowPassFilter lpf_b{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current B low pass filter
// LowPassFilter lpf_c{DEF_LPF_PER_PHASE_CURRENT_SENSE_Tf}; //!< current C low pass filter
private:
// hardware variables
int pinA; //!< pin A analog pin for current measurement
int pinB; //!< pin B analog pin for current measurement
int pinC; //!< pin C analog pin for current measurement
// gain variables
float shunt_resistor; //!< Shunt resistor value
float amp_gain; //!< amp gain value
float volts_to_amps_ratio; //!< Volts to amps ratio
/**
* Function finding zero offsets of the ADC
*/
void calibrateOffsets();
float offset_ia; //!< zero current A voltage value (center of the adc reading)
float offset_ib; //!< zero current B voltage value (center of the adc reading)
float offset_ic; //!< zero current C voltage value (center of the adc reading)
};
#endif
@@ -0,0 +1,46 @@
#ifndef HARDWARE_UTILS_CURRENT_H
#define HARDWARE_UTILS_CURRENT_H
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - the arduino pin to be read (it has to be ADC pin)
*/
float _readADCVoltageInline(const int pinA);
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - adc pin A
* @param pinB - adc pin B
* @param pinC - adc pin C
*/
void _configureADCInline(const int pinA,const int pinB,const int pinC = NOT_SET);
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - adc pin A
* @param pinB - adc pin B
* @param pinC - adc pin C
*/
void _configureADCLowSide(const int pinA,const int pinB,const int pinC = NOT_SET);
void _startADC3PinConversionLowSide();
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - the arduino pin to be read (it has to be ADC pin)
*/
float _readADCVoltageLowSide(const int pinA);
/**
* function syncing the Driver with the ADC for the LowSide Sensing
*/
void _driverSyncLowSide();
#endif
@@ -0,0 +1,55 @@
#include "../hardware_api.h"
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328PB__)
#define _ADC_VOLTAGE 5.0f
#define _ADC_RESOLUTION 1024.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
// set hight frequency adc - ADPS2,ADPS1,ADPS0 | 001 (16mhz/2) | 010 ( 16mhz/4 ) | 011 (16mhz/8) | 100(16mhz/16) | 101 (16mhz/32) | 110 (16mhz/64) | 111 (16mhz/128 default)
// set divisor to 8 - adc frequency 16mhz/8 = 2 mhz
// arduino takes 25 conversions per sample so - 2mhz/25 = 80k samples per second - 12.5us per sample
cbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
}
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,38 @@
#include "../hardware_api.h"
#if defined(__arm__) && defined(__SAM3X8E__)
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 1024.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,225 @@
#include "esp32_adc_driver.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "rom/ets_sys.h"
#include "esp_attr.h"
#include "esp_intr.h"
#include "soc/rtc_io_reg.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
static uint8_t __analogAttenuation = 3;//11db
static uint8_t __analogWidth = 3;//12 bits
static uint8_t __analogCycles = 8;
static uint8_t __analogSamples = 0;//1 sample
static uint8_t __analogClockDiv = 1;
// Width of returned answer ()
static uint8_t __analogReturnedWidth = 12;
void __analogSetWidth(uint8_t bits){
if(bits < 9){
bits = 9;
} else if(bits > 12){
bits = 12;
}
__analogReturnedWidth = bits;
__analogWidth = bits - 9;
SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR1_BIT_WIDTH, __analogWidth, SENS_SAR1_BIT_WIDTH_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_SAMPLE_BIT, __analogWidth, SENS_SAR1_SAMPLE_BIT_S);
SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR2_BIT_WIDTH, __analogWidth, SENS_SAR2_BIT_WIDTH_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_SAMPLE_BIT, __analogWidth, SENS_SAR2_SAMPLE_BIT_S);
}
void __analogSetCycles(uint8_t cycles){
__analogCycles = cycles;
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_SAMPLE_CYCLE, __analogCycles, SENS_SAR1_SAMPLE_CYCLE_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_SAMPLE_CYCLE, __analogCycles, SENS_SAR2_SAMPLE_CYCLE_S);
}
void __analogSetSamples(uint8_t samples){
if(!samples){
return;
}
__analogSamples = samples - 1;
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_SAMPLE_NUM, __analogSamples, SENS_SAR1_SAMPLE_NUM_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_SAMPLE_NUM, __analogSamples, SENS_SAR2_SAMPLE_NUM_S);
}
void __analogSetClockDiv(uint8_t clockDiv){
if(!clockDiv){
return;
}
__analogClockDiv = clockDiv;
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_CLK_DIV, __analogClockDiv, SENS_SAR1_CLK_DIV_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_CLK_DIV, __analogClockDiv, SENS_SAR2_CLK_DIV_S);
}
void __analogSetAttenuation(uint8_t attenuation)
{
__analogAttenuation = attenuation & 3;
uint32_t att_data = 0;
int i = 10;
while(i--){
att_data |= __analogAttenuation << (i * 2);
}
WRITE_PERI_REG(SENS_SAR_ATTEN1_REG, att_data & 0xFFFF);//ADC1 has 8 channels
WRITE_PERI_REG(SENS_SAR_ATTEN2_REG, att_data);
}
void IRAM_ATTR __analogInit(){
static bool initialized = false;
if(initialized){
return;
}
__analogSetAttenuation(__analogAttenuation);
__analogSetCycles(__analogCycles);
__analogSetSamples(__analogSamples + 1);//in samples
__analogSetClockDiv(__analogClockDiv);
__analogSetWidth(__analogWidth + 9);//in bits
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DATA_INV);
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
SET_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_START_FORCE_M); //SAR ADC1 controller (in RTC) is started by SW
SET_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_SAR1_EN_PAD_FORCE_M); //SAR ADC1 pad enable bitmap is controlled by SW
SET_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_START_FORCE_M); //SAR ADC2 controller (in RTC) is started by SW
SET_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_SAR2_EN_PAD_FORCE_M); //SAR ADC2 pad enable bitmap is controlled by SW
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_M); //force XPD_SAR=0, use XPD_FSM
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_AMP, 0x2, SENS_FORCE_XPD_AMP_S); //force XPD_AMP=0
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_CTRL_REG, 0xfff << SENS_AMP_RST_FB_FSM_S); //clear FSM
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT1_REG, SENS_SAR_AMP_WAIT1, 0x1, SENS_SAR_AMP_WAIT1_S);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT1_REG, SENS_SAR_AMP_WAIT2, 0x1, SENS_SAR_AMP_WAIT2_S);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_SAR_AMP_WAIT3, 0x1, SENS_SAR_AMP_WAIT3_S);
while (GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR1_REG, 0x7, SENS_MEAS_STATUS_S) != 0); //wait det_fsm==
initialized = true;
}
void __analogSetPinAttenuation(uint8_t pin, uint8_t attenuation)
{
int8_t channel = digitalPinToAnalogChannel(pin);
if(channel < 0 || attenuation > 3){
return ;
}
__analogInit();
if(channel > 7){
SET_PERI_REG_BITS(SENS_SAR_ATTEN2_REG, 3, attenuation, ((channel - 10) * 2));
} else {
SET_PERI_REG_BITS(SENS_SAR_ATTEN1_REG, 3, attenuation, (channel * 2));
}
}
bool IRAM_ATTR __adcAttachPin(uint8_t pin){
int8_t channel = digitalPinToAnalogChannel(pin);
if(channel < 0){
return false;//not adc pin
}
int8_t pad = digitalPinToTouchChannel(pin);
if(pad >= 0){
uint32_t touch = READ_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG);
if(touch & (1 << pad)){
touch &= ~((1 << (pad + SENS_TOUCH_PAD_OUTEN2_S))
| (1 << (pad + SENS_TOUCH_PAD_OUTEN1_S))
| (1 << (pad + SENS_TOUCH_PAD_WORKEN_S)));
WRITE_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG, touch);
}
} else if(pin == 25){
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE); //stop dac1
} else if(pin == 26){
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_XPD_DAC | RTC_IO_PDAC2_DAC_XPD_FORCE); //stop dac2
}
pinMode(pin, ANALOG);
__analogInit();
return true;
}
bool IRAM_ATTR __adcStart(uint8_t pin){
int8_t channel = digitalPinToAnalogChannel(pin);
if(channel < 0){
return false;//not adc pin
}
if(channel > 9){
channel -= 10;
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_START_SAR_M);
SET_PERI_REG_BITS(SENS_SAR_MEAS_START2_REG, SENS_SAR2_EN_PAD, (1 << channel), SENS_SAR2_EN_PAD_S);
SET_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_START_SAR_M);
} else {
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_START_SAR_M);
SET_PERI_REG_BITS(SENS_SAR_MEAS_START1_REG, SENS_SAR1_EN_PAD, (1 << channel), SENS_SAR1_EN_PAD_S);
SET_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_START_SAR_M);
}
return true;
}
bool IRAM_ATTR __adcBusy(uint8_t pin){
int8_t channel = digitalPinToAnalogChannel(pin);
if(channel < 0){
return false;//not adc pin
}
if(channel > 7){
return (GET_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_DONE_SAR) == 0);
}
return (GET_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_DONE_SAR) == 0);
}
uint16_t IRAM_ATTR __adcEnd(uint8_t pin)
{
uint16_t value = 0;
int8_t channel = digitalPinToAnalogChannel(pin);
if(channel < 0){
return 0;//not adc pin
}
if(channel > 7){
while (GET_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_DONE_SAR) == 0); //wait for conversion
value = GET_PERI_REG_BITS2(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_DATA_SAR, SENS_MEAS2_DATA_SAR_S);
} else {
while (GET_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_DONE_SAR) == 0); //wait for conversion
value = GET_PERI_REG_BITS2(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_DATA_SAR, SENS_MEAS1_DATA_SAR_S);
}
// Shift result if necessary
uint8_t from = __analogWidth + 9;
if (from == __analogReturnedWidth) {
return value;
}
if (from > __analogReturnedWidth) {
return value >> (from - __analogReturnedWidth);
}
return value << (__analogReturnedWidth - from);
}
void __analogReadResolution(uint8_t bits)
{
if(!bits || bits > 16){
return;
}
__analogSetWidth(bits); // hadware from 9 to 12
__analogReturnedWidth = bits; // software from 1 to 16
}
uint16_t IRAM_ATTR adcRead(uint8_t pin)
{
if(!__adcAttachPin(pin) || !__adcStart(pin)){
return 0;
}
return __adcEnd(pin);
}
#endif
@@ -0,0 +1,88 @@
#ifndef SIMPLEFOC_ESP32_HAL_ADC_DRIVER_H_
#define SIMPLEFOC_ESP32_HAL_ADC_DRIVER_H_
#include "Arduino.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED)
/*
* Get ADC value for pin
* */
uint16_t adcRead(uint8_t pin);
/*
* Set the resolution of analogRead return values. Default is 12 bits (range from 0 to 4096).
* If between 9 and 12, it will equal the set hardware resolution, else value will be shifted.
* Range is 1 - 16
*
* Note: compatibility with Arduino SAM
*/
void __analogReadResolution(uint8_t bits);
/*
* Sets the sample bits and read resolution
* Default is 12bit (0 - 4095)
* Range is 9 - 12
* */
void __analogSetWidth(uint8_t bits);
/*
* Set number of cycles per sample
* Default is 8 and seems to do well
* Range is 1 - 255
* */
void __analogSetCycles(uint8_t cycles);
/*
* Set number of samples in the range.
* Default is 1
* Range is 1 - 255
* This setting splits the range into
* "samples" pieces, which could look
* like the sensitivity has been multiplied
* that many times
* */
void __analogSetSamples(uint8_t samples);
/*
* Set the divider for the ADC clock.
* Default is 1
* Range is 1 - 255
* */
void __analogSetClockDiv(uint8_t clockDiv);
/*
* Set the attenuation for all channels
* Default is 11db
* */
void __analogSetAttenuation(uint8_t attenuation);
/*
* Set the attenuation for particular pin
* Default is 11db
* */
void __analogSetPinAttenuation(uint8_t pin, uint8_t attenuation);
/*
* Attach pin to ADC (will also clear any other analog mode that could be on)
* */
bool __adcAttachPin(uint8_t pin);
/*
* Start ADC conversion on attached pin's bus
* */
bool __adcStart(uint8_t pin);
/*
* Check if conversion on the pin's ADC bus is currently running
* */
bool __adcBusy(uint8_t pin);
/*
* Get the result of the conversion (will wait if it have not finished)
* */
uint16_t __adcEnd(uint8_t pin);
#endif /* SIMPLEFOC_ESP32_HAL_ADC_DRIVER_H_ */
#endif /* ESP32 */
@@ -0,0 +1,30 @@
#include "../hardware_api.h"
#include "../../drivers/hardware_api.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && !defined(SOC_MCPWM_SUPPORTED)
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 4095.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
/**
* Inline adc reading implementation
*/
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
// uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB, const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,124 @@
#include "../hardware_api.h"
#include "../../drivers/hardware_api.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED)
#include "esp32_adc_driver.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 4095.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
/**
* Inline adc reading implementation
*/
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = adcRead(pinA);
// uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB, const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
/**
* Low side adc reading implementation
*/
static mcpwm_dev_t *MCPWM[2] = {&MCPWM0, &MCPWM1};
int a1, a2, a3; // Current readings from internal current sensor amplifiers
int _pinA, _pinB, _pinC;
static void IRAM_ATTR isr_handler(void*);
byte currentState = 1;
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pin){
uint32_t raw_adc;
if (pin == _pinA) raw_adc = a1;
else if (pin == _pinB) raw_adc = a2;
else if (pin == _pinC) raw_adc = a3;
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
_pinA = pinA;
_pinB = pinB;
_pinC = pinC;
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
void _driverSyncLowSide(){
// high side registers enable interrupt
// MCPWM[MCPWM_UNIT_0]->int_ena.timer0_tez_int_ena = true;//A PWM timer 0 TEP event will trigger this interrupt
// MCPWM[MCPWM_UNIT_0]->int_ena.timer1_tez_int_ena = true;//A PWM timer 1 TEP event will trigger this interrupt
// if( _isset(_pinC) ) MCPWM[MCPWM_UNIT_0]->int_ena.timer2_tez_int_ena = true;//A PWM timer 2 TEP event will trigger this interrupt
// low-side register enable interrupt
MCPWM[MCPWM_UNIT_0]->int_ena.timer0_tep_int_ena = true;//A PWM timer 0 TEP event will trigger this interrupt
MCPWM[MCPWM_UNIT_0]->int_ena.timer1_tep_int_ena = true;//A PWM timer 1 TEP event will trigger this interrupt
if( _isset(_pinC) ) MCPWM[MCPWM_UNIT_0]->int_ena.timer2_tep_int_ena = true;//A PWM timer 2 TEP event will trigger this interrupt
mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler
}
// Read currents when interrupt is triggered
static void IRAM_ATTR isr_handler(void*){
// // high side
// uint32_t mcpwm_intr_status_0 = MCPWM[MCPWM_UNIT_0]->int_st.timer0_tez_int_st;
// uint32_t mcpwm_intr_status_1 = MCPWM[MCPWM_UNIT_0]->int_st.timer1_tez_int_st;
// uint32_t mcpwm_intr_status_2 = _isset(_pinC) ? MCPWM[MCPWM_UNIT_0]->int_st.timer2_tez_int_st : 0;
// low side
uint32_t mcpwm_intr_status_0 = MCPWM[MCPWM_UNIT_0]->int_st.timer0_tep_int_st;
uint32_t mcpwm_intr_status_1 = MCPWM[MCPWM_UNIT_0]->int_st.timer1_tep_int_st;
uint32_t mcpwm_intr_status_2 = _isset(_pinC) ? MCPWM[MCPWM_UNIT_0]->int_st.timer2_tep_int_st : 0;
switch (currentState)
{
case 1 :
if (mcpwm_intr_status_0 > 0) a1 = adcRead(_pinA);
currentState = 2;
break;
case 2 :
if (mcpwm_intr_status_1 > 0) a2 = adcRead(_pinB);
currentState = _isset(_pinC) ? 3 : 1;
break;
case 3 :
if (mcpwm_intr_status_2 > 0) a3 = adcRead(_pinC);
currentState = 1;
break;
}
// high side
// MCPWM[MCPWM_UNIT_0]->int_clr.timer0_tez_int_clr = mcpwm_intr_status_0;
// MCPWM[MCPWM_UNIT_0]->int_clr.timer1_tez_int_clr = mcpwm_intr_status_1;
// if( _isset(_pinC) ) MCPWM[MCPWM_UNIT_0]->int_clr.timer2_tez_int_clr = mcpwm_intr_status_2;
// low side
MCPWM[MCPWM_UNIT_0]->int_clr.timer0_tep_int_clr = mcpwm_intr_status_0;
MCPWM[MCPWM_UNIT_0]->int_clr.timer1_tep_int_clr = mcpwm_intr_status_1;
if( _isset(_pinC) ) MCPWM[MCPWM_UNIT_0]->int_clr.timer2_tep_int_clr = mcpwm_intr_status_2;
}
#endif
@@ -0,0 +1,31 @@
#include "../hardware_api.h"
// function reading an ADC value and returning the read voltage
__attribute__((weak)) float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * 5.0f/1024.0f;
}
// function reading an ADC value and returning the read voltage
__attribute__((weak)) void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// function reading an ADC value and returning the read voltage
__attribute__((weak)) float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
__attribute__((weak)) void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// sync driver and the adc
__attribute__((weak)) void _driverSyncLowSide(){ }
__attribute__((weak)) void _startADC3PinConversionLowSide(){ }
@@ -0,0 +1,311 @@
#ifdef _SAMD21_
#include "samd21_mcu.h"
#include "../hardware_api.h"
static bool freeRunning = false;
static int _pinA, _pinB, _pinC;
static uint16_t a = 0xFFFF, b = 0xFFFF, c = 0xFFFF; // updated by adcStopWithDMA when configured in freerunning mode
static SAMDCurrentSenseADCDMA instance;
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - adc pin A
* @param pinB - adc pin B
* @param pinC - adc pin C
*/
void _configureADCLowSide(const int pinA,const int pinB,const int pinC)
{
_pinA = pinA;
_pinB = pinB;
_pinC = pinC;
freeRunning = true;
instance.init(pinA, pinB, pinC);
}
void _startADC3PinConversionLowSide()
{
instance.startADCScan();
}
/**
* function reading an ADC value and returning the read voltage
*
* @param pinA - the arduino pin to be read (it has to be ADC pin)
*/
float _readADCVoltageLowSide(const int pinA)
{
instance.readResults(a, b, c);
if(pinA == _pinA)
return instance.toVolts(a);
if(pinA == _pinB)
return instance.toVolts(b);
if(pinA == _pinC)
return instance.toVolts(c);
return NAN;
}
/**
* function syncing the Driver with the ADC for the LowSide Sensing
*/
void _driverSyncLowSide()
{
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(F("TODO! _driverSyncLowSide() is not implemented"));
instance.startADCScan();
//TODO: hook with PWM interrupts
}
// Credit: significant portions of this code were pulled from Paul Gould's git https://github.com/gouldpa/FOC-Arduino-Brushless
static void adcStopWithDMA(void);
static void adcStartWithDMA(void);
/**
* @brief ADC sync wait
* @retval void
*/
static __inline__ void ADCsync() __attribute__((always_inline, unused));
static void ADCsync() {
while (ADC->STATUS.bit.SYNCBUSY == 1); //Just wait till the ADC is free
}
// ADC DMA sequential free running (6) with Interrupts /////////////////
SAMDCurrentSenseADCDMA * SAMDCurrentSenseADCDMA::getHardwareAPIInstance()
{
return &instance;
}
SAMDCurrentSenseADCDMA::SAMDCurrentSenseADCDMA()
{
}
void SAMDCurrentSenseADCDMA::init(int pinA, int pinB, int pinC, int pinAREF, float voltageAREF, uint32_t adcBits, uint32_t channelDMA)
{
this->pinA = pinA;
this->pinB = pinB;
this->pinC = pinC;
this->pinAREF = pinAREF;
this->channelDMA = channelDMA;
this->voltageAREF = voltageAREF;
this->maxCountsADC = 1 << adcBits;
countsToVolts = ( voltageAREF / maxCountsADC );
initPins();
initADC();
initDMA();
startADCScan(); //so we have something to read next time we call readResults()
}
void SAMDCurrentSenseADCDMA::startADCScan(){
adcToDMATransfer(adcBuffer + oneBeforeFirstAIN, BufferSize);
adcStartWithDMA();
}
bool SAMDCurrentSenseADCDMA::readResults(uint16_t & a, uint16_t & b, uint16_t & c){
if(ADC->CTRLA.bit.ENABLE)
return false;
uint32_t ainA = g_APinDescription[pinA].ulADCChannelNumber;
uint32_t ainB = g_APinDescription[pinB].ulADCChannelNumber;
a = adcBuffer[ainA];
b = adcBuffer[ainB];
if(_isset(pinC))
{
uint32_t ainC = g_APinDescription[pinC].ulADCChannelNumber;
c = adcBuffer[ainC];
}
return true;
}
float SAMDCurrentSenseADCDMA::toVolts(uint16_t counts) {
return counts * countsToVolts;
}
void SAMDCurrentSenseADCDMA::initPins(){
if (pinAREF>=0)
pinMode(pinAREF, INPUT);
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
uint32_t ainA = g_APinDescription[pinA].ulADCChannelNumber;
uint32_t ainB = g_APinDescription[pinB].ulADCChannelNumber;
firstAIN = min(ainA, ainB);
lastAIN = max(ainA, ainB);
if( _isset(pinC) )
{
uint32_t ainC = g_APinDescription[pinC].ulADCChannelNumber;
pinMode(pinC, INPUT);
firstAIN = min(firstAIN, ainC);
lastAIN = max(lastAIN, ainC);
}
oneBeforeFirstAIN = firstAIN - 1; //hack to discard noisy first readout
BufferSize = lastAIN - oneBeforeFirstAIN + 1;
}
void SAMDCurrentSenseADCDMA::initADC(){
analogRead(pinA); // do some pin init pinPeripheral()
analogRead(pinB); // do some pin init pinPeripheral()
analogRead(pinC); // do some pin init pinPeripheral()
ADC->CTRLA.bit.ENABLE = 0x00; // Disable ADC
ADCsync();
//ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0_Val; // 2.2297 V Supply VDDANA
ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_1X_Val; // Gain select as 1X
// ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_DIV2_Val; // default
if (pinAREF>=0)
ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA;
else
ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0;
ADCsync(); // ref 31.6.16
/*
Bits 19:16 – INPUTSCAN[3:0]: Number of Input Channels Included in Scan
This register gives the number of input sources included in the pin scan. The number of input sources included is
INPUTSCAN + 1. The input channels included are in the range from MUXPOS + INPUTOFFSET to MUXPOS +
INPUTOFFSET + INPUTSCAN.
The range of the scan mode must not exceed the number of input channels available on the device.
Bits 4:0 – MUXPOS[4:0]: Positive Mux Input Selection
These bits define the Mux selection for the positive ADC input. Table 32-14 shows the possible input selections. If
the internal bandgap voltage or temperature sensor input channel is selected, then the Sampling Time Length bit
group in the SamplingControl register must be written.
Table 32-14. Positive Mux Input Selection
MUXPOS[4:0] Group configuration Description
0x00 PIN0 ADC AIN0 pin
0x01 PIN1 ADC AIN1 pin
0x02 PIN2 ADC AIN2 pin
0x03 PIN3 ADC AIN3 pin
0x04 PIN4 ADC AIN4 pin
0x05 PIN5 ADC AIN5 pin
0x06 PIN6 ADC AIN6 pin
0x07 PIN7 ADC AIN7 pin
0x08 PIN8 ADC AIN8 pin
0x09 PIN9 ADC AIN9 pin
0x0A PIN10 ADC AIN10 pin
0x0B PIN11 ADC AIN11 pin
0x0C PIN12 ADC AIN12 pin
0x0D PIN13 ADC AIN13 pin
0x0E PIN14 ADC AIN14 pin
0x0F PIN15 ADC AIN15 pin
0x10 PIN16 ADC AIN16 pin
0x11 PIN17 ADC AIN17 pin
0x12 PIN18 ADC AIN18 pin
0x13 PIN19 ADC AIN19 pin
0x14-0x17 Reserved
0x18 TEMP Temperature reference
0x19 BANDGAP Bandgap voltage
0x1A SCALEDCOREVCC 1/4 scaled core supply
0x1B SCALEDIOVCC 1/4 scaled I/O supply
0x1C DAC DAC output
0x1D-0x1F Reserved
*/
ADC->INPUTCTRL.bit.MUXPOS = oneBeforeFirstAIN;
ADCsync();
ADC->INPUTCTRL.bit.INPUTSCAN = lastAIN; // so the adc will scan from oneBeforeFirstAIN to lastAIN (inclusive)
ADCsync();
ADC->INPUTCTRL.bit.INPUTOFFSET = 0; //input scan cursor
ADCsync();
ADC->AVGCTRL.reg = 0x00 ; //no averaging
ADC->SAMPCTRL.reg = 0x05; ; //sample length in 1/2 CLK_ADC cycles, see GCLK_ADC and ADC_CTRLB_PRESCALER_DIV16
// according to the specsheet: f_GCLK_ADC ADC input clock frequency 48 MHz, so same as fCPU
ADCsync();
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_FREERUN | ADC_CTRLB_RESSEL_12BIT;
ADCsync();
}
volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));
void SAMDCurrentSenseADCDMA::initDMA() {
// probably on by default
PM->AHBMASK.reg |= PM_AHBMASK_DMAC ;
PM->APBBMASK.reg |= PM_APBBMASK_DMAC ;
NVIC_EnableIRQ( DMAC_IRQn ) ;
DMAC->BASEADDR.reg = (uint32_t)descriptor_section;
DMAC->WRBADDR.reg = (uint32_t)wrb;
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
}
void SAMDCurrentSenseADCDMA::adcToDMATransfer(void *rxdata, uint32_t hwords) {
DMAC->CHID.reg = DMAC_CHID_ID(channelDMA);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channelDMA));
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0)
| DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY)
| DMAC_CHCTRLB_TRIGACT_BEAT;
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
descriptor.descaddr = 0;
descriptor.srcaddr = (uint32_t) &ADC->RESULT.reg;
descriptor.btcnt = hwords;
descriptor.dstaddr = (uint32_t)rxdata + hwords*2; // end address
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_VALID;
memcpy(&descriptor_section[channelDMA],&descriptor, sizeof(dmacdescriptor));
// start channel
DMAC->CHID.reg = DMAC_CHID_ID(channelDMA);
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
}
int iii = 0;
void adcStopWithDMA(void){
ADCsync();
ADC->CTRLA.bit.ENABLE = 0x00;
// ADCsync();
// if(iii++ % 1000 == 0)
// {
// SIMPLEFOC_SAMD_DEBUG_SERIAL.print(a);
// SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" :: ");
// SIMPLEFOC_SAMD_DEBUG_SERIAL.print(b);
// SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" :: ");
// SIMPLEFOC_SAMD_DEBUG_SERIAL.print(c);
// SIMPLEFOC_SAMD_DEBUG_SERIAL.println("yo!");
// }
}
void adcStartWithDMA(void){
ADCsync();
ADC->INPUTCTRL.bit.INPUTOFFSET = 0;
ADCsync();
ADC->SWTRIG.bit.FLUSH = 1;
ADCsync();
ADC->CTRLA.bit.ENABLE = 0x01;
ADCsync();
}
void DMAC_Handler() {
uint8_t active_channel;
active_channel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // get channel number
DMAC->CHID.reg = DMAC_CHID_ID(active_channel);
adcStopWithDMA();
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; // clear
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
}
#endif
@@ -0,0 +1,69 @@
#ifdef _SAMD21_
#ifndef CURRENT_SENSE_SAMD21_H
#define CURRENT_SENSE_SAMD21_H
#define SIMPLEFOC_SAMD_DEBUG
#if !defined(SIMPLEFOC_SAMD_DEBUG_SERIAL)
#define SIMPLEFOC_SAMD_DEBUG_SERIAL Serial
#endif
#include <stdint.h>
typedef struct {
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
// AREF pin is 42
class SAMDCurrentSenseADCDMA
{
public:
static SAMDCurrentSenseADCDMA * getHardwareAPIInstance();
SAMDCurrentSenseADCDMA();
void init(int pinA, int pinB, int pinC, int pinAREF = 42, float voltageAREF = 3.3, uint32_t adcBits = 12, uint32_t channelDMA = 3);
void startADCScan();
bool readResults(uint16_t & a, uint16_t & b, uint16_t & c);
float toVolts(uint16_t counts);
private:
void adcToDMATransfer(void *rxdata, uint32_t hwords);
void initPins();
void initADC();
void initDMA();
uint32_t oneBeforeFirstAIN; // hack to discard first noisy readout
uint32_t firstAIN;
uint32_t lastAIN;
uint32_t BufferSize = 0;
uint16_t adcBuffer[20];
uint32_t pinA;
uint32_t pinB;
uint32_t pinC;
uint32_t pinAREF;
uint32_t channelDMA; // DMA channel
bool freeRunning;
float voltageAREF;
float maxCountsADC;
float countsToVolts;
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));
dmacdescriptor descriptor __attribute__ ((aligned (16)));
};
#endif
#endif
@@ -0,0 +1,37 @@
#include "../hardware_api.h"
#if defined(_SAMD21_)||defined(_SAMD51_)||defined(_SAME51_)
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 1024.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,39 @@
#include "../hardware_api.h"
#if defined(_STM32_DEF_) and !defined(STM32G4xx)
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 1024.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,333 @@
#include "../hardware_api.h"
#if defined(STM32G4xx)
#include "stm32g4xx_hal.h"
#include "stm32g4xx_ll_pwr.h"
#include "stm32g4xx_hal_adc.h"
#include "stm32g4_hal.h"
// From STM32 cube IDE
/**
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_ADC12_CLK_ENABLE();
}
/**
* Enable DMA controller clock
*/
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
/* DMA1_Channel2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
// Enable external clock for ADC
RCC_PeriphCLKInitTypeDef PeriphClkInit;
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_PLL;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
void MX_ADC1_Init(ADC_HandleTypeDef* hadc1)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1->Instance = ADC1;
hadc1->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16;
hadc1->Init.Resolution = ADC_RESOLUTION_12B;
hadc1->Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1->Init.GainCompensation = 0;
hadc1->Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1->Init.LowPowerAutoWait = DISABLE;
hadc1->Init.ContinuousConvMode = DISABLE;
hadc1->Init.NbrOfConversion = 2;
hadc1->Init.DiscontinuousConvMode = DISABLE;
hadc1->Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO;
hadc1->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1->Init.DMAContinuousRequests = ENABLE;
hadc1->Init.Overrun = ADC_OVR_DATA_PRESERVED;
if (HAL_ADC_Init(hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_12; // ADC1_IN12 = PB1 = OP3_OUT
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_3; // ADC1_IN3 = PA2 = OP1_OUT
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief ADC2 Initialization Function
* @param None
* @retval None
*/
void MX_ADC2_Init(ADC_HandleTypeDef* hadc2)
{
/* USER CODE BEGIN ADC2_Init 0 */
/* USER CODE END ADC2_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC2_Init 1 */
/* USER CODE END ADC2_Init 1 */
/** Common config
*/
hadc2->Instance = ADC2;
hadc2->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16;
hadc2->Init.Resolution = ADC_RESOLUTION_12B;
hadc2->Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2->Init.GainCompensation = 0;
hadc2->Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc2->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc2->Init.LowPowerAutoWait = DISABLE;
hadc2->Init.ContinuousConvMode = DISABLE;
hadc2->Init.NbrOfConversion = 1;
hadc2->Init.DiscontinuousConvMode = DISABLE;
hadc2->Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO;
hadc2->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc2->Init.DMAContinuousRequests = ENABLE;
hadc2->Init.Overrun = ADC_OVR_DATA_PRESERVED;
if (HAL_ADC_Init(hadc2) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_3; // ADC2_IN3 = PA6
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC2_Init 2 */
/* USER CODE END ADC2_Init 2 */
}
/**
* @brief OPAMP MSP Initialization
* This function configures the hardware resources used in this example
* @param hopamp-> OPAMP handle pointer
* @retval None
*/
void HAL_OPAMP_MspInit(OPAMP_HandleTypeDef* hopamp)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hopamp->Instance==OPAMP1)
{
/* USER CODE BEGIN OPAMP1_MspInit 0 */
/* USER CODE END OPAMP1_MspInit 0 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/**OPAMP1 GPIO Configuration
PA1 ------> OPAMP1_VINP
PA2 ------> OPAMP1_VOUT
PA3 ------> OPAMP1_VINM
*/
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN OPAMP1_MspInit 1 */
/* USER CODE END OPAMP1_MspInit 1 */
}
else if(hopamp->Instance==OPAMP2)
{
/* USER CODE BEGIN OPAMP2_MspInit 0 */
/* USER CODE END OPAMP2_MspInit 0 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/**OPAMP2 GPIO Configuration
PA5 ------> OPAMP2_VINM
PA6 ------> OPAMP2_VOUT
PA7 ------> OPAMP2_VINP
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN OPAMP2_MspInit 1 */
/* USER CODE END OPAMP2_MspInit 1 */
}
else if(hopamp->Instance==OPAMP3)
{
/* USER CODE BEGIN OPAMP3_MspInit 0 */
/* USER CODE END OPAMP3_MspInit 0 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/**OPAMP3 GPIO Configuration
PB0 ------> OPAMP3_VINP
PB1 ------> OPAMP3_VOUT
PB2 ------> OPAMP3_VINM
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN OPAMP3_MspInit 1 */
/* USER CODE END OPAMP3_MspInit 1 */
}
}
/**
* @brief OPAMP MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param hopamp-> OPAMP handle pointer
* @retval None
*/
void HAL_OPAMP_MspDeInit(OPAMP_HandleTypeDef* hopamp)
{
if(hopamp->Instance==OPAMP1)
{
/* USER CODE BEGIN OPAMP1_MspDeInit 0 */
/* USER CODE END OPAMP1_MspDeInit 0 */
/**OPAMP1 GPIO Configuration
PA1 ------> OPAMP1_VINP
PA2 ------> OPAMP1_VOUT
PA3 ------> OPAMP1_VINM
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
/* USER CODE BEGIN OPAMP1_MspDeInit 1 */
/* USER CODE END OPAMP1_MspDeInit 1 */
}
else if(hopamp->Instance==OPAMP2)
{
/* USER CODE BEGIN OPAMP2_MspDeInit 0 */
/* USER CODE END OPAMP2_MspDeInit 0 */
/**OPAMP2 GPIO Configuration
PA5 ------> OPAMP2_VINM
PA6 ------> OPAMP2_VOUT
PA7 ------> OPAMP2_VINP
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
/* USER CODE BEGIN OPAMP2_MspDeInit 1 */
/* USER CODE END OPAMP2_MspDeInit 1 */
}
else if(hopamp->Instance==OPAMP3)
{
/* USER CODE BEGIN OPAMP3_MspDeInit 0 */
/* USER CODE END OPAMP3_MspDeInit 0 */
/**OPAMP3 GPIO Configuration
PB0 ------> OPAMP3_VINP
PB1 ------> OPAMP3_VOUT
PB2 ------> OPAMP3_VINM
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2);
/* USER CODE BEGIN OPAMP3_MspDeInit 1 */
/* USER CODE END OPAMP3_MspDeInit 1 */
}
}
#endif
@@ -0,0 +1,18 @@
#ifndef stm32g4_hal
#define stm32g4_hal
#if defined(STM32G4xx)
#include <stm32g4xx_hal_adc.h>
#include <stm32g4xx_hal_opamp.h>
void MX_GPIO_Init(void);
void MX_DMA_Init(void);
void MX_ADC1_Init(ADC_HandleTypeDef* hadc1);
void MX_ADC2_Init(ADC_HandleTypeDef* hadc2);
void MX_OPAMP1_Init(OPAMP_HandleTypeDef* hopamp);
void MX_OPAMP2_Init(OPAMP_HandleTypeDef* hopamp);
void MX_OPAMP3_Init(OPAMP_HandleTypeDef* hopamp);
#endif
#endif
@@ -0,0 +1,130 @@
#include "../hardware_api.h"
#include "stm32g4_hal.h"
#if defined(STM32G4xx)
#define _ADC_VOLTAGE 3.3
#define _ADC_RESOLUTION 4096.0
#define ADC_BUF_LEN_1 2
#define ADC_BUF_LEN_2 1
static ADC_HandleTypeDef hadc1;
static ADC_HandleTypeDef hadc2;
static OPAMP_HandleTypeDef hopamp1;
static OPAMP_HandleTypeDef hopamp2;
static OPAMP_HandleTypeDef hopamp3;
static DMA_HandleTypeDef hdma_adc1;
static DMA_HandleTypeDef hdma_adc2;
uint16_t adcBuffer1[ADC_BUF_LEN_1] = {0}; // Buffer for store the results of the ADC conversion
uint16_t adcBuffer2[ADC_BUF_LEN_2] = {0}; // Buffer for store the results of the ADC conversion
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
// As DMA is being used just return the DMA result
float _readADCVoltageInline(const int pin){
uint32_t raw_adc = 0;
if(pin == PA2) // = ADC1_IN3 = phase U (OP1_OUT) on B-G431B-ESC1
raw_adc = adcBuffer1[1];
else if(pin == PA6) // = ADC2_IN3 = phase V (OP2_OUT) on B-G431B-ESC1
raw_adc = adcBuffer2[0];
else if(pin == PB1) // = ADC1_IN12 = phase W (OP3_OUT) on B-G431B-ESC1
raw_adc = adcBuffer1[0];
return raw_adc * _ADC_CONV;
}
// do the same for low side sensing
float _readADCVoltageLowSide(const int pin){
return _readADCVoltageInline(pin);
}
void _configureOPAMP(OPAMP_HandleTypeDef *hopamp, OPAMP_TypeDef *OPAMPx_Def){
// could this be replaced with LL_OPAMP calls??
hopamp->Instance = OPAMPx_Def;
hopamp->Init.PowerMode = OPAMP_POWERMODE_HIGHSPEED;
hopamp->Init.Mode = OPAMP_PGA_MODE;
hopamp->Init.NonInvertingInput = OPAMP_NONINVERTINGINPUT_IO0;
hopamp->Init.InternalOutput = DISABLE;
hopamp->Init.TimerControlledMuxmode = OPAMP_TIMERCONTROLLEDMUXMODE_DISABLE;
hopamp->Init.PgaConnect = OPAMP_PGA_CONNECT_INVERTINGINPUT_IO0_BIAS;
hopamp->Init.PgaGain = OPAMP_PGA_GAIN_16_OR_MINUS_15;
hopamp->Init.UserTrimming = OPAMP_TRIMMING_FACTORY;
if (HAL_OPAMP_Init(hopamp) != HAL_OK)
{
Error_Handler();
}
}
void _configureOPAMPs(OPAMP_HandleTypeDef *OPAMPA, OPAMP_HandleTypeDef *OPAMPB, OPAMP_HandleTypeDef *OPAMPC){
// Configure the opamps
_configureOPAMP(OPAMPA, OPAMP1);
_configureOPAMP(OPAMPB, OPAMP2);
_configureOPAMP(OPAMPC, OPAMP3);
}
void MX_DMA1_Init(ADC_HandleTypeDef *hadc, DMA_HandleTypeDef *hdma_adc, DMA_Channel_TypeDef* channel, uint32_t request) {
hdma_adc->Instance = channel;
hdma_adc->Init.Request = request;
hdma_adc->Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc->Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc->Init.MemInc = DMA_MINC_ENABLE;
hdma_adc->Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc->Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc->Init.Mode = DMA_CIRCULAR;
hdma_adc->Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_DeInit(hdma_adc);
if (HAL_DMA_Init(hdma_adc) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hadc, DMA_Handle, *hdma_adc);
}
void _configureADCInline(const int pinA,const int pinB,const int pinC){
HAL_Init();
MX_GPIO_Init();
MX_DMA_Init();
_configureOPAMPs(&hopamp1, &hopamp3, &hopamp2);
MX_ADC1_Init(&hadc1);
MX_ADC2_Init(&hadc2);
MX_DMA1_Init(&hadc1, &hdma_adc1, DMA1_Channel1, DMA_REQUEST_ADC1);
MX_DMA1_Init(&hadc2, &hdma_adc2, DMA1_Channel2, DMA_REQUEST_ADC2);
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuffer1, ADC_BUF_LEN_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adcBuffer2, ADC_BUF_LEN_2) != HAL_OK)
{
Error_Handler();
}
HAL_OPAMP_Start(&hopamp1);
HAL_OPAMP_Start(&hopamp2);
HAL_OPAMP_Start(&hopamp3);
// Check if the ADC DMA is collecting any data.
// If this fails, it likely means timer1 has not started. Verify that your application starts
// the motor pwm (usually BLDCDriver6PWM::init()) before initializing the ADC engine.
_delay(5);
if (adcBuffer1[0] == 0 || adcBuffer1[1] == 0 || adcBuffer2[0] == 0) {
Error_Handler();
}
}
// do the same for low side
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
_configureADCInline(pinA, pinB, pinC);
}
extern "C" {
void DMA1_Channel1_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_adc1);
}
void DMA1_Channel2_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_adc2);
}
}
#endif
@@ -0,0 +1,37 @@
#include "../hardware_api.h"
#if defined(__arm__) && defined(CORE_TEENSY)
#define _ADC_VOLTAGE 3.3f
#define _ADC_RESOLUTION 1024.0f
// adc counts to voltage conversion ratio
// some optimizing for faster execution
#define _ADC_CONV ( (_ADC_VOLTAGE) / (_ADC_RESOLUTION) )
// function reading an ADC value and returning the read voltage
float _readADCVoltageInline(const int pinA){
uint32_t raw_adc = analogRead(pinA);
return raw_adc * _ADC_CONV;
}
// function reading an ADC value and returning the read voltage
void _configureADCInline(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
// function reading an ADC value and returning the read voltage
float _readADCVoltageLowSide(const int pinA){
return _readADCVoltageInline(pinA);
}
// Configure low side for generic mcu
// cannot do much but
void _configureADCLowSide(const int pinA,const int pinB,const int pinC){
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if( _isset(pinC) ) pinMode(pinC, INPUT);
}
#endif
@@ -0,0 +1,92 @@
#include "BLDCDriver3PWM.h"
BLDCDriver3PWM::BLDCDriver3PWM(int phA, int phB, int phC, int en1, int en2, int en3){
// Pin initialization
pwmA = phA;
pwmB = phB;
pwmC = phC;
// enable_pin pin
enableA_pin = en1;
enableB_pin = en2;
enableC_pin = en3;
// default power-supply value
voltage_power_supply = DEF_POWER_SUPPLY;
voltage_limit = NOT_SET;
pwm_frequency = NOT_SET;
}
// enable motor driver
void BLDCDriver3PWM::enable(){
// enable_pin the driver - if enable_pin pin available
if ( _isset(enableA_pin) ) digitalWrite(enableA_pin, enable_active_high);
if ( _isset(enableB_pin) ) digitalWrite(enableB_pin, enable_active_high);
if ( _isset(enableC_pin) ) digitalWrite(enableC_pin, enable_active_high);
// set zero to PWM
setPwm(0,0,0);
}
// disable motor driver
void BLDCDriver3PWM::disable()
{
// set zero to PWM
setPwm(0, 0, 0);
// disable the driver - if enable_pin pin available
if ( _isset(enableA_pin) ) digitalWrite(enableA_pin, !enable_active_high);
if ( _isset(enableB_pin) ) digitalWrite(enableB_pin, !enable_active_high);
if ( _isset(enableC_pin) ) digitalWrite(enableC_pin, !enable_active_high);
}
// init hardware pins
int BLDCDriver3PWM::init() {
// PWM pins
pinMode(pwmA, OUTPUT);
pinMode(pwmB, OUTPUT);
pinMode(pwmC, OUTPUT);
if( _isset(enableA_pin)) pinMode(enableA_pin, OUTPUT);
if( _isset(enableB_pin)) pinMode(enableB_pin, OUTPUT);
if( _isset(enableC_pin)) pinMode(enableC_pin, OUTPUT);
// sanity check for the voltage limit configuration
if(!_isset(voltage_limit) || voltage_limit > voltage_power_supply) voltage_limit = voltage_power_supply;
// Set the pwm frequency to the pins
// hardware specific function - depending on driver and mcu
_configure3PWM(pwm_frequency, pwmA, pwmB, pwmC);
initialized = true; // TODO atm the api gives no feedback if initialization succeeded
return 0;
}
// Set voltage to the pwm pin
void BLDCDriver3PWM::setPhaseState(int sa, int sb, int sc) {
// disable if needed
if( _isset(enableA_pin) && _isset(enableB_pin) && _isset(enableC_pin) ){
digitalWrite(enableA_pin, sa == _HIGH_IMPEDANCE ? LOW : HIGH);
digitalWrite(enableB_pin, sb == _HIGH_IMPEDANCE ? LOW : HIGH);
digitalWrite(enableC_pin, sc == _HIGH_IMPEDANCE ? LOW : HIGH);
}
}
// Set voltage to the pwm pin
void BLDCDriver3PWM::setPwm(float Ua, float Ub, float Uc) {
// limit the voltage in driver
Ua = _constrain(Ua, 0.0f, voltage_limit);
Ub = _constrain(Ub, 0.0f, voltage_limit);
Uc = _constrain(Uc, 0.0f, voltage_limit);
// calculate duty cycle
// limited in [0,1]
dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f );
dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f );
dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f );
// hardware specific writing
// hardware specific function - depending on driver and mcu
_writeDutyCycle3PWM(dc_a, dc_b, dc_c, pwmA, pwmB, pwmC);
}
@@ -0,0 +1,65 @@
#ifndef BLDCDriver3PWM_h
#define BLDCDriver3PWM_h
#include "../common/base_classes/BLDCDriver.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "hardware_api.h"
/**
3 pwm bldc driver class
*/
class BLDCDriver3PWM: public BLDCDriver
{
public:
/**
BLDCDriver class constructor
@param phA A phase pwm pin
@param phB B phase pwm pin
@param phC C phase pwm pin
@param en1 enable pin (optional input)
@param en2 enable pin (optional input)
@param en3 enable pin (optional input)
*/
BLDCDriver3PWM(int phA,int phB,int phC, int en1 = NOT_SET, int en2 = NOT_SET, int en3 = NOT_SET);
/** Motor hardware init function */
int init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
// hardware variables
int pwmA; //!< phase A pwm pin number
int pwmB; //!< phase B pwm pin number
int pwmC; //!< phase C pwm pin number
int enableA_pin; //!< enable pin number
int enableB_pin; //!< enable pin number
int enableC_pin; //!< enable pin number
bool enable_active_high = true;
/**
* Set phase voltages to the harware
*
* @param Ua - phase A voltage
* @param Ub - phase B voltage
* @param Uc - phase C voltage
*/
void setPwm(float Ua, float Ub, float Uc) override;
/**
* Set phase voltages to the harware
*
* @param sc - phase A state : active / disabled ( high impedance )
* @param sb - phase B state : active / disabled ( high impedance )
* @param sa - phase C state : active / disabled ( high impedance )
*/
virtual void setPhaseState(int sa, int sb, int sc) override;
private:
};
#endif
@@ -0,0 +1,86 @@
#include "BLDCDriver6PWM.h"
BLDCDriver6PWM::BLDCDriver6PWM(int phA_h,int phA_l,int phB_h,int phB_l,int phC_h,int phC_l, int en){
// Pin initialization
pwmA_h = phA_h;
pwmB_h = phB_h;
pwmC_h = phC_h;
pwmA_l = phA_l;
pwmB_l = phB_l;
pwmC_l = phC_l;
// enable_pin pin
enable_pin = en;
// default power-supply value
voltage_power_supply = DEF_POWER_SUPPLY;
voltage_limit = NOT_SET;
pwm_frequency = NOT_SET;
// dead zone initial - 2%
dead_zone = 0.02f;
}
// enable motor driver
void BLDCDriver6PWM::enable(){
// enable_pin the driver - if enable_pin pin available
if ( _isset(enable_pin) ) digitalWrite(enable_pin, enable_active_high);
// set zero to PWM
setPwm(0, 0, 0);
}
// disable motor driver
void BLDCDriver6PWM::disable()
{
// set zero to PWM
setPwm(0, 0, 0);
// disable the driver - if enable_pin pin available
if ( _isset(enable_pin) ) digitalWrite(enable_pin, !enable_active_high);
}
// init hardware pins
int BLDCDriver6PWM::init() {
// PWM pins
pinMode(pwmA_h, OUTPUT);
pinMode(pwmB_h, OUTPUT);
pinMode(pwmC_h, OUTPUT);
pinMode(pwmA_l, OUTPUT);
pinMode(pwmB_l, OUTPUT);
pinMode(pwmC_l, OUTPUT);
if(_isset(enable_pin)) pinMode(enable_pin, OUTPUT);
// sanity check for the voltage limit configuration
if( !_isset(voltage_limit) || voltage_limit > voltage_power_supply) voltage_limit = voltage_power_supply;
// configure 6pwm
// hardware specific function - depending on driver and mcu
int result = _configure6PWM(pwm_frequency, dead_zone, pwmA_h,pwmA_l, pwmB_h,pwmB_l, pwmC_h,pwmC_l);
initialized = (result==0);
return result;
}
// Set voltage to the pwm pin
void BLDCDriver6PWM::setPwm(float Ua, float Ub, float Uc) {
// limit the voltage in driver
Ua = _constrain(Ua, 0, voltage_limit);
Ub = _constrain(Ub, 0, voltage_limit);
Uc = _constrain(Uc, 0, voltage_limit);
// calculate duty cycle
// limited in [0,1]
dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f );
dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f );
dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f );
// hardware specific writing
// hardware specific function - depending on driver and mcu
_writeDutyCycle6PWM(dc_a, dc_b, dc_c, dead_zone, pwmA_h,pwmA_l, pwmB_h,pwmB_l, pwmC_h,pwmC_l);
}
// Set voltage to the pwm pin
void BLDCDriver6PWM::setPhaseState(int sa, int sb, int sc) {
// TODO implement disabling
}
@@ -0,0 +1,67 @@
#ifndef BLDCDriver6PWM_h
#define BLDCDriver6PWM_h
#include "../common/base_classes/BLDCDriver.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "hardware_api.h"
/**
6 pwm bldc driver class
*/
class BLDCDriver6PWM: public BLDCDriver
{
public:
/**
BLDCDriver class constructor
@param phA_h A phase pwm pin
@param phA_l A phase pwm pin
@param phB_h B phase pwm pin
@param phB_l A phase pwm pin
@param phC_h C phase pwm pin
@param phC_l A phase pwm pin
@param en enable pin (optional input)
*/
BLDCDriver6PWM(int phA_h,int phA_l,int phB_h,int phB_l,int phC_h,int phC_l, int en = NOT_SET);
/** Motor hardware init function */
int init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
// hardware variables
int pwmA_h,pwmA_l; //!< phase A pwm pin number
int pwmB_h,pwmB_l; //!< phase B pwm pin number
int pwmC_h,pwmC_l; //!< phase C pwm pin number
int enable_pin; //!< enable pin number
bool enable_active_high = true;
float dead_zone; //!< a percentage of dead-time(zone) (both high and low side in low) for each pwm cycle [0,1]
/**
* Set phase voltages to the harware
*
* @param Ua - phase A voltage
* @param Ub - phase B voltage
* @param Uc - phase C voltage
*/
void setPwm(float Ua, float Ub, float Uc) override;
/**
* Set phase voltages to the harware
*
* @param sc - phase A state : active / disabled ( high impedance )
* @param sb - phase B state : active / disabled ( high impedance )
* @param sa - phase C state : active / disabled ( high impedance )
*/
virtual void setPhaseState(int sa, int sb, int sc) override;
private:
};
#endif
@@ -0,0 +1,107 @@
#include "StepperDriver2PWM.h"
StepperDriver2PWM::StepperDriver2PWM(int _pwm1, int* _in1, int _pwm2, int* _in2, int en1, int en2){
// Pin initialization
pwm1 = _pwm1; // phase 1 pwm pin number
dir1a = _in1[0]; // phase 1 INA pin number
dir1b = _in1[1]; // phase 1 INB pin number
pwm2 = _pwm2; // phase 2 pwm pin number
dir2a = _in2[0]; // phase 2 INA pin number
dir2b = _in2[1]; // phase 2 INB pin number
// enable_pin pin
enable_pin1 = en1;
enable_pin2 = en2;
// default power-supply value
voltage_power_supply = DEF_POWER_SUPPLY;
voltage_limit = NOT_SET;
pwm_frequency = NOT_SET;
}
StepperDriver2PWM::StepperDriver2PWM(int _pwm1, int _dir1, int _pwm2, int _dir2, int en1, int en2){
// Pin initialization
pwm1 = _pwm1; // phase 1 pwm pin number
dir1a = _dir1; // phase 1 direction pin
pwm2 = _pwm2; // phase 2 pwm pin number
dir2a = _dir2; // phase 2 direction pin
// these pins are not used
dir1b = NOT_SET;
dir2b = NOT_SET;
// enable_pin pin
enable_pin1 = en1;
enable_pin2 = en2;
// default power-supply value
voltage_power_supply = DEF_POWER_SUPPLY;
voltage_limit = NOT_SET;
pwm_frequency = NOT_SET;
}
// enable motor driver
void StepperDriver2PWM::enable(){
// enable_pin the driver - if enable_pin pin available
if ( _isset(enable_pin1) ) digitalWrite(enable_pin1, HIGH);
if ( _isset(enable_pin2) ) digitalWrite(enable_pin2, HIGH);
// set zero to PWM
setPwm(0,0);
}
// disable motor driver
void StepperDriver2PWM::disable()
{
// set zero to PWM
setPwm(0, 0);
// disable the driver - if enable_pin pin available
if ( _isset(enable_pin1) ) digitalWrite(enable_pin1, LOW);
if ( _isset(enable_pin2) ) digitalWrite(enable_pin2, LOW);
}
// init hardware pins
int StepperDriver2PWM::init() {
// PWM pins
pinMode(pwm1, OUTPUT);
pinMode(pwm2, OUTPUT);
pinMode(dir1a, OUTPUT);
pinMode(dir2a, OUTPUT);
if( _isset(dir1b) ) pinMode(dir1b, OUTPUT);
if( _isset(dir2b) ) pinMode(dir2b, OUTPUT);
if( _isset(enable_pin1) ) pinMode(enable_pin1, OUTPUT);
if( _isset(enable_pin2) ) pinMode(enable_pin2, OUTPUT);
// sanity check for the voltage limit configuration
if( !_isset(voltage_limit) || voltage_limit > voltage_power_supply) voltage_limit = voltage_power_supply;
// Set the pwm frequency to the pins
// hardware specific function - depending on driver and mcu
_configure2PWM(pwm_frequency, pwm1, pwm2);
initialized = true; // TODO atm the api gives no feedback if initialization succeeded
return 0;
}
// Set voltage to the pwm pin
void StepperDriver2PWM::setPwm(float Ua, float Ub) {
float duty_cycle1(0.0f),duty_cycle2(0.0f);
// limit the voltage in driver
Ua = _constrain(Ua, -voltage_limit, voltage_limit);
Ub = _constrain(Ub, -voltage_limit, voltage_limit);
// hardware specific writing
duty_cycle1 = _constrain(abs(Ua)/voltage_power_supply,0.0f,1.0f);
duty_cycle2 = _constrain(abs(Ub)/voltage_power_supply,0.0f,1.0f);
// phase 1 direction
digitalWrite(dir1a, Ua >= 0 ? LOW : HIGH);
if( _isset(dir1b) ) digitalWrite(dir1b, Ua >= 0 ? HIGH : LOW );
// phase 2 direction
digitalWrite(dir2a, Ub >= 0 ? LOW : HIGH);
if( _isset(dir2b) ) digitalWrite(dir2b, Ub >= 0 ? HIGH : LOW );
// write to hardware
_writeDutyCycle2PWM(duty_cycle1, duty_cycle2, pwm1, pwm2);
}
@@ -0,0 +1,68 @@
#ifndef STEPPER_DRIVER_2PWM_h
#define STEPPER_DRIVER_2PWM_h
#include "../common/base_classes/StepperDriver.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "hardware_api.h"
/**
2 pwm stepper driver class
*/
class StepperDriver2PWM: public StepperDriver
{
public:
/**
StepperMotor class constructor
@param pwm1 PWM1 phase pwm pin
@param in1 IN1A phase dir pin
@param pwm2 PWM2 phase pwm pin
@param in2 IN2A phase dir
@param en1 enable pin phase 1 (optional input)
@param en2 enable pin phase 2 (optional input)
*/
StepperDriver2PWM(int pwm1, int* in1, int pwm2, int* in2, int en1 = NOT_SET, int en2 = NOT_SET);
/**
StepperMotor class constructor
@param pwm1 PWM1 phase pwm pin
@param dir1 DIR1 phase dir pin
@param pwm2 PWM2 phase pwm pin
@param dir2 DIR2 phase dir pin
@param en1 enable pin phase 1 (optional input)
@param en2 enable pin phase 2 (optional input)
*/
StepperDriver2PWM(int pwm1, int dir1, int pwm2, int dir2, int en1 = NOT_SET, int en2 = NOT_SET);
/** Motor hardware init function */
int init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
// hardware variables
int pwm1; //!< phase 1 pwm pin number
int dir1a; //!< phase 1 INA pin number
int dir1b; //!< phase 1 INB pin number
int pwm2; //!< phase 2 pwm pin number
int dir2a; //!< phase 2 INA pin number
int dir2b; //!< phase 2 INB pin number
int enable_pin1; //!< enable pin number phase 1
int enable_pin2; //!< enable pin number phase 2
/**
* Set phase voltages to the harware
*
* @param Ua phase A voltage
* @param Ub phase B voltage
*/
void setPwm(float Ua, float Ub) override;
private:
};
#endif
@@ -0,0 +1,81 @@
#include "StepperDriver4PWM.h"
StepperDriver4PWM::StepperDriver4PWM(int ph1A,int ph1B,int ph2A,int ph2B,int en1, int en2){
// Pin initialization
pwm1A = ph1A;
pwm1B = ph1B;
pwm2A = ph2A;
pwm2B = ph2B;
// enable_pin pin
enable_pin1 = en1;
enable_pin2 = en2;
// default power-supply value
voltage_power_supply = DEF_POWER_SUPPLY;
voltage_limit = NOT_SET;
pwm_frequency = NOT_SET;
}
// enable motor driver
void StepperDriver4PWM::enable(){
// enable_pin the driver - if enable_pin pin available
if ( _isset(enable_pin1) ) digitalWrite(enable_pin1, HIGH);
if ( _isset(enable_pin2) ) digitalWrite(enable_pin2, HIGH);
// set zero to PWM
setPwm(0,0);
}
// disable motor driver
void StepperDriver4PWM::disable()
{
// set zero to PWM
setPwm(0, 0);
// disable the driver - if enable_pin pin available
if ( _isset(enable_pin1) ) digitalWrite(enable_pin1, LOW);
if ( _isset(enable_pin2) ) digitalWrite(enable_pin2, LOW);
}
// init hardware pins
int StepperDriver4PWM::init() {
// PWM pins
pinMode(pwm1A, OUTPUT);
pinMode(pwm1B, OUTPUT);
pinMode(pwm2A, OUTPUT);
pinMode(pwm2B, OUTPUT);
if( _isset(enable_pin1) ) pinMode(enable_pin1, OUTPUT);
if( _isset(enable_pin2) ) pinMode(enable_pin2, OUTPUT);
// sanity check for the voltage limit configuration
if( !_isset(voltage_limit) || voltage_limit > voltage_power_supply) voltage_limit = voltage_power_supply;
// Set the pwm frequency to the pins
// hardware specific function - depending on driver and mcu
_configure4PWM(pwm_frequency, pwm1A, pwm1B, pwm2A, pwm2B);
initialized = true; // TODO atm the api gives no feedback if initialization succeeded
return 0;
}
// Set voltage to the pwm pin
void StepperDriver4PWM::setPwm(float Ualpha, float Ubeta) {
float duty_cycle1A(0.0f),duty_cycle1B(0.0f),duty_cycle2A(0.0f),duty_cycle2B(0.0f);
// limit the voltage in driver
Ualpha = _constrain(Ualpha, -voltage_limit, voltage_limit);
Ubeta = _constrain(Ubeta, -voltage_limit, voltage_limit);
// hardware specific writing
if( Ualpha > 0 )
duty_cycle1B = _constrain(abs(Ualpha)/voltage_power_supply,0.0f,1.0f);
else
duty_cycle1A = _constrain(abs(Ualpha)/voltage_power_supply,0.0f,1.0f);
if( Ubeta > 0 )
duty_cycle2B = _constrain(abs(Ubeta)/voltage_power_supply,0.0f,1.0f);
else
duty_cycle2A = _constrain(abs(Ubeta)/voltage_power_supply,0.0f,1.0f);
// write to hardware
_writeDutyCycle4PWM(duty_cycle1A, duty_cycle1B, duty_cycle2A, duty_cycle2B, pwm1A, pwm1B, pwm2A, pwm2B);
}
@@ -0,0 +1,55 @@
#ifndef STEPPER_DRIVER_4PWM_h
#define STEPPER_DRIVER_4PWM_h
#include "../common/base_classes/StepperDriver.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/defaults.h"
#include "hardware_api.h"
/**
4 pwm stepper driver class
*/
class StepperDriver4PWM: public StepperDriver
{
public:
/**
StepperMotor class constructor
@param ph1A 1A phase pwm pin
@param ph1B 1B phase pwm pin
@param ph2A 2A phase pwm pin
@param ph2B 2B phase pwm pin
@param en1 enable pin phase 1 (optional input)
@param en2 enable pin phase 2 (optional input)
*/
StepperDriver4PWM(int ph1A,int ph1B,int ph2A,int ph2B, int en1 = NOT_SET, int en2 = NOT_SET);
/** Motor hardware init function */
int init() override;
/** Motor disable function */
void disable() override;
/** Motor enable function */
void enable() override;
// hardware variables
int pwm1A; //!< phase 1A pwm pin number
int pwm1B; //!< phase 1B pwm pin number
int pwm2A; //!< phase 2A pwm pin number
int pwm2B; //!< phase 2B pwm pin number
int enable_pin1; //!< enable pin number phase 1
int enable_pin2; //!< enable pin number phase 2
/**
* Set phase voltages to the harware
*
* @param Ua phase A voltage
* @param Ub phase B voltage
*/
void setPwm(float Ua, float Ub) override;
private:
};
#endif
@@ -0,0 +1,123 @@
#ifndef HARDWARE_UTILS_DRIVER_H
#define HARDWARE_UTILS_DRIVER_H
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
/**
* Configuring PWM frequency, resolution and alignment
* - Stepper driver - 2PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pinA pinA bldc driver
* @param pinB pinB bldc driver
*/
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB);
/**
* Configuring PWM frequency, resolution and alignment
* - BLDC driver - 3PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pinA pinA bldc driver
* @param pinB pinB bldc driver
* @param pinC pinC bldc driver
*/
void _configure3PWM(long pwm_frequency, const int pinA, const int pinB, const int pinC);
/**
* Configuring PWM frequency, resolution and alignment
* - Stepper driver - 4PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pin1A pin1A stepper driver
* @param pin1B pin1B stepper driver
* @param pin2A pin2A stepper driver
* @param pin2B pin2B stepper driver
*/
void _configure4PWM(long pwm_frequency, const int pin1A, const int pin1B, const int pin2A, const int pin2B);
/**
* Configuring PWM frequency, resolution and alignment
* - BLDC driver - 6PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param dead_zone duty cycle protection zone [0, 1] - both low and high side low - if applicable
* @param pinA_h pinA high-side bldc driver
* @param pinA_l pinA low-side bldc driver
* @param pinB_h pinA high-side bldc driver
* @param pinB_l pinA low-side bldc driver
* @param pinC_h pinA high-side bldc driver
* @param pinC_l pinA low-side bldc driver
*
* @return 0 if config good, -1 if failed
*/
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l);
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - Stepper driver - 2PWM setting
* - hardware specific
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param pinA phase A hardware pin number
* @param pinB phase B hardware pin number
*/
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB);
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - BLDC driver - 3PWM setting
* - hardware specific
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param dc_c duty cycle phase C [0, 1]
* @param pinA phase A hardware pin number
* @param pinB phase B hardware pin number
* @param pinC phase C hardware pin number
*/
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC);
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - Stepper driver - 4PWM setting
* - hardware specific
*
* @param dc_1a duty cycle phase 1A [0, 1]
* @param dc_1b duty cycle phase 1B [0, 1]
* @param dc_2a duty cycle phase 2A [0, 1]
* @param dc_2b duty cycle phase 2B [0, 1]
* @param pin1A phase 1A hardware pin number
* @param pin1B phase 1B hardware pin number
* @param pin2A phase 2A hardware pin number
* @param pin2B phase 2B hardware pin number
*/
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B);
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - BLDC driver - 6PWM setting
* - hardware specific
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param dc_c duty cycle phase C [0, 1]
* @param dead_zone duty cycle protection zone [0, 1] - both low and high side low
* @param pinA_h phase A high-side hardware pin number
* @param pinA_l phase A low-side hardware pin number
* @param pinB_h phase B high-side hardware pin number
* @param pinB_l phase B low-side hardware pin number
* @param pinC_h phase C high-side hardware pin number
* @param pinC_l phase C low-side hardware pin number
*
*/
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l);
#endif
@@ -0,0 +1,158 @@
#include "../hardware_api.h"
#if defined(__AVR_ATmega2560__)
// set pwm frequency to 32KHz
void _pinHighFrequency(const int pin){
// High PWM frequency
// https://sites.google.com/site/qeewiki/books/avr-guide/timers-on-the-atmega328
// https://forum.arduino.cc/index.php?topic=72092.0
if (pin == 13 || pin == 4 ) {
TCCR0A = ((TCCR0A & 0b11111100) | 0x01); // configure the pwm phase-corrected mode
TCCR0B = ((TCCR0B & 0b11110000) | 0x01); // set prescaler to 1
}
else if (pin == 12 || pin == 11 )
TCCR1B = ((TCCR1B & 0b11111000) | 0x01); // set prescaler to 1
else if (pin == 10 || pin == 9 )
TCCR2B = ((TCCR2B & 0b11111000) | 0x01); // set prescaler to 1
else if (pin == 5 || pin == 3 || pin == 2)
TCCR3B = ((TCCR2B & 0b11111000) | 0x01); // set prescaler to 1
else if (pin == 8 || pin == 7 || pin == 6)
TCCR4B = ((TCCR4B & 0b11111000) | 0x01); // set prescaler to 1
else if (pin == 44 || pin == 45 || pin == 46)
TCCR5B = ((TCCR5B & 0b11111000) | 0x01); // set prescaler to 1
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
_pinHighFrequency(pinC);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
analogWrite(pinC, 255.0f*dc_c);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pin1A);
_pinHighFrequency(pin1B);
_pinHighFrequency(pin2A);
_pinHighFrequency(pin2B);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0f*dc_1a);
analogWrite(pin1B, 255.0f*dc_1b);
analogWrite(pin2A, 255.0f*dc_2a);
analogWrite(pin2B, 255.0f*dc_2b);
}
// function configuring pair of high-low side pwm channels, 32khz frequency and center aligned pwm
// supports Arudino/ATmega2560
int _configureComplementaryPair(int pinH, int pinL) {
if( (pinH == 4 && pinL == 13 ) || (pinH == 13 && pinL == 4 ) ){
// configure the pwm phase-corrected mode
TCCR0A = ((TCCR0A & 0b11111100) | 0x01);
// configure complementary pwm on low side
if(pinH == 13 ) TCCR0A = 0b10110000 | (TCCR0A & 0b00001111) ;
else TCCR0A = 0b11100000 | (TCCR0A & 0b00001111) ;
// set prescaler to 1 - 32kHz freq
TCCR0B = ((TCCR0B & 0b11110000) | 0x01);
}else if( (pinH == 11 && pinL == 12 ) || (pinH == 12 && pinL == 11 ) ){
// set prescaler to 1 - 32kHz freq
TCCR1B = ((TCCR1B & 0b11111000) | 0x01);
// configure complementary pwm on low side
if(pinH == 11 ) TCCR1A = 0b10110000 | (TCCR1A & 0b00001111) ;
else TCCR1A = 0b11100000 | (TCCR1A & 0b00001111) ;
}else if((pinH == 10 && pinL == 9 ) || (pinH == 9 && pinL == 10 ) ){
// set prescaler to 1 - 32kHz freq
TCCR2B = ((TCCR2B & 0b11111000) | 0x01);
// configure complementary pwm on low side
if(pinH == 10 ) TCCR2A = 0b10110000 | (TCCR2A & 0b00001111) ;
else TCCR2A = 0b11100000 | (TCCR2A & 0b00001111) ;
}else{
return -1;
}
return 0;
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
// supports Arudino/ATmega328
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
// High PWM frequency
// - always max 32kHz
int ret_flag = 0;
ret_flag += _configureComplementaryPair(pinA_h, pinA_l);
ret_flag += _configureComplementaryPair(pinB_h, pinB_l);
ret_flag += _configureComplementaryPair(pinC_h, pinC_l);
return ret_flag; // returns -1 if not well configured
}
// function setting the
void _setPwmPair(int pinH, int pinL, float val, int dead_time)
{
int pwm_h = _constrain(val-dead_time/2,0,255);
int pwm_l = _constrain(val+dead_time/2,0,255);
analogWrite(pinH, pwm_h);
if(pwm_l == 255 || pwm_l == 0)
digitalWrite(pinL, pwm_l ? LOW : HIGH);
else
analogWrite(pinL, pwm_l);
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
// supports Arudino/ATmega328
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
_setPwmPair(pinA_h, pinA_l, dc_a*255.0, dead_zone*255.0);
_setPwmPair(pinB_h, pinB_l, dc_b*255.0, dead_zone*255.0);
_setPwmPair(pinC_h, pinC_l, dc_c*255.0, dead_zone*255.0);
}
#endif
@@ -0,0 +1,156 @@
#include "../hardware_api.h"
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328PB__)
// set pwm frequency to 32KHz
void _pinHighFrequency(const int pin){
// High PWM frequency
// https://sites.google.com/site/qeewiki/books/avr-guide/timers-on-the-atmega328
if (pin == 5 || pin == 6 ) {
TCCR0A = ((TCCR0A & 0b11111100) | 0x01); // configure the pwm phase-corrected mode
TCCR0B = ((TCCR0B & 0b11110000) | 0x01); // set prescaler to 1
}
if (pin == 9 || pin == 10 )
TCCR1B = ((TCCR1B & 0b11111000) | 0x01); // set prescaler to 1
if (pin == 3 || pin == 11)
TCCR2B = ((TCCR2B & 0b11111000) | 0x01);// set prescaler to 1
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
// supports Arudino/ATmega328
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
_UNUSED(pwm_frequency);
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
// supports Arudino/ATmega328
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
_UNUSED(pwm_frequency);
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
_pinHighFrequency(pinC);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
analogWrite(pinC, 255.0f*dc_c);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
// supports Arudino/ATmega328
void _configure4PWM(long pwm_frequency,const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pin1A);
_pinHighFrequency(pin1B);
_pinHighFrequency(pin2A);
_pinHighFrequency(pin2B);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0f*dc_1a);
analogWrite(pin1B, 255.0f*dc_1b);
analogWrite(pin2A, 255.0f*dc_2a);
analogWrite(pin2B, 255.0f*dc_2b);
}
// function configuring pair of high-low side pwm channels, 32khz frequency and center aligned pwm
int _configureComplementaryPair(int pinH, int pinL) {
if( (pinH == 5 && pinL == 6 ) || (pinH == 6 && pinL == 5 ) ){
// configure the pwm phase-corrected mode
TCCR0A = ((TCCR0A & 0b11111100) | 0x01);
// configure complementary pwm on low side
if(pinH == 6 ) TCCR0A = 0b10110000 | (TCCR0A & 0b00001111) ;
else TCCR0A = 0b11100000 | (TCCR0A & 0b00001111) ;
// set prescaler to 1 - 32kHz freq
TCCR0B = ((TCCR0B & 0b11110000) | 0x01);
}else if( (pinH == 9 && pinL == 10 ) || (pinH == 10 && pinL == 9 ) ){
// set prescaler to 1 - 32kHz freq
TCCR1B = ((TCCR1B & 0b11111000) | 0x01);
// configure complementary pwm on low side
if(pinH == 9 ) TCCR1A = 0b10110000 | (TCCR1A & 0b00001111) ;
else TCCR1A = 0b11100000 | (TCCR1A & 0b00001111) ;
}else if((pinH == 3 && pinL == 11 ) || (pinH == 11 && pinL == 3 ) ){
// set prescaler to 1 - 32kHz freq
TCCR2B = ((TCCR2B & 0b11111000) | 0x01);
// configure complementary pwm on low side
if(pinH == 11 ) TCCR2A = 0b10110000 | (TCCR2A & 0b00001111) ;
else TCCR2A = 0b11100000 | (TCCR2A & 0b00001111) ;
}else{
return -1;
}
return 0;
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
// supports Arudino/ATmega328
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
_UNUSED(pwm_frequency);
_UNUSED(dead_zone);
// High PWM frequency
// - always max 32kHz
int ret_flag = 0;
ret_flag += _configureComplementaryPair(pinA_h, pinA_l);
ret_flag += _configureComplementaryPair(pinB_h, pinB_l);
ret_flag += _configureComplementaryPair(pinC_h, pinC_l);
return ret_flag; // returns -1 if not well configured
}
// function setting the
void _setPwmPair(int pinH, int pinL, float val, int dead_time)
{
int pwm_h = _constrain(val-dead_time/2,0,255);
int pwm_l = _constrain(val+dead_time/2,0,255);
analogWrite(pinH, pwm_h);
if(pwm_l == 255 || pwm_l == 0)
digitalWrite(pinL, pwm_l ? LOW : HIGH);
else
analogWrite(pinL, pwm_l);
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
// supports Arudino/ATmega328
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
_setPwmPair(pinA_h, pinA_l, dc_a*255.0, dead_zone*255.0);
_setPwmPair(pinB_h, pinB_l, dc_b*255.0, dead_zone*255.0);
_setPwmPair(pinC_h, pinC_l, dc_c*255.0, dead_zone*255.0);
}
#endif
@@ -0,0 +1,172 @@
#include "../hardware_api.h"
#if defined(__AVR_ATmega32U4__)
// set pwm frequency to 32KHz
void _pinHighFrequency(const int pin){
// High PWM frequency
// reference: http://r6500.blogspot.com/2014/12/fast-pwm-on-arduino-leonardo.html
if (pin == 3 || pin == 11 ) {
TCCR0A = ((TCCR0A & 0b11111100) | 0x01); // configure the pwm phase-corrected mode
TCCR0B = ((TCCR0B & 0b11110000) | 0x01); // set prescaler to 1
}
else if (pin == 9 || pin == 10 )
TCCR1B = ((TCCR1B & 0b11111000) | 0x01); // set prescaler to 1
else if (pin == 5 )
TCCR3B = ((TCCR3B & 0b11111000) | 0x01); // set prescaler to 1
else if ( pin == 6 || pin == 13 ) { // a bit more complicated 10 bit timer
// PLL Configuration
PLLFRQ= ((PLLFRQ & 0b11001111) | 0x20); // Use 96MHz / 1.5 = 64MHz
TCCR4B = ((TCCR4B & 0b11110000) | 0xB); // configure prescaler to get 64M/2/1024 = 31.25 kHz
TCCR4D = ((TCCR4D & 0b11111100) | 0x01); // configure the pwm phase-corrected mode
if (pin == 6) TCCR4A = 0x82; // activate channel A - pin 13
else if (pin == 13) TCCR4C |= 0x09; // Activate channel D - pin 6
}
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pinA);
_pinHighFrequency(pinB);
_pinHighFrequency(pinC);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0*dc_a);
analogWrite(pinB, 255.0*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0*dc_a);
analogWrite(pinB, 255.0*dc_b);
analogWrite(pinC, 255.0*dc_c);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
// High PWM frequency
// - always max 32kHz
_pinHighFrequency(pin1A);
_pinHighFrequency(pin1B);
_pinHighFrequency(pin2A);
_pinHighFrequency(pin2B);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0*dc_1a);
analogWrite(pin1B, 255.0*dc_1b);
analogWrite(pin2A, 255.0*dc_2a);
analogWrite(pin2B, 255.0*dc_2b);
}
// function configuring pair of high-low side pwm channels, 32khz frequency and center aligned pwm
int _configureComplementaryPair(int pinH, int pinL) {
if( (pinH == 3 && pinL == 11 ) || (pinH == 11 && pinL == 3 ) ){
// configure the pwm phase-corrected mode
TCCR0A = ((TCCR0A & 0b11111100) | 0x01);
// configure complementary pwm on low side
if(pinH == 11 ) TCCR0A = 0b10110000 | (TCCR0A & 0b00001111) ;
else TCCR0A = 0b11100000 | (TCCR0A & 0b00001111) ;
// set prescaler to 1 - 32kHz freq
TCCR0B = ((TCCR0B & 0b11110000) | 0x01);
}else if( (pinH == 9 && pinL == 10 ) || (pinH == 10 && pinL == 9 ) ){
// set prescaler to 1 - 32kHz freq
TCCR1B = ((TCCR1B & 0b11111000) | 0x01);
// configure complementary pwm on low side
if(pinH == 9 ) TCCR1A = 0b10110000 | (TCCR1A & 0b00001111) ;
else TCCR1A = 0b11100000 | (TCCR1A & 0b00001111) ;
}else if((pinH == 6 && pinL == 13 ) || (pinH == 13 && pinL == 6 ) ){
// PLL Configuration
PLLFRQ= ((PLLFRQ & 0b11001111) | 0x20); // Use 96MHz / 1.5 = 64MHz
TCCR4B = ((TCCR4B & 0b11110000) | 0xB); // configure prescaler to get 64M/2/1024 = 31.25 kHz
TCCR4D = ((TCCR4D & 0b11111100) | 0x01); // configure the pwm phase-corrected mode
// configure complementary pwm on low side
if(pinH == 13 ){
TCCR4A = 0x82; // activate channel A - pin 13
TCCR4C |= 0x0d; // Activate complementary channel D - pin 6
}else {
TCCR4C |= 0x09; // Activate channel D - pin 6
TCCR4A = 0xc2; // activate complementary channel A - pin 13
}
}else{
return -1;
}
return 0;
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
_UNUSED(pwm_frequency);
_UNUSED(dead_zone);
// High PWM frequency
// - always max 32kHz
int ret_flag = 0;
ret_flag += _configureComplementaryPair(pinA_h, pinA_l);
ret_flag += _configureComplementaryPair(pinB_h, pinB_l);
ret_flag += _configureComplementaryPair(pinC_h, pinC_l);
return ret_flag; // returns -1 if not well configured
// function setting the
void _setPwmPair(int pinH, int pinL, float val, int dead_time)
{
int pwm_h = _constrain(val-dead_time/2,0,255);
int pwm_l = _constrain(val+dead_time/2,0,255);
analogWrite(pinH, pwm_h);
if(pwm_l == 255 || pwm_l == 0)
digitalWrite(pinL, pwm_l ? LOW : HIGH);
else
analogWrite(pinL, pwm_l);
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
// supports Arudino/ATmega328
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
_setPwmPair(pinA_h, pinA_l, dc_a*255.0, dead_zone*255.0);
_setPwmPair(pinB_h, pinB_l, dc_b*255.0, dead_zone*255.0);
_setPwmPair(pinC_h, pinC_l, dc_c*255.0, dead_zone*255.0);
}
#endif
@@ -0,0 +1,406 @@
#include "../hardware_api.h"
#if defined(__arm__) && defined(__SAM3X8E__)
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 50000 // 50khz
#define _PWM_RES_MIN 255 // 50khz
// pwm frequency and max duty cycle
static unsigned long _pwm_frequency;
static int _max_pwm_value = 1023;
// array mapping the timer values to the interrupt handlers
static IRQn_Type irq_type[] = {TC0_IRQn, TC0_IRQn, TC1_IRQn, TC1_IRQn, TC2_IRQn, TC2_IRQn, TC3_IRQn, TC3_IRQn, TC4_IRQn, TC4_IRQn, TC5_IRQn, TC5_IRQn, TC6_IRQn, TC6_IRQn, TC7_IRQn, TC7_IRQn, TC8_IRQn, TC8_IRQn};
// current counter values
static volatile uint32_t pwm_counter_vals[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// variables copied from wiring_analog.cpp for arduino due
static uint8_t PWMEnabled = 0;
static uint8_t TCChanEnabled[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
static const uint32_t channelToChNo[] = { 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2 };
static const uint32_t channelToAB[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
static Tc *channelToTC[] = {
TC0, TC0, TC0, TC0, TC0, TC0,
TC1, TC1, TC1, TC1, TC1, TC1,
TC2, TC2, TC2, TC2, TC2, TC2 };
static const uint32_t channelToId[] = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8 };
// function setting the CMR register
static void TC_SetCMR_ChannelA(Tc *tc, uint32_t chan, uint32_t v){ tc->TC_CHANNEL[chan].TC_CMR = (tc->TC_CHANNEL[chan].TC_CMR & 0xFFF0FFFF) | v;}
static void TC_SetCMR_ChannelB(Tc *tc, uint32_t chan, uint32_t v){ tc->TC_CHANNEL[chan].TC_CMR = (tc->TC_CHANNEL[chan].TC_CMR & 0xF0FFFFFF) | v; }
// function which starts and syncs the timers
// if the pin is the true PWM pin this function does not do anything
void syncTimers(uint32_t ulPin1,uint32_t ulPin2, uint32_t ulPin3 = -1, uint32_t ulPin4 = -1){
uint32_t chNo1,chNo2,chNo3,chNo4;
Tc *chTC1 = nullptr,*chTC2 = nullptr,*chTC3 = nullptr,*chTC4 = nullptr;
// configure timer channel for the first pin if it is a timer pin
uint32_t attr = g_APinDescription[ulPin1].ulPinAttribute;
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) {
ETCChannel channel1 = g_APinDescription[ulPin1].ulTCChannel;
chNo1 = channelToChNo[channel1];
chTC1 = channelToTC[channel1];
TCChanEnabled[channelToId[channel1]] = 1;
}
// configure timer channel for the first pin if it is a timer pin
attr = g_APinDescription[ulPin2].ulPinAttribute;
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) {
ETCChannel channel2 = g_APinDescription[ulPin2].ulTCChannel;
chNo2 = channelToChNo[channel2];
chTC2 = channelToTC[channel2];
TCChanEnabled[channelToId[channel2]] = 1;
}
if(ulPin3 > 0 ){
// configure timer channel for the first pin if it is a timer pin
attr = g_APinDescription[ulPin3].ulPinAttribute;
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) {
ETCChannel channel3 = g_APinDescription[ulPin3].ulTCChannel;
chNo3 = channelToChNo[channel3];
chTC3 = channelToTC[channel3];
TCChanEnabled[channelToId[channel3]] = 1;
}
}
if(ulPin4 > 0 ){
// configure timer channel for the first pin if it is a timer pin
attr = g_APinDescription[ulPin4].ulPinAttribute;
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) {
ETCChannel channel4 = g_APinDescription[ulPin4].ulTCChannel;
chNo4 = channelToChNo[channel4];
chTC4 = channelToTC[channel4];
TCChanEnabled[channelToId[channel4]] = 1;
}
}
// start timers and make them synced
if(chTC1){
TC_Start(chTC1, chNo1);
chTC1->TC_BCR = TC_BCR_SYNC;
}
if(chTC2){
TC_Start(chTC2, chNo2);
chTC2->TC_BCR = TC_BCR_SYNC;
}
if(chTC3 && ulPin3){
TC_Start(chTC3, chNo3);
chTC3->TC_BCR = TC_BCR_SYNC;
}
if(chTC4 && ulPin4){
TC_Start(chTC4, chNo4);
chTC4->TC_BCR = TC_BCR_SYNC;
}
}
// function configuring the pwm frequency for given pin
// possible to supply the pwm pin and the timer pin
void initPWM(uint32_t ulPin, uint32_t pwm_freq){
// check which pin type
uint32_t attr = g_APinDescription[ulPin].ulPinAttribute;
if ((attr & PIN_ATTR_PWM) == PIN_ATTR_PWM) { // if pwm pin
if (!PWMEnabled) {
// PWM Startup code
pmc_enable_periph_clk(PWM_INTERFACE_ID);
// this function does not work too well - I'll rewrite it
// PWMC_ConfigureClocks(PWM_FREQUENCY * _max_pwm_value, 0, VARIANT_MCK);
// finding the divisors an prescalers form FindClockConfiguration function
uint32_t divisors[11] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};
uint8_t divisor = 0;
uint32_t prescaler;
/* Find prescaler and divisor values */
prescaler = (VARIANT_MCK / divisors[divisor]) / (pwm_freq*_max_pwm_value);
while ((prescaler > 255) && (divisor < 11)) {
divisor++;
prescaler = (VARIANT_MCK / divisors[divisor]) / (pwm_freq*_max_pwm_value);
}
// update the divisor*prescaler value
prescaler = prescaler | (divisor << 8);
// now calculate the real resolution timer period necessary (pwm resolution)
// pwm_res = bus_freq / (pwm_freq * (prescaler))
_max_pwm_value = (double)VARIANT_MCK / (double)pwm_freq / (double)(prescaler);
// set the prescaler value
PWM->PWM_CLK = prescaler;
PWMEnabled = 1;
}
uint32_t chan = g_APinDescription[ulPin].ulPWMChannel;
if ((g_pinStatus[ulPin] & 0xF) != PIN_STATUS_PWM) {
// Setup PWM for this pin
PIO_Configure(g_APinDescription[ulPin].pPort,
g_APinDescription[ulPin].ulPinType,
g_APinDescription[ulPin].ulPin,
g_APinDescription[ulPin].ulPinConfiguration);
// PWM_CMR_CALG - center align
// PWMC_ConfigureChannel(PWM_INTERFACE, chan, PWM_CMR_CPRE_CLKA, PWM_CMR_CALG, 0);
PWMC_ConfigureChannel(PWM_INTERFACE, chan, PWM_CMR_CPRE_CLKA, 0, 0);
PWMC_SetPeriod(PWM_INTERFACE, chan, _max_pwm_value);
PWMC_SetDutyCycle(PWM_INTERFACE, chan, 0);
PWMC_EnableChannel(PWM_INTERFACE, chan);
g_pinStatus[ulPin] = (g_pinStatus[ulPin] & 0xF0) | PIN_STATUS_PWM;
}
return;
}
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) { // if timer pin
// We use MCLK/2 as clock.
const uint32_t TC = VARIANT_MCK / 2 / pwm_freq ;
// Setup Timer for this pin
ETCChannel channel = g_APinDescription[ulPin].ulTCChannel;
uint32_t chNo = channelToChNo[channel];
uint32_t chA = channelToAB[channel];
Tc *chTC = channelToTC[channel];
uint32_t interfaceID = channelToId[channel];
if (!TCChanEnabled[interfaceID]) {
pmc_enable_periph_clk(TC_INTERFACE_ID + interfaceID);
TC_Configure(chTC, chNo,
TC_CMR_TCCLKS_TIMER_CLOCK1 |
TC_CMR_WAVE | // Waveform mode
TC_CMR_WAVSEL_UP_RC | // Counter running up and reset when equals to RC
TC_CMR_EEVT_XC0 | // Set external events from XC0 (this setup TIOB as output)
TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR);
TC_SetRC(chTC, chNo, TC);
}
// disable the counter on start
if (chA){
TC_SetCMR_ChannelA(chTC, chNo, TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET);
}else{
TC_SetCMR_ChannelB(chTC, chNo, TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_SET);
}
// configure input-ouput structure
if ((g_pinStatus[ulPin] & 0xF) != PIN_STATUS_PWM) {
PIO_Configure(g_APinDescription[ulPin].pPort,
g_APinDescription[ulPin].ulPinType,
g_APinDescription[ulPin].ulPin,
g_APinDescription[ulPin].ulPinConfiguration);
g_pinStatus[ulPin] = (g_pinStatus[ulPin] & 0xF0) | PIN_STATUS_PWM;
}
// enable interrupts
chTC->TC_CHANNEL[chNo].TC_IER = TC_IER_CPAS // interrupt on RA compare match
| TC_IER_CPBS // interrupt on RB compare match
| TC_IER_CPCS; // interrupt on RC compare match
chTC->TC_CHANNEL[chNo].TC_IDR = ~TC_IER_CPAS // interrupt on RA compare match
& ~TC_IER_CPBS // interrupt on RB compare match
& ~ TC_IER_CPCS; // interrupt on RC compare match
// enable interrupts for this timer
NVIC_EnableIRQ(irq_type[channel]);
return;
}
}
// pwm setting function
// it sets the duty cycle for pwm pin or timer pin
void setPwm(uint32_t ulPin, uint32_t ulValue) {
// check pin type
uint32_t attr = g_APinDescription[ulPin].ulPinAttribute;
if ((attr & PIN_ATTR_PWM) == PIN_ATTR_PWM) { // if pwm
uint32_t chan = g_APinDescription[ulPin].ulPWMChannel;
PWMC_SetDutyCycle(PWM_INTERFACE, chan, ulValue);
return;
}
if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER) { // if timer pin
// get the timer variables
ETCChannel channel = g_APinDescription[ulPin].ulTCChannel;
Tc *chTC = channelToTC[channel];
uint32_t chNo = channelToChNo[channel];
if(!ulValue) {
// if the value 0 disable counter
if (channelToAB[channel])
TC_SetCMR_ChannelA(chTC, chNo, TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR);
else
TC_SetCMR_ChannelB(chTC, chNo, TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR);
}else{
// if the value not zero
// calculate clock
const uint32_t TC = VARIANT_MCK / 2 / _pwm_frequency;
// Map value to Timer ranges 0..max_duty_cycle => 0..TC
// Setup Timer for this pin
ulValue = ulValue * TC ;
pwm_counter_vals[channel] = ulValue / _max_pwm_value;
// enable counter
if (channelToAB[channel])
TC_SetCMR_ChannelA(chTC, chNo, TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET);
else
TC_SetCMR_ChannelB(chTC, chNo, TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_SET);
}
return;
}
}
// interrupt handlers for seamless pwm duty-cycle setting
void TC0_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC0, 0);
// update the counters
if(pwm_counter_vals[0]) TC_SetRA(TC0, 0, pwm_counter_vals[0]);
if(pwm_counter_vals[1]) TC_SetRB(TC0, 0, pwm_counter_vals[1]);
}
void TC1_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC0, 1);
// update the counters
if(pwm_counter_vals[2]) TC_SetRA(TC0, 1, pwm_counter_vals[2]);
if(pwm_counter_vals[3]) TC_SetRB(TC0, 1, pwm_counter_vals[3]);
}
void TC2_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC0, 2);
// update the counters
if(pwm_counter_vals[4]) TC_SetRA(TC0, 2, pwm_counter_vals[4]);
if(pwm_counter_vals[5]) TC_SetRB(TC0, 2, pwm_counter_vals[5]);
}
void TC3_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC1, 0);
// update the counters
if(pwm_counter_vals[6]) TC_SetRA(TC1, 0, pwm_counter_vals[6]);
if(pwm_counter_vals[7]) TC_SetRB(TC1, 0, pwm_counter_vals[7]);
}
void TC4_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC1, 1);
// update the counters
if(pwm_counter_vals[8]) TC_SetRA(TC1, 1, pwm_counter_vals[8]);
if(pwm_counter_vals[9]) TC_SetRB(TC1, 1, pwm_counter_vals[9]);
}
void TC5_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC1, 2);
// update the counters
if(pwm_counter_vals[10]) TC_SetRA(TC1, 2, pwm_counter_vals[10]);
if(pwm_counter_vals[11]) TC_SetRB(TC1, 2, pwm_counter_vals[11]);
}
void TC6_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC2, 0);
// update the counters
if(pwm_counter_vals[12]) TC_SetRA(TC2, 0, pwm_counter_vals[12]);
if(pwm_counter_vals[13]) TC_SetRB(TC2, 0, pwm_counter_vals[13]);
}
void TC7_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC2, 1);
// update the counters
if(pwm_counter_vals[14]) TC_SetRA(TC2, 1, pwm_counter_vals[14]);
if(pwm_counter_vals[15]) TC_SetRB(TC2, 1, pwm_counter_vals[15]);
}
void TC8_Handler()
{
// read/clear interrupt status
TC_GetStatus(TC2, 2);
// update the counters
if(pwm_counter_vals[16]) TC_SetRA(TC2, 2, pwm_counter_vals[16]);
if(pwm_counter_vals[17]) TC_SetRB(TC2, 2, pwm_counter_vals[17]);
}
// implementation of the hardware_api.cpp
// ---------------------------------------------------------------------------------------------------------------------------------
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware specific
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 50khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// save the pwm frequency
_pwm_frequency = pwm_frequency;
// cinfigure pwm pins
initPWM(pinA, _pwm_frequency);
initPWM(pinB, _pwm_frequency);
initPWM(pinC, _pwm_frequency);
// sync the timers if possible
syncTimers(pinA, pinB, pinC);
}
// Configuring PWM frequency, resolution and alignment
//- Stepper driver - 2PWM setting
// - hardware specific
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
if(!pwm_frequency || !_isset(pwm_frequency)) pwm_frequency = _PWM_FREQUENCY; // default frequency 50khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// save the pwm frequency
_pwm_frequency = pwm_frequency;
// cinfigure pwm pins
initPWM(pinA, _pwm_frequency);
initPWM(pinB, _pwm_frequency);
// sync the timers if possible
syncTimers(pinA, pinB);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if(!pwm_frequency || !_isset(pwm_frequency)) pwm_frequency = _PWM_FREQUENCY; // default frequency 50khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// save the pwm frequency
_pwm_frequency = pwm_frequency;
// cinfigure pwm pins
initPWM(pinA, _pwm_frequency);
initPWM(pinB, _pwm_frequency);
initPWM(pinC, _pwm_frequency);
initPWM(pinD, _pwm_frequency);
// sync the timers if possible
syncTimers(pinA, pinB, pinC, pinD);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,_max_pwm_value]
setPwm(pinA, _max_pwm_value*dc_a);
setPwm(pinB, _max_pwm_value*dc_b);
setPwm(pinC, _max_pwm_value*dc_c);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,_max_pwm_value]
setPwm(pin1A, _max_pwm_value*dc_1a);
setPwm(pin1B, _max_pwm_value*dc_1b);
setPwm(pin2A, _max_pwm_value*dc_2a);
setPwm(pin2B, _max_pwm_value*dc_2b);
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - Stepper driver - 2PWM setting
// - hardware specific
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,_max_pwm_value]
setPwm(pinA, _max_pwm_value*dc_a);
setPwm(pinB, _max_pwm_value*dc_b);
}
#endif
@@ -0,0 +1,204 @@
#include "../hardware_api.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && !defined(SOC_MCPWM_SUPPORTED)
#include "driver/ledc.h"
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 38000 // 38khz max to be able to have 10 bit pwm resolution
#define _PWM_RES_BIT 10 // 10 bir resolution
#define _PWM_RES 1023 // 2^10-1 = 1023-1
// empty motor slot
#define _EMPTY_SLOT -20
#define _TAKEN_SLOT -21
// figure out how many ledc channels are avaible
// esp32 - 2x8=16
// esp32s2 - 8
// esp32c3 - 6
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
// current channel stack index
// support for multiple motors
// esp32 has 16 channels
// esp32s2 has 8 channels
// esp32c3 has 6 channels
int channel_index = 0;
// slot for the 3pwm bldc motors
typedef struct {
int pinID;
int ch1;
int ch2;
int ch3;
} bldc_3pwm_motor_slots_t;
// slot for the 2pwm stepper motors
typedef struct {
int pinID;
int ch1;
int ch2;
} stepper_2pwm_motor_slots_t;
// slot for the 4pwm stepper motors
typedef struct {
int pinID;
int ch1;
int ch2;
int ch3;
int ch4;
} stepper_4pwm_motor_slots_t;
// define motor slots array
bldc_3pwm_motor_slots_t esp32_bldc_3pwm_motor_slots[4] = {
{_EMPTY_SLOT, 0,0,0}, // 1st motor
{_EMPTY_SLOT, 0,0,0}, // 2nd motor
{_EMPTY_SLOT, 0,0,0}, // 3st motor // esp32s2 & esp32
{_EMPTY_SLOT, 0,0,0}, // 4nd motor // esp32 only
};
stepper_2pwm_motor_slots_t esp32_stepper_2pwm_motor_slots[4]={
{_EMPTY_SLOT, 0,0}, // 1st motor
{_EMPTY_SLOT, 0,0}, // 2nd motor
{_EMPTY_SLOT, 0,0}, // 3rd motor
{_EMPTY_SLOT, 0,0} // 4th motor - esp32s2 and esp32
};
stepper_4pwm_motor_slots_t esp32_stepper_4pwm_motor_slots[4]={
{_EMPTY_SLOT, 0,0,0,0}, // 1st motor
{_EMPTY_SLOT, 0,0,0,0}, // 2nd motor - esp32s2 and esp32
{_EMPTY_SLOT, 0,0,0,0}, // 3st motor - only esp32
{_EMPTY_SLOT, 0,0,0,0}, // 4st motor - only esp32
};
// configure High PWM frequency
void _setHighFrequency(const long freq, const int pin, const int channel){
ledcSetup(channel, freq, _PWM_RES_BIT );
ledcAttachPin(pin, channel);
}
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// check if enough channels available
if ( channel_index + 2 >= LEDC_CHANNELS ) return;
stepper_2pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
for(int slot_num = 0; slot_num < 4; slot_num++){
if(esp32_stepper_2pwm_motor_slots[slot_num].pinID == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_stepper_2pwm_motor_slots[slot_num].pinID = pinA;
esp32_stepper_2pwm_motor_slots[slot_num].ch1 = channel_index++;
esp32_stepper_2pwm_motor_slots[slot_num].ch2 = channel_index++;
m_slot = esp32_stepper_2pwm_motor_slots[slot_num];
break;
}
}
_setHighFrequency(pwm_frequency, pinA, m_slot.ch1);
_setHighFrequency(pwm_frequency, pinB, m_slot.ch2);
}
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// check if enough channels available
if ( channel_index + 3 >= LEDC_CHANNELS ) return;
bldc_3pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
for(int slot_num = 0; slot_num < 4; slot_num++){
if(esp32_bldc_3pwm_motor_slots[slot_num].pinID == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_bldc_3pwm_motor_slots[slot_num].pinID = pinA;
esp32_bldc_3pwm_motor_slots[slot_num].ch1 = channel_index++;
esp32_bldc_3pwm_motor_slots[slot_num].ch2 = channel_index++;
esp32_bldc_3pwm_motor_slots[slot_num].ch3 = channel_index++;
m_slot = esp32_bldc_3pwm_motor_slots[slot_num];
break;
}
}
_setHighFrequency(pwm_frequency, pinA, m_slot.ch1);
_setHighFrequency(pwm_frequency, pinB, m_slot.ch2);
_setHighFrequency(pwm_frequency, pinC, m_slot.ch3);
}
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// check if enough channels available
if ( channel_index + 4 >= LEDC_CHANNELS ) return;
stepper_4pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
for(int slot_num = 0; slot_num < 4; slot_num++){
if(esp32_stepper_4pwm_motor_slots[slot_num].pinID == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_stepper_4pwm_motor_slots[slot_num].pinID = pinA;
esp32_stepper_4pwm_motor_slots[slot_num].ch1 = channel_index++;
esp32_stepper_4pwm_motor_slots[slot_num].ch2 = channel_index++;
esp32_stepper_4pwm_motor_slots[slot_num].ch3 = channel_index++;
esp32_stepper_4pwm_motor_slots[slot_num].ch4 = channel_index++;
m_slot = esp32_stepper_4pwm_motor_slots[slot_num];
break;
}
}
_setHighFrequency(pwm_frequency, pinA, m_slot.ch1);
_setHighFrequency(pwm_frequency, pinB, m_slot.ch2);
_setHighFrequency(pwm_frequency, pinC, m_slot.ch3);
_setHighFrequency(pwm_frequency, pinD, m_slot.ch4);
}
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(esp32_stepper_2pwm_motor_slots[i].pinID == pinA){ // if motor slot found
ledcWrite(esp32_stepper_2pwm_motor_slots[i].ch1, _constrain(_PWM_RES*dc_a, 0, _PWM_RES));
ledcWrite(esp32_stepper_2pwm_motor_slots[i].ch2, _constrain(_PWM_RES*dc_b, 0, _PWM_RES));
break;
}
}
}
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(esp32_bldc_3pwm_motor_slots[i].pinID == pinA){ // if motor slot found
ledcWrite(esp32_bldc_3pwm_motor_slots[i].ch1, _constrain(_PWM_RES*dc_a, 0, _PWM_RES));
ledcWrite(esp32_bldc_3pwm_motor_slots[i].ch2, _constrain(_PWM_RES*dc_b, 0, _PWM_RES));
ledcWrite(esp32_bldc_3pwm_motor_slots[i].ch3, _constrain(_PWM_RES*dc_c, 0, _PWM_RES));
break;
}
}
}
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(esp32_stepper_4pwm_motor_slots[i].pinID == pin1A){ // if motor slot found
ledcWrite(esp32_stepper_4pwm_motor_slots[i].ch1, _constrain(_PWM_RES*dc_1a, 0, _PWM_RES));
ledcWrite(esp32_stepper_4pwm_motor_slots[i].ch2, _constrain(_PWM_RES*dc_1b, 0, _PWM_RES));
ledcWrite(esp32_stepper_4pwm_motor_slots[i].ch3, _constrain(_PWM_RES*dc_2a, 0, _PWM_RES));
ledcWrite(esp32_stepper_4pwm_motor_slots[i].ch4, _constrain(_PWM_RES*dc_2b, 0, _PWM_RES));
break;
}
}
}
#endif
@@ -0,0 +1,440 @@
#include "../hardware_api.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED)
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"
// empty motor slot
#define _EMPTY_SLOT -20
#define _TAKEN_SLOT -21
// ABI bus frequency - would be better to take it from somewhere
// but I did nto find a good exposed variable
#define _MCPWM_FREQ 160e6f
// preferred pwm resolution default
#define _PWM_RES_DEF 2048
// min resolution
#define _PWM_RES_MIN 1500
// max resolution
#define _PWM_RES_MAX 3000
// pwm frequency
#define _PWM_FREQUENCY 25000 // default
#define _PWM_FREQUENCY_MAX 50000 // mqx
// structure containing motor slot configuration
// this library supports up to 4 motors
typedef struct {
int pinA;
mcpwm_dev_t* mcpwm_num;
mcpwm_unit_t mcpwm_unit;
mcpwm_operator_t mcpwm_operator;
mcpwm_io_signals_t mcpwm_a;
mcpwm_io_signals_t mcpwm_b;
mcpwm_io_signals_t mcpwm_c;
} bldc_3pwm_motor_slots_t;
typedef struct {
int pin1A;
mcpwm_dev_t* mcpwm_num;
mcpwm_unit_t mcpwm_unit;
mcpwm_operator_t mcpwm_operator1;
mcpwm_operator_t mcpwm_operator2;
mcpwm_io_signals_t mcpwm_1a;
mcpwm_io_signals_t mcpwm_1b;
mcpwm_io_signals_t mcpwm_2a;
mcpwm_io_signals_t mcpwm_2b;
} stepper_4pwm_motor_slots_t;
typedef struct {
int pin1pwm;
mcpwm_dev_t* mcpwm_num;
mcpwm_unit_t mcpwm_unit;
mcpwm_operator_t mcpwm_operator;
mcpwm_io_signals_t mcpwm_a;
mcpwm_io_signals_t mcpwm_b;
} stepper_2pwm_motor_slots_t;
typedef struct {
int pinAH;
mcpwm_dev_t* mcpwm_num;
mcpwm_unit_t mcpwm_unit;
mcpwm_operator_t mcpwm_operator1;
mcpwm_operator_t mcpwm_operator2;
mcpwm_io_signals_t mcpwm_ah;
mcpwm_io_signals_t mcpwm_bh;
mcpwm_io_signals_t mcpwm_ch;
mcpwm_io_signals_t mcpwm_al;
mcpwm_io_signals_t mcpwm_bl;
mcpwm_io_signals_t mcpwm_cl;
} bldc_6pwm_motor_slots_t;
// define bldc motor slots array
bldc_3pwm_motor_slots_t esp32_bldc_3pwm_motor_slots[4] = {
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_A, MCPWM0A, MCPWM1A, MCPWM2A}, // 1st motor will be MCPWM0 channel A
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_B, MCPWM0B, MCPWM1B, MCPWM2B}, // 2nd motor will be MCPWM0 channel B
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_A, MCPWM0A, MCPWM1A, MCPWM2A}, // 3rd motor will be MCPWM1 channel A
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_B, MCPWM0B, MCPWM1B, MCPWM2B} // 4th motor will be MCPWM1 channel B
};
// define stepper motor slots array
bldc_6pwm_motor_slots_t esp32_bldc_6pwm_motor_slots[2] = {
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_A, MCPWM_OPR_B, MCPWM0A, MCPWM1A, MCPWM2A, MCPWM0B, MCPWM1B, MCPWM2B}, // 1st motor will be on MCPWM0
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_A, MCPWM_OPR_B, MCPWM0A, MCPWM1A, MCPWM2A, MCPWM0B, MCPWM1B, MCPWM2B}, // 1st motor will be on MCPWM0
};
// define 4pwm stepper motor slots array
stepper_4pwm_motor_slots_t esp32_stepper_4pwm_motor_slots[2] = {
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_A, MCPWM_OPR_B, MCPWM0A, MCPWM1A, MCPWM0B, MCPWM1B}, // 1st motor will be on MCPWM0
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_A, MCPWM_OPR_B, MCPWM0A, MCPWM1A, MCPWM0B, MCPWM1B}, // 1st motor will be on MCPWM1
};
// define 2pwm stepper motor slots array
stepper_2pwm_motor_slots_t esp32_stepper_2pwm_motor_slots[4] = {
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_A, MCPWM0A, MCPWM1A}, // 1st motor will be MCPWM0 channel A
{_EMPTY_SLOT, &MCPWM0, MCPWM_UNIT_0, MCPWM_OPR_B, MCPWM0B, MCPWM1B}, // 2nd motor will be MCPWM0 channel B
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_A, MCPWM0A, MCPWM1A}, // 3rd motor will be MCPWM1 channel A
{_EMPTY_SLOT, &MCPWM1, MCPWM_UNIT_1, MCPWM_OPR_B, MCPWM0B, MCPWM1B} // 4th motor will be MCPWM1 channel B
};
// configuring high frequency pwm timer
// a lot of help from this post from Paul Gauld
// https://hackaday.io/project/169905-esp-32-bldc-robot-actuator-controller
// a short tutorial for v2.0.1+
// https://kzhead.info/sun/q6uFktWgkYeMeZ8/esp32-arduino.html
//
void _configureTimerFrequency(long pwm_frequency, mcpwm_dev_t* mcpwm_num, mcpwm_unit_t mcpwm_unit, float dead_zone = NOT_SET){
mcpwm_config_t pwm_config;
pwm_config.counter_mode = MCPWM_UP_DOWN_COUNTER; // Up-down counter (triangle wave)
pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active HIGH
pwm_config.frequency = 2*pwm_frequency; // set the desired freq - just a placeholder for now https://github.com/simplefoc/Arduino-FOC/issues/76
mcpwm_init(mcpwm_unit, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A & PWM0B with above settings
mcpwm_init(mcpwm_unit, MCPWM_TIMER_1, &pwm_config); //Configure PWM1A & PWM1B with above settings
mcpwm_init(mcpwm_unit, MCPWM_TIMER_2, &pwm_config); //Configure PWM2A & PWM2B with above settings
if (_isset(dead_zone)){
// dead zone is configured
float dead_time = (float)(_MCPWM_FREQ / (pwm_frequency)) * dead_zone;
mcpwm_deadtime_enable(mcpwm_unit, MCPWM_TIMER_0, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, dead_time/2.0, dead_time/2.0);
mcpwm_deadtime_enable(mcpwm_unit, MCPWM_TIMER_1, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, dead_time/2.0, dead_time/2.0);
mcpwm_deadtime_enable(mcpwm_unit, MCPWM_TIMER_2, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, dead_time/2.0, dead_time/2.0);
}
_delay(100);
mcpwm_stop(mcpwm_unit, MCPWM_TIMER_0);
mcpwm_stop(mcpwm_unit, MCPWM_TIMER_1);
mcpwm_stop(mcpwm_unit, MCPWM_TIMER_2);
// manual configuration due to the lack of config flexibility in mcpwm_init()
mcpwm_num->clk_cfg.clk_prescale = 0;
// calculate prescaler and period
// step 1: calculate the prescaler using the default pwm resolution
// prescaler = bus_freq / (pwm_freq * default_pwm_res) - 1
int prescaler = ceil((double)_MCPWM_FREQ / (double)_PWM_RES_DEF / 2.0f / (double)pwm_frequency) - 1;
// constrain prescaler
prescaler = _constrain(prescaler, 0, 128);
// now calculate the real resolution timer period necessary (pwm resolution)
// pwm_res = bus_freq / (pwm_freq * (prescaler + 1))
int resolution_corrected = (double)_MCPWM_FREQ / 2.0f / (double)pwm_frequency / (double)(prescaler + 1);
// if pwm resolution too low lower the prescaler
if(resolution_corrected < _PWM_RES_MIN && prescaler > 0 )
resolution_corrected = (double)_MCPWM_FREQ / 2.0f / (double)pwm_frequency / (double)(--prescaler + 1);
resolution_corrected = _constrain(resolution_corrected, _PWM_RES_MIN, _PWM_RES_MAX);
// set prescaler
mcpwm_num->timer[0].timer_cfg0.timer_prescale = prescaler;
mcpwm_num->timer[1].timer_cfg0.timer_prescale = prescaler;
mcpwm_num->timer[2].timer_cfg0.timer_prescale = prescaler;
_delay(1);
//set period
mcpwm_num->timer[0].timer_cfg0.timer_period = resolution_corrected;
mcpwm_num->timer[1].timer_cfg0.timer_period = resolution_corrected;
mcpwm_num->timer[2].timer_cfg0.timer_period = resolution_corrected;
_delay(1);
mcpwm_num->timer[0].timer_cfg0.timer_period_upmethod = 0;
mcpwm_num->timer[1].timer_cfg0.timer_period_upmethod = 0;
mcpwm_num->timer[2].timer_cfg0.timer_period_upmethod = 0;
_delay(1);
// _delay(1);
//restart the timers
mcpwm_start(mcpwm_unit, MCPWM_TIMER_0);
mcpwm_start(mcpwm_unit, MCPWM_TIMER_1);
mcpwm_start(mcpwm_unit, MCPWM_TIMER_2);
_delay(1);
mcpwm_sync_config_t sync_conf = {
.sync_sig = MCPWM_SELECT_TIMER0_SYNC,
.timer_val = 0,
.count_direction = MCPWM_TIMER_DIRECTION_UP
};
mcpwm_sync_configure(mcpwm_unit, MCPWM_TIMER_0, &sync_conf);
mcpwm_sync_configure(mcpwm_unit, MCPWM_TIMER_1, &sync_conf);
mcpwm_sync_configure(mcpwm_unit, MCPWM_TIMER_2, &sync_conf);
// Enable sync event for all timers to be the TEZ of timer0
mcpwm_set_timer_sync_output(mcpwm_unit, MCPWM_TIMER_0, MCPWM_SWSYNC_SOURCE_TEZ);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
// supports Arudino/ATmega328, STM32 and ESP32
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25hz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 40kHz max
stepper_2pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 4; slot_num++){
if(esp32_stepper_2pwm_motor_slots[slot_num].pin1pwm == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_stepper_2pwm_motor_slots[slot_num].pin1pwm = pinA;
m_slot = esp32_stepper_2pwm_motor_slots[slot_num];
break;
}
}
// disable all the slots with the same MCPWM
// disable 3pwm bldc motor which would go in the same slot
esp32_bldc_3pwm_motor_slots[slot_num].pinA = _TAKEN_SLOT;
if( slot_num < 2 ){
// slot 0 of the 4pwm stepper
esp32_stepper_4pwm_motor_slots[0].pin1A = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[0].pinAH = _TAKEN_SLOT;
}else{
// slot 1 of the stepper
esp32_stepper_4pwm_motor_slots[1].pin1A = _TAKEN_SLOT;
// slot 1 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[1].pinAH = _TAKEN_SLOT;
}
// configure pins
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_a, pinA);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_b, pinB);
// configure the timer
_configureTimerFrequency(pwm_frequency, m_slot.mcpwm_num, m_slot.mcpwm_unit);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
// supports Arudino/ATmega328, STM32 and ESP32
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25hz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 40kHz max
bldc_3pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 4; slot_num++){
if(esp32_bldc_3pwm_motor_slots[slot_num].pinA == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_bldc_3pwm_motor_slots[slot_num].pinA = pinA;
m_slot = esp32_bldc_3pwm_motor_slots[slot_num];
break;
}
}
// disable all the slots with the same MCPWM
// disable 2pwm steppr motor which would go in the same slot
esp32_stepper_2pwm_motor_slots[slot_num].pin1pwm = _TAKEN_SLOT;
if( slot_num < 2 ){
// slot 0 of the 4pwm stepper
esp32_stepper_4pwm_motor_slots[0].pin1A = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[0].pinAH = _TAKEN_SLOT;
}else{
// slot 1 of the stepper
esp32_stepper_4pwm_motor_slots[1].pin1A = _TAKEN_SLOT;
// slot 1 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[1].pinAH = _TAKEN_SLOT;
}
// configure pins
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_a, pinA);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_b, pinB);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_c, pinC);
// configure the timer
_configureTimerFrequency(pwm_frequency, m_slot.mcpwm_num, m_slot.mcpwm_unit);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25hz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 40kHz max
stepper_4pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 2; slot_num++){
if(esp32_stepper_4pwm_motor_slots[slot_num].pin1A == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_stepper_4pwm_motor_slots[slot_num].pin1A = pinA;
m_slot = esp32_stepper_4pwm_motor_slots[slot_num];
break;
}
}
// disable all the slots with the same MCPWM
if( slot_num == 0 ){
// slots 0 and 1 of the 3pwm bldc
esp32_bldc_3pwm_motor_slots[0].pinA = _TAKEN_SLOT;
esp32_bldc_3pwm_motor_slots[1].pinA = _TAKEN_SLOT;
// slots 0 and 1 of the 2pwm stepper taken
esp32_stepper_2pwm_motor_slots[0].pin1pwm = _TAKEN_SLOT;
esp32_stepper_2pwm_motor_slots[1].pin1pwm = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[0].pinAH = _TAKEN_SLOT;
}else{
// slots 2 and 3 of the 3pwm bldc
esp32_bldc_3pwm_motor_slots[2].pinA = _TAKEN_SLOT;
esp32_bldc_3pwm_motor_slots[3].pinA = _TAKEN_SLOT;
// slots 2 and 3 of the 2pwm stepper taken
esp32_stepper_2pwm_motor_slots[2].pin1pwm = _TAKEN_SLOT;
esp32_stepper_2pwm_motor_slots[3].pin1pwm = _TAKEN_SLOT;
// slot 1 of the 6pwm bldc
esp32_bldc_6pwm_motor_slots[1].pinAH = _TAKEN_SLOT;
}
// configure pins
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_1a, pinA);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_1b, pinB);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_2a, pinC);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_2b, pinD);
// configure the timer
_configureTimerFrequency(pwm_frequency, m_slot.mcpwm_num, m_slot.mcpwm_unit);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
// ESP32 uses MCPWM
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(esp32_stepper_2pwm_motor_slots[i].pin1pwm == pinA){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,100]
mcpwm_set_duty(esp32_stepper_2pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, esp32_stepper_2pwm_motor_slots[i].mcpwm_operator, dc_a*100.0);
mcpwm_set_duty(esp32_stepper_2pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, esp32_stepper_2pwm_motor_slots[i].mcpwm_operator, dc_b*100.0);
break;
}
}
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
// ESP32 uses MCPWM
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(esp32_bldc_3pwm_motor_slots[i].pinA == pinA){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,100]
mcpwm_set_duty(esp32_bldc_3pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, esp32_bldc_3pwm_motor_slots[i].mcpwm_operator, dc_a*100.0);
mcpwm_set_duty(esp32_bldc_3pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, esp32_bldc_3pwm_motor_slots[i].mcpwm_operator, dc_b*100.0);
mcpwm_set_duty(esp32_bldc_3pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_2, esp32_bldc_3pwm_motor_slots[i].mcpwm_operator, dc_c*100.0);
break;
}
}
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
// ESP32 uses MCPWM
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// determine which motor slot is the motor connected to
for(int i = 0; i < 2; i++){
if(esp32_stepper_4pwm_motor_slots[i].pin1A == pin1A){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,100]
mcpwm_set_duty(esp32_stepper_4pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, esp32_stepper_4pwm_motor_slots[i].mcpwm_operator1, dc_1a*100.0);
mcpwm_set_duty(esp32_stepper_4pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, esp32_stepper_4pwm_motor_slots[i].mcpwm_operator1, dc_1b*100.0);
mcpwm_set_duty(esp32_stepper_4pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, esp32_stepper_4pwm_motor_slots[i].mcpwm_operator2, dc_2a*100.0);
mcpwm_set_duty(esp32_stepper_4pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, esp32_stepper_4pwm_motor_slots[i].mcpwm_operator2, dc_2b*100.0);
break;
}
}
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = 20000; // default frequency 20khz - centered pwm has twice lower frequency
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 40kHz max - centered pwm has twice lower frequency
bldc_6pwm_motor_slots_t m_slot = {};
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 2; slot_num++){
if(esp32_bldc_6pwm_motor_slots[slot_num].pinAH == _EMPTY_SLOT){ // put the new motor in the first empty slot
esp32_bldc_6pwm_motor_slots[slot_num].pinAH = pinA_h;
m_slot = esp32_bldc_6pwm_motor_slots[slot_num];
break;
}
}
// if no slots available
if(slot_num >= 2) return -1;
// disable all the slots with the same MCPWM
if( slot_num == 0 ){
// slots 0 and 1 of the 3pwm bldc
esp32_bldc_3pwm_motor_slots[0].pinA = _TAKEN_SLOT;
esp32_bldc_3pwm_motor_slots[1].pinA = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
esp32_stepper_4pwm_motor_slots[0].pin1A = _TAKEN_SLOT;
}else{
// slots 2 and 3 of the 3pwm bldc
esp32_bldc_3pwm_motor_slots[2].pinA = _TAKEN_SLOT;
esp32_bldc_3pwm_motor_slots[3].pinA = _TAKEN_SLOT;
// slot 1 of the 6pwm bldc
esp32_stepper_4pwm_motor_slots[1].pin1A = _TAKEN_SLOT;
}
// configure pins
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_ah, pinA_h);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_al, pinA_l);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_bh, pinB_h);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_bl, pinB_l);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_ch, pinC_h);
mcpwm_gpio_init(m_slot.mcpwm_unit, m_slot.mcpwm_cl, pinC_l);
// configure the timer
_configureTimerFrequency(pwm_frequency, m_slot.mcpwm_num, m_slot.mcpwm_unit, dead_zone);
// return
return 0;
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
// determine which motor slot is the motor connected to
for(int i = 0; i < 2; i++){
if(esp32_bldc_6pwm_motor_slots[i].pinAH == pinA_h){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,100.0]
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, MCPWM_OPR_A, dc_a*100.0);
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_0, MCPWM_OPR_B, dc_a*100.0);
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, MCPWM_OPR_A, dc_b*100.0);
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_1, MCPWM_OPR_B, dc_b*100.0);
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_2, MCPWM_OPR_A, dc_c*100.0);
mcpwm_set_duty(esp32_bldc_6pwm_motor_slots[i].mcpwm_unit, MCPWM_TIMER_2, MCPWM_OPR_B, dc_c*100.0);
break;
}
}
}
#endif
@@ -0,0 +1,77 @@
#include "../hardware_api.h"
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP8266)
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 50000 // 50khz
// configure High PWM frequency
void _setHighFrequency(const long freq, const int pin){
analogWrite(pin, 0);
analogWriteFreq(freq);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
_setHighFrequency(pwm_frequency, pinC);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
_setHighFrequency(pwm_frequency, pinC);
_setHighFrequency(pwm_frequency, pinD);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
analogWrite(pinC, 255.0f*dc_c);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0f*dc_1a);
analogWrite(pin1B, 255.0f*dc_1b);
analogWrite(pin2A, 255.0f*dc_2a);
analogWrite(pin2B, 255.0f*dc_2b);
}
#endif
@@ -0,0 +1,107 @@
#include "../hardware_api.h"
// if the mcu doen't have defiend analogWrite
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32)
__attribute__((weak)) void analogWrite(uint8_t pin, int value){ };
#endif
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
// in generic case dont do anything
__attribute__((weak)) void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
_UNUSED(pwm_frequency);
_UNUSED(pinA);
_UNUSED(pinB);
return;
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
// in generic case dont do anything
__attribute__((weak)) void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
_UNUSED(pwm_frequency);
_UNUSED(pinA);
_UNUSED(pinB);
_UNUSED(pinC);
return;
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
// in generic case dont do anything
__attribute__((weak)) void _configure4PWM(long pwm_frequency,const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
_UNUSED(pwm_frequency);
_UNUSED(pin1A);
_UNUSED(pin1B);
_UNUSED(pin2A);
_UNUSED(pin2B);
return;
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
__attribute__((weak)) int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
_UNUSED(pwm_frequency);
_UNUSED(dead_zone);
_UNUSED(pinA_h);
_UNUSED(pinB_h);
_UNUSED(pinC_h);
_UNUSED(pinA_l);
_UNUSED(pinB_l);
_UNUSED(pinC_l);
return -1;
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
__attribute__((weak)) void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
__attribute__((weak)) void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
analogWrite(pinC, 255.0f*dc_c);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
__attribute__((weak)) void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0f*dc_1a);
analogWrite(pin1B, 255.0f*dc_1b);
analogWrite(pin2A, 255.0f*dc_2a);
analogWrite(pin2B, 255.0f*dc_2b);
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
__attribute__((weak)) void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
_UNUSED(dc_a);
_UNUSED(dc_b);
_UNUSED(dc_c);
_UNUSED(dead_zone);
_UNUSED(pinA_h);
_UNUSED(pinB_h);
_UNUSED(pinC_h);
_UNUSED(pinA_l);
_UNUSED(pinB_l);
_UNUSED(pinC_l);
return;
}
@@ -0,0 +1,355 @@
#include "../hardware_api.h"
#if defined(NRF52_SERIES)
#define PWM_CLK (16000000)
#define PWM_FREQ (40000)
#define PWM_RESOLUTION (PWM_CLK/PWM_FREQ)
#define PWM_MAX_FREQ (62500)
#define DEAD_ZONE (250) // in ns
#define DEAD_TIME (DEAD_ZONE / (PWM_RESOLUTION * 0.25 * 62.5)) // 62.5ns resolution of PWM
#ifdef NRF_PWM3
#define PWM_COUNT 4
#else
#define PWM_COUNT 3
#endif
// empty motor slot
#define _EMPTY_SLOT (-0xAA)
#define _TAKEN_SLOT (-0x55)
int pwm_range;
float dead_time;
static NRF_PWM_Type* pwms[PWM_COUNT] = {
NRF_PWM0,
NRF_PWM1,
NRF_PWM2,
#ifdef NRF_PWM3
NRF_PWM3
#endif
};
typedef struct {
int pinA;
NRF_PWM_Type* mcpwm;
uint16_t mcpwm_channel_sequence[4];
} bldc_3pwm_motor_slots_t;
typedef struct {
int pin1A;
NRF_PWM_Type* mcpwm;
uint16_t mcpwm_channel_sequence[4];
} stepper_motor_slots_t;
typedef struct {
int pinAH;
NRF_PWM_Type* mcpwm1;
NRF_PWM_Type* mcpwm2;
uint16_t mcpwm_channel_sequence[8];
} bldc_6pwm_motor_slots_t;
// define bldc motor slots array
bldc_3pwm_motor_slots_t nrf52_bldc_3pwm_motor_slots[4] = {
{_EMPTY_SLOT, pwms[0], {0,0,0,0}},// 1st motor will be PWM0
{_EMPTY_SLOT, pwms[1], {0,0,0,0}},// 2nd motor will be PWM1
{_EMPTY_SLOT, pwms[2], {0,0,0,0}},// 3rd motor will be PWM2
{_EMPTY_SLOT, pwms[3], {0,0,0,0}} // 4th motor will be PWM3
};
// define stepper motor slots array
stepper_motor_slots_t nrf52_stepper_motor_slots[4] = {
{_EMPTY_SLOT, pwms[0], {0,0,0,0}},// 1st motor will be on PWM0
{_EMPTY_SLOT, pwms[1], {0,0,0,0}},// 1st motor will be on PWM1
{_EMPTY_SLOT, pwms[2], {0,0,0,0}},// 1st motor will be on PWM2
{_EMPTY_SLOT, pwms[3], {0,0,0,0}} // 1st motor will be on PWM3
};
// define BLDC motor slots array
bldc_6pwm_motor_slots_t nrf52_bldc_6pwm_motor_slots[2] = {
{_EMPTY_SLOT, pwms[0], pwms[1], {0,0,0,0,0,0,0,0}},// 1st motor will be on PWM0 & PWM1
{_EMPTY_SLOT, pwms[2], pwms[3], {0,0,0,0,0,0,0,0}} // 2nd motor will be on PWM1 & PWM2
};
// configuring high frequency pwm timer
void _configureHwPwm(NRF_PWM_Type* mcpwm1, NRF_PWM_Type* mcpwm2){
mcpwm1->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
mcpwm1->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
mcpwm1->MODE = (PWM_MODE_UPDOWN_UpAndDown << PWM_MODE_UPDOWN_Pos);
mcpwm1->COUNTERTOP = pwm_range; //pwm freq.
mcpwm1->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
mcpwm1->DECODER = ((uint32_t)PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | ((uint32_t)PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
mcpwm1->SEQ[0].REFRESH = 0;
mcpwm1->SEQ[0].ENDDELAY = 0;
if(mcpwm1 != mcpwm2){
mcpwm2->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
mcpwm2->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
mcpwm2->MODE = (PWM_MODE_UPDOWN_UpAndDown << PWM_MODE_UPDOWN_Pos);
mcpwm2->COUNTERTOP = pwm_range; //pwm freq.
mcpwm2->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
mcpwm2->DECODER = ((uint32_t)PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | ((uint32_t)PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
mcpwm2->SEQ[0].REFRESH = 0;
mcpwm2->SEQ[0].ENDDELAY = 0;
}else{
mcpwm1->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
}
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if( !pwm_frequency || pwm_frequency == NOT_SET) pwm_frequency = PWM_FREQ; // default frequency 20khz for a resolution of 800
else pwm_frequency = _constrain(pwm_frequency, 0, PWM_MAX_FREQ); // constrain to 62.5kHz max for a resolution of 256
pwm_range = (PWM_CLK / pwm_frequency);
int pA = digitalPinToPinName(pinA); //g_ADigitalPinMap[pinA];
int pB = digitalPinToPinName(pinB); //g_ADigitalPinMap[pinB];
int pC = digitalPinToPinName(pinC); //g_ADigitalPinMap[pinC];
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 4; slot_num++){
if(nrf52_bldc_3pwm_motor_slots[slot_num].pinA == _EMPTY_SLOT){ // put the new motor in the first empty slot
nrf52_bldc_3pwm_motor_slots[slot_num].pinA = pinA;
break;
}
}
// disable all the slots with the same MCPWM
if(slot_num < 2){
// slot 0 of the stepper
nrf52_stepper_motor_slots[slot_num].pin1A = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
nrf52_bldc_6pwm_motor_slots[0].pinAH = _TAKEN_SLOT;
//NRF_PPI->CHEN &= ~1UL;
}else{
// slot 1 of the stepper
nrf52_stepper_motor_slots[slot_num].pin1A = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
nrf52_bldc_6pwm_motor_slots[1].pinAH = _TAKEN_SLOT;
//NRF_PPI->CHEN &= ~2UL;
}
// configure pwm outputs
nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm->PSEL.OUT[0] = pA;
nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm->PSEL.OUT[1] = pB;
nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm->PSEL.OUT[2] = pC;
nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm->SEQ[0].PTR = (uint32_t)&nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm_channel_sequence[0];
nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm->SEQ[0].CNT = 4;
// configure the pwm
_configureHwPwm(nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm, nrf52_bldc_3pwm_motor_slots[slot_num].mcpwm);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if( !pwm_frequency || pwm_frequency == NOT_SET) pwm_frequency = PWM_FREQ; // default frequency 20khz for a resolution of 800
else pwm_frequency = _constrain(pwm_frequency, 0, PWM_MAX_FREQ); // constrain to 62.5kHz max for a resolution of 256
pwm_range = (PWM_CLK / pwm_frequency);
int pA = digitalPinToPinName(pinA); //g_ADigitalPinMap[pinA];
int pB = digitalPinToPinName(pinB); //g_ADigitalPinMap[pinB];
int pC = digitalPinToPinName(pinC); //g_ADigitalPinMap[pinC];
int pD = digitalPinToPinName(pinD); //g_ADigitalPinMap[pinD];
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 4; slot_num++){
if(nrf52_stepper_motor_slots[slot_num].pin1A == _EMPTY_SLOT){ // put the new motor in the first empty slot
nrf52_stepper_motor_slots[slot_num].pin1A = pinA;
break;
}
}
// disable all the slots with the same MCPWM
if( slot_num < 2 ){
// slots 0 and 1 of the 3pwm bldc
nrf52_bldc_3pwm_motor_slots[slot_num].pinA = _TAKEN_SLOT;
// slot 0 of the 6pwm bldc
nrf52_bldc_6pwm_motor_slots[0].pinAH = _TAKEN_SLOT;
//NRF_PPI->CHEN &= ~1UL;
}else{
// slots 2 and 3 of the 3pwm bldc
nrf52_bldc_3pwm_motor_slots[slot_num].pinA = _TAKEN_SLOT;
// slot 1 of the 6pwm bldc
nrf52_bldc_6pwm_motor_slots[1].pinAH = _TAKEN_SLOT;
//NRF_PPI->CHEN &= ~2UL;
}
// configure pwm outputs
nrf52_stepper_motor_slots[slot_num].mcpwm->PSEL.OUT[0] = pA;
nrf52_stepper_motor_slots[slot_num].mcpwm->PSEL.OUT[1] = pB;
nrf52_stepper_motor_slots[slot_num].mcpwm->PSEL.OUT[2] = pC;
nrf52_stepper_motor_slots[slot_num].mcpwm->PSEL.OUT[3] = pD;
nrf52_stepper_motor_slots[slot_num].mcpwm->SEQ[0].PTR = (uint32_t)&nrf52_stepper_motor_slots[slot_num].mcpwm_channel_sequence[0];
nrf52_stepper_motor_slots[slot_num].mcpwm->SEQ[0].CNT = 4;
// configure the pwm
_configureHwPwm(nrf52_stepper_motor_slots[slot_num].mcpwm, nrf52_stepper_motor_slots[slot_num].mcpwm);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(nrf52_bldc_3pwm_motor_slots[i].pinA == pinA){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,range]
nrf52_bldc_3pwm_motor_slots[i].mcpwm_channel_sequence[0] = (int)(dc_a * pwm_range) | 0x8000;
nrf52_bldc_3pwm_motor_slots[i].mcpwm_channel_sequence[1] = (int)(dc_b * pwm_range) | 0x8000;
nrf52_bldc_3pwm_motor_slots[i].mcpwm_channel_sequence[2] = (int)(dc_c * pwm_range) | 0x8000;
nrf52_bldc_3pwm_motor_slots[i].mcpwm->TASKS_SEQSTART[0] = 1;
break;
}
}
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A){
// determine which motor slot is the motor connected to
for(int i = 0; i < 4; i++){
if(nrf52_stepper_motor_slots[i].pin1A == pin1A){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,range]
nrf52_stepper_motor_slots[i].mcpwm_channel_sequence[0] = (int)(dc_1a * pwm_range) | 0x8000;
nrf52_stepper_motor_slots[i].mcpwm_channel_sequence[1] = (int)(dc_1b * pwm_range) | 0x8000;
nrf52_stepper_motor_slots[i].mcpwm_channel_sequence[2] = (int)(dc_2a * pwm_range) | 0x8000;
nrf52_stepper_motor_slots[i].mcpwm_channel_sequence[3] = (int)(dc_2b * pwm_range) | 0x8000;
nrf52_stepper_motor_slots[i].mcpwm->TASKS_SEQSTART[0] = 1;
break;
}
}
}
/* Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
*/
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
if( !pwm_frequency || pwm_frequency == NOT_SET) pwm_frequency = PWM_FREQ; // default frequency 20khz - centered pwm has twice lower frequency for a resolution of 400
else pwm_frequency = _constrain(pwm_frequency*2, 0, PWM_MAX_FREQ); // constrain to 62.5kHz max => 31.25kHz for a resolution of 256
pwm_range = (PWM_CLK / pwm_frequency);
pwm_range /= 2; // scale the frequency (centered PWM)
if (dead_zone != NOT_SET){
dead_time = dead_zone/2;
}else{
dead_time = DEAD_TIME/2;
}
int pA_l = digitalPinToPinName(pinA_l); //g_ADigitalPinMap[pinA_l];
int pA_h = digitalPinToPinName(pinA_h); //g_ADigitalPinMap[pinA_h];
int pB_l = digitalPinToPinName(pinB_l); //g_ADigitalPinMap[pinB_l];
int pB_h = digitalPinToPinName(pinB_h); //g_ADigitalPinMap[pinB_h];
int pC_l = digitalPinToPinName(pinC_l); //g_ADigitalPinMap[pinC_l];
int pC_h = digitalPinToPinName(pinC_h); //g_ADigitalPinMap[pinC_h];
// determine which motor are we connecting
// and set the appropriate configuration parameters
int slot_num;
for(slot_num = 0; slot_num < 2; slot_num++){
if(nrf52_bldc_6pwm_motor_slots[slot_num].pinAH == _EMPTY_SLOT){ // put the new motor in the first empty slot
nrf52_bldc_6pwm_motor_slots[slot_num].pinAH = pinA_h;
break;
}
}
// if no slots available
if(slot_num >= 2) return -1;
// disable all the slots with the same MCPWM
if( slot_num == 0 ){
// slots 0 and 1 of the 3pwm bldc
nrf52_bldc_3pwm_motor_slots[0].pinA = _TAKEN_SLOT;
nrf52_bldc_3pwm_motor_slots[1].pinA = _TAKEN_SLOT;
// slot 0 and 1 of the stepper
nrf52_stepper_motor_slots[0].pin1A = _TAKEN_SLOT;
nrf52_stepper_motor_slots[1].pin1A = _TAKEN_SLOT;
}else{
// slots 2 and 3 of the 3pwm bldc
nrf52_bldc_3pwm_motor_slots[2].pinA = _TAKEN_SLOT;
nrf52_bldc_3pwm_motor_slots[3].pinA = _TAKEN_SLOT;
// slot 1 of the stepper
nrf52_stepper_motor_slots[2].pin1A = _TAKEN_SLOT;
nrf52_stepper_motor_slots[3].pin1A = _TAKEN_SLOT;
}
// Configure pwm outputs
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->PSEL.OUT[0] = pA_h;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->PSEL.OUT[1] = pA_l;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->PSEL.OUT[2] = pB_h;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->PSEL.OUT[3] = pB_l;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->SEQ[0].PTR = (uint32_t)&nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm_channel_sequence[0];
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->SEQ[0].CNT = 4;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2->PSEL.OUT[0] = pC_h;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2->PSEL.OUT[1] = pC_l;
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2->SEQ[0].PTR = (uint32_t)&nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm_channel_sequence[4];
nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2->SEQ[0].CNT = 4;
// Initializing the PPI peripheral for sync the pwm slots
NRF_PPI->CH[slot_num].EEP = (uint32_t)&NRF_EGU0->EVENTS_TRIGGERED[0];
NRF_PPI->CH[slot_num].TEP = (uint32_t)&nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1->TASKS_SEQSTART[0];
NRF_PPI->FORK[slot_num].TEP = (uint32_t)&nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2->TASKS_SEQSTART[0];
NRF_PPI->CHEN = 1UL << slot_num;
// configure the pwm type
_configureHwPwm(nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm1, nrf52_bldc_6pwm_motor_slots[slot_num].mcpwm2);
// return
return 0;
}
/* Function setting the duty cycle to the pwm pin
// - BLDC driver - 6PWM setting
// - hardware specific
*/
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, const int pinA_h, const int, const int, const int, const int, const int){
for(int i = 0; i < 2; i++){
if(nrf52_bldc_6pwm_motor_slots[i].pinAH == pinA_h){ // if motor slot found
// se the PWM on the slot timers
// transform duty cycle from [0,1] to [0,range]
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[0] = (int)(_constrain(dc_a-dead_time,0,1)*pwm_range) | 0x8000;
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[1] = (int)(_constrain(dc_a+dead_time,0,1)*pwm_range);
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[2] = (int)(_constrain(dc_b-dead_time,0,1)*pwm_range) | 0x8000;
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[3] = (int)(_constrain(dc_b+dead_time,0,1)*pwm_range);
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[4] = (int)(_constrain(dc_c-dead_time,0,1)*pwm_range) | 0x8000;
nrf52_bldc_6pwm_motor_slots[i].mcpwm_channel_sequence[5] = (int)(_constrain(dc_c+dead_time,0,1)*pwm_range);
NRF_EGU0->TASKS_TRIGGER[0] = 1;
break;
}
}
}
#endif
@@ -0,0 +1,539 @@
#include "../hardware_api.h"
#if defined(TARGET_PORTENTA_H7)
#include "pwmout_api.h"
#include "pinDefinitions.h"
#include "platform/mbed_critical.h"
#include "platform/mbed_power_mgmt.h"
#include "platform/mbed_assert.h"
#include "PeripheralPins.h"
#include "pwmout_device.h"
// default pwm parameters
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 50000 // 50khz
// // 6pwm parameters
// #define _HARDWARE_6PWM 1
// #define _SOFTWARE_6PWM 0
// #define _ERROR_6PWM -1
typedef struct{
int id ;
pwmout_t pins[6];
} portenta_h7_mcu_enty_s;
portenta_h7_mcu_enty_s motor_slot[10];
int slot_index = 0;
/* Convert STM32 Cube HAL channel to LL channel */
uint32_t _TIM_ChannelConvert_HAL2LL(uint32_t channel, pwmout_t *obj)
{
#if !defined(PWMOUT_INVERTED_NOT_SUPPORTED)
if (obj->inverted) {
switch (channel) {
case TIM_CHANNEL_1 :
return LL_TIM_CHANNEL_CH1N;
case TIM_CHANNEL_2 :
return LL_TIM_CHANNEL_CH2N;
case TIM_CHANNEL_3 :
return LL_TIM_CHANNEL_CH3N;
#if defined(LL_TIM_CHANNEL_CH4N)
case TIM_CHANNEL_4 :
return LL_TIM_CHANNEL_CH4N;
#endif
default : /* Optional */
return 0;
}
} else
#endif
{
switch (channel) {
case TIM_CHANNEL_1 :
return LL_TIM_CHANNEL_CH1;
case TIM_CHANNEL_2 :
return LL_TIM_CHANNEL_CH2;
case TIM_CHANNEL_3 :
return LL_TIM_CHANNEL_CH3;
case TIM_CHANNEL_4 :
return LL_TIM_CHANNEL_CH4;
default : /* Optional */
return 0;
}
}
}
// int _pwm_init(pwmout_t *obj, uint32_t pin, long frequency){
// return _pwm_init(obj, digitalPinToPinName(pin), frequency);
// }
int _pwm_init(pwmout_t *obj, uint32_t pin, long frequency){
int peripheral = (int)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
int function = (int)pinmap_find_function(digitalPinToPinName(pin), PinMap_PWM);
const PinMap static_pinmap = {digitalPinToPinName(pin), peripheral, function};
pwmout_init_direct(obj, &static_pinmap);
TIM_HandleTypeDef TimHandle;
TimHandle.Instance = (TIM_TypeDef *)(obj->pwm);
RCC_ClkInitTypeDef RCC_ClkInitStruct;
uint32_t PclkFreq = 0;
uint32_t APBxCLKDivider = RCC_HCLK_DIV1;
uint8_t i = 0;
__HAL_TIM_DISABLE(&TimHandle);
// Get clock configuration
// Note: PclkFreq contains here the Latency (not used after)
HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &PclkFreq);
/* Parse the pwm / apb mapping table to find the right entry */
while (pwm_apb_map_table[i].pwm != obj->pwm) i++;
// sanity check
if (pwm_apb_map_table[i].pwm == 0) return -1;
if (pwm_apb_map_table[i].pwmoutApb == PWMOUT_ON_APB1) {
PclkFreq = HAL_RCC_GetPCLK1Freq();
APBxCLKDivider = RCC_ClkInitStruct.APB1CLKDivider;
} else {
#if !defined(PWMOUT_APB2_NOT_SUPPORTED)
PclkFreq = HAL_RCC_GetPCLK2Freq();
APBxCLKDivider = RCC_ClkInitStruct.APB2CLKDivider;
#endif
}
long period_us = 500000.0/((float)frequency);
/* By default use, 1us as SW pre-scaler */
obj->prescaler = 1;
// TIMxCLK = PCLKx when the APB prescaler = 1 else TIMxCLK = 2 * PCLKx
if (APBxCLKDivider == RCC_HCLK_DIV1) {
TimHandle.Init.Prescaler = (((PclkFreq) / 1000000)) - 1; // 1 us tick
} else {
TimHandle.Init.Prescaler = (((PclkFreq * 2) / 1000000)) - 1; // 1 us tick
}
TimHandle.Init.Period = (period_us - 1);
/* In case period or pre-scalers are out of range, loop-in to get valid values */
while ((TimHandle.Init.Period > 0xFFFF) || (TimHandle.Init.Prescaler > 0xFFFF)) {
obj->prescaler = obj->prescaler * 2;
if (APBxCLKDivider == RCC_HCLK_DIV1) {
TimHandle.Init.Prescaler = (((PclkFreq) / 1000000) * obj->prescaler) - 1;
} else {
TimHandle.Init.Prescaler = (((PclkFreq * 2) / 1000000) * obj->prescaler) - 1;
}
TimHandle.Init.Period = (period_us - 1) / obj->prescaler;
/* Period decreases and prescaler increases over loops, so check for
* possible out of range cases */
if ((TimHandle.Init.Period < 0xFFFF) && (TimHandle.Init.Prescaler > 0xFFFF)) {
break;
}
}
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3; // center aligned
if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK) {
return -1;
}
TIM_OC_InitTypeDef sConfig;
// Configure channels
sConfig.OCMode = TIM_OCMODE_PWM1;
sConfig.Pulse = obj->pulse / obj->prescaler;
sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfig.OCFastMode = TIM_OCFAST_DISABLE;
#if defined(TIM_OCIDLESTATE_RESET)
sConfig.OCIdleState = TIM_OCIDLESTATE_RESET;
#endif
#if defined(TIM_OCNIDLESTATE_RESET)
sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
#endif
int channel = 0;
switch (obj->channel) {
case 1:
channel = TIM_CHANNEL_1;
break;
case 2:
channel = TIM_CHANNEL_2;
break;
case 3:
channel = TIM_CHANNEL_3;
break;
case 4:
channel = TIM_CHANNEL_4;
break;
default:
return -1;
}
if (LL_TIM_CC_IsEnabledChannel(TimHandle.Instance, _TIM_ChannelConvert_HAL2LL(channel, obj)) == 0) {
// If channel is not enabled, proceed to channel configuration
if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, channel) != HAL_OK) {
return -1;
}
}
// Save for future use
obj->period = period_us;
#if !defined(PWMOUT_INVERTED_NOT_SUPPORTED)
if (obj->inverted) {
HAL_TIMEx_PWMN_Start(&TimHandle, channel);
} else
#endif
{
HAL_TIM_PWM_Start(&TimHandle, channel);
}
return 0;
}
// setting pwm to hardware pin - instead analogWrite()
void _pwm_write(pwmout_t *obj, float value){
TIM_HandleTypeDef TimHandle;
int channel = 0;
TimHandle.Instance = (TIM_TypeDef *)(obj->pwm);
if (value < (float)0.0) {
value = 0.0;
} else if (value > (float)1.0) {
value = 1.0;
}
obj->pulse = (uint32_t)((float)obj->period * value + 0.5);
switch (obj->channel) {
case 1:
channel = TIM_CHANNEL_1;
break;
case 2:
channel = TIM_CHANNEL_2;
break;
case 3:
channel = TIM_CHANNEL_3;
break;
case 4:
channel = TIM_CHANNEL_4;
break;
default:
return;
}
// If channel already enabled, only update compare value to avoid glitch
__HAL_TIM_SET_COMPARE(&TimHandle, channel, obj->pulse / obj->prescaler);
}
// init low side pin
// HardwareTimer* _initPinPWMLow(uint32_t PWM_freq, int ulPin)
// {
// PinName pin = digitalPinToPinName(ulPin);
// TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM);
// uint32_t index = get_timer_index(Instance);
// if (HardwareTimer_Handle[index] == NULL) {
// HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM));
// HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
// HAL_TIM_Base_Init(&(HardwareTimer_Handle[index]->handle));
// TIM_OC_InitTypeDef sConfigOC = TIM_OC_InitTypeDef();
// sConfigOC.OCMode = TIM_OCMODE_PWM2;
// sConfigOC.Pulse = 100;
// sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
// sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
// sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
// sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
// HAL_TIM_PWM_ConfigChannel(&(HardwareTimer_Handle[index]->handle), &sConfigOC, channel);
// }
// HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
// uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
// HT->setMode(channel, TIMER_OUTPUT_COMPARE_PWM2, pin);
// HT->setOverflow(PWM_freq, HERTZ_FORMAT);
// HT->pause();
// HT->refresh();
// return HT;
// }
// align the timers to end the init
void _alignPWMTimers(pwmout_t *t1, pwmout_t *t2){
TIM_HandleTypeDef TimHandle1, TimHandle2;
TimHandle1.Instance = (TIM_TypeDef *)(t1->pwm);
TimHandle2.Instance = (TIM_TypeDef *)(t2->pwm);
__HAL_TIM_DISABLE(&TimHandle1);
__HAL_TIM_DISABLE(&TimHandle2);
__HAL_TIM_ENABLE(&TimHandle1);
__HAL_TIM_ENABLE(&TimHandle2);
}
// align the timers to end the init
void _alignPWMTimers(pwmout_t *t1, pwmout_t *t2, pwmout_t *t3){
TIM_HandleTypeDef TimHandle1, TimHandle2, TimHandle3;
TimHandle1.Instance = (TIM_TypeDef *)(t1->pwm);
TimHandle2.Instance = (TIM_TypeDef *)(t2->pwm);
TimHandle3.Instance = (TIM_TypeDef *)(t3->pwm);
__HAL_TIM_DISABLE(&TimHandle1);
__HAL_TIM_DISABLE(&TimHandle2);
__HAL_TIM_DISABLE(&TimHandle3);
__HAL_TIM_ENABLE(&TimHandle1);
__HAL_TIM_ENABLE(&TimHandle2);
__HAL_TIM_ENABLE(&TimHandle3);
}
// align the timers to end the init
void _alignPWMTimers(pwmout_t *t1, pwmout_t *t2, pwmout_t *t3, pwmout_t *t4){
TIM_HandleTypeDef TimHandle1, TimHandle2, TimHandle3, TimHandle4;
TimHandle1.Instance = (TIM_TypeDef *)(t1->pwm);
TimHandle2.Instance = (TIM_TypeDef *)(t2->pwm);
TimHandle3.Instance = (TIM_TypeDef *)(t3->pwm);
TimHandle4.Instance = (TIM_TypeDef *)(t4->pwm);
__HAL_TIM_DISABLE(&TimHandle1);
__HAL_TIM_DISABLE(&TimHandle2);
__HAL_TIM_DISABLE(&TimHandle3);
__HAL_TIM_DISABLE(&TimHandle4);
__HAL_TIM_ENABLE(&TimHandle1);
__HAL_TIM_ENABLE(&TimHandle2);
__HAL_TIM_ENABLE(&TimHandle3);
__HAL_TIM_ENABLE(&TimHandle4);
}
// // configure hardware 6pwm interface only one timer with inverted channels
// HardwareTimer* _initHardware6PWMInterface(uint32_t PWM_freq, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l)
// {
// PinName uhPinName = digitalPinToPinName(pinA_h);
// PinName ulPinName = digitalPinToPinName(pinA_l);
// PinName vhPinName = digitalPinToPinName(pinB_h);
// PinName vlPinName = digitalPinToPinName(pinB_l);
// PinName whPinName = digitalPinToPinName(pinC_h);
// PinName wlPinName = digitalPinToPinName(pinC_l);
// TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(uhPinName, PinMap_PWM);
// uint32_t index = get_timer_index(Instance);
// if (HardwareTimer_Handle[index] == NULL) {
// HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(uhPinName, PinMap_PWM));
// HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
// HAL_TIM_Base_Init(&(HardwareTimer_Handle[index]->handle));
// ((HardwareTimer *)HardwareTimer_Handle[index]->__this)->setOverflow(PWM_freq, HERTZ_FORMAT);
// }
// HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(uhPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, uhPinName);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(ulPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, ulPinName);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(vhPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, vhPinName);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(vlPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, vlPinName);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(whPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, whPinName);
// HT->setMode(STM_PIN_CHANNEL(pinmap_function(wlPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, wlPinName);
// // dead time is set in nanoseconds
// uint32_t dead_time_ns = (float)(1e9f/PWM_freq)*dead_zone;
// uint32_t dead_time = __LL_TIM_CALC_DEADTIME(SystemCoreClock, LL_TIM_GetClockDivision(HT->getHandle()->Instance), dead_time_ns);
// LL_TIM_OC_SetDeadTime(HT->getHandle()->Instance, dead_time); // deadtime is non linear!
// LL_TIM_CC_EnableChannel(HT->getHandle()->Instance, LL_TIM_CHANNEL_CH1 | LL_TIM_CHANNEL_CH1N | LL_TIM_CHANNEL_CH2 | LL_TIM_CHANNEL_CH2N | LL_TIM_CHANNEL_CH3 | LL_TIM_CHANNEL_CH3N);
// HT->pause();
// HT->refresh();
// HT->resume();
// return HT;
// }
// // returns 0 if each pair of pwm channels has same channel
// // returns 1 all the channels belong to the same timer - hardware inverted channels
// // returns -1 if neither - avoid configuring - error!!!
// int _interfaceType(const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
// PinName nameAH = digitalPinToPinName(pinA_h);
// PinName nameAL = digitalPinToPinName(pinA_l);
// PinName nameBH = digitalPinToPinName(pinB_h);
// PinName nameBL = digitalPinToPinName(pinB_l);
// PinName nameCH = digitalPinToPinName(pinC_h);
// PinName nameCL = digitalPinToPinName(pinC_l);
// int tim1 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameAH, PinMap_PWM));
// int tim2 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameAL, PinMap_PWM));
// int tim3 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameBH, PinMap_PWM));
// int tim4 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameBL, PinMap_PWM));
// int tim5 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameCH, PinMap_PWM));
// int tim6 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameCL, PinMap_PWM));
// if(tim1 == tim2 && tim2==tim3 && tim3==tim4 && tim4==tim5 && tim5==tim6)
// return _HARDWARE_6PWM; // hardware 6pwm interface - only on timer
// else if(tim1 == tim2 && tim3==tim4 && tim5==tim6)
// return _SOFTWARE_6PWM; // software 6pwm interface - each pair of high-low side same timer
// else
// return _ERROR_6PWM; // might be error avoid configuration
// }
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
motor_slot[slot_index].id = pinA;
core_util_critical_section_enter();
_pwm_init(&(motor_slot[slot_index].pins[0]), pinA, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[1]), pinB, (long)pwm_frequency);
// allign the timers
_alignPWMTimers(&(motor_slot[slot_index].pins[0]), &(motor_slot[slot_index].pins[1]));
core_util_critical_section_exit();
slot_index++;
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
motor_slot[slot_index].id = pinA;
core_util_critical_section_enter();
_pwm_init(&(motor_slot[slot_index].pins[0]), pinA, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[1]), pinB, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[2]), pinC, (long)pwm_frequency);
// allign the timers
_alignPWMTimers(&(motor_slot[slot_index].pins[0]), &(motor_slot[slot_index].pins[1]), &(motor_slot[slot_index].pins[2]));
core_util_critical_section_exit();
slot_index++;
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
motor_slot[slot_index].id = pinA;
core_util_critical_section_enter();
_pwm_init(&(motor_slot[slot_index].pins[0]), pinA, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[1]), pinB, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[2]), pinC, (long)pwm_frequency);
_pwm_init(&(motor_slot[slot_index].pins[3]), pinD, (long)pwm_frequency);
// allign the timers
_alignPWMTimers(&(motor_slot[slot_index].pins[0]), &(motor_slot[slot_index].pins[1]), &(motor_slot[slot_index].pins[2]), &(motor_slot[slot_index].pins[3]));
core_util_critical_section_exit();
slot_index++;
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
//- hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
for(int i=0; i<slot_index; i++){
if(motor_slot[i].id == pinA){
core_util_critical_section_enter();
_pwm_write(&(motor_slot[i].pins[0]), (float)dc_a);
_pwm_write(&(motor_slot[i].pins[1]), (float)dc_b);
core_util_critical_section_exit();
}
}
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
//- hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
for(int i=0; i<slot_index; i++){
if(motor_slot[i].id == pinA){
core_util_critical_section_enter();
_pwm_write(&(motor_slot[i].pins[0]), (float)dc_a);
_pwm_write(&(motor_slot[i].pins[1]), (float)dc_b);
_pwm_write(&(motor_slot[i].pins[2]), (float)dc_c);
core_util_critical_section_exit();
}
}
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
//- hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
for(int i=0; i<slot_index; i++){
if(motor_slot[i].id == pin1A){
core_util_critical_section_enter();
_pwm_write(&(motor_slot[i].pins[0]), (float)dc_1a);
_pwm_write(&(motor_slot[i].pins[1]), (float)dc_1b);
_pwm_write(&(motor_slot[i].pins[2]), (float)dc_2a);
_pwm_write(&(motor_slot[i].pins[3]), (float)dc_2b);
core_util_critical_section_exit();
}
}
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
// if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
// else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to |%0kHz max
// // center-aligned frequency is uses two periods
// pwm_frequency *=2;
// // find configuration
// int config = _interfaceType(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
// // configure accordingly
// switch(config){
// case _ERROR_6PWM:
// return -1; // fail
// break;
// case _HARDWARE_6PWM:
// _initHardware6PWMInterface(pwm_frequency, dead_zone, pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
// break;
// case _SOFTWARE_6PWM:
// HardwareTimer* HT1 = _initPinPWMHigh(pwm_frequency, pinA_h);
// _initPinPWMLow(pwm_frequency, pinA_l);
// HardwareTimer* HT2 = _initPinPWMHigh(pwm_frequency,pinB_h);
// _initPinPWMLow(pwm_frequency, pinB_l);
// HardwareTimer* HT3 = _initPinPWMHigh(pwm_frequency,pinC_h);
// _initPinPWMLow(pwm_frequency, pinC_l);
// _alignPWMTimers(HT1, HT2, HT3);
// break;
// }
return -1; // success
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
// // find configuration
// int config = _interfaceType(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
// // set pwm accordingly
// switch(config){
// case _HARDWARE_6PWM:
// _setPwm(pinA_h, _PWM_RANGE*dc_a, _PWM_RESOLUTION);
// _setPwm(pinB_h, _PWM_RANGE*dc_b, _PWM_RESOLUTION);
// _setPwm(pinC_h, _PWM_RANGE*dc_c, _PWM_RESOLUTION);
// break;
// case _SOFTWARE_6PWM:
// _setPwm(pinA_l, _constrain(dc_a + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// _setPwm(pinA_h, _constrain(dc_a - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// _setPwm(pinB_l, _constrain(dc_b + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// _setPwm(pinB_h, _constrain(dc_b - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// _setPwm(pinC_l, _constrain(dc_c + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// _setPwm(pinC_h, _constrain(dc_c - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
// break;
// }
}
#endif
@@ -0,0 +1,163 @@
/**
* Support for the RP2040 MCU, as found on the Raspberry Pi Pico.
*/
#if defined(TARGET_RP2040)
#define SIMPLEFOC_DEBUG_RP2040
#ifdef SIMPLEFOC_DEBUG_RP2040
#ifndef SIMPLEFOC_RP2040_DEBUG_SERIAL
#define SIMPLEFOC_RP2040_DEBUG_SERIAL Serial
#endif
#endif
#include "Arduino.h"
// until I can figure out if this can be quickly read from some register, keep it here.
// it also serves as a marker for what slices are already used.
uint16_t wrapvalues[NUM_PWM_SLICES];
// TODO add checks which channels are already used...
void setupPWM(int pin, long pwm_frequency, bool invert = false) {
gpio_set_function(pin, GPIO_FUNC_PWM);
uint slice = pwm_gpio_to_slice_num(pin);
uint chan = pwm_gpio_to_channel(pin);
pwm_set_clkdiv_int_frac(slice, 1, 0); // fastest pwm we can get
pwm_set_phase_correct(slice, true);
uint16_t wrapvalue = ((125L * 1000L * 1000L) / pwm_frequency) / 2L - 1L;
if (wrapvalue < 999) wrapvalue = 999; // 66kHz, resolution 1000
if (wrapvalue > 3299) wrapvalue = 3299; // 20kHz, resolution 3300
#ifdef SIMPLEFOC_DEBUG_RP2040
SIMPLEFOC_RP2040_DEBUG_SERIAL.print("Configuring pin ");
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(pin);
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(" slice ");
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(slice);
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(" channel ");
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(chan);
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(" frequency ");
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(pwm_frequency);
SIMPLEFOC_RP2040_DEBUG_SERIAL.print(" top value ");
SIMPLEFOC_RP2040_DEBUG_SERIAL.println(wrapvalue);
#endif
pwm_set_wrap(slice, wrapvalue);
wrapvalues[slice] = wrapvalue;
if (invert) {
if (chan==0)
hw_write_masked(&pwm_hw->slice[slice].csr, 0x1 << PWM_CH0_CSR_A_INV_LSB, PWM_CH0_CSR_A_INV_BITS);
else
hw_write_masked(&pwm_hw->slice[slice].csr, 0x1 << PWM_CH0_CSR_B_INV_LSB, PWM_CH0_CSR_B_INV_BITS);
}
pwm_set_chan_level(slice, chan, 0); // switch off initially
}
void syncSlices() {
for (int i=0;i<NUM_PWM_SLICES;i++) {
pwm_set_enabled(i, false);
pwm_set_counter(i, 0);
}
// enable all slices
pwm_set_mask_enabled(0x7F);
}
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
setupPWM(pinA, pwm_frequency);
setupPWM(pinB, pwm_frequency);
syncSlices();
}
void _configure3PWM(long pwm_frequency, const int pinA, const int pinB, const int pinC) {
setupPWM(pinA, pwm_frequency);
setupPWM(pinB, pwm_frequency);
setupPWM(pinC, pwm_frequency);
syncSlices();
}
void _configure4PWM(long pwm_frequency, const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
setupPWM(pin1A, pwm_frequency);
setupPWM(pin1B, pwm_frequency);
setupPWM(pin2A, pwm_frequency);
setupPWM(pin2B, pwm_frequency);
syncSlices();
}
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
// non-PIO solution...
setupPWM(pinA_h, pwm_frequency);
setupPWM(pinB_h, pwm_frequency);
setupPWM(pinC_h, pwm_frequency);
setupPWM(pinA_l, pwm_frequency, true);
setupPWM(pinB_l, pwm_frequency, true);
setupPWM(pinC_l, pwm_frequency, true);
syncSlices();
return 0;
}
void writeDutyCycle(float val, int pin) {
uint slice = pwm_gpio_to_slice_num(pin);
uint chan = pwm_gpio_to_channel(pin);
pwm_set_chan_level(slice, chan, (wrapvalues[slice]+1) * val);
}
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB) {
writeDutyCycle(dc_a, pinA);
writeDutyCycle(dc_b, pinB);
}
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC) {
writeDutyCycle(dc_a, pinA);
writeDutyCycle(dc_b, pinB);
writeDutyCycle(dc_c, pinC);
}
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B) {
writeDutyCycle(dc_1a, pin1A);
writeDutyCycle(dc_1b, pin1B);
writeDutyCycle(dc_2a, pin2A);
writeDutyCycle(dc_2b, pin2B);
}
inline float swDti(float val, float dt) {
float ret = dt+val;
if (ret>1.0) ret = 1.0f;
return ret;
}
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l) {
writeDutyCycle(dc_a, pinA_h);
writeDutyCycle(swDti(dc_a, dead_zone), pinA_l);
writeDutyCycle(dc_b, pinB_h);
writeDutyCycle(swDti(dc_b,dead_zone), pinB_l);
writeDutyCycle(dc_c, pinC_h);
writeDutyCycle(swDti(dc_c,dead_zone), pinC_l);
}
#endif
@@ -0,0 +1,348 @@
#include "./samd_mcu.h"
#ifdef _SAMD21_
#ifndef TCC3_CH0
#define TCC3_CH0 NOT_ON_TIMER
#endif
#ifndef TCC3_CH1
#define TCC3_CH1 NOT_ON_TIMER
#endif
#ifndef TCC3_CH2
#define TCC3_CH2 NOT_ON_TIMER
#endif
#ifndef TCC3_CH3
#define TCC3_CH3 NOT_ON_TIMER
#endif
#ifndef TCC3_CH4
#define TCC3_CH4 NOT_ON_TIMER
#endif
#ifndef TCC3_CH5
#define TCC3_CH5 NOT_ON_TIMER
#endif
#ifndef TCC3_CH6
#define TCC3_CH6 NOT_ON_TIMER
#endif
#ifndef TCC3_CH7
#define TCC3_CH7 NOT_ON_TIMER
#endif
#ifndef TC6_CH0
#define TC6_CH0 NOT_ON_TIMER
#endif
#ifndef TC6_CH1
#define TC6_CH1 NOT_ON_TIMER
#endif
#ifndef TC7_CH0
#define TC7_CH0 NOT_ON_TIMER
#endif
#ifndef TC7_CH1
#define TC7_CH1 NOT_ON_TIMER
#endif
#define NUM_WO_ASSOCIATIONS 48
/*
* For SAM D21 A/B/C/D Variant Devices and SAM DA1 A/B Variant Devices
* Good for SAMD2xE, SAMD2xG and SAMD2xJ devices. Other SAMD21s currently not supported in arduino anyway?
*
* Note: only the pins which have timers associated are listed in this table.
* You can use the values from g_APinDescription.ulPort and g_APinDescription.ulPin to find the correct row in the table.
*
* See Microchip Technology datasheet DS40001882F-page 30
*/
struct wo_association WO_associations[] = {
{ PORTA, 0, TCC2_CH0, 0, NOT_ON_TIMER, 0},
{ PORTA, 1, TCC2_CH1, 1, NOT_ON_TIMER, 0},
{ PORTA, 2, NOT_ON_TIMER, 0, TCC3_CH0, 0},
{ PORTA, 3, NOT_ON_TIMER, 0, TCC3_CH1, 1},
// PB04, PB05, PB06, PB07 - no timers
{ PORTB, 8, TC4_CH0, 0, TCC3_CH6, 6},
{ PORTB, 9, TC4_CH1, 1, TCC3_CH7, 7},
{ PORTA, 4, TCC0_CH0, 0, TCC3_CH2, 2},
{ PORTA, 5, TCC0_CH1, 1, TCC3_CH3, 3},
{ PORTA, 6, TCC1_CH0, 0, TCC3_CH4, 4},
{ PORTA, 7, TCC1_CH1, 1, TCC3_CH5, 5},
{ PORTA, 8, TCC0_CH0, 0, TCC1_CH2, 2},
{ PORTA, 9, TCC0_CH1, 1, TCC1_CH3, 3},
{ PORTA, 10, TCC1_CH0, 0, TCC0_CH2, 2},
{ PORTA, 11, TCC1_CH1, 1, TCC0_CH3, 3},
{ PORTB, 10, TC5_CH0, 0, TCC0_CH4, 4},
{ PORTB, 11, TC5_CH1, 1, TCC0_CH5, 5},
{ PORTB, 12, TC4_CH0, 0, TCC0_CH6, 6},
{ PORTB, 13, TC4_CH1, 1, TCC0_CH7, 7},
{ PORTB, 14, TC5_CH0, 0, NOT_ON_TIMER, 0},
{ PORTB, 15, TC5_CH1, 1, NOT_ON_TIMER, 0},
{ PORTA, 12, TCC2_CH0, 0, TCC0_CH6, 6},
{ PORTA, 13, TCC2_CH1, 1, TCC0_CH7, 7},
{ PORTA, 14, TC3_CH0, 0, TCC0_CH4, 4},
{ PORTA, 15, TC3_CH1, 1, TCC0_CH5, 5},
{ PORTA, 16, TCC2_CH0, 0, TCC0_CH6, 6},
{ PORTA, 17, TCC2_CH1, 1, TCC0_CH7, 7},
{ PORTA, 18, TC3_CH0, 0, TCC0_CH2, 2},
{ PORTA, 19, TC3_CH1, 1, TCC0_CH3, 3},
{ PORTB, 16, TC6_CH0, 0, TCC0_CH4, 4},
{ PORTB, 17, TC6_CH1, 1, TCC0_CH5, 5},
{ PORTA, 20, TC7_CH0, 0, TCC0_CH6, 6},
{ PORTA, 21, TC7_CH1, 1, TCC0_CH7, 7},
{ PORTA, 22, TC4_CH0, 0, TCC0_CH4, 4},
{ PORTA, 23, TC4_CH1, 1, TCC0_CH5, 5},
{ PORTA, 24, TC5_CH0, 0, TCC1_CH2, 2},
{ PORTA, 25, TC5_CH1, 1, TCC1_CH3, 3},
{ PORTB, 22, TC7_CH0, 0, TCC3_CH0, 0},
{ PORTB, 23, TC7_CH1, 1, TCC3_CH1, 1},
{ PORTA, 27, NOT_ON_TIMER, 0, TCC3_CH6, 6},
{ PORTA, 28, NOT_ON_TIMER, 0, TCC3_CH7, 7},
{ PORTA, 30, TCC1_CH0, 0, TCC3_CH4, 4},
{ PORTA, 31, TCC1_CH1, 1, TCC3_CH5, 5},
{ PORTB, 30, TCC0_CH0, 0, TCC1_CH2, 2},
{ PORTB, 31, TCC0_CH1, 1, TCC1_CH3, 3},
{ PORTB, 0, TC7_CH0, 0, NOT_ON_TIMER, 0},
{ PORTB, 1, TC7_CH1, 1, NOT_ON_TIMER, 0},
{ PORTB, 2, TC6_CH0, 0, TCC3_CH2, 2},
{ PORTB, 3, TC6_CH1, 1, TCC3_CH3, 3}
};
wo_association ASSOCIATION_NOT_FOUND = { NOT_A_PORT, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0};
struct wo_association& getWOAssociation(EPortType port, uint32_t pin) {
for (int i=0;i<NUM_WO_ASSOCIATIONS;i++) {
if (WO_associations[i].port==port && WO_associations[i].pin==pin)
return WO_associations[i];
}
return ASSOCIATION_NOT_FOUND;
};
EPioType getPeripheralOfPermutation(int permutation, int pin_position) {
return ((permutation>>pin_position)&0x01)==0x1?PIO_TIMER_ALT:PIO_TIMER;
}
void syncTCC(Tcc* TCCx) {
while (TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK); // Wait for synchronization of registers between the clock domains
}
/**
* Configure Clock 4 - we want all simplefoc PWMs to use the same clock. This ensures that
* any compatible pin combination can be used without having to worry about configuring different
* clocks.
*/
void configureSAMDClock() {
// TODO investigate using the FDPLL96M clock to get 96MHz timer clocks... this
// would enable 48KHz PWM clocks, and setting the frequency between 24Khz with resolution 2000, to 48KHz with resolution 1000
if (!SAMDClockConfigured) {
SAMDClockConfigured = true; // mark clock as configured
for (int i=0;i<TCC_INST_NUM;i++) // mark all the TCCs as not yet configured
tccConfigured[i] = false;
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor N=1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
// GCLK_GENCTRL_SRC_FDPLL | // Set the 96MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured clock...");
#endif
}
}
/**
* Configure a TCC unit
* pwm_frequency is fixed at 24kHz for now. We could go slower, but going
* faster won't be possible without sacrificing resolution.
*/
void configureTCC(tccConfiguration& tccConfig, long pwm_frequency, bool negate, float hw6pwm) {
long pwm_resolution = (24000000) / pwm_frequency;
if (pwm_resolution>SIMPLEFOC_SAMD_MAX_PWM_RESOLUTION)
pwm_resolution = SIMPLEFOC_SAMD_MAX_PWM_RESOLUTION;
if (pwm_resolution<SIMPLEFOC_SAMD_MIN_PWM_RESOLUTION)
pwm_resolution = SIMPLEFOC_SAMD_MIN_PWM_RESOLUTION;
// store for later use
tccConfig.pwm_res = pwm_resolution;
// TODO for the moment we ignore the frequency...
if (!tccConfigured[tccConfig.tcc.tccn]) {
uint32_t GCLK_CLKCTRL_ID_ofthistcc = -1;
switch (tccConfig.tcc.tccn>>1) {
case 0: GCLK_CLKCTRL_ID_ofthistcc = GCLK_CLKCTRL_ID(GCM_TCC0_TCC1); break; //GCLK_CLKCTRL_ID_TCC0_TCC1;
case 1: GCLK_CLKCTRL_ID_ofthistcc = GCLK_CLKCTRL_ID(GCM_TCC2_TC3); break; //GCLK_CLKCTRL_ID_TCC2_TC3;
case 2: GCLK_CLKCTRL_ID_ofthistcc = GCLK_CLKCTRL_ID(GCM_TC4_TC5); break; //GCLK_CLKCTRL_ID_TC4_TC5;
case 3: GCLK_CLKCTRL_ID_ofthistcc = GCLK_CLKCTRL_ID(GCM_TC6_TC7); break;
default: return;
}
// Feed GCLK4 to TCC
REG_GCLK_CLKCTRL = (uint16_t) GCLK_CLKCTRL_CLKEN | // Enable GCLK4
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_ofthistcc; // Feed GCLK4 to tcc
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
tccConfigured[tccConfig.tcc.tccn] = true;
if (tccConfig.tcc.tccn>=TCC_INST_NUM) {
Tc* tc = (Tc*)GetTC(tccConfig.tcc.chaninfo);
// disable
tc->COUNT8.CTRLA.bit.ENABLE = 0;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// unfortunately we need the 8-bit counter mode to use the PER register...
tc->COUNT8.CTRLA.reg |= TC_CTRLA_MODE_COUNT8 | TC_CTRLA_WAVEGEN_NPWM ;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// meaning prescaler of 8, since TC-Unit has no up/down mode, and has resolution of 250 rather than 1000...
tc->COUNT8.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV8_Val ;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// period is 250, period cannot be higher than 256!
tc->COUNT8.PER.reg = SIMPLEFOC_SAMD_PWM_TC_RESOLUTION-1;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// initial duty cycle is 0
tc->COUNT8.CC[tccConfig.tcc.chan].reg = 0;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// enable
tc->COUNT8.CTRLA.bit.ENABLE = 1;
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Initialized TC ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(tccConfig.tcc.tccn);
#endif
}
else {
Tcc* tcc = (Tcc*)GetTC(tccConfig.tcc.chaninfo);
uint8_t invenMask = ~(1<<tccConfig.tcc.chan); // negate (invert) the signal if needed
uint8_t invenVal = negate?(1<<tccConfig.tcc.chan):0;
tcc->DRVCTRL.vec.INVEN = (tcc->DRVCTRL.vec.INVEN&invenMask)|invenVal;
syncTCC(tcc); // wait for sync
tcc->WAVE.reg |= TCC_WAVE_POL(0xF)|TCC_WAVEB_WAVEGENB_DSBOTH; // Set wave form configuration
while ( tcc->SYNCBUSY.bit.WAVE == 1 ); // wait for sync
if (hw6pwm>0.0) {
tcc->WEXCTRL.vec.DTIEN |= (1<<tccConfig.tcc.chan);
tcc->WEXCTRL.bit.DTLS = hw6pwm*(pwm_resolution-1);
tcc->WEXCTRL.bit.DTHS = hw6pwm*(pwm_resolution-1);
syncTCC(tcc); // wait for sync
}
tcc->PER.reg = pwm_resolution - 1; // Set counter Top using the PER register
while ( tcc->SYNCBUSY.bit.PER == 1 ); // wait for sync
// set all channels to 0%
uint8_t chanCount = (tccConfig.tcc.tccn==1||tccConfig.tcc.tccn==2)?2:4;
for (int i=0;i<chanCount;i++) {
tcc->CC[i].reg = 0; // start off at 0% duty cycle
uint32_t chanbit = 0x1<<(TCC_SYNCBUSY_CC0_Pos+i);
while ( (tcc->SYNCBUSY.reg & chanbit) > 0 );
}
// Enable TC
tcc->CTRLA.reg |= TCC_CTRLA_ENABLE | TCC_CTRLA_PRESCALER_DIV1; //48Mhz/1=48Mhz/2(up/down)=24MHz/1024=24KHz
while ( tcc->SYNCBUSY.bit.ENABLE == 1 ); // wait for sync
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" Initialized TCC ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.tccn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.chan);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.wo);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("] pwm res ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(pwm_resolution);
#endif
}
}
else if (tccConfig.tcc.tccn<TCC_INST_NUM) {
// set invert bit for TCC channels in SW 6-PWM...
Tcc* tcc = (Tcc*)GetTC(tccConfig.tcc.chaninfo);
tcc->CTRLA.bit.ENABLE = 0;
while ( tcc->SYNCBUSY.bit.ENABLE == 1 );
uint8_t invenMask = ~(1<<tccConfig.tcc.chan); // negate (invert) the signal if needed
uint8_t invenVal = negate?(1<<tccConfig.tcc.chan):0;
tcc->DRVCTRL.vec.INVEN = (tcc->DRVCTRL.vec.INVEN&invenMask)|invenVal;
syncTCC(tcc); // wait for sync
if (hw6pwm>0.0) {
tcc->WEXCTRL.vec.DTIEN |= (1<<tccConfig.tcc.chan);
tcc->WEXCTRL.bit.DTLS = hw6pwm*(pwm_resolution-1);
tcc->WEXCTRL.bit.DTHS = hw6pwm*(pwm_resolution-1);
syncTCC(tcc); // wait for sync
}
tcc->CTRLA.bit.ENABLE = 1;
while ( tcc->SYNCBUSY.bit.ENABLE == 1 );
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("(Re-)Initialized TCC ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.tccn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.chan);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.wo);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("] pwm res ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(pwm_resolution);
#endif
}
}
void writeSAMDDutyCycle(tccConfiguration* info, float dc) {
uint8_t tccn = GetTCNumber(info->tcc.chaninfo);
uint8_t chan = GetTCChannelNumber(info->tcc.chaninfo);
if (tccn<TCC_INST_NUM) {
Tcc* tcc = (Tcc*)GetTC(info->tcc.chaninfo);
// set via CC
// tcc->CC[chan].reg = (uint32_t)((SIMPLEFOC_SAMD_PWM_RESOLUTION-1) * dc);
// uint32_t chanbit = 0x1<<(TCC_SYNCBUSY_CC0_Pos+chan);
// while ( (tcc->SYNCBUSY.reg & chanbit) > 0 );
// set via CCB
//while ( (tcc->SYNCBUSY.vec.CC & (0x1<<chan)) > 0 );
tcc->CCB[chan].reg = (uint32_t)((info->pwm_res-1) * dc);
// while ( (tcc->SYNCBUSY.vec.CCB & (0x1<<chan)) > 0 );
// tcc->STATUS.vec.CCBV |= (0x1<<chan);
// while ( tcc->SYNCBUSY.bit.STATUS > 0 );
// tcc->CTRLBSET.reg |= TCC_CTRLBSET_CMD(TCC_CTRLBSET_CMD_UPDATE_Val);
// while ( tcc->SYNCBUSY.bit.CTRLB > 0 );
}
else {
Tc* tc = (Tc*)GetTC(info->tcc.chaninfo);
tc->COUNT8.CC[chan].reg = (uint8_t)((SIMPLEFOC_SAMD_PWM_TC_RESOLUTION-1) * dc);
while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
}
}
#endif
@@ -0,0 +1,311 @@
#include "./samd_mcu.h"
#if defined(_SAMD51_)||defined(_SAME51_)
// expected frequency on DPLL, since we don't configure it ourselves. Typically this is the CPU frequency.
// for custom boards or overclockers you can override it using this define.
#ifndef SIMPLEFOC_SAMD51_DPLL_FREQ
#define SIMPLEFOC_SAMD51_DPLL_FREQ 120000000
#endif
// TCC# Channels WO_NUM Counter size Fault Dithering Output matrix DTI SWAP Pattern generation
// 0 6 8 24-bit Yes Yes Yes Yes Yes Yes
// 1 4 8 24-bit Yes Yes Yes Yes Yes Yes
// 2 3 3 16-bit Yes - Yes - - -
// 3 2 2 16-bit Yes - - - - -
// 4 2 2 16-bit Yes - - - - -
#define NUM_WO_ASSOCIATIONS 72
struct wo_association WO_associations[] = {
{ PORTB, 9, TC4_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTA, 4, TC0_CH0, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTA, 5, TC0_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTA, 6, TC1_CH0, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTA, 7, TC1_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTC, 4, NOT_ON_TIMER, 0, TCC0_CH0, 0, NOT_ON_TIMER, 0},
// PC05, PC06, PC07 -> no timers
{ PORTA, 8, TC0_CH0, 0, TCC0_CH0, 0, TCC1_CH0, 4},
{ PORTA, 9, TC0_CH1, 1, TCC0_CH1, 1, TCC1_CH1, 5},
{ PORTA, 10, TC1_CH0, 0, TCC0_CH2, 2, TCC1_CH2, 6},
{ PORTA, 11, TC1_CH1, 1, TCC0_CH3, 3, TCC1_CH3, 7},
{ PORTB, 10, TC5_CH0, 0, TCC0_CH4, 4, TCC1_CH0, 0},
{ PORTB, 11, TC5_CH1, 1, TCC0_CH5, 5, TCC1_CH1, 1},
{ PORTB, 12, TC4_CH0, 0, TCC3_CH0, 0, TCC0_CH0, 0},
{ PORTB, 13, TC4_CH1, 1, TCC3_CH1, 1, TCC0_CH1, 1},
{ PORTB, 14, TC5_CH0, 0, TCC4_CH0, 0, TCC0_CH2, 2},
{ PORTB, 15, TC5_CH1, 1, TCC4_CH1, 1, TCC0_CH3, 3},
{ PORTD, 8, NOT_ON_TIMER, 0, TCC0_CH1, 1, NOT_ON_TIMER, 0},
{ PORTD, 9, NOT_ON_TIMER, 0, TCC0_CH2, 2, NOT_ON_TIMER, 0},
{ PORTD, 10, NOT_ON_TIMER, 0, TCC0_CH3, 3, NOT_ON_TIMER, 0},
{ PORTD, 11, NOT_ON_TIMER, 0, TCC0_CH4, 4, NOT_ON_TIMER, 0},
{ PORTD, 12, NOT_ON_TIMER, 0, TCC0_CH5, 5, NOT_ON_TIMER, 0},
{ PORTC, 10, NOT_ON_TIMER, 0, TCC0_CH0, 0, TCC1_CH0, 4},
{ PORTC, 11, NOT_ON_TIMER, 0, TCC0_CH1, 1, TCC1_CH1, 5},
{ PORTC, 12, NOT_ON_TIMER, 0, TCC0_CH2, 2, TCC1_CH2, 6},
{ PORTC, 13, NOT_ON_TIMER, 0, TCC0_CH3, 3, TCC1_CH3, 7},
{ PORTC, 14, NOT_ON_TIMER, 0, TCC0_CH4, 4, TCC1_CH0, 0},
{ PORTC, 15, NOT_ON_TIMER, 0, TCC0_CH5, 5, TCC1_CH1, 1},
{ PORTA, 12, TC2_CH0, 0, TCC0_CH0, 6, TCC1_CH2, 2},
{ PORTA, 13, TC2_CH1, 1, TCC0_CH1, 7, TCC1_CH3, 3},
{ PORTA, 14, TC3_CH0, 0, TCC2_CH0, 0, TCC1_CH2, 2},
{ PORTA, 15, TC3_CH1, 1, TCC1_CH1, 1, TCC1_CH3, 3},
{ PORTA, 16, TC2_CH0, 0, TCC1_CH0, 0, TCC0_CH4, 4},
{ PORTA, 17, TC2_CH1, 1, TCC1_CH1, 1, TCC0_CH5, 5},
{ PORTA, 18, TC3_CH0, 0, TCC1_CH2, 2, TCC0_CH0, 6},
{ PORTA, 19, TC3_CH1, 1, TCC1_CH3, 3, TCC0_CH1, 7},
{ PORTC, 16, NOT_ON_TIMER, 0, TCC0_CH0, 0, NOT_ON_TIMER, 0}, // PDEC0
{ PORTC, 17, NOT_ON_TIMER, 0, TCC0_CH1, 1, NOT_ON_TIMER, 0}, // PDEC1
{ PORTC, 18, NOT_ON_TIMER, 0, TCC0_CH2, 2, NOT_ON_TIMER, 0}, // PDEC2
{ PORTC, 19, NOT_ON_TIMER, 0, TCC0_CH3, 3, NOT_ON_TIMER, 0},
{ PORTC, 20, NOT_ON_TIMER, 0, TCC0_CH4, 4, NOT_ON_TIMER, 0},
{ PORTC, 21, NOT_ON_TIMER, 0, TCC0_CH5, 5, NOT_ON_TIMER, 0},
{ PORTC, 22, NOT_ON_TIMER, 0, TCC0_CH0, 6, NOT_ON_TIMER, 0},
{ PORTC, 23, NOT_ON_TIMER, 0, TCC0_CH1, 7, NOT_ON_TIMER, 0},
{ PORTD, 20, NOT_ON_TIMER, 0, TCC1_CH0, 0, NOT_ON_TIMER, 0},
{ PORTD, 21, NOT_ON_TIMER, 0, TCC1_CH1, 1, NOT_ON_TIMER, 0},
{ PORTB, 16, TC6_CH0, 0, TCC3_CH0, 0, TCC0_CH4, 4},
{ PORTB, 17, TC6_CH1, 1, TCC3_CH1, 1, TCC0_CH5, 5},
{ PORTB, 18, NOT_ON_TIMER, 0, TCC1_CH0, 0, NOT_ON_TIMER, 0}, // PDEC0
{ PORTB, 19, NOT_ON_TIMER, 0, TCC1_CH1, 1, NOT_ON_TIMER, 0}, // PDEC1
{ PORTB, 20, NOT_ON_TIMER, 0, TCC1_CH2, 2, NOT_ON_TIMER, 0}, // PDEC2
{ PORTB, 21, NOT_ON_TIMER, 0, TCC1_CH3, 3, NOT_ON_TIMER, 0},
{ PORTA, 20, TC7_CH0, 0, TCC1_CH0, 4, TCC0_CH0, 0},
{ PORTA, 21, TC7_CH1, 1, TCC1_CH1, 5, TCC0_CH1, 1},
{ PORTA, 22, TC4_CH0, 0, TCC1_CH2, 6, TCC0_CH2, 2},
{ PORTA, 23, TC4_CH1, 1, TCC1_CH3, 7, TCC0_CH3, 3},
{ PORTA, 24, TC5_CH0, 0, TCC2_CH2, 2, NOT_ON_TIMER, 0}, // PDEC0
{ PORTA, 25, TC5_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0}, // PDEC1
{ PORTB, 22, TC7_CH0, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0}, // PDEC2
{ PORTB, 23, TC7_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0}, // PDEC0
{ PORTB, 24, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0}, // PDEC1
{ PORTB, 25, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0}, // PDEC2
{ PORTB, 26, NOT_ON_TIMER, 0, TCC1_CH2, 2, NOT_ON_TIMER, 0},
{ PORTB, 27, NOT_ON_TIMER, 0, TCC1_CH3, 3, NOT_ON_TIMER, 0},
{ PORTB, 28, NOT_ON_TIMER, 0, TCC1_CH0, 4, NOT_ON_TIMER, 0},
{ PORTB, 29, NOT_ON_TIMER, 1, TCC1_CH1, 5, NOT_ON_TIMER, 0},
// PC24-PC28, PA27, RESET -> no TC/TCC peripherals
{ PORTA, 30, TC6_CH0, 0, TCC2_CH0, 0, NOT_ON_TIMER, 0},
{ PORTA, 31, TC6_CH1, 1, TCC2_CH1, 1, NOT_ON_TIMER, 0},
{ PORTB, 30, TC0_CH0, 0, TCC4_CH0, 0, TCC0_CH0, 6},
{ PORTB, 31, TC0_CH1, 1, TCC4_CH1, 1, TCC0_CH1, 7},
// PC30, PC31 -> no TC/TCC peripherals
{ PORTB, 0, TC7_CH0, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTB, 1, TC7_CH1, 1, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0},
{ PORTB, 2, TC6_CH0, 0, TCC2_CH2, 2, NOT_ON_TIMER, 0},
};
wo_association ASSOCIATION_NOT_FOUND = { NOT_A_PORT, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0, NOT_ON_TIMER, 0};
uint8_t TCC_CHANNEL_COUNT[] = { TCC0_CC_NUM, TCC1_CC_NUM, TCC2_CC_NUM, TCC3_CC_NUM, TCC4_CC_NUM };
struct wo_association& getWOAssociation(EPortType port, uint32_t pin) {
for (int i=0;i<NUM_WO_ASSOCIATIONS;i++) {
if (WO_associations[i].port==port && WO_associations[i].pin==pin)
return WO_associations[i];
}
return ASSOCIATION_NOT_FOUND;
};
EPioType getPeripheralOfPermutation(int permutation, int pin_position) {
return ((permutation>>pin_position)&0x01)==0x1?PIO_TCC_PDEC:PIO_TIMER_ALT;
}
void syncTCC(Tcc* TCCx) {
while (TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK);
}
void writeSAMDDutyCycle(tccConfiguration* info, float dc) {
uint8_t tccn = GetTCNumber(info->tcc.chaninfo);
uint8_t chan = GetTCChannelNumber(info->tcc.chaninfo);
if (tccn<TCC_INST_NUM) {
Tcc* tcc = (Tcc*)GetTC(info->tcc.chaninfo);
// set via CCBUF
// while ( (tcc->SYNCBUSY.vec.CC & (0x1<<chan)) > 0 );
tcc->CCBUF[chan].reg = (uint32_t)((info->pwm_res-1) * dc); // TODO pwm frequency!
}
else {
// we don't support the TC channels on SAMD51, isn't worth it.
}
}
#define DPLL_CLOCK_NUM 2 // use GCLK2
#define PWM_CLOCK_NUM 3 // use GCLK3
/**
* Configure Clock 4 - we want all simplefoc PWMs to use the same clock. This ensures that
* any compatible pin combination can be used without having to worry about configuring different
* clocks.
*/
void configureSAMDClock() {
if (!SAMDClockConfigured) {
SAMDClockConfigured = true; // mark clock as configured
for (int i=0;i<TCC_INST_NUM;i++) // mark all the TCCs as not yet configured
tccConfigured[i] = false;
// GCLK->GENCTRL[DPLL_CLOCK_NUM].reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_DIV(1)
// | GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL_Val);
// while (GCLK->SYNCBUSY.vec.GENCTRL&(0x1<<DPLL_CLOCK_NUM));
// GCLK->PCHCTRL[1].reg = GCLK_PCHCTRL_GEN(DPLL_CLOCK_NUM)|GCLK_PCHCTRL_CHEN;
// while (GCLK->SYNCBUSY.vec.GENCTRL&(0x1<<DPLL_CLOCK_NUM));
// OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 0;
// while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg!=0x0);
// OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK = OSCCTRL_DPLLCTRLB_REFCLK_GCLK_Val;
// while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg!=0x0);
// OSCCTRL->Dpll[0].DPLLRATIO.reg = 3;
// while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg!=0x0);
// OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 1;
// while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg!=0x0);
GCLK->GENCTRL[PWM_CLOCK_NUM].bit.GENEN = 0;
while (GCLK->SYNCBUSY.vec.GENCTRL&(0x1<<PWM_CLOCK_NUM));
GCLK->GENCTRL[PWM_CLOCK_NUM].reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_IDC
//| GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL_Val);
| GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DPLL0_Val);
while (GCLK->SYNCBUSY.vec.GENCTRL&(0x1<<PWM_CLOCK_NUM));
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured clock...");
#endif
}
}
/**
* Configure a TCC unit
* pwm_frequency is fixed at 24kHz for now. We could go slower, but going
* faster won't be possible without sacrificing resolution.
*/
void configureTCC(tccConfiguration& tccConfig, long pwm_frequency, bool negate, float hw6pwm) {
// TODO for the moment we ignore the frequency...
if (!tccConfigured[tccConfig.tcc.tccn]) {
uint32_t GCLK_CLKCTRL_ID_ofthistcc = GCLK_CLKCTRL_IDs[tccConfig.tcc.tccn];
GCLK->PCHCTRL[GCLK_CLKCTRL_ID_ofthistcc].reg = GCLK_PCHCTRL_GEN(PWM_CLOCK_NUM)|GCLK_PCHCTRL_CHEN;
while (GCLK->SYNCBUSY.vec.GENCTRL&(0x1<<PWM_CLOCK_NUM));
}
if (tccConfig.tcc.tccn<TCC_INST_NUM) {
Tcc* tcc = (Tcc*)GetTC(tccConfig.tcc.chaninfo);
tcc->CTRLA.bit.ENABLE = 0; //switch off tcc
while ( tcc->SYNCBUSY.bit.ENABLE == 1 ); // wait for sync
uint8_t invenMask = ~(1<<tccConfig.tcc.chan); // negate (invert) the signal if needed
uint8_t invenVal = negate?(1<<tccConfig.tcc.chan):0;
tcc->DRVCTRL.vec.INVEN = (tcc->DRVCTRL.vec.INVEN&invenMask)|invenVal;
syncTCC(tcc); // wait for sync
// work out pwm resolution for desired frequency and constrain to max/min values
long pwm_resolution = (SIMPLEFOC_SAMD51_DPLL_FREQ/2) / pwm_frequency;
if (pwm_resolution>SIMPLEFOC_SAMD_MAX_PWM_RESOLUTION)
pwm_resolution = SIMPLEFOC_SAMD_MAX_PWM_RESOLUTION;
if (pwm_resolution<SIMPLEFOC_SAMD_MIN_PWM_RESOLUTION)
pwm_resolution = SIMPLEFOC_SAMD_MIN_PWM_RESOLUTION;
// store for later use
tccConfig.pwm_res = pwm_resolution;
if (hw6pwm>0.0) {
tcc->WEXCTRL.vec.DTIEN |= (1<<tccConfig.tcc.chan);
tcc->WEXCTRL.bit.DTLS = hw6pwm*(pwm_resolution-1);
tcc->WEXCTRL.bit.DTHS = hw6pwm*(pwm_resolution-1);
syncTCC(tcc); // wait for sync
}
if (!tccConfigured[tccConfig.tcc.tccn]) {
tcc->WAVE.reg |= TCC_WAVE_POL(0xF)|TCC_WAVE_WAVEGEN_DSTOP; // Set wave form configuration - TODO check this... why set like this?
while ( tcc->SYNCBUSY.bit.WAVE == 1 ); // wait for sync
tcc->PER.reg = pwm_resolution - 1; // Set counter Top using the PER register
while ( tcc->SYNCBUSY.bit.PER == 1 ); // wait for sync
// set all channels to 0%
for (int i=0;i<TCC_CHANNEL_COUNT[tccConfig.tcc.tccn];i++) {
tcc->CC[i].reg = 0; // start off at 0% duty cycle
uint32_t chanbit = 0x1<<(TCC_SYNCBUSY_CC0_Pos+i);
while ( (tcc->SYNCBUSY.reg & chanbit) > 0 );
}
}
// Enable TCC
tcc->CTRLA.reg |= TCC_CTRLA_ENABLE | TCC_CTRLA_PRESCALER_DIV1; //48Mhz/1=48Mhz/2(up/down)=24MHz/1024=24KHz
while ( tcc->SYNCBUSY.bit.ENABLE == 1 ); // wait for sync
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("(Re-)Initialized TCC ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.tccn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.tcc.chan);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tccConfig.wo);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("] pwm res ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(pwm_resolution);
#endif
}
else if (tccConfig.tcc.tccn>=TCC_INST_NUM) {
//Tc* tc = (Tc*)GetTC(tccConfig.tcc.chaninfo);
// disable
// tc->COUNT8.CTRLA.bit.ENABLE = 0;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// // unfortunately we need the 8-bit counter mode to use the PER register...
// tc->COUNT8.CTRLA.reg |= TC_CTRLA_MODE_COUNT8 | TC_CTRLA_WAVEGEN_NPWM ;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// // meaning prescaler of 8, since TC-Unit has no up/down mode, and has resolution of 250 rather than 1000...
// tc->COUNT8.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV8_Val ;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// // period is 250, period cannot be higher than 256!
// tc->COUNT8.PER.reg = SIMPLEFOC_SAMD_PWM_TC_RESOLUTION-1;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// // initial duty cycle is 0
// tc->COUNT8.CC[tccConfig.tcc.chan].reg = 0;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
// // enable
// tc->COUNT8.CTRLA.bit.ENABLE = 1;
// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Not initialized: TC ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(tccConfig.tcc.tccn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("TC units not supported on SAMD51");
#endif
}
// set as configured
tccConfigured[tccConfig.tcc.tccn] = true;
}
#endif
@@ -0,0 +1,903 @@
#include "./samd_mcu.h"
#if defined(_SAMD21_)||defined(_SAMD51_)||defined(_SAME51_)
/**
* Global state
*/
tccConfiguration tccPinConfigurations[SIMPLEFOC_SAMD_MAX_TCC_PINCONFIGURATIONS];
uint8_t numTccPinConfigurations = 0;
bool SAMDClockConfigured = false;
bool tccConfigured[TCC_INST_NUM+TC_INST_NUM];
/**
* Attach the TCC to the pin
*/
bool attachTCC(tccConfiguration& tccConfig) {
if (numTccPinConfigurations>=SIMPLEFOC_SAMD_MAX_TCC_PINCONFIGURATIONS)
return false;
pinMode(tccConfig.pin, OUTPUT);
pinPeripheral(tccConfig.pin, tccConfig.peripheral);
tccPinConfigurations[numTccPinConfigurations++] = tccConfig;
return true;
}
int getPermutationNumber(int pins) {
int num = 1;
for (int i=0;i<pins;i++)
num *= NUM_PIO_TIMER_PERIPHERALS;
return num;
}
/**
* Check if the configuration is in use already.
*/
bool inUse(tccConfiguration& tccConfig) {
for (int i=0;i<numTccPinConfigurations;i++) {
if (tccPinConfigurations[i].tcc.chaninfo==tccConfig.tcc.chaninfo)
return true;
}
return false;
}
tccConfiguration* getTccPinConfiguration(uint8_t pin) {
for (int i=0;i<numTccPinConfigurations;i++)
if (tccPinConfigurations[i].pin==pin)
return &tccPinConfigurations[i];
return NULL;
}
/**
* Get the TCC channel associated with that pin
*/
tccConfiguration getTCCChannelNr(int pin, EPioType peripheral) {
tccConfiguration result;
result.pin = pin;
result.peripheral = peripheral;
result.tcc.tccn = -2;
result.tcc.chan = -2;
const PinDescription& pinDesc = g_APinDescription[pin];
struct wo_association& association = getWOAssociation(pinDesc.ulPort, pinDesc.ulPin);
if (association.port==NOT_A_PORT)
return result; // could not find the port/pin
if (peripheral==PIO_TIMER) {
result.tcc.chaninfo = association.tccE;
result.wo = association.woE;
}
else if (peripheral==PIO_TIMER_ALT) {
result.tcc.chaninfo = association.tccF;
result.wo = association.woF;
}
#if defined(_SAMD51_)||defined(_SAME51_)
else if (peripheral==PIO_TCC_PDEC) {
result.tcc.chaninfo = association.tccG;
result.wo = association.woG;
}
#endif
return result;
}
bool checkPeripheralPermutationSameTCC6(tccConfiguration& pinAh, tccConfiguration& pinAl, tccConfiguration& pinBh, tccConfiguration& pinBl, tccConfiguration& pinCh, tccConfiguration& pinCl) {
if (inUse(pinAh) || inUse(pinAl) || inUse(pinBh) || inUse(pinBl) || inUse(pinCh) || inUse(pinCl))
return false;
if (pinAh.tcc.tccn<0 || pinAh.tcc.tccn!=pinAl.tcc.tccn || pinAh.tcc.tccn!=pinBh.tcc.tccn || pinAh.tcc.tccn!=pinBl.tcc.tccn
|| pinAh.tcc.tccn!=pinCh.tcc.tccn || pinAh.tcc.tccn!=pinCl.tcc.tccn || pinAh.tcc.tccn>=TCC_INST_NUM)
return false;
if (pinAh.tcc.chan==pinBh.tcc.chan || pinAh.tcc.chan==pinBl.tcc.chan || pinAh.tcc.chan==pinCh.tcc.chan || pinAh.tcc.chan==pinCl.tcc.chan)
return false;
if (pinBh.tcc.chan==pinCh.tcc.chan || pinBh.tcc.chan==pinCl.tcc.chan)
return false;
if (pinAl.tcc.chan==pinBh.tcc.chan || pinAl.tcc.chan==pinBl.tcc.chan || pinAl.tcc.chan==pinCh.tcc.chan || pinAl.tcc.chan==pinCl.tcc.chan)
return false;
if (pinBl.tcc.chan==pinCh.tcc.chan || pinBl.tcc.chan==pinCl.tcc.chan)
return false;
if (pinAh.tcc.chan!=pinAl.tcc.chan || pinBh.tcc.chan!=pinBl.tcc.chan || pinCh.tcc.chan!=pinCl.tcc.chan)
return false;
if (pinAh.wo==pinAl.wo || pinBh.wo==pinBl.wo || pinCh.wo!=pinCl.wo)
return false;
return true;
}
bool checkPeripheralPermutationCompatible(tccConfiguration pins[], uint8_t num) {
for (int i=0;i<num;i++)
if (pins[i].tcc.tccn<0)
return false;
for (int i=0;i<num-1;i++)
for (int j=i+1;j<num;j++)
if (pins[i].tcc.chaninfo==pins[j].tcc.chaninfo)
return false;
for (int i=0;i<num;i++)
if (inUse(pins[i]))
return false;
return true;
}
bool checkPeripheralPermutationCompatible6(tccConfiguration& pinAh, tccConfiguration& pinAl, tccConfiguration& pinBh, tccConfiguration& pinBl, tccConfiguration& pinCh, tccConfiguration& pinCl) {
// check we're valid PWM pins
if (pinAh.tcc.tccn<0 || pinAl.tcc.tccn<0 || pinBh.tcc.tccn<0 || pinBl.tcc.tccn<0 || pinCh.tcc.tccn<0 || pinCl.tcc.tccn<0)
return false;
// only TCC units for 6-PWM
if (pinAh.tcc.tccn>=TCC_INST_NUM || pinAl.tcc.tccn>=TCC_INST_NUM || pinBh.tcc.tccn>=TCC_INST_NUM
|| pinBl.tcc.tccn>=TCC_INST_NUM || pinCh.tcc.tccn>=TCC_INST_NUM || pinCl.tcc.tccn>=TCC_INST_NUM)
return false;
// check we're not in use
if (inUse(pinAh) || inUse(pinAl) || inUse(pinBh) || inUse(pinBl) || inUse(pinCh) || inUse(pinCl))
return false;
// check pins are all different tccs/channels
if (pinAh.tcc.chaninfo==pinBh.tcc.chaninfo || pinAh.tcc.chaninfo==pinBl.tcc.chaninfo || pinAh.tcc.chaninfo==pinCh.tcc.chaninfo || pinAh.tcc.chaninfo==pinCl.tcc.chaninfo)
return false;
if (pinAl.tcc.chaninfo==pinBh.tcc.chaninfo || pinAl.tcc.chaninfo==pinBl.tcc.chaninfo || pinAl.tcc.chaninfo==pinCh.tcc.chaninfo || pinAl.tcc.chaninfo==pinCl.tcc.chaninfo)
return false;
if (pinBh.tcc.chaninfo==pinCh.tcc.chaninfo || pinBh.tcc.chaninfo==pinCl.tcc.chaninfo)
return false;
if (pinBl.tcc.chaninfo==pinCh.tcc.chaninfo || pinBl.tcc.chaninfo==pinCl.tcc.chaninfo)
return false;
// check H/L pins are on same timer
if (pinAh.tcc.tccn!=pinAl.tcc.tccn || pinBh.tcc.tccn!=pinBl.tcc.tccn || pinCh.tcc.tccn!=pinCl.tcc.tccn)
return false;
// check H/L pins aren't on both the same timer and wo
if (pinAh.wo==pinAl.wo || pinBh.wo==pinBl.wo || pinCh.wo==pinCl.wo)
return false;
return true;
}
int checkHardware6PWM(const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
for (int i=0;i<64;i++) {
tccConfiguration pinAh = getTCCChannelNr(pinA_h, getPeripheralOfPermutation(i, 0));
tccConfiguration pinAl = getTCCChannelNr(pinA_l, getPeripheralOfPermutation(i, 1));
tccConfiguration pinBh = getTCCChannelNr(pinB_h, getPeripheralOfPermutation(i, 2));
tccConfiguration pinBl = getTCCChannelNr(pinB_l, getPeripheralOfPermutation(i, 3));
tccConfiguration pinCh = getTCCChannelNr(pinC_h, getPeripheralOfPermutation(i, 4));
tccConfiguration pinCl = getTCCChannelNr(pinC_l, getPeripheralOfPermutation(i, 5));
if (checkPeripheralPermutationSameTCC6(pinAh, pinAl, pinBh, pinBl, pinCh, pinCl))
return i;
}
return -1;
}
int checkSoftware6PWM(const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
for (int i=0;i<64;i++) {
tccConfiguration pinAh = getTCCChannelNr(pinA_h, getPeripheralOfPermutation(i, 0));
tccConfiguration pinAl = getTCCChannelNr(pinA_l, getPeripheralOfPermutation(i, 1));
tccConfiguration pinBh = getTCCChannelNr(pinB_h, getPeripheralOfPermutation(i, 2));
tccConfiguration pinBl = getTCCChannelNr(pinB_l, getPeripheralOfPermutation(i, 3));
tccConfiguration pinCh = getTCCChannelNr(pinC_h, getPeripheralOfPermutation(i, 4));
tccConfiguration pinCl = getTCCChannelNr(pinC_l, getPeripheralOfPermutation(i, 5));
if (checkPeripheralPermutationCompatible6(pinAh, pinAl, pinBh, pinBl, pinCh, pinCl))
return i;
}
return -1;
}
int scorePermutation(tccConfiguration pins[], uint8_t num) {
uint32_t usedtccs = 0;
for (int i=0;i<num;i++)
usedtccs |= (1<<pins[i].tcc.tccn);
int score = 0;
for (int i=0;i<TCC_INST_NUM;i++){
if (usedtccs&0x1)
score+=1;
usedtccs = usedtccs>>1;
}
for (int i=0;i<TC_INST_NUM;i++){
if (usedtccs&0x1)
score+=(num+1);
usedtccs = usedtccs>>1;
}
return score;
}
int checkPermutations(uint8_t num, int pins[], bool (*checkFunc)(tccConfiguration[], uint8_t) ) {
tccConfiguration tccConfs[num];
int best = -1;
int bestscore = 1000000;
for (int i=0;i<(0x1<<num);i++) {
for (int j=0;j<num;j++)
tccConfs[j] = getTCCChannelNr(pins[j], getPeripheralOfPermutation(i, j));
if (checkFunc(tccConfs, num)) {
int score = scorePermutation(tccConfs, num);
if (score<bestscore) {
bestscore = score;
best = i;
}
}
}
return best;
}
/**
* Configuring PWM frequency, resolution and alignment
* - Stepper driver - 2PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pinA pinA bldc driver
* @param pinB pinB bldc driver
*/
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
#ifdef SIMPLEFOC_SAMD_DEBUG
printAllPinInfos();
#endif
int pins[2] = { pinA, pinB };
int compatibility = checkPermutations(2, pins, checkPeripheralPermutationCompatible);
if (compatibility<0) {
// no result!
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Bad combination!");
#endif
return;
}
tccConfiguration tccConfs[2] = { getTCCChannelNr(pinA, getPeripheralOfPermutation(compatibility, 0)),
getTCCChannelNr(pinB, getPeripheralOfPermutation(compatibility, 1)) };
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Found configuration: (score=");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(scorePermutation(tccConfs, 2));
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(")");
printTCCConfiguration(tccConfs[0]);
printTCCConfiguration(tccConfs[1]);
#endif
// attach pins to timer peripherals
attachTCC(tccConfs[0]); // in theory this can fail, but there is no way to signal it...
attachTCC(tccConfs[1]);
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Attached pins...");
#endif
// set up clock - Note: if we did this right it should be possible to get all TCC units synchronized?
// e.g. attach all the timers, start them, and then start the clock... but this would require API-changes in SimpleFOC...
configureSAMDClock();
if (pwm_frequency==NOT_SET) {
// use default frequency
pwm_frequency = SIMPLEFOC_SAMD_DEFAULT_PWM_FREQUENCY_HZ;
}
// configure the TCC (waveform, top-value, pre-scaler = frequency)
configureTCC(tccConfs[0], pwm_frequency);
configureTCC(tccConfs[1], pwm_frequency);
getTccPinConfiguration(pinA)->pwm_res = tccConfs[0].pwm_res;
getTccPinConfiguration(pinB)->pwm_res = tccConfs[1].pwm_res;
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured TCCs...");
#endif
return; // Someone with a stepper-setup who can test it?
}
/**
* Configuring PWM frequency, resolution and alignment
* - BLDC driver - 3PWM setting
* - hardware specific
*
* SAMD21 will support up to 2 BLDC motors in 3-PWM:
* one on TCC0 using 3 of the channels 0,1,2 or 3
* one on TCC3 using 3 of the channels 0,1,2 or 3
* i.e. 8 different pins can be used, but only 4 different signals (WO[x]) on those 8 pins
* WO[0] and WO[4] are the same
* WO[1] and WO[5] are the same
* WO[2] and WO[6] are the same
* WO[3] and WO[7] are the same
*
* If you're on the Arduino Nano33 IoT, please see the Nano33 IoT pinout diagram to see which TCC0/WO[x]
* signal is on which pin of the Nano. You can drive one motor on TCC0. For other boards, consult their documentation.
*
* Note:
* That's if we want to keep the signals strictly in sync.
*
* If we can accept out-of-sync PWMs on the different phases, we could drive up to 4 BLDCs in 3-PWM mode,
* using all the TCC channels. (TCC0 & TCC3 - 4 channels each, TCC1 & TCC2 - 2 channels each)
*
* All channels will use the same resolution, prescaler and clock, but they will have different start-times leading
* to misaligned signals.
*
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pinA pinA bldc driver
* @param pinB pinB bldc driver
* @param pinC pinC bldc driver
*/
void _configure3PWM(long pwm_frequency, const int pinA, const int pinB, const int pinC) {
#ifdef SIMPLEFOC_SAMD_DEBUG
printAllPinInfos();
#endif
int pins[3] = { pinA, pinB, pinC };
int compatibility = checkPermutations(3, pins, checkPeripheralPermutationCompatible);
if (compatibility<0) {
// no result!
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Bad combination!");
#endif
return;
}
tccConfiguration tccConfs[3] = { getTCCChannelNr(pinA, getPeripheralOfPermutation(compatibility, 0)),
getTCCChannelNr(pinB, getPeripheralOfPermutation(compatibility, 1)),
getTCCChannelNr(pinC, getPeripheralOfPermutation(compatibility, 2)) };
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Found configuration: (score=");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(scorePermutation(tccConfs, 3));
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(")");
printTCCConfiguration(tccConfs[0]);
printTCCConfiguration(tccConfs[1]);
printTCCConfiguration(tccConfs[2]);
#endif
// attach pins to timer peripherals
attachTCC(tccConfs[0]); // in theory this can fail, but there is no way to signal it...
attachTCC(tccConfs[1]);
attachTCC(tccConfs[2]);
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Attached pins...");
#endif
// set up clock - Note: if we did this right it should be possible to get all TCC units synchronized?
// e.g. attach all the timers, start them, and then start the clock... but this would require API-changes in SimpleFOC...
configureSAMDClock();
if (pwm_frequency==NOT_SET) {
// use default frequency
pwm_frequency = SIMPLEFOC_SAMD_DEFAULT_PWM_FREQUENCY_HZ;
}
// configure the TCC (waveform, top-value, pre-scaler = frequency)
configureTCC(tccConfs[0], pwm_frequency);
configureTCC(tccConfs[1], pwm_frequency);
configureTCC(tccConfs[2], pwm_frequency);
getTccPinConfiguration(pinA)->pwm_res = tccConfs[0].pwm_res;
getTccPinConfiguration(pinB)->pwm_res = tccConfs[1].pwm_res;
getTccPinConfiguration(pinC)->pwm_res = tccConfs[2].pwm_res;
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured TCCs...");
#endif
}
/**
* Configuring PWM frequency, resolution and alignment
* - Stepper driver - 4PWM setting
* - hardware specific
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param pin1A pin1A stepper driver
* @param pin1B pin1B stepper driver
* @param pin2A pin2A stepper driver
* @param pin2B pin2B stepper driver
*/
void _configure4PWM(long pwm_frequency, const int pin1A, const int pin1B, const int pin2A, const int pin2B) {
#ifdef SIMPLEFOC_SAMD_DEBUG
printAllPinInfos();
#endif
int pins[4] = { pin1A, pin1B, pin2A, pin2B };
int compatibility = checkPermutations(4, pins, checkPeripheralPermutationCompatible);
if (compatibility<0) {
// no result!
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Bad combination!");
#endif
return;
}
tccConfiguration tccConfs[4] = { getTCCChannelNr(pin1A, getPeripheralOfPermutation(compatibility, 0)),
getTCCChannelNr(pin1B, getPeripheralOfPermutation(compatibility, 1)),
getTCCChannelNr(pin2A, getPeripheralOfPermutation(compatibility, 2)),
getTCCChannelNr(pin2B, getPeripheralOfPermutation(compatibility, 3)) };
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Found configuration: (score=");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(scorePermutation(tccConfs, 4));
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(")");
printTCCConfiguration(tccConfs[0]);
printTCCConfiguration(tccConfs[1]);
printTCCConfiguration(tccConfs[2]);
printTCCConfiguration(tccConfs[3]);
#endif
// attach pins to timer peripherals
attachTCC(tccConfs[0]); // in theory this can fail, but there is no way to signal it...
attachTCC(tccConfs[1]);
attachTCC(tccConfs[2]);
attachTCC(tccConfs[3]);
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Attached pins...");
#endif
// set up clock - Note: if we did this right it should be possible to get all TCC units synchronized?
// e.g. attach all the timers, start them, and then start the clock... but this would require API-changes in SimpleFOC...
configureSAMDClock();
if (pwm_frequency==NOT_SET) {
// use default frequency
pwm_frequency = SIMPLEFOC_SAMD_DEFAULT_PWM_FREQUENCY_HZ;
}
// configure the TCC (waveform, top-value, pre-scaler = frequency)
configureTCC(tccConfs[0], pwm_frequency);
configureTCC(tccConfs[1], pwm_frequency);
configureTCC(tccConfs[2], pwm_frequency);
configureTCC(tccConfs[3], pwm_frequency);
getTccPinConfiguration(pin1A)->pwm_res = tccConfs[0].pwm_res;
getTccPinConfiguration(pin2A)->pwm_res = tccConfs[1].pwm_res;
getTccPinConfiguration(pin1B)->pwm_res = tccConfs[2].pwm_res;
getTccPinConfiguration(pin2B)->pwm_res = tccConfs[3].pwm_res;
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured TCCs...");
#endif
return; // Someone with a stepper-setup who can test it?
}
/**
* Configuring PWM frequency, resolution and alignment
* - BLDC driver - 6PWM setting
* - hardware specific
*
* SAMD21 will support up to 2 BLDC motors in 6-PWM:
* one on TCC0 using 3 of the channels 0,1,2 or 3
* one on TCC3 using 3 of the channels 0,1,2 or 3
* i.e. 6 out of 8 pins must be used, in the following high/low side pairs:
* WO[0] & WO[4] (high side & low side)
* WO[1] & WO[5]
* WO[2] & WO[6]
* WO[3] & WO[7]
*
* If you're on the Arduino Nano33 IoT, please see the Nano33 IoT pinout diagram to see which TCC0/WO[x]
* signal is on which pin of the Nano. You can drive 1 BLDC on TCC0. For other boards, consult their documentation.
*
*
* @param pwm_frequency - frequency in hertz - if applicable
* @param dead_zone duty cycle protection zone [0, 1] - both low and high side low - if applicable
* @param pinA_h pinA high-side bldc driver
* @param pinA_l pinA low-side bldc driver
* @param pinB_h pinA high-side bldc driver
* @param pinB_l pinA low-side bldc driver
* @param pinC_h pinA high-side bldc driver
* @param pinC_l pinA low-side bldc driver
*
* @return 0 if config good, -1 if failed
*/
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) {
// we want to use a TCC channel with 1 non-inverted and 1 inverted output for each phase, with dead-time insertion
#ifdef SIMPLEFOC_SAMD_DEBUG
printAllPinInfos();
#endif
int compatibility = checkHardware6PWM(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
if (compatibility<0) {
compatibility = checkSoftware6PWM(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
if (compatibility<0) {
// no result!
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Bad combination!");
#endif
return -1;
}
}
tccConfiguration pinAh = getTCCChannelNr(pinA_h, getPeripheralOfPermutation(compatibility, 0));
tccConfiguration pinAl = getTCCChannelNr(pinA_l, getPeripheralOfPermutation(compatibility, 1));
tccConfiguration pinBh = getTCCChannelNr(pinB_h, getPeripheralOfPermutation(compatibility, 2));
tccConfiguration pinBl = getTCCChannelNr(pinB_l, getPeripheralOfPermutation(compatibility, 3));
tccConfiguration pinCh = getTCCChannelNr(pinC_h, getPeripheralOfPermutation(compatibility, 4));
tccConfiguration pinCl = getTCCChannelNr(pinC_l, getPeripheralOfPermutation(compatibility, 5));
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Found configuration: ");
printTCCConfiguration(pinAh);
printTCCConfiguration(pinAl);
printTCCConfiguration(pinBh);
printTCCConfiguration(pinBl);
printTCCConfiguration(pinCh);
printTCCConfiguration(pinCl);
#endif
// attach pins to timer peripherals
bool allAttached = true;
allAttached |= attachTCC(pinAh); // in theory this can fail, but there is no way to signal it...
allAttached |= attachTCC(pinAl);
allAttached |= attachTCC(pinBh);
allAttached |= attachTCC(pinBl);
allAttached |= attachTCC(pinCh);
allAttached |= attachTCC(pinCl);
if (!allAttached)
return -1;
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Attached pins...");
#endif
// set up clock - if we did this right it should be possible to get all TCC units synchronized?
// e.g. attach all the timers, start them, and then start the clock... but this would require API changes in SimpleFOC driver API
configureSAMDClock();
if (pwm_frequency==NOT_SET) {
// use default frequency
pwm_frequency = SIMPLEFOC_SAMD_DEFAULT_PWM_FREQUENCY_HZ;
}
// configure the TCC(s)
configureTCC(pinAh, pwm_frequency, false, (pinAh.tcc.chaninfo==pinAl.tcc.chaninfo)?dead_zone:-1);
if ((pinAh.tcc.chaninfo!=pinAl.tcc.chaninfo))
configureTCC(pinAl, pwm_frequency, true, -1.0);
configureTCC(pinBh, pwm_frequency, false, (pinBh.tcc.chaninfo==pinBl.tcc.chaninfo)?dead_zone:-1);
if ((pinBh.tcc.chaninfo!=pinBl.tcc.chaninfo))
configureTCC(pinBl, pwm_frequency, true, -1.0);
configureTCC(pinCh, pwm_frequency, false, (pinCh.tcc.chaninfo==pinCl.tcc.chaninfo)?dead_zone:-1);
if ((pinCh.tcc.chaninfo!=pinCl.tcc.chaninfo))
configureTCC(pinCl, pwm_frequency, true, -1.0);
getTccPinConfiguration(pinA_h)->pwm_res = pinAh.pwm_res;
getTccPinConfiguration(pinA_l)->pwm_res = pinAh.pwm_res; // use the high phase resolution, in case we didn't set it
getTccPinConfiguration(pinB_h)->pwm_res = pinBh.pwm_res;
getTccPinConfiguration(pinB_l)->pwm_res = pinBh.pwm_res;
getTccPinConfiguration(pinC_h)->pwm_res = pinCh.pwm_res;
getTccPinConfiguration(pinC_l)->pwm_res = pinCh.pwm_res;
#ifdef SIMPLEFOC_SAMD_DEBUG
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("Configured TCCs...");
#endif
return 0;
}
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - Stepper driver - 2PWM setting
* - hardware specific
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param pinA phase A hardware pin number
* @param pinB phase B hardware pin number
*/
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB) {
tccConfiguration* tccI = getTccPinConfiguration(pinA);
writeSAMDDutyCycle(tccI, dc_a);
tccI = getTccPinConfiguration(pinB);
writeSAMDDutyCycle(tccI, dc_b);
return;
}
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - BLDC driver - 3PWM setting
* - hardware specific
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param dc_c duty cycle phase C [0, 1]
* @param pinA phase A hardware pin number
* @param pinB phase B hardware pin number
* @param pinC phase C hardware pin number
*/
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC) {
tccConfiguration* tccI = getTccPinConfiguration(pinA);
writeSAMDDutyCycle(tccI, dc_a);
tccI = getTccPinConfiguration(pinB);
writeSAMDDutyCycle(tccI, dc_b);
tccI = getTccPinConfiguration(pinC);
writeSAMDDutyCycle(tccI, dc_c);
return;
}
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - Stepper driver - 4PWM setting
* - hardware specific
*
* @param dc_1a duty cycle phase 1A [0, 1]
* @param dc_1b duty cycle phase 1B [0, 1]
* @param dc_2a duty cycle phase 2A [0, 1]
* @param dc_2b duty cycle phase 2B [0, 1]
* @param pin1A phase 1A hardware pin number
* @param pin1B phase 1B hardware pin number
* @param pin2A phase 2A hardware pin number
* @param pin2B phase 2B hardware pin number
*/
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
tccConfiguration* tccI = getTccPinConfiguration(pin1A);
writeSAMDDutyCycle(tccI, dc_1a);
tccI = getTccPinConfiguration(pin2A);
writeSAMDDutyCycle(tccI, dc_2a);
tccI = getTccPinConfiguration(pin1B);
writeSAMDDutyCycle(tccI, dc_1b);
tccI = getTccPinConfiguration(pin2B);
writeSAMDDutyCycle(tccI, dc_2b);
return;
}
/**
* Function setting the duty cycle to the pwm pin (ex. analogWrite())
* - BLDC driver - 6PWM setting
* - hardware specific
*
* Note: dead-time must be setup in advance, so parameter "dead_zone" is ignored
* the low side pins are automatically driven by the SAMD DTI module, so it is enough to set the high-side
* duty cycle.
* No sanity checks are perfomed to ensure the pinA, pinB, pinC are the same pins you used in configure method...
* so use appropriately.
*
* @param dc_a duty cycle phase A [0, 1]
* @param dc_b duty cycle phase B [0, 1]
* @param dc_c duty cycle phase C [0, 1]
* @param dead_zone duty cycle protection zone [0, 1] - both low and high side low
* @param pinA_h phase A high-side hardware pin number
* @param pinA_l phase A low-side hardware pin number
* @param pinB_h phase B high-side hardware pin number
* @param pinB_l phase B low-side hardware pin number
* @param pinC_h phase C high-side hardware pin number
* @param pinC_l phase C low-side hardware pin number
*
*/
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
tccConfiguration* tcc1 = getTccPinConfiguration(pinA_h);
tccConfiguration* tcc2 = getTccPinConfiguration(pinA_l);
if (tcc1->tcc.chaninfo!=tcc2->tcc.chaninfo) {
// low-side on a different pin of same TCC - do dead-time in software...
float ls = dc_a+(dead_zone*(SIMPLEFOC_SAMD_PWM_RESOLUTION-1));
if (ls>1.0) ls = 1.0f; // no off-time is better than too-short dead-time
writeSAMDDutyCycle(tcc1, dc_a);
writeSAMDDutyCycle(tcc2, ls);
}
else
writeSAMDDutyCycle(tcc1, dc_a); // dead-time is done is hardware, no need to set low side pin explicitly
tcc1 = getTccPinConfiguration(pinB_h);
tcc2 = getTccPinConfiguration(pinB_l);
if (tcc1->tcc.chaninfo!=tcc2->tcc.chaninfo) {
float ls = dc_b+(dead_zone*(SIMPLEFOC_SAMD_PWM_RESOLUTION-1));
if (ls>1.0) ls = 1.0f; // no off-time is better than too-short dead-time
writeSAMDDutyCycle(tcc1, dc_b);
writeSAMDDutyCycle(tcc2, ls);
}
else
writeSAMDDutyCycle(tcc1, dc_b);
tcc1 = getTccPinConfiguration(pinC_h);
tcc2 = getTccPinConfiguration(pinC_l);
if (tcc1->tcc.chaninfo!=tcc2->tcc.chaninfo) {
float ls = dc_c+(dead_zone*(SIMPLEFOC_SAMD_PWM_RESOLUTION-1));
if (ls>1.0) ls = 1.0f; // no off-time is better than too-short dead-time
writeSAMDDutyCycle(tcc1, dc_c);
writeSAMDDutyCycle(tcc2, ls);
}
else
writeSAMDDutyCycle(tcc1, dc_c);
return;
}
#ifdef SIMPLEFOC_SAMD_DEBUG
/**
* Prints a table of pin assignments for your SAMD MCU. Very useful since the
* board pinout descriptions and variant.cpp are usually quite wrong, and this
* saves you hours of cross-referencing with the datasheet.
*/
void printAllPinInfos() {
SIMPLEFOC_SAMD_DEBUG_SERIAL.println();
for (uint8_t pin=0;pin<PINS_COUNT;pin++) {
const PinDescription& pinDesc = g_APinDescription[pin];
wo_association& association = getWOAssociation(pinDesc.ulPort, pinDesc.ulPin);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("Pin ");
if (pin<10) SIMPLEFOC_SAMD_DEBUG_SERIAL.print("0");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(pin);
switch (pinDesc.ulPort) {
case NOT_A_PORT: SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" "); break;
case PORTA: SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" PA"); break;
case PORTB: SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" PB"); break;
case PORTC: SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" PC"); break;
#if defined(_SAMD51_)||defined(_SAME51_)
case PORTD: SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" PD"); break;
#endif
}
if (pinDesc.ulPin <10) SIMPLEFOC_SAMD_DEBUG_SERIAL.print("0");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(pinDesc.ulPin);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" E=");
if (association.tccE>=0) {
int tcn = GetTCNumber(association.tccE);
if (tcn>=TCC_INST_NUM)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" TC");
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("TCC");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tcn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.tccE));
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.woE));
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("]");
if (tcn<10)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" ");
}
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" None ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" F=");
if (association.tccF>=0) {
int tcn = GetTCNumber(association.tccF);
if (tcn>=TCC_INST_NUM)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" TC");
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("TCC");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tcn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.tccF));
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.woF));
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("]");
if (tcn<10)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" ");
}
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" None ");
#if defined(_SAMD51_)||defined(_SAME51_)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" G=");
if (association.tccG>=0) {
int tcn = GetTCNumber(association.tccG);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("TCC");
if (tcn<10)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" ");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(tcn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.tccG));
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(GetTCChannelNumber(association.woG));
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("]");
}
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(" None ");
#else
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("");
#endif
}
SIMPLEFOC_SAMD_DEBUG_SERIAL.println();
}
void printTCCConfiguration(tccConfiguration& info) {
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(info.pin);
if (info.tcc.tccn>=TCC_INST_NUM)
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(": TC Peripheral");
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(": TCC Peripheral");
switch (info.peripheral) {
case PIO_TIMER:
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" E "); break;
case PIO_TIMER_ALT:
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" F "); break;
#if defined(_SAMD51_)||defined(_SAME51_)
case PIO_TCC_PDEC:
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" G "); break;
#endif
default:
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(" ? "); break;
}
if (info.tcc.tccn>=0) {
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(info.tcc.tccn);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("-");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(info.tcc.chan);
SIMPLEFOC_SAMD_DEBUG_SERIAL.print("[");
SIMPLEFOC_SAMD_DEBUG_SERIAL.print(info.wo);
SIMPLEFOC_SAMD_DEBUG_SERIAL.println("]");
}
else
SIMPLEFOC_SAMD_DEBUG_SERIAL.println(" None");
}
#endif
#endif
@@ -0,0 +1,120 @@
#ifndef SAMD_MCU_H
#define SAMD_MCU_H
// uncomment to enable debug output to Serial port
// #define SIMPLEFOC_SAMD_DEBUG
#if !defined(SIMPLEFOC_SAMD_DEBUG_SERIAL)
#define SIMPLEFOC_SAMD_DEBUG_SERIAL Serial
#endif
#include "../hardware_api.h"
#if defined(__SAME51J19A__) || defined(__ATSAME51J19A__)
#ifndef _SAME51_
#define _SAME51_
#endif
#endif
#if defined(_SAMD21_)||defined(_SAMD51_)||defined(_SAME51_)
#include "Arduino.h"
#include "variant.h"
#include "wiring_private.h"
#ifndef SIMPLEFOC_SAMD_PWM_RESOLUTION
#define SIMPLEFOC_SAMD_PWM_RESOLUTION 1000
#endif
#define SIMPLEFOC_SAMD_DEFAULT_PWM_FREQUENCY_HZ 24000
// arbitrary maximum. On SAMD51 with 120MHz clock this means 2kHz minimum pwm frequency
#define SIMPLEFOC_SAMD_MAX_PWM_RESOLUTION 30000
// lets not go too low - 400 with clock speed of 120MHz on SAMD51 means 150kHz maximum PWM frequency...
// 400 with 48MHz clock on SAMD21 means 60kHz maximum PWM frequency...
#define SIMPLEFOC_SAMD_MIN_PWM_RESOLUTION 400
// this is the most we can support on the TC units
#define SIMPLEFOC_SAMD_PWM_TC_RESOLUTION 250
#ifndef SIMPLEFOC_SAMD_MAX_TCC_PINCONFIGURATIONS
#define SIMPLEFOC_SAMD_MAX_TCC_PINCONFIGURATIONS 24
#endif
struct tccConfiguration {
uint8_t pin;
EPioType peripheral; // 1=true, 0=false
uint8_t wo;
union tccChanInfo {
struct {
int8_t chan;
int8_t tccn;
};
uint16_t chaninfo;
} tcc;
uint16_t pwm_res;
};
struct wo_association {
EPortType port;
uint32_t pin;
ETCChannel tccE;
uint8_t woE;
ETCChannel tccF;
uint8_t woF;
#if defined(_SAMD51_)||defined(_SAME51_)
ETCChannel tccG;
uint8_t woG;
#endif
};
#if defined(_SAMD21_)
#define NUM_PIO_TIMER_PERIPHERALS 2
#elif defined(_SAMD51_)||defined(_SAME51_)
#define NUM_PIO_TIMER_PERIPHERALS 3
#endif
/**
* Global state
*/
extern struct wo_association WO_associations[];
extern uint8_t TCC_CHANNEL_COUNT[];
extern tccConfiguration tccPinConfigurations[SIMPLEFOC_SAMD_MAX_TCC_PINCONFIGURATIONS];
extern uint8_t numTccPinConfigurations;
extern bool SAMDClockConfigured;
extern bool tccConfigured[TCC_INST_NUM+TC_INST_NUM];
struct wo_association& getWOAssociation(EPortType port, uint32_t pin);
void writeSAMDDutyCycle(tccConfiguration* info, float dc);
void configureSAMDClock();
void configureTCC(tccConfiguration& tccConfig, long pwm_frequency, bool negate=false, float hw6pwm=-1);
__inline__ void syncTCC(Tcc* TCCx) __attribute__((always_inline, unused));
EPioType getPeripheralOfPermutation(int permutation, int pin_position);
#ifdef SIMPLEFOC_SAMD_DEBUG
void printTCCConfiguration(tccConfiguration& info);
void printAllPinInfos();
#endif
#endif
#endif
@@ -0,0 +1,355 @@
#include "../hardware_api.h"
#if defined(_STM32_DEF_)
// default pwm parameters
#define _PWM_RESOLUTION 12 // 12bit
#define _PWM_RANGE 4095.0 // 2^12 -1 = 4095
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 50000 // 50khz
// 6pwm parameters
#define _HARDWARE_6PWM 1
#define _SOFTWARE_6PWM 0
#define _ERROR_6PWM -1
// setting pwm to hardware pin - instead analogWrite()
void _setPwm(int ulPin, uint32_t value, int resolution)
{
PinName pin = digitalPinToPinName(ulPin);
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM);
uint32_t index = get_timer_index(Instance);
HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
HT->setCaptureCompare(channel, value, (TimerCompareFormat_t)resolution);
}
// init pin pwm
HardwareTimer* _initPinPWM(uint32_t PWM_freq, int ulPin)
{
PinName pin = digitalPinToPinName(ulPin);
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM);
uint32_t index = get_timer_index(Instance);
if (HardwareTimer_Handle[index] == NULL) {
HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM));
HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
HAL_TIM_Base_Init(&(HardwareTimer_Handle[index]->handle));
}
HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
HT->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin);
HT->setOverflow(PWM_freq, HERTZ_FORMAT);
HT->pause();
HT->refresh();
return HT;
}
// init high side pin
HardwareTimer* _initPinPWMHigh(uint32_t PWM_freq, int ulPin)
{
return _initPinPWM(PWM_freq, ulPin);
}
// init low side pin
HardwareTimer* _initPinPWMLow(uint32_t PWM_freq, int ulPin)
{
PinName pin = digitalPinToPinName(ulPin);
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM);
uint32_t index = get_timer_index(Instance);
if (HardwareTimer_Handle[index] == NULL) {
HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(pin, PinMap_PWM));
HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
HAL_TIM_Base_Init(&(HardwareTimer_Handle[index]->handle));
TIM_OC_InitTypeDef sConfigOC = TIM_OC_InitTypeDef();
sConfigOC.OCMode = TIM_OCMODE_PWM2;
sConfigOC.Pulse = 100;
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
#if defined(TIM_OCIDLESTATE_SET)
sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
#endif
#if defined(TIM_OCNIDLESTATE_RESET)
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
#endif
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
HAL_TIM_PWM_ConfigChannel(&(HardwareTimer_Handle[index]->handle), &sConfigOC, channel);
}
HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin, PinMap_PWM));
HT->setMode(channel, TIMER_OUTPUT_COMPARE_PWM2, pin);
HT->setOverflow(PWM_freq, HERTZ_FORMAT);
HT->pause();
HT->refresh();
return HT;
}
// align the timers to end the init
void _alignPWMTimers(HardwareTimer *HT1,HardwareTimer *HT2,HardwareTimer *HT3)
{
HT1->pause();
HT1->refresh();
HT2->pause();
HT2->refresh();
HT3->pause();
HT3->refresh();
HT1->resume();
HT2->resume();
HT3->resume();
}
// align the timers to end the init
void _alignPWMTimers(HardwareTimer *HT1,HardwareTimer *HT2,HardwareTimer *HT3,HardwareTimer *HT4)
{
HT1->pause();
HT1->refresh();
HT2->pause();
HT2->refresh();
HT3->pause();
HT3->refresh();
HT4->pause();
HT4->refresh();
HT1->resume();
HT2->resume();
HT3->resume();
HT4->resume();
}
// configure hardware 6pwm interface only one timer with inverted channels
HardwareTimer* _initHardware6PWMInterface(uint32_t PWM_freq, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l)
{
#if !defined(STM32L0xx) // L0 boards dont have hardware 6pwm interface
PinName uhPinName = digitalPinToPinName(pinA_h);
PinName ulPinName = digitalPinToPinName(pinA_l);
PinName vhPinName = digitalPinToPinName(pinB_h);
PinName vlPinName = digitalPinToPinName(pinB_l);
PinName whPinName = digitalPinToPinName(pinC_h);
PinName wlPinName = digitalPinToPinName(pinC_l);
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(uhPinName, PinMap_PWM);
uint32_t index = get_timer_index(Instance);
if (HardwareTimer_Handle[index] == NULL) {
HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(uhPinName, PinMap_PWM));
HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
HardwareTimer_Handle[index]->handle.Init.RepetitionCounter = 1;
HAL_TIM_Base_Init(&(HardwareTimer_Handle[index]->handle));
((HardwareTimer *)HardwareTimer_Handle[index]->__this)->setOverflow(PWM_freq, HERTZ_FORMAT);
}
HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(uhPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, uhPinName);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(ulPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, ulPinName);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(vhPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, vhPinName);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(vlPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, vlPinName);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(whPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, whPinName);
HT->setMode(STM_PIN_CHANNEL(pinmap_function(wlPinName, PinMap_PWM)), TIMER_OUTPUT_COMPARE_PWM1, wlPinName);
// dead time is set in nanoseconds
uint32_t dead_time_ns = (float)(1e9f/PWM_freq)*dead_zone;
uint32_t dead_time = __LL_TIM_CALC_DEADTIME(SystemCoreClock, LL_TIM_GetClockDivision(HT->getHandle()->Instance), dead_time_ns);
LL_TIM_OC_SetDeadTime(HT->getHandle()->Instance, dead_time); // deadtime is non linear!
LL_TIM_CC_EnableChannel(HT->getHandle()->Instance, LL_TIM_CHANNEL_CH1 | LL_TIM_CHANNEL_CH1N | LL_TIM_CHANNEL_CH2 | LL_TIM_CHANNEL_CH2N | LL_TIM_CHANNEL_CH3 | LL_TIM_CHANNEL_CH3N);
// Set Trigger out for DMA transfer
LL_TIM_SetTriggerOutput(HT->getHandle()->Instance, LL_TIM_TRGO_UPDATE);
HT->pause();
HT->refresh();
// adjust the initial timer state such that the trigger for DMA transfer aligns with the pwm peaks instead of throughs.
HT->getHandle()->Instance->CR1 |= TIM_CR1_DIR;
HT->getHandle()->Instance->CNT = TIM1->ARR;
HT->resume();
return HT;
#else
return nullptr; // return nothing
#endif
}
// returns 0 if each pair of pwm channels has same channel
// returns 1 all the channels belong to the same timer - hardware inverted channels
// returns -1 if neither - avoid configuring - error!!!
int _interfaceType(const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
PinName nameAH = digitalPinToPinName(pinA_h);
PinName nameAL = digitalPinToPinName(pinA_l);
PinName nameBH = digitalPinToPinName(pinB_h);
PinName nameBL = digitalPinToPinName(pinB_l);
PinName nameCH = digitalPinToPinName(pinC_h);
PinName nameCL = digitalPinToPinName(pinC_l);
int tim1 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameAH, PinMap_PWM));
int tim2 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameAL, PinMap_PWM));
int tim3 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameBH, PinMap_PWM));
int tim4 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameBL, PinMap_PWM));
int tim5 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameCH, PinMap_PWM));
int tim6 = get_timer_index((TIM_TypeDef *)pinmap_peripheral(nameCL, PinMap_PWM));
#if defined(STM32L0xx) // L0 boards dont have hardware 6pwm interface
if(tim1 == tim2 && tim3==tim4 && tim5==tim6)
return _SOFTWARE_6PWM; // software 6pwm interface - each pair of high-low side same timer
else
return _ERROR_6PWM; // might be error avoid configuration
#else // the rest of stm32 boards
if(tim1 == tim2 && tim2==tim3 && tim3==tim4 && tim4==tim5 && tim5==tim6)
return _HARDWARE_6PWM; // hardware 6pwm interface - only on timer
else if(tim1 == tim2 && tim3==tim4 && tim5==tim6)
return _SOFTWARE_6PWM; // software 6pwm interface - each pair of high-low side same timer
else
return _ERROR_6PWM; // might be error avoid configuration
#endif
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency,const int pinA, const int pinB) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// center-aligned frequency is uses two periods
pwm_frequency *=2;
HardwareTimer* HT1 = _initPinPWM(pwm_frequency, pinA);
HardwareTimer* HT2 = _initPinPWM(pwm_frequency, pinB);
// allign the timers
_alignPWMTimers(HT1, HT2, HT2);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// center-aligned frequency is uses two periods
pwm_frequency *=2;
HardwareTimer* HT1 = _initPinPWM(pwm_frequency, pinA);
HardwareTimer* HT2 = _initPinPWM(pwm_frequency, pinB);
HardwareTimer* HT3 = _initPinPWM(pwm_frequency, pinC);
// allign the timers
_alignPWMTimers(HT1, HT2, HT3);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
// center-aligned frequency is uses two periods
pwm_frequency *=2;
HardwareTimer* HT1 = _initPinPWM(pwm_frequency, pinA);
HardwareTimer* HT2 = _initPinPWM(pwm_frequency, pinB);
HardwareTimer* HT3 = _initPinPWM(pwm_frequency, pinC);
HardwareTimer* HT4 = _initPinPWM(pwm_frequency, pinD);
// allign the timers
_alignPWMTimers(HT1, HT2, HT3, HT4);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
//- hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,4095]
_setPwm(pinA, _PWM_RANGE*dc_a, _PWM_RESOLUTION);
_setPwm(pinB, _PWM_RANGE*dc_b, _PWM_RESOLUTION);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
//- hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,4095]
_setPwm(pinA, _PWM_RANGE*dc_a, _PWM_RESOLUTION);
_setPwm(pinB, _PWM_RANGE*dc_b, _PWM_RESOLUTION);
_setPwm(pinC, _PWM_RANGE*dc_c, _PWM_RESOLUTION);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
//- hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,4095]
_setPwm(pin1A, _PWM_RANGE*dc_1a, _PWM_RESOLUTION);
_setPwm(pin1B, _PWM_RANGE*dc_1b, _PWM_RESOLUTION);
_setPwm(pin2A, _PWM_RANGE*dc_2a, _PWM_RESOLUTION);
_setPwm(pin2B, _PWM_RANGE*dc_2b, _PWM_RESOLUTION);
}
// Configuring PWM frequency, resolution and alignment
// - BLDC driver - 6PWM setting
// - hardware specific
int _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l){
if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to |%0kHz max
// center-aligned frequency is uses two periods
pwm_frequency *=2;
// find configuration
int config = _interfaceType(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
// configure accordingly
switch(config){
case _ERROR_6PWM:
return -1; // fail
break;
case _HARDWARE_6PWM:
_initHardware6PWMInterface(pwm_frequency, dead_zone, pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
break;
case _SOFTWARE_6PWM:
HardwareTimer* HT1 = _initPinPWMHigh(pwm_frequency, pinA_h);
_initPinPWMLow(pwm_frequency, pinA_l);
HardwareTimer* HT2 = _initPinPWMHigh(pwm_frequency,pinB_h);
_initPinPWMLow(pwm_frequency, pinB_l);
HardwareTimer* HT3 = _initPinPWMHigh(pwm_frequency,pinC_h);
_initPinPWMLow(pwm_frequency, pinC_l);
_alignPWMTimers(HT1, HT2, HT3);
break;
}
return 0; // success
}
// Function setting the duty cycle to the pwm pin (ex. analogWrite())
// - BLDC driver - 6PWM setting
// - hardware specific
void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, float dead_zone, int pinA_h, int pinA_l, int pinB_h, int pinB_l, int pinC_h, int pinC_l){
// find configuration
int config = _interfaceType(pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l);
// set pwm accordingly
switch(config){
case _HARDWARE_6PWM:
_setPwm(pinA_h, _PWM_RANGE*dc_a, _PWM_RESOLUTION);
_setPwm(pinB_h, _PWM_RANGE*dc_b, _PWM_RESOLUTION);
_setPwm(pinC_h, _PWM_RANGE*dc_c, _PWM_RESOLUTION);
break;
case _SOFTWARE_6PWM:
_setPwm(pinA_l, _constrain(dc_a + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
_setPwm(pinA_h, _constrain(dc_a - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
_setPwm(pinB_l, _constrain(dc_b + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
_setPwm(pinB_h, _constrain(dc_b - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
_setPwm(pinC_l, _constrain(dc_c + dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
_setPwm(pinC_h, _constrain(dc_c - dead_zone/2, 0, 1)*_PWM_RANGE, _PWM_RESOLUTION);
break;
}
}
#endif
@@ -0,0 +1,77 @@
#include "../hardware_api.h"
#if defined(__arm__) && defined(CORE_TEENSY)
#define _PWM_FREQUENCY 25000 // 25khz
#define _PWM_FREQUENCY_MAX 50000 // 50khz
// configure High PWM frequency
void _setHighFrequency(const long freq, const int pin){
analogWrite(pin, 0);
analogWriteFrequency(pin, freq);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _configure2PWM(long pwm_frequency, const int pinA, const int pinB) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
}
// function setting the high pwm frequency to the supplied pins
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _configure3PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
_setHighFrequency(pwm_frequency, pinC);
}
// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _configure4PWM(long pwm_frequency,const int pinA, const int pinB, const int pinC, const int pinD) {
if(!pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
_setHighFrequency(pwm_frequency, pinA);
_setHighFrequency(pwm_frequency, pinB);
_setHighFrequency(pwm_frequency, pinC);
_setHighFrequency(pwm_frequency, pinD);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 2PWM setting
// - hardware speciffic
void _writeDutyCycle2PWM(float dc_a, float dc_b, int pinA, int pinB){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
}
// function setting the pwm duty cycle to the hardware
// - BLDC motor - 3PWM setting
// - hardware speciffic
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, int pinA, int pinB, int pinC){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pinA, 255.0f*dc_a);
analogWrite(pinB, 255.0f*dc_b);
analogWrite(pinC, 255.0f*dc_c);
}
// function setting the pwm duty cycle to the hardware
// - Stepper motor - 4PWM setting
// - hardware speciffic
void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, int pin1A, int pin1B, int pin2A, int pin2B){
// transform duty cycle from [0,1] to [0,255]
analogWrite(pin1A, 255.0f*dc_1a);
analogWrite(pin1B, 255.0f*dc_1b);
analogWrite(pin2A, 255.0f*dc_2a);
analogWrite(pin2B, 255.0f*dc_2b);
}
#endif
+230
View File
@@ -0,0 +1,230 @@
#include "Encoder.h"
/*
Encoder(int encA, int encB , int cpr, int index)
- encA, encB - encoder A and B pins
- cpr - counts per rotation number (cpm=ppm*4)
- index pin - (optional input)
*/
Encoder::Encoder(int _encA, int _encB , float _ppr, int _index){
// Encoder measurement structure init
// hardware pins
pinA = _encA;
pinB = _encB;
// counter setup
pulse_counter = 0;
pulse_timestamp = 0;
cpr = _ppr;
A_active = 0;
B_active = 0;
I_active = 0;
// index pin
index_pin = _index; // its 0 if not used
// velocity calculation variables
prev_Th = 0;
pulse_per_second = 0;
prev_pulse_counter = 0;
prev_timestamp_us = _micros();
// extern pullup as default
pullup = Pullup::USE_EXTERN;
// enable quadrature encoder by default
quadrature = Quadrature::ON;
}
// Encoder interrupt callback functions
// A channel
void Encoder::handleA() {
bool A = digitalRead(pinA);
switch (quadrature){
case Quadrature::ON:
// CPR = 4xPPR
if ( A != A_active ) {
pulse_counter += (A_active == B_active) ? 1 : -1;
pulse_timestamp = _micros();
A_active = A;
}
break;
case Quadrature::OFF:
// CPR = PPR
if(A && !digitalRead(pinB)){
pulse_counter++;
pulse_timestamp = _micros();
}
break;
}
}
// B channel
void Encoder::handleB() {
bool B = digitalRead(pinB);
switch (quadrature){
case Quadrature::ON:
// // CPR = 4xPPR
if ( B != B_active ) {
pulse_counter += (A_active != B_active) ? 1 : -1;
pulse_timestamp = _micros();
B_active = B;
}
break;
case Quadrature::OFF:
// CPR = PPR
if(B && !digitalRead(pinA)){
pulse_counter--;
pulse_timestamp = _micros();
}
break;
}
}
// Index channel
void Encoder::handleIndex() {
if(hasIndex()){
bool I = digitalRead(index_pin);
if(I && !I_active){
index_found = true;
// align encoder on each index
long tmp = pulse_counter;
// corrent the counter value
pulse_counter = round((double)pulse_counter/(double)cpr)*cpr;
// preserve relative speed
prev_pulse_counter += pulse_counter - tmp;
}
I_active = I;
}
}
void Encoder::update() {
// do nothing for Encoder
}
/*
Shaft angle calculation
*/
float Encoder::getSensorAngle(){
return getAngle();
}
// TODO: numerical precision issue here if the pulse_counter overflows the angle will be lost
float Encoder::getMechanicalAngle(){
return _2PI * ((pulse_counter) % ((int)cpr)) / ((float)cpr);
}
float Encoder::getAngle(){
return _2PI * (pulse_counter) / ((float)cpr);
}
double Encoder::getPreciseAngle(){
return _2PI * (pulse_counter) / ((double)cpr);
}
int32_t Encoder::getFullRotations(){
return pulse_counter / (int)cpr;
}
/*
Shaft velocity calculation
function using mixed time and frequency measurement technique
*/
float Encoder::getVelocity(){
// timestamp
long timestamp_us = _micros();
// sampling time calculation
float Ts = (timestamp_us - prev_timestamp_us) * 1e-6f;
// quick fix for strange cases (micros overflow)
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// time from last impulse
float Th = (timestamp_us - pulse_timestamp) * 1e-6f;
long dN = pulse_counter - prev_pulse_counter;
// Pulse per second calculation (Eq.3.)
// dN - impulses received
// Ts - sampling time - time in between function calls
// Th - time from last impulse
// Th_1 - time form last impulse of the previous call
// only increment if some impulses received
float dt = Ts + prev_Th - Th;
pulse_per_second = (dN != 0 && dt > Ts/2) ? dN / dt : pulse_per_second;
// if more than 0.05f passed in between impulses
if ( Th > 0.1f) pulse_per_second = 0;
// velocity calculation
float velocity = pulse_per_second / ((float)cpr) * (_2PI);
// save variables for next pass
prev_timestamp_us = timestamp_us;
// save velocity calculation variables
prev_Th = Th;
prev_pulse_counter = pulse_counter;
return velocity;
}
// getter for index pin
// return -1 if no index
int Encoder::needsSearch(){
return hasIndex() && !index_found;
}
// private function used to determine if encoder has index
int Encoder::hasIndex(){
return index_pin != 0;
}
// encoder initialisation of the hardware pins
// and calculation variables
void Encoder::init(){
// Encoder - check if pullup needed for your encoder
if(pullup == Pullup::USE_INTERN){
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
if(hasIndex()) pinMode(index_pin,INPUT_PULLUP);
}else{
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
if(hasIndex()) pinMode(index_pin,INPUT);
}
// counter setup
pulse_counter = 0;
pulse_timestamp = _micros();
// velocity calculation variables
prev_Th = 0;
pulse_per_second = 0;
prev_pulse_counter = 0;
prev_timestamp_us = _micros();
// initial cpr = PPR
// change it if the mode is quadrature
if(quadrature == Quadrature::ON) cpr = 4*cpr;
// we don't call Sensor::init() here because init is handled in Encoder class.
}
// function enabling hardware interrupts of the for the callback provided
// if callback is not provided then the interrupt is not enabled
void Encoder::enableInterrupts(void (*doA)(), void(*doB)(), void(*doIndex)()){
// attach interrupt if functions provided
switch(quadrature){
case Quadrature::ON:
// A callback and B callback
if(doA != nullptr) attachInterrupt(digitalPinToInterrupt(pinA), doA, CHANGE);
if(doB != nullptr) attachInterrupt(digitalPinToInterrupt(pinB), doB, CHANGE);
break;
case Quadrature::OFF:
// A callback and B callback
if(doA != nullptr) attachInterrupt(digitalPinToInterrupt(pinA), doA, RISING);
if(doB != nullptr) attachInterrupt(digitalPinToInterrupt(pinB), doB, RISING);
break;
}
// if index used initialize the index interrupt
if(hasIndex() && doIndex != nullptr) attachInterrupt(digitalPinToInterrupt(index_pin), doIndex, CHANGE);
}
+95
View File
@@ -0,0 +1,95 @@
#ifndef ENCODER_LIB_H
#define ENCODER_LIB_H
#include "Arduino.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/base_classes/Sensor.h"
/**
* Quadrature mode configuration structure
*/
enum Quadrature : uint8_t {
ON = 0x00, //!< Enable quadrature mode CPR = 4xPPR
OFF = 0x01 //!< Disable quadrature mode / CPR = PPR
};
class Encoder: public Sensor{
public:
/**
Encoder class constructor
@param encA encoder B pin
@param encB encoder B pin
@param ppr impulses per rotation (cpr=ppr*4)
@param index index pin number (optional input)
*/
Encoder(int encA, int encB , float ppr, int index = 0);
/** encoder initialise pins */
void init() override;
/**
* function enabling hardware interrupts for the encoder channels with provided callback functions
* if callback is not provided then the interrupt is not enabled
*
* @param doA pointer to the A channel interrupt handler function
* @param doB pointer to the B channel interrupt handler function
* @param doIndex pointer to the Index channel interrupt handler function
*
*/
void enableInterrupts(void (*doA)() = nullptr, void(*doB)() = nullptr, void(*doIndex)() = nullptr);
// Encoder interrupt callback functions
/** A channel callback function */
void handleA();
/** B channel callback function */
void handleB();
/** Index channel callback function */
void handleIndex();
// pins A and B
int pinA; //!< encoder hardware pin A
int pinB; //!< encoder hardware pin B
int index_pin; //!< index pin
// Encoder configuration
Pullup pullup; //!< Configuration parameter internal or external pullups
Quadrature quadrature;//!< Configuration parameter enable or disable quadrature mode
float cpr;//!< encoder cpr number
// Abstract functions of the Sensor class implementation
/** get current angle (rad) */
float getSensorAngle() override;
float getMechanicalAngle() override;
/** get current angular velocity (rad/s) */
float getVelocity() override;
float getAngle() override;
double getPreciseAngle() override;
int32_t getFullRotations() override;
virtual void update() override;
/**
* returns 0 if it does need search for absolute zero
* 0 - encoder without index
* 1 - ecoder with index
*/
int needsSearch() override;
private:
int hasIndex(); //!< function returning 1 if encoder has index pin and 0 if not.
volatile long pulse_counter;//!< current pulse counter
volatile long pulse_timestamp;//!< last impulse timestamp in us
volatile int A_active; //!< current active states of A channel
volatile int B_active; //!< current active states of B channel
volatile int I_active; //!< current active states of Index channel
volatile bool index_found = false; //!< flag stating that the index has been found
// velocity calculation variables
float prev_Th, pulse_per_second;
volatile long prev_pulse_counter, prev_timestamp_us;
};
#endif
@@ -0,0 +1,26 @@
#include "GenericSensor.h"
/*
GenericSensor( float (*readCallback)() )
- readCallback - pointer to the function which reads the sensor angle.
*/
GenericSensor::GenericSensor(float (*readCallback)(), void (*initCallback)()){
// if function provided add it to the
if(readCallback != nullptr) this->readCallback = readCallback;
if(initCallback != nullptr) this->initCallback = initCallback;
}
void GenericSensor::init(){
// if init callback specified run it
if(initCallback != nullptr) this->initCallback();
this->Sensor::init(); // call base class init
}
/*
Shaft angle calculation
*/
float GenericSensor::getSensorAngle(){
return this->readCallback();
}
@@ -0,0 +1,31 @@
#ifndef GENERIC_SENSOR_LIB_H
#define GENERIC_SENSOR_LIB_H
#include "Arduino.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#include "../common/base_classes/Sensor.h"
class GenericSensor: public Sensor{
public:
/**
GenericSensor class constructor
* @param readCallback pointer to the function reading the sensor angle
* @param initCallback pointer to the function initialising the sensor
*/
GenericSensor(float (*readCallback)() = nullptr, void (*initCallback)() = nullptr);
float (*readCallback)() = nullptr; //!< function pointer to sensor reading
void (*initCallback)() = nullptr; //!< function pointer to sensor initialisation
void init() override;
// Abstract functions of the Sensor class implementation
/** get current angle (rad) */
float getSensorAngle() override;
};
#endif
@@ -0,0 +1,181 @@
#include "HallSensor.h"
/*
HallSensor(int hallA, int hallB , int cpr, int index)
- hallA, hallB, hallC - HallSensor A, B and C pins
- pp - pole pairs
*/
HallSensor::HallSensor(int _hallA, int _hallB, int _hallC, int _pp){
// hardware pins
pinA = _hallA;
pinB = _hallB;
pinC = _hallC;
// hall has 6 segments per electrical revolution
cpr = _pp * 6;
// extern pullup as default
pullup = Pullup::USE_EXTERN;
}
// HallSensor interrupt callback functions
// A channel
void HallSensor::handleA() {
A_active= digitalRead(pinA);
updateState();
}
// B channel
void HallSensor::handleB() {
B_active = digitalRead(pinB);
updateState();
}
// C channel
void HallSensor::handleC() {
C_active = digitalRead(pinC);
updateState();
}
/**
* Updates the state and sector following an interrupt
*/
void HallSensor::updateState() {
long new_pulse_timestamp = _micros();
int8_t new_hall_state = C_active + (B_active << 1) + (A_active << 2);
// glitch avoidance #1 - sometimes we get an interrupt but pins haven't changed
if (new_hall_state == hall_state) {
return;
}
hall_state = new_hall_state;
int8_t new_electric_sector = ELECTRIC_SECTORS[hall_state];
static Direction old_direction;
if (new_electric_sector - electric_sector > 3) {
//underflow
direction = Direction::CCW;
electric_rotations += direction;
} else if (new_electric_sector - electric_sector < (-3)) {
//overflow
direction = Direction::CW;
electric_rotations += direction;
} else {
direction = (new_electric_sector > electric_sector)? Direction::CW : Direction::CCW;
}
electric_sector = new_electric_sector;
// glitch avoidance #2 changes in direction can cause velocity spikes. Possible improvements needed in this area
if (direction == old_direction) {
// not oscilating or just changed direction
pulse_diff = new_pulse_timestamp - pulse_timestamp;
} else {
pulse_diff = 0;
}
pulse_timestamp = new_pulse_timestamp;
total_interrupts++;
old_direction = direction;
if (onSectorChange != nullptr) onSectorChange(electric_sector);
}
/**
* Optionally set a function callback to be fired when sector changes
* void onSectorChange(int sector) {
* ... // for debug or call driver directly?
* }
* sensor.attachSectorCallback(onSectorChange);
*/
void HallSensor::attachSectorCallback(void (*_onSectorChange)(int sector)) {
onSectorChange = _onSectorChange;
}
float HallSensor::getSensorAngle() {
return getAngle();
}
/*
Shaft angle calculation
TODO: numerical precision issue here if the electrical rotation overflows the angle will be lost
*/
float HallSensor::getMechanicalAngle() {
return ((float)((electric_rotations * 6 + electric_sector) % cpr) / (float)cpr) * _2PI ;
}
/*
Shaft velocity calculation
function using mixed time and frequency measurement technique
*/
float HallSensor::getVelocity(){
if (pulse_diff == 0 || ((long)(_micros() - pulse_timestamp) > pulse_diff) ) { // last velocity isn't accurate if too old
return 0;
} else {
return direction * (_2PI / (float)cpr) / (pulse_diff / 1000000.0f);
}
}
float HallSensor::getAngle() {
return ((float)(electric_rotations * 6 + electric_sector) / (float)cpr) * _2PI ;
}
double HallSensor::getPreciseAngle() {
return ((double)(electric_rotations * 6 + electric_sector) / (double)cpr) * (double)_2PI ;
}
int32_t HallSensor::getFullRotations() {
return (int32_t)((electric_rotations * 6 + electric_sector) / cpr);
}
// HallSensor initialisation of the hardware pins
// and calculation variables
void HallSensor::init(){
// initialise the electrical rotations to 0
electric_rotations = 0;
// HallSensor - check if pullup needed for your HallSensor
if(pullup == Pullup::USE_INTERN){
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
}else{
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
pinMode(pinC, INPUT);
}
// init hall_state
A_active= digitalRead(pinA);
B_active = digitalRead(pinB);
C_active = digitalRead(pinC);
updateState();
pulse_timestamp = _micros();
// we don't call Sensor::init() here because init is handled in HallSensor class.
}
// function enabling hardware interrupts for the callback provided
// if callback is not provided then the interrupt is not enabled
void HallSensor::enableInterrupts(void (*doA)(), void(*doB)(), void(*doC)()){
// attach interrupt if functions provided
// A, B and C callback
if(doA != nullptr) attachInterrupt(digitalPinToInterrupt(pinA), doA, CHANGE);
if(doB != nullptr) attachInterrupt(digitalPinToInterrupt(pinB), doB, CHANGE);
if(doC != nullptr) attachInterrupt(digitalPinToInterrupt(pinC), doC, CHANGE);
}
@@ -0,0 +1,97 @@
#ifndef HALL_SENSOR_LIB_H
#define HALL_SENSOR_LIB_H
#include "Arduino.h"
#include "../common/base_classes/Sensor.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
// seq 1 > 5 > 4 > 6 > 2 > 3 > 1 000 001 010 011 100 101 110 111
const int8_t ELECTRIC_SECTORS[8] = { -1, 0, 4, 5, 2, 1, 3 , -1 };
class HallSensor: public Sensor{
public:
/**
HallSensor class constructor
@param encA HallSensor A pin
@param encB HallSensor B pin
@param encC HallSensor C pin
@param pp pole pairs (e.g hoverboard motor has 15pp and small gimbals often have 7pp)
@param index index pin number (optional input)
*/
HallSensor(int encA, int encB, int encC, int pp);
/** HallSensor initialise pins */
void init();
/**
* function enabling hardware interrupts for the HallSensor channels with provided callback functions
* if callback is not provided then the interrupt is not enabled
*
* @param doA pointer to the A channel interrupt handler function
* @param doB pointer to the B channel interrupt handler function
* @param doIndex pointer to the Index channel interrupt handler function
*
*/
void enableInterrupts(void (*doA)() = nullptr, void(*doB)() = nullptr, void(*doC)() = nullptr);
// HallSensor interrupt callback functions
/** A channel callback function */
void handleA();
/** B channel callback function */
void handleB();
/** C channel callback function */
void handleC();
// pins A and B
int pinA; //!< HallSensor hardware pin A
int pinB; //!< HallSensor hardware pin B
int pinC; //!< HallSensor hardware pin C
// HallSensor configuration
Pullup pullup; //!< Configuration parameter internal or external pullups
int cpr;//!< HallSensor cpr number
// Abstract functions of the Sensor class implementation
/** get current angle (rad) */
float getSensorAngle() override;
float getMechanicalAngle() override;
float getAngle() override;
/** get current angular velocity (rad/s) */
float getVelocity() override;
double getPreciseAngle() override;
int32_t getFullRotations() override;
// whether last step was CW (+1) or CCW (-1).
Direction direction;
void attachSectorCallback(void (*onSectorChange)(int a) = nullptr);
// the current 3bit state of the hall sensors
volatile int8_t hall_state;
// the current sector of the sensor. Each sector is 60deg electrical
volatile int8_t electric_sector;
// the number of electric rotations
volatile long electric_rotations;
// this is sometimes useful to identify interrupt issues (e.g. weak or no pullup resulting in 1000s of interrupts)
volatile long total_interrupts;
private:
Direction decodeDirection(int oldState, int newState);
void updateState();
volatile unsigned long pulse_timestamp;//!< last impulse timestamp in us
volatile int A_active; //!< current active states of A channel
volatile int B_active; //!< current active states of B channel
volatile int C_active; //!< current active states of C channel
// function pointer for on sector change call back
void (*onSectorChange)(int sector) = nullptr;
volatile long pulse_diff;
};
#endif
@@ -0,0 +1,42 @@
#include "MagneticSensorAnalog.h"
/** MagneticSensorAnalog(uint8_t _pinAnalog, int _min, int _max)
* @param _pinAnalog the pin that is reading the pwm from magnetic sensor
* @param _min_raw_count the smallest expected reading. Whilst you might expect it to be 0 it is often ~15. Getting this wrong results in a small click once per revolution
* @param _max_raw_count the largest value read. whilst you might expect it to be 2^10 = 1023 it is often ~ 1020. Note: For ESP32 (with 12bit ADC the value will be nearer 4096)
*/
MagneticSensorAnalog::MagneticSensorAnalog(uint8_t _pinAnalog, int _min_raw_count, int _max_raw_count){
pinAnalog = _pinAnalog;
cpr = _max_raw_count - _min_raw_count;
min_raw_count = _min_raw_count;
max_raw_count = _max_raw_count;
if(pullup == Pullup::USE_INTERN){
pinMode(pinAnalog, INPUT_PULLUP);
}else{
pinMode(pinAnalog, INPUT);
}
}
void MagneticSensorAnalog::init(){
raw_count = getRawCount();
this->Sensor::init(); // call base class init
}
// Shaft angle calculation
// angle is in radians [rad]
float MagneticSensorAnalog::getSensorAngle(){
// raw data from the sensor
raw_count = getRawCount();
return ( (float) (raw_count) / (float)cpr) * _2PI;
}
// function reading the raw counter of the magnetic sensor
int MagneticSensorAnalog::getRawCount(){
return analogRead(pinAnalog);
}
@@ -0,0 +1,51 @@
#ifndef MAGNETICSENSORANALOG_LIB_H
#define MAGNETICSENSORANALOG_LIB_H
#include "Arduino.h"
#include "../common/base_classes/Sensor.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
/**
* This sensor has been tested with AS5600 running in 'analog mode'. This is where output pin of AS6000 is connected to an analog pin on your microcontroller.
* This approach is very simple but you may more accurate results with MagneticSensorI2C if that is also supported as its skips the DAC -> ADC conversion (ADC supports MagneticSensorI2C)
*/
class MagneticSensorAnalog: public Sensor{
public:
/**
* MagneticSensorAnalog class constructor
* @param _pinAnalog the pin to read the PWM signal
*/
MagneticSensorAnalog(uint8_t _pinAnalog, int _min = 0, int _max = 0);
/** sensor initialise pins */
void init();
int pinAnalog; //!< encoder hardware pin A
// Encoder configuration
Pullup pullup;
// implementation of abstract functions of the Sensor class
/** get current angle (rad) */
float getSensorAngle() override;
/** raw count (typically in range of 0-1023), useful for debugging resolution issues */
int raw_count;
private:
int min_raw_count;
int max_raw_count;
int cpr;
int read();
/**
* Function getting current angle register value
* it uses angle_register variable
*/
int getRawCount();
};
#endif
@@ -0,0 +1,156 @@
#include "MagneticSensorI2C.h"
/** Typical configuration for the 12bit AMS AS5600 magnetic sensor over I2C interface */
MagneticSensorI2CConfig_s AS5600_I2C = {
.chip_address = 0x36,
.bit_resolution = 12,
.angle_register = 0x0C,
.data_start_bit = 11
};
/** Typical configuration for the 12bit AMS AS5048 magnetic sensor over I2C interface */
MagneticSensorI2CConfig_s AS5048_I2C = {
.chip_address = 0x40, // highly configurable. if A1 and A2 are held low, this is probable value
.bit_resolution = 14,
.angle_register = 0xFE,
.data_start_bit = 15
};
// MagneticSensorI2C(uint8_t _chip_address, float _cpr, uint8_t _angle_register_msb)
// @param _chip_address I2C chip address
// @param _bit_resolution bit resolution of the sensor
// @param _angle_register_msb angle read register
// @param _bits_used_msb number of used bits in msb
MagneticSensorI2C::MagneticSensorI2C(uint8_t _chip_address, int _bit_resolution, uint8_t _angle_register_msb, int _bits_used_msb){
// chip I2C address
chip_address = _chip_address;
// angle read register of the magnetic sensor
angle_register_msb = _angle_register_msb;
// register maximum value (counts per revolution)
cpr = pow(2, _bit_resolution);
// depending on the sensor architecture there are different combinations of
// LSB and MSB register used bits
// AS5600 uses 0..7 LSB and 8..11 MSB
// AS5048 uses 0..5 LSB and 6..13 MSB
// used bits in LSB
lsb_used = _bit_resolution - _bits_used_msb;
// extraction masks
lsb_mask = (uint8_t)( (2 << lsb_used) - 1 );
msb_mask = (uint8_t)( (2 << _bits_used_msb) - 1 );
wire = &Wire;
}
MagneticSensorI2C::MagneticSensorI2C(MagneticSensorI2CConfig_s config){
chip_address = config.chip_address;
// angle read register of the magnetic sensor
angle_register_msb = config.angle_register;
// register maximum value (counts per revolution)
cpr = pow(2, config.bit_resolution);
int bits_used_msb = config.data_start_bit - 7;
lsb_used = config.bit_resolution - bits_used_msb;
// extraction masks
lsb_mask = (uint8_t)( (2 << lsb_used) - 1 );
msb_mask = (uint8_t)( (2 << bits_used_msb) - 1 );
wire = &Wire;
}
void MagneticSensorI2C::init(TwoWire* _wire){
wire = _wire;
// I2C communication begin
wire->begin();
this->Sensor::init(); // call base class init
}
// Shaft angle calculation
// angle is in radians [rad]
float MagneticSensorI2C::getSensorAngle(){
// (number of full rotations)*2PI + current sensor angle
return ( getRawCount() / (float)cpr) * _2PI ;
}
// function reading the raw counter of the magnetic sensor
int MagneticSensorI2C::getRawCount(){
return (int)MagneticSensorI2C::read(angle_register_msb);
}
// I2C functions
/*
* Read a register from the sensor
* Takes the address of the register as a uint8_t
* Returns the value of the register
*/
int MagneticSensorI2C::read(uint8_t angle_reg_msb) {
// read the angle register first MSB then LSB
byte readArray[2];
uint16_t readValue = 0;
// notify the device that is aboout to be read
wire->beginTransmission(chip_address);
wire->write(angle_reg_msb);
wire->endTransmission(false);
// read the data msb and lsb
wire->requestFrom(chip_address, (uint8_t)2);
for (byte i=0; i < 2; i++) {
readArray[i] = wire->read();
}
// depending on the sensor architecture there are different combinations of
// LSB and MSB register used bits
// AS5600 uses 0..7 LSB and 8..11 MSB
// AS5048 uses 0..5 LSB and 6..13 MSB
readValue = ( readArray[1] & lsb_mask );
readValue += ( ( readArray[0] & msb_mask ) << lsb_used );
return readValue;
}
/*
* Checks whether other devices have locked the bus. Can clear SDA locks.
* This should be called before sensor.init() on devices that suffer i2c slaves locking sda
* e.g some stm32 boards with AS5600 chips
* Takes the sda_pin and scl_pin
* Returns 0 for OK, 1 for other master and 2 for unfixable sda locked LOW
*/
int MagneticSensorI2C::checkBus(byte sda_pin, byte scl_pin) {
pinMode(scl_pin, INPUT_PULLUP);
pinMode(sda_pin, INPUT_PULLUP);
delay(250);
if (digitalRead(scl_pin) == LOW) {
// Someone else has claimed master!");
return 1;
}
if(digitalRead(sda_pin) == LOW) {
// slave is communicating and awaiting clocks, we are blocked
pinMode(scl_pin, OUTPUT);
for (byte i = 0; i < 16; i++) {
// toggle clock for 2 bytes of data
digitalWrite(scl_pin, LOW);
delayMicroseconds(20);
digitalWrite(scl_pin, HIGH);
delayMicroseconds(20);
}
pinMode(sda_pin, INPUT);
delayMicroseconds(20);
if (digitalRead(sda_pin) == LOW) {
// SDA still blocked
return 2;
}
_delay(1000);
}
// SDA is clear (HIGH)
pinMode(sda_pin, INPUT);
pinMode(scl_pin, INPUT);
return 0;
}
@@ -0,0 +1,81 @@
#ifndef MAGNETICSENSORI2C_LIB_H
#define MAGNETICSENSORI2C_LIB_H
#include "Arduino.h"
#include <Wire.h>
#include "../common/base_classes/Sensor.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
struct MagneticSensorI2CConfig_s {
int chip_address;
int bit_resolution;
int angle_register;
int data_start_bit;
};
// some predefined structures
extern MagneticSensorI2CConfig_s AS5600_I2C,AS5048_I2C;
#if defined(TARGET_RP2040)
#define SDA I2C_SDA
#define SCL I2C_SCL
#endif
class MagneticSensorI2C: public Sensor{
public:
/**
* MagneticSensorI2C class constructor
* @param chip_address I2C chip address
* @param bits number of bits of the sensor resolution
* @param angle_register_msb angle read register msb
* @param _bits_used_msb number of used bits in msb
*/
MagneticSensorI2C(uint8_t _chip_address, int _bit_resolution, uint8_t _angle_register_msb, int _msb_bits_used);
/**
* MagneticSensorI2C class constructor
* @param config I2C config
*/
MagneticSensorI2C(MagneticSensorI2CConfig_s config);
static MagneticSensorI2C AS5600();
/** sensor initialise pins */
void init(TwoWire* _wire = &Wire);
// implementation of abstract functions of the Sensor class
/** get current angle (rad) */
float getSensorAngle() override;
/** experimental function to check and fix SDA locked LOW issues */
int checkBus(byte sda_pin , byte scl_pin );
private:
float cpr; //!< Maximum range of the magnetic sensor
uint16_t lsb_used; //!< Number of bits used in LSB register
uint8_t lsb_mask;
uint8_t msb_mask;
// I2C variables
uint8_t angle_register_msb; //!< I2C angle register to read
uint8_t chip_address; //!< I2C chip select pins
// I2C functions
/** Read one I2C register value */
int read(uint8_t angle_register_msb);
/**
* Function getting current angle register value
* it uses angle_register variable
*/
int getRawCount();
/* the two wire instance for this sensor */
TwoWire* wire;
};
#endif
@@ -0,0 +1,71 @@
#include "MagneticSensorPWM.h"
#include "Arduino.h"
/** MagneticSensorPWM(uint8_t _pinPWM, int _min, int _max)
* @param _pinPWM the pin that is reading the pwm from magnetic sensor
* @param _min_raw_count the smallest expected reading
* @param _max_raw_count the largest expected reading
*/
MagneticSensorPWM::MagneticSensorPWM(uint8_t _pinPWM, int _min_raw_count, int _max_raw_count){
pinPWM = _pinPWM;
cpr = _max_raw_count - _min_raw_count;
min_raw_count = _min_raw_count;
max_raw_count = _max_raw_count;
// define if the sensor uses interrupts
is_interrupt_based = false;
// define as not set
last_call_us = _micros();
}
void MagneticSensorPWM::init(){
// initial hardware
pinMode(pinPWM, INPUT);
raw_count = getRawCount();
this->Sensor::init(); // call base class init
}
// get current angle (rad)
float MagneticSensorPWM::getSensorAngle(){
// raw data from sensor
raw_count = getRawCount();
return( (float) (raw_count) / (float)cpr) * _2PI;
}
// read the raw counter of the magnetic sensor
int MagneticSensorPWM::getRawCount(){
if (!is_interrupt_based){ // if it's not interrupt based read the value in a blocking way
pulse_length_us = pulseIn(pinPWM, HIGH);
}
return pulse_length_us;
}
void MagneticSensorPWM::handlePWM() {
// unsigned long now_us = ticks();
unsigned long now_us = _micros();
// if falling edge, calculate the pulse length
if (!digitalRead(pinPWM)) pulse_length_us = now_us - last_call_us;
// save the currrent timestamp for the next call
last_call_us = now_us;
is_interrupt_based = true; // set the flag to true
}
// function enabling hardware interrupts of the for the callback provided
// if callback is not provided then the interrupt is not enabled
void MagneticSensorPWM::enableInterrupt(void (*doPWM)()){
// declare it's interrupt based
is_interrupt_based = true;
// enable interrupts on pwm input pin
attachInterrupt(digitalPinToInterrupt(pinPWM), doPWM, CHANGE);
}
@@ -0,0 +1,58 @@
#ifndef MAGNETICSENSORPWM_LIB_H
#define MAGNETICSENSORPWM_LIB_H
#include "Arduino.h"
#include "../common/base_classes/Sensor.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
// This sensor has been tested with AS5048a running in PWM mode.
class MagneticSensorPWM: public Sensor{
public:
/**
* MagneticSensorPWM class constructor
* @param _pinPWM the pin to read the PWM sensor input signal
*/
MagneticSensorPWM(uint8_t _pinPWM,int _min = 0, int _max = 0);
// initialize the sensor hardware
void init();
int pinPWM;
// get current angle (rad)
float getSensorAngle() override;
// pwm handler
void handlePWM();
void enableInterrupt(void (*doPWM)());
unsigned long pulse_length_us;
private:
// raw count (typically in range of 0-1023)
int raw_count;
int min_raw_count;
int max_raw_count;
int cpr;
// flag saying if the readings are interrupt based or not
bool is_interrupt_based;
int read();
/**
* Function getting current angle register value
* it uses angle_register variable
*/
int getRawCount();
// time tracking variables
unsigned long last_call_us;
// unsigned long pulse_length_us;
};
#endif
@@ -0,0 +1,161 @@
#include "MagneticSensorSPI.h"
/** Typical configuration for the 14bit AMS AS5147 magnetic sensor over SPI interface */
MagneticSensorSPIConfig_s AS5147_SPI = {
.spi_mode = SPI_MODE1,
.clock_speed = 1000000,
.bit_resolution = 14,
.angle_register = 0x3FFF,
.data_start_bit = 13,
.command_rw_bit = 14,
.command_parity_bit = 15
};
// AS5048 and AS5047 are the same as AS5147
MagneticSensorSPIConfig_s AS5048_SPI = AS5147_SPI;
MagneticSensorSPIConfig_s AS5047_SPI = AS5147_SPI;
/** Typical configuration for the 14bit MonolithicPower MA730 magnetic sensor over SPI interface */
MagneticSensorSPIConfig_s MA730_SPI = {
.spi_mode = SPI_MODE0,
.clock_speed = 1000000,
.bit_resolution = 14,
.angle_register = 0x0000,
.data_start_bit = 15,
.command_rw_bit = 0, // not required
.command_parity_bit = 0 // parity not implemented
};
// MagneticSensorSPI(int cs, float _bit_resolution, int _angle_register)
// cs - SPI chip select pin
// _bit_resolution sensor resolution bit number
// _angle_register - (optional) angle read register - default 0x3FFF
MagneticSensorSPI::MagneticSensorSPI(int cs, float _bit_resolution, int _angle_register){
chip_select_pin = cs;
// angle read register of the magnetic sensor
angle_register = _angle_register ? _angle_register : DEF_ANGLE_REGISTER;
// register maximum value (counts per revolution)
cpr = pow(2,_bit_resolution);
spi_mode = SPI_MODE1;
clock_speed = 1000000;
bit_resolution = _bit_resolution;
command_parity_bit = 15; // for backwards compatibilty
command_rw_bit = 14; // for backwards compatibilty
data_start_bit = 13; // for backwards compatibilty
}
MagneticSensorSPI::MagneticSensorSPI(MagneticSensorSPIConfig_s config, int cs){
chip_select_pin = cs;
// angle read register of the magnetic sensor
angle_register = config.angle_register ? config.angle_register : DEF_ANGLE_REGISTER;
// register maximum value (counts per revolution)
cpr = pow(2, config.bit_resolution);
spi_mode = config.spi_mode;
clock_speed = config.clock_speed;
bit_resolution = config.bit_resolution;
command_parity_bit = config.command_parity_bit; // for backwards compatibilty
command_rw_bit = config.command_rw_bit; // for backwards compatibilty
data_start_bit = config.data_start_bit; // for backwards compatibilty
}
void MagneticSensorSPI::init(SPIClass* _spi){
spi = _spi;
// 1MHz clock (AMS should be able to accept up to 10MHz)
settings = SPISettings(clock_speed, MSBFIRST, spi_mode);
//setup pins
pinMode(chip_select_pin, OUTPUT);
//SPI has an internal SPI-device counter, it is possible to call "begin()" from different devices
spi->begin();
// do any architectures need to set the clock divider for SPI? Why was this in the code?
//spi->setClockDivider(SPI_CLOCK_DIV8);
digitalWrite(chip_select_pin, HIGH);
this->Sensor::init(); // call base class init
}
// Shaft angle calculation
// angle is in radians [rad]
float MagneticSensorSPI::getSensorAngle(){
return (getRawCount() / (float)cpr) * _2PI;
}
// function reading the raw counter of the magnetic sensor
int MagneticSensorSPI::getRawCount(){
return (int)MagneticSensorSPI::read(angle_register);
}
// SPI functions
/**
* Utility function used to calculate even parity of word
*/
byte MagneticSensorSPI::spiCalcEvenParity(word value){
byte cnt = 0;
byte i;
for (i = 0; i < 16; i++)
{
if (value & 0x1) cnt++;
value >>= 1;
}
return cnt & 0x1;
}
/*
* Read a register from the sensor
* Takes the address of the register as a 16 bit word
* Returns the value of the register
*/
word MagneticSensorSPI::read(word angle_register){
word command = angle_register;
if (command_rw_bit > 0) {
command = angle_register | (1 << command_rw_bit);
}
if (command_parity_bit > 0) {
//Add a parity bit on the the MSB
command |= ((word)spiCalcEvenParity(command) << command_parity_bit);
}
//SPI - begin transaction
spi->beginTransaction(settings);
//Send the command
digitalWrite(chip_select_pin, LOW);
spi->transfer16(command);
digitalWrite(chip_select_pin,HIGH);
#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) // if ESP32 board
delayMicroseconds(50); // why do we need to delay 50us on ESP32? In my experience no extra delays are needed, on any of the architectures I've tested...
#else
delayMicroseconds(1); // delay 1us, the minimum time possible in plain arduino. 350ns is the required time for AMS sensors, 80ns for MA730, MA702
#endif
//Now read the response
digitalWrite(chip_select_pin, LOW);
word register_value = spi->transfer16(0x00);
digitalWrite(chip_select_pin, HIGH);
//SPI - end transaction
spi->endTransaction();
register_value = register_value >> (1 + data_start_bit - bit_resolution); //this should shift data to the rightmost bits of the word
const static word data_mask = 0xFFFF >> (16 - bit_resolution);
return register_value & data_mask; // Return the data, stripping the non data (e.g parity) bits
}
/**
* Closes the SPI connection
* SPI has an internal SPI-device counter, for each init()-call the close() function must be called exactly 1 time
*/
void MagneticSensorSPI::close(){
spi->end();
}
@@ -0,0 +1,84 @@
#ifndef MAGNETICSENSORSPI_LIB_H
#define MAGNETICSENSORSPI_LIB_H
#include "Arduino.h"
#include <SPI.h>
#include "../common/base_classes/Sensor.h"
#include "../common/foc_utils.h"
#include "../common/time_utils.h"
#define DEF_ANGLE_REGISTER 0x3FFF
struct MagneticSensorSPIConfig_s {
int spi_mode;
long clock_speed;
int bit_resolution;
int angle_register;
int data_start_bit;
int command_rw_bit;
int command_parity_bit;
};
// typical configuration structures
extern MagneticSensorSPIConfig_s AS5147_SPI,AS5048_SPI,AS5047_SPI, MA730_SPI;
class MagneticSensorSPI: public Sensor{
public:
/**
* MagneticSensorSPI class constructor
* @param cs SPI chip select pin
* @param bit_resolution sensor resolution bit number
* @param angle_register (optional) angle read register - default 0x3FFF
*/
MagneticSensorSPI(int cs, float bit_resolution, int angle_register = 0);
/**
* MagneticSensorSPI class constructor
* @param config SPI config
* @param cs SPI chip select pin
*/
MagneticSensorSPI(MagneticSensorSPIConfig_s config, int cs);
/** sensor initialise pins */
void init(SPIClass* _spi = &SPI);
// implementation of abstract functions of the Sensor class
/** get current angle (rad) */
float getSensorAngle() override;
// returns the spi mode (phase/polarity of read/writes) i.e one of SPI_MODE0 | SPI_MODE1 | SPI_MODE2 | SPI_MODE3
int spi_mode;
/* returns the speed of the SPI clock signal */
long clock_speed;
private:
float cpr; //!< Maximum range of the magnetic sensor
// spi variables
int angle_register; //!< SPI angle register to read
int chip_select_pin; //!< SPI chip select pin
SPISettings settings; //!< SPI settings variable
// spi functions
/** Stop SPI communication */
void close();
/** Read one SPI register value */
word read(word angle_register);
/** Calculate parity value */
byte spiCalcEvenParity(word value);
/**
* Function getting current angle register value
* it uses angle_register variable
*/
int getRawCount();
int bit_resolution; //!< the number of bites of angle data
int command_parity_bit; //!< the bit where parity flag is stored in command
int command_rw_bit; //!< the bit where read/write flag is stored in command
int data_start_bit; //!< the the position of first bit
SPIClass* spi;
};
#endif