ionpak-thermostat/firmware/src/main.rs

517 lines
22 KiB
Rust

#![feature(const_fn, proc_macro_hygiene)]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)]
use cortex_m_rt::entry;
use core::fmt::{self, Write};
use smoltcp::time::Instant;
use smoltcp::wire::{IpCidr, IpAddress, EthernetAddress};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
use cortex_m_semihosting::hio;
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
use core::fmt::Write;
write!($crate::UART0, $($arg)*).unwrap()
})
}
#[macro_export]
macro_rules! println {
($fmt:expr) => (print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
#[cfg(not(test))]
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
#[panic_handler]
pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
println!("{}", info);
let mut stdout = hio::hstdout().unwrap();
let _ = writeln!(stdout, "{}", info);
loop {}
}
mod board;
use self::board::{
gpio::Gpio,
systick::get_time,
};
mod ethmac;
mod command_parser;
use command_parser::{Command, ShowCommand, PwmSetup, PwmMode, PwmConfig};
mod session;
use self::session::{Session, SessionOutput};
mod ad7172;
mod pid;
mod tec;
use tec::{Tec, TecPin};
mod steinhart_hart;
use steinhart_hart as sh;
pub struct UART0;
impl fmt::Write for UART0 {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let uart_0 = unsafe { &*tm4c129x::UART0::ptr() };
for c in s.bytes() {
while uart_0.fr.read().txff().bit() {}
uart_0.dr.write(|w| w.data().bits(c))
}
Ok(())
}
}
const TCP_RX_BUFFER_SIZE: usize = 256;
const TCP_TX_BUFFER_SIZE: usize = 8192;
macro_rules! create_socket_storage {
($rx_storage:ident, $tx_storage:ident) => (
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE];
)
}
macro_rules! create_socket {
($set:ident, $rx_storage:ident, $tx_storage:ident, $target:ident) => (
let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]);
let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]);
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
let $target = $set.add(tcp_socket);
)
}
const VCC: f32 = 3.3;
const PWM_PID_WIDTH: u16 = 0xffff;
const PWM_MAX: f32 = PWM_PID_WIDTH as f32;
const DEFAULT_PID_PARAMETERS: pid::Parameters = pid::Parameters {
kp: 0.5 * PWM_MAX,
ki: 0.05 * PWM_MAX,
kd: 0.45 * PWM_MAX,
output_min: 0.0,
output_max: PWM_MAX,
integral_min: 0.0,
integral_max: PWM_MAX,
};
const DEFAULT_SH_PARAMETERS: sh::Parameters = sh::Parameters {
a: 0.001_4,
b: 0.000_237,
c: 0.000_000_099,
parallel_r: 5_110.0, // Ohm (TODO: verify)
};
// TODO: maybe rename to `TECS`?
/// Number of TEC channels with four PWM channels each
pub const CHANNELS: usize = 2;
// TODO: maybe rename to `TecState`?
/// State per TEC channel
#[derive(Clone)]
struct ControlState {
/// Report data (time, data, temperature)
report: Option<(u64, i32, f32, Option<u16>)>,
pid_enabled: bool,
pid: pid::Controller,
sh: sh::Parameters,
}
#[cfg(not(test))]
#[entry]
fn main() -> ! {
let mut stdout = hio::hstdout().unwrap();
writeln!(stdout, "tecpak boot").unwrap();
board::init();
writeln!(stdout, "board initialized").unwrap();
let mut tec0 = Tec::tec0().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
let mut tec1 = Tec::tec1().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
println!(r#"
_ _
| | | |
/ _/___ _ __ _ __ __ _| |
| |/ _ \ /'__\| '_ \ / _` | |/ /
| | (/_/| |___| |_) | (_| | <
|_|\___\ \___/| .__/ \__,_|_|\_\
| |
|_| v1
"#);
// CSn
let pb4 = board::gpio::PB4.into_output();
// SCLK
let pb5 = board::gpio::PB5.into_output();
// MOSI
let pe4 = board::gpio::PE4.into_output();
// MISO
let pe5 = board::gpio::PE5.into_input();
// max 2 MHz = 0.5 us
let mut delay_fn = || for _ in 0..10 { cortex_m::asm::nop(); };
let spi = board::softspi::SyncSoftSpi::new(
board::softspi::SoftSpi::new(pb5, pe4, pe5),
&mut delay_fn
);
let mut adc = ad7172::Adc::new(spi, pb4).unwrap();
loop {
let r = adc.identify();
match r {
Err(e) =>
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
Ok(id) if id & 0xFFF0 == 0x00D0 => {
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
break;
}
Ok(_id) => {
// This always happens on the first attempt. So retry silently
}
};
}
writeln!(stdout, "AD7172: setting checksum mode").unwrap();
adc.set_checksum_mode(ad7172::ChecksumMode::Crc).unwrap();
loop {
let r = adc.identify();
match r {
Err(e) =>
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
Ok(id) if id & 0xFFF0 == 0x00D0 => {
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
break;
}
Ok(id) =>
writeln!(stdout, "Corrupt ADC id: {:04X}", id).unwrap(),
};
}
adc.set_sync_enable(false).unwrap();
// SENS0_{P,N}
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
// SENS1_{P,N}
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
let init_state = ControlState {
report: None,
// Start with disengaged PID to let user setup parameters first
pid_enabled: false,
pid: pid::Controller::new(DEFAULT_PID_PARAMETERS.clone()),
sh: DEFAULT_SH_PARAMETERS.clone(),
};
let mut states = [init_state.clone(), init_state.clone()];
let mut hardware_addr = EthernetAddress(board::get_mac_address());
writeln!(stdout, "MAC address: {}", hardware_addr).unwrap();
if hardware_addr.is_multicast() {
writeln!(stdout, "programmed MAC address is invalid, using default").unwrap();
hardware_addr = EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]);
}
let mut ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 1, 26), 24)];
println!("MAC {} IP {}", hardware_addr, ip_addrs[0]);
let mut neighbor_cache_storage = [None; 8];
let neighbor_cache = NeighborCache::new(&mut neighbor_cache_storage[..]);
let mut device = ethmac::Device::new();
unsafe { device.init(hardware_addr) };
let mut iface = EthernetInterfaceBuilder::new(&mut device)
.ethernet_addr(hardware_addr)
.neighbor_cache(neighbor_cache)
.ip_addrs(&mut ip_addrs[..])
.finalize();
create_socket_storage!(tcp_rx_storage0, tcp_tx_storage0);
create_socket_storage!(tcp_rx_storage1, tcp_tx_storage1);
create_socket_storage!(tcp_rx_storage2, tcp_tx_storage2);
create_socket_storage!(tcp_rx_storage3, tcp_tx_storage3);
create_socket_storage!(tcp_rx_storage4, tcp_tx_storage4);
create_socket_storage!(tcp_rx_storage5, tcp_tx_storage5);
create_socket_storage!(tcp_rx_storage6, tcp_tx_storage6);
create_socket_storage!(tcp_rx_storage7, tcp_tx_storage7);
let mut socket_set_entries: [_; 8] = Default::default();
let mut sockets = SocketSet::new(&mut socket_set_entries[..]);
create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle0);
create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, tcp_handle1);
create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, tcp_handle2);
create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, tcp_handle3);
create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, tcp_handle4);
create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, tcp_handle5);
create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, tcp_handle6);
create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, tcp_handle7);
let mut sessions_handles = [
(Session::new(), tcp_handle0),
(Session::new(), tcp_handle1),
(Session::new(), tcp_handle2),
(Session::new(), tcp_handle3),
(Session::new(), tcp_handle4),
(Session::new(), tcp_handle5),
(Session::new(), tcp_handle6),
(Session::new(), tcp_handle7),
];
loop {
// ADC input
adc.data_ready()
.unwrap_or_else(|e| {
writeln!(stdout, "ADC error: {:?}", e).unwrap();
None
}).map(|channel| {
let now = get_time();
let data = adc.read_data().unwrap();
let state = &mut states[usize::from(channel)];
let voltage = VCC * (data as f32) / (0x7FFFFF as f32);
let temperature = state.sh.get_temperature(voltage);
let pwm_width = if state.pid_enabled {
let width = state.pid.update(temperature) as u16;
match channel {
0 => tec0.set(TecPin::ISet, width, PWM_PID_WIDTH),
1 => tec1.set(TecPin::ISet, width, PWM_PID_WIDTH),
_ => unreachable!(),
}
Some(width)
} else {
None
};
state.report = Some((now, data, temperature, pwm_width));
for (session, _) in sessions_handles.iter_mut() {
session.set_report_pending(channel.into());
}
});
for (session, tcp_handle) in sessions_handles.iter_mut() {
let socket = &mut *sockets.get::<TcpSocket>(*tcp_handle);
if !socket.is_open() {
if session.is_dirty() {
// Reset a previously uses session/socket
*session = Session::new();
}
socket.listen(23).unwrap()
}
if socket.may_recv() && socket.may_send() {
let output = socket.recv(|buf| session.feed(buf));
// TODO: use "{}" to display pretty errors
match output {
Ok(SessionOutput::Nothing) => {}
Ok(SessionOutput::Command(command)) => match command {
Command::Quit =>
socket.close(),
Command::Reporting(reporting) => {
let _ = writeln!(socket, "report={}", if reporting { "on" } else { "off" });
}
Command::Show(ShowCommand::Reporting) => {
let _ = writeln!(socket, "report={}", if session.reporting() { "on" } else { "off" });
}
Command::Show(ShowCommand::Input) => {
for (channel, state) in states.iter().enumerate() {
state.report.map(|(time, data, temp, pwm_width)| {
let _ = write!(
socket, "t={} temp{}={} raw{}=0x{:06X}",
time, channel, temp, channel, data
);
pwm_width.map(|width| {
let _ = write!(
socket, " pwm{}=0x{:04X}",
channel, width
);
});
let _ = writeln!(socket, "");
});
}
}
Command::Show(ShowCommand::Pid) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(socket, "PID settings for channel {}", channel);
let pid = &state.pid;
let _ = writeln!(socket, "- target={:.4}", pid.get_target());
let p = pid.get_parameters();
macro_rules! out {
($p: tt) => {
let _ = writeln!(socket, "- {}={:.4}", stringify!($p), p.$p);
};
}
out!(kp);
out!(ki);
out!(kd);
out!(output_min);
out!(output_max);
out!(integral_min);
out!(integral_max);
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::Pwm) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(
socket, "channel {}: PID={}",
channel,
if state.pid_enabled { "engaged" } else { "disengaged" }
);
for pin in TecPin::VALID_VALUES {
let (width, total) = match channel {
0 => tec0.get(*pin),
1 => tec1.get(*pin),
_ => unreachable!(),
};
let _ = writeln!(socket, "- {}={}/{}", pin, width, total);
}
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::SteinhartHart) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(
socket, "channel {}: Steinhart-Hart equation parameters",
channel,
);
let _ = writeln!(socket, "- a={}", state.sh.a);
let _ = writeln!(socket, "- b={}", state.sh.b);
let _ = writeln!(socket, "- c={}", state.sh.c);
let _ = writeln!(socket, "- parallel_r={}", state.sh.parallel_r);
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::PostFilter) => {
for (channel, _) in states.iter().enumerate() {
match adc.get_postfilter(channel as u8).unwrap() {
Some(filter) => {
let _ = writeln!(
socket, "channel {}: postfilter={:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(
socket, "channel {}: no postfilter",
channel
);
}
}
}
}
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Pid) } => {
states[channel].pid_enabled = true;
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel);
}
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Manual(config))} => {
states[channel].pid_enabled = false;
let PwmConfig { width, total } = config;
match channel {
0 => tec0.set(TecPin::ISet, width, total),
1 => tec1.set(TecPin::ISet, width, total),
_ => unreachable!(),
}
let _ = writeln!(
socket, "channel {}: PWM duty cycle manually set to {}/{}",
channel, config.width, config.total
);
}
Command::Pwm { channel, setup } => {
let (pin, config) = match setup {
PwmSetup::ISet(_) =>
// Handled above
unreachable!(),
PwmSetup::MaxIPos(config) =>
(TecPin::MaxIPos, config),
PwmSetup::MaxINeg(config) =>
(TecPin::MaxINeg, config),
PwmSetup::MaxV(config) =>
(TecPin::MaxV, config),
};
let PwmConfig { width, total } = config;
match channel {
0 => tec0.set(pin, width, total),
1 => tec1.set(pin, width, total),
_ => unreachable!(),
}
let _ = writeln!(
socket, "channel {}: PWM {} reconfigured to {}/{}",
channel, pin, width, total
);
}
Command::Pid { channel, parameter, value } => {
let pid = &mut states[channel].pid;
use command_parser::PidParameter::*;
match parameter {
Target =>
pid.set_target(value),
KP =>
pid.update_parameters(|parameters| parameters.kp = value),
KI =>
pid.update_parameters(|parameters| parameters.ki = value),
KD =>
pid.update_parameters(|parameters| parameters.kd = value),
OutputMin =>
pid.update_parameters(|parameters| parameters.output_min = value),
OutputMax =>
pid.update_parameters(|parameters| parameters.output_max = value),
IntegralMin =>
pid.update_parameters(|parameters| parameters.integral_min = value),
IntegralMax =>
pid.update_parameters(|parameters| parameters.integral_max = value),
}
pid.reset();
let _ = writeln!(socket, "PID parameter updated");
}
Command::SteinhartHart { channel, parameter, value } => {
let sh = &mut states[channel].sh;
use command_parser::ShParameter::*;
match parameter {
A => sh.a = value,
B => sh.b = value,
C => sh.c = value,
ParallelR => sh.parallel_r = value,
}
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
}
Command::PostFilter { channel, rate } => {
let filter = ad7172::PostFilter::closest(rate);
match filter {
Some(filter) => {
adc.set_postfilter(channel as u8, Some(filter)).unwrap();
let _ = writeln!(
socket, "channel {}: postfilter set to {:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(socket, "Unable to choose postfilter");
}
}
}
}
Ok(SessionOutput::Error(e)) => {
let _ = writeln!(socket, "Command error: {:?}", e);
}
Err(_) => {}
}
}
if socket.may_send() {
if let Some(channel) = session.is_report_pending() {
states[channel].report.map(|(time, data, temp, pwm_width)| {
let _ = write!(
socket, "t={} temp{}={} raw{}=0x{:06X}",
time, channel, temp, channel, data
);
pwm_width.map(|width| {
let _ = write!(
socket, " pwm{}=0x{:04X}",
channel, width
);
});
let _ = writeln!(socket, "");
});
session.mark_report_sent(channel);
}
}
}
match iface.poll(&mut sockets, Instant::from_millis((get_time() / 1000) as i64)) {
Ok(_) => (),
Err(e) => println!("poll error: {}", e)
}
}
}