Skip to content

Commit 5e35fb8

Browse files
committed
Fix for issue #270
Overhaul of interrupt-based sensors to eliminate bad readings due to interrupts modifying variables in the middle of get functions. All sensor classes are now required to use the base class state variables, and do their angle reading once per frame in the update() function rather than using the virtual get functions to return real-time updated readings. I have chosen to make an exception for getVelocity() and allow it to access volatile variables as well, although it may be better to merge it into update() instead. Interrupts should be disabled/enabled as necessary in the update() and getVelocity() functions, and preferably nowhere else. The Arduino library provides no mechanism to restore the previous state of interrupts, so use of the unconditional enable should be kept to as few locations as possible so we can be reasonably sure that it won't be called at a time when interrupts should remain disabled. Additional changes to specific sensors: HallSensor: Removed the max_velocity variable because it was a quick fix for bad velocity readings that were coming from this interrupt problem, which should no longer occur. Also changed the condition for "velocity too old" from (_micros() - pulse_timestamp) > pulse_diff to (_micros() - pulse_timestamp) > pulse_diff*2, because any deceleration would cause inappropriate reporting of zero velocity. MagneticSensorPWM: Unrelated to the interrupt problem, timestamp is now saved from the rising edge of the PWM pulse because that's when the angle was sampled, and communicating it takes a significant and variable amount of time. This gives more accurate velocity calculations, and will allow extrapolating accurate angles using the new SmoothingSensor class.
1 parent 4c8ea20 commit 5e35fb8

File tree

6 files changed

+50
-53
lines changed

6 files changed

+50
-53
lines changed

