Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(thermocycler and heater-shaker): grab reset flags on startup #485

Merged
merged 9 commits into from
Dec 3, 2024
Merged
101 changes: 101 additions & 0 deletions stm32-modules/heater-shaker/firmware/motor_task/motor_hardware.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@ motor_hardware_handles *MOTOR_HW_HANDLE = NULL;

static _Atomic int16_t motor_speed_buffer[MOTOR_SPEED_BUFFER_SIZE];
static _Atomic size_t motor_speed_buffer_itr = 0;
uint16_t reset_reason;

enum RCC_FLAGS {
_NONE,
// high speed internal clock ready
HSIRDY, // = 1
// high speed external clock ready
HSERDY, // = 2
// main phase-locked loop clock ready
PLLRDY, // = 3
// hsi48 clock ready
HSI48RDY, // = 4
// low-speed external clock ready
LSERDY, // = 5
// lse clock security system failure
LSECSSD, // = 6
// low-speed internal clock ready
LSIRDY, // = 7
// brown out
BORRST, // = 8
// option byte-loader reset
OBLRST, // = 9
// pin reset
PINRST, // = 10
// software reset
SFTRST, // = 11
// independent watchdog
IWDGRST, // = 12
// window watchdog
WWDGRST, // = 13
// low power reset
LPWRRST, // = 14
};

static void MX_NVIC_Init(void)
{
Expand Down Expand Up @@ -491,6 +524,69 @@ static void DAC_Init(DAC_HandleTypeDef* dac) {
HAL_DAC_SetValue(dac, SOLENOID_DAC_CHANNEL, DAC_ALIGN_8B_R, 0);
}

static void save_reset_reason() {
// check various reset flags to see if the HAL RCC
// reset flag matches any of them
reset_reason = 0;

// high speed internal clock ready
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY)) {
reset_reason |= HSIRDY;
}
// high speed external clock ready
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY)) {
reset_reason |= HSERDY;
}
// main phase-locked loop clock ready
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY)) {
reset_reason |= PLLRDY;
}
// hsi48 clock ready
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY)) {
reset_reason |= HSI48RDY;
}
// low-speed external clock ready
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY)) {
reset_reason |= LSERDY;
}
// lse clock security system failure
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY)) {
reset_reason |= LSECSSD;
}
// low-speed internal clock ready
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY)) {
reset_reason |= LSIRDY;
}
// brown out
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
reset_reason |= BORRST;
}
// option byte-loader reset
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_OBLRST)) {
reset_reason |= OBLRST;
}
// pin reset
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)) {
reset_reason |= PINRST;
}
// software reset
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
reset_reason |= SFTRST;
}
// independent watchdog
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
reset_reason |= IWDGRST;
}
// window watchdog
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
reset_reason |= WWDGRST;
}
// low power reset
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST)) {
reset_reason |= LPWRRST;
}
}

/**
* Initializes the Global MSP.
*/
Expand Down Expand Up @@ -814,6 +910,7 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
}

