Compare commits
9 Commits
master
...
f06582caed
| Author | SHA1 | Date | |
|---|---|---|---|
| f06582caed | |||
| 2a0d9e0097 | |||
| d0b792be0e | |||
| d6300afe50 | |||
| 349e8efdb6 | |||
| 52c2169e1c | |||
| b3906b08e4 | |||
| fef05b937d | |||
| 18f7e19726 |
+30
-18
@@ -16,8 +16,8 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.6"
|
||||
edition = "2021"
|
||||
version = "0.5.0"
|
||||
edition = "2024"
|
||||
repository = "https://git.bfpower.io/BFPOWER/physical"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
@@ -30,14 +30,14 @@ default-features = false
|
||||
[workspace.dependencies.libm]
|
||||
version = "0.2.*"
|
||||
[workspace.dependencies.float-cmp]
|
||||
version = "0.9.*"
|
||||
version = "0.10.*"
|
||||
# Logging
|
||||
[workspace.dependencies.tracing]
|
||||
version = "0.1.*"
|
||||
[workspace.dependencies.defmt]
|
||||
version = "0.3.*"
|
||||
version = "1.0.*"
|
||||
[workspace.dependencies.defmt-rtt]
|
||||
version = "0.4.*"
|
||||
version = "1.1.*"
|
||||
# Embedded-HAL
|
||||
[workspace.dependencies.embedded-hal]
|
||||
version = "1.0.*"
|
||||
@@ -46,7 +46,7 @@ version = "1.0.*"
|
||||
# Memory
|
||||
[workspace.dependencies.static_cell]
|
||||
version = "2.1.*"
|
||||
# Serioalization
|
||||
# Serialization
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.*"
|
||||
default-features = false
|
||||
@@ -57,34 +57,45 @@ version = "0.7.*"
|
||||
[workspace.dependencies.cortex-m-rt]
|
||||
version = "0.7.*"
|
||||
[workspace.dependencies.panic-probe]
|
||||
version = "0.3.*"
|
||||
version = "1.0.*"
|
||||
features = ["print-defmt"]
|
||||
# Embassy
|
||||
[workspace.dependencies.embassy-futures]
|
||||
version = "0.1.*"
|
||||
[workspace.dependencies.embassy-time]
|
||||
version = "0.3.*"
|
||||
version = "0.5.*"
|
||||
features = ["defmt", "defmt-timestamp-uptime"]
|
||||
[workspace.dependencies.embassy-sync]
|
||||
version = "0.6.*"
|
||||
version = "0.8.*"
|
||||
features = ["defmt"]
|
||||
[workspace.dependencies.embassy-embedded-hal]
|
||||
version = "0.1.*"
|
||||
version = "0.6.*"
|
||||
[workspace.dependencies.embassy-executor]
|
||||
version = "0.5.*"
|
||||
features = ["defmt", "arch-cortex-m", "integrated-timers", "executor-interrupt", "executor-thread"]
|
||||
version = "0.10.*"
|
||||
features = ["defmt", "platform-cortex-m", "executor-interrupt", "executor-thread"]
|
||||
[workspace.dependencies.embassy-usb]
|
||||
version = "0.2.*"
|
||||
version = "0.6.*"
|
||||
features = ["defmt"]
|
||||
[workspace.dependencies.embassy-stm32]
|
||||
version = "0.1.*"
|
||||
features = ["defmt", "unstable-pac"]
|
||||
version = "0.6.*"
|
||||
features = ["defmt", "unstable-pac", "gpio-init-analog"]
|
||||
[workspace.dependencies.embassy-nrf]
|
||||
version = "0.1.*"
|
||||
version = "0.10.*"
|
||||
features = ["defmt"]
|
||||
# Meta
|
||||
[workspace.dependencies.cfg-if]
|
||||
version = "1.0.*"
|
||||
[workspace.dependencies.derive_more]
|
||||
version = "0.99.*"
|
||||
version = "2.1.*"
|
||||
default-features = false
|
||||
features = ["add", "add_assign", "not", "display"]
|
||||
[workspace.dependencies.thiserror]
|
||||
version = "2.0.*"
|
||||
default-features = false
|
||||
[workspace.dependencies.bitfields]
|
||||
version = "1.0.*"
|
||||
[workspace.dependencies.enumflags2]
|
||||
version = "0.7.*"
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0.*"
|
||||
features = ["extra-traits", "parsing"]
|
||||
@@ -113,7 +124,6 @@ libm = ["dep:libm", "num-traits/libm"]
|
||||
resistive-divider = []
|
||||
thermocouple-k = ["libm"]
|
||||
thermistor = ["libm"]
|
||||
lm35 = []
|
||||
pid = []
|
||||
stm32 = []
|
||||
|
||||
@@ -132,6 +142,8 @@ optional = true
|
||||
[dependencies.serde]
|
||||
workspace = true
|
||||
optional = true
|
||||
[dependencies.thiserror]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies.float-cmp]
|
||||
workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
drate, mux, opcodes, status, AdControl, Ads1256, BlockingDelay, CalibrationCommand, Conversion,
|
||||
DataRate, Gain, Multiplexer, Status,
|
||||
DataRate, Multiplexer, Status,
|
||||
};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi;
|
||||
|
||||
@@ -6,10 +6,13 @@ mod io;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
mod mutex;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use crate::adc::*;
|
||||
pub use crate::delay::*;
|
||||
#[allow(unused_imports)]
|
||||
pub use crate::io::*;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
#[allow(unused_imports)]
|
||||
pub use crate::mutex::*;
|
||||
pub use ads1256_types::adcon::{ClockOut, Gain, Sdcs};
|
||||
pub use ads1256_types::drate::DataRate;
|
||||
|
||||
@@ -157,7 +157,7 @@ impl Status {
|
||||
#[inline(always)]
|
||||
pub const fn data_ready(self) -> bool {
|
||||
const MASK: u8 = 0b1;
|
||||
unsafe { mem::transmute::<u8, bool>(self.0 & MASK) }
|
||||
unsafe { !mem::transmute::<u8, bool>(self.0 & MASK) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod input {
|
||||
use crate::{Buffer, Config, DataRate, Gain, Multiplexer, MuxInput};
|
||||
use crate::{Multiplexer, MuxInput};
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
|
||||
@@ -34,4 +34,4 @@ features = ["tick-hz-16_000_000"]
|
||||
[dependencies.panic-probe]
|
||||
workspace = true
|
||||
[dependencies]
|
||||
log = "0.4.20"
|
||||
log = "0.4.*"
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::prelude::{_embedded_hal_blocking_delay_DelayMs, _embedded_hal_blocking_delay_DelayUs};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
use {embassy_executor as executor, embassy_stm32 as stm32};
|
||||
|
||||
use ads1256::{
|
||||
AdControl, Ads1256, AutoCal, BitOrder, Buffer, ClockOut, Config, DState, DataRate, DigitalIo,
|
||||
DigitalIoDirection, DigitalIoState, DioDirection, Gain, Multiplexer, MuxInput, OutputPin, Sdcs,
|
||||
SpiBus, Status, Wait, BlockingDelay
|
||||
AdControl, Ads1256, AutoCal, BitOrder, Buffer, ClockOut, Config,
|
||||
DataRate, DigitalIo, DigitalIoDirection, DigitalIoState, Gain, Multiplexer,
|
||||
MuxInput, OutputPin, Sdcs, SpiBus, Status, Wait,
|
||||
};
|
||||
use embassy_time::{Delay, Timer};
|
||||
use embassy_time::Delay;
|
||||
use executor::Spawner;
|
||||
use physical::quantity::Quantity;
|
||||
use stm32::dma::NoDma;
|
||||
use stm32::exti::ExtiInput;
|
||||
use stm32::gpio::{Input, Level, Output, Pull, Speed};
|
||||
use stm32::gpio::{Level, Output, Pull, Speed};
|
||||
use stm32::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use defmt::{debug, error, info, trace, unwrap};
|
||||
use defmt::info;
|
||||
|
||||
const AUTOCAL_CONF: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
@@ -33,15 +31,17 @@ const AUTOCAL_CONF: Config = Config {
|
||||
|
||||
const ADS1256_DELAY: ads1256::DefaultDelay<Delay> = ads1256::DefaultDelay::new(Delay);
|
||||
|
||||
stm32::bind_interrupts!(struct Irqs {
|
||||
EXTI3 => stm32::exti::InterruptHandler<stm32::interrupt::typelevel::EXTI3>;
|
||||
});
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
unsafe {
|
||||
pac::FLASH.acr().modify(|v| {
|
||||
v.set_prften(true);
|
||||
v.set_icen(true);
|
||||
v.set_dcen(true);
|
||||
});
|
||||
}
|
||||
async fn main(_spawner: Spawner) {
|
||||
pac::FLASH.acr().modify(|v| {
|
||||
v.set_prften(true);
|
||||
v.set_icen(true);
|
||||
v.set_dcen(true);
|
||||
});
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
@@ -50,18 +50,10 @@ async fn main(spawner: Spawner) {
|
||||
spi_conf.bit_order = spi::BitOrder::MsbFirst;
|
||||
spi_conf.frequency = Hertz(ads1256::defaults::SPI_CLK_HZ);
|
||||
|
||||
let ads1256_data_ready = ExtiInput::new(Input::new(p.PA3, Pull::Up), p.EXTI3);
|
||||
let ads1256_data_ready = ExtiInput::new(p.PA3, p.EXTI3, Pull::Up, Irqs);
|
||||
let select_ads1256 = Output::new(p.PA1, Level::High, Speed::VeryHigh);
|
||||
|
||||
let mut spi = Spi::new(
|
||||
p.SPI1,
|
||||
p.PA5,
|
||||
p.PA7,
|
||||
p.PA6,
|
||||
NoDma,
|
||||
NoDma,
|
||||
spi_conf,
|
||||
);
|
||||
let mut spi = Spi::new_blocking(p.SPI1, p.PA5, p.PA7, p.PA6, spi_conf);
|
||||
|
||||
let mut ads_1256 = Ads1256::new(ADS1256_DELAY, select_ads1256, ads1256_data_ready);
|
||||
// single_conversion(&mut spi, &mut ads_1256).await;
|
||||
@@ -70,6 +62,7 @@ async fn main(spawner: Spawner) {
|
||||
cycle_multiplexer(&mut spi, &mut ads_1256).await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn single_conversion<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -78,9 +71,14 @@ async fn single_conversion<DelayerT: ads1256::BlockingDelay, SST: OutputPin, Drd
|
||||
ads_1256.delayer.t11_1_delay();
|
||||
ads_1256.conversion_init(spi).unwrap();
|
||||
let data = ads_1256.cmd_read_data(spi).await.unwrap();
|
||||
info!("data: {}, volts: {}", data, data.to_voltage(AUTOCAL_CONF.ad_control.gain()).fmt(Some(5)));
|
||||
info!(
|
||||
"data: {}, volts: {}",
|
||||
data,
|
||||
data.to_voltage(AUTOCAL_CONF.ad_control.gain()).fmt(Some(5))
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn read_continuous<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -90,7 +88,11 @@ async fn read_continuous<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT
|
||||
ads_1256.start_rdatac(spi).await.unwrap();
|
||||
loop {
|
||||
let data = ads_1256.read_data(spi).await.unwrap();
|
||||
info!("data: {}, volts: {}", data, data.to_voltage(AUTOCAL_CONF.ad_control.gain()).fmt(Some(5)));
|
||||
info!(
|
||||
"data: {}, volts: {}",
|
||||
data,
|
||||
data.to_voltage(AUTOCAL_CONF.ad_control.gain()).fmt(Some(5))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +105,6 @@ async fn cycle_multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, Drd
|
||||
const INPUT_3: Multiplexer = Multiplexer::setting(MuxInput::AIn3, MuxInput::Common);
|
||||
|
||||
let ad_control = AUTOCAL_CONF.ad_control;
|
||||
let status = AUTOCAL_CONF.status;
|
||||
|
||||
ads_1256.write_config(spi, AUTOCAL_CONF).unwrap();
|
||||
ads_1256.delayer.t11_1_delay();
|
||||
@@ -113,19 +114,30 @@ async fn cycle_multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, Drd
|
||||
.autocal_convert(spi, INPUT_1, None, Some(ad_control.with_gain(Gain::X4)), None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Input 1: data: {}, volts: {}", data, data.to_voltage(ad_control.gain()).fmt(Some(5)));
|
||||
info!(
|
||||
"Input 1: data: {}, volts: {}",
|
||||
data,
|
||||
data.to_voltage(ad_control.gain()).fmt(Some(5))
|
||||
);
|
||||
let ad_control = ad_control.with_gain(Gain::X8);
|
||||
let data = ads_1256
|
||||
.autocal_convert(spi, INPUT_2, None, Some(ad_control.with_gain(Gain::X8)), None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Input 2: data: {}, volts: {}", data, data.to_voltage(ad_control.gain()).fmt(Some(5)));
|
||||
info!(
|
||||
"Input 2: data: {}, volts: {}",
|
||||
data,
|
||||
data.to_voltage(ad_control.gain()).fmt(Some(5))
|
||||
);
|
||||
let ad_control = ad_control.with_gain(Gain::X16);
|
||||
let data = ads_1256
|
||||
.autocal_convert(spi, INPUT_3, None, Some(ad_control.with_gain(Gain::X16)), None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Input 3: data: {}, volts: {}", data, data.to_voltage(ad_control.gain()).fmt(Some(5)));
|
||||
info!(
|
||||
"Input 3: data: {}, volts: {}",
|
||||
data,
|
||||
data.to_voltage(ad_control.gain()).fmt(Some(5))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
use embassy_stm32::bind_interrupts;
|
||||
use {embassy_executor as executor, embassy_stm32 as stm32};
|
||||
|
||||
use ads1256::{
|
||||
@@ -13,26 +13,28 @@ use ads1256::{
|
||||
};
|
||||
use embassy_time::Delay;
|
||||
use executor::Spawner;
|
||||
use stm32::dma::NoDma;
|
||||
|
||||
use stm32::exti::ExtiInput;
|
||||
use stm32::gpio::{Input, Level, Output, Pull, Speed};
|
||||
use stm32::gpio::{Level, Output, Pull, Speed};
|
||||
use stm32::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use defmt::{debug, error, info, trace, unwrap};
|
||||
use defmt::info;
|
||||
|
||||
const ADS1256_DELAY: ads1256::DefaultDelay<Delay> = ads1256::DefaultDelay::new(Delay);
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
EXTI3 => stm32::exti::InterruptHandler<stm32::interrupt::typelevel::EXTI3>;
|
||||
});
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
unsafe {
|
||||
pac::FLASH.acr().modify(|v| {
|
||||
v.set_prften(true);
|
||||
v.set_icen(true);
|
||||
v.set_dcen(true);
|
||||
});
|
||||
}
|
||||
async fn main(_spawner: Spawner) {
|
||||
pac::FLASH.acr().modify(|v| {
|
||||
v.set_prften(true);
|
||||
v.set_icen(true);
|
||||
v.set_dcen(true);
|
||||
});
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
@@ -41,18 +43,10 @@ async fn main(spawner: Spawner) {
|
||||
spi_conf.bit_order = spi::BitOrder::MsbFirst;
|
||||
spi_conf.frequency = Hertz(ads1256::defaults::SPI_CLK_HZ);
|
||||
|
||||
let ads1256_data_ready = ExtiInput::new(Input::new(p.PA3, Pull::Up), p.EXTI3);
|
||||
let ads1256_data_ready = ExtiInput::new(p.PA3, p.EXTI3, Pull::Up, Irqs);
|
||||
let select_ads1256 = Output::new(p.PA1, Level::High, Speed::VeryHigh);
|
||||
|
||||
let mut spi = Spi::new(
|
||||
p.SPI1,
|
||||
p.PA5,
|
||||
p.PA7,
|
||||
p.PA6,
|
||||
NoDma,
|
||||
NoDma,
|
||||
spi_conf,
|
||||
);
|
||||
let mut spi = Spi::new_blocking(p.SPI1, p.PA5, p.PA7, p.PA6, spi_conf);
|
||||
|
||||
let mut ads_1256 = Ads1256::new(ADS1256_DELAY, select_ads1256, ads1256_data_ready);
|
||||
// status(&mut spi, &mut ads_1256);
|
||||
@@ -60,9 +54,10 @@ async fn main(spawner: Spawner) {
|
||||
// ad_control(&mut spi, &mut ads_1256);
|
||||
// data_rate(&mut spi, &mut ads_1256);
|
||||
// gpio(&mut spi, &mut ads_1256);
|
||||
config(&mut spi, &mut ads_1256);
|
||||
config(&mut spi, &mut ads_1256);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn status<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -79,6 +74,7 @@ fn status<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
info!("ADS1256 new status: {}", status);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -98,6 +94,7 @@ fn multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
info!("ADS1256 new ad_control: {}", multiplexer);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn ad_control<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -115,6 +112,7 @@ fn ad_control<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
info!("ADS1256 new ad_control: {}", ad_control);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn data_rate<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
@@ -129,6 +127,7 @@ fn data_rate<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
info!("ADS1256 new data rate: {}", data_rate);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn gpio<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
use std::ops::Deref;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{parse_macro_input, Ident, LitStr, Token};
|
||||
use syn::{Ident, LitStr, Token, parse_macro_input};
|
||||
|
||||
const NUMBER_TYPES: &[&str] = &[
|
||||
"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", "f32",
|
||||
@@ -108,9 +108,10 @@ pub fn quantity_type(input: TokenStream) -> TokenStream {
|
||||
derive_more::AddAssign,
|
||||
derive_more::Sub,
|
||||
derive_more::SubAssign,
|
||||
derive_more::Neg,
|
||||
derive_more::Display
|
||||
)]
|
||||
#[display(fmt = "{} {}", _0, "Self::symbol()")]
|
||||
#[display("{_0} {}", Self::symbol())]
|
||||
#[repr(transparent)]
|
||||
pub struct #struct_name<V: Value>(pub V);
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@ license.workspace = true
|
||||
comms = []
|
||||
single-packet-msgs = []
|
||||
usb = ["embassy-usb"]
|
||||
counter = []
|
||||
stm32 = ["embassy-stm32", "physical/stm32"]
|
||||
|
||||
[dependencies.physical]
|
||||
path = ".."
|
||||
[dependencies.cfg-if]
|
||||
workspace = true
|
||||
[dependencies.embedded-hal]
|
||||
workspace = true
|
||||
[dependencies.embedded-hal-async]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Sender {
|
||||
async fn send(&mut self, msg: &[u8]) -> Result<(), Reset>;
|
||||
}
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Receiver {
|
||||
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Reset>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
//! Hardware pulse counter using a timer in External Clock Mode.
|
||||
//!
|
||||
//! Counts edges on an external signal entirely in hardware. Supports three
|
||||
//! pin sources per timer: the dedicated ETR pin, or any CH1/CH2 pin.
|
||||
|
||||
use embassy_stm32::Peri;
|
||||
use embassy_stm32::gpio::{AfType, Flex, Pull};
|
||||
use embassy_stm32::pac::timer::vals::{Etp, Etps};
|
||||
use embassy_stm32::timer::low_level::{
|
||||
FilterValue, InputCaptureMode, InputTISelection, SlaveMode, Timer, TriggerSource,
|
||||
};
|
||||
use embassy_stm32::timer::{
|
||||
Ch1, Ch2, Channel, ExternalTriggerPin, GeneralInstance4Channel, TimerPin,
|
||||
};
|
||||
|
||||
/// Which edge increments the counter.
|
||||
#[derive(Clone, Copy, Default, defmt::Format)]
|
||||
pub enum CountEdge {
|
||||
#[default]
|
||||
Rising,
|
||||
Falling,
|
||||
/// Count on both rising and falling edges.
|
||||
/// Only supported with channel pins (CH1/CH2), not ETR.
|
||||
Both,
|
||||
}
|
||||
|
||||
/// Pulse counter configuration.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PulseCounterConfig {
|
||||
pub edge: CountEdge,
|
||||
pub filter: FilterValue,
|
||||
pub pull: Pull,
|
||||
}
|
||||
|
||||
impl Default for PulseCounterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
edge: CountEdge::Rising,
|
||||
filter: FilterValue::NO_FILTER,
|
||||
pull: Pull::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hardware pulse counter driven by an external signal.
|
||||
///
|
||||
/// Takes ownership of the timer and pin, preventing reuse elsewhere.
|
||||
pub struct PulseCounter<'d, T: GeneralInstance4Channel> {
|
||||
inner: Timer<'d, T>,
|
||||
_pin: Flex<'d>,
|
||||
}
|
||||
|
||||
// ---- Constructors ----
|
||||
|
||||
impl<'d, T: GeneralInstance4Channel> PulseCounter<'d, T> {
|
||||
/// Count pulses on the timer's ETR (External Trigger) pin.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `config.edge` is [`CountEdge::Both`] — ETR only supports single-edge detection.
|
||||
pub fn new_etr(
|
||||
tim: Peri<'d, T>,
|
||||
pin: Peri<'d, impl ExternalTriggerPin<T>>,
|
||||
config: PulseCounterConfig,
|
||||
) -> Self {
|
||||
assert!(
|
||||
!matches!(config.edge, CountEdge::Both),
|
||||
"ETR does not support both-edge detection"
|
||||
);
|
||||
|
||||
let af_num = pin.af_num();
|
||||
let mut flex_pin = Flex::new(pin);
|
||||
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||
|
||||
let inner = Timer::new(tim);
|
||||
|
||||
inner.set_slave_mode(SlaveMode::EXT_CLOCK_MODE);
|
||||
inner.set_trigger_source(TriggerSource::ETRF);
|
||||
inner.set_external_trigger_filter(config.filter);
|
||||
inner.set_external_trigger_prescaler(Etps::DIV1);
|
||||
inner.set_external_trigger_polarity(match config.edge {
|
||||
CountEdge::Rising => Etp::from(0),
|
||||
CountEdge::Falling => Etp::from(1),
|
||||
CountEdge::Both => unreachable!(),
|
||||
});
|
||||
|
||||
inner.start();
|
||||
Self {
|
||||
inner,
|
||||
_pin: flex_pin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Count pulses on a CH1 pin.
|
||||
pub fn new_ch1(
|
||||
tim: Peri<'d, T>,
|
||||
pin: Peri<'d, impl TimerPin<T, Ch1>>,
|
||||
config: PulseCounterConfig,
|
||||
) -> Self {
|
||||
let af_num = pin.af_num();
|
||||
let mut flex_pin = Flex::new(pin);
|
||||
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||
|
||||
let inner = Timer::new(tim);
|
||||
Self::configure_channel(&inner, Channel::Ch1, TriggerSource::TI1FP1, &config);
|
||||
|
||||
inner.start();
|
||||
Self {
|
||||
inner,
|
||||
_pin: flex_pin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Count pulses on a CH2 pin.
|
||||
pub fn new_ch2(
|
||||
tim: Peri<'d, T>,
|
||||
pin: Peri<'d, impl TimerPin<T, Ch2>>,
|
||||
config: PulseCounterConfig,
|
||||
) -> Self {
|
||||
let af_num = pin.af_num();
|
||||
let mut flex_pin = Flex::new(pin);
|
||||
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||
|
||||
let inner = Timer::new(tim);
|
||||
Self::configure_channel(&inner, Channel::Ch2, TriggerSource::TI2FP2, &config);
|
||||
|
||||
inner.start();
|
||||
Self {
|
||||
inner,
|
||||
_pin: flex_pin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Common channel setup: put the channel in input mode, configure its
|
||||
/// filter and edge polarity, then wire it as the slave-mode trigger.
|
||||
fn configure_channel(
|
||||
inner: &Timer<'d, T>,
|
||||
channel: Channel,
|
||||
trigger: TriggerSource,
|
||||
config: &PulseCounterConfig,
|
||||
) {
|
||||
// Channel must be in input mode for the ICxF filter bits to exist.
|
||||
// Normal = direct mapping (TI1→IC1, TI2→IC2).
|
||||
inner.set_input_ti_selection(channel, InputTISelection::Normal);
|
||||
inner.set_input_capture_filter(channel, config.filter);
|
||||
inner.set_input_capture_mode(
|
||||
channel,
|
||||
match config.edge {
|
||||
CountEdge::Rising => InputCaptureMode::Rising,
|
||||
CountEdge::Falling => InputCaptureMode::Falling,
|
||||
CountEdge::Both => InputCaptureMode::BothEdges,
|
||||
},
|
||||
);
|
||||
|
||||
inner.set_trigger_source(trigger);
|
||||
inner.set_slave_mode(SlaveMode::EXT_CLOCK_MODE);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Reading ----
|
||||
|
||||
impl<'d, T: GeneralInstance4Channel> PulseCounter<'d, T> {
|
||||
/// Read the current count (lower 16 bits).
|
||||
#[inline]
|
||||
pub fn count(&self) -> u16 {
|
||||
self.inner.regs_gp16().cnt().read().cnt()
|
||||
}
|
||||
|
||||
/// Reset the counter to zero.
|
||||
#[inline]
|
||||
pub fn reset(&self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: embassy_stm32::timer::GeneralInstance32bit4Channel> PulseCounter<'d, T> {
|
||||
/// Read the current count as a full 32-bit value.
|
||||
#[inline]
|
||||
pub fn count_32(&self) -> u32 {
|
||||
self.inner.regs_gp32().cnt().read()
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
#[cfg(feature = "usb")]
|
||||
pub mod usb;
|
||||
pub mod usb;
|
||||
#[cfg(feature = "counter")]
|
||||
pub mod counter;
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
use crate::comms;
|
||||
use embassy_stm32::peripherals::USB_OTG_FS;
|
||||
use embassy_stm32::usb_otg::{Driver, Endpoint, In, Out};
|
||||
use embassy_usb::driver::{EndpointIn, EndpointOut};
|
||||
use embassy_stm32::usb::Driver as StmUsbDriver;
|
||||
use embassy_usb::driver::{Driver as UsbDriverTrait, EndpointIn, EndpointOut};
|
||||
use embassy_usb::UsbDevice;
|
||||
|
||||
pub type TypedUSB = UsbDevice<'static, Driver<'static, USB_OTG_FS>>;
|
||||
pub type TypedInterIn = Endpoint<'static, USB_OTG_FS, In>;
|
||||
pub type TypedInterOut = Endpoint<'static, USB_OTG_FS, Out>;
|
||||
type UsbDriver = StmUsbDriver<'static, USB_OTG_FS>;
|
||||
pub type TypedInterIn = <UsbDriver as UsbDriverTrait<'static>>::EndpointIn;
|
||||
pub type TypedInterOut = <UsbDriver as UsbDriverTrait<'static>>::EndpointOut;
|
||||
|
||||
pub type TypedUSB = UsbDevice<'static, UsbDriver>;
|
||||
|
||||
pub struct UsbIO {
|
||||
/// Send to master
|
||||
@@ -18,13 +20,13 @@ pub struct UsbIO {
|
||||
pub interrupt_out: TypedInterOut,
|
||||
}
|
||||
|
||||
impl comms::Sender for TypedInterIn {
|
||||
impl<T: EndpointIn> comms::Sender for T {
|
||||
async fn send(&mut self, msg: &[u8]) -> Result<(), comms::Reset> {
|
||||
self.write(msg).await.map_err(|_| comms::Reset)
|
||||
}
|
||||
}
|
||||
|
||||
impl comms::Receiver for TypedInterOut {
|
||||
impl<T: EndpointOut> comms::Receiver for T {
|
||||
#[cfg(feature = "single-packet-msgs")]
|
||||
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), comms::Reset> {
|
||||
// This is OK when all our messages are smaller than a single packet.
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
# Before upgrading check that everything is available on all tier1 targets here:
|
||||
# https://rust-lang.github.io/rustup-components-history
|
||||
[toolchain]
|
||||
channel = "1.82"
|
||||
components = [ "rust-src", "rustfmt", "llvm-tools" ]
|
||||
channel = "stable"
|
||||
components = [ "rust-src", "rustfmt" ]
|
||||
targets = [
|
||||
"thumbv7em-none-eabi",
|
||||
"thumbv7m-none-eabi",
|
||||
|
||||
+5
-3
@@ -2,7 +2,9 @@ imports_granularity = "Module"
|
||||
format_strings = true
|
||||
wrap_comments = true
|
||||
match_block_trailing_comma = true
|
||||
enum_discrim_align_threshold = 25
|
||||
fn_call_width = 100
|
||||
enum_discrim_align_threshold = 50
|
||||
max_width = 110
|
||||
chain_width = 110
|
||||
fn_call_width = 110
|
||||
single_line_if_else_max_width = 110
|
||||
comment_width = 120
|
||||
single_line_if_else_max_width = 100
|
||||
+10
-4
@@ -1,9 +1,13 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Indicates the transducer value is known to be impossible.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||
#[error("invalid value")]
|
||||
pub struct InvalidValue;
|
||||
|
||||
/// Indicates that the encoded data is not valid for the type.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||
#[error("invalid encoding")]
|
||||
pub struct InvalidEncoding;
|
||||
/// An error that it is likely impossible to recover from. This error should only be created in
|
||||
/// situations where attempts to recover have already been attempted and have failed. Error handling
|
||||
@@ -12,11 +16,13 @@ pub struct InvalidEncoding;
|
||||
///
|
||||
/// In many systems there can be a single function for handling any critical error as a critical
|
||||
/// error always means everything needs to be stopped.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Error)]
|
||||
pub enum CriticalError {
|
||||
/// Critical communication failed and retries are either impossible or also failed.
|
||||
#[error("critical communication failure")]
|
||||
Communication,
|
||||
InvalidValue(InvalidValue),
|
||||
#[error(transparent)]
|
||||
InvalidValue(#[from] InvalidValue),
|
||||
}
|
||||
|
||||
/// A state of this type may mean the program has encountered an error that prevents it from continuing to run
|
||||
|
||||
+10
-13
@@ -6,9 +6,6 @@ mod volume;
|
||||
mod volume_rate;
|
||||
mod pressure;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
pub use defmt_impl::*;
|
||||
|
||||
pub use irradiance::*;
|
||||
pub use resistance::*;
|
||||
pub use temperature::*;
|
||||
@@ -22,16 +19,16 @@ use core::marker::PhantomData;
|
||||
use core::ops::{Add, Sub};
|
||||
use num_traits::{FromPrimitive, Num, NumCast};
|
||||
|
||||
const DECA: u8 = 10;
|
||||
const DECI: u8 = 10;
|
||||
const HECTO: u8 = 100;
|
||||
const CENTI: u8 = 100;
|
||||
const KILO: u16 = 1_000;
|
||||
const MILLI: u16 = 1_000;
|
||||
const MEGA: u32 = 1_000_000;
|
||||
const MICRO: u32 = 1_000_000;
|
||||
const GIGA: u32 = 1_000_000_000;
|
||||
const NANO: u32 = 1_000_000_000;
|
||||
pub const DECA: u8 = 10;
|
||||
pub const DECI: u8 = 10;
|
||||
pub const HECTO: u8 = 100;
|
||||
pub const CENTI: u8 = 100;
|
||||
pub const KILO: u16 = 1_000;
|
||||
pub const MILLI: u16 = 1_000;
|
||||
pub const MEGA: u32 = 1_000_000;
|
||||
pub const MICRO: u32 = 1_000_000;
|
||||
pub const GIGA: u32 = 1_000_000_000;
|
||||
pub const NANO: u32 = 1_000_000_000;
|
||||
|
||||
pub trait Quantity<V: Value>: Copy + PartialEq + PartialOrd + Add + Sub {
|
||||
fn value(self) -> V;
|
||||
|
||||
@@ -19,9 +19,9 @@ quantity_type! {MilliLiters, "mL"}
|
||||
|
||||
impl<V: Value> MilliLiters<V> {
|
||||
#[inline]
|
||||
pub fn to_liters(self) -> MilliLiters<V> {
|
||||
pub fn to_liters(self) -> Liters<V> {
|
||||
let divisor = V::from_u16(MILLI).unwrap();
|
||||
MilliLiters(self.0 / divisor)
|
||||
Liters(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod part;
|
||||
|
||||
pub use part::*;
|
||||
#[allow(unused_imports)]
|
||||
pub use part::*;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::error::InvalidValue;
|
||||
use crate::quantity::{DeciCelsius, MilliVolts, Quantity};
|
||||
|
||||
#[inline]
|
||||
pub fn convert(
|
||||
voltage: MilliVolts<i16>,
|
||||
) -> Result<DeciCelsius<i16>, InvalidValue> {
|
||||
const MIN_VOLTAGE: MilliVolts<i16> = MilliVolts(-550);
|
||||
const MAX_VOLTAGE: MilliVolts<i16> = MilliVolts(1_500);
|
||||
|
||||
if voltage >= MIN_VOLTAGE && voltage <= MAX_VOLTAGE {
|
||||
Ok(DeciCelsius(voltage.value()))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
mod thermocouple;
|
||||
|
||||
#[cfg(feature = "lm35")]
|
||||
pub mod lm35;
|
||||
pub mod thermocouple;
|
||||
|
||||
#[cfg(feature = "thermistor")]
|
||||
pub mod thermistor;
|
||||
|
||||
#[cfg(feature = "thermocouple-k")]
|
||||
pub use thermocouple::type_k as thermocouple_k;
|
||||
pub mod thermistor;
|
||||
@@ -0,0 +1,198 @@
|
||||
#[cfg(feature = "thermocouple-k")]
|
||||
pub mod k {
|
||||
//! Type K thermocouple conversion using [f64] arithmetic internally.
|
||||
//!
|
||||
//! All conversion functions clamp their inputs to the valid range rather than returning errors.
|
||||
//! Use the `MIN_*` / `MAX_*` constants to check whether an input is in range before
|
||||
//! calling a conversion function if out-of-range detection is needed.
|
||||
|
||||
use libm::exp;
|
||||
use crate::quantity::{Celsius, MilliVolts};
|
||||
|
||||
// ----- Voltage-to-temperature constants -----
|
||||
|
||||
/// Minimum voltage accepted by the NIST inverse polynomial (-5.891 mV ≈ -210 °C).
|
||||
pub const MIN_VOLTAGE: MilliVolts<f64> = MilliVolts(-5.891);
|
||||
/// Maximum voltage accepted by the NIST inverse polynomial (54.886 mV ≈ 1372 °C).
|
||||
pub const MAX_VOLTAGE: MilliVolts<f64> = MilliVolts(54.886);
|
||||
|
||||
// ----- Temperature-to-voltage constants -----
|
||||
|
||||
/// Minimum temperature accepted by the NIST forward polynomial (-270 °C).
|
||||
pub const MIN_TEMP_POLY: Celsius<f64> = Celsius(-270.0);
|
||||
/// Maximum temperature accepted by the NIST forward polynomial (1372 °C).
|
||||
pub const MAX_TEMP_POLY: Celsius<f64> = Celsius(1372.0);
|
||||
|
||||
/// Minimum temperature accepted by the Seebeck approximation (-2 °C).
|
||||
pub const MIN_TEMP_SEEBECK: Celsius<f32> = Celsius(-2.0);
|
||||
/// Maximum temperature accepted by the Seebeck approximation (800 °C).
|
||||
pub const MAX_TEMP_SEEBECK: Celsius<f32> = Celsius(800.0);
|
||||
|
||||
// ----- Voltage to temperature (inverse polynomial) -----
|
||||
|
||||
/// Core NIST ITS-90 inverse polynomial for type K.
|
||||
/// Input is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`].
|
||||
fn voltage_to_temp(voltage: MilliVolts<f64>) -> Celsius<f32> {
|
||||
let mv = voltage.0.clamp(MIN_VOLTAGE.0, MAX_VOLTAGE.0);
|
||||
let mv_pow2 = mv * mv;
|
||||
let mv_pow3 = mv_pow2 * mv;
|
||||
let mv_pow4 = mv_pow3 * mv;
|
||||
let mv_pow5 = mv_pow4 * mv;
|
||||
let mv_pow6 = mv_pow5 * mv;
|
||||
|
||||
let celsius = if mv >= -5.891 && mv <= 0.0 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
|
||||
2.5173462E+1 * mv
|
||||
+ -1.1662878 * mv_pow2
|
||||
+ -1.0833638 * mv_pow3
|
||||
+ -8.9773540E-1 * mv_pow4
|
||||
+ -3.7342377E-1 * mv_pow5
|
||||
+ -8.6632643E-2 * mv_pow6
|
||||
+ -1.0450598E-2 * mv_pow7
|
||||
+ -5.1920577E-4 * mv_pow8
|
||||
} else if mv > 0.0 && mv < 20.644 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
let mv_pow9 = mv_pow8 * mv;
|
||||
|
||||
2.508355E+1 * mv
|
||||
+ 7.860106E-2 * mv_pow2
|
||||
+ -2.503131E-1 * mv_pow3
|
||||
+ 8.315270E-2 * mv_pow4
|
||||
+ -1.228034E-2 * mv_pow5
|
||||
+ 9.804036E-4 * mv_pow6
|
||||
+ -4.413030E-5 * mv_pow7
|
||||
+ 1.057734E-6 * mv_pow8
|
||||
+ -1.052755E-8 * mv_pow9
|
||||
} else {
|
||||
// mv >= 20.644 && mv <= 54.886
|
||||
-1.318058e2
|
||||
+ 4.830222E+1 * mv
|
||||
+ -1.646031 * mv_pow2
|
||||
+ 5.464731E-2 * mv_pow3
|
||||
+ -9.650715E-4 * mv_pow4
|
||||
+ 8.802193E-6 * mv_pow5
|
||||
+ -3.110810E-8 * mv_pow6
|
||||
};
|
||||
|
||||
Celsius(celsius as f32)
|
||||
}
|
||||
|
||||
// ----- Public voltage-to-temperature conversions -----
|
||||
|
||||
/// Convert thermocouple voltage to temperature by directly adding the reference junction
|
||||
/// temperature to the polynomial result for cold-junction compensation.
|
||||
///
|
||||
/// Can be useful compared to [`convert_seebeck`] when the reference temperature or the
|
||||
/// temperature being read by the thermocouple is fairly close to 0 °C.
|
||||
///
|
||||
/// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`].
|
||||
///
|
||||
/// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_direct(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f32>,
|
||||
) -> Celsius<f32> {
|
||||
voltage_to_temp(voltage) + r_junction
|
||||
}
|
||||
|
||||
/// Convert thermocouple voltage to temperature using a constant Seebeck coefficient to
|
||||
/// correct the input voltage for cold-junction compensation.
|
||||
///
|
||||
/// Probably the right choice most of the time.
|
||||
///
|
||||
/// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`].
|
||||
/// Reference junction temperature is clamped to [`MIN_TEMP_SEEBECK`]..=[`MAX_TEMP_SEEBECK`].
|
||||
///
|
||||
/// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_seebeck(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f32>,
|
||||
) -> Celsius<f32> {
|
||||
let voltage_correction = temp_to_voltage_seebeck(r_junction);
|
||||
voltage_to_temp(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
}
|
||||
|
||||
/// Convert thermocouple voltage to temperature using the full NIST forward polynomial to
|
||||
/// correct the input voltage for cold-junction compensation.
|
||||
///
|
||||
/// This is the most accurate method but uses the most processor cycles by a wide margin.
|
||||
///
|
||||
/// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`].
|
||||
/// Reference junction temperature is clamped to [`MIN_TEMP_POLY`]..=[`MAX_TEMP_POLY`].
|
||||
///
|
||||
/// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_polynomial(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f64>,
|
||||
) -> Celsius<f32> {
|
||||
let voltage_correction = temp_to_voltage_poly(r_junction);
|
||||
voltage_to_temp(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
}
|
||||
|
||||
// ----- Temperature to voltage (forward functions) -----
|
||||
|
||||
/// Convert a temperature to a type K thermocouple voltage using the full NIST forward
|
||||
/// polynomial.
|
||||
///
|
||||
/// Temperature is clamped to [`MIN_TEMP_POLY`]..=[`MAX_TEMP_POLY`].
|
||||
pub fn temp_to_voltage_poly(temperature: Celsius<f64>) -> MilliVolts<f32> {
|
||||
let celsius = temperature.0.clamp(MIN_TEMP_POLY.0, MAX_TEMP_POLY.0);
|
||||
let cel_pow2 = celsius * celsius;
|
||||
let cel_pow3 = cel_pow2 * celsius;
|
||||
let cel_pow4 = cel_pow3 * celsius;
|
||||
let cel_pow5 = cel_pow4 * celsius;
|
||||
let cel_pow6 = cel_pow5 * celsius;
|
||||
let cel_pow7 = cel_pow6 * celsius;
|
||||
let cel_pow8 = cel_pow7 * celsius;
|
||||
let cel_pow9 = cel_pow8 * celsius;
|
||||
|
||||
let mv = if celsius >= -270.0 && celsius < 0.0 {
|
||||
let cel_pow10 = cel_pow9 * celsius;
|
||||
|
||||
0.394501280250E-01 * celsius
|
||||
+ 0.236223735980E-04 * cel_pow2
|
||||
+ -0.328589067840E-06 * cel_pow3
|
||||
+ -0.499048287770E-08 * cel_pow4
|
||||
+ -0.675090591730E-10 * cel_pow5
|
||||
+ -0.574103274280E-12 * cel_pow6
|
||||
+ -0.310888728940E-14 * cel_pow7
|
||||
+ -0.104516093650E-16 * cel_pow8
|
||||
+ -0.198892668780E-19 * cel_pow9
|
||||
+ -0.163226974860E-22 * cel_pow10
|
||||
} else {
|
||||
// celsius >= 0.0 && celsius <= 1372.0
|
||||
let base = celsius - 0.126968600000E+03;
|
||||
let exponent = -0.118343200000E-03 * (base * base);
|
||||
let addition = 0.1185976 * exp(exponent);
|
||||
|
||||
-0.176004136860E-01
|
||||
+ 0.389212049750E-01 * celsius
|
||||
+ 0.185587700320E-04 * cel_pow2
|
||||
+ -0.994575928740E-07 * cel_pow3
|
||||
+ 0.318409457190E-09 * cel_pow4
|
||||
+ -0.560728448890E-12 * cel_pow5
|
||||
+ 0.560750590590E-15 * cel_pow6
|
||||
+ -0.320207200030E-18 * cel_pow7
|
||||
+ 0.971511471520E-22 * cel_pow8
|
||||
+ -0.121047212750E-25 * cel_pow9
|
||||
+ addition
|
||||
};
|
||||
|
||||
MilliVolts(mv as f32)
|
||||
}
|
||||
|
||||
/// Convert a temperature to a type K thermocouple voltage using a constant Seebeck
|
||||
/// coefficient approximation (41 µV/°C).
|
||||
///
|
||||
/// Temperature is clamped to [`MIN_TEMP_SEEBECK`]..=[`MAX_TEMP_SEEBECK`].
|
||||
#[inline]
|
||||
pub fn temp_to_voltage_seebeck(temperature: Celsius<f32>) -> MilliVolts<f32> {
|
||||
MilliVolts(0.041 * temperature.0.clamp(MIN_TEMP_SEEBECK.0, MAX_TEMP_SEEBECK.0))
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "thermocouple-k")]
|
||||
pub mod type_k;
|
||||
@@ -1,169 +0,0 @@
|
||||
//! Note - Thermocouple conversion uses [f64] arithmetic internally.
|
||||
|
||||
use libm::pow;
|
||||
use crate::error::InvalidValue;
|
||||
use crate::quantity::{Celsius, MilliVolts, Quantity};
|
||||
|
||||
fn _convert(
|
||||
voltage: MilliVolts<f64>,
|
||||
) -> Result<Celsius<f32>, InvalidValue> {
|
||||
let mv = voltage.value();
|
||||
let mv_pow2 = mv * mv;
|
||||
let mv_pow3 = mv_pow2 * mv;
|
||||
let mv_pow4 = mv_pow3 * mv;
|
||||
let mv_pow5 = mv_pow4 * mv;
|
||||
let mv_pow6 = mv_pow5 * mv;
|
||||
|
||||
if mv >= -5.891 && mv <= 0.0 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
|
||||
let celsius = 2.5173462E+1 * mv
|
||||
+ -1.1662878 * mv_pow2
|
||||
+ -1.0833638 * mv_pow3
|
||||
+ -8.9773540E-1 * mv_pow4
|
||||
+ -3.7342377E-1 * mv_pow5
|
||||
+ -8.6632643E-2 * mv_pow6
|
||||
+ -1.0450598E-2 * mv_pow7
|
||||
+ -5.1920577E-4 * mv_pow8;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
} else if mv > 0.0 && mv < 20.644 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
let mv_pow9 = mv_pow8 * mv;
|
||||
|
||||
let celsius = 2.508355E+1 * mv
|
||||
+ 7.860106E-2 * mv_pow2
|
||||
+ -2.503131E-1 * mv_pow3
|
||||
+ 8.315270E-2 * mv_pow4
|
||||
+ -1.228034E-2 * mv_pow5
|
||||
+ 9.804036E-4 * mv_pow6
|
||||
+ -4.413030E-5 * mv_pow7
|
||||
+ 1.057734E-6 * mv_pow8
|
||||
+ -1.052755E-8 * mv_pow9;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
} else if mv >= 20.644 && mv <= 54.886 {
|
||||
let celsius = 1.318058e2
|
||||
+ 4.830222E+1 * mv
|
||||
+ -1.646031 * mv_pow2
|
||||
+ 5.464731E-2 * mv_pow3
|
||||
+ -9.650715E-4 * mv_pow4
|
||||
+ 8.802193E-6 * mv_pow5
|
||||
+ -3.110810E-8 * mv_pow6;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// directly adding the reference junction temperature to the result for offset compensation.
|
||||
///
|
||||
/// Can be useful compared to [convert_seebeck] when the reference temperature or the temperature
|
||||
/// being read by the thermocouple is fairly close to 0.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_direct(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f32>,
|
||||
) -> Result<Celsius<f32>, InvalidValue> {
|
||||
let base_temp = _convert(voltage)?;
|
||||
|
||||
Ok(base_temp + r_junction)
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// using a constant seebeck coefficient to correct the input voltage for offset compensation.
|
||||
///
|
||||
/// Probably the right choice most of the time.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_seebeck(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f32>,
|
||||
) -> Result<Celsius<f32>, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_seebeck(r_junction)?;
|
||||
_convert(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// using a polynomial to correct the input voltage for offset compensation.
|
||||
///
|
||||
/// This is the most accurate method but uses the most processor cycles by a wide margin.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_polynomial(
|
||||
voltage: MilliVolts<f64>,
|
||||
r_junction: Celsius<f64>,
|
||||
) -> Result<Celsius<f32>, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_poly(r_junction)?;
|
||||
_convert(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
}
|
||||
|
||||
pub fn temp_to_voltage_poly(
|
||||
temperature: Celsius<f64>,
|
||||
) -> Result<MilliVolts<f32>, InvalidValue> {
|
||||
let celsius = temperature.value();
|
||||
let cel_pow2 = celsius * celsius;
|
||||
let cel_pow3 = cel_pow2 * celsius;
|
||||
let cel_pow4 = cel_pow3 * celsius;
|
||||
let cel_pow5 = cel_pow4 * celsius;
|
||||
let cel_pow6 = cel_pow5 * celsius;
|
||||
let cel_pow7 = cel_pow6 * celsius;
|
||||
let cel_pow8 = cel_pow7 * celsius;
|
||||
let cel_pow9 = cel_pow8 * celsius;
|
||||
|
||||
if celsius >= -270.0 && celsius < 0.0 {
|
||||
let cel_pow10 = cel_pow9 * celsius;
|
||||
|
||||
let mv = 0.394501280250E-01 * celsius
|
||||
+ 0.236223735980E-04 * cel_pow2
|
||||
+ -0.328589067840E-06 * cel_pow3
|
||||
+ -0.499048287770E-08 * cel_pow4
|
||||
+ -0.675090591730E-10 * cel_pow5
|
||||
+ -0.574103274280E-12 * cel_pow6
|
||||
+ -0.310888728940E-14 * cel_pow7
|
||||
+ -0.104516093650E-16 * cel_pow8
|
||||
+ -0.198892668780E-19 * cel_pow9
|
||||
+ -0.163226974860E-22 * cel_pow10;
|
||||
|
||||
Ok(MilliVolts(mv as f32))
|
||||
} else if celsius >= 0.0 && celsius <= 1372.0 {
|
||||
let base = celsius - 0.126968600000E+03;
|
||||
let exp = -0.118343200000E-03 * (base * base);
|
||||
let addition = pow(0.1185976, exp);
|
||||
|
||||
let mv = -0.176004136860E-01
|
||||
+ 0.389212049750E-01 * celsius
|
||||
+ 0.185587700320E-04 * cel_pow2
|
||||
+ -0.994575928740E-07 * cel_pow3
|
||||
+ 0.318409457190E-09 * cel_pow4
|
||||
+ -0.560728448890E-12 * cel_pow5
|
||||
+ 0.560750590590E-15 * cel_pow6
|
||||
+ -0.320207200030E-18 * cel_pow7
|
||||
+ 0.971511471520E-22 * cel_pow8
|
||||
+ -0.121047212750E-25 * cel_pow9
|
||||
+ addition;
|
||||
|
||||
Ok(MilliVolts(mv as f32))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn temp_to_voltage_seebeck(
|
||||
temperature: Celsius<f32>,
|
||||
) -> Result<MilliVolts<f32>, InvalidValue> {
|
||||
if temperature.value() >= -2.0 && temperature.value() <= 800.0 {
|
||||
Ok(MilliVolts(0.041 * temperature.value()))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user