src/common/base_classes/Sensor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum Pullup : uint8_t {
4242
*
4343
*/
4444
class Sensor{
45+
friend class SmoothingSensor;
4546
public:
4647
/**
4748
* Get mechanical shaft angle in the range 0 to 2PI. This value will be as precise as possible with

src/sensors/Encoder.cpp

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,29 +99,21 @@ void Encoder::handleIndex() {
9999
}
100100

101101

102+
// Sensor update function. Safely copy volatile interrupt variables into Sensor base class state variables.
102103
void Encoder::update() {
103-
// do nothing for Encoder
104+
noInterrupts();
105+
// TODO: numerical precision issue here if the pulse_counter overflows the angle will be lost
106+
full_rotations = pulse_counter / (int)cpr;
107+
angle_prev = _2PI * ((pulse_counter) % ((int)cpr)) / ((float)cpr);
108+
angle_prev_ts = pulse_timestamp;
109+
interrupts();
104110
}
105111

106112
/*
107113
Shaft angle calculation
108114
*/
109115
float Encoder::getSensorAngle(){
110-
return getAngle();
111-
}
112-
// TODO: numerical precision issue here if the pulse_counter overflows the angle will be lost
113-
float Encoder::getMechanicalAngle(){
114-
return _2PI * ((pulse_counter) % ((int)cpr)) / ((float)cpr);
115-
}
116-
117-
float Encoder::getAngle(){
118-
return _2PI * (pulse_counter) / ((float)cpr);
119-
}
120-
double Encoder::getPreciseAngle(){
121-
return _2PI * (pulse_counter) / ((double)cpr);
122-
}
123-
int32_t Encoder::getFullRotations(){
124-
return pulse_counter / (int)cpr;
116+
return _2PI * (pulse_counter) / ((float)cpr);
125117
}
126118

127119

@@ -131,6 +123,8 @@ int32_t Encoder::getFullRotations(){
131123
function using mixed time and frequency measurement technique
132124
*/
133125
float Encoder::getVelocity(){
126+
// Make sure no interrupts modify the state variables in the middle of these calculations
127+
noInterrupts();
134128
// timestamp
135129
long timestamp_us = _micros();
136130
// sampling time calculation
@@ -162,6 +156,8 @@ float Encoder::getVelocity(){
162156
// save velocity calculation variables
163157
prev_Th = Th;
164158
prev_pulse_counter = pulse_counter;
159+
// Re-enable interrupts (ideally this would restore to the previous state rather than unconditionally enabling)
160+
interrupts();
165161
return velocity;
166162
}
167163

src/sensors/HallSensor.cpp

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,13 @@ void HallSensor::attachSectorCallback(void (*_onSectorChange)(int sector)) {
9494

9595

9696

97-
float HallSensor::getSensorAngle() {
98-
return getAngle();
97+
// Sensor update function. Safely copy volatile interrupt variables into Sensor base class state variables.
98+
void HallSensor::update() {
99+
noInterrupts();
100+
angle_prev = ((float)((electric_rotations * 6 + electric_sector) % cpr) / (float)cpr) * _2PI ;
101+
full_rotations = (int32_t)((electric_rotations * 6 + electric_sector) / cpr);
102+
angle_prev_ts = pulse_timestamp;
103+
interrupts();
99104
}
100105

101106

@@ -104,47 +109,25 @@ float HallSensor::getSensorAngle() {
104109
Shaft angle calculation
105110
TODO: numerical precision issue here if the electrical rotation overflows the angle will be lost
106111
*/
107-
float HallSensor::getMechanicalAngle() {
108-
return ((float)((electric_rotations * 6 + electric_sector) % cpr) / (float)cpr) * _2PI ;
112+
float HallSensor::getSensorAngle() {
113+
return ((float)(electric_rotations * 6 + electric_sector) / (float)cpr) * _2PI ;
109114
}
110115

111116
/*
112117
Shaft velocity calculation
113118
function using mixed time and frequency measurement technique
114119
*/
115120
float HallSensor::getVelocity(){
116-
long last_pulse_diff = pulse_diff;
117-
if (last_pulse_diff == 0 || ((long)(_micros() - pulse_timestamp) > last_pulse_diff) ) { // last velocity isn't accurate if too old
118-
return 0;
119-
} else {
120-
float vel = direction * (_2PI / (float)cpr) / (last_pulse_diff / 1000000.0f);
121-
// quick fix https://github.com/simplefoc/Arduino-FOC/issues/192
122-
if(vel < -velocity_max || vel > velocity_max) vel = 0.0f; //if velocity is out of range then make it zero
123-
return vel;
124-
}
125-
121+
noInterrupts();
122+
float vel = 0;
123+
if (pulse_diff != 0 && ((long)(_micros() - pulse_timestamp) < pulse_diff*2) ) // last velocity isn't accurate if too old
124+
vel = direction * (_2PI / (float)cpr) / (pulse_diff / 1000000.0f);
125+
interrupts();
126+
return vel;
126127
}
127128

128129

129130

130-
float HallSensor::getAngle() {
131-
return ((float)(electric_rotations * 6 + electric_sector) / (float)cpr) * _2PI ;
132-
}
133-
134-
135-
double HallSensor::getPreciseAngle() {
136-
return ((double)(electric_rotations * 6 + electric_sector) / (double)cpr) * (double)_2PI ;
137-
}
138-
139-
140-
int32_t HallSensor::getFullRotations() {
141-
return (int32_t)((electric_rotations * 6 + electric_sector) / cpr);
142-
}
143-
144-
145-
146-
147-
148131
// HallSensor initialisation of the hardware pins
149132
// and calculation variables
150133
void HallSensor::init(){

src/sensors/HallSensor.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,12 @@ class HallSensor: public Sensor{
5353
int cpr;//!< HallSensor cpr number
5454

5555
// Abstract functions of the Sensor class implementation
56+
/** Interrupt-safe update */
57+
void update() override;
5658
/** get current angle (rad) */
5759
float getSensorAngle() override;
58-
float getMechanicalAngle() override;
59-
float getAngle() override;
6060
/** get current angular velocity (rad/s) */
6161
float getVelocity() override;
62-
double getPreciseAngle() override;
63-
int32_t getFullRotations() override;
6462

6563
// whether last step was CW (+1) or CCW (-1).
6664
Direction direction;

src/sensors/MagneticSensorPWM.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,21 @@ void MagneticSensorPWM::init(){
5656
// initial hardware
5757
pinMode(pinPWM, INPUT);
5858
raw_count = getRawCount();
59+
pulse_timestamp = _micros();
5960

6061
this->Sensor::init(); // call base class init
6162
}
6263

64+
// Sensor update function. Safely copy volatile interrupt variables into Sensor base class state variables.
65+
void MagneticSensorPWM::update() {
66+
if (is_interrupt_based)
67+
noInterrupts();
68+
Sensor::update();
69+
angle_prev_ts = pulse_timestamp; // Timestamp of actual sample, before the time-consuming PWM communication
70+
if (is_interrupt_based)
71+
interrupts();
72+
}
73+
6374
// get current angle (rad)
6475
float MagneticSensorPWM::getSensorAngle(){
6576
// raw data from sensor
@@ -73,6 +84,7 @@ float MagneticSensorPWM::getSensorAngle(){
7384
// read the raw counter of the magnetic sensor
7485
int MagneticSensorPWM::getRawCount(){
7586
if (!is_interrupt_based){ // if it's not interrupt based read the value in a blocking way
87+
pulse_timestamp = _micros(); // ideally this should be done right at the rising edge of the pulse
7688
pulse_length_us = pulseIn(pinPWM, HIGH);
7789
}
7890
return pulse_length_us;
@@ -84,7 +96,10 @@ void MagneticSensorPWM::handlePWM() {
8496
unsigned long now_us = _micros();
8597

8698
// if falling edge, calculate the pulse length
87-
if (!digitalRead(pinPWM)) pulse_length_us = now_us - last_call_us;
99+
if (!digitalRead(pinPWM)) {
100+
pulse_length_us = now_us - last_call_us;
101+
pulse_timestamp = last_call_us; // angle was sampled at the rising edge of the pulse, so use that timestamp
102+
}
88103

89104
// save the currrent timestamp for the next call
90105
last_call_us = now_us;

src/sensors/MagneticSensorPWM.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class MagneticSensorPWM: public Sensor{
3232
void init();
3333

3434
int pinPWM;
35+
36+
// Interrupt-safe update
37+
void update() override;
3538

3639
// get current angle (rad)
3740
float getSensorAngle() override;
@@ -62,6 +65,7 @@ class MagneticSensorPWM: public Sensor{
6265
// time tracking variables
6366
unsigned long last_call_us;
6467
// unsigned long pulse_length_us;
68+
unsigned long pulse_timestamp;
6569

6670

6771
};

0 commit comments

Comments
 (0)