Update stuff
This commit is contained in:
+82
@@ -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"
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
pub mod ringbuffer;
|
||||||
|
pub mod uart_parser;
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use defmt_rtt as _; // global logger
|
use defmt_rtt as _; // global logger
|
||||||
|
|
||||||
|
|||||||
+106
-24
@@ -1,13 +1,15 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(type_alias_impl_trait)]
|
|
||||||
|
|
||||||
use pid_control as _; // global logger + panicking-behavior + memory layout
|
use pid_control as _; // global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// - protect from i windup -> done?
|
// - embedded-cli
|
||||||
// - uart configurable reference
|
// - average adc samples -> done?
|
||||||
// - impl timer
|
// - uart configurable reference -> done
|
||||||
|
// - fix analog reference -> done
|
||||||
|
// - impl timer -> done
|
||||||
|
// - protect from i windup -> done
|
||||||
#[rtic::app(
|
#[rtic::app(
|
||||||
device = stm32h7xx_hal::pac,
|
device = stm32h7xx_hal::pac,
|
||||||
dispatchers = [EXTI0]
|
dispatchers = [EXTI0]
|
||||||
@@ -15,14 +17,18 @@ use pid_control as _; // global logger + panicking-behavior + memory layout
|
|||||||
mod app {
|
mod app {
|
||||||
use defmt::info;
|
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::{
|
use stm32h7xx_hal::{
|
||||||
|
self as hal,
|
||||||
adc::{self, Adc},
|
adc::{self, Adc},
|
||||||
dac::{self, C1},
|
dac::{self, C1},
|
||||||
gpio::{Analog, Pin, PinExt},
|
gpio::{Analog, Pin, PinExt},
|
||||||
pac,
|
pac,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
rcc::rec::AdcClkSel,
|
||||||
serial::{self, Serial},
|
serial::{self, Serial},
|
||||||
stm32::{ADC1, ADC2, DAC, USART1},
|
stm32::{ADC1, ADC2, DAC, USART1},
|
||||||
timer::{Event, Timer},
|
timer::{Event, Timer},
|
||||||
@@ -33,10 +39,14 @@ mod app {
|
|||||||
const KI: f32 = 0.5;
|
const KI: f32 = 0.5;
|
||||||
const KD: f32 = 0f32;
|
const KD: f32 = 0f32;
|
||||||
|
|
||||||
|
const AVGBUFSIZE: usize = 5000;
|
||||||
|
|
||||||
// Shared resources go here
|
// Shared resources go here
|
||||||
#[shared]
|
#[shared]
|
||||||
struct Shared {
|
struct Shared {
|
||||||
target: f32,
|
target: f32,
|
||||||
|
serial: Serial<USART1>,
|
||||||
|
config: PIDConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local resources go here
|
// Local resources go here
|
||||||
@@ -47,12 +57,14 @@ mod app {
|
|||||||
p: f32,
|
p: f32,
|
||||||
i: f32,
|
i: f32,
|
||||||
d: f32,
|
d: f32,
|
||||||
|
reference: Pin<'C', 2, Analog>,
|
||||||
input: Pin<'C', 3, Analog>,
|
input: Pin<'C', 3, Analog>,
|
||||||
adc1: Adc<ADC1, adc::Enabled>,
|
adc1: Adc<ADC1, adc::Enabled>,
|
||||||
adc2: Adc<ADC2, adc::Enabled>,
|
adc2: Adc<ADC2, adc::Enabled>,
|
||||||
dac: C1<DAC, dac::Enabled>,
|
dac: C1<DAC, dac::Enabled>,
|
||||||
serial: Serial<USART1>,
|
parser: Parser,
|
||||||
// freq: fugit::Kilohertz<u32>,
|
freq: hal::time::Hertz,
|
||||||
|
avgbuffer: Ringbuffer<AVGBUFSIZE>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
@@ -66,7 +78,12 @@ mod app {
|
|||||||
let rcc = ctx.device.RCC.constrain();
|
let rcc = ctx.device.RCC.constrain();
|
||||||
|
|
||||||
// Configure clocks
|
// 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);
|
let mut delay = ctx.core.SYST.delay(ccdr.clocks);
|
||||||
|
|
||||||
@@ -144,7 +161,7 @@ mod app {
|
|||||||
info!("TIM3:");
|
info!("TIM3:");
|
||||||
info!("\tfreq: {} Hz", freq.to_Hz());
|
info!("\tfreq: {} Hz", freq.to_Hz());
|
||||||
|
|
||||||
let mut timer3 = ctx
|
let timer3 = ctx
|
||||||
.device
|
.device
|
||||||
.TIM3
|
.TIM3
|
||||||
.timer(freq, ccdr.peripheral.TIM3, &ccdr.clocks);
|
.timer(freq, ccdr.peripheral.TIM3, &ccdr.clocks);
|
||||||
@@ -161,30 +178,62 @@ mod app {
|
|||||||
info!("===========init complete==========");
|
info!("===========init complete==========");
|
||||||
|
|
||||||
// Enable interrupts
|
// 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);
|
timer2.listen(Event::TimeOut);
|
||||||
serial.listen(serial::Event::Rxne);
|
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 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 {
|
Local {
|
||||||
|
avgbuffer,
|
||||||
timer3,
|
timer3,
|
||||||
timer2,
|
timer2,
|
||||||
p: 0f32,
|
p: 0f32,
|
||||||
i: 0f32,
|
i: 0f32,
|
||||||
d: 0f32,
|
d: 0f32,
|
||||||
|
reference,
|
||||||
input,
|
input,
|
||||||
adc1,
|
adc1,
|
||||||
adc2,
|
adc2,
|
||||||
dac,
|
dac,
|
||||||
serial,
|
parser: Parser::new(),
|
||||||
// TODO: freq,
|
freq,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PIDConfig {
|
||||||
|
averaging: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Command)]
|
#[derive(Debug, Command)]
|
||||||
enum TargetCommand {
|
enum TargetCommand {
|
||||||
/// Set Voltage
|
/// Set Voltage
|
||||||
@@ -202,28 +251,53 @@ mod app {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USART1, local = [serial], shared = [target])]
|
#[task(binds = USART1, local = [parser], shared = [serial, target], priority = 2)]
|
||||||
fn uart_target(ctx: uart_target::Context) {
|
fn uart_target(mut ctx: uart_target::Context) {
|
||||||
// TODO: Do I need to clear the interrupt flag?
|
let parser = ctx.local.parser;
|
||||||
todo!() // TODO: use more embedded-cli
|
|
||||||
|
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])]
|
#[task(binds = TIM3, local = [timer3, adc1, reference], shared = [target])]
|
||||||
fn adc_target(ctx: adc_target::Context) {
|
fn adc_target(mut ctx: adc_target::Context) {
|
||||||
// Clear interrupt flag
|
// Clear interrupt flag
|
||||||
ctx.local.timer3.clear_irq();
|
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) {
|
fn pid(mut ctx: pid::Context) {
|
||||||
// Clear interrupt flag
|
// Clear interrupt flag
|
||||||
ctx.local.timer2.clear_irq();
|
ctx.local.timer2.clear_irq();
|
||||||
|
|
||||||
// TODO get sensible T
|
// let config: PIDConfig = ctx.shared.config.lock(|c| *c);
|
||||||
let t = 1.0;
|
let avgbuffer = ctx.local.avgbuffer;
|
||||||
|
|
||||||
let input: u32 = ctx.local.adc2.read(ctx.local.input).unwrap(); // WHY COMPILER WHY
|
let t = 1f32 / (ctx.local.freq.to_kHz() as f32);
|
||||||
let input: f32 = input 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 p = ctx.local.p;
|
||||||
let i = ctx.local.i;
|
let i = ctx.local.i;
|
||||||
@@ -255,5 +329,13 @@ mod app {
|
|||||||
"target: {:05},\tinput: {:05},\toutput: {:05},\tp: {:05},\ti: {:05},\td: {:05}",
|
"target: {:05},\tinput: {:05},\toutput: {:05},\tp: {:05},\ti: {:05},\td: {:05}",
|
||||||
target, input, output, p, i, d
|
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
|
||||||
|
// )
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user