|
| 1 | +#include "HallSensorAny.h" |
| 2 | +#include "common/time_utils.h" |
| 3 | +#include "communication/SimpleFOCDebug.h" |
| 4 | + |
| 5 | + |
| 6 | +// seq 1 > 5 > 4 > 6 > 2 > 3 > 1 000 001 010 011 100 101 110 111 |
| 7 | +const int8_t ELECTRIC_SECTORS[8] = { -1, 0, 4, 5, 2, 1, 3 , -1 }; |
| 8 | + |
| 9 | +/* |
| 10 | + HallSensorAny(int hallA, int hallB , int cpr, int index) |
| 11 | + - hallA, hallB, hallC - HallSensorAny A, B and C pins |
| 12 | + - pp - pole pairs |
| 13 | +*/ |
| 14 | +HallSensorAny::HallSensorAny(int _hallA, int _hallB, int _hallC, int _pp, HallType _hall_type){ |
| 15 | + |
| 16 | + // hardware pins |
| 17 | + pinA = _hallA; |
| 18 | + pinB = _hallB; |
| 19 | + pinC = _hallC; |
| 20 | + hall_type = _hall_type; |
| 21 | + last_print_type = hall_type; |
| 22 | + for (size_t i = 0; i < sizeof(previous_states); i++) |
| 23 | + { |
| 24 | + previous_states[i] = -1; |
| 25 | + } |
| 26 | + |
| 27 | + |
| 28 | + // hall has 6 segments per electrical revolution |
| 29 | + cpr = _pp * 6; |
| 30 | + |
| 31 | + // extern pullup as default |
| 32 | + pullup = Pullup::USE_EXTERN; |
| 33 | +} |
| 34 | + |
| 35 | +// HallSensorAny interrupt callback functions |
| 36 | +// A channel |
| 37 | +void HallSensorAny::handleA() { |
| 38 | + A_active= digitalRead(pinA); |
| 39 | + updateState(); |
| 40 | +} |
| 41 | +// B channel |
| 42 | +void HallSensorAny::handleB() { |
| 43 | + B_active = digitalRead(pinB); |
| 44 | + updateState(); |
| 45 | +} |
| 46 | + |
| 47 | +// C channel |
| 48 | +void HallSensorAny::handleC() { |
| 49 | + C_active = digitalRead(pinC); |
| 50 | + updateState(); |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Updates the state and sector following an interrupt |
| 55 | + */ |
| 56 | +void HallSensorAny::updateState() { |
| 57 | + int8_t new_hall_state = C_active + (B_active << 1) + (A_active << 2); |
| 58 | + // glitch avoidance #1 - sometimes we get an interrupt but pins haven't changed |
| 59 | + if (new_hall_state == hall_state_raw) return; |
| 60 | + hall_state_raw = new_hall_state; |
| 61 | + |
| 62 | + static const int num_previous_states = sizeof(previous_states); |
| 63 | + |
| 64 | + //flip a line maybe |
| 65 | + if (hall_type != HallType::UNKNOWN) |
| 66 | + { |
| 67 | + new_hall_state ^= static_cast<int8_t>(hall_type); |
| 68 | + } |
| 69 | + |
| 70 | + long new_pulse_timestamp = _micros(); |
| 71 | + if (hall_type == HallType::UNKNOWN) //Store previous steps for hall config detection |
| 72 | + { |
| 73 | + for (int i = num_previous_states - 2; i >= 0; i--) |
| 74 | + { |
| 75 | + previous_states[i+1] = previous_states[i]; |
| 76 | + } |
| 77 | + previous_states[0] = new_hall_state; |
| 78 | + //7 and 0 are illegal in 120deg mode, so we're gonna try to see which line hel up during that time and flip it so it doesn't happen |
| 79 | + if ((previous_states[1] == 0b111 || previous_states[1] == 0b000) && previous_states[2] != -1) |
| 80 | + { |
| 81 | + if (previous_states[2] == previous_states[0]) |
| 82 | + { |
| 83 | + //went back, can't do anything |
| 84 | + } |
| 85 | + else |
| 86 | + { |
| 87 | + hall_type = static_cast<HallType>((0b111 - previous_states[0] ^ previous_states[2])%8); |
| 88 | + previous_states[0] ^= static_cast<int8_t>(hall_type); |
| 89 | + } |
| 90 | + } |
| 91 | + if (abs(electric_rotations) > 2) |
| 92 | + { |
| 93 | + hall_type = HallType::HALL_120; |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | + int8_t new_electric_sector; |
| 101 | + new_electric_sector = ELECTRIC_SECTORS[new_hall_state]; |
| 102 | + int8_t electric_sector_dif = new_electric_sector - electric_sector; |
| 103 | + if (electric_sector_dif > 3) { |
| 104 | + //underflow |
| 105 | + direction = Direction::CCW; |
| 106 | + electric_rotations += direction; |
| 107 | + } else if (electric_sector_dif < (-3)) { |
| 108 | + //overflow |
| 109 | + direction = Direction::CW; |
| 110 | + electric_rotations += direction; |
| 111 | + } else { |
| 112 | + direction = (new_electric_sector > electric_sector)? Direction::CW : Direction::CCW; |
| 113 | + } |
| 114 | + electric_sector = new_electric_sector; |
| 115 | + |
| 116 | + // glitch avoidance #2 changes in direction can cause velocity spikes. Possible improvements needed in this area |
| 117 | + if (direction == old_direction) { |
| 118 | + // not oscilating or just changed direction |
| 119 | + pulse_diff = new_pulse_timestamp - pulse_timestamp; |
| 120 | + } else { |
| 121 | + pulse_diff = 0; |
| 122 | + } |
| 123 | + |
| 124 | + pulse_timestamp = new_pulse_timestamp; |
| 125 | + total_interrupts++; |
| 126 | + old_direction = direction; |
| 127 | + if (onSectorChange != nullptr) onSectorChange(electric_sector); |
| 128 | +} |
| 129 | + |
| 130 | +/** |
| 131 | + * Optionally set a function callback to be fired when sector changes |
| 132 | + * void onSectorChange(int sector) { |
| 133 | + * ... // for debug or call driver directly? |
| 134 | + * } |
| 135 | + * sensor.attachSectorCallback(onSectorChange); |
| 136 | + */ |
| 137 | +void HallSensorAny::attachSectorCallback(void (*_onSectorChange)(int sector)) { |
| 138 | + onSectorChange = _onSectorChange; |
| 139 | +} |
| 140 | + |
| 141 | + |
| 142 | + |
| 143 | +// Sensor update function. Safely copy volatile interrupt variables into Sensor base class state variables. |
| 144 | +void HallSensorAny::update() { |
| 145 | + // Copy volatile variables in minimal-duration blocking section to ensure no interrupts are missed |
| 146 | + if (use_interrupt){ |
| 147 | + noInterrupts(); |
| 148 | + }else{ |
| 149 | + A_active = digitalRead(pinA); |
| 150 | + B_active = digitalRead(pinB); |
| 151 | + C_active = digitalRead(pinC); |
| 152 | + updateState(); |
| 153 | + } |
| 154 | + |
| 155 | + angle_prev_ts = pulse_timestamp; |
| 156 | + long last_electric_rotations = electric_rotations; |
| 157 | + int8_t last_electric_sector = electric_sector; |
| 158 | + if (use_interrupt) interrupts(); |
| 159 | + angle_prev = ((float)((last_electric_rotations * 6 + last_electric_sector) % cpr) / (float)cpr) * _2PI ; |
| 160 | + full_rotations = (int32_t)((last_electric_rotations * 6 + last_electric_sector) / cpr); |
| 161 | + if (last_print_type != hall_type) |
| 162 | + { |
| 163 | + last_print_type = hall_type; |
| 164 | + switch (hall_type) |
| 165 | + { |
| 166 | + case HallType::HALL_120 : |
| 167 | + SIMPLEFOC_DEBUG("HALL: Found type: HALL_120"); |
| 168 | + break; |
| 169 | + case HallType::HALL_60A : |
| 170 | + SIMPLEFOC_DEBUG("HALL: Found type: HALL_60A"); |
| 171 | + break; |
| 172 | + case HallType::HALL_60B : |
| 173 | + SIMPLEFOC_DEBUG("HALL: Found type: HALL_60B"); |
| 174 | + break; |
| 175 | + case HallType::HALL_60C : |
| 176 | + SIMPLEFOC_DEBUG("HALL: Found type: HALL_60C"); |
| 177 | + break; |
| 178 | + |
| 179 | + default: |
| 180 | + SIMPLEFOC_DEBUG("HALL: Type unknown! Wtf!"); |
| 181 | + break; |
| 182 | + } |
| 183 | + |
| 184 | + } |
| 185 | + |
| 186 | +} |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | +/* |
| 191 | + Shaft angle calculation |
| 192 | + TODO: numerical precision issue here if the electrical rotation overflows the angle will be lost |
| 193 | +*/ |
| 194 | +float HallSensorAny::getSensorAngle() { |
| 195 | + return ((float)(electric_rotations * 6 + electric_sector) / (float)cpr) * _2PI ; |
| 196 | +} |
| 197 | + |
| 198 | +/* |
| 199 | + Shaft velocity calculation |
| 200 | + function using mixed time and frequency measurement technique |
| 201 | +*/ |
| 202 | +float HallSensorAny::getVelocity(){ |
| 203 | + noInterrupts(); |
| 204 | + long last_pulse_timestamp = pulse_timestamp; |
| 205 | + long last_pulse_diff = pulse_diff; |
| 206 | + interrupts(); |
| 207 | + if (last_pulse_diff == 0 || ((long)(_micros() - last_pulse_timestamp) > last_pulse_diff*2) ) { // last velocity isn't accurate if too old |
| 208 | + return 0; |
| 209 | + } else { |
| 210 | + return direction * (_2PI / (float)cpr) / (last_pulse_diff / 1000000.0f); |
| 211 | + } |
| 212 | + |
| 213 | +} |
| 214 | + |
| 215 | +int HallSensorAny::needsSearch() |
| 216 | +{ |
| 217 | + return hall_type == HallType::UNKNOWN; |
| 218 | +} |
| 219 | + |
| 220 | +// HallSensorAny initialisation of the hardware pins |
| 221 | +// and calculation variables |
| 222 | +void HallSensorAny::init(){ |
| 223 | + // initialise the electrical rotations to 0 |
| 224 | + electric_rotations = 0; |
| 225 | + |
| 226 | + // HallSensorAny - check if pullup needed for your HallSensorAny |
| 227 | + if(pullup == Pullup::USE_INTERN){ |
| 228 | + pinMode(pinA, INPUT_PULLUP); |
| 229 | + pinMode(pinB, INPUT_PULLUP); |
| 230 | + pinMode(pinC, INPUT_PULLUP); |
| 231 | + }else{ |
| 232 | + pinMode(pinA, INPUT); |
| 233 | + pinMode(pinB, INPUT); |
| 234 | + pinMode(pinC, INPUT); |
| 235 | + } |
| 236 | + |
| 237 | + // init hall_state |
| 238 | + A_active = digitalRead(pinA); |
| 239 | + B_active = digitalRead(pinB); |
| 240 | + C_active = digitalRead(pinC); |
| 241 | + updateState(); |
| 242 | + |
| 243 | + pulse_timestamp = _micros(); |
| 244 | + |
| 245 | + // we don't call Sensor::init() here because init is handled in HallSensorAny class. |
| 246 | +} |
| 247 | + |
| 248 | +// function enabling hardware interrupts for the callback provided |
| 249 | +// if callback is not provided then the interrupt is not enabled |
| 250 | +void HallSensorAny::enableInterrupts(void (*doA)(), void(*doB)(), void(*doC)()){ |
| 251 | + // attach interrupt if functions provided |
| 252 | + |
| 253 | + // A, B and C callback |
| 254 | + if(doA != nullptr) attachInterrupt(digitalPinToInterrupt(pinA), doA, CHANGE); |
| 255 | + if(doB != nullptr) attachInterrupt(digitalPinToInterrupt(pinB), doB, CHANGE); |
| 256 | + if(doC != nullptr) attachInterrupt(digitalPinToInterrupt(pinC), doC, CHANGE); |
| 257 | + |
| 258 | + use_interrupt = true; |
| 259 | +} |
0 commit comments