Skip to content

Commit edeccde

Browse files
committed
Created SmoothingSensor
The example sketch is untested due to lack of hardware, but minimal changes from the original hall sensor velocity example so it should work.
1 parent d47243d commit edeccde

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
*
3+
* Hall sensor velocity motion control example, modified to demonstrate usage of SmoothingSensor
4+
* Steps:
5+
* 1) Configure the motor and sensor
6+
* 2) Run the code
7+
* 3) Set the target velocity (in radians per second) from serial terminal
8+
* 4) Try with and without smoothing to see the difference (send E1 and E0 commands from serial terminal)
9+
*
10+
*
11+
*
12+
* NOTE :
13+
* > Specifically for Arduino UNO example code for running velocity motion control using a hall sensor
14+
* > Since Arduino UNO doesn't have enough interrupt pins we have to use software interrupt library PciManager.
15+
*
16+
* > If running this code with Nucleo or Bluepill or any other board which has more than 2 interrupt pins
17+
* > you can supply doC directly to the sensor.enableInterrupts(doA,doB,doC) and avoid using PciManger
18+
*
19+
*/
20+
#include <SimpleFOC.h>
21+
// software interrupt library
22+
#include <PciManager.h>
23+
#include <PciListenerImp.h>
24+
#include <SimpleFOCDrivers.h>
25+
#include <encoders/smoothing/SmoothingSensor.h>
26+
27+
// BLDC motor & driver instance
28+
BLDCMotor motor = BLDCMotor(11);
29+
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);
30+
// Stepper motor & driver instance
31+
//StepperMotor motor = StepperMotor(50);
32+
//StepperDriver4PWM driver = StepperDriver4PWM(9, 5, 10, 6, 8);
33+
34+
// hall sensor instance
35+
HallSensor sensor = HallSensor(2, 3, 4, 11);
36+
// wrapper instance
37+
SmoothingSensor smooth(sensor, motor);
38+
39+
// Interrupt routine intialisation
40+
// channel A and B callbacks
41+
void doA(){sensor.handleA();}
42+
void doB(){sensor.handleB();}
43+
void doC(){sensor.handleC();}
44+
// If no available hadware interrupt pins use the software interrupt
45+
PciListenerImp listenerIndex(sensor.pinC, doC);
46+
47+
// velocity set point variable
48+
float target_velocity = 0;
49+
// instantiate the commander
50+
Commander command = Commander(Serial);
51+
void doTarget(char* cmd) { command.scalar(&target_velocity, cmd); }
52+
53+
void enableSmoothing(char* cmd) {
54+
float enable;
55+
command.scalar(&enable, cmd);
56+
motor.linkSensor(enable == 0 ? (Sensor*)&sensor : (Sensor*)&smooth);
57+
}
58+
59+
void setup() {
60+
61+
// initialize sensor sensor hardware
62+
sensor.init();
63+
sensor.enableInterrupts(doA, doB); //, doC);
64+
// software interrupts
65+
PciManager.registerListener(&listenerIndex);
66+
// set SmoothingSensor phase correction for hall sensors
67+
smooth.phase_correction = -_PI_6;
68+
// link the SmoothingSensor to the motor
69+
motor.linkSensor(&smooth);
70+
71+
// driver config
72+
// power supply voltage [V]
73+
driver.voltage_power_supply = 12;
74+
driver.init();
75+
// link the motor and the driver
76+
motor.linkDriver(&driver);
77+
78+
// aligning voltage [V]
79+
motor.voltage_sensor_align = 3;
80+
81+
// set motion control loop to be used
82+
motor.controller = MotionControlType::velocity;
83+
84+
// contoller configuration
85+
// default parameters in defaults.h
86+
87+
// velocity PI controller parameters
88+
motor.PID_velocity.P = 0.2f;
89+
motor.PID_velocity.I = 2;
90+
motor.PID_velocity.D = 0;
91+
// default voltage_power_supply
92+
motor.voltage_limit = 6;
93+
// jerk control using voltage voltage ramp
94+
// default value is 300 volts per sec ~ 0.3V per millisecond
95+
motor.PID_velocity.output_ramp = 1000;
96+
97+
// velocity low pass filtering time constant
98+
motor.LPF_velocity.Tf = 0.01f;
99+
100+
// use monitoring with serial
101+
Serial.begin(115200);
102+
// comment out if not needed
103+
motor.useMonitoring(Serial);
104+
105+
// initialize motor
106+
motor.init();
107+
// align sensor and start FOC
108+
motor.initFOC();
109+
110+
// add target command T
111+
command.add('T', doTarget, "target voltage");
112+
113+
// add smoothing enable/disable command E (send E0 to use hall sensor alone, or E1 to use smoothing)
114+
command.add('E', enableSmoothing, "enable smoothing");
115+
116+
Serial.println(F("Motor ready."));
117+
Serial.println(F("Set the target velocity using serial terminal:"));
118+
_delay(1000);
119+
}
120+
121+
122+
void loop() {
123+
// main FOC algorithm function
124+
// the faster you run this function the better
125+
// Arduino UNO loop ~1kHz
126+
// Bluepill loop ~10kHz
127+
motor.loopFOC();
128+
129+
// Motion control function
130+
// velocity, position or voltage (defined in motor.controller)
131+
// this function can be run at much lower frequency than loopFOC() function
132+
// You can also use motor.move() and set the motor.target in the code
133+
motor.move(target_velocity);
134+
135+
// function intended to be used with serial plotter to monitor motor variables
136+
// significantly slowing the execution down!!!!
137+
// motor.monitor();
138+
139+
// user communication
140+
command.run();
141+
}