void motor_hardware_setup(motor_hardware_handles* handles) {
save_reset_reason();
MOTOR_HW_HANDLE = handles;
memset(motor_speed_buffer, 0, sizeof(motor_speed_buffer));
MX_GPIO_Init();
Expand All @@ -840,6 +937,10 @@ void motor_hardware_solenoid_release(DAC_HandleTypeDef* dac1) {
HAL_DAC_SetValue(dac1, SOLENOID_DAC_CHANNEL, DAC_ALIGN_8B_R, 0);
}

uint16_t motor_hardware_reset_reason() {
return reset_reason;
}

void motor_hardware_plate_lock_on(TIM_HandleTypeDef* tim3, float power) {
float power_scale = fabs(power);
if (power_scale > 1.f) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ bool motor_hardware_plate_lock_sensor_read(uint16_t GPIO_Pin);

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim);

/**
* @brief Returns the reason saved upon startup for the last
* reset of the software.
* @return a flag containing the reason for reset.
* */
uint16_t motor_hardware_reset_reason();

/**
* @brief To be called every time the motor control library updates its speed
* measurement. This function adds new speed values to a circular buffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,7 @@ auto MotorPolicy::get_serial_number(void)
-> std::array<char, SYSTEM_SERIAL_NUMBER_LENGTH> {
return _serial.get_serial_number();
}

auto MotorPolicy::last_reset_reason() const -> uint16_t {
return motor_hardware_reset_reason();
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class MotorPolicy {
auto plate_lock_closed_sensor_read() -> bool;
auto get_serial_number(void)
-> std::array<char, SYSTEM_SERIAL_NUMBER_LENGTH>;
auto last_reset_reason() const -> uint16_t;

private:
static constexpr uint16_t MAX_SOLENOID_CURRENT_MA = 330;
Expand Down
8 changes: 8 additions & 0 deletions stm32-modules/heater-shaker/simulator/motor_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,21 @@ struct SimMotorPolicy {
}
}

auto last_reset_reason() -> uint16_t { return reset_reason; }

auto set_last_reset_reason(uint16_t sim_reason) -> void {
reset_reason = sim_reason;
}

private:
int16_t rpm_setpoint = 0;
int16_t rpm_current = 0;
int32_t ramp_rate = DEFAULT_RAMP_RATE_RPM_PER_S;
float sim_plate_lock_power = 0;
bool sim_plate_lock_enabled = false;
bool sim_plate_lock_braked = false;
// Simulated reset reason from HAL RCC flag
uint16_t reset_reason = 0;
};

struct motor_thread::TaskControlBlock {
Expand Down
38 changes: 38 additions & 0 deletions stm32-modules/include/heater-shaker/heater-shaker/gcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,44 @@ struct GetSystemInfo {
}
};

struct GetResetReason {
/*
* M114- GetResetReason retrieves the value of the RCC reset flag
* that was captured at the beginning of the hardware setup
* */
using ParseResult = std::optional<GetResetReason>;
static constexpr auto prefix = std::array{'M', '1', '1', '4'};

template <typename InputIt, typename InLimit>
requires std::forward_iterator<InputIt> &&
std::sized_sentinel_for<InputIt, InLimit>
static auto write_response_into(InputIt buf, InLimit limit, uint16_t reason)
-> InputIt {
int res = 0;
// print a hexadecimal representation of the reset flags
res = snprintf(&*buf, (limit - buf), "M114 Last Reset Reason: %X OK\n",
reason);
if (res <= 0) {
return buf;
}
return buf + res;
}
template <typename InputIt, typename Limit>
requires std::forward_iterator<InputIt> &&
std::sized_sentinel_for<Limit, InputIt>
static auto parse(const InputIt& input, Limit limit)
-> std::pair<ParseResult, InputIt> {
auto working = prefix_matches(input, limit, prefix);
if (working == input) {
return std::make_pair(ParseResult(), input);
}
if (working != limit && !std::isspace(*working)) {
return std::make_pair(ParseResult(), input);
}
return std::make_pair(ParseResult(GetResetReason()), working);
}
};

