File STM32Board.cpp
File List > Firmware > Libraries > STM32Board > STM32Board.cpp
Go to the documentation of this file
#ifdef ARDUINO_ARCH_STM32
#include "STM32Board.h"
#include <CANProtocol.h> // for CanStatus enum values
// ── Private types ─────────────────────────────────────────────────────────────
// Under STM32BOARD_TEST this enum lives in the header (so currentState() can return it);
// keep the two definitions identical.
#ifndef STM32BOARD_TEST
enum class LedState {
OFF,
BOOTING,
NORMAL,
CONNECTED,
CAN_ERROR,
BUS_OFF,
WARNING,
};
#endif
// ── Private state ─────────────────────────────────────────────────────────────
static LedState _state = LedState::OFF;
static bool _blinkPhase = false;
static uint32_t _ledLastMs = 0;
static bool _debugOn = false;
static HardwareSerial _diag(PA10, PA9); // USART1: RX=PA10, TX=PA9
static CAN_HandleTypeDef _hcan;
// Derived-state inputs — arbitrated by _recompute() into the effective _state.
// Storing inputs (not the final state) lets CONNECTED survive a transient CAN fault and
// re-engage on recovery, and lets WARNING be cleared independently.
static CanStatus _canStatus = CanStatus::STARTING;
static bool _linkActive = false;
static uint32_t _linkLastMs = 0;
static bool _warning = false;
static bool _begun = false;
static constexpr uint32_t LINK_DECAY_MS = 500;
// ── Private helpers ───────────────────────────────────────────────────────────
static uint16_t _blinkPeriodFor(LedState s) {
switch (s) {
case LedState::BOOTING: return 1000;
case LedState::NORMAL: return 1000;
case LedState::CAN_ERROR: return 250;
case LedState::WARNING: return 500;
default: return 0; // solid or off — no timer needed
}
}
static void _applyLed() {
switch (_state) {
case LedState::OFF:
digitalWrite(STM32Board::PIN_LED_RED, LOW);
digitalWrite(STM32Board::PIN_LED_GREEN, LOW);
break;
case LedState::BOOTING:
case LedState::CAN_ERROR:
digitalWrite(STM32Board::PIN_LED_RED, _blinkPhase ? HIGH : LOW);
digitalWrite(STM32Board::PIN_LED_GREEN, LOW);
break;
case LedState::NORMAL:
digitalWrite(STM32Board::PIN_LED_RED, LOW);
digitalWrite(STM32Board::PIN_LED_GREEN, _blinkPhase ? HIGH : LOW);
break;
case LedState::CONNECTED:
digitalWrite(STM32Board::PIN_LED_RED, LOW);
digitalWrite(STM32Board::PIN_LED_GREEN, HIGH);
break;
case LedState::BUS_OFF:
digitalWrite(STM32Board::PIN_LED_RED, HIGH);
digitalWrite(STM32Board::PIN_LED_GREEN, LOW);
break;
case LedState::WARNING:
digitalWrite(STM32Board::PIN_LED_RED, _blinkPhase ? HIGH : LOW);
digitalWrite(STM32Board::PIN_LED_GREEN, _blinkPhase ? LOW : HIGH);
break;
}
}
// Arbitrate the inputs into the effective LED state by fixed precedence (highest first).
// Sole writer of _state. Resets the blink phase and repaints only on an actual transition,
// preserving the invariant that the phase resets only when the state genuinely changes.
//
// not begun → OFF
// CAN BUS_OFF → BUS_OFF (solid red) } CAN faults outrank everything —
// CAN TX_ERROR → CAN_ERROR (fast red) } a dead/erroring bus must not be
// CAN STARTING → BOOTING (slow red) } masked by an app-layer warning.
// warning → WARNING (amber alt)
// NORMAL + linkActive → CONNECTED (solid green) ← only reachable when CAN is NORMAL
// NORMAL → NORMAL (slow green)
static void _recompute() {
LedState next;
if (!_begun) next = LedState::OFF;
else if (_canStatus == CanStatus::BUS_OFF) next = LedState::BUS_OFF;
else if (_canStatus == CanStatus::TX_ERROR) next = LedState::CAN_ERROR;
else if (_canStatus == CanStatus::STARTING) next = LedState::BOOTING;
else if (_warning) next = LedState::WARNING;
else if (_linkActive) next = LedState::CONNECTED;
else next = LedState::NORMAL;
if (next != _state) {
_state = next;
_blinkPhase = false;
_ledLastMs = millis();
_applyLed();
}
}
// HAL weak-symbol override — defines CAN GPIO once for all OpenSkyhawk STM32 boards.
extern "C" void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan_p) {
if (hcan_p->Instance != CAN1) return;
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio = {};
gpio.Pin = GPIO_PIN_12;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);
gpio.Pin = GPIO_PIN_11;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &gpio);
}
// ── Public API ────────────────────────────────────────────────────────────────
namespace STM32Board {
void begin() {
pinMode(PIN_LED_RED, OUTPUT);
pinMode(PIN_LED_GREEN, OUTPUT);
_begun = true;
_canStatus = CanStatus::STARTING;
_recompute(); // → BOOTING
_diag.begin(115200);
analogReadResolution(16); // STM32duino defaults to 10-bit; 16-bit fills uint16_t directly for PinRef
// 500 kbps on APB1 @ 36 MHz: prescaler=4, BS1=13TQ, BS2=4TQ → 18TQ total.
// SJW=4TQ required for Blue Pill clone crystal tolerance (validated Experiment B).
_hcan.Instance = CAN1;
_hcan.Init.Prescaler = 4;
_hcan.Init.Mode = CAN_MODE_NORMAL;
_hcan.Init.SyncJumpWidth = CAN_SJW_4TQ;
_hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
_hcan.Init.TimeSeg2 = CAN_BS2_4TQ;
_hcan.Init.TimeTriggeredMode = DISABLE;
_hcan.Init.AutoBusOff = ENABLE; // hardware recovery ~3 ms, no firmware action needed
_hcan.Init.AutoWakeUp = DISABLE;
_hcan.Init.AutoRetransmission = DISABLE; // prevents runaway bus-off on unACKed frames
_hcan.Init.ReceiveFifoLocked = DISABLE;
_hcan.Init.TransmitFifoPriority = DISABLE;
HAL_CAN_Init(&_hcan);
}
void setDebug(bool on) { _debugOn = on; }
bool isDebug() { return _debugOn; }
void tick() {
uint32_t now = millis();
// Decay the data-flowing link → drop CONNECTED back to NORMAL after a quiet gap.
if (_linkActive && (now - _linkLastMs) >= LINK_DECAY_MS) {
_linkActive = false;
_recompute();
}
uint16_t period = _blinkPeriodFor(_state);
if (period > 0 && (now - _ledLastMs) >= (uint32_t)(period / 2)) {
_blinkPhase = !_blinkPhase;
_ledLastMs = now;
_applyLed();
}
}
void onCanStatus(CanStatus status) {
_canStatus = status;
_recompute();
}
void setWarning(bool on) {
_warning = on;
_recompute();
}
void setLinkActive(bool active) {
if (active) {
_linkActive = true;
_linkLastMs = millis();
} else {
_linkActive = false;
}
_recompute();
}
void log(const char* msg) {
if (_debugOn) _diag.println(msg);
}
HardwareSerial& diagSerial() { return _diag; }
CAN_HandleTypeDef* canHandle() { return &_hcan; }
#ifdef STM32BOARD_TEST
LedState currentState() { return _state; }
#endif
} // namespace STM32Board
#endif // ARDUINO_ARCH_STM32