forked from M-Labs/thermostat
rewrite config for sfkv-based flash_store
This commit is contained in:
parent
088bd6eb76
commit
383ebcd8e4
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -391,6 +391,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sfkv"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@ -436,7 +445,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f4xx-hal"
|
name = "stm32f4xx-hal"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
source = "git+https://github.com/stm32-rs/stm32f4xx-hal.git#e80925770d2fe72f0f01a7b46147f4e31d512689"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 0.2.5",
|
"bare-metal 0.2.5",
|
||||||
"cast",
|
"cast",
|
||||||
@ -444,6 +452,7 @@ dependencies = [
|
|||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"embedded-dma",
|
"embedded-dma",
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
|
"log",
|
||||||
"nb 0.1.3",
|
"nb 0.1.3",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"stm32f4",
|
"stm32f4",
|
||||||
@ -491,9 +500,9 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"panic-abort",
|
"panic-abort",
|
||||||
"panic-semihosting",
|
"panic-semihosting",
|
||||||
"postcard",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde-json-core",
|
"serde-json-core",
|
||||||
|
"sfkv",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
"stm32-eth",
|
"stm32-eth",
|
||||||
"stm32f4xx-hal",
|
"stm32f4xx-hal",
|
||||||
|
@ -34,9 +34,9 @@ nb = "1"
|
|||||||
uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] }
|
uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] }
|
||||||
eeprom24x = "0.3"
|
eeprom24x = "0.3"
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
postcard = "0.5"
|
|
||||||
heapless = "0.5"
|
heapless = "0.5"
|
||||||
serde-json-core = "0.1"
|
serde-json-core = "0.1"
|
||||||
|
sfkv = "0.1"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" }
|
stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" }
|
||||||
|
62
README.md
62
README.md
@ -91,37 +91,37 @@ The scope of this setting is per TCP session.
|
|||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
| Syntax | Function |
|
| Syntax | Function |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `report` | Show current input |
|
| `report` | Show current input |
|
||||||
| `report mode` | Show current report mode |
|
| `report mode` | Show current report mode |
|
||||||
| `report mode <off/on>` | Set report mode |
|
| `report mode <off/on>` | Set report mode |
|
||||||
| `pwm` | Show current PWM settings |
|
| `pwm` | Show current PWM settings |
|
||||||
| `pwm <0/1> max_i_pos <amp>` | Set PWM duty cycle for **max_i_pos** to *ampere* |
|
| `pwm <0/1> max_i_pos <amp>` | Set PWM duty cycle for **max_i_pos** to *ampere* |
|
||||||
| `pwm <0/1> max_i_neg <amp>` | Set PWM duty cycle for **max_i_neg** to *ampere* |
|
| `pwm <0/1> max_i_neg <amp>` | Set PWM duty cycle for **max_i_neg** to *ampere* |
|
||||||
| `pwm <0/1> max_v <volts>` | Set PWM duty cycle for **max_v** to *volt* |
|
| `pwm <0/1> max_v <volts>` | Set PWM duty cycle for **max_v** to *volt* |
|
||||||
| `pwm <0/1> i_set <amp>` | Disengage PID, set **i_set** DAC to *ampere* |
|
| `pwm <0/1> i_set <amp>` | Disengage PID, set **i_set** DAC to *ampere* |
|
||||||
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
|
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
|
||||||
| `center <0/1> <volts>` | Set the MAX1968 0A-centerpoint to *volts* |
|
| `center <0/1> <volts>` | Set the MAX1968 0A-centerpoint to *volts* |
|
||||||
| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
|
| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
|
||||||
| `pid` | Show PID configuration |
|
| `pid` | Show PID configuration |
|
||||||
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
|
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
|
||||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||||
| `pid <0/1> ki <value>` | Set integral gain (unit: 10 Hz) |
|
| `pid <0/1> ki <value>` | Set integral gain (unit: 10 Hz) |
|
||||||
| `pid <0/1> kd <value>` | Set differential gain (unit: 0.1 seconds) |
|
| `pid <0/1> kd <value>` | Set differential gain (unit: 0.1 seconds) |
|
||||||
| `pid <0/1> output_min <amp>` | Set mininum output |
|
| `pid <0/1> output_min <amp>` | Set mininum output |
|
||||||
| `pid <0/1> output_max <amp>` | Set maximum output |
|
| `pid <0/1> output_max <amp>` | Set maximum output |
|
||||||
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
||||||
| `pid <0/1> integral_max <value>` | Set integral upper bound |
|
| `pid <0/1> integral_max <value>` | Set integral upper bound |
|
||||||
| `s-h` | Show Steinhart-Hart equation parameters |
|
| `s-h` | Show Steinhart-Hart equation parameters |
|
||||||
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
||||||
| `postfilter` | Show postfilter settings |
|
| `postfilter` | Show postfilter settings |
|
||||||
| `postfilter <0/1> off` | Disable postfilter |
|
| `postfilter <0/1> off` | Disable postfilter |
|
||||||
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
|
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
|
||||||
| `load` | Restore configuration from EEPROM |
|
| `load [0/1]` | Restore configuration for channel all/0/1 from flash |
|
||||||
| `save` | Save configuration to EEPROM |
|
| `save [0/1]` | Save configuration for channel all/0/1 to flash |
|
||||||
| `reset` | Reset the device |
|
| `reset` | Reset the device |
|
||||||
| `ipv4 <X.X.X.X>` | Configure IPv4 address |
|
| `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
|
||||||
|
|
||||||
|
|
||||||
## USB
|
## USB
|
||||||
|
@ -132,8 +132,12 @@ pub enum CenterPoint {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Quit,
|
Quit,
|
||||||
Load,
|
Load {
|
||||||
Save,
|
channel: Option<usize>,
|
||||||
|
},
|
||||||
|
Save {
|
||||||
|
channel: Option<usize>,
|
||||||
|
},
|
||||||
Reset,
|
Reset,
|
||||||
Ipv4([u8; 4]),
|
Ipv4([u8; 4]),
|
||||||
Show(ShowCommand),
|
Show(ShowCommand),
|
||||||
@ -437,6 +441,38 @@ fn postfilter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
|
let (input, _) = tag("load")(input)?;
|
||||||
|
let (input, channel) = alt((
|
||||||
|
|input| {
|
||||||
|
let (input, _) = whitespace(input)?;
|
||||||
|
let (input, channel) = channel(input)?;
|
||||||
|
let (input, _) = end(input)?;
|
||||||
|
Ok((input, Some(channel)))
|
||||||
|
},
|
||||||
|
value(None, end)
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
let result = Ok(Command::Load { channel });
|
||||||
|
Ok((input, result))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
|
let (input, _) = tag("save")(input)?;
|
||||||
|
let (input, channel) = alt((
|
||||||
|
|input| {
|
||||||
|
let (input, _) = whitespace(input)?;
|
||||||
|
let (input, channel) = channel(input)?;
|
||||||
|
let (input, _) = end(input)?;
|
||||||
|
Ok((input, Some(channel)))
|
||||||
|
},
|
||||||
|
value(None, end)
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
let result = Ok(Command::Save { channel });
|
||||||
|
Ok((input, result))
|
||||||
|
}
|
||||||
|
|
||||||
fn ipv4(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
fn ipv4(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
let (input, _) = tag("ipv4")(input)?;
|
let (input, _) = tag("ipv4")(input)?;
|
||||||
let (input, _) = whitespace(input)?;
|
let (input, _) = whitespace(input)?;
|
||||||
@ -457,8 +493,8 @@ fn ipv4(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
|
|
||||||
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
alt((value(Ok(Command::Quit), tag("quit")),
|
alt((value(Ok(Command::Quit), tag("quit")),
|
||||||
value(Ok(Command::Load), tag("load")),
|
load,
|
||||||
value(Ok(Command::Save), tag("save")),
|
save,
|
||||||
value(Ok(Command::Reset), tag("reset")),
|
value(Ok(Command::Reset), tag("reset")),
|
||||||
ipv4,
|
ipv4,
|
||||||
map(report, Ok),
|
map(report, Ok),
|
||||||
@ -496,13 +532,25 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_load() {
|
fn parse_load() {
|
||||||
let command = Command::parse(b"load");
|
let command = Command::parse(b"load");
|
||||||
assert_eq!(command, Ok(Command::Load));
|
assert_eq!(command, Ok(Command::Load { channel: None }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_load_channel() {
|
||||||
|
let command = Command::parse(b"load 0");
|
||||||
|
assert_eq!(command, Ok(Command::Load { channel: Some(0) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_save() {
|
fn parse_save() {
|
||||||
let command = Command::parse(b"save");
|
let command = Command::parse(b"save");
|
||||||
assert_eq!(command, Ok(Command::Save));
|
assert_eq!(command, Ok(Command::Save { channel: None }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_save_channel() {
|
||||||
|
let command = Command::parse(b"save 0");
|
||||||
|
assert_eq!(command, Ok(Command::Save { channel: Some(0) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
194
src/config.rs
194
src/config.rs
@ -1,102 +1,24 @@
|
|||||||
use postcard::{from_bytes, to_slice};
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use smoltcp::wire::Ipv4Address;
|
use smoltcp::wire::Ipv4Address;
|
||||||
use stm32f4xx_hal::i2c;
|
|
||||||
use uom::si::{
|
use uom::si::{
|
||||||
electric_potential::volt,
|
electric_potential::volt,
|
||||||
electric_current::ampere,
|
electric_current::ampere,
|
||||||
electrical_resistance::ohm,
|
f64::{ElectricCurrent, ElectricPotential},
|
||||||
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance, ThermodynamicTemperature},
|
|
||||||
thermodynamic_temperature::degree_celsius,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ad7172::PostFilter,
|
ad7172::PostFilter,
|
||||||
channels::{CHANNELS, Channels},
|
channels::Channels,
|
||||||
command_parser::CenterPoint,
|
command_parser::CenterPoint,
|
||||||
EEPROM_SIZE, EEPROM_PAGE_SIZE,
|
|
||||||
pid,
|
pid,
|
||||||
pins,
|
|
||||||
steinhart_hart,
|
steinhart_hart,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Eeprom(eeprom24x::Error<i2c::Error>),
|
|
||||||
Encode(postcard::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<eeprom24x::Error<i2c::Error>> for Error {
|
|
||||||
fn from(e: eeprom24x::Error<i2c::Error>) -> Self {
|
|
||||||
Error::Eeprom(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<postcard::Error> for Error {
|
|
||||||
fn from(e: postcard::Error) -> Self {
|
|
||||||
Error::Encode(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Just for encoding/decoding, actual state resides in ChannelState
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
channels: [ChannelConfig; CHANNELS],
|
|
||||||
pub ipv4_address: [u8; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new(channels: &mut Channels, ipv4_address: Ipv4Address) -> Self {
|
|
||||||
Config {
|
|
||||||
channels: [
|
|
||||||
ChannelConfig::new(channels, 0),
|
|
||||||
ChannelConfig::new(channels, 1),
|
|
||||||
],
|
|
||||||
ipv4_address: ipv4_address.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// apply loaded config to system
|
|
||||||
pub fn apply(&self, channels: &mut Channels) {
|
|
||||||
for i in 0..CHANNELS {
|
|
||||||
self.channels[i].apply(channels, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(eeprom: &mut pins::Eeprom) -> Result<Self, Error> {
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
eeprom.read_data(0, &mut buffer)?;
|
|
||||||
log::info!("load: {:?}", buffer);
|
|
||||||
let config = from_bytes(&mut buffer)?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&self, eeprom: &mut pins::Eeprom) -> Result<(), Error> {
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
let config_buffer = to_slice(self, &mut buffer)?;
|
|
||||||
log::info!("save: {:?}", config_buffer);
|
|
||||||
|
|
||||||
let mut addr = 0;
|
|
||||||
for chunk in config_buffer.chunks(EEPROM_PAGE_SIZE) {
|
|
||||||
'write_retry: loop {
|
|
||||||
match eeprom.write_page(addr, chunk) {
|
|
||||||
Ok(()) => break 'write_retry,
|
|
||||||
Err(eeprom24x::Error::I2C(i2c::Error::NACK)) => {},
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr += chunk.len() as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ChannelConfig {
|
pub struct ChannelConfig {
|
||||||
center: CenterPoint,
|
center: CenterPoint,
|
||||||
pid: pid::Parameters,
|
pid: pid::Parameters,
|
||||||
pid_target: f32,
|
pid_target: f32,
|
||||||
sh: SteinhartHartConfig,
|
sh: steinhart_hart::Parameters,
|
||||||
pwm: PwmLimits,
|
pwm: PwmLimits,
|
||||||
/// uses variant `PostFilter::Invalid` instead of `None` to save space
|
/// uses variant `PostFilter::Invalid` instead of `None` to save space
|
||||||
adc_postfilter: PostFilter,
|
adc_postfilter: PostFilter,
|
||||||
@ -115,7 +37,7 @@ impl ChannelConfig {
|
|||||||
center: state.center.clone(),
|
center: state.center.clone(),
|
||||||
pid: state.pid.parameters.clone(),
|
pid: state.pid.parameters.clone(),
|
||||||
pid_target: state.pid.target as f32,
|
pid_target: state.pid.target as f32,
|
||||||
sh: (&state.sh).into(),
|
sh: state.sh.clone(),
|
||||||
pwm,
|
pwm,
|
||||||
adc_postfilter,
|
adc_postfilter,
|
||||||
}
|
}
|
||||||
@ -126,7 +48,7 @@ impl ChannelConfig {
|
|||||||
state.center = self.center.clone();
|
state.center = self.center.clone();
|
||||||
state.pid.parameters = self.pid.clone();
|
state.pid.parameters = self.pid.clone();
|
||||||
state.pid.target = self.pid_target.into();
|
state.pid.target = self.pid_target.into();
|
||||||
state.sh = (&self.sh).into();
|
state.sh = self.sh.clone();
|
||||||
|
|
||||||
self.pwm.apply(channels, channel);
|
self.pwm.apply(channels, channel);
|
||||||
|
|
||||||
@ -138,38 +60,11 @@ impl ChannelConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
struct SteinhartHartConfig {
|
|
||||||
t0: f32,
|
|
||||||
r0: f32,
|
|
||||||
b: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&steinhart_hart::Parameters> for SteinhartHartConfig {
|
|
||||||
fn from(sh: &steinhart_hart::Parameters) -> Self {
|
|
||||||
SteinhartHartConfig {
|
|
||||||
t0: sh.t0.get::<degree_celsius>() as f32,
|
|
||||||
r0: sh.r0.get::<ohm>() as f32,
|
|
||||||
b: sh.b as f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<steinhart_hart::Parameters> for &SteinhartHartConfig {
|
|
||||||
fn into(self) -> steinhart_hart::Parameters {
|
|
||||||
steinhart_hart::Parameters {
|
|
||||||
t0: ThermodynamicTemperature::new::<degree_celsius>(self.t0.into()),
|
|
||||||
r0: ElectricalResistance::new::<ohm>(self.r0.into()),
|
|
||||||
b: self.b.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
struct PwmLimits {
|
struct PwmLimits {
|
||||||
max_v: f32,
|
max_v: f64,
|
||||||
max_i_pos: f32,
|
max_i_pos: f64,
|
||||||
max_i_neg: f32,
|
max_i_neg: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PwmLimits {
|
impl PwmLimits {
|
||||||
@ -178,76 +73,15 @@ impl PwmLimits {
|
|||||||
let (max_i_pos, _) = channels.get_max_i_pos(channel);
|
let (max_i_pos, _) = channels.get_max_i_pos(channel);
|
||||||
let (max_i_neg, _) = channels.get_max_i_neg(channel);
|
let (max_i_neg, _) = channels.get_max_i_neg(channel);
|
||||||
PwmLimits {
|
PwmLimits {
|
||||||
max_v: max_v.get::<volt>() as f32,
|
max_v: max_v.get::<volt>(),
|
||||||
max_i_pos: max_i_pos.get::<ampere>() as f32,
|
max_i_pos: max_i_pos.get::<ampere>(),
|
||||||
max_i_neg: max_i_neg.get::<ampere>() as f32,
|
max_i_neg: max_i_neg.get::<ampere>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply(&self, channels: &mut Channels, channel: usize) {
|
pub fn apply(&self, channels: &mut Channels, channel: usize) {
|
||||||
channels.set_max_v(channel, ElectricPotential::new::<volt>(self.max_v.into()));
|
channels.set_max_v(channel, ElectricPotential::new::<volt>(self.max_v));
|
||||||
channels.set_max_i_pos(channel, ElectricCurrent::new::<ampere>(self.max_i_pos.into()));
|
channels.set_max_i_pos(channel, ElectricCurrent::new::<ampere>(self.max_i_pos));
|
||||||
channels.set_max_i_neg(channel, ElectricCurrent::new::<ampere>(self.max_i_neg.into()));
|
channels.set_max_i_neg(channel, ElectricCurrent::new::<ampere>(self.max_i_neg));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::DEFAULT_IPV4_ADDRESS;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fit_eeprom() {
|
|
||||||
let channel_config = ChannelConfig {
|
|
||||||
center: CenterPoint::Override(1.5),
|
|
||||||
pid: pid::Parameters::default(),
|
|
||||||
pid_target: 93.7,
|
|
||||||
sh: (&steinhart_hart::Parameters::default()).into(),
|
|
||||||
pwm: PwmLimits {
|
|
||||||
max_v: 1.65,
|
|
||||||
max_i_pos: 2.1,
|
|
||||||
max_i_neg: 2.25,
|
|
||||||
},
|
|
||||||
adc_postfilter: PostFilter::F21SPS,
|
|
||||||
};
|
|
||||||
let config = Config {
|
|
||||||
channels: [
|
|
||||||
channel_config.clone(),
|
|
||||||
channel_config.clone(),
|
|
||||||
],
|
|
||||||
ipv4_address: DEFAULT_IPV4_ADDRESS.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
let buffer = to_slice(&config, &mut buffer).unwrap();
|
|
||||||
assert!(buffer.len() <= EEPROM_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encode_decode() {
|
|
||||||
let channel_config = ChannelConfig {
|
|
||||||
center: CenterPoint::Override(1.5),
|
|
||||||
pid: pid::Parameters::default(),
|
|
||||||
pid_target: 93.7,
|
|
||||||
sh: (&steinhart_hart::Parameters::default()).into(),
|
|
||||||
pwm: PwmLimits {
|
|
||||||
max_v: 1.65,
|
|
||||||
max_i_pos: 2.1,
|
|
||||||
max_i_neg: 2.25,
|
|
||||||
},
|
|
||||||
adc_postfilter: PostFilter::F21SPS,
|
|
||||||
};
|
|
||||||
let config = Config {
|
|
||||||
channels: [
|
|
||||||
channel_config.clone(),
|
|
||||||
channel_config.clone(),
|
|
||||||
],
|
|
||||||
ipv4_address: DEFAULT_IPV4_ADDRESS.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
to_slice(&config, &mut buffer).unwrap();
|
|
||||||
let decoded: Config = from_bytes(&buffer).unwrap();
|
|
||||||
assert_eq!(decoded, config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/flash_store.rs
Normal file
45
src/flash_store.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use stm32f4xx_hal::{
|
||||||
|
flash::{Error, FlashExt},
|
||||||
|
stm32::FLASH,
|
||||||
|
};
|
||||||
|
use sfkv::{Store, StoreBackend};
|
||||||
|
|
||||||
|
/// 16 KiB
|
||||||
|
pub const FLASH_SECTOR_SIZE: usize = 0x4000;
|
||||||
|
pub const FLASH_SECTOR: u8 = 12;
|
||||||
|
pub const FLASH_SECTOR_OFFSET: usize = 0x10_0000;
|
||||||
|
static mut BACKUP_SPACE: [u8; FLASH_SECTOR_SIZE] = [0; FLASH_SECTOR_SIZE];
|
||||||
|
|
||||||
|
pub struct FlashBackend {
|
||||||
|
flash: FLASH,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoreBackend for FlashBackend {
|
||||||
|
type Data = [u8];
|
||||||
|
|
||||||
|
fn data(&self) -> &Self::Data {
|
||||||
|
self.flash.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error = Error;
|
||||||
|
fn erase(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.flash.unlocked().erase(FLASH_SECTOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.flash.unlocked()
|
||||||
|
.program(FLASH_SECTOR_OFFSET + offset, payload.iter().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn backup_space(&self) -> &'static mut [u8] {
|
||||||
|
unsafe { &mut BACKUP_SPACE }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FlashStore = Store<FlashBackend>;
|
||||||
|
|
||||||
|
pub fn store(flash: FLASH) -> FlashStore {
|
||||||
|
let backend = FlashBackend { flash };
|
||||||
|
FlashStore::new(backend)
|
||||||
|
}
|
63
src/main.rs
63
src/main.rs
@ -64,8 +64,8 @@ use channels::{CHANNELS, Channels};
|
|||||||
mod channel;
|
mod channel;
|
||||||
mod channel_state;
|
mod channel_state;
|
||||||
mod config;
|
mod config;
|
||||||
use config::Config;
|
use config::ChannelConfig;
|
||||||
|
mod flash_store;
|
||||||
|
|
||||||
const HSE: MegaHertz = MegaHertz(8);
|
const HSE: MegaHertz = MegaHertz(8);
|
||||||
#[cfg(not(feature = "semihosting"))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
@ -73,8 +73,7 @@ const WATCHDOG_INTERVAL: u32 = 1_000;
|
|||||||
#[cfg(feature = "semihosting")]
|
#[cfg(feature = "semihosting")]
|
||||||
const WATCHDOG_INTERVAL: u32 = 30_000;
|
const WATCHDOG_INTERVAL: u32 = 30_000;
|
||||||
|
|
||||||
pub const EEPROM_PAGE_SIZE: usize = 8;
|
const CHANNEL_CONFIG_KEY: [&str; 2] = ["ch0", "ch1"];
|
||||||
pub const EEPROM_SIZE: usize = 128;
|
|
||||||
|
|
||||||
pub const DEFAULT_IPV4_ADDRESS: Ipv4Address = Ipv4Address([192, 168, 1, 26]);
|
pub const DEFAULT_IPV4_ADDRESS: Ipv4Address = Ipv4Address([192, 168, 1, 26]);
|
||||||
const TCP_PORT: u16 = 23;
|
const TCP_PORT: u16 = 23;
|
||||||
@ -160,14 +159,22 @@ fn main() -> ! {
|
|||||||
|
|
||||||
usb::State::setup(usb);
|
usb::State::setup(usb);
|
||||||
|
|
||||||
let mut ipv4_address = DEFAULT_IPV4_ADDRESS;
|
let mut store = flash_store::store(dp.FLASH);
|
||||||
|
let mut store_value_buf = [0u8; 256];
|
||||||
|
|
||||||
let mut channels = Channels::new(pins);
|
let mut channels = Channels::new(pins);
|
||||||
let _ = Config::load(&mut eeprom)
|
for c in 0..CHANNELS {
|
||||||
.map(|config| {
|
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
|
||||||
config.apply(&mut channels);
|
Ok(Some(config)) =>
|
||||||
ipv4_address = Ipv4Address::from_bytes(&config.ipv4_address);
|
config.apply(&mut channels, c),
|
||||||
})
|
Ok(None) =>
|
||||||
.map_err(|e| warn!("error loading config: {:?}", e));
|
error!("flash config not found for channel {}", c),
|
||||||
|
Err(e) =>
|
||||||
|
error!("unable to load config {} from flash: {:?}", c, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ipv4_address = DEFAULT_IPV4_ADDRESS;
|
||||||
info!("IPv4 address: {}", ipv4_address);
|
info!("IPv4 address: {}", ipv4_address);
|
||||||
|
|
||||||
// EEPROM ships with a read-only EUI-48 identifier
|
// EEPROM ships with a read-only EUI-48 identifier
|
||||||
@ -341,22 +348,30 @@ fn main() -> ! {
|
|||||||
error!("unable to choose postfilter for rate {:.3}", rate),
|
error!("unable to choose postfilter for rate {:.3}", rate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Load => {
|
Command::Load { channel } => {
|
||||||
match Config::load(&mut eeprom) {
|
for c in 0..CHANNELS {
|
||||||
Ok(config) => {
|
if channel.is_none() || channel == Some(c) {
|
||||||
config.apply(&mut channels);
|
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
|
||||||
new_ipv4_address = Some(Ipv4Address::from_bytes(&config.ipv4_address));
|
Ok(Some(config)) =>
|
||||||
|
config.apply(&mut channels, c),
|
||||||
|
Ok(None) =>
|
||||||
|
error!("flash config not found"),
|
||||||
|
Err(e) =>
|
||||||
|
error!("unable to load config from flash: {:?}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) =>
|
|
||||||
error!("unable to load eeprom config: {:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Save => {
|
Command::Save { channel } => {
|
||||||
let config = Config::new(&mut channels, ipv4_address);
|
for c in 0..CHANNELS {
|
||||||
match config.save(&mut eeprom) {
|
if channel.is_none() || channel == Some(c) {
|
||||||
Ok(()) => {},
|
let config = ChannelConfig::new(&mut channels, c);
|
||||||
Err(e) =>
|
let _ = store
|
||||||
error!("unable to save eeprom config: {:?}", e),
|
.write_value(CHANNEL_CONFIG_KEY[c], &config, &mut store_value_buf)
|
||||||
|
.map_err(
|
||||||
|
|e| error!("unable to save config to flash: {:?}", e)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Ipv4(address) => {
|
Command::Ipv4(address) => {
|
||||||
|
@ -8,12 +8,10 @@ use uom::si::{
|
|||||||
ratio::ratio,
|
ratio::ratio,
|
||||||
thermodynamic_temperature::{degree_celsius, kelvin},
|
thermodynamic_temperature::{degree_celsius, kelvin},
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U200>;
|
|
||||||
|
|
||||||
/// Steinhart-Hart equation parameters
|
/// Steinhart-Hart equation parameters
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
/// Base temperature
|
/// Base temperature
|
||||||
pub t0: ThermodynamicTemperature,
|
pub t0: ThermodynamicTemperature,
|
||||||
@ -29,10 +27,6 @@ impl Parameters {
|
|||||||
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b;
|
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b;
|
||||||
ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp)
|
ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Parameters {
|
impl Default for Parameters {
|
||||||
|
Loading…
Reference in New Issue
Block a user