struct SetSerialNumber {
/*
** Set Serial Number uses a random gcode, M996, adjacent to the firmware
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class HostCommsTask {
gcode::GetPlateLockStateDebug, gcode::SetLEDDebug,
gcode::IdentifyModuleStartLED, gcode::IdentifyModuleStopLED,
gcode::SetOffsetConstants, gcode::GetOffsetConstants,
gcode::DeactivateHeater>;
gcode::DeactivateHeater, gcode::GetResetReason>;
using AckOnlyCache =
AckCache<8, gcode::SetRPM, gcode::SetTemperature,
gcode::SetAcceleration, gcode::SetPIDConstants,
Expand All @@ -66,6 +66,7 @@ class HostCommsTask {
using GetPlateLockStateDebugCache =
AckCache<8, gcode::GetPlateLockStateDebug>;
using GetOffsetConstantsCache = AckCache<8, gcode::GetOffsetConstants>;
using GetResetReasonCache = AckCache<8, gcode::GetResetReason>;

public:
static constexpr size_t TICKS_TO_WAIT_ON_SEND = 10;
Expand All @@ -88,7 +89,9 @@ class HostCommsTask {
// NOLINTNEXTLINE(readability-redundant-member-init)
get_plate_lock_state_debug_cache(),
// NOLINTNEXTLINE(readability-redundant-member-init)
get_offset_constants_cache() {}
get_offset_constants_cache(),
// NOLINTNEXTLINE(readability-redundant-member-init)
get_reset_reason_cache() {}
HostCommsTask(const HostCommsTask& other) = delete;
auto operator=(const HostCommsTask& other) -> HostCommsTask& = delete;
HostCommsTask(HostCommsTask&& other) noexcept = delete;
Expand Down Expand Up @@ -478,6 +481,50 @@ class HostCommsTask {
return std::make_pair(true, tx_into);
}

template <typename InputIt, typename InputLimit>
requires std::forward_iterator<InputIt> &&
std::sized_sentinel_for<InputLimit, InputIt>
auto visit_message(const messages::GetResetReasonResponse& response,
InputIt tx_into, InputLimit tx_limit) -> InputIt {
auto cache_entry =
get_reset_reason_cache.remove_if_present(response.responding_to_id);
return std::visit(
[tx_into, tx_limit, response](auto cache_element) {
using T = std::decay_t<decltype(cache_element)>;
if constexpr (std::is_same_v<std::monostate, T>) {
return errors::write_into(
tx_into, tx_limit,
errors::ErrorCode::BAD_MESSAGE_ACKNOWLEDGEMENT);
} else {
return cache_element.write_response_into(tx_into, tx_limit,
response.reason);
}
},
cache_entry);
}

template <typename InputIt, typename InputLimit>
requires std::forward_iterator<InputIt> &&
std::sized_sentinel_for<InputLimit, InputIt>
auto visit_gcode(const gcode::GetResetReason& gcode, InputIt tx_into,
InputLimit tx_limit) -> std::pair<bool, InputIt> {
auto id = get_reset_reason_cache.add(gcode);
if (id == 0) {
return std::make_pair(
false, errors::write_into(tx_into, tx_limit,
errors::ErrorCode::GCODE_CACHE_FULL));
}
auto message = messages::GetResetReasonMessage{.id = id};
if (!task_registry->motor->get_message_queue().try_send(
message, TICKS_TO_WAIT_ON_SEND)) {
auto wrote_to = errors::write_into(
tx_into, tx_limit, errors::ErrorCode::INTERNAL_QUEUE_FULL);
get_reset_reason_cache.remove_if_present(id);
return std::make_pair(false, wrote_to);
}
return std::make_pair(true, tx_into);
}

template <typename InputIt, typename InputLimit>
requires std::forward_iterator<InputIt> &&
std::sized_sentinel_for<InputLimit, InputIt>
Expand Down Expand Up @@ -1053,6 +1100,7 @@ class HostCommsTask {
GetPlateLockStateCache get_plate_lock_state_cache;
GetPlateLockStateDebugCache get_plate_lock_state_debug_cache;
GetOffsetConstantsCache get_offset_constants_cache;
GetResetReasonCache get_reset_reason_cache;
bool may_connect_latch = true;
};

Expand Down
14 changes: 12 additions & 2 deletions stm32-modules/include/heater-shaker/heater-shaker/messages.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ struct GetOffsetConstantsResponse {
double const_b, const_c;
};

struct GetResetReasonMessage {
uint32_t id;
};

struct GetResetReasonResponse {
uint32_t responding_to_id;
uint16_t reason;
};

struct DeactivateHeaterMessage {
uint32_t id;
};
Expand All @@ -295,7 +304,7 @@ using MotorMessage = ::std::variant<
ActuateSolenoidMessage, SetPlateLockPowerMessage, OpenPlateLockMessage,
ClosePlateLockMessage, SetPIDConstantsMessage, PlateLockComplete,
GetPlateLockStateMessage, GetPlateLockStateDebugMessage,
CheckPlateLockStatusMessage>;
CheckPlateLockStatusMessage, GetResetReasonMessage>;
using SystemMessage =
::std::variant<std::monostate, EnterBootloaderMessage, AcknowledgePrevious,
SetSerialNumberMessage, GetSystemInfoMessage, SetLEDMessage,
Expand All @@ -307,5 +316,6 @@ using HostCommsMessage =
ErrorMessage, GetTemperatureResponse, GetRPMResponse,
GetTemperatureDebugResponse, ForceUSBDisconnectMessage,
GetPlateLockStateResponse, GetPlateLockStateDebugResponse,
GetSystemInfoResponse, GetOffsetConstantsResponse>;
GetSystemInfoResponse, GetOffsetConstantsResponse,
GetResetReasonResponse>;
}; // namespace messages
11 changes: 11 additions & 0 deletions stm32-modules/include/heater-shaker/heater-shaker/motor_task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,17 @@ class MotorTask {
static_cast<void>(get_message_queue().try_send(check_state_message));
}

template <MotorExecutionPolicy Policy>
auto visit_message(const messages::GetResetReasonMessage& msg,
Policy& policy) -> void {
auto reason = policy.last_reset_reason();

auto response = messages::GetResetReasonResponse{
.responding_to_id = msg.id, .reason = reason};
static_cast<void>(task_registry->comms->get_message_queue().try_send(
messages::HostCommsMessage(response)));
}

template <typename Policy>
auto visit_message(const messages::CheckPlateLockStatusMessage& msg,
Policy& policy) -> void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ class TestMotorPolicy {
auto get_serial_number(void)
-> std::array<char, SYSTEM_SERIAL_NUMBER_LENGTH>;

auto last_reset_reason() const -> uint16_t { return _reset_reason; }

auto set_last_reset_reason(uint16_t sim_reason) -> void {
_reset_reason = sim_reason;
}

private:
int16_t target_rpm;
int16_t current_rpm;
Expand All @@ -76,4 +82,5 @@ class TestMotorPolicy {
double overridden_kp = 0.0;
double overridden_kd = 0.0;
bool plate_lock_braked = false;
uint16_t _reset_reason = 0;
};
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ void motor_hardware_seal_switch_set_disarmed();
*/
void motor_hardware_seal_switch_interrupt();

/**
* @brief Returns the reason saved upon startup for the last
* reset of the software.
* @return a flag containing the reasons for reset.
* */
uint16_t motor_hardware_reset_reason();

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
Expand Down
Loading
Loading