thermostat/src/main.rs

503 lines
24 KiB
Rust
Raw Normal View History

#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)]
#![feature(maybe_uninit_extra, maybe_uninit_ref, asm)]
2020-09-18 07:22:41 +08:00
#![cfg_attr(test, allow(unused))]
2020-03-12 06:17:17 +08:00
// TODO: #![deny(warnings, unused)]
2019-03-07 23:27:33 +08:00
#[cfg(not(any(feature = "semihosting", test)))]
2019-04-27 21:23:50 +08:00
use panic_abort as _;
2020-09-18 06:23:30 +08:00
#[cfg(all(feature = "semihosting", not(test)))]
2019-04-27 21:23:50 +08:00
use panic_semihosting as _;
2019-03-07 23:27:33 +08:00
use log::{error, info, warn};
2019-03-15 02:58:41 +08:00
use core::fmt::Write;
2019-03-12 01:23:52 +08:00
use cortex_m::asm::wfi;
2019-03-07 23:27:33 +08:00
use cortex_m_rt::entry;
2019-03-12 01:23:52 +08:00
use stm32f4xx_hal::{
2020-09-18 06:55:53 +08:00
hal::watchdog::{WatchdogEnable, Watchdog},
2019-03-12 01:23:52 +08:00
rcc::RccExt,
2020-09-25 06:14:29 +08:00
stm32::{CorePeripherals, Peripherals, SCB},
time::{U32Ext, MegaHertz},
watchdog::IndependentWatchdog,
2019-03-12 01:23:52 +08:00
};
use smoltcp::{
time::Instant,
socket::TcpSocket,
wire::EthernetAddress,
};
2020-09-14 04:52:20 +08:00
use uom::{
si::{
f64::{
2020-09-17 04:05:31 +08:00
ElectricCurrent,
2020-09-14 04:52:20 +08:00
ElectricPotential,
ElectricalResistance,
2020-09-17 00:40:07 +08:00
ThermodynamicTemperature,
2020-09-14 04:52:20 +08:00
},
electric_current::ampere,
electric_potential::volt,
electrical_resistance::ohm,
2020-09-17 00:40:07 +08:00
thermodynamic_temperature::degree_celsius,
2020-09-14 04:52:20 +08:00
},
};
2019-03-12 01:23:52 +08:00
2020-03-12 07:50:24 +08:00
mod init_log;
use init_log::init_log;
2020-09-11 05:17:31 +08:00
mod usb;
2020-09-07 03:10:10 +08:00
mod leds;
2020-03-09 07:27:35 +08:00
mod pins;
use pins::Pins;
mod ad7172;
2020-03-13 04:27:03 +08:00
mod ad5680;
2019-03-13 05:52:39 +08:00
mod net;
mod server;
use server::Server;
2020-03-14 06:39:22 +08:00
mod session;
use session::{Session, SessionInput};
2020-03-14 06:39:22 +08:00
mod command_parser;
use command_parser::{Command, Ipv4Config, PwmPin, ShowCommand};
2019-03-15 01:13:25 +08:00
mod timer;
2020-03-19 04:51:30 +08:00
mod pid;
mod steinhart_hart;
2020-05-13 05:16:57 +08:00
mod channels;
2020-05-13 06:04:55 +08:00
use channels::{CHANNELS, Channels};
2020-05-13 05:16:57 +08:00
mod channel;
mod channel_state;
2020-09-24 07:18:33 +08:00
mod config;
use config::ChannelConfig;
mod flash_store;
mod dfu;
2019-03-07 23:27:33 +08:00
2020-03-20 01:34:57 +08:00
const HSE: MegaHertz = MegaHertz(8);
2020-03-12 06:17:34 +08:00
#[cfg(not(feature = "semihosting"))]
const WATCHDOG_INTERVAL: u32 = 1_000;
2020-03-12 06:17:34 +08:00
#[cfg(feature = "semihosting")]
const WATCHDOG_INTERVAL: u32 = 30_000;
2020-03-12 06:17:34 +08:00
const CHANNEL_CONFIG_KEY: [&str; 2] = ["ch0", "ch1"];
2020-09-25 05:04:29 +08:00
2020-03-14 06:39:22 +08:00
const TCP_PORT: u16 = 23;
2019-03-15 02:58:41 +08:00
2020-10-01 04:10:42 +08:00
fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool {
let send_free = socket.send_capacity() - socket.send_queue();
2020-10-01 04:10:42 +08:00
if data.len() > send_free + 1 {
2020-12-17 05:14:21 +08:00
// Not enough buffer space, skip report for now,
// instead of sending incomplete line
2020-10-01 04:10:42 +08:00
warn!(
"TCP socket has only {}/{} needed {}",
send_free + 1, socket.send_capacity(), data.len(),
);
} else {
match socket.send_slice(&data) {
Ok(sent) if sent == data.len() => {
let _ = socket.send_slice(b"\n");
// success
return true
}
2020-10-01 04:10:42 +08:00
Ok(sent) =>
warn!("sent only {}/{} bytes", sent, data.len()),
Err(e) =>
error!("error sending line: {:?}", e),
}
}
// not success
false
}
2019-03-19 04:41:51 +08:00
/// Initialization and main loop
#[cfg(not(test))]
2019-03-07 23:27:33 +08:00
#[entry]
fn main() -> ! {
2019-03-15 02:58:41 +08:00
init_log();
2020-10-11 02:45:44 +08:00
info!("thermostat");
2019-03-12 01:23:52 +08:00
let mut cp = CorePeripherals::take().unwrap();
2019-03-13 05:52:39 +08:00
cp.SCB.enable_icache();
cp.SCB.enable_dcache(&mut cp.CPUID);
2019-03-12 01:23:52 +08:00
let dp = Peripherals::take().unwrap();
2019-03-15 01:13:25 +08:00
let clocks = dp.RCC.constrain()
2019-03-12 01:23:52 +08:00
.cfgr
.use_hse(HSE)
.sysclk(168.mhz())
.hclk(168.mhz())
.pclk1(32.mhz())
.pclk2(64.mhz())
2019-03-12 01:23:52 +08:00
.freeze();
let mut wd = IndependentWatchdog::new(dp.IWDG);
2020-03-12 06:17:34 +08:00
wd.start(WATCHDOG_INTERVAL.ms());
2019-03-12 01:23:52 +08:00
wd.feed();
2020-05-29 02:45:42 +08:00
timer::setup(cp.SYST, clocks);
let (pins, mut leds, mut eeprom, eth_pins, usb) = Pins::setup(
2020-03-13 00:26:14 +08:00
clocks, dp.TIM1, dp.TIM3,
2020-09-07 03:10:10 +08:00
dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOD, dp.GPIOE, dp.GPIOF, dp.GPIOG,
dp.I2C1,
2020-04-11 03:05:05 +08:00
dp.SPI2, dp.SPI4, dp.SPI5,
2020-05-29 02:43:34 +08:00
dp.ADC1,
2020-09-11 05:17:31 +08:00
dp.OTG_FS_GLOBAL,
dp.OTG_FS_DEVICE,
dp.OTG_FS_PWRCLK,
2020-03-13 00:26:14 +08:00
);
2020-09-07 03:10:10 +08:00
leds.r1.on();
leds.g3.off();
leds.g4.off();
2020-09-11 05:17:31 +08:00
usb::State::setup(usb);
let mut store = flash_store::store(dp.FLASH);
let mut store_value_buf = [0u8; 256];
2020-05-13 05:16:57 +08:00
let mut channels = Channels::new(pins);
for c in 0..CHANNELS {
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
Ok(Some(config)) =>
config.apply(&mut channels, c),
Ok(None) =>
error!("flash config not found for channel {}", c),
Err(e) =>
error!("unable to load config {} from flash: {:?}", c, e),
}
}
// default net config:
let mut ipv4_config = Ipv4Config {
address: [192, 168, 1, 26],
mask_len: 24,
gateway: None,
};
match store.read_value("ipv4") {
Ok(Some(config)) =>
ipv4_config = config,
Ok(None) => {}
Err(e) =>
error!("cannot read ipv4 config: {:?}", e),
}
2019-03-15 01:13:25 +08:00
// EEPROM ships with a read-only EUI-48 identifier
let mut eui48 = [0; 6];
eeprom.read_data(0xFA, &mut eui48).unwrap();
let hwaddr = EthernetAddress(eui48);
info!("EEPROM MAC address: {}", hwaddr);
net::run(clocks, dp.ETHERNET_MAC, dp.ETHERNET_DMA, eth_pins, hwaddr, ipv4_config.clone(), |iface| {
2020-03-14 06:39:22 +08:00
Server::<Session>::run(iface, |server| {
2020-09-07 03:10:10 +08:00
leds.r1.off();
let mut should_reset = false;
2020-09-07 03:10:10 +08:00
2019-03-19 03:02:57 +08:00
loop {
let mut new_ipv4_config = None;
let instant = Instant::from_millis(i64::from(timer::now()));
2020-05-13 06:04:55 +08:00
let updated_channel = channels.poll_adc(instant);
if let Some(channel) = updated_channel {
2020-03-14 06:39:22 +08:00
server.for_each(|_, session| session.set_report_pending(channel.into()));
2020-05-13 06:04:55 +08:00
}
2020-03-14 06:39:22 +08:00
let instant = Instant::from_millis(i64::from(timer::now()));
cortex_m::interrupt::free(net::clear_pending);
server.poll(instant)
.unwrap_or_else(|e| {
warn!("poll: {:?}", e);
});
if ! should_reset {
// TCP protocol handling
server.for_each(|mut socket, session| {
if ! socket.is_active() {
let _ = socket.listen(TCP_PORT);
session.reset();
} else if socket.may_send() && !socket.may_recv() {
socket.close()
} else if socket.can_send() && socket.can_recv() {
match socket.recv(|buf| session.feed(buf)) {
// SessionInput::Nothing happens when the line reader parses a string of characters that is not
// followed by a newline character. Could be due to partial commands not terminated with newline,
// socket RX ring buffer wraps around, or when the command is sent as seperate TCP packets etc.
// Do nothing and feed more data to the line reader in the next loop cycle.
Ok(SessionInput::Nothing) => {}
Ok(SessionInput::Command(command)) => match command {
Command::Quit =>
socket.close(),
Command::Reporting(_reporting) => {
// handled by session
send_line(&mut socket, b"{}");
}
Command::Show(ShowCommand::Reporting) => {
let _ = writeln!(socket, "{{ \"report\": {:?} }}", session.reporting());
}
Command::Show(ShowCommand::Input) => {
match channels.reports_json() {
Ok(buf) => {
send_line(&mut socket, &buf[..]);
}
Err(e) => {
error!("unable to serialize report: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
2020-12-17 05:14:21 +08:00
}
2020-12-17 05:14:21 +08:00
}
2020-03-14 06:39:22 +08:00
}
Command::Show(ShowCommand::Pid) => {
match channels.pid_summaries_json() {
Ok(buf) => {
send_line(&mut socket, &buf);
}
Err(e) => {
error!("unable to serialize pid summary: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-03-20 05:00:22 +08:00
}
2020-03-20 01:34:57 +08:00
}
Command::Show(ShowCommand::Pwm) => {
match channels.pwm_summaries_json() {
Ok(buf) => {
send_line(&mut socket, &buf);
}
Err(e) => {
error!("unable to serialize pwm summary: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-09-24 04:30:04 +08:00
}
2020-03-20 01:34:57 +08:00
}
Command::Show(ShowCommand::SteinhartHart) => {
match channels.steinhart_hart_summaries_json() {
Ok(buf) => {
send_line(&mut socket, &buf);
}
Err(e) => {
error!("unable to serialize steinhart-hart summaries: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-09-17 00:40:07 +08:00
}
2020-03-20 01:34:57 +08:00
}
Command::Show(ShowCommand::PostFilter) => {
match channels.postfilter_summaries_json() {
Ok(buf) => {
send_line(&mut socket, &buf);
}
Err(e) => {
error!("unable to serialize postfilter summary: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-03-20 01:34:57 +08:00
}
}
Command::Show(ShowCommand::Ipv4) => {
let (cidr, gateway) = net::split_ipv4_config(ipv4_config.clone());
let _ = write!(socket, "{{\"addr\":\"{}\"", cidr);
gateway.map(|gateway| write!(socket, ",\"gateway\":\"{}\"", gateway));
let _ = writeln!(socket, "}}");
}
Command::PwmPid { channel } => {
channels.channel_state(channel).pid_engaged = true;
leds.g3.on();
send_line(&mut socket, b"{}");
}
Command::Pwm { channel, pin, value } => {
match pin {
PwmPin::ISet => {
channels.channel_state(channel).pid_engaged = false;
leds.g3.off();
let current = ElectricCurrent::new::<ampere>(value);
channels.set_i(channel, current);
channels.power_up(channel);
}
PwmPin::MaxV => {
let voltage = ElectricPotential::new::<volt>(value);
channels.set_max_v(channel, voltage);
}
PwmPin::MaxIPos => {
let current = ElectricCurrent::new::<ampere>(value);
channels.set_max_i_pos(channel, current);
}
PwmPin::MaxINeg => {
let current = ElectricCurrent::new::<ampere>(value);
channels.set_max_i_neg(channel, current);
}
2020-09-17 04:05:31 +08:00
}
send_line(&mut socket, b"{}");
}
Command::CenterPoint { channel, center } => {
let i_tec = channels.get_i(channel);
let state = channels.channel_state(channel);
state.center = center;
if !state.pid_engaged {
channels.set_i(channel, i_tec);
2020-09-17 04:05:31 +08:00
}
send_line(&mut socket, b"{}");
2020-09-17 04:05:31 +08:00
}
Command::Pid { channel, parameter, value } => {
let pid = &mut channels.channel_state(channel).pid;
use command_parser::PidParameter::*;
match parameter {
Target =>
pid.target = value,
KP =>
pid.parameters.kp = value as f32,
KI =>
pid.update_ki(value as f32),
KD =>
pid.parameters.kd = value as f32,
OutputMin =>
pid.parameters.output_min = value as f32,
OutputMax =>
pid.parameters.output_max = value as f32,
IntegralMin =>
pid.parameters.integral_min = value as f32,
IntegralMax =>
pid.parameters.integral_max = value as f32,
}
send_line(&mut socket, b"{}");
2020-09-24 04:30:04 +08:00
}
Command::SteinhartHart { channel, parameter, value } => {
let sh = &mut channels.channel_state(channel).sh;
use command_parser::ShParameter::*;
match parameter {
T0 => sh.t0 = ThermodynamicTemperature::new::<degree_celsius>(value),
B => sh.b = value,
R0 => sh.r0 = ElectricalResistance::new::<ohm>(value),
}
send_line(&mut socket, b"{}");
2020-03-20 01:34:57 +08:00
}
Command::PostFilter { channel, rate: None } => {
channels.adc.set_postfilter(channel as u8, None).unwrap();
send_line(&mut socket, b"{}");
2020-03-20 01:34:57 +08:00
}
Command::PostFilter { channel, rate: Some(rate) } => {
let filter = ad7172::PostFilter::closest(rate);
match filter {
Some(filter) => {
channels.adc.set_postfilter(channel as u8, Some(filter)).unwrap();
send_line(&mut socket, b"{}");
}
None => {
error!("unable to choose postfilter for rate {:.3}", rate);
send_line(&mut socket, b"{{\"error\": \"unable to choose postfilter rate\"}}");
}
2020-12-17 05:14:21 +08:00
}
2020-03-20 01:34:57 +08:00
}
Command::Load { channel } => {
for c in 0..CHANNELS {
if channel.is_none() || channel == Some(c) {
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
Ok(Some(config)) => {
config.apply(&mut channels, c);
send_line(&mut socket, b"{}");
}
Ok(None) => {
error!("flash config not found");
send_line(&mut socket, b"{{\"error\": \"flash config not found\"}}");
}
Err(e) => {
error!("unable to load config from flash: {:?}", e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-12-17 05:14:21 +08:00
}
}
}
2020-09-25 05:04:29 +08:00
}
Command::Save { channel } => {
for c in 0..CHANNELS {
if channel.is_none() || channel == Some(c) {
let config = ChannelConfig::new(&mut channels, c);
match store.write_value(CHANNEL_CONFIG_KEY[c], &config, &mut store_value_buf) {
Ok(()) => {
send_line(&mut socket, b"{}");
}
Err(e) => {
error!("unable to save channel {} config to flash: {:?}", c, e);
let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e);
}
2020-12-17 05:14:21 +08:00
}
}
}
2020-09-24 07:18:33 +08:00
}
Command::Ipv4(config) => {
let _ = store
.write_value("ipv4", &config, [0; 16])
.map_err(|e| error!("unable to save ipv4 config to flash: {:?}", e));
new_ipv4_config = Some(config);
send_line(&mut socket, b"{}");
}
Command::Reset => {
for i in 0..CHANNELS {
channels.power_down(i);
}
should_reset = true;
}
Command::Dfu => {
for i in 0..CHANNELS {
channels.power_down(i);
}
unsafe {
dfu::set_dfu_trigger();
}
should_reset = true;
}
2020-09-25 06:14:29 +08:00
}
Ok(SessionInput::Error(e)) => {
error!("session input: {:?}", e);
send_line(&mut socket, b"{ \"error\": \"invalid input\" }");
2020-12-17 05:14:21 +08:00
}
Err(_) =>
socket.close(),
}
} else if socket.can_send() {
if let Some(channel) = session.is_report_pending() {
match channels.reports_json() {
Ok(buf) => {
send_line(&mut socket, &buf[..]);
session.mark_report_sent(channel);
}
Err(e) => {
error!("unable to serialize report: {:?}", e);
2020-12-17 05:14:21 +08:00
}
2020-12-17 05:14:21 +08:00
}
}
2020-03-14 06:39:22 +08:00
}
});
} else {
// Should reset, close all TCP sockets.
let mut any_socket_alive = false;
server.for_each(|mut socket, _| {
if socket.is_active() {
socket.abort();
any_socket_alive = true;
}
});
// Must let loop run for one more cycle to poll server for RST to be sent,
// this makes sure system does not reset right after socket.abort() is called.
if !any_socket_alive {
SCB::sys_reset();
}
}
2020-03-12 07:44:15 +08:00
// Apply new IPv4 address/gateway
new_ipv4_config.take()
.map(|config| {
server.set_ipv4_config(config.clone());
ipv4_config = config;
});
2019-03-19 03:02:57 +08:00
// Update watchdog
wd.feed();
2020-09-07 03:10:10 +08:00
leds.g4.off();
cortex_m::interrupt::free(|cs| {
if !net::is_pending(cs) {
// Wait for interrupts
2020-09-11 05:17:31 +08:00
// (Ethernet, SysTick, or USB)
wfi();
}
});
2020-09-07 03:10:10 +08:00
leds.g4.on();
2019-03-15 01:13:25 +08:00
}
2019-03-19 03:02:57 +08:00
});
});
2019-03-15 03:43:35 +08:00
unreachable!()
2019-03-07 23:27:33 +08:00
}