Update stuff

This commit is contained in:
2026-06-07 01:12:42 +02:00
parent 35e020c62e
commit e01ce0a570
5 changed files with 302 additions and 24 deletions
+82
View File
@@ -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"
+3
View File
@@ -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
+106 -24
View File
@@ -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<USART1>,
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<ADC1, adc::Enabled>,
adc2: Adc<ADC2, adc::Enabled>,
dac: C1<DAC, dac::Enabled>,
serial: Serial<USART1>,
// freq: fugit::Kilohertz<u32>,
parser: Parser,
freq: hal::time::Hertz,
avgbuffer: Ringbuffer<AVGBUFSIZE>,
}
#[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
// )
// });
}
}
+67
View File
@@ -0,0 +1,67 @@
pub struct Ringbuffer<const N: usize> {
buf: [u32; N],
start: usize,
end: usize,
len: usize,
}
impl<const N: usize> Ringbuffer<N> {
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<u32> {
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;
}
}
+44
View File
@@ -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<u32> {
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
}
}
}