thermostat/src/main.rs

391 lines
20 KiB
Rust
Raw Normal View History

2019-03-07 23:27:33 +08:00
#![no_std]
#![no_main]
2020-03-12 06:17:17 +08:00
// TODO: #![deny(warnings, unused)]
2019-03-07 23:27:33 +08:00
2019-03-15 02:58:41 +08:00
#[cfg(not(feature = "semihosting"))]
2019-04-27 21:23:50 +08:00
use panic_abort as _;
2019-03-15 02:58:41 +08:00
#[cfg(feature = "semihosting")]
2019-04-27 21:23:50 +08:00
use panic_semihosting as _;
2019-03-07 23:27:33 +08:00
2019-04-27 21:23:50 +08:00
use log::{info, warn};
2019-03-15 02:58:41 +08:00
2020-03-20 01:34:57 +08:00
use core::ops::DerefMut;
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-03-20 01:34:57 +08:00
hal::{
self,
watchdog::{WatchdogEnable, Watchdog},
},
2019-03-12 01:23:52 +08:00
rcc::RccExt,
watchdog::IndependentWatchdog,
time::{U32Ext, MegaHertz},
2019-03-12 01:23:52 +08:00
stm32::{CorePeripherals, Peripherals},
};
use smoltcp::{
time::Instant,
wire::EthernetAddress,
};
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-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;
2020-05-13 06:04:55 +08:00
use session::{Session, SessionOutput};
2020-03-14 06:39:22 +08:00
mod command_parser;
2020-03-20 01:34:57 +08:00
use command_parser::{Command, ShowCommand, PwmPin};
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-03-14 06:39:22 +08:00
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 = 100;
#[cfg(feature = "semihosting")]
const WATCHDOG_INTERVAL: u32 = 10_000;
#[cfg(not(feature = "generate-hwaddr"))]
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
2020-03-14 06:39:22 +08:00
const TCP_PORT: u16 = 23;
2019-03-15 02:58:41 +08:00
2019-03-19 04:41:51 +08:00
/// Initialization and main loop
2019-03-07 23:27:33 +08:00
#[entry]
fn main() -> ! {
2019-03-15 02:58:41 +08:00
init_log();
2020-03-14 06:39:22 +08:00
info!("tecpak");
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();
stm32_eth::setup(&dp.RCC, &dp.SYSCFG);
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-03-13 00:26:14 +08:00
let pins = Pins::setup(
clocks, dp.TIM1, dp.TIM3,
2020-03-13 02:24:57 +08:00
dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOE, dp.GPIOF, dp.GPIOG,
2020-04-11 03:05:05 +08:00
dp.SPI2, dp.SPI4, dp.SPI5,
dp.ADC1, dp.ADC2,
2020-03-13 00:26:14 +08:00
);
2020-05-13 05:16:57 +08:00
let mut channels = Channels::new(pins);
2019-03-15 01:13:25 +08:00
timer::setup(cp.SYST, clocks);
#[cfg(not(feature = "generate-hwaddr"))]
let hwaddr = EthernetAddress(NET_HWADDR);
#[cfg(feature = "generate-hwaddr")]
let hwaddr = {
let uid = stm32f4xx_hal::signature::Uid::get();
EthernetAddress(hash2hwaddr::generate_hwaddr(uid))
};
info!("Net hwaddr: {}", hwaddr);
2020-03-09 07:07:56 +08:00
net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
2020-03-14 06:39:22 +08:00
Server::<Session>::run(iface, |server| {
2019-03-19 03:02:57 +08:00
loop {
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);
});
2020-03-14 06:39:22 +08:00
// TCP protocol handling
server.for_each(|mut socket, session| {
2020-03-21 07:37:24 +08:00
if ! socket.is_active() {
2020-03-14 06:39:22 +08:00
let _ = socket.listen(TCP_PORT);
2020-03-21 06:20:38 +08:00
session.reset();
2020-03-21 07:11:23 +08:00
} else if socket.can_send() && socket.can_recv() && socket.send_capacity() - socket.send_queue() > 1024 {
2020-03-14 06:39:22 +08:00
match socket.recv(|buf| session.feed(buf)) {
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) => {
2020-05-13 06:04:55 +08:00
for channel in 0..CHANNELS {
2020-05-13 06:15:29 +08:00
if let Some(adc_data) = channels.channel_state(channel).adc_data {
let ref_adc_data = channels.read_ref_adc(channel);
let state = channels.channel_state(channel);
2020-03-14 06:39:22 +08:00
let _ = writeln!(
2020-05-13 06:15:29 +08:00
socket, "t={} raw{}=0x{:06X} ref_adc={}",
state.adc_time, channel, adc_data,
ref_adc_data
2020-03-14 06:39:22 +08:00
);
}
}
}
Command::Show(ShowCommand::Pid) => {
2020-05-13 06:04:55 +08:00
for channel in 0..CHANNELS {
let state = channels.channel_state(channel);
2020-03-20 01:34:57 +08:00
let _ = writeln!(socket, "PID settings for channel {}", channel);
let pid = &state.pid;
2020-03-20 05:00:22 +08:00
let _ = writeln!(socket, "- target={:.4}", pid.target);
macro_rules! show_pid_parameter {
2020-03-20 01:34:57 +08:00
($p: tt) => {
2020-03-20 05:00:22 +08:00
let _ = writeln!(
socket, "- {}={:.4}",
stringify!($p), pid.parameters.$p
);
2020-03-20 01:34:57 +08:00
};
}
2020-03-20 05:00:22 +08:00
show_pid_parameter!(kp);
show_pid_parameter!(ki);
show_pid_parameter!(kd);
show_pid_parameter!(integral_min);
show_pid_parameter!(integral_max);
2020-03-24 06:02:50 +08:00
show_pid_parameter!(output_min);
show_pid_parameter!(output_max);
2020-03-20 05:00:22 +08:00
if let Some(last_output) = pid.last_output {
2020-03-24 06:02:50 +08:00
let _ = writeln!(socket, "- last_output={:.4}", last_output);
2020-03-20 05:00:22 +08:00
}
2020-03-20 01:34:57 +08:00
let _ = writeln!(socket, "");
}
2020-03-14 06:39:22 +08:00
}
Command::Show(ShowCommand::Pwm) => {
2020-05-13 06:04:55 +08:00
for channel in 0..CHANNELS {
let state = channels.channel_state(channel);
2020-03-20 01:34:57 +08:00
let _ = writeln!(
socket, "channel {}: PID={}",
channel,
2020-05-13 06:04:55 +08:00
if state.pid_engaged { "engaged" } else { "disengaged" }
2020-03-20 01:34:57 +08:00
);
let _ = writeln!(socket, "- i_set={}/{}", state.dac_value, ad5680::MAX_VALUE);
fn show_pwm_channel<S, P>(mut socket: S, name: &str, pin: &P)
where
S: core::fmt::Write,
P: hal::PwmPin<Duty=u16>,
{
let _ = writeln!(
socket,
"- {}={}/{}",
name, pin.get_duty(), pin.get_max_duty()
);
}
match channel {
0 => {
2020-05-13 05:16:57 +08:00
show_pwm_channel(socket.deref_mut(), "max_v", &channels.pwm.max_v0);
show_pwm_channel(socket.deref_mut(), "max_i_pos", &channels.pwm.max_i_pos0);
show_pwm_channel(socket.deref_mut(), "max_i_neg", &channels.pwm.max_i_neg0);
2020-03-20 01:34:57 +08:00
}
1 => {
2020-05-13 05:16:57 +08:00
show_pwm_channel(socket.deref_mut(), "max_v", &channels.pwm.max_v1);
show_pwm_channel(socket.deref_mut(), "max_i_pos", &channels.pwm.max_i_pos1);
show_pwm_channel(socket.deref_mut(), "max_i_neg", &channels.pwm.max_i_neg1);
2020-03-20 01:34:57 +08:00
}
_ => unreachable!(),
}
let _ = writeln!(socket, "");
}
2020-03-14 06:39:22 +08:00
}
Command::Show(ShowCommand::SteinhartHart) => {
2020-05-13 06:04:55 +08:00
for channel in 0..CHANNELS {
let state = channels.channel_state(channel);
2020-03-20 01:34:57 +08:00
let _ = writeln!(
socket, "channel {}: Steinhart-Hart equation parameters",
channel,
);
let _ = writeln!(socket, "- t0={}", state.sh.t0);
2020-03-20 05:56:14 +08:00
let _ = writeln!(socket, "- b={}", state.sh.b);
2020-03-20 01:34:57 +08:00
let _ = writeln!(socket, "- r0={}", state.sh.r0);
let _ = writeln!(socket, "");
}
2020-03-14 06:39:22 +08:00
}
Command::Show(ShowCommand::PostFilter) => {
2020-05-13 06:04:55 +08:00
for channel in 0..CHANNELS {
2020-05-13 05:16:57 +08:00
match channels.adc.get_postfilter(channel as u8).unwrap() {
2020-03-20 01:34:57 +08:00
Some(filter) => {
let _ = writeln!(
socket, "channel {}: postfilter={:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(
socket, "channel {}: no postfilter",
channel
);
}
}
}
}
Command::PwmPid { channel } => {
2020-05-13 06:04:55 +08:00
channels.channel_state(channel).pid_engaged = true;
2020-03-20 01:34:57 +08:00
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel
);
}
Command::Pwm { channel, pin: PwmPin::ISet, duty } if duty <= ad5680::MAX_VALUE => {
2020-05-13 06:04:55 +08:00
channels.channel_state(channel).pid_engaged = false;
2020-05-14 03:02:26 +08:00
channels.set_dac(channel, duty);
2020-03-20 01:34:57 +08:00
let _ = writeln!(
socket, "channel {}: PWM duty cycle manually set to {}/{}",
channel, duty, ad5680::MAX_VALUE
);
2020-03-14 06:39:22 +08:00
}
2020-03-20 01:34:57 +08:00
Command::Pwm { pin: PwmPin::ISet, duty, .. } if duty > ad5680::MAX_VALUE => {
let _ = writeln!(
socket, "error: PWM duty range must not exceed {}",
ad5680::MAX_VALUE
);
2020-03-14 06:39:22 +08:00
}
2020-03-20 01:34:57 +08:00
Command::Pwm { channel, pin, duty } if duty <= 0xFFFF => {
let duty = duty as u16;
fn set_pwm_channel<P: hal::PwmPin<Duty=u16>>(pin: &mut P, duty: u16) -> u16 {
pin.set_duty(duty);
pin.get_max_duty()
}
let max = match (channel, pin) {
(_, PwmPin::ISet) =>
// Handled above
unreachable!(),
(0, PwmPin::MaxIPos) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_i_pos0, duty),
2020-03-20 01:34:57 +08:00
(0, PwmPin::MaxINeg) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_i_neg0, duty),
2020-03-20 01:34:57 +08:00
(0, PwmPin::MaxV) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_v0, duty),
2020-03-20 01:34:57 +08:00
(1, PwmPin::MaxIPos) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_i_pos1, duty),
2020-03-20 01:34:57 +08:00
(1, PwmPin::MaxINeg) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_i_neg1, duty),
2020-03-20 01:34:57 +08:00
(1, PwmPin::MaxV) =>
2020-05-13 05:16:57 +08:00
set_pwm_channel(&mut channels.pwm.max_v1, duty),
2020-03-20 01:34:57 +08:00
_ =>
unreachable!(),
};
let _ = writeln!(
socket, "channel {}: PWM {} reconfigured to {}/{}",
channel, pin.name(), duty, max
);
2020-03-14 06:39:22 +08:00
}
2020-03-20 01:34:57 +08:00
Command::Pwm { duty, .. } if duty > 0xFFFF => {
let _ = writeln!(socket, "error: PWM duty range must fit 16 bits");
2020-03-14 06:39:22 +08:00
}
Command::Pid { channel, parameter, value } => {
2020-05-13 06:04:55 +08:00
let pid = &mut channels.channel_state(channel).pid;
2020-03-20 01:34:57 +08:00
use command_parser::PidParameter::*;
match parameter {
Target =>
2020-03-20 05:01:16 +08:00
pid.target = value,
2020-03-20 01:34:57 +08:00
KP =>
2020-03-20 05:01:16 +08:00
pid.parameters.kp = value,
2020-03-20 01:34:57 +08:00
KI =>
2020-03-20 05:01:16 +08:00
pid.parameters.ki = value,
2020-03-20 01:34:57 +08:00
KD =>
2020-03-20 05:01:16 +08:00
pid.parameters.kd = value,
2020-03-20 01:34:57 +08:00
OutputMin =>
2020-03-20 05:01:16 +08:00
pid.parameters.output_min = value,
2020-03-20 01:34:57 +08:00
OutputMax =>
2020-03-20 05:01:16 +08:00
pid.parameters.output_max = value,
2020-03-20 01:34:57 +08:00
IntegralMin =>
2020-03-20 05:01:16 +08:00
pid.parameters.integral_min = value,
2020-03-20 01:34:57 +08:00
IntegralMax =>
2020-03-20 05:01:16 +08:00
pid.parameters.integral_max = value,
2020-03-20 01:34:57 +08:00
}
2020-03-20 05:01:16 +08:00
// TODO: really reset PID state
// after each parameter change?
2020-03-20 01:34:57 +08:00
pid.reset();
let _ = writeln!(socket, "PID parameter updated");
2020-03-14 06:39:22 +08:00
}
Command::SteinhartHart { channel, parameter, value } => {
2020-05-13 06:04:55 +08:00
let sh = &mut channels.channel_state(channel).sh;
2020-03-20 01:34:57 +08:00
use command_parser::ShParameter::*;
match parameter {
T0 => sh.t0 = value,
2020-03-20 05:56:14 +08:00
B => sh.b = value,
2020-03-20 01:34:57 +08:00
R0 => sh.r0 = value,
}
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
2020-03-14 06:39:22 +08:00
}
Command::PostFilter { channel, rate } => {
2020-03-20 01:34:57 +08:00
let filter = ad7172::PostFilter::closest(rate);
match filter {
Some(filter) => {
2020-05-13 05:16:57 +08:00
channels.adc.set_postfilter(channel as u8, Some(filter)).unwrap();
2020-03-20 01:34:57 +08:00
let _ = writeln!(
socket, "channel {}: postfilter set to {:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(socket, "Unable to choose postfilter");
}
}
}
cmd => {
let _ = writeln!(socket, "Not yet implemented: {:?}", cmd);
2020-03-14 06:39:22 +08:00
}
}
Ok(SessionOutput::Error(e)) => {
let _ = writeln!(socket, "Command error: {:?}", e);
}
Err(_) =>
socket.close(),
}
} else if socket.can_send() && socket.send_capacity() - socket.send_queue() > 256 {
while let Some(channel) = session.is_report_pending() {
2020-05-13 06:04:55 +08:00
let state = &mut channels.channel_state(usize::from(channel));
2020-03-14 06:39:22 +08:00
let _ = writeln!(
2020-03-21 07:11:03 +08:00
socket, "t={} raw{}=0x{:06X}",
2020-03-14 06:39:22 +08:00
state.adc_time, channel, state.adc_data.unwrap_or(0)
).map(|_| {
session.mark_report_sent(channel);
});
}
}
});
2020-03-12 07:44:15 +08:00
2019-03-19 03:02:57 +08:00
// Update watchdog
wd.feed();
cortex_m::interrupt::free(|cs| {
if !net::is_pending(cs) {
// Wait for interrupts
// (Ethernet or SysTick)
wfi();
}
});
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
}