2020-09-17 07:48:27 +08:00
|
|
|
#![cfg_attr(not(test), no_std)]
|
|
|
|
#![cfg_attr(not(test), no_main)]
|
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
|
|
|
|
2020-09-17 07:48:27 +08:00
|
|
|
#[cfg(not(any(feature = "semihosting", test)))]
|
2024-01-12 17:26:40 +08:00
|
|
|
use panic_halt 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
|
|
|
|
2020-09-28 05:58:03 +08:00
|
|
|
use log::{error, info, warn};
|
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},
|
2020-12-13 06:44:16 +08:00
|
|
|
time::{U32Ext, MegaHertz},
|
|
|
|
watchdog::IndependentWatchdog,
|
2019-03-12 01:23:52 +08:00
|
|
|
};
|
2019-03-22 00:41:33 +08:00
|
|
|
use smoltcp::{
|
|
|
|
time::Instant,
|
2020-09-28 05:58:03 +08:00
|
|
|
socket::TcpSocket,
|
2020-12-13 06:44:16 +08:00
|
|
|
wire::EthernetAddress,
|
2019-03-22 00:41:33 +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;
|
2020-03-12 06:16:48 +08:00
|
|
|
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-10-01 05:35:11 +08:00
|
|
|
use session::{Session, SessionInput};
|
2020-03-14 06:39:22 +08:00
|
|
|
mod command_parser;
|
2021-06-07 10:07:05 +08:00
|
|
|
use command_parser::Ipv4Config;
|
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;
|
2020-03-20 05:09:16 +08:00
|
|
|
mod channel_state;
|
2020-09-24 07:18:33 +08:00
|
|
|
mod config;
|
2020-12-12 08:25:07 +08:00
|
|
|
use config::ChannelConfig;
|
|
|
|
mod flash_store;
|
2021-01-13 11:59:06 +08:00
|
|
|
mod dfu;
|
2021-06-07 10:07:05 +08:00
|
|
|
mod command_handler;
|
|
|
|
use command_handler::Handler;
|
2023-03-22 17:15:49 +08:00
|
|
|
mod fan_ctrl;
|
|
|
|
use fan_ctrl::FanCtrl;
|
|
|
|
mod hw_rev;
|
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"))]
|
2024-09-16 17:32:58 +08:00
|
|
|
const WATCHDOG_INTERVAL: u32 = 2_000;
|
2020-03-12 06:17:34 +08:00
|
|
|
#[cfg(feature = "semihosting")]
|
2020-05-21 05:07:31 +08:00
|
|
|
const WATCHDOG_INTERVAL: u32 = 30_000;
|
2020-03-12 06:17:34 +08:00
|
|
|
|
2020-12-12 08:25:07 +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 {
|
2020-09-28 05:58:03 +08:00
|
|
|
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-09-28 05:58:03 +08:00
|
|
|
}
|
2020-10-01 04:10:42 +08:00
|
|
|
Ok(sent) =>
|
|
|
|
warn!("sent only {}/{} bytes", sent, data.len()),
|
|
|
|
Err(e) =>
|
|
|
|
error!("error sending line: {:?}", e),
|
2020-09-28 05:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// not success
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2019-03-19 04:41:51 +08:00
|
|
|
/// Initialization and main loop
|
2020-09-17 07:48:27 +08:00
|
|
|
#[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
|
2020-03-13 01:31:43 +08:00
|
|
|
.use_hse(HSE)
|
2019-03-19 05:04:34 +08:00
|
|
|
.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);
|
|
|
|
|
2023-03-22 17:15:49 +08:00
|
|
|
let (pins, mut leds, mut eeprom, eth_pins, usb, fan, hwrev, hw_settings) = Pins::setup(
|
|
|
|
clocks, dp.TIM1, dp.TIM3, dp.TIM8,
|
2020-09-07 03:10:10 +08:00
|
|
|
dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOD, dp.GPIOE, dp.GPIOF, dp.GPIOG,
|
2020-09-24 06:16:40 +08:00
|
|
|
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);
|
|
|
|
|
2020-12-12 08:25:07 +08:00
|
|
|
let mut store = flash_store::store(dp.FLASH);
|
|
|
|
|
2024-05-02 14:38:40 +08:00
|
|
|
let mut channels = Channels::new(pins);
|
2020-12-12 08:25:07 +08:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-22 17:15:49 +08:00
|
|
|
let mut fan_ctrl = FanCtrl::new(fan, hw_settings);
|
|
|
|
|
2020-12-13 06:44:16 +08:00
|
|
|
// 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
|
|
|
|
2020-09-24 06:16:40 +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);
|
2019-03-22 00:41:33 +08:00
|
|
|
|
2020-12-21 03:44:10 +08:00
|
|
|
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();
|
2021-01-26 17:45:14 +08:00
|
|
|
let mut should_reset = false;
|
2020-09-07 03:10:10 +08:00
|
|
|
|
2019-03-19 03:02:57 +08:00
|
|
|
loop {
|
2020-12-13 06:44:16 +08:00
|
|
|
let mut new_ipv4_config = None;
|
2020-03-21 07:07:18 +08:00
|
|
|
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
|
|
|
|
2023-08-25 10:15:51 +08:00
|
|
|
fan_ctrl.cycle(channels.current_abs_max_tec_i());
|
2023-03-22 17:15:49 +08:00
|
|
|
|
2023-08-10 16:41:08 +08:00
|
|
|
if channels.pid_engaged() {
|
|
|
|
leds.g3.on();
|
|
|
|
} else {
|
|
|
|
leds.g3.off();
|
|
|
|
}
|
|
|
|
|
2020-03-21 07:46:24 +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);
|
|
|
|
});
|
|
|
|
|
2021-01-26 17:45:14 +08:00
|
|
|
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)) {
|
2021-01-29 16:18:07 +08:00
|
|
|
// 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) => {}
|
2021-06-07 10:07:05 +08:00
|
|
|
Ok(SessionInput::Command(command)) => {
|
2023-09-20 11:11:07 +08:00
|
|
|
match Handler::handle_command(command, &mut socket, &mut channels, session, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) {
|
2021-06-07 10:07:05 +08:00
|
|
|
Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip),
|
|
|
|
Ok(Handler::Handled) => {},
|
|
|
|
Ok(Handler::CloseSocket) => socket.close(),
|
|
|
|
Ok(Handler::Reset) => should_reset = true,
|
|
|
|
Err(_) => {},
|
2021-01-13 11:59:06 +08:00
|
|
|
}
|
2020-09-25 06:14:29 +08:00
|
|
|
}
|
2021-01-26 17:45:14 +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
|
|
|
}
|
2021-01-26 17:45:14 +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
|
|
|
|
2021-01-26 17:45:14 +08:00
|
|
|
}
|
2020-12-17 05:14:21 +08:00
|
|
|
}
|
2020-09-28 05:58:03 +08:00
|
|
|
}
|
2020-03-14 06:39:22 +08:00
|
|
|
}
|
2021-01-26 17:45:14 +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
|
|
|
|
2020-12-13 06:44:16 +08:00
|
|
|
// Apply new IPv4 address/gateway
|
2020-12-21 03:43:20 +08:00
|
|
|
new_ipv4_config.take()
|
|
|
|
.map(|config| {
|
|
|
|
server.set_ipv4_config(config.clone());
|
|
|
|
ipv4_config = config;
|
|
|
|
});
|
2020-10-01 07:34:46 +08:00
|
|
|
|
2019-03-19 03:02:57 +08:00
|
|
|
// Update watchdog
|
|
|
|
wd.feed();
|
|
|
|
|
2020-09-07 03:10:10 +08:00
|
|
|
leds.g4.off();
|
2020-03-21 07:46:24 +08:00
|
|
|
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)
|
2020-03-21 07:46:24 +08:00
|
|
|
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
|
|
|
|
2019-03-19 04:17:27 +08:00
|
|
|
unreachable!()
|
2019-03-07 23:27:33 +08:00
|
|
|
}
|