From e01ce0a57055f70473bb0a2ecbd84c0cd57f3086 Mon Sep 17 00:00:00 2001 From: gnxlxnxx Date: Sun, 7 Jun 2026 01:12:42 +0200 Subject: [PATCH] Update stuff --- Embed.toml | 82 ++++++++++++++++++++++++++++ src/lib.rs | 3 ++ src/main.rs | 130 ++++++++++++++++++++++++++++++++++++--------- src/ringbuffer.rs | 67 +++++++++++++++++++++++ src/uart_parser.rs | 44 +++++++++++++++ 5 files changed, 302 insertions(+), 24 deletions(-) create mode 100644 Embed.toml create mode 100644 src/ringbuffer.rs create mode 100644 src/uart_parser.rs diff --git a/Embed.toml b/Embed.toml new file mode 100644 index 0000000..af5d2b7 --- /dev/null +++ b/Embed.toml @@ -0,0 +1,82 @@ +[default.probe] +# USB vendor ID +# usb_vid = "1337" +# USB product ID +# usb_pid = "1337" +# Serial number +# serial = "12345678" +# The protocol to be used for communicating with the target. +protocol = "Swd" +# The speed in kHz of the data link to the target. +# speed = 1337 + +[default.flashing] +# Whether or not the target should be flashed. +enabled = true +# Whether or not the target should be halted after reset. +# DEPRECATED, moved to reset section +#halt_afterwards = false +# Whether or not bytes erased but not rewritten with data from the ELF +# should be restored with their contents before erasing. +restore_unwritten_bytes = false +# The path where an SVG of the assembled flash layout should be written to. +# flash_layout_output_path = "out.svg" +# Triggers a full chip erase instead of a page by page erase. +do_chip_erase = false + +[default.reset] +# Whether or not the target should be reset. +# When flashing is enabled as well, the target will be reset after flashing. +enabled = true +# Whether or not the target should be halted after reset. +halt_afterwards = false + +[default.general] +# The chip name of the chip to be debugged. +# chip = "name" +# A list of chip descriptions to be loaded during runtime. +chip_descriptions = [] +# The default log level to be used. Possible values are one of: +# "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" +log_level = "WARN" +# Use this flag to assert the nreset & ntrst pins during attaching the probe to the chip. +connect_under_reset = false + +[default.rtt] +# Whether or not an RTTUI should be opened after flashing. +enabled = true +# How the target handles RTT outputs that won't fit in the buffer. This can be +# overridden per-channel. If left unset, the firmware will determine the default +# for each RTT up channel. +# NoBlockSkip - Skip writing the data completely if it doesn't fit in its +# entirety. +# NoBlockTrim - Write as much as possible of the data and ignore the rest. +# BlockIfFull - Spin until the host reads data. Can result in app freezing. +# +# up_mode = "BlockIfFull" + +# A list of channel associations to be displayed. If left empty, all channels are displayed. +# up, down (Optional) - RTT channel numbers +# name (Optional) - String to be displayed in the RTTUI tab +# up_mode (Optional) - RTT channel specific as described above +# format (Required) - How to interpret data from target firmware. One of: +# String - Directly show output from the target +# Defmt - Format output on the host, see https://defmt.ferrous-systems.com/ +# BinaryLE - Display as raw hex +#channels = [ +# # { up = 0, down = 0, name = "name", up_mode = "BlockIfFull", format = "Defmt" }, +#] +# The duration in ms for which the logger should retry to attach to RTT. +timeout = 3000 +# Whether timestamps in the RTTUI are enabled +#show_timestamps = true +# Whether to save rtt history buffer on exit. +log_enabled = false +# Where to save rtt history buffer relative to manifest path. +log_path = "./logs" + +[default.gdb] +# Whether or not a GDB server should be opened after flashing. +enabled = false +# The connection string in host:port format wher the GDB server will open a socket. +gdb_connection_string = "127.0.0.1:1337" diff --git a/src/lib.rs b/src/lib.rs index 1090c8e..30d0a2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,9 @@ #![no_main] #![no_std] +pub mod ringbuffer; +pub mod uart_parser; + use core::sync::atomic::{AtomicUsize, Ordering}; use defmt_rtt as _; // global logger diff --git a/src/main.rs b/src/main.rs index cdc10d8..9b0387a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ #![no_main] #![no_std] -#![feature(type_alias_impl_trait)] use pid_control as _; // global logger + panicking-behavior + memory layout // TODO -// - protect from i windup -> done? -// - uart configurable reference -// - impl timer +// - embedded-cli +// - average adc samples -> done? +// - uart configurable reference -> done +// - fix analog reference -> done +// - impl timer -> done +// - protect from i windup -> done #[rtic::app( device = stm32h7xx_hal::pac, dispatchers = [EXTI0] @@ -15,14 +17,18 @@ use pid_control as _; // global logger + panicking-behavior + memory layout mod app { use defmt::info; - use embedded_cli::Command; + use embedded_cli::{Command, cli::CliBuilder, writer::Writer}; + use pid_control::ringbuffer::Ringbuffer; + use pid_control::uart_parser::Parser; use stm32h7xx_hal::{ + self as hal, adc::{self, Adc}, dac::{self, C1}, gpio::{Analog, Pin, PinExt}, pac, prelude::*, + rcc::rec::AdcClkSel, serial::{self, Serial}, stm32::{ADC1, ADC2, DAC, USART1}, timer::{Event, Timer}, @@ -33,10 +39,14 @@ mod app { const KI: f32 = 0.5; const KD: f32 = 0f32; + const AVGBUFSIZE: usize = 5000; + // Shared resources go here #[shared] struct Shared { target: f32, + serial: Serial, + config: PIDConfig, } // Local resources go here @@ -47,12 +57,14 @@ mod app { p: f32, i: f32, d: f32, + reference: Pin<'C', 2, Analog>, input: Pin<'C', 3, Analog>, adc1: Adc, adc2: Adc, dac: C1, - serial: Serial, - // freq: fugit::Kilohertz, + parser: Parser, + freq: hal::time::Hertz, + avgbuffer: Ringbuffer, } #[init] @@ -66,7 +78,12 @@ mod app { let rcc = ctx.device.RCC.constrain(); // Configure clocks - let ccdr = rcc.sys_ck(400.MHz()).freeze(pwrcfg, &ctx.device.SYSCFG); + let mut ccdr = rcc + .sys_ck(400.MHz()) + .pll1_q_ck(100.MHz()) + .freeze(pwrcfg, &ctx.device.SYSCFG); + + ccdr.peripheral.kernel_adc_clk_mux(AdcClkSel::Per); let mut delay = ctx.core.SYST.delay(ccdr.clocks); @@ -144,7 +161,7 @@ mod app { info!("TIM3:"); info!("\tfreq: {} Hz", freq.to_Hz()); - let mut timer3 = ctx + let timer3 = ctx .device .TIM3 .timer(freq, ccdr.peripheral.TIM3, &ccdr.clocks); @@ -161,30 +178,62 @@ mod app { info!("===========init complete=========="); // Enable interrupts - timer3.listen(Event::TimeOut); // NOTE: comment for digital reference via uart + // timer3.listen(Event::TimeOut); // NOTE: comment for digital reference via uart timer2.listen(Event::TimeOut); serial.listen(serial::Event::Rxne); + // // create static buffers for use in cli (so we're not using stack memory) + // // History buffer is 1 byte longer so max command fits in it (it requires extra byte at end) + // // SAFETY: buffers are passed to cli and are used by cli only + // let (command_buffer, history_buffer) = unsafe { + // static mut COMMAND_BUFFER: [u8; 40] = [0; 40]; + // static mut HISTORY_BUFFER: [u8; 41] = [0; 41]; + // #[allow(static_mut_refs)] + // (COMMAND_BUFFER.as_mut(), HISTORY_BUFFER.as_mut()) + // }; + // let writer = Writer::new(&mut tx); + // let mut cli = CliBuilder::default() + // .writer(writer) + // .command_buffer(command_buffer) + // .history_buffer(history_buffer) + // .build() + // .ok()?; + let target = 0.7 / 3.3 * 4095f32; + let averaging = 50; + let config = PIDConfig { averaging }; + let avgbuffer = Ringbuffer::new(averaging); + ( - Shared { target }, + Shared { + target, + serial, + config, + }, Local { + avgbuffer, timer3, timer2, p: 0f32, i: 0f32, d: 0f32, + reference, input, adc1, adc2, dac, - serial, - // TODO: freq, + parser: Parser::new(), + freq, }, ) } + #[derive(Clone, Copy)] + pub struct PIDConfig { + averaging: usize, + } + #[derive(Debug, Command)] enum TargetCommand { /// Set Voltage @@ -202,28 +251,53 @@ mod app { }, } - #[task(binds = USART1, local = [serial], shared = [target])] - fn uart_target(ctx: uart_target::Context) { - // TODO: Do I need to clear the interrupt flag? - todo!() // TODO: use more embedded-cli + #[task(binds = USART1, local = [parser], shared = [serial, target], priority = 2)] + fn uart_target(mut ctx: uart_target::Context) { + let parser = ctx.local.parser; + + ctx.shared.serial.lock(|serial| { + use core::fmt::Write; + while serial.is_rxne() { + if let Ok(data) = serial.read() { + // echo back + let _ = write!(serial, "{}", data as char); + + // parse incoming byte + if let Some(target) = parser.input_byte(data) { + ctx.shared.target.lock(|t| { + *t = target as f32; + }); + } + } + } + }); } - #[task(binds = TIM3, local = [timer3, adc1], shared = [target])] - fn adc_target(ctx: adc_target::Context) { + #[task(binds = TIM3, local = [timer3, adc1, reference], shared = [target])] + fn adc_target(mut ctx: adc_target::Context) { // Clear interrupt flag ctx.local.timer3.clear_irq(); + let target: u32 = ctx.local.adc1.read(ctx.local.reference).unwrap(); // WHY COMPILER WHY + ctx.shared.target.lock(|t| { + *t = target as f32; + }); } - #[task(binds = TIM2, local = [timer2, p, i, d, input, adc2, dac], shared = [target], priority = 1)] + // #[task(binds = TIM2, local = [timer2, p, i, d, input, adc2, dac, freq], shared = [target, serial], priority = 1)] + #[task(binds = TIM2, local = [timer2, p, i, d, input, adc2, dac, freq, avgbuffer], shared = [target, config], priority = 1)] fn pid(mut ctx: pid::Context) { // Clear interrupt flag ctx.local.timer2.clear_irq(); - // TODO get sensible T - let t = 1.0; + // let config: PIDConfig = ctx.shared.config.lock(|c| *c); + let avgbuffer = ctx.local.avgbuffer; - let input: u32 = ctx.local.adc2.read(ctx.local.input).unwrap(); // WHY COMPILER WHY - let input: f32 = input as f32; + let t = 1f32 / (ctx.local.freq.to_kHz() as f32); + + let input: u32 = ctx.local.adc2.read(ctx.local.input).unwrap(); + avgbuffer.push(input); + // let input: f32 = input as f32; + let input: f32 = avgbuffer.average() as f32; let p = ctx.local.p; let i = ctx.local.i; @@ -255,5 +329,13 @@ mod app { "target: {:05},\tinput: {:05},\toutput: {:05},\tp: {:05},\ti: {:05},\td: {:05}", target, input, output, p, i, d ); + // let _ = ctx.shared.serial.lock(|serial| { + // use core::fmt::Write; + // write!( + // serial, + // "target: {:05},\tinput: {:05},\toutput: {:05},\tp: {:05},\ti: {:05},\td: {:05}", + // target, input, output, p, i, d + // ) + // }); } } diff --git a/src/ringbuffer.rs b/src/ringbuffer.rs new file mode 100644 index 0000000..ef8b7c7 --- /dev/null +++ b/src/ringbuffer.rs @@ -0,0 +1,67 @@ +pub struct Ringbuffer { + buf: [u32; N], + start: usize, + end: usize, + len: usize, +} + +impl Ringbuffer { + pub fn new(len: usize) -> Self { + let buf = [0; N]; + let start = 0; + let end = 0; + + Self { + buf, + start, + end, + len, + } + } + + fn curr_len(&self) -> usize { + (self.end - self.start + N) % N + } + + pub fn curr_start(&self) -> usize { + self.start + } + + pub fn push(&mut self, val: u32) { + if self.curr_len() == self.len { + self.start += 1; + self.start %= N; + } + self.end += 1; + self.end %= N; + self.buf[self.end] = val; + } + + pub fn pop(&mut self) -> Option { + if self.end - self.start > 0 { + let i = self.start; + self.start += 1; + self.start %= N; + Some(self.buf[i]) + } else { + None + } + } + + pub fn average(&self) -> u32 { + let mut sum = 0; + for i in 0..self.curr_len() { + sum += self.buf[(self.start + i) % N]; + } + sum / self.curr_len() as u32 + } + + pub fn resize(&mut self, len: usize) { + if self.curr_len() < len { + assert!(len <= N); // panic if length exceeds buffer length + } else { + self.start = (self.end - len + N) % N; + } + self.len = len; + } +} diff --git a/src/uart_parser.rs b/src/uart_parser.rs new file mode 100644 index 0000000..3985058 --- /dev/null +++ b/src/uart_parser.rs @@ -0,0 +1,44 @@ +use core::f32; +use core::str::{self, FromStr}; + +pub struct Parser { + pub curr: u32, + pub buffer: [u8; 100], +} + +impl Parser { + pub fn new() -> Self { + Self { + curr: 0, + buffer: [0_u8; 100], + } + } + + pub fn input_byte(&mut self, b: u8) -> Option { + if b == b'\n' || b == b'\r' { + let mut split = self.buffer.split(|num| { + num == &(u8::try_from(',').unwrap()) + || num == &(u8::try_from(' ').unwrap()) + || num == &0 + }); // TODO: the use \0 and ' ' as split is kinda hacky + let buf = split.next()?; + + let ret = if buf.contains(&b'.') { + Some((4095f32 / 3.3 * f32::from_str(str::from_utf8(buf).ok()?).ok()?) as u32) + } else { + Some(u32::from_str(str::from_utf8(buf).ok()?).ok()?) + }; + + for i in 0..self.curr { + self.buffer[i as usize] = 0; + } + + self.curr = 0; + ret + } else { + self.buffer[self.curr as usize] = b; + self.curr += 1; + None + } + } +}