diff --git a/Cargo.lock b/Cargo.lock index 6a5f12c..5c77636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,18 +145,15 @@ dependencies = [ [[package]] name = "cortex-m-rt" version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb" +source = "git+https://github.com/rust-embedded/cortex-m-rt.git?rev=a2e3ad5#a2e3ad54478c6b98e519a1b0946395d790c0b6c7" dependencies = [ "cortex-m-rt-macros", - "r0", ] [[package]] name = "cortex-m-rt-macros" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" +version = "0.6.11" +source = "git+https://github.com/rust-embedded/cortex-m-rt.git?rev=a2e3ad5#a2e3ad54478c6b98e519a1b0946395d790c0b6c7" dependencies = [ "proc-macro2", "quote", @@ -203,7 +200,7 @@ dependencies = [ [[package]] name = "derive_miniconf" version = "0.1.0" -source = "git+https://github.com/quartiq/miniconf.git?rev=c6f2b28#c6f2b28f735e27b337eaa986846536e904c6f2bd" +source = "git+https://github.com/quartiq/miniconf.git?rev=2750533#275053396f0334e9efefa1ab2aae4c19b95a9a53" dependencies = [ "proc-macro2", "quote", @@ -350,7 +347,6 @@ dependencies = [ "as-slice", "generic-array 0.14.4", "hash32 0.1.1", - "serde", "stable_deref_trait", ] @@ -362,6 +358,7 @@ checksum = "c7ee8a997d259962217f40279f34201fdf06e669bafa69d7c1f4c7ff1893b5f6" dependencies = [ "atomic-polyfill", "hash32 0.2.1", + "serde", "stable_deref_trait", ] @@ -416,7 +413,7 @@ dependencies = [ [[package]] name = "miniconf" version = "0.1.0" -source = "git+https://github.com/quartiq/miniconf.git?rev=c6f2b28#c6f2b28f735e27b337eaa986846536e904c6f2bd" +source = "git+https://github.com/quartiq/miniconf.git?rev=2750533#275053396f0334e9efefa1ab2aae4c19b95a9a53" dependencies = [ "derive_miniconf", "serde", @@ -605,12 +602,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r0" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" - [[package]] name = "rand" version = "0.8.3" @@ -734,11 +725,11 @@ dependencies = [ [[package]] name = "serde-json-core" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39af17f40c2a28d2c9a7918663ddc8a10f54cc6f109ead5c3f010869761df186" +checksum = "8014aeea272bca0f0779778d43253f2f3375b414185b30e6ecc4d3e4a9994781" dependencies = [ - "heapless 0.6.1", + "heapless 0.7.1", "ryu", "serde", ] @@ -797,7 +788,7 @@ dependencies = [ "cortex-m-rtic", "dsp", "embedded-hal", - "heapless 0.6.1", + "heapless 0.7.1", "log", "mcp23017", "miniconf", diff --git a/Cargo.toml b/Cargo.toml index 9cfda87..ec3b2fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ cortex-m-rt = { version = "0.6", features = ["device"] } log = { version = "0.4", features = ["max_level_trace", "release_max_level_info"] } rtt-target = { version = "0.3", features = ["cortex-m"] } serde = { version = "1.0", features = ["derive"], default-features = false } -heapless = { version = "0.6", features = ["serde"] } +heapless = { version = "0.7", features = ["serde"] } cortex-m-rtic = "0.5.6" embedded-hal = "0.2.5" nb = "1.0.0" @@ -44,13 +44,14 @@ dsp = { path = "dsp" } ad9959 = { path = "ad9959" } miniconf = "0.1.0" shared-bus = {version = "0.2.2", features = ["cortex-m"] } -serde-json-core = "0.3" -postcard = "0.6" +serde-json-core = "0.4" +# rtt-target bump [dependencies.rtt-logger] git = "https://github.com/quartiq/rtt-logger.git" rev = "70b0eb5" +# rewrite [dependencies.mcp23017] git = "https://github.com/lucazulian/mcp23017.git" rev = "523d71d" @@ -59,9 +60,14 @@ rev = "523d71d" features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] version = "0.9.0" +# link.x section start/end +[patch.crates-io.cortex-m-rt] +git = "https://github.com/rust-embedded/cortex-m-rt.git" +rev = "a2e3ad5" + [patch.crates-io.miniconf] git = "https://github.com/quartiq/miniconf.git" -rev = "c6f2b28" +rev = "2750533" [dependencies.smoltcp-nal] git = "https://github.com/quartiq/smoltcp-nal.git" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e71f6a6 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/memory.x b/memory.x index c1569ce..d87640a 100644 --- a/memory.x +++ b/memory.x @@ -13,10 +13,6 @@ MEMORY } SECTIONS { - .itcm : ALIGN(8) { - *(.itcm .itcm.*); - . = ALIGN(8); - } > ITCM .axisram (NOLOAD) : ALIGN(8) { *(.axisram .axisram.*); . = ALIGN(8); @@ -33,4 +29,18 @@ SECTIONS { *(.sram3 .sram3.*); . = ALIGN(4); } > SRAM3 -} INSERT AFTER .bss; + .itcm : ALIGN(8) { + . = ALIGN(8); + __sitcm = .; + *(.itcm .itcm.*); + . = ALIGN(8); + __eitcm = .; + } > ITCM AT>FLASH + __siitcm = LOADADDR(.itcm); +} INSERT AFTER .uninit; + +ASSERT(__sitcm % 8 == 0 && __eitcm % 8 == 0, " +BUG(cortex-m-rt): .itcm is not 8-byte aligned"); + +ASSERT(__siitcm % 4 == 0, " +BUG(cortex-m-rt): the LMA of .itcm is not 4-byte aligned"); diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 1fad201..a6017fb 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -129,6 +129,8 @@ const APP: () = { /// Because the ADC and DAC operate at the same rate, these two constraints actually implement /// the same time bounds, meeting one also means the other is also met. #[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry, generator], priority=2)] + #[inline(never)] + #[link_section = ".itcm.process"] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 7200eb4..4864760 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -157,6 +157,8 @@ const APP: () = { /// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale. /// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available. #[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)] + #[inline(never)] + #[link_section = ".itcm.process"] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 786a72c..2a783f9 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -126,6 +126,52 @@ pub struct PounderDevices { /// Static storage for the ethernet DMA descriptor ring. static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); +/// Setup ITCM and load its code from flash. +/// +/// For portability and maintainability this is implemented in Rust. +/// Since this is implemented in Rust the compiler may assume that bss and data are set +/// up already. There is no easy way to ensure this implementation will never need bss +/// or data. Hence we can't safely run this as the cortex-m-rt `pre_init` hook before +/// bss/data is setup. +/// +/// Calling (through IRQ or directly) any code in ITCM before having called +/// this method is undefined. +fn load_itcm() { + extern "C" { + static mut __sitcm: u32; + static mut __eitcm: u32; + static mut __siitcm: u32; + } + use core::{ptr, slice, sync::atomic}; + + // NOTE(unsafe): Assuming the address symbols from the linker as well as + // the source instruction data are all valid, this is safe as it only + // copies linker-prepared data to where the code expects it to be. + // Calling it multiple times is safe as well. + + unsafe { + // ITCM is enabled on reset on our CPU but might not be on others. + // Keep for completeness. + const ITCMCR: *mut u32 = 0xE000_EF90usize as _; + ptr::write_volatile(ITCMCR, ptr::read_volatile(ITCMCR) | 1); + + // Ensure ITCM is enabled before loading. + atomic::fence(atomic::Ordering::SeqCst); + + let len = + (&__eitcm as *const u32).offset_from(&__sitcm as *const _) as usize; + let dst = slice::from_raw_parts_mut(&mut __sitcm as *mut _, len); + let src = slice::from_raw_parts(&__siitcm as *const _, len); + // Load code into ITCM. + dst.copy_from_slice(src); + } + + // Ensure ITCM is loaded before potentially executing any instructions from it. + atomic::fence(atomic::Ordering::SeqCst); + cortex_m::asm::dsb(); + cortex_m::asm::isb(); +} + /// Configure the stabilizer hardware for operation. /// /// # Args @@ -178,9 +224,12 @@ pub fn setup( log::set_logger(&LOGGER) .map(|()| log::set_max_level(log::LevelFilter::Trace)) .unwrap(); - log::info!("starting..."); + log::info!("Starting"); } + // Before being able to call any code in ITCM, load that code from flash. + load_itcm(); + // Set up the system timer for RTIC scheduling. { let tim15 = @@ -906,6 +955,7 @@ pub fn setup( #[cfg(feature = "pounder_v1_1")] let pounder_stamper = { + log::info!("Assuming Pounder v1.1 or later"); let etr_pin = gpioa.pa0.into_alternate_af3(); // The frequency in the constructor is dont-care, as we will modify the period + clock @@ -968,13 +1018,13 @@ pub fn setup( digital_inputs, }; + // Enable the instruction cache. + core.SCB.enable_icache(); + // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); // info!("Built on {}", build_info::BUILT_TIME_UTC); // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); log::info!("setup() complete"); - // Enable the instruction cache. - core.SCB.enable_icache(); - (stabilizer, pounder) } diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index 66152e0..98c12fa 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -90,11 +90,11 @@ fn panic(info: &core::panic::PanicInfo) -> ! { } #[cortex_m_rt::exception] -fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { panic!("HardFault at {:#?}", ef); } #[cortex_m_rt::exception] -fn DefaultHandler(irqn: i16) { +unsafe fn DefaultHandler(irqn: i16) { panic!("Unhandled exception (IRQn = {})", irqn); } diff --git a/src/net/data_stream.rs b/src/net/data_stream.rs index ab07457..cc61a28 100644 --- a/src/net/data_stream.rs +++ b/src/net/data_stream.rs @@ -8,12 +8,12 @@ use super::NetworkReference; use crate::hardware::design_parameters::SAMPLE_BUFFER_SIZE; // The number of data blocks that we will buffer in the queue. -type BlockBufferSize = heapless::consts::U30; +const BLOCK_BUFFER_SIZE: usize = 30; pub fn setup_streaming( stack: NetworkReference, ) -> (BlockGenerator, DataStream) { - let queue = cortex_m::singleton!(: Queue = Queue::new()).unwrap(); + let queue = cortex_m::singleton!(: Queue = Queue::new()).unwrap(); let (producer, consumer) = queue.split(); @@ -25,7 +25,7 @@ pub fn setup_streaming( } pub fn serialize_blocks<'a>(buffer: &'a mut [u8], max_buffer_size: usize, queue: &mut Consumer<'static, - AdcDacData, BlockBufferSize>) -> &'a [u8] { + AdcDacData, BLOCK_BUFFER_SIZE>) -> &'a [u8] { // While there is space in the buffer, serialize into it. let block_size = (SAMPLE_BUFFER_SIZE * 2) * 2 * 2 + 8; @@ -69,12 +69,12 @@ pub struct AdcDacData { } pub struct BlockGenerator { - queue: Producer<'static, AdcDacData, BlockBufferSize>, + queue: Producer<'static, AdcDacData, BLOCK_BUFFER_SIZE>, current_id: u32, } impl BlockGenerator { - pub fn new(queue: Producer<'static, AdcDacData, BlockBufferSize>) -> Self { + pub fn new(queue: Producer<'static, AdcDacData, BLOCK_BUFFER_SIZE>) -> Self { Self { queue, current_id: 0, @@ -103,7 +103,7 @@ impl BlockGenerator { pub struct DataStream { stack: NetworkReference, socket: Option<::UdpSocket>, - queue: Consumer<'static, AdcDacData, BlockBufferSize>, + queue: Consumer<'static, AdcDacData, BLOCK_BUFFER_SIZE>, remote: Option, buffer: [u8; 1024], } @@ -138,7 +138,7 @@ impl DataBlock { impl DataStream { pub fn new( stack: NetworkReference, - consumer: Consumer<'static, AdcDacData, BlockBufferSize>, + consumer: Consumer<'static, AdcDacData, BLOCK_BUFFER_SIZE>, ) -> Self { Self { stack, diff --git a/src/net/messages.rs b/src/net/messages.rs index 167e440..3887fef 100644 --- a/src/net/messages.rs +++ b/src/net/messages.rs @@ -1,4 +1,4 @@ -use heapless::{consts, String, Vec}; +use heapless::{String, Vec}; use serde::Serialize; use core::fmt::Write; @@ -12,15 +12,15 @@ pub enum SettingsResponseCode { /// Represents a generic MQTT message. pub struct MqttMessage<'a> { pub topic: &'a str, - pub message: Vec, - pub properties: Vec, consts::U1>, + pub message: Vec, + pub properties: Vec, 1>, } /// The payload of the MQTT response message to a settings update request. #[derive(Serialize)] pub struct SettingsResponse { code: u8, - msg: String, + msg: String<64>, } impl<'a> MqttMessage<'a> { @@ -48,8 +48,7 @@ impl<'a> MqttMessage<'a> { .unwrap_or(&default_response); // Associate any provided correlation data with the response. - let mut correlation_data: Vec, consts::U1> = - Vec::new(); + let mut correlation_data: Vec, 1> = Vec::new(); if let Some(data) = properties .iter() .find(|prop| matches!(prop, minimq::Property::CorrelationData(_))) diff --git a/src/net/miniconf_client.rs b/src/net/miniconf_client.rs index 76c64d2..cc838f6 100644 --- a/src/net/miniconf_client.rs +++ b/src/net/miniconf_client.rs @@ -10,7 +10,7 @@ ///! ///! Respones to settings updates are sent without quality-of-service guarantees, so there's no ///! guarantee that the requestee will be informed that settings have been applied. -use heapless::{consts, String}; +use heapless::String; use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; use crate::hardware::design_parameters::MQTT_BROKER; @@ -20,11 +20,11 @@ pub struct MiniconfClient where S: miniconf::Miniconf + Default + Clone, { - default_response_topic: String, + default_response_topic: String<128>, mqtt: minimq::Minimq, settings: S, subscribed: bool, - settings_prefix: String, + settings_prefix: String<64>, } impl MiniconfClient @@ -41,10 +41,10 @@ where let mqtt = minimq::Minimq::new(MQTT_BROKER.into(), client_id, stack).unwrap(); - let mut response_topic: String = String::from(prefix); + let mut response_topic: String<128> = String::from(prefix); response_topic.push_str("/log").unwrap(); - let mut settings_prefix: String = String::from(prefix); + let mut settings_prefix: String<64> = String::from(prefix); settings_prefix.push_str("/settings").unwrap(); Self { @@ -81,7 +81,7 @@ where if !self.subscribed && mqtt_connected { // Note(unwrap): We construct a string with two more characters than the prefix // strucutre, so we are guaranteed to have space for storage. - let mut settings_topic: String = + let mut settings_topic: String<66> = String::from(self.settings_prefix.as_str()); settings_topic.push_str("/#").unwrap(); diff --git a/src/net/mod.rs b/src/net/mod.rs index 7d52bc4..7c31bf0 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -5,7 +5,7 @@ ///! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data ///! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines ///! related to Stabilizer networking operations. -use heapless::{consts, String}; +use heapless::String; use miniconf::Miniconf; use serde::Serialize; @@ -129,7 +129,6 @@ where /// # Returns /// An indication if any of the network users indicated a state change. pub fn update(&mut self) -> NetworkState { - super::debug::high(); // Update the MQTT clients. self.telemetry.update(); @@ -149,8 +148,6 @@ where UpdateState::NoChange => poll_result, }; - super::debug::low(); - result } } @@ -168,7 +165,7 @@ fn get_client_id( app: &str, client: &str, mac: smoltcp_nal::smoltcp::wire::EthernetAddress, -) -> String { +) -> String<64> { let mut identifier = String::new(); write!(&mut identifier, "{}-{}-{}", app, mac, client).unwrap(); identifier @@ -185,10 +182,10 @@ fn get_client_id( pub fn get_device_prefix( app: &str, mac: smoltcp_nal::smoltcp::wire::EthernetAddress, -) -> String { +) -> String<128> { // Note(unwrap): The mac address + binary name must be short enough to fit into this string. If // they are defined too long, this will panic and the device will fail to boot. - let mut prefix: String = String::new(); + let mut prefix: String<128> = String::new(); write!(&mut prefix, "dt/sinara/{}/{}", app, mac).unwrap(); prefix diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index b6b9c34..d976fee 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -10,7 +10,7 @@ ///! sampling frequency. Instead, the raw codes are stored and the telemetry is generated as ///! required immediately before transmission. This ensures that any slower computation required ///! for unit conversion can be off-loaded to lower priority tasks. -use heapless::{consts, String, Vec}; +use heapless::{String, Vec}; use minimq::QoS; use serde::Serialize; @@ -22,7 +22,7 @@ use crate::hardware::{ /// The telemetry client for reporting telemetry data over MQTT. pub struct TelemetryClient { mqtt: minimq::Minimq, - telemetry_topic: String, + telemetry_topic: String<128>, _telemetry: core::marker::PhantomData, } @@ -99,7 +99,7 @@ impl TelemetryClient { let mqtt = minimq::Minimq::new(MQTT_BROKER.into(), client_id, stack).unwrap(); - let mut telemetry_topic: String = String::from(prefix); + let mut telemetry_topic: String<128> = String::from(prefix); telemetry_topic.push_str("/telemetry").unwrap(); Self { @@ -118,7 +118,7 @@ impl TelemetryClient { /// # Args /// * `telemetry` - The telemetry to report pub fn publish(&mut self, telemetry: &T) { - let telemetry: Vec = + let telemetry: Vec = serde_json_core::to_vec(telemetry).unwrap(); self.mqtt .client