Use fixed-precision decimal numbers

This commit is contained in:
Luca 2024-01-22 23:11:48 +01:00
parent 95e253efed
commit e7aa77494b
5 changed files with 115 additions and 57 deletions

67
Cargo.lock generated
View File

@ -41,12 +41,24 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.7" version = "0.7.7"
@ -94,6 +106,12 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "debug-helper" name = "debug-helper"
version = "0.3.13" version = "0.3.13"
@ -175,6 +193,18 @@ dependencies = [
"void", "void",
] ]
[[package]]
name = "fixed"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c69ce7e7c0f17aa18fdd9d0de39727adb9c6281f2ad12f57cbe54ae6e76e7d"
dependencies = [
"az",
"bytemuck",
"half",
"typenum",
]
[[package]] [[package]]
name = "float-cmp" name = "float-cmp"
version = "0.9.0" version = "0.9.0"
@ -199,6 +229,35 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
[[package]]
name = "half"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -407,7 +466,9 @@ dependencies = [
"display-interface", "display-interface",
"embedded-graphics", "embedded-graphics",
"embedded-hal", "embedded-hal",
"fixed",
"fugit", "fugit",
"heapless",
"nb 1.1.0", "nb 1.1.0",
"panic-halt", "panic-halt",
"rp-pico", "rp-pico",
@ -446,6 +507,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.10" version = "1.0.10"

View File

@ -11,7 +11,9 @@ cortex-m-rt = "0.7.3"
display-interface = "0.4.1" display-interface = "0.4.1"
embedded-graphics = "0.8.0" embedded-graphics = "0.8.0"
embedded-hal = "0.2.7" embedded-hal = "0.2.7"
fixed = "1.24.0"
fugit = "0.3.7" fugit = "0.3.7"
heapless = "0.8.0"
nb = "1.1.0" nb = "1.1.0"
panic-halt = "0.2.0" panic-halt = "0.2.0"
rp-pico = "0.7.0" rp-pico = "0.7.0"

View File

@ -3,7 +3,8 @@
mod max6675; mod max6675;
use core::str; use core::fmt::Write;
use core::write;
use cortex_m::delay::Delay; use cortex_m::delay::Delay;
@ -23,7 +24,9 @@ use embedded_hal::{
use fugit::{ExtU64, MicrosDurationU64, RateExtU32}; use fugit::{ExtU64, MicrosDurationU64, RateExtU32};
use max6675::{Error, Max6675}; use heapless::String;
use max6675::{Error, FixedU16, Max6675};
use nb::Error::WouldBlock; use nb::Error::WouldBlock;
@ -49,19 +52,14 @@ use usb_device::{bus::UsbBus as UsbBusTrait, bus::UsbBusAllocator, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC}; use usbd_serial::{SerialPort, USB_CLASS_CDC};
const HYSTERESIS: u16 = 10; const HYSTERESIS: FixedU16 = FixedU16::const_from_int(5);
const PEAK_TEMPERATURE: u16 = 240; const PEAK_TEMPERATURE: FixedU16 = FixedU16::const_from_int(240);
const POWER_FACTOR: u16 = 10; const PREHEAT_TEMPERATURE: FixedU16 = FixedU16::const_from_int(150);
const POWER_MAX: u16 = 1000;
const PREHEAT_TEMPERATURE: u16 = 150;
const SOAK_TIME: MicrosDurationU64 = MicrosDurationU64::secs(90); const SOAK_TIME: MicrosDurationU64 = MicrosDurationU64::secs(90);
const TEMPERATURE_BUFF: u16 = 10;
enum State<'a> { enum State<'a> {
Off, Off,
Preheat, Preheat,
@ -70,14 +68,6 @@ enum State<'a> {
Cooling, Cooling,
} }
fn clamp(value: u16, max: u16) -> u16 {
if value > max {
max
} else {
value
}
}
fn print_serial<'a, B: UsbBusTrait>( fn print_serial<'a, B: UsbBusTrait>(
serial: &mut SerialPort<'a, B>, serial: &mut SerialPort<'a, B>,
msg: &str, msg: &str,
@ -230,7 +220,6 @@ fn main() -> ! {
let so = pins.gpio6.into_floating_input(); let so = pins.gpio6.into_floating_input();
let cs = pins.gpio7.into_push_pull_output_in_state(PinState::High); let cs = pins.gpio7.into_push_pull_output_in_state(PinState::High);
let sck = pins.gpio8.into_push_pull_output(); let sck = pins.gpio8.into_push_pull_output();
let mut thermocouple = Max6675::new(cs, sck, so); let mut thermocouple = Max6675::new(cs, sck, so);
let button = pins.gpio15.into_pull_up_input(); let button = pins.gpio15.into_pull_up_input();
@ -246,7 +235,7 @@ fn main() -> ! {
let mut counter = 0; let mut counter = 0;
let mut state = State::Off; let mut state = State::Off;
let mut heating = false; let mut heating = false;
let mut power = 0; let mut error_counter = 0;
loop { loop {
delay.delay_ms(5); delay.delay_ms(5);
counter = (counter + 1) % 2000; counter = (counter + 1) % 2000;
@ -271,15 +260,13 @@ fn main() -> ! {
if counter % 50 == 0 { if counter % 50 == 0 {
let thermocouple_result = thermocouple.read_temperature(&mut timer.count_down()); let thermocouple_result = thermocouple.read_temperature(&mut timer.count_down());
if let Some(mut temperature) = thermocouple_result.as_ref().copied().ok() { if let Some(temperature) = thermocouple_result.as_ref().copied().ok() {
temperature = (temperature >> 2) + ((temperature & 2) >> 1); error_counter = 0;
match state { match state {
State::Off => (),
State::Preheat => { State::Preheat => {
if temperature < PREHEAT_TEMPERATURE { if temperature < PREHEAT_TEMPERATURE {
heating = true; heating = true;
power = clamp((PREHEAT_TEMPERATURE - temperature + TEMPERATURE_BUFF) * POWER_FACTOR, POWER_MAX);
} else { } else {
let mut time = timer.count_down(); let mut time = timer.count_down();
time.start(SOAK_TIME); time.start(SOAK_TIME);
@ -289,58 +276,61 @@ fn main() -> ! {
State::Soak(ref mut time) => match time.wait() { State::Soak(ref mut time) => match time.wait() {
Ok(_) => state = State::Reflow, Ok(_) => state = State::Reflow,
Err(WouldBlock) => { Err(WouldBlock) => {
if temperature < PREHEAT_TEMPERATURE - HYSTERESIS { if temperature < PREHEAT_TEMPERATURE {
heating = true; heating = true;
power = clamp((PREHEAT_TEMPERATURE - temperature + TEMPERATURE_BUFF) * POWER_FACTOR, POWER_MAX); }
} else if temperature > PREHEAT_TEMPERATURE + HYSTERESIS * 2 { if temperature > PREHEAT_TEMPERATURE + HYSTERESIS {
state = State::Reflow;
} else if temperature > PREHEAT_TEMPERATURE {
heating = false; heating = false;
} }
} }
Err(_) => state = State::Off, Err(_) => unreachable!(),
}, },
State::Reflow => { State::Reflow => {
if temperature < PEAK_TEMPERATURE { if temperature < PEAK_TEMPERATURE {
heating = true; heating = true;
power = clamp((PEAK_TEMPERATURE - temperature + TEMPERATURE_BUFF) * POWER_FACTOR, POWER_MAX);
} else { } else {
heating = false; heating = false;
state = State::Cooling; state = State::Cooling;
} }
} }
State::Cooling => state = State::Off, State::Cooling if temperature < PREHEAT_TEMPERATURE => {
state = State::Preheat;
}
_ => (),
} }
} else { } else {
error_counter += 1;
}
if error_counter >= 4 {
state = State::Off; state = State::Off;
} }
let mut bytes: [u8; 7] = [32, 32, 32, 32, 194, 176, 67]; let mut text: String<9> = String::new();
let text = match thermocouple_result { let text_str;
Ok(mut temperature) => {
temperature = (temperature >> 2) + ((temperature & 2) >> 1);
for i in (0..3).rev() { match thermocouple_result {
bytes[i] = 48u8 + (temperature % 10) as u8; Ok(temperature) => {
temperature /= 10; if let Err(_) = write!(&mut text, "{}", temperature) {
text_str = "format error";
if temperature == 0 { } else {
break; text_str = text.as_str();
}
} }
str::from_utf8(&bytes).unwrap()
} }
Err(Error::IOError) => "i/o error", Err(Error::IOError) => {
Err(Error::OpenThermocouple) => "n/c", text_str = "i/o error";
}
Err(Error::OpenThermocouple) => {
text_str = "n/c";
}
}; };
let _ = println_serial(&mut serial, text); let _ = println_serial(&mut serial, text_str);
display.clear_buffer(); display.clear_buffer();
if let Err(err) = display_text(&mut display, text, Baseline::Bottom) { if let Err(err) = display_text(&mut display, text_str, Baseline::Bottom) {
let _ = println_serial(&mut serial, stringify_display_error(err)); let _ = println_serial(&mut serial, stringify_display_error(err));
} }
@ -362,16 +352,11 @@ fn main() -> ! {
} }
if let State::Off = state { if let State::Off = state {
relay.set_low().unwrap();
heating = false; heating = false;
} }
if heating { if heating {
if counter == 0 { relay.set_high().unwrap();
relay.set_high().unwrap();
} else if counter >= power {
relay.set_low().unwrap();
}
} else { } else {
relay.set_low().unwrap(); relay.set_low().unwrap();
} }

View File

@ -3,10 +3,14 @@ use embedded_hal::{
timer::CountDown, timer::CountDown,
}; };
use fixed::types::extra::U2;
use fugit::{ExtU64, MicrosDurationU64}; use fugit::{ExtU64, MicrosDurationU64};
use nb::block; use nb::block;
pub type FixedU16 = fixed::FixedU16<U2>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
IOError, IOError,
@ -34,7 +38,7 @@ where
Self { cs, sck, so } Self { cs, sck, so }
} }
pub fn read_temperature<CD>(&mut self, timer: &mut CD) -> Result<u16, Error> pub fn read_temperature<CD>(&mut self, timer: &mut CD) -> Result<FixedU16, Error>
where where
CD: CountDown, CD: CountDown,
CD::Time: From<MicrosDurationU64>, CD::Time: From<MicrosDurationU64>,
@ -61,7 +65,7 @@ where
if d & 0x4 == 0x4 { if d & 0x4 == 0x4 {
Err(Error::OpenThermocouple) Err(Error::OpenThermocouple)
} else { } else {
Ok(d >> 3) Ok(FixedU16::from_num(d >> 3))
} }
} }
} }