From fc6fbd083dd6526717582fa356ac4eed040c4185 Mon Sep 17 00:00:00 2001 From: Michael Ehrenreich Date: Thu, 28 Apr 2022 21:39:02 +0200 Subject: [PATCH] WIP CAN flashing support --- config_partition.h | 5 + flasher/CANFlasher.hpp | 96 +++++++++++++++++++ flasher/Flasher.hpp | 203 +++++++++++++++++++++++++++++++++++++++++ main.cpp | 58 +++++++++++- 4 files changed, 359 insertions(+), 3 deletions(-) create mode 100644 config_partition.h create mode 100644 flasher/CANFlasher.hpp create mode 100644 flasher/Flasher.hpp diff --git a/config_partition.h b/config_partition.h new file mode 100644 index 0000000..fd3496d --- /dev/null +++ b/config_partition.h @@ -0,0 +1,5 @@ +#include "ab_boot/ab_boot.h" + +struct config_partition { + struct ab_boot_config ab_boot_config; +}; diff --git a/flasher/CANFlasher.hpp b/flasher/CANFlasher.hpp new file mode 100644 index 0000000..2d6fe72 --- /dev/null +++ b/flasher/CANFlasher.hpp @@ -0,0 +1,96 @@ +#include +#include + +#include "Flasher.hpp" + +namespace can_flasher { + namespace { + flasher::Result handle_start(uint8_t *data, size_t length) { + uintptr_t address; + if (length != sizeof(address)) + return flasher::Result::InvalidParameter; + + memcpy(&address, data, sizeof(address)); + return flasher::start(address); + } + + flasher::Result handle_write(uint8_t *data, size_t length) { + return flasher::write(data, length); + } + } + + void handle(uint8_t *data, size_t length) { + using enum flasher::State; + + if (length < 1) { + flasher::flasher_state_callback(flasher::get_state(), flasher::Result::InvalidParameter, 0); + return; + } + + flasher::State target_state = (flasher::State)*data; + data += 1; + length -= 1; + + flasher::Result result; + switch (target_state) { + case Idle: + flasher::init(); + result = flasher::Result::Success; + break; + case Erasing: + result = handle_start(data, length); + break; + case Writing: + result = handle_write(data, length); + break; + default: + result = flasher::Result::InvalidParameter; + } + + flasher::flasher_state_callback(flasher::get_state(), result, 0); + } + + constexpr size_t FeedbackSize = + sizeof(flasher::State) + sizeof(flasher::Result) + sizeof(uint32_t); + static_assert(FeedbackSize <= 8); + + struct { + uint32_t last_sent; + std::atomic updated; + bool valid; + uint8_t data[FeedbackSize]; + } feedback; + + void generate_feedback(flasher::State state, flasher::Result result, uint32_t arg) { + uint8_t *ptr = feedback.data; + std::memcpy(ptr, &state, sizeof(state)); + ptr += sizeof(state); + std::memcpy(ptr, &result, sizeof(result)); + ptr += sizeof(result); + std::memcpy(ptr, &arg, sizeof(arg)); + // this works because poll_feedback can't interrupt us + feedback.updated.store(true); + } + + bool poll_feedback(uint32_t now, uint8_t *out) { + if (feedback.updated.load() || + (feedback.valid && now - feedback.last_sent >= 500)) { + feedback.valid = true; + do { + feedback.updated.store(false); + std::memcpy(out, feedback.data, sizeof(feedback.data)); + // this works because we cannot interrupt generate_feedback + } while (feedback.updated.load()); + feedback.last_sent = now; + return true; + } + + return false; + } +} + +namespace flasher { +void flasher_state_callback(flasher::State state, flasher::Result result, uint32_t arg) { + can_flasher::generate_feedback(state, result, arg); +} +} diff --git a/flasher/Flasher.hpp b/flasher/Flasher.hpp new file mode 100644 index 0000000..a8188c1 --- /dev/null +++ b/flasher/Flasher.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include +#include + +#include "stm32f1xx_hal.h" + +#include "ab_boot/ab_boot.h" +#include "stm32f1xx_hal_def.h" +#include "stm32f1xx_hal_flash.h" +#include "stm32f1xx_hal_flash_ex.h" + + +namespace flasher { + enum class State : uint8_t { + Idle, + Erasing, + Waiting, + Writing, + Error + }; + + namespace { + enum class FlashRegion { + Invalid, + Bootloader, + AppA, + AppB, + Config + }; + + FlashRegion region_for_address(uintptr_t address) { + using enum FlashRegion; + + if (address >= FLASH_START && address < APP_A_START) { + return Bootloader; + } else if (address >= APP_A_START && address < (APP_A_START + APP_SIZE)) { + return AppA; + } else if (address >= APP_B_START && address < (APP_B_START + APP_SIZE)) { + return AppB; + } else if (address >= CONFIG_START && address < (CONFIG_START + CONFIG_SIZE)) { + return Config; + } + + return Invalid; + } + + size_t region_size(FlashRegion region) { + using enum FlashRegion; + + switch (region) { + case Bootloader: + return AB_BOOT_SIZE; + case AppA: + case AppB: + return APP_SIZE; + case Config: + return CONFIG_SIZE; + default: + return 0; + } + } + + bool is_valid_start_address(uint32_t address) { + return address == FLASH_START || address == APP_A_START || + address == APP_B_START || address == CONFIG_START; + } + + static State state_; + static FlashRegion region_; + static uintptr_t address_; + static uint8_t write_size_; + } + + enum class Result : uint8_t { + Success, + RegionNotAllowed, + InvalidParameter, + InvalidState, + WriteError, + InProgress + }; + + void flasher_state_callback(State state, Result result, uint32_t arg); + + void init() { + state_ = State::Idle; + region_ = FlashRegion::Invalid; + address_ = 0; + write_size_ = 0; + HAL_FLASH_Lock(); + } + + Result start(uintptr_t address) { + if (state_ != State::Idle) + return Result::InvalidState; + + if (!is_valid_start_address(address)) { + return Result::InvalidParameter; + } + + FlashRegion flashed_region = region_for_address(address); + FlashRegion running_region = region_for_address((uintptr_t)&start); + + if (flashed_region == FlashRegion::Invalid) { + return Result::InvalidParameter; + } + + if (flashed_region == running_region) { + // prohibit flashing the currently running app + return Result::RegionNotAllowed; + } + + size_t size = region_size(flashed_region); + FLASH_EraseInitTypeDef ferase = { + .TypeErase = FLASH_TYPEERASE_PAGES, + .PageAddress = address, + .NbPages = size / FLASH_PAGE_SIZE + }; + + HAL_FLASH_Unlock(); + if (HAL_FLASHEx_Erase_IT(&ferase) != HAL_OK) { + return Result::WriteError; + } + + state_ = State::Erasing; + region_ = flashed_region; + address_ = address; + + return Result::InProgress; + } + + Result write(uint8_t *data, uint8_t length) { + if (state_ != State::Waiting) + return Result::InvalidState; + + uint32_t program_type; + switch (length) { + case 2: + program_type = FLASH_PROC_PROGRAMHALFWORD; + break; + case 4: + program_type = FLASH_PROC_PROGRAMWORD; + break; + case 8: + program_type = FLASH_PROC_PROGRAMDOUBLEWORD; + break; + default: + return Result::InvalidParameter; + } + + uint64_t data_int = 0; + memcpy(&data_int, data, length); + + if (HAL_FLASH_Program_IT(program_type, address_, data_int) != HAL_OK) + return Result::WriteError; + + state_ = State::Writing; + write_size_ = length; + + return Result::InProgress; + } + + State get_state() { + return state_; + } + + void flash_callback(bool success) { + using enum State; + + // Ignore if we are in Idle state, could be the result of + // a cancelled operation. + if (state_ == Idle) + return; + + if (success) { + switch (state_) { + case Writing: + case Erasing: + address_ += write_size_; + flasher_state_callback(state_, Result::Success, address_); + state_ = Waiting; + write_size_ = 0; + break; + default: + // Spurious callback + HAL_FLASH_Lock(); + state_ = Error; + } + } else { + switch (state_) { + case Writing: + case Erasing: + flasher_state_callback(state_, Result::WriteError, address_); + [[fallthrough]]; + default: + // Spurious callback + HAL_FLASH_Lock(); + state_ = Error; + } + } + } +} diff --git a/main.cpp b/main.cpp index 4cdd03d..fd18e75 100644 --- a/main.cpp +++ b/main.cpp @@ -39,6 +39,8 @@ #endif #ifdef FEATURE_CAN #include "bobbycar-can.h" +#include "flasher/Flasher.hpp" +#include "flasher/CANFlasher.hpp" #endif extern "C" { @@ -112,7 +114,7 @@ CAN_HandleTypeDef CanHandle; #define CANx_TX_IRQn USB_HP_CAN1_TX_IRQn #define CANx_TX_IRQHandler USB_HP_CAN1_TX_IRQHandler -constexpr bool doDelayWithCanPoll = false; +constexpr bool doDelayWithCanPoll = true; #endif #ifdef LOG_TO_SERIAL @@ -249,6 +251,7 @@ void sendFeedback(); void parseCanCommand(); void applyIncomingCanMessage(); void sendCanFeedback(); +void sendFlasherFeedback(); #endif #ifdef FEATURE_BUTTON @@ -379,6 +382,7 @@ int main() while ((HAL_GetTick() - tickstart) < wait) { applyIncomingCanMessage(); + sendFlasherFeedback(); } }; @@ -406,6 +410,7 @@ int main() parseCanCommand(); sendCanFeedback(); + sendFlasherFeedback(); #endif #ifdef FEATURE_BUTTON @@ -450,6 +455,7 @@ void updateMotors() if (offsetcount < 2000) // calibrate ADC offsets { offsetcount++; + // TODO this is not an average offsetrl1 = (adc_buffer.rl1 + offsetrl1) / 2; offsetrl2 = (adc_buffer.rl2 + offsetrl2) / 2; offsetrr1 = (adc_buffer.rr1 + offsetrr1) / 2; @@ -1404,8 +1410,8 @@ void communicationTimeout() { applyDefaultSettings(); - buzzer.freq = 24; - buzzer.pattern = 1; + //buzzer.freq = 24; + //buzzer.pattern = 1; HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET); } @@ -1767,6 +1773,31 @@ void sendCanFeedback() arr[whichToSend++](); } + +void sendFlasherFeedback() { + using bobbycar::protocol::can::MotorController; + static CAN_TxHeaderTypeDef header = { + .StdId = MotorController::Command::FlasherCtrl, + .ExtId = 0, + .IDE = CAN_ID_STD, + .RTR = CAN_RTR_DATA, + .DLC = can_flasher::FeedbackSize, + .TransmitGlobalTime = DISABLE + }; + + if (HAL_CAN_GetTxMailboxesFreeLevel(&CanHandle) == 0) + return; + + uint8_t buf[8]; + if (!can_flasher::poll_feedback(HAL_GetTick(), buf)) + return; + + uint32_t TxMailbox; + if (const auto result = HAL_CAN_AddTxMessage(&CanHandle, &header, buf, &TxMailbox); result != HAL_OK) { + myPrintf("HAL_CAN_AddTxMessage() failed with %i", result); + //while (true); + } +} #endif #ifdef FEATURE_BUTTON @@ -2061,4 +2092,25 @@ extern "C" void CANx_TX_IRQHandler(void) using namespace bobbycar::controller; HAL_CAN_IRQHandler(&CanHandle); } + +// CAN flasher stuff +extern "C" void FLASH_IRQHandler(void) +{ + HAL_FLASH_IRQHandler(); +} + +extern "C" void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue) +{ + (void)ReturnValue; + + flasher::flash_callback(true); +} + +extern "C" void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue) +{ + (void)ReturnValue; + + flasher::flash_callback(false); +} + #endif