diff --git a/README.md b/README.md index a3ba034..89dc63a 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ formatted as line-delimited JSON. | `load [0/1]` | Restore configuration for channel all/0/1 from flash | | `save [0/1]` | Save configuration for channel all/0/1 to flash | | `reset` | Reset the device | +| `dfu` | Reset device and enters USB device firmware update (DFU) mode | | `ipv4 [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway | diff --git a/memory.x b/memory.x index 213f9a1..c0ae171 100644 --- a/memory.x +++ b/memory.x @@ -3,10 +3,13 @@ MEMORY FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K /* reserved for config data */ CONFIG (rx) : ORIGIN = 0x8100000, LENGTH = 16K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K - 4 + /* reserved for DFU trigger message */ + DFU_MSG (wrx) : ORIGIN = 0x2001BFFC, LENGTH = 4 RAM2 (xrw) : ORIGIN = 0x2001C000, LENGTH = 16K RAM3 (xrw) : ORIGIN = 0x20020000, LENGTH = 64K CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K } _stack_start = ORIGIN(CCMRAM) + LENGTH(CCMRAM); +_dfu_msg = ORIGIN(DFU_MSG); diff --git a/src/command_parser.rs b/src/command_parser.rs index db1ea8f..09fac7b 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -179,6 +179,7 @@ pub enum Command { channel: usize, rate: Option, }, + Dfu, } fn end(input: &[u8]) -> IResult<&[u8], ()> { @@ -535,6 +536,7 @@ fn command(input: &[u8]) -> IResult<&[u8], Result> { pid, steinhart_hart, postfilter, + value(Ok(Command::Dfu), tag("dfu")), ))(input) } diff --git a/src/dfu.rs b/src/dfu.rs new file mode 100644 index 0000000..95a4085 --- /dev/null +++ b/src/dfu.rs @@ -0,0 +1,49 @@ +use cortex_m_rt::{pre_init}; + +const DFU_TRIG_MSG: u32 = 0xDECAFBAD; + +extern "C" { + // This symbol comes from memory.x + static mut _dfu_msg: u32; +} + +pub unsafe fn set_dfu_trigger() { + _dfu_msg = DFU_TRIG_MSG; +} + +/// Called by reset handler in lib.rs immediately after reset. +/// This function should not be called outside of reset handler as +/// bootloader expects MCU to be in reset state when called. +#[pre_init] +unsafe fn __pre_init() { + if _dfu_msg == DFU_TRIG_MSG { + _dfu_msg = 0x00000000; + + // Enable system config controller clock + const RCC_APB2ENR: *mut u32 = 0xE000_ED88 as *mut u32; + const RCC_APB2ENR_ENABLE_SYSCFG_CLOCK: u32 = 0x00004000; + + core::ptr::write_volatile( + RCC_APB2ENR, + *RCC_APB2ENR | RCC_APB2ENR_ENABLE_SYSCFG_CLOCK, + ); + + // Bypass BOOT pins and remap bootloader to 0x00000000 + const SYSCFG_MEMRMP: *mut u32 = 0x40013800 as *mut u32; + const SYSCFG_MEMRMP_MAP_ROM: u32 = 0x00000001; + + core::ptr::write_volatile( + SYSCFG_MEMRMP, + *SYSCFG_MEMRMP | SYSCFG_MEMRMP_MAP_ROM, + ); + + asm!( + // Set stack pointer to bootloader location + "LDR R0, =0x1FFF0000", + "LDR SP,[R0, #0]", + // Jump to bootloader + "LDR R0,[R0, #4]", + "BX R0", + ); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c745a8f..7f29472 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] -#![feature(maybe_uninit_extra, maybe_uninit_ref)] +#![feature(maybe_uninit_extra, maybe_uninit_ref, asm)] #![cfg_attr(test, allow(unused))] // TODO: #![deny(warnings, unused)] @@ -66,6 +66,7 @@ mod channel_state; mod config; use config::ChannelConfig; mod flash_store; +mod dfu; const HSE: MegaHertz = MegaHertz(8); #[cfg(not(feature = "semihosting"))] @@ -77,7 +78,6 @@ const CHANNEL_CONFIG_KEY: [&str; 2] = ["ch0", "ch1"]; const TCP_PORT: u16 = 23; - fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool { let send_free = socket.send_capacity() - socket.send_queue(); if data.len() > send_free + 1 { @@ -422,6 +422,16 @@ fn main() -> ! { channels.power_down(i); } + SCB::sys_reset(); + } + Command::Dfu => { + for i in 0..CHANNELS { + channels.power_down(i); + } + unsafe { + dfu::set_dfu_trigger(); + } + SCB::sys_reset(); } }