mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-05-27 06:10:05 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41fd62500e | |||
| f37df48fa2 | |||
| ceba801573 | |||
| 4d58f00bf8 | |||
| 8eb753068a | |||
| a7e93d1145 | |||
| 0f687cdbb4 | |||
| d773946f76 |
@@ -0,0 +1,280 @@
|
||||
AM32 ESC Flasher Module - Implementation Plan
|
||||
|
||||
Context
|
||||
|
||||
PX4 has no upstream support for AM32 ESC firmware updates. Matt's branch (jake/matt_firefly_am32_flasher) proves the concept works but has architectural issues: firmware baked into the build as a C
|
||||
array, ~100-state machine with poor error recovery, tight coupling to DShot via a 4-message uORB handshake, no GCS progress reporting, and ~826 lines of flashing logic injected into the DShot driver.
|
||||
|
||||
Goal: Clean, upstream-quality esc_flasher module that reads firmware from SD card, is triggered via MAVLink command from QGC, reports per-ESC progress via a custom MAVLink message, and requires a
|
||||
reboot to resume normal DShot operation. Structured to allow future USB passthrough (4-way interface) extension.
|
||||
|
||||
Architecture Overview
|
||||
|
||||
QGC PX4
|
||||
│ │
|
||||
├─COMMAND_LONG──────────────►│ vehicle_command uORB
|
||||
│ MAV_CMD_ESC_FW_UPDATE │ │
|
||||
│ │ esc_flasher module
|
||||
│ │ (dormant at boot, wakes on cmd)
|
||||
│ │ │
|
||||
│ │ 1. Validate (disarmed, files exist)
|
||||
│ │ 2. ACK command
|
||||
│ │ 3. Stop DShot module
|
||||
│ │ 4. Take over motor GPIOs
|
||||
│ │ 5. For each ESC:
|
||||
│ │ - Bootloader handshake
|
||||
│ │ - Read device info → pick FW file
|
||||
│◄─ESC_FW_UPDATE_STATUS──────│ - Flash 256-byte chunks
|
||||
│ (per-ESC progress) │ - Send RUN_APP
|
||||
│ │ 6. Publish REBOOT_REQUIRED
|
||||
│◄─COMMAND_ACK (ACCEPTED)────│ 7. Go idle (reboot needed)
|
||||
|
||||
|
||||
New Files
|
||||
|
||||
1. src/modules/esc_flasher/EscFlasher.hpp
|
||||
|
||||
Module class inheriting ModuleBase<EscFlasher> + ModuleParams + ScheduledWorkItem.
|
||||
|
||||
Key members:
|
||||
- _vehicle_command_sub — polls for MAV_CMD trigger
|
||||
- _esc_fw_update_status_pub — publishes per-ESC progress
|
||||
- _command_ack_pub — ACKs the initial command
|
||||
- State enum: Idle, StoppingDShot, InitGPIOs, ConnectingESC, ReadingInfo, Flashing, RunApp, Complete, Failed
|
||||
- Per-ESC state: _current_esc_index, _esc_count, _bytes_written, _firmware_size
|
||||
- GPIO pin array: uint32_t _gpio_pins[MAX_MOTORS]
|
||||
- File handle for current firmware
|
||||
|
||||
2. src/modules/esc_flasher/EscFlasher.cpp
|
||||
|
||||
Main module implementation (~600-800 lines, much simpler than Matt's ~1,900).
|
||||
|
||||
Module lifecycle:
|
||||
- Auto-started at boot on supported boards (Kconfig opt-in)
|
||||
- Runs at 1Hz when idle (vehicle_command poll only — negligible overhead)
|
||||
- Transitions to 100Hz active polling during flashing
|
||||
- Returns to idle after completion (reboot required to restore DShot)
|
||||
|
||||
DShot shutdown:
|
||||
- Calls system("dshot stop") via NuttX shell — DShot destructor calls up_dshot_arm(false)
|
||||
- Waits up to 2s for DShot to exit, then proceeds
|
||||
- Simple, no coupling — DShot module has no knowledge of the flasher
|
||||
|
||||
GPIO takeover:
|
||||
- After DShot stops, configures motor pins as GPIO outputs via px4_arch_configgpio(io_timer_channel_get_gpio_output(ch))
|
||||
- Drives all pins HIGH for 600ms to force ESCs into bootloader mode on reboot
|
||||
- ESC power cycle not required — AM32 bootloader detects signal-high at startup
|
||||
|
||||
Flashing loop (per ESC):
|
||||
1. Select ESC (drive target pin, keep others HIGH)
|
||||
2. Send bootloader handshake → get 9-byte device info
|
||||
3. Read firmware tag (CMD_READ_FLASH at tag address) → extract hardware target string
|
||||
4. Open /fs/microsd/am32/<target>.bin (e.g., AM32_G071_48K.bin)
|
||||
5. For each 256-byte chunk:
|
||||
- CMD_SET_ADDRESS
|
||||
- CMD_SET_BUFFER + data + CRC
|
||||
- CMD_PROG_FLASH → wait ACK
|
||||
- Publish progress (chunk_index / total_chunks * 100)
|
||||
6. Write firmware tag section
|
||||
7. CMD_RUN → ESC reboots to application
|
||||
8. Move to next ESC
|
||||
|
||||
3. src/modules/esc_flasher/am32_protocol.hpp / .cpp
|
||||
|
||||
Extracted and cleaned AM32 bootloader protocol implementation.
|
||||
|
||||
Reused from Matt's code (protocol is correct):
|
||||
- crc16_ccitt() — CRC-16 with polynomial 0xA001
|
||||
- bitbang_send_packet() — bit-banged UART TX/RX at 19200 baud
|
||||
- 52µs bit time, 26µs half-bit for sampling
|
||||
- px4_enter_critical_section() for timing accuracy
|
||||
- hrt_absolute_time() busy-wait loops
|
||||
- Bootloader handshake packet: {0x00 * 8, 0x0D, 'B','L','H','e','l','i', CRC}
|
||||
- Command encoding: SET_ADDRESS(0xFF), SET_BUFFER(0xFE), PROG_FLASH(0x01), RUN(0x00), READ_FLASH(0x03)
|
||||
|
||||
Cleaned up vs Matt's code:
|
||||
- Function-based API instead of inline state machine code
|
||||
- Proper error return codes instead of magic numbers
|
||||
- Configurable timeouts instead of hardcoded values
|
||||
- Separated from module state machine logic
|
||||
|
||||
Public API:
|
||||
namespace am32 {
|
||||
int handshake(uint32_t gpio, uint8_t device_info[9]);
|
||||
int set_address(uint32_t gpio, uint16_t address);
|
||||
int write_chunk(uint32_t gpio, const uint8_t *data, uint16_t len);
|
||||
int read_flash(uint32_t gpio, uint16_t address, uint8_t *buf, uint16_t len);
|
||||
int run_app(uint32_t gpio);
|
||||
uint16_t crc16(const uint8_t *buf, uint16_t len);
|
||||
}
|
||||
|
||||
4. src/modules/esc_flasher/CMakeLists.txt
|
||||
|
||||
px4_add_module(
|
||||
MODULE modules__esc_flasher
|
||||
MAIN esc_flasher
|
||||
SRCS
|
||||
EscFlasher.cpp
|
||||
am32_protocol.cpp
|
||||
DEPENDS
|
||||
px4_work_queue
|
||||
)
|
||||
|
||||
5. src/modules/esc_flasher/Kconfig
|
||||
|
||||
menuconfig MODULES_ESC_FLASHER
|
||||
bool "esc_flasher"
|
||||
default n
|
||||
---help---
|
||||
AM32 ESC firmware update module
|
||||
|
||||
|
||||
6. msg/EscFirmwareUpdateStatus.msg
|
||||
|
||||
uint64 timestamp
|
||||
|
||||
uint8 esc_index # Current ESC being flashed (0-based)
|
||||
uint8 esc_count # Total ESCs being flashed
|
||||
uint8 progress_pct # 0-100 for current ESC
|
||||
uint8 overall_progress_pct # 0-100 for entire operation
|
||||
|
||||
uint8 status
|
||||
uint8 STATUS_IDLE = 0
|
||||
uint8 STATUS_CONNECTING = 1
|
||||
uint8 STATUS_READING_INFO = 2
|
||||
uint8 STATUS_ERASING = 3
|
||||
uint8 STATUS_WRITING = 4
|
||||
uint8 STATUS_COMPLETE = 5
|
||||
uint8 STATUS_FAILED = 6
|
||||
uint8 STATUS_REBOOT_REQUIRED = 7
|
||||
|
||||
uint8 error
|
||||
uint8 ERROR_NONE = 0
|
||||
uint8 ERROR_NOT_DISARMED = 1
|
||||
uint8 ERROR_NO_FIRMWARE_FILE = 2
|
||||
uint8 ERROR_COMMS_TIMEOUT = 3
|
||||
uint8 ERROR_CRC_MISMATCH = 4
|
||||
uint8 ERROR_VERIFICATION_FAILED = 5
|
||||
uint8 ERROR_UNSUPPORTED_HARDWARE = 6
|
||||
uint8 ERROR_DSHOT_STOP_FAILED = 7
|
||||
|
||||
uint32 current_firmware_version # Currently installed version
|
||||
uint32 new_firmware_version # Version being written
|
||||
|
||||
|
||||
Modified Files
|
||||
|
||||
7. msg/versioned/VehicleCommand.msg
|
||||
|
||||
Add command constant in PX4-internal range (for development; upstream MAVLink later):
|
||||
|
||||
uint32 VEHICLE_CMD_ESC_FIRMWARE_UPDATE = 100002
|
||||
|
||||
|
||||
- param1: ESC index (0-based, 255 = all ESCs)
|
||||
- param2: ESC type (0 = AM32)
|
||||
|
||||
8. MAVLink message definition (development.xml in mavlink submodule)
|
||||
|
||||
Add ESC_FIRMWARE_UPDATE_STATUS message definition. This is in the MAVLink submodule — we'll fork/PR it separately. For initial development, stream the uORB message as a generic STATUSTEXT fallback.
|
||||
|
||||
9. src/modules/mavlink/streams/ESC_FIRMWARE_UPDATE_STATUS.hpp
|
||||
|
||||
New MAVLink stream class that subscribes to esc_firmware_update_status uORB and sends the MAVLink message. Follow the pattern of existing streams like ESC_STATUS.hpp.
|
||||
|
||||
10. src/modules/mavlink/mavlink_messages.cpp
|
||||
|
||||
Register the new stream.
|
||||
|
||||
11. Board configs
|
||||
|
||||
Enable MODULES_ESC_FLASHER on boards that use AM32 ESCs (e.g., ARK boards). Add esc_flasher start to the board's startup script (rc.board_defaults or similar).
|
||||
|
||||
Key Design Decisions
|
||||
|
||||
Why stop DShot entirely (not just pause)?
|
||||
- DShot uses DMA + timer hardware that conflicts with GPIO bit-bang
|
||||
- Clean shutdown via destructor guarantees all DMA released, timers freed
|
||||
- Reboot to restore is simple and safe — avoids complex reinit edge cases
|
||||
- ESCs need power cycle anyway after reflash
|
||||
|
||||
Why system("dshot stop") instead of uORB handshake?
|
||||
- Zero coupling — DShot module doesn't know flasher exists
|
||||
- Works with any output driver (PWM, OneShot, future drivers)
|
||||
- DShot's existing ModuleBase::stop() + destructor already handles cleanup
|
||||
- Matt's 4-message uORB handshake added 826 lines to DShot for this
|
||||
|
||||
Why auto-start + idle instead of on-demand spawn?
|
||||
- Module is already loaded and subscribed to vehicle_command — instant response
|
||||
- 1Hz idle poll is negligible overhead
|
||||
- Avoids complexity of commander spawning external modules
|
||||
- Kconfig opt-in means boards that don't need it pay zero cost
|
||||
|
||||
Why separate am32_protocol from module?
|
||||
- Clean separation of protocol logic from module orchestration
|
||||
- Protocol code can be reused by future 4-way passthrough implementation
|
||||
- Easier to test protocol functions in isolation
|
||||
|
||||
Firmware File Convention
|
||||
|
||||
Files in /fs/microsd/am32/:
|
||||
- Named by AM32 hardware target: AM32_G071_48K.bin, AM32_F051_32K.bin, etc.
|
||||
- The flasher connects to each ESC bootloader, reads the firmware tag area to determine hardware target
|
||||
- Maps tag → filename, opens file, flashes
|
||||
- If no matching file found, reports ERROR_NO_FIRMWARE_FILE for that ESC and moves on
|
||||
|
||||
State Machine (simplified)
|
||||
|
||||
Idle ──(MAV_CMD)──► StoppingDShot ──(dshot stopped)──► InitGPIOs
|
||||
│
|
||||
(pins HIGH, 600ms)
|
||||
│
|
||||
┌──► ConnectESC
|
||||
│ │
|
||||
│ (handshake OK)
|
||||
│ │
|
||||
│ ReadInfo ──► LoadFirmware ──► Flash
|
||||
│ │
|
||||
│ (all chunks done)
|
||||
│ │
|
||||
│ RunApp ◄──────────────────────┘
|
||||
│ │
|
||||
│ (next ESC?)
|
||||
│ │
|
||||
└──YES─┘
|
||||
│ NO
|
||||
▼
|
||||
Complete
|
||||
(REBOOT_REQUIRED)
|
||||
|
||||
|
||||
Error from any flashing state → Failed (with error code), skip to next ESC or abort.
|
||||
|
||||
Implementation Order
|
||||
|
||||
1. am32_protocol.cpp/hpp — Extract and clean up Matt's protocol code (CRC, bitbang, commands)
|
||||
2. EscFirmwareUpdateStatus.msg — uORB message definition
|
||||
3. VehicleCommand.msg — Add command constant
|
||||
4. EscFlasher.hpp/cpp — Module skeleton with state machine
|
||||
5. CMakeLists.txt + Kconfig — Build integration
|
||||
6. MAVLink stream — ESC_FIRMWARE_UPDATE_STATUS.hpp + register in mavlink_messages.cpp
|
||||
7. Board config — Enable on a test board
|
||||
8. Test — SITL smoke test (state machine logic), then hardware test
|
||||
|
||||
Verification
|
||||
|
||||
1. Build: make ark_fmu-v6x_default (or target board) — verify clean compile
|
||||
2. SITL smoke test: Module starts, responds to vehicle command, reports error (no GPIOs in SITL)
|
||||
3. Hardware test:
|
||||
- Place AM32 firmware on SD card
|
||||
- Send MAV_CMD_ESC_FIRMWARE_UPDATE via commander shell or MAVLink
|
||||
- Verify DShot stops, ESCs enter bootloader, firmware flashes, progress reported
|
||||
- Reboot, verify ESCs running new firmware
|
||||
4. QGC integration: Verify COMMAND_ACK and status messages appear in MAVLink inspector
|
||||
|
||||
Future Extensions (not in this PR)
|
||||
|
||||
- 4-way interface passthrough: Add USB serial passthrough mode using the same am32_protocol infrastructure. The flasher module would proxy between USB serial (4-way protocol) and GPIO bit-bang.
|
||||
- Runtime DShot restart: Instead of requiring reboot, add dshot start after flashing. Needs careful timer/DMA reinit validation.
|
||||
- ESC settings read/write: Extend am32_protocol with EEPROM read/write commands.
|
||||
- Orchestrator module: If hot-switching between DShot and passthrough becomes needed, add a motor_output_mgr similar to CDC ACM.
|
||||
@@ -44,12 +44,13 @@ CONFIG_DRIVERS_UAVCAN=y
|
||||
CONFIG_BOARD_UAVCAN_TIMER_OVERRIDE=2
|
||||
CONFIG_MODULES_AIRSPEED_SELECTOR=y
|
||||
CONFIG_MODULES_BATTERY_STATUS=y
|
||||
CONFIG_MODULES_CAMERA_FEEDBACK=y
|
||||
CONFIG_MODULES_CAMERA_FEEDBACK=n
|
||||
CONFIG_MODULES_COMMANDER=y
|
||||
CONFIG_MODULES_CONTROL_ALLOCATOR=y
|
||||
CONFIG_MODULES_DATAMAN=y
|
||||
CONFIG_MODULES_EKF2=y
|
||||
CONFIG_MODULES_ESC_BATTERY=y
|
||||
CONFIG_MODULES_ESC_FLASHER=y
|
||||
CONFIG_MODULES_EVENTS=y
|
||||
CONFIG_MODULES_FLIGHT_MODE_MANAGER=y
|
||||
CONFIG_MODULES_FW_ATT_CONTROL=y
|
||||
|
||||
@@ -69,6 +69,7 @@ set(msg_files
|
||||
DistanceSensorModeChangeRequest.msg
|
||||
DronecanNodeStatus.msg
|
||||
Ekf2Timestamps.msg
|
||||
EscFirmwareUpdateStatus.msg
|
||||
EscReport.msg
|
||||
EscStatus.msg
|
||||
EstimatorAidSource1d.msg
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
uint64 timestamp # time since system start (microseconds)
|
||||
|
||||
uint8 esc_index # Current ESC being flashed (0-based)
|
||||
uint8 esc_count # Total ESCs being flashed
|
||||
uint8 progress_pct # 0-100 for current ESC
|
||||
uint8 overall_progress_pct # 0-100 for entire operation
|
||||
|
||||
uint8 status
|
||||
uint8 STATUS_IDLE = 0
|
||||
uint8 STATUS_CONNECTING = 1
|
||||
uint8 STATUS_READING_INFO = 2
|
||||
uint8 STATUS_ERASING = 3
|
||||
uint8 STATUS_WRITING = 4
|
||||
uint8 STATUS_COMPLETE = 5
|
||||
uint8 STATUS_FAILED = 6
|
||||
uint8 STATUS_REBOOT_REQUIRED = 7
|
||||
|
||||
uint8 error
|
||||
uint8 ERROR_NONE = 0
|
||||
uint8 ERROR_NOT_DISARMED = 1
|
||||
uint8 ERROR_NO_FIRMWARE_FILE = 2
|
||||
uint8 ERROR_COMMS_TIMEOUT = 3
|
||||
uint8 ERROR_CRC_MISMATCH = 4
|
||||
uint8 ERROR_VERIFICATION_FAILED = 5
|
||||
uint8 ERROR_UNSUPPORTED_HARDWARE = 6
|
||||
uint8 ERROR_DSHOT_STOP_FAILED = 7
|
||||
|
||||
uint32 current_firmware_version # Currently installed version
|
||||
uint32 new_firmware_version # Version being written
|
||||
@@ -114,6 +114,7 @@ uint16 VEHICLE_CMD_EXTERNAL_WIND_ESTIMATE = 43004
|
||||
uint32 VEHICLE_CMD_PX4_INTERNAL_START = 65537 # Start of PX4 internal only vehicle commands (> UINT16_MAX).
|
||||
uint32 VEHICLE_CMD_SET_GPS_GLOBAL_ORIGIN = 100000 # Sets the GPS coordinates of the vehicle local origin (0,0,0) position. |Unused|Unused|Unused|Unused|Latitude (WGS-84)|Longitude (WGS-84)|[m] Altitude (AMSL from GNSS, positive above ground)|
|
||||
uint32 VEHICLE_CMD_SET_NAV_STATE = 100001 # Change mode by specifying nav_state directly. |nav_state|Unused|Unused|Unused|Unused|Unused|Unused|
|
||||
uint32 VEHICLE_CMD_ESC_FIRMWARE_UPDATE = 100002 # Trigger AM32 ESC firmware update from SD card. |ESC index (0-based, 255=all)|ESC type (0=AM32)|Unused|Unused|Unused|Unused|Unused|
|
||||
|
||||
uint8 VEHICLE_MOUNT_MODE_RETRACT = 0 # Load and keep safe position (Roll,Pitch,Yaw) from permanent memory and stop stabilization.
|
||||
uint8 VEHICLE_MOUNT_MODE_NEUTRAL = 1 # Load and keep neutral position (Roll,Pitch,Yaw) from permanent memory.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
############################################################################
|
||||
#
|
||||
# Copyright (c) 2024 PX4 Development Team. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# 3. Neither the name PX4 nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
px4_add_module(
|
||||
MODULE modules__esc_flasher
|
||||
MAIN esc_flasher
|
||||
SRCS
|
||||
EscFlasher.cpp
|
||||
am32_protocol.cpp
|
||||
DEPENDS
|
||||
px4_work_queue
|
||||
)
|
||||
@@ -0,0 +1,595 @@
|
||||
/****************************************************************************
|
||||
*
|
||||
* Copyright (c) 2024 PX4 Development Team. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* 3. Neither the name PX4 nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* @file EscFlasher.cpp
|
||||
*
|
||||
* AM32 ESC firmware update module.
|
||||
*/
|
||||
|
||||
#include "EscFlasher.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <px4_platform_common/px4_config.h>
|
||||
|
||||
#ifdef __PX4_NUTTX
|
||||
#include <px4_arch/io_timer.h>
|
||||
#endif
|
||||
|
||||
using namespace time_literals;
|
||||
|
||||
EscFlasher::EscFlasher() :
|
||||
ModuleParams(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
EscFlasher::~EscFlasher()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// ───────────────────────────── Module lifecycle ─────────────────────────────
|
||||
|
||||
int EscFlasher::task_spawn(int argc, char *argv[])
|
||||
{
|
||||
_task_id = px4_task_spawn_cmd("esc_flasher",
|
||||
SCHED_DEFAULT,
|
||||
SCHED_PRIORITY_DEFAULT,
|
||||
PX4_STACK_ADJUSTED(4096),
|
||||
(px4_main_t)&run_trampoline,
|
||||
(char *const *)argv);
|
||||
|
||||
if (_task_id < 0) {
|
||||
_task_id = -1;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EscFlasher *EscFlasher::instantiate(int argc, char *argv[])
|
||||
{
|
||||
EscFlasher *instance = new EscFlasher();
|
||||
|
||||
if (instance == nullptr) {
|
||||
PX4_ERR("alloc failed");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void EscFlasher::run()
|
||||
{
|
||||
while (!should_exit()) {
|
||||
|
||||
switch (_state) {
|
||||
case State::Idle:
|
||||
handle_vehicle_command();
|
||||
break;
|
||||
|
||||
case State::StoppingDShot:
|
||||
state_stopping_dshot();
|
||||
break;
|
||||
|
||||
case State::InitGPIOs:
|
||||
state_init_gpios();
|
||||
break;
|
||||
|
||||
case State::ConnectESC:
|
||||
state_connect_esc();
|
||||
break;
|
||||
|
||||
case State::ReadInfo:
|
||||
state_read_info();
|
||||
break;
|
||||
|
||||
case State::LoadFirmware:
|
||||
state_load_firmware();
|
||||
break;
|
||||
|
||||
case State::Flashing:
|
||||
state_flash_chunk();
|
||||
break;
|
||||
|
||||
case State::RunApp:
|
||||
state_run_app();
|
||||
break;
|
||||
|
||||
case State::Complete:
|
||||
state_complete();
|
||||
break;
|
||||
|
||||
case State::Failed:
|
||||
state_failed();
|
||||
break;
|
||||
}
|
||||
|
||||
// Idle → slow poll; active → no extra sleep (protocol
|
||||
// bit-bang already takes wall-clock time per chunk)
|
||||
if (_state == State::Idle) {
|
||||
px4_usleep(IDLE_INTERVAL_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────── Vehicle command ──────────────────────────────
|
||||
|
||||
void EscFlasher::handle_vehicle_command()
|
||||
{
|
||||
vehicle_command_s cmd;
|
||||
|
||||
while (_vehicle_cmd_sub.update(&cmd)) {
|
||||
if (cmd.command != vehicle_command_s::VEHICLE_CMD_ESC_FIRMWARE_UPDATE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Must be disarmed
|
||||
vehicle_status_s vs{};
|
||||
_vehicle_status_sub.copy(&vs);
|
||||
|
||||
if (vs.arming_state != vehicle_status_s::ARMING_STATE_DISARMED) {
|
||||
PX4_ERR("cannot flash ESCs while armed");
|
||||
ack_vehicle_command(cmd, vehicle_command_ack_s::VEHICLE_CMD_RESULT_DENIED);
|
||||
_status.error = esc_firmware_update_status_s::ERROR_NOT_DISARMED;
|
||||
publish_status();
|
||||
continue;
|
||||
}
|
||||
|
||||
ack_vehicle_command(cmd, vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED);
|
||||
|
||||
uint8_t esc_index = (uint8_t)cmd.param1; // 255 = all
|
||||
begin_update(esc_index);
|
||||
return; // exit the poll loop — we are now active
|
||||
}
|
||||
}
|
||||
|
||||
void EscFlasher::ack_vehicle_command(const vehicle_command_s &cmd, uint8_t result)
|
||||
{
|
||||
vehicle_command_ack_s ack{};
|
||||
ack.command = cmd.command;
|
||||
ack.result = result;
|
||||
ack.target_system = cmd.source_system;
|
||||
ack.target_component = cmd.source_component;
|
||||
ack.timestamp = hrt_absolute_time();
|
||||
_cmd_ack_pub.publish(ack);
|
||||
}
|
||||
|
||||
void EscFlasher::begin_update(uint8_t esc_index)
|
||||
{
|
||||
_target_esc = esc_index;
|
||||
_current_esc = 0;
|
||||
_esc_count = 0;
|
||||
_flash_started = hrt_absolute_time();
|
||||
|
||||
memset(&_status, 0, sizeof(_status));
|
||||
_status.status = esc_firmware_update_status_s::STATUS_CONNECTING;
|
||||
publish_status();
|
||||
|
||||
mavlink_log_info(&_mavlink_log_pub, "ESC firmware update starting");
|
||||
|
||||
transition(State::StoppingDShot);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: StoppingDShot ─────────────────────────
|
||||
|
||||
void EscFlasher::state_stopping_dshot()
|
||||
{
|
||||
PX4_INFO("stopping dshot driver");
|
||||
|
||||
int ret = system("dshot stop");
|
||||
|
||||
if (ret != 0) {
|
||||
PX4_WARN("dshot stop returned %d (may already be stopped)", ret);
|
||||
}
|
||||
|
||||
// Give DShot destructor time to release DMA/timers
|
||||
px4_usleep(200000); // 200 ms
|
||||
|
||||
transition(State::InitGPIOs);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: InitGPIOs ─────────────────────────────
|
||||
|
||||
void EscFlasher::state_init_gpios()
|
||||
{
|
||||
#ifdef __PX4_NUTTX
|
||||
|
||||
// Discover motor GPIO pins from the io_timer subsystem
|
||||
_esc_count = 0;
|
||||
|
||||
for (unsigned ch = 0; ch < MAX_ESCS; ch++) {
|
||||
uint32_t gpio = io_timer_channel_get_gpio_output(ch);
|
||||
|
||||
if (gpio != 0) {
|
||||
_gpio_pins[_esc_count] = gpio & (GPIO_PORT_MASK | GPIO_PIN_MASK);
|
||||
_esc_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_esc_count == 0) {
|
||||
PX4_ERR("no motor GPIOs found");
|
||||
fail(esc_firmware_update_status_s::ERROR_DSHOT_STOP_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
PX4_INFO("found %u motor GPIO(s)", _esc_count);
|
||||
|
||||
// If targeting a specific ESC, validate index
|
||||
if (_target_esc != 255 && _target_esc >= _esc_count) {
|
||||
PX4_ERR("ESC index %u out of range (have %u)", _target_esc, _esc_count);
|
||||
fail(esc_firmware_update_status_s::ERROR_UNSUPPORTED_HARDWARE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Drive all target pins HIGH to force ESCs into bootloader on next reset
|
||||
for (uint8_t i = 0; i < _esc_count; i++) {
|
||||
bool target = (_target_esc == 255) || (i == _target_esc);
|
||||
|
||||
if (target) {
|
||||
uint32_t gpio_out = _gpio_pins[i]
|
||||
| GPIO_OUTPUT | GPIO_OUTPUT_SET | GPIO_PULLUP;
|
||||
px4_arch_configgpio(gpio_out);
|
||||
}
|
||||
}
|
||||
|
||||
PX4_INFO("GPIOs HIGH — waiting %u ms for ESC bootloader entry",
|
||||
(unsigned)(GPIO_SETTLE_TIME_US / 1000));
|
||||
|
||||
_status.esc_count = (_target_esc == 255) ? _esc_count : 1;
|
||||
publish_status();
|
||||
|
||||
// Wait for ESCs to detect HIGH and enter bootloader
|
||||
px4_usleep(GPIO_SETTLE_TIME_US);
|
||||
|
||||
// Set starting ESC
|
||||
if (_target_esc != 255) {
|
||||
_current_esc = _target_esc;
|
||||
|
||||
} else {
|
||||
_current_esc = 0;
|
||||
}
|
||||
|
||||
transition(State::ConnectESC);
|
||||
|
||||
#else
|
||||
PX4_ERR("GPIO operations not supported on this platform");
|
||||
fail(esc_firmware_update_status_s::ERROR_DSHOT_STOP_FAILED);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: ConnectESC ────────────────────────────
|
||||
|
||||
void EscFlasher::state_connect_esc()
|
||||
{
|
||||
PX4_INFO("connecting to ESC %u", _current_esc);
|
||||
|
||||
_status.esc_index = _current_esc;
|
||||
_status.status = esc_firmware_update_status_s::STATUS_CONNECTING;
|
||||
_status.error = esc_firmware_update_status_s::ERROR_NONE;
|
||||
publish_status();
|
||||
|
||||
int ret = am32::handshake(_gpio_pins[_current_esc], _device_info);
|
||||
|
||||
if (ret != am32::OK) {
|
||||
PX4_ERR("bootloader handshake failed on ESC %u (err %d)", _current_esc, ret);
|
||||
fail(esc_firmware_update_status_s::ERROR_COMMS_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
PX4_INFO("ESC %u bootloader: %02x %02x %02x %02x %02x %02x %02x %02x %02x",
|
||||
_current_esc,
|
||||
_device_info[0], _device_info[1], _device_info[2],
|
||||
_device_info[3], _device_info[4], _device_info[5],
|
||||
_device_info[6], _device_info[7], _device_info[8]);
|
||||
|
||||
transition(State::ReadInfo);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: ReadInfo ──────────────────────────────
|
||||
|
||||
void EscFlasher::state_read_info()
|
||||
{
|
||||
_status.status = esc_firmware_update_status_s::STATUS_READING_INFO;
|
||||
publish_status();
|
||||
|
||||
// Device info already obtained during handshake.
|
||||
// Future: read firmware tag to auto-select firmware file.
|
||||
|
||||
transition(State::LoadFirmware);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: LoadFirmware ──────────────────────────
|
||||
|
||||
void EscFlasher::state_load_firmware()
|
||||
{
|
||||
// Close any previously open file (when looping over ESCs)
|
||||
if (_fw_fd >= 0) {
|
||||
close(_fw_fd);
|
||||
_fw_fd = -1;
|
||||
}
|
||||
|
||||
_fw_fd = open(FIRMWARE_PATH, O_RDONLY);
|
||||
|
||||
if (_fw_fd < 0) {
|
||||
PX4_ERR("cannot open %s", FIRMWARE_PATH);
|
||||
mavlink_log_critical(&_mavlink_log_pub, "No firmware file on SD card");
|
||||
fail(esc_firmware_update_status_s::ERROR_NO_FIRMWARE_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
|
||||
if (fstat(_fw_fd, &st) != 0 || st.st_size == 0) {
|
||||
PX4_ERR("cannot stat firmware file");
|
||||
close(_fw_fd);
|
||||
_fw_fd = -1;
|
||||
fail(esc_firmware_update_status_s::ERROR_NO_FIRMWARE_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
_fw_size = (uint32_t)st.st_size;
|
||||
_fw_written = 0;
|
||||
|
||||
PX4_INFO("firmware %s: %lu bytes, %lu chunks",
|
||||
FIRMWARE_PATH, (unsigned long)_fw_size,
|
||||
(unsigned long)((_fw_size + am32::MAX_CHUNK_SIZE - 1) / am32::MAX_CHUNK_SIZE));
|
||||
|
||||
transition(State::Flashing);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: Flashing ──────────────────────────────
|
||||
|
||||
void EscFlasher::state_flash_chunk()
|
||||
{
|
||||
_status.status = esc_firmware_update_status_s::STATUS_WRITING;
|
||||
|
||||
uint32_t remaining = _fw_size - _fw_written;
|
||||
uint16_t chunk_len = (remaining > am32::MAX_CHUNK_SIZE)
|
||||
? am32::MAX_CHUNK_SIZE : (uint16_t)remaining;
|
||||
|
||||
uint8_t buf[am32::MAX_CHUNK_SIZE];
|
||||
ssize_t nread = read(_fw_fd, buf, chunk_len);
|
||||
|
||||
if (nread != (ssize_t)chunk_len) {
|
||||
PX4_ERR("firmware read error at offset %lu", (unsigned long)_fw_written);
|
||||
fail(esc_firmware_update_status_s::ERROR_NO_FIRMWARE_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set flash address (16-bit, relative to STM32 flash base)
|
||||
uint16_t address = (uint16_t)((am32::FIRMWARE_ADDR & 0xFFFF) + _fw_written);
|
||||
|
||||
int ret = am32::set_address(_gpio_pins[_current_esc], address);
|
||||
|
||||
if (ret != am32::OK) {
|
||||
PX4_ERR("set_address failed on ESC %u (err %d)", _current_esc, ret);
|
||||
fail(esc_firmware_update_status_s::ERROR_COMMS_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = am32::write_chunk(_gpio_pins[_current_esc], buf, chunk_len);
|
||||
|
||||
if (ret != am32::OK) {
|
||||
PX4_ERR("write_chunk failed on ESC %u at 0x%04x (err %d)",
|
||||
_current_esc, address, ret);
|
||||
fail(esc_firmware_update_status_s::ERROR_CRC_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
_fw_written += chunk_len;
|
||||
|
||||
// Progress for current ESC
|
||||
_status.progress_pct = (uint8_t)((_fw_written * 100) / _fw_size);
|
||||
|
||||
// Overall progress across all ESCs
|
||||
uint8_t esc_total = (_target_esc == 255) ? _esc_count : 1;
|
||||
uint8_t esc_done;
|
||||
|
||||
if (_target_esc == 255) {
|
||||
esc_done = _current_esc;
|
||||
|
||||
} else {
|
||||
esc_done = 0;
|
||||
}
|
||||
|
||||
_status.overall_progress_pct = (uint8_t)(
|
||||
((esc_done * 100) + _status.progress_pct) / esc_total);
|
||||
|
||||
publish_status();
|
||||
|
||||
uint32_t total_chunks = (_fw_size + am32::MAX_CHUNK_SIZE - 1) / am32::MAX_CHUNK_SIZE;
|
||||
uint32_t current_chunk = (_fw_written + am32::MAX_CHUNK_SIZE - 1) / am32::MAX_CHUNK_SIZE;
|
||||
PX4_INFO("ESC %u: chunk %lu/%lu (%u%%)",
|
||||
_current_esc, (unsigned long)current_chunk,
|
||||
(unsigned long)total_chunks, _status.progress_pct);
|
||||
|
||||
if (_fw_written >= _fw_size) {
|
||||
transition(State::RunApp);
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: RunApp ────────────────────────────────
|
||||
|
||||
void EscFlasher::state_run_app()
|
||||
{
|
||||
PX4_INFO("sending RUN_APP to ESC %u", _current_esc);
|
||||
|
||||
am32::run_app(_gpio_pins[_current_esc]);
|
||||
|
||||
// Close firmware file so it can be re-opened for the next ESC
|
||||
if (_fw_fd >= 0) {
|
||||
close(_fw_fd);
|
||||
_fw_fd = -1;
|
||||
}
|
||||
|
||||
advance_to_next_esc();
|
||||
}
|
||||
|
||||
void EscFlasher::advance_to_next_esc()
|
||||
{
|
||||
if (_target_esc != 255) {
|
||||
// Single-ESC mode — done
|
||||
transition(State::Complete);
|
||||
return;
|
||||
}
|
||||
|
||||
_current_esc++;
|
||||
|
||||
if (_current_esc >= _esc_count) {
|
||||
transition(State::Complete);
|
||||
|
||||
} else {
|
||||
// Next ESC
|
||||
transition(State::ConnectESC);
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: Complete ──────────────────────────────
|
||||
|
||||
void EscFlasher::state_complete()
|
||||
{
|
||||
hrt_abstime elapsed = hrt_absolute_time() - _flash_started;
|
||||
unsigned seconds = (unsigned)(elapsed / 1000000);
|
||||
|
||||
PX4_INFO("ESC firmware update complete in %u seconds", seconds);
|
||||
mavlink_log_info(&_mavlink_log_pub, "ESC flash done in %us — reboot required", seconds);
|
||||
|
||||
_status.status = esc_firmware_update_status_s::STATUS_REBOOT_REQUIRED;
|
||||
_status.progress_pct = 100;
|
||||
_status.overall_progress_pct = 100;
|
||||
_status.error = esc_firmware_update_status_s::ERROR_NONE;
|
||||
publish_status();
|
||||
|
||||
cleanup();
|
||||
transition(State::Idle);
|
||||
}
|
||||
|
||||
// ───────────────────────────── State: Failed ────────────────────────────────
|
||||
|
||||
void EscFlasher::state_failed()
|
||||
{
|
||||
PX4_ERR("ESC firmware update failed (error %u)", _status.error);
|
||||
mavlink_log_critical(&_mavlink_log_pub, "ESC flash failed (error %u)", _status.error);
|
||||
|
||||
_status.status = esc_firmware_update_status_s::STATUS_FAILED;
|
||||
publish_status();
|
||||
|
||||
cleanup();
|
||||
transition(State::Idle);
|
||||
}
|
||||
|
||||
// ───────────────────────────── Helpers ──────────────────────────────────────
|
||||
|
||||
void EscFlasher::transition(State s)
|
||||
{
|
||||
_state = s;
|
||||
_state_entered = hrt_absolute_time();
|
||||
}
|
||||
|
||||
void EscFlasher::fail(uint8_t error_code)
|
||||
{
|
||||
_status.error = error_code;
|
||||
transition(State::Failed);
|
||||
}
|
||||
|
||||
void EscFlasher::cleanup()
|
||||
{
|
||||
if (_fw_fd >= 0) {
|
||||
close(_fw_fd);
|
||||
_fw_fd = -1;
|
||||
}
|
||||
|
||||
_fw_size = 0;
|
||||
_fw_written = 0;
|
||||
}
|
||||
|
||||
void EscFlasher::publish_status()
|
||||
{
|
||||
_status.timestamp = hrt_absolute_time();
|
||||
_status_pub.publish(_status);
|
||||
}
|
||||
|
||||
// ───────────────────────────── ModuleBase boilerplate ────────────────────────
|
||||
|
||||
int EscFlasher::print_status()
|
||||
{
|
||||
static const char *state_names[] = {
|
||||
"Idle", "StoppingDShot", "InitGPIOs", "ConnectESC",
|
||||
"ReadInfo", "LoadFirmware", "Flashing", "RunApp",
|
||||
"Complete", "Failed"
|
||||
};
|
||||
|
||||
PX4_INFO("state: %s", state_names[(int)_state]);
|
||||
PX4_INFO("ESC %u / %u, firmware %lu / %lu bytes",
|
||||
_current_esc, _esc_count,
|
||||
(unsigned long)_fw_written, (unsigned long)_fw_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EscFlasher::print_usage(const char *reason)
|
||||
{
|
||||
if (reason) {
|
||||
PX4_ERR("%s", reason);
|
||||
}
|
||||
|
||||
PRINT_MODULE_DESCRIPTION(
|
||||
R"DESCR_STR(
|
||||
### Description
|
||||
AM32 ESC firmware update module.
|
||||
|
||||
Reads firmware from SD card, stops DShot, and flashes ESCs
|
||||
via bit-banged bootloader protocol. Triggered by the
|
||||
VEHICLE_CMD_ESC_FIRMWARE_UPDATE vehicle command.
|
||||
A reboot is required after flashing to restore normal operation.
|
||||
)DESCR_STR");
|
||||
|
||||
PRINT_MODULE_USAGE_NAME("esc_flasher", "system");
|
||||
PRINT_MODULE_USAGE_COMMAND_DESCR("start", "Start the module (auto-started on supported boards)");
|
||||
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EscFlasher::custom_command(int argc, char *argv[])
|
||||
{
|
||||
return print_usage("unrecognised command");
|
||||
}
|
||||
|
||||
extern "C" __EXPORT int esc_flasher_main(int argc, char *argv[])
|
||||
{
|
||||
return EscFlasher::main(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/****************************************************************************
|
||||
*
|
||||
* Copyright (c) 2024 PX4 Development Team. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* 3. Neither the name PX4 nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* @file EscFlasher.hpp
|
||||
*
|
||||
* AM32 ESC firmware update module.
|
||||
*
|
||||
* Triggered by MAV_CMD_ESC_FIRMWARE_UPDATE via vehicle_command.
|
||||
* Reads firmware from SD card, stops DShot, takes over motor GPIOs,
|
||||
* and flashes each ESC via bit-banged AM32 bootloader protocol.
|
||||
* Requires reboot to restore normal DShot operation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <px4_platform_common/module.h>
|
||||
#include <px4_platform_common/module_params.h>
|
||||
#include <px4_platform_common/posix.h>
|
||||
#include <drivers/drv_hrt.h>
|
||||
#include <systemlib/mavlink_log.h>
|
||||
|
||||
#include <uORB/Publication.hpp>
|
||||
#include <uORB/Subscription.hpp>
|
||||
#include <uORB/topics/esc_firmware_update_status.h>
|
||||
#include <uORB/topics/vehicle_command.h>
|
||||
#include <uORB/topics/vehicle_command_ack.h>
|
||||
#include <uORB/topics/vehicle_status.h>
|
||||
|
||||
#include "am32_protocol.hpp"
|
||||
|
||||
class EscFlasher : public ModuleBase<EscFlasher>, public ModuleParams
|
||||
{
|
||||
public:
|
||||
EscFlasher();
|
||||
~EscFlasher() override;
|
||||
|
||||
EscFlasher(const EscFlasher &) = delete;
|
||||
EscFlasher &operator=(const EscFlasher &) = delete;
|
||||
|
||||
/** @see ModuleBase */
|
||||
static int task_spawn(int argc, char *argv[]);
|
||||
|
||||
/** @see ModuleBase */
|
||||
static EscFlasher *instantiate(int argc, char *argv[]);
|
||||
|
||||
/** @see ModuleBase::run() */
|
||||
void run() override;
|
||||
|
||||
/** @see ModuleBase */
|
||||
static int custom_command(int argc, char *argv[]);
|
||||
|
||||
/** @see ModuleBase */
|
||||
static int print_usage(const char *reason = nullptr);
|
||||
|
||||
/** @see ModuleBase::print_status() */
|
||||
int print_status() override;
|
||||
|
||||
private:
|
||||
static constexpr uint8_t MAX_ESCS = 8;
|
||||
static constexpr const char *FIRMWARE_PATH = "/fs/microsd/am32/firmware.bin";
|
||||
static constexpr uint32_t DSHOT_STOP_TIMEOUT_US = 2000000; // 2 s
|
||||
static constexpr uint32_t GPIO_SETTLE_TIME_US = 600000; // 600 ms
|
||||
static constexpr uint32_t IDLE_INTERVAL_US = 1000000; // 1 s
|
||||
|
||||
enum class State : uint8_t {
|
||||
Idle,
|
||||
StoppingDShot,
|
||||
InitGPIOs,
|
||||
ConnectESC,
|
||||
ReadInfo,
|
||||
LoadFirmware,
|
||||
Flashing,
|
||||
RunApp,
|
||||
Complete,
|
||||
Failed
|
||||
};
|
||||
|
||||
// --- state machine helpers ---
|
||||
void handle_vehicle_command();
|
||||
void ack_vehicle_command(const vehicle_command_s &cmd, uint8_t result);
|
||||
void begin_update(uint8_t esc_index);
|
||||
void advance_to_next_esc();
|
||||
void transition(State s);
|
||||
void fail(uint8_t error_code);
|
||||
void cleanup();
|
||||
void publish_status();
|
||||
|
||||
// --- state handlers (called from run loop) ---
|
||||
void state_stopping_dshot();
|
||||
void state_init_gpios();
|
||||
void state_connect_esc();
|
||||
void state_read_info();
|
||||
void state_load_firmware();
|
||||
void state_flash_chunk();
|
||||
void state_run_app();
|
||||
void state_complete();
|
||||
void state_failed();
|
||||
|
||||
// --- current state ---
|
||||
State _state{State::Idle};
|
||||
hrt_abstime _state_entered{0};
|
||||
hrt_abstime _flash_started{0};
|
||||
|
||||
// --- ESC tracking ---
|
||||
uint8_t _target_esc{255}; // from vehicle_command param1
|
||||
uint8_t _current_esc{0}; // index into _gpio_pins[]
|
||||
uint8_t _esc_count{0};
|
||||
uint32_t _gpio_pins[MAX_ESCS] {};
|
||||
uint8_t _device_info[am32::DEVICE_INFO_SIZE] {};
|
||||
|
||||
// --- firmware file ---
|
||||
int _fw_fd{-1};
|
||||
uint32_t _fw_size{0};
|
||||
uint32_t _fw_written{0};
|
||||
|
||||
// --- published status ---
|
||||
esc_firmware_update_status_s _status{};
|
||||
|
||||
// --- publications ---
|
||||
uORB::Publication<esc_firmware_update_status_s> _status_pub{ORB_ID(esc_firmware_update_status)};
|
||||
uORB::Publication<vehicle_command_ack_s> _cmd_ack_pub{ORB_ID(vehicle_command_ack)};
|
||||
|
||||
// --- subscriptions ---
|
||||
uORB::Subscription _vehicle_cmd_sub{ORB_ID(vehicle_command)};
|
||||
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)};
|
||||
|
||||
// --- mavlink log ---
|
||||
orb_advert_t _mavlink_log_pub{nullptr};
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
menuconfig MODULES_ESC_FLASHER
|
||||
bool "esc_flasher"
|
||||
default n
|
||||
---help---
|
||||
Enable support for ESC Flasher module
|
||||
@@ -0,0 +1,305 @@
|
||||
/****************************************************************************
|
||||
*
|
||||
* Copyright (c) 2024 PX4 Development Team. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* 3. Neither the name PX4 nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* @file am32_protocol.cpp
|
||||
*
|
||||
* AM32 ESC bootloader protocol — bit-banged UART implementation.
|
||||
*/
|
||||
|
||||
#include "am32_protocol.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <px4_platform_common/px4_config.h>
|
||||
#include <px4_platform_common/micro_hal.h>
|
||||
#include <drivers/drv_hrt.h>
|
||||
|
||||
namespace am32
|
||||
{
|
||||
|
||||
// Bootloader handshake packet (8 zero bytes + 0x0D + "BLHeli" + CRC)
|
||||
static const uint8_t boot_init[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0D, 'B', 'L', 'H', 'e', 'l', 'i', 0xF4, 0x7D
|
||||
};
|
||||
|
||||
uint16_t crc16(const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
uint16_t crc = 0;
|
||||
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
uint8_t xb = buf[i];
|
||||
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
if (((xb & 0x01) ^ (crc & 0x0001)) != 0) {
|
||||
crc = (crc >> 1) ^ 0xA001;
|
||||
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
|
||||
xb >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
int send_packet(uint32_t gpio, const uint8_t *tx_data, uint16_t tx_len,
|
||||
uint8_t *rx_data, uint8_t rx_len)
|
||||
{
|
||||
// Build GPIO configurations from the port+pin value
|
||||
uint32_t gpio_out = (gpio & (GPIO_PORT_MASK | GPIO_PIN_MASK))
|
||||
| GPIO_OUTPUT | GPIO_OUTPUT_SET | GPIO_PULLUP;
|
||||
uint32_t gpio_in = (gpio & (GPIO_PORT_MASK | GPIO_PIN_MASK))
|
||||
| GPIO_INPUT | GPIO_PULLUP;
|
||||
|
||||
// Start as output HIGH
|
||||
px4_arch_configgpio(gpio_out);
|
||||
|
||||
// --- critical section: interrupts off for timing accuracy ---
|
||||
irqstate_t irq_state = px4_enter_critical_section();
|
||||
|
||||
// Transmit bytes (19200 8N1, LSB-first)
|
||||
for (uint16_t k = 0; k < tx_len; k++) {
|
||||
uint8_t byte = tx_data[k];
|
||||
|
||||
// Start bit (LOW)
|
||||
px4_arch_gpiowrite(gpio_out, 0);
|
||||
hrt_abstime t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US) {}
|
||||
|
||||
// 8 data bits, LSB first
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
px4_arch_gpiowrite(gpio_out, (byte >> i) & 1);
|
||||
t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US) {}
|
||||
}
|
||||
|
||||
// Stop bit (HIGH)
|
||||
px4_arch_gpiowrite(gpio_out, 1);
|
||||
t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US) {}
|
||||
}
|
||||
|
||||
// Switch to input for receiving
|
||||
px4_arch_configgpio(gpio_in);
|
||||
|
||||
// Receive response bytes
|
||||
uint8_t read_count = 0;
|
||||
|
||||
if (rx_len > 0 && rx_data != nullptr) {
|
||||
for (uint8_t i = 0; i < rx_len; i++) {
|
||||
|
||||
// Wait for start bit (line goes LOW)
|
||||
hrt_abstime t = hrt_absolute_time();
|
||||
|
||||
while (px4_arch_gpioread(gpio_in)) {
|
||||
if (hrt_absolute_time() - t > RX_TIMEOUT_US) {
|
||||
// Timeout — restore output HIGH and bail
|
||||
px4_arch_configgpio(gpio_out);
|
||||
px4_leave_critical_section(irq_state);
|
||||
return ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
// Half-bit delay to centre sampling window
|
||||
t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < HALF_BIT_TIME_US) {}
|
||||
|
||||
// Read 8 data bits
|
||||
uint8_t byte = 0;
|
||||
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US) {}
|
||||
|
||||
if (px4_arch_gpioread(gpio_in)) {
|
||||
byte |= (1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
rx_data[read_count++] = byte;
|
||||
|
||||
// Wait through stop bit
|
||||
t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore pin to output HIGH
|
||||
px4_arch_configgpio(gpio_out);
|
||||
|
||||
// Inter-packet gap (8 bit-times)
|
||||
hrt_abstime t = hrt_absolute_time();
|
||||
|
||||
while (hrt_absolute_time() - t < BIT_TIME_US * 8) {}
|
||||
|
||||
px4_leave_critical_section(irq_state);
|
||||
|
||||
return read_count;
|
||||
}
|
||||
|
||||
int handshake(uint32_t gpio, uint8_t device_info[DEVICE_INFO_SIZE])
|
||||
{
|
||||
int ret = send_packet(gpio, boot_init, sizeof(boot_init),
|
||||
device_info, DEVICE_INFO_SIZE);
|
||||
|
||||
if (ret == DEVICE_INFO_SIZE) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return (ret < 0) ? ret : ERR_NACK;
|
||||
}
|
||||
|
||||
int set_address(uint32_t gpio, uint16_t address)
|
||||
{
|
||||
uint8_t cmd[6];
|
||||
cmd[0] = CMD_SET_ADDRESS;
|
||||
cmd[1] = 0x00;
|
||||
cmd[2] = (address >> 8) & 0xFF; // high byte
|
||||
cmd[3] = address & 0xFF; // low byte
|
||||
|
||||
uint16_t crc = crc16(cmd, 4);
|
||||
cmd[4] = crc & 0xFF;
|
||||
cmd[5] = (crc >> 8) & 0xFF;
|
||||
|
||||
uint8_t response;
|
||||
int ret = send_packet(gpio, cmd, sizeof(cmd), &response, 1);
|
||||
|
||||
if (ret == 1 && response == ACK) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return (ret < 0) ? ret : ERR_NACK;
|
||||
}
|
||||
|
||||
int write_chunk(uint32_t gpio, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (len == 0 || len > MAX_CHUNK_SIZE || data == nullptr) {
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
// 1) CMD_SET_BUFFER — tell bootloader how many bytes follow
|
||||
uint8_t size_cmd[6];
|
||||
size_cmd[0] = CMD_SET_BUFFER;
|
||||
size_cmd[1] = 0x00;
|
||||
|
||||
if (len == MAX_CHUNK_SIZE) {
|
||||
size_cmd[2] = 1; // 256 encoded as (1, 0)
|
||||
size_cmd[3] = 0;
|
||||
|
||||
} else {
|
||||
size_cmd[2] = 0;
|
||||
size_cmd[3] = (uint8_t)len;
|
||||
}
|
||||
|
||||
uint16_t crc = crc16(size_cmd, 4);
|
||||
size_cmd[4] = crc & 0xFF;
|
||||
size_cmd[5] = (crc >> 8) & 0xFF;
|
||||
|
||||
// SET_BUFFER has no response
|
||||
send_packet(gpio, size_cmd, sizeof(size_cmd), nullptr, 0);
|
||||
|
||||
// 2) Send data + CRC — bootloader ACKs after receiving
|
||||
uint8_t buf[MAX_CHUNK_SIZE + 2];
|
||||
memcpy(buf, data, len);
|
||||
crc = crc16(data, len);
|
||||
buf[len] = crc & 0xFF;
|
||||
buf[len + 1] = (crc >> 8) & 0xFF;
|
||||
|
||||
uint8_t response;
|
||||
int ret = send_packet(gpio, buf, len + 2, &response, 1);
|
||||
|
||||
if (ret != 1 || response != ACK) {
|
||||
return (ret < 0) ? ret : ERR_NACK;
|
||||
}
|
||||
|
||||
// 3) CMD_PROG_FLASH — commit buffer to flash
|
||||
uint8_t prog_cmd[4];
|
||||
prog_cmd[0] = CMD_PROG_FLASH;
|
||||
prog_cmd[1] = 0x00;
|
||||
crc = crc16(prog_cmd, 2);
|
||||
prog_cmd[2] = crc & 0xFF;
|
||||
prog_cmd[3] = (crc >> 8) & 0xFF;
|
||||
|
||||
ret = send_packet(gpio, prog_cmd, sizeof(prog_cmd), &response, 1);
|
||||
|
||||
if (ret == 1 && response == ACK) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return (ret < 0) ? ret : ERR_NACK;
|
||||
}
|
||||
|
||||
int read_flash(uint32_t gpio, uint8_t *buf, uint16_t len)
|
||||
{
|
||||
if (len == 0 || buf == nullptr) {
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
// Address must already be set via set_address()
|
||||
uint8_t cmd[4];
|
||||
cmd[0] = CMD_READ_FLASH;
|
||||
cmd[1] = (uint8_t)len;
|
||||
|
||||
uint16_t crc = crc16(cmd, 2);
|
||||
cmd[2] = crc & 0xFF;
|
||||
cmd[3] = (crc >> 8) & 0xFF;
|
||||
|
||||
int ret = send_packet(gpio, cmd, sizeof(cmd), buf, (uint8_t)len);
|
||||
|
||||
if (ret == (int)len) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return (ret < 0) ? ret : ERR_NACK;
|
||||
}
|
||||
|
||||
int run_app(uint32_t gpio)
|
||||
{
|
||||
static const uint8_t cmd[] = {0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// No response expected
|
||||
send_packet(gpio, cmd, sizeof(cmd), nullptr, 0);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace am32
|
||||
@@ -0,0 +1,157 @@
|
||||
/****************************************************************************
|
||||
*
|
||||
* Copyright (c) 2024 PX4 Development Team. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* 3. Neither the name PX4 nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* @file am32_protocol.hpp
|
||||
*
|
||||
* AM32 ESC bootloader protocol — bit-banged UART at 19200 baud.
|
||||
*
|
||||
* Extracted from Matt's proof-of-concept and cleaned up into a
|
||||
* function-based API for reuse by the esc_flasher module.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace am32
|
||||
{
|
||||
|
||||
// AM32 bootloader command bytes
|
||||
static constexpr uint8_t CMD_RUN = 0x00;
|
||||
static constexpr uint8_t CMD_PROG_FLASH = 0x01;
|
||||
static constexpr uint8_t CMD_READ_FLASH = 0x03;
|
||||
static constexpr uint8_t CMD_SET_BUFFER = 0xFE;
|
||||
static constexpr uint8_t CMD_SET_ADDRESS = 0xFF;
|
||||
|
||||
// Bootloader ACK byte
|
||||
static constexpr uint8_t ACK = 0x30;
|
||||
|
||||
// AM32 firmware memory layout (STM32 flash addresses)
|
||||
static constexpr uint32_t FIRMWARE_ADDR = 0x08001000;
|
||||
static constexpr uint32_t FIRMWARE_TAG_ADDR = 0x08007BE0;
|
||||
static constexpr uint16_t FIRMWARE_TAG_SIZE = 16;
|
||||
|
||||
// Protocol constants
|
||||
static constexpr uint16_t MAX_CHUNK_SIZE = 256;
|
||||
static constexpr uint32_t BIT_TIME_US = 52; // ~19200 baud
|
||||
static constexpr uint32_t HALF_BIT_TIME_US = 26;
|
||||
static constexpr uint32_t RX_TIMEOUT_US = 100000; // 100 ms
|
||||
|
||||
// Device info from handshake response
|
||||
static constexpr uint8_t DEVICE_INFO_SIZE = 9;
|
||||
|
||||
// Return codes
|
||||
static constexpr int OK = 0;
|
||||
static constexpr int ERR_TIMEOUT = -1;
|
||||
static constexpr int ERR_NACK = -2;
|
||||
static constexpr int ERR_CRC = -3;
|
||||
static constexpr int ERR_INVALID = -4;
|
||||
|
||||
/**
|
||||
* CRC-16 with polynomial 0xA001 (AM32 bootloader).
|
||||
*/
|
||||
uint16_t crc16(const uint8_t *buf, uint16_t len);
|
||||
|
||||
/**
|
||||
* Bit-banged UART send/receive on a GPIO pin.
|
||||
*
|
||||
* Enters a critical section (interrupts disabled) for the
|
||||
* duration of the transfer to maintain 19200-baud timing.
|
||||
*
|
||||
* @param gpio GPIO configuration value (port + pin bits)
|
||||
* @param tx_data Bytes to transmit
|
||||
* @param tx_len Number of bytes to transmit
|
||||
* @param rx_data Buffer for response (may be nullptr if rx_len == 0)
|
||||
* @param rx_len Expected number of response bytes
|
||||
* @return Number of bytes received, or negative error code
|
||||
*/
|
||||
int send_packet(uint32_t gpio, const uint8_t *tx_data, uint16_t tx_len,
|
||||
uint8_t *rx_data, uint8_t rx_len);
|
||||
|
||||
/**
|
||||
* Perform the bootloader handshake.
|
||||
*
|
||||
* Sends the AM32 boot-init sequence and reads back 9 bytes
|
||||
* of device information.
|
||||
*
|
||||
* @param gpio Target ESC GPIO
|
||||
* @param device_info Output buffer for 9-byte device info
|
||||
* @return OK or negative error code
|
||||
*/
|
||||
int handshake(uint32_t gpio, uint8_t device_info[DEVICE_INFO_SIZE]);
|
||||
|
||||
/**
|
||||
* Set the flash write/read address.
|
||||
*
|
||||
* @param gpio Target ESC GPIO
|
||||
* @param address 16-bit flash address
|
||||
* @return OK or negative error code
|
||||
*/
|
||||
int set_address(uint32_t gpio, uint16_t address);
|
||||
|
||||
/**
|
||||
* Write a chunk to flash.
|
||||
*
|
||||
* Sends CMD_SET_BUFFER + size, then data + CRC, then
|
||||
* CMD_PROG_FLASH and waits for ACK. Caller must call
|
||||
* set_address() first.
|
||||
*
|
||||
* @param gpio Target ESC GPIO
|
||||
* @param data Data to write (max MAX_CHUNK_SIZE bytes)
|
||||
* @param len Byte count
|
||||
* @return OK or negative error code
|
||||
*/
|
||||
int write_chunk(uint32_t gpio, const uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* Read flash memory.
|
||||
*
|
||||
* Caller must call set_address() first.
|
||||
*
|
||||
* @param gpio Target ESC GPIO
|
||||
* @param buf Output buffer
|
||||
* @param len Number of bytes to read
|
||||
* @return OK or negative error code
|
||||
*/
|
||||
int read_flash(uint32_t gpio, uint8_t *buf, uint16_t len);
|
||||
|
||||
/**
|
||||
* Send the RUN_APP command so the ESC boots its application.
|
||||
*
|
||||
* @param gpio Target ESC GPIO
|
||||
* @return OK (always; no response expected)
|
||||
*/
|
||||
int run_app(uint32_t gpio);
|
||||
|
||||
} // namespace am32
|
||||
Reference in New Issue
Block a user