src/encoders/smoothing/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Smoothing Sensor
2+
3+
by [@dekutree64](https://github.com/dekutree64)
4+
5+
A SimpleFOC Sensor wrapper implementation which adds angle extrapolation
6+
7+
Please also see our [forum thread](https://community.simplefoc.com/t/smoothingsensor-experimental-sensor-angle-extrapoltion/3105) on this topic.
8+
9+
10+
SmoothingSensor is a wrapper class which is inserted inbetween a sensor and motor to provide better quality angle readings from sensors that are low resolution or are slow to communicate. It provides no improvement for sensors that are high resolution and quick to update. It uses the timestamp of the last angle reading from the sensor and the low-pass filtered velocity from the motor to predict what the angle should be at the current time.
11+
12+
13+
## Hardware setup
14+
15+
Connect your sensor as usual. Make sure the sensor is working 'normally' i.e. without smoothing first. Once things are working and tuned without smoothing, you can add the SmoothingSensor to see if you get an improvement.
16+
17+
18+
## Softwate setup
19+
20+
The SmoothingSensor acts as a wrapper to the actual sensor class. When creating the SmoothingSensor object, provide the real sensor to the constructor of SmoothingSensor. Initialize the real sensor instance as normal. Then link the SmoothingSensor to the motor and call motor.initFOC().
21+
22+
23+
```c++
24+
// BLDC motor & driver instance
25+
BLDCMotor motor = BLDCMotor(11);
26+
BLDCDriver3PWM driver = BLDCDriver3PWM(PB4,PC7,PB10,PA9);
27+
// real sensor instance
28+
MagneticSensorPWM sensor = MagneticSensorPWM(2, 4, 904);
29+
// instantiate the smoothing sensor, providing the real sensor as a constructor argument
30+
SmoothingSensor smooth = SmoothingSensor(sensor, motor);
31+
32+
void doPWM(){sensor.handlePWM();}
33+
34+
void setup() {
35+
sensor.init();
36+
sensor.enableInterrupt(doPWM);
37+
// Link motor to sensor
38+
motor.linkSensor(&smooth);
39+
// power supply voltage
40+
driver.voltage_power_supply = 20;
41+
driver.init();
42+
motor.linkDriver(&driver);
43+
// aligning voltage
44+
motor.voltage_sensor_align = 8;
45+
motor.voltage_limit = 20;
46+
// set motion control loop to be used
47+
motor.controller = MotionControlType::torque;
48+
49+
// use monitoring with serial
50+
Serial.begin(115200);
51+
// comment out if not needed
52+
motor.useMonitoring(Serial);
53+
motor.monitor_variables = _MON_VEL;
54+
motor.monitor_downsample = 10; // default 10
55+
56+
// initialize motor
57+
motor.init();
58+
motor.initFOC();
59+
}
60+
```
61+
62+
63+
## Roadmap
64+
65+
Possible future improvements we've thought about:
66+
67+
- Add extrapolation of acceleration as well
68+
- Switch to open loop control at very low speed with hall sensors, which otherwise move in steps even with smoothing.
69+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include "SmoothingSensor.h"
2+
#include "common/foc_utils.h"
3+
#include "common/time_utils.h"
4+
5+
6+
SmoothingSensor::SmoothingSensor(Sensor& s, const FOCMotor& m) : _wrapped(s), _motor(m)
7+
{
8+
}
9+
10+
11+
void SmoothingSensor::update() {
12+
// Update sensor, with optional downsampling of update rate
13+
if(sensor_cnt++ >= sensor_downsample) {
14+
sensor_cnt = 0;
15+
_wrapped.update();
16+
}
17+
18+
// Copy state variables from the sensor
19+
angle_prev = _wrapped.angle_prev;
20+
angle_prev_ts = _wrapped.angle_prev_ts;
21+
full_rotations = _wrapped.full_rotations;
22+
23+
// Perform angle prediction, using low-pass filtered velocity. But don't advance more than
24+
// pi/3 (equivalent to one step of block commutation) from the last true angle reading.
25+
float dt = (_micros() - angle_prev_ts) * 1e-6f;
26+
angle_prev += _constrain(_motor.shaft_velocity * dt, -_PI_3 / _motor.pole_pairs, _PI_3 / _motor.pole_pairs);
27+
28+
// Apply phase correction if needed
29+
if (phase_correction != 0) {
30+
if (_motor.shaft_velocity < -0) angle_prev -= phase_correction / _motor.pole_pairs;
31+
else if (_motor.shaft_velocity > 0) angle_prev += phase_correction / _motor.pole_pairs;
32+
}
33+
34+
// Handle wraparound of the projected angle
35+
if (angle_prev < 0) full_rotations -= 1, angle_prev += _2PI;
36+
else if (angle_prev >= _2PI) full_rotations += 1, angle_prev -= _2PI;
37+
}
38+
39+
40+
float SmoothingSensor::getVelocity() {
41+
return _wrapped.getVelocity();
42+
}
43+
44+
45+
int SmoothingSensor::needsSearch() {
46+
return _wrapped.needsSearch();
47+
}
48+
49+
50+
float SmoothingSensor::getSensorAngle() {
51+
return _wrapped.getSensorAngle();
52+
}
53+
54+
55+
void SmoothingSensor::init() {
56+
_wrapped.init();
57+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#ifndef SMOOTHING_SENSOR_H
2+
#define SMOOTHING_SENSOR_H
3+
4+
#include "Arduino.h"
5+
#include "common/base_classes/FOCMotor.h"
6+
#include "common/base_classes/Sensor.h"
7+
8+
/**
9+
SmoothingSensor is a wrapper class which is inserted inbetween a sensor and motor to provide better
10+
quality angle readings from sensors that are low resolution or are slow to communicate. It provides
11+
no improvement for sensors that are high resolution and quick to update.
12+
It uses the timestamp of the last angle reading from the sensor and the low-pass filtered velocity
13+
from the motor to predict what the angle should be at the current time.
14+
*/
15+
16+
class SmoothingSensor : public Sensor
17+
{
18+
public:
19+
/**
20+
SmoothingSensor class constructor
21+
@param s Wrapped sensor
22+
@param m Motor that the SmoothingSensor will be linked to
23+
*/
24+
SmoothingSensor(Sensor& s, const FOCMotor& m);
25+
26+
void update() override;
27+
float getVelocity() override;
28+
int needsSearch() override;
29+
30+
// For sensors with slow communication, use these to poll less often
31+
unsigned int sensor_downsample = 0; // parameter defining the ratio of downsampling for sensor update
32+
unsigned int sensor_cnt = 0; // counting variable for downsampling
33+
34+
// For hall sensors, the predicted angle is always 0 to 60 electrical degrees ahead of where it would be without
35+
// smoothing, so offset it backward by 30 degrees (set this to -PI_6) to get the average in phase with the rotor
36+
float phase_correction = 0;
37+
38+
protected:
39+
float getSensorAngle() override;
40+
void init() override;
41+
42+
Sensor& _wrapped;
43+
const FOCMotor& _motor;
44+
};
45+
46+
#endif

0 commit comments

Comments
 (0)