Compare commits
10 Commits
master
..
01280c79fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 01280c79fe | |||
| ffe0efede0 | |||
| 346c52e617 | |||
| babfb92222 | |||
| b299f59d9f | |||
| 93efdd247f | |||
| 6fc828e864 | |||
| 886fbf0020 | |||
| 407aaa951c | |||
| 1a44ea892b |
+34
-52
@@ -6,31 +6,32 @@ members = [
|
||||
# Device types
|
||||
"node",
|
||||
"commander",
|
||||
# Drivers
|
||||
"drivers/ads1256/types",
|
||||
"drivers/ads1256/driver",
|
||||
# Meta
|
||||
"generate-quantity",
|
||||
# Peripherals
|
||||
|
||||
# Peripheral components
|
||||
"peripheral-components/ads1256/*",
|
||||
# Macros
|
||||
"macros/node-poll-variants",
|
||||
# Examples
|
||||
"examples/ads1256"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.6"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
repository = "https://git.bfpower.io/BFPOWER/physical"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
#----- no-std ----------------------------------
|
||||
# Numbers
|
||||
[workspace.dependencies.num-traits]
|
||||
version = "0.2.*"
|
||||
default-features = false
|
||||
# Math
|
||||
[workspace.dependencies.libm]
|
||||
version = "0.2.*"
|
||||
[workspace.dependencies.float-cmp]
|
||||
version = "0.9.*"
|
||||
# Units of measurement
|
||||
[workspace.dependencies.uom]
|
||||
version = "0.35.*"
|
||||
default-features = false
|
||||
features = ["f32", "si"]
|
||||
# Logging
|
||||
[workspace.dependencies.tracing]
|
||||
version = "0.1.*"
|
||||
@@ -38,6 +39,10 @@ version = "0.1.*"
|
||||
version = "0.3.*"
|
||||
[workspace.dependencies.defmt-rtt]
|
||||
version = "0.4.*"
|
||||
# Serialization
|
||||
[workspace.dependencies.parity-scale-codec]
|
||||
version = "3.6.*"
|
||||
default-features = false
|
||||
# Embedded-HAL
|
||||
[workspace.dependencies.embedded-hal]
|
||||
version = "1.0.*"
|
||||
@@ -45,12 +50,9 @@ version = "1.0.*"
|
||||
version = "1.0.*"
|
||||
# Memory
|
||||
[workspace.dependencies.static_cell]
|
||||
version = "2.1.*"
|
||||
# Serioalization
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.*"
|
||||
default-features = false
|
||||
features = ["derive"]
|
||||
version = "2.0.*"
|
||||
[workspace.dependencies.heapless]
|
||||
version = "0.8.*"
|
||||
# Other embedded utilities
|
||||
[workspace.dependencies.cortex-m]
|
||||
version = "0.7.*"
|
||||
@@ -59,6 +61,13 @@ version = "0.7.*"
|
||||
[workspace.dependencies.panic-probe]
|
||||
version = "0.3.*"
|
||||
features = ["print-defmt"]
|
||||
# BFPOWER Drivers
|
||||
[workspace.dependencies.ads1256-types]
|
||||
git = "https://git.bfpower.io/BFPOWER/bfpower-drivers.git"
|
||||
features = ["defmt"]
|
||||
[workspace.dependencies.ads1256]
|
||||
git = "https://git.bfpower.io/BFPOWER/bfpower-drivers.git"
|
||||
features = ["uom"]
|
||||
# Embassy
|
||||
[workspace.dependencies.embassy-futures]
|
||||
version = "0.1.*"
|
||||
@@ -66,32 +75,25 @@ version = "0.1.*"
|
||||
version = "0.3.*"
|
||||
features = ["defmt", "defmt-timestamp-uptime"]
|
||||
[workspace.dependencies.embassy-sync]
|
||||
version = "0.6.*"
|
||||
version = "0.5.*"
|
||||
features = ["defmt"]
|
||||
[workspace.dependencies.embassy-embedded-hal]
|
||||
version = "0.1.*"
|
||||
[workspace.dependencies.embassy-executor]
|
||||
version = "0.5.*"
|
||||
features = ["defmt", "arch-cortex-m", "integrated-timers", "executor-interrupt", "executor-thread"]
|
||||
[workspace.dependencies.embassy-usb]
|
||||
version = "0.2.*"
|
||||
features = ["defmt"]
|
||||
[workspace.dependencies.embassy-stm32]
|
||||
version = "0.1.*"
|
||||
features = ["defmt", "unstable-pac"]
|
||||
[workspace.dependencies.embassy-nrf]
|
||||
version = "0.1.*"
|
||||
features = ["defmt"]
|
||||
# Meta
|
||||
[workspace.dependencies.derive_more]
|
||||
version = "0.99.*"
|
||||
# Macros
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0.*"
|
||||
features = ["extra-traits", "parsing"]
|
||||
[workspace.dependencies.quote]
|
||||
version = "1.0.*"
|
||||
[workspace.dependencies.proc-macro2]
|
||||
version = "1.0.*"
|
||||
[workspace.dependencies.trybuild]
|
||||
version = "1.0.*"
|
||||
|
||||
@@ -108,33 +110,13 @@ readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
std = ["num-traits/std"]
|
||||
libm = ["dep:libm", "num-traits/libm"]
|
||||
resistive-divider = []
|
||||
thermocouple-k = ["libm"]
|
||||
thermistor = ["libm"]
|
||||
thermocouple_k = []
|
||||
lm35 = []
|
||||
pid = []
|
||||
stm32 = []
|
||||
|
||||
[dependencies.generate-quantity]
|
||||
path = "generate-quantity"
|
||||
[dependencies.num-traits]
|
||||
workspace = true
|
||||
[dependencies.derive_more]
|
||||
workspace = true
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
optional = true
|
||||
[dependencies.libm]
|
||||
workspace = true
|
||||
optional = true
|
||||
[dependencies.serde]
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.float-cmp]
|
||||
workspace = true
|
||||
[dependencies]
|
||||
uom = { workspace = true }
|
||||
parity-scale-codec = { workspace = true }
|
||||
libm = { workspace = true }
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------
|
||||
#----- Profiles ------------------------
|
||||
|
||||
@@ -19,5 +19,8 @@ The main concepts of Physical are:
|
||||
* Node: A node hosts peripherals. A node can have a commander but does not need one. A node can ignore or even override
|
||||
commands from the commander. In a complex system, nodes are intended to be kept simple, less likely to
|
||||
encounter an error than the commander, and in some cases should check for obvious problems in commands from the
|
||||
commander. Node can also communicate with other nodes.
|
||||
* Commander: A commander hosts nodes. It performs long running computations and directs nodes based on the results.
|
||||
commander.
|
||||
* Commander: A commander hosts nodes. It is possible for a device to be both a node and a commander at the same time,
|
||||
although it may not be the best idea to make such a setup. There is no concept of nesting commanders built into
|
||||
Physical. If some kind of abstraction for a computer that commands multiple commanders, which command nodes, is
|
||||
necessary, it should be made for that specific application.
|
||||
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "ads1256"
|
||||
description = "Driver for ADS1256."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------
|
||||
#----- Dependencies ------------------------
|
||||
#---------------------------------------------------------------------------------------------------------------------
|
||||
[dependencies.ads1256-types]
|
||||
path = "../types"
|
||||
features = ["defmt"]
|
||||
[dependencies.physical]
|
||||
path = "../../.."
|
||||
[dependencies.physical-node]
|
||||
path = "../../../node"
|
||||
[dependencies.embedded-hal]
|
||||
workspace = true
|
||||
[dependencies.embedded-hal-async]
|
||||
workspace = true
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
[dependencies.embassy-sync]
|
||||
workspace = true
|
||||
optional = true
|
||||
@@ -1,562 +0,0 @@
|
||||
use crate::{
|
||||
drate, mux, opcodes, status, AdControl, Ads1256, BlockingDelay, CalibrationCommand, Conversion,
|
||||
DataRate, Gain, Multiplexer, Status,
|
||||
};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi;
|
||||
use embedded_hal::spi::SpiBus;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use physical_node::GPIO_ERROR_MSG;
|
||||
use physical_node::spi::{end_spi, end_spi_if_err};
|
||||
|
||||
impl<DelayerT, SST, DrdyT> Ads1256<DelayerT, SST, DrdyT>
|
||||
where
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
{
|
||||
/// Enter read data continuous mode (wait for drdy low, issue RDATAC command)
|
||||
#[inline]
|
||||
pub async fn start_rdatac<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = spi.write(&[opcodes::RDATAC]);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Exit read data continuous mode (wait for drdy low, issue SDATAC command)
|
||||
#[inline]
|
||||
pub async fn stop_rdatac<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = spi.write(&[opcodes::SDATAC]);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn read_data<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let mut buffer = [0u8; 3];
|
||||
let spi_op_result = spi.read(&mut buffer);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)?;
|
||||
|
||||
Ok(Conversion::from_reading(buffer))
|
||||
}
|
||||
|
||||
/// Issues RDATA command and receives result, does not handle any housekeeping such as setting
|
||||
/// slave select or flushing the bus on successful completion.
|
||||
#[inline]
|
||||
pub fn raw_cmd_read_data<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
let spi_op_result = spi.write(&[opcodes::RDATA]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
self.delayer.t6_delay();
|
||||
let mut buffer = [0u8; 3];
|
||||
spi.read(&mut buffer)?;
|
||||
|
||||
Ok(Conversion::from_reading(buffer))
|
||||
}
|
||||
|
||||
/// Reads the digital conversion value.
|
||||
/// Action sequence:
|
||||
/// 1. Wait for data_ready to go low
|
||||
/// 1. Issue RDATA command
|
||||
/// 1. Read the conversion value
|
||||
#[inline]
|
||||
pub async fn cmd_read_data<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = self.raw_cmd_read_data(spi);
|
||||
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn conversion_init<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
// Issue sync command
|
||||
let spi_op_result = spi.write(&[opcodes::SYNC]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
// Issue wakeup command
|
||||
let spi_op_result = spi.write(&[opcodes::WAKEUP]);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
//----- Configure Convert Functions ----------------------------------
|
||||
|
||||
/// Function for configuring and reading AD conversions using auto-calibration. If
|
||||
/// only the inputs are changed and no other configuration, auto-calibration will not be
|
||||
/// run.
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Switch inputs and optionally adjust different configuration parameters.
|
||||
/// 1. If only the input was switched without configuration changes.
|
||||
/// 1. Issue sync command followed by wakeup command
|
||||
/// 1. Else, auto-calibration will take place
|
||||
/// 1. Wait for data_ready low
|
||||
/// 1. RDATA command (read the conversion value)
|
||||
/// 1. Optionally enter standby mode
|
||||
///
|
||||
/// **WARNING:** Auto-calibration must be enabled for intended functionality when changing
|
||||
/// [Status], [AdControl], or [DataRate]. Furthermore if setting [Status] or [AdControl], their
|
||||
/// [Buffer] and [Gain] settings must be modified respectively to trigger auto-calibration.
|
||||
#[inline]
|
||||
pub async fn autocal_convert<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
status: Option<Status>,
|
||||
ad_control: Option<AdControl>,
|
||||
data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
match (status, ad_control, data_rate) {
|
||||
// Only modifying the multiplexer, not changing any configuration
|
||||
(None, None, None) => {
|
||||
self._none_config(spi, input)?;
|
||||
self._manual_conversion_init(spi)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer) and changing multiplexer
|
||||
(Some(status), None, None) => {
|
||||
self._status_config(spi, input, status)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying AD control (gain) and changing multiplexer
|
||||
(None, Some(ad_control), None) => {
|
||||
self._ad_config(spi, input, ad_control)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying data rate and change multiplexer
|
||||
(None, None, Some(data_rate)) => {
|
||||
self._drate_config(spi, input, data_rate)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), and changing multiplexer
|
||||
(Some(status), Some(ad_control), None) => {
|
||||
self._status_ad_config(spi, input, status, ad_control)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), data rate, and changing multiplexer
|
||||
(Some(status), None, Some(data_rate)) => {
|
||||
self._status_drate_config(spi, input, status, data_rate)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying AD control (gain), data rate, and changing multiplexer
|
||||
(None, Some(ad_control), Some(data_rate)) => {
|
||||
self._ad_drate_config(spi, input, ad_control, data_rate)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), data rate, and changing
|
||||
// multiplexer
|
||||
(Some(status), Some(ad_control), Some(data_rate)) => {
|
||||
self._all_config(spi, input, status, ad_control, data_rate)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Function for periodically configuring and reading AD conversions using stored calibration
|
||||
/// values. If only the inputs are changed and no other configuration, auto-calibration will
|
||||
/// not be run. For immediate continuous multiplexing see [Ads1256::loadcal_convert_next].
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Switch inputs and optionally adjust different configuration parameters.
|
||||
/// 1. Issue sync command followed by wakeup command
|
||||
/// 1. Wait for data_ready low
|
||||
/// 1. RDATA command (read the conversion value)
|
||||
/// 1. Optionally enter standby mode
|
||||
///
|
||||
/// **WARNING:** Auto-calibration must be disabled for intended functionality when changing
|
||||
/// [Status], [AdControl], or [DataRate].
|
||||
#[inline]
|
||||
pub async fn loadcal_convert<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
calibration: Option<&CalibrationCommand>,
|
||||
status: Option<Status>,
|
||||
ad_control: Option<AdControl>,
|
||||
data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
match (status, ad_control, data_rate) {
|
||||
// Only modifying the multiplexer, not changing any configuration
|
||||
(None, None, None) => {
|
||||
self._none_config(spi, input)?;
|
||||
self._manual_conversion_init(spi)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer) and changing multiplexer
|
||||
(Some(status), None, None) => {
|
||||
self._status_config(spi, input, status)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying AD control (gain) and changing multiplexer
|
||||
(None, Some(ad_control), None) => {
|
||||
self._ad_config(spi, input, ad_control)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying data rate and change multiplexer
|
||||
(None, None, Some(data_rate)) => {
|
||||
self._drate_config(spi, input, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), and changing multiplexer
|
||||
(Some(status), Some(ad_control), None) => {
|
||||
self._status_ad_config(spi, input, status, ad_control)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), data rate, and changing multiplexer
|
||||
(Some(status), None, Some(data_rate)) => {
|
||||
self._status_drate_config(spi, input, status, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying AD control (gain), data rate, and changing multiplexer
|
||||
(None, Some(ad_control), Some(data_rate)) => {
|
||||
self._ad_drate_config(spi, input, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), data rate, and changing
|
||||
// multiplexer
|
||||
(Some(status), Some(ad_control), Some(data_rate)) => {
|
||||
self._all_config(spi, input, status, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
self._read_when_rdy(spi, standby).await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Function for rapidly configuring and reading AD conversions using stored calibration
|
||||
/// values. If only the inputs are changed and no other configuration, auto-calibration will
|
||||
/// not be run. For periodic reading see [Ads1256::loadcal_convert].
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Wait for data_ready low
|
||||
/// 1. RDATA command (read the conversion value)
|
||||
/// 1. Switch inputs and optionally adjust different configuration parameters.
|
||||
/// 1. Issue sync command followed by wakeup command
|
||||
///
|
||||
/// **WARNING:** Auto-calibration must be disabled for intended functionality when changing
|
||||
/// [Status], [AdControl], or [DataRate].
|
||||
#[inline]
|
||||
pub async fn loadcal_convert_next<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
next_input: Multiplexer,
|
||||
next_calibration: Option<&CalibrationCommand>,
|
||||
next_status: Option<Status>,
|
||||
next_ad_control: Option<AdControl>,
|
||||
next_data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self._loadcal_convert_next(
|
||||
spi,
|
||||
next_input,
|
||||
next_calibration,
|
||||
next_status,
|
||||
next_ad_control,
|
||||
next_data_rate,
|
||||
standby,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) async fn _loadcal_convert_next<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
next_input: Multiplexer,
|
||||
next_calibration: Option<&CalibrationCommand>,
|
||||
next_status: Option<Status>,
|
||||
next_ad_control: Option<AdControl>,
|
||||
next_data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
match (next_status, next_ad_control, next_data_rate) {
|
||||
// Only modifying the multiplexer, not changing any configuration
|
||||
(None, None, None) => {
|
||||
self._none_config(spi, next_input)?;
|
||||
self._manual_conversion_init(spi)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying status (toggle buffer) and changing multiplexer
|
||||
(Some(status), None, None) => {
|
||||
self._status_config(spi, next_input, status)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying AD control (gain) and changing multiplexer
|
||||
(None, Some(ad_control), None) => {
|
||||
self._ad_config(spi, next_input, ad_control)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying data rate and change multiplexer
|
||||
(None, None, Some(data_rate)) => {
|
||||
self._drate_config(spi, next_input, data_rate)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), and changing multiplexer
|
||||
(Some(status), Some(ad_control), None) => {
|
||||
self._status_ad_config(spi, next_input, status, ad_control)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying status (toggle buffer), data rate, and changing multiplexer
|
||||
(Some(status), None, Some(data_rate)) => {
|
||||
self._status_drate_config(spi, next_input, status, data_rate)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying AD control (gain), data rate, and changing multiplexer
|
||||
(None, Some(ad_control), Some(data_rate)) => {
|
||||
self._ad_drate_config(spi, next_input, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), data rate, and changing
|
||||
// multiplexer
|
||||
(Some(status), Some(ad_control), Some(data_rate)) => {
|
||||
self._all_config(spi, next_input, status, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, next_calibration)?;
|
||||
self._read_mux_conversion(spi, standby)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a pre-defined number of samples. This will use read continuous command, the ADS1256 must be
|
||||
/// properly configure before calling this function. This uses [BlockingDelay::t11_1_delay] before running so as it's
|
||||
/// almost always used after configuring the ADS1256 for a specific channel.
|
||||
pub async fn multi_sample<SpiT: SpiBus, const NUM_SAMPLES: usize>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<[Conversion; NUM_SAMPLES], <SpiT as spi::ErrorType>::Error> {
|
||||
let mut samples: [Conversion; NUM_SAMPLES] = [0.into(); NUM_SAMPLES];
|
||||
self.delayer.t11_1_delay();
|
||||
|
||||
self.start_rdatac(spi).await?;
|
||||
for i in 0..NUM_SAMPLES {
|
||||
samples[i] = self.read_data(spi).await?;
|
||||
}
|
||||
self.delayer.t6_delay();
|
||||
self.stop_rdatac(spi).await?;
|
||||
|
||||
Ok(samples)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _none_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Change inputs
|
||||
let buffer = [0u8, 0u8, input.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, mux::ADDRESS, buffer);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _loadcal_init<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
calibration: Option<&CalibrationCommand>,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.delayer.t11_1_delay();
|
||||
if let Some(calibration) = calibration {
|
||||
let spi_op_result = spi.write(calibration.into());
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
}
|
||||
self._manual_conversion_init(spi)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _manual_conversion_init<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.delayer.t11_1_delay();
|
||||
// Since we did not change configuration, ADS1256 will not auto-calibrate so we
|
||||
// need to sync, wakeup
|
||||
// Issue sync command
|
||||
let spi_op_result = spi.write(&[opcodes::SYNC]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
// Issue wakeup command
|
||||
let spi_op_result = spi.write(&[opcodes::WAKEUP]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _status_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
status: Status,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, status.0, input.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, status::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _ad_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
ad_control: AdControl,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, input.0, ad_control.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, mux::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _drate_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
data_rate: DataRate,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Switch inputs
|
||||
let mut buffer = [0u8, 0u8, input.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, mux::ADDRESS, buffer);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
self.delayer.t11_1_delay();
|
||||
// Modify data rate
|
||||
buffer[2] = data_rate as u8;
|
||||
let spi_op_result = self.raw_write_registers(spi, drate::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _status_ad_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
status: Status,
|
||||
ad_control: AdControl,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, status.0, input.0, ad_control.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, status::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _status_drate_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
status: Status,
|
||||
data_rate: DataRate,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Modify status and switch inputs
|
||||
let buffer = [0u8, 0u8, status.0, input.0];
|
||||
let spi_op_result = self.raw_write_registers(spi, status::ADDRESS, buffer);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
self.delayer.t11_1_delay();
|
||||
// Modify data rate
|
||||
let buffer = [0u8, 0u8, data_rate as u8];
|
||||
let spi_op_result = self.raw_write_registers(spi, drate::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _ad_drate_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
ad_control: AdControl,
|
||||
data_rate: DataRate,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, input.0, ad_control.0, data_rate as u8];
|
||||
let spi_op_result = self.raw_write_registers(spi, mux::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _all_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
status: Status,
|
||||
ad_control: AdControl,
|
||||
data_rate: DataRate,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, status.0, input.0, ad_control.0, data_rate as u8];
|
||||
let spi_op_result = self.raw_write_registers(spi, status::ADDRESS, buffer);
|
||||
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn _read_when_rdy<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
// Wait for data ready low
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
// Read data
|
||||
self._read_mux_conversion(spi, standby)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn _read_mux_conversion<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
// Read data
|
||||
let spi_op_result = self.raw_cmd_read_data(spi);
|
||||
let cvalue = end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
// if standby is true enter standby mode
|
||||
if standby {
|
||||
let spi_op_result = spi.write(&[opcodes::STANDBY]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
}
|
||||
// end SPI
|
||||
self.slave_select.set_high().expect(GPIO_ERROR_MSG);
|
||||
spi.flush()?;
|
||||
|
||||
Ok(cvalue)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use embedded_hal::delay::DelayNs;
|
||||
|
||||
//TODO: Change to maybe async when available instead of always blocking
|
||||
pub trait BlockingDelay {
|
||||
/// Delay between spi write and read in a read data command
|
||||
/// Total 50 master clock cycles
|
||||
fn t6_delay(&mut self);
|
||||
|
||||
/// Delay after RREG, WREG, RDATA
|
||||
/// Total 4 master clock cycles
|
||||
fn t11_1_delay(&mut self);
|
||||
|
||||
/// Delay after RDATAC, RESET, SYNC
|
||||
/// Total 24 master clock cycles
|
||||
fn t11_2_delay(&mut self);
|
||||
}
|
||||
|
||||
pub struct DefaultDelay<DelayT: DelayNs> {
|
||||
delayer: DelayT,
|
||||
}
|
||||
|
||||
impl<DelayT: DelayNs> DefaultDelay<DelayT> {
|
||||
#[inline(always)]
|
||||
pub const fn new(delayer: DelayT) -> Self {
|
||||
Self { delayer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DelayT: DelayNs> BlockingDelay for DefaultDelay<DelayT> {
|
||||
#[inline(always)]
|
||||
fn t6_delay(&mut self) {
|
||||
self.delayer.delay_us(7);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn t11_1_delay(&mut self) {
|
||||
self.delayer.delay_us(1);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn t11_2_delay(&mut self) {
|
||||
self.delayer.delay_us(4);
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
use crate::{
|
||||
adcon, drate, fsc0, fsc2, gpio, mux, ofc0, opcodes, status, AdControl, Ads1256, AllCalibration,
|
||||
BlockingDelay, CalibrationCommand, Config, DataRate, DigitalIo, GainCalibration, Multiplexer,
|
||||
OffsetCalibration, Status,
|
||||
};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi;
|
||||
use embedded_hal::spi::SpiBus;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use physical_node::GPIO_ERROR_MSG;
|
||||
use physical_node::spi::{end_spi, end_spi_if_err};
|
||||
|
||||
impl<DelayerT, SST, DrdyT> Ads1256<DelayerT, SST, DrdyT>
|
||||
where
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
{
|
||||
//----- Base register read/write ----------------------------------
|
||||
/// [buffer] - The data to send the the ADS1256 starting at index 2, the first two bytes are
|
||||
/// reserved as the command bytes.
|
||||
#[inline]
|
||||
pub fn raw_write_registers<SpiT: SpiBus, const BUF_SIZE: usize>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
start_address: u8,
|
||||
mut buffer: [u8; BUF_SIZE],
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let num_registers = BUF_SIZE - 2;
|
||||
assert!(start_address <= fsc2::ADDRESS, "Invalid starting register address.");
|
||||
//TODO: Change to compile time assertion or bound in future Rust version.
|
||||
assert!(num_registers <= 11, "Cannot write more than the total number of registers.");
|
||||
assert!(num_registers >= 1, "Must write at least one register.");
|
||||
// num_registers represents the total number of registers to write, including the one at the
|
||||
// provided address. Adjust values based on it accordingly.
|
||||
|
||||
// First command byte = 4 bits for the write register opcode, followed by 4 bits for the
|
||||
// starting register address.
|
||||
let cmd_start = opcodes::WREG | start_address;
|
||||
// Second byte = number of registers to write in addition to the register at the starting
|
||||
// address.
|
||||
let num_additional = num_registers as u8 - 1;
|
||||
buffer[0] = cmd_start;
|
||||
buffer[1] = num_additional;
|
||||
|
||||
spi.write(&buffer)
|
||||
}
|
||||
|
||||
/// [buffer] - The data to send the the ADS1256 starting at index 2, the first two bytes are
|
||||
/// reserved as the command bytes.
|
||||
#[inline]
|
||||
pub fn write_registers<SpiT: SpiBus, const BUF_SIZE: usize>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
start_address: u8,
|
||||
buffer: [u8; BUF_SIZE],
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = self.raw_write_registers(spi, start_address, buffer);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
fn read_registers<SpiT: SpiBus, const NUM: usize>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
start_address: u8,
|
||||
) -> Result<[u8; NUM], <SpiT as spi::ErrorType>::Error> {
|
||||
assert!(start_address <= fsc2::ADDRESS, "Invalid starting register address.");
|
||||
//TODO: Change to compile time assertion or bound.
|
||||
assert!(NUM <= 11, "Cannot read more than the total number of registers.");
|
||||
assert!(NUM >= 1, "Must read at least one register.");
|
||||
// NUM represents the total number of registers to read, including the one at the provided
|
||||
// address. Adjust values based on it accordingly.
|
||||
|
||||
// First command byte = 4 bits for the read register opcode, followed by 4 bits for the
|
||||
// starting register address.
|
||||
let cmd_start = opcodes::RREG | start_address;
|
||||
// Second byte = number of registers to read in addition to the register at the starting
|
||||
// address.
|
||||
let num_additional = NUM as u8 - 1;
|
||||
let mut buffer = [0u8; NUM];
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = spi.write(&[cmd_start, num_additional]);
|
||||
end_spi_if_err(&mut self.slave_select, spi, spi_op_result)?;
|
||||
self.delayer.t6_delay();
|
||||
let spi_op_result = spi.read(&mut buffer);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
//----- Standalone commands ----------------------------------
|
||||
#[inline]
|
||||
fn standalone_command<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
opcode: u8,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = spi.write(&[opcode]);
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn standby<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.standalone_command(spi, opcodes::STANDBY)
|
||||
}
|
||||
|
||||
/// Self calibration is performed after reset, therefore additional commands should not be sent
|
||||
/// until data ready pin goes low indicating the calibration is complete.
|
||||
#[inline(always)]
|
||||
pub fn reset<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.standalone_command(spi, opcodes::RESET)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn wake<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
self.standalone_command(spi, opcodes::WAKEUP)
|
||||
}
|
||||
|
||||
/// Perform self offset and gain calibration.
|
||||
#[inline]
|
||||
pub async fn self_calibrate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let result = self.standalone_command(spi, opcodes::SELFCAL);
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
result
|
||||
}
|
||||
|
||||
/// Perform self offset calibration.
|
||||
#[inline]
|
||||
pub async fn self_offset_calibrate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let result = self.standalone_command(spi, opcodes::SELFOCAL);
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
result
|
||||
}
|
||||
|
||||
/// Perform self gain calibration.
|
||||
#[inline]
|
||||
pub async fn self_gain_calibrate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let result = self.standalone_command(spi, opcodes::SELFGCAL);
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
result
|
||||
}
|
||||
|
||||
/// Perform system offset calibration.
|
||||
#[inline]
|
||||
pub async fn system_offset_calibrate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let result = self.standalone_command(spi, opcodes::SYSOCAL);
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
result
|
||||
}
|
||||
|
||||
/// Perform system gain calibration.
|
||||
#[inline]
|
||||
pub async fn system_gain_calibrate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let result = self.standalone_command(spi, opcodes::SYSGCAL);
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
result
|
||||
}
|
||||
|
||||
//----- Public register read/write ----------------------------------
|
||||
#[inline]
|
||||
pub fn read_status<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Status, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(Status(self.read_registers::<_, 1>(spi, status::ADDRESS)?[0]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_status<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: Status,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 3] = [0, 0, setting.0];
|
||||
self.write_registers(spi, status::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_multiplexer<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Multiplexer, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(Multiplexer(self.read_registers::<_, 1>(spi, mux::ADDRESS)?[0]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_multiplexer<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: Multiplexer,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 3] = [0, 0, setting.0];
|
||||
self.write_registers(spi, mux::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_ad_control<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<AdControl, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(AdControl(self.read_registers::<_, 1>(spi, adcon::ADDRESS)?[0]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_ad_control<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: AdControl,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 3] = [0, 0, setting.0];
|
||||
self.write_registers(spi, adcon::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
/// Combined function to write the ADC and multiplexer registers at the same time since this is such a common
|
||||
/// occurrence.
|
||||
#[inline]
|
||||
pub fn write_mux_adc<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
input: Multiplexer,
|
||||
ad_control: AdControl,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let buffer = [0u8, 0u8, input.0, ad_control.0];
|
||||
self.write_registers(spi, mux::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_data_rate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<DataRate, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(DataRate::from_byte(self.read_registers::<_, 1>(spi, drate::ADDRESS)?[0]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_data_rate<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: DataRate,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 3] = [0, 0, setting as u8];
|
||||
self.write_registers(spi, drate::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_gpio<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<DigitalIo, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(DigitalIo(self.read_registers::<_, 1>(spi, gpio::ADDRESS)?[0]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_gpio<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: DigitalIo,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 3] = [0, 0, setting.0];
|
||||
self.write_registers(spi, gpio::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_offset_calibration<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<OffsetCalibration, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(OffsetCalibration(self.read_registers(spi, ofc0::ADDRESS)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_offset_calibration<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: OffsetCalibration,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data values to offset
|
||||
// calibration bytes
|
||||
let buffer = [0, 0, setting.0[0], setting.0[1], setting.0[2]];
|
||||
self.write_registers(spi, ofc0::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_gain_calibration<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<GainCalibration, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(GainCalibration(self.read_registers(spi, fsc0::ADDRESS)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_gain_calibration<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: GainCalibration,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data values to gain
|
||||
// calibration bytes
|
||||
let buffer = [0, 0, setting.0[0], setting.0[1], setting.0[2]];
|
||||
self.write_registers(spi, fsc0::ADDRESS, buffer)
|
||||
}
|
||||
|
||||
/// Reads all calibration registers. Bytes 0 to 2 are offset calibration, bytes 3 to 5 are gain
|
||||
/// calibration.
|
||||
#[inline]
|
||||
pub fn read_all_calibration<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<AllCalibration, <SpiT as spi::ErrorType>::Error> {
|
||||
Ok(self.read_registers(spi, ofc0::ADDRESS)?.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn exec_cal_command<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
command: &CalibrationCommand,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
let spi_op_result = spi.write(command.into());
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
) -> Result<Config, <SpiT as spi::ErrorType>::Error> {
|
||||
let bytes = self.read_registers::<_, 5>(spi, status::ADDRESS)?;
|
||||
Ok(Config::from_bytes(bytes))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_config<SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &mut SpiT,
|
||||
setting: Config,
|
||||
) -> Result<(), <SpiT as spi::ErrorType>::Error> {
|
||||
// Create full command buffer, initialize command bytes to 0 and set data value to status
|
||||
// byte.
|
||||
let buffer: [u8; 7] = [
|
||||
0,
|
||||
0,
|
||||
setting.status.0,
|
||||
setting.multiplexer.0,
|
||||
setting.ad_control.0,
|
||||
setting.data_rate as u8,
|
||||
setting.digital_io.0,
|
||||
];
|
||||
self.write_registers(spi, status::ADDRESS, buffer)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#![no_std]
|
||||
|
||||
mod adc;
|
||||
mod delay;
|
||||
mod io;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
mod mutex;
|
||||
|
||||
pub use crate::adc::*;
|
||||
pub use crate::delay::*;
|
||||
pub use crate::io::*;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
pub use crate::mutex::*;
|
||||
pub use ads1256_types::adcon::{ClockOut, Gain, Sdcs};
|
||||
pub use ads1256_types::drate::DataRate;
|
||||
pub use ads1256_types::gpio::{DState, DioDirection};
|
||||
pub use ads1256_types::mux::MuxInput;
|
||||
pub use ads1256_types::status::{AutoCal, BitOrder, Buffer};
|
||||
pub use ads1256_types::*;
|
||||
|
||||
pub use embedded_hal::digital::OutputPin;
|
||||
pub use embedded_hal::spi::SpiBus;
|
||||
pub use embedded_hal_async::digital::Wait;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Ads1256 ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
/// **WARNING:** All [Ads1256] methods only ever wait in the middle of a multi-command method, in
|
||||
/// some cases you may need to use [BlockingDelay::t11_1_delay] or [BlockingDelay::t11_2_delay]
|
||||
/// between methods that issue commands if they are done one immediately following the other.
|
||||
pub struct Ads1256<DelayerT, SST, DrdyT> {
|
||||
pub delayer: DelayerT,
|
||||
slave_select: SST,
|
||||
pub data_ready: DrdyT,
|
||||
}
|
||||
|
||||
impl<DelayerT, SST, DrdyT> Ads1256<DelayerT, SST, DrdyT>
|
||||
where
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
{
|
||||
//----- New ----------------------------------
|
||||
#[inline(always)]
|
||||
pub fn new(delayer: DelayerT, slave_select: SST, data_ready: DrdyT) -> Self {
|
||||
Self {
|
||||
delayer,
|
||||
slave_select,
|
||||
data_ready,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
use crate::{AdControl, Ads1256, BlockingDelay, CalibrationCommand, Conversion,
|
||||
DataRate, Multiplexer, Status,
|
||||
};
|
||||
use core::ops::DerefMut;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
|
||||
use physical_node::GPIO_ERROR_MSG;
|
||||
use physical_node::spi::end_spi;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi;
|
||||
use embedded_hal::spi::SpiBus;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
|
||||
impl<DelayerT, SST, DrdyT> Ads1256<DelayerT, SST, DrdyT>
|
||||
where
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
{
|
||||
/// Functionally the same as [Ads1256::cmd_read_data] but exercises fine-grained control
|
||||
/// over the [Mutex] of a [SpiBus] in cases where one is used. This function will unlock the
|
||||
/// [Mutex] while it is waiting for data from the ADS1256.
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Wait for data_ready to go low
|
||||
/// 1. Lock mutex, mutably borrow SPI
|
||||
/// 1. Read the conversion value
|
||||
#[inline]
|
||||
pub async fn cmd_read_data_m<MutexT: RawMutex, SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi: &Mutex<MutexT, SpiT>,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
let mut spi_guard = spi.lock().await;
|
||||
let spi = spi_guard.deref_mut();
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
let spi_op_result = self.raw_cmd_read_data(spi);
|
||||
|
||||
end_spi(&mut self.slave_select, spi, spi_op_result)
|
||||
}
|
||||
|
||||
/// Functionally the same as [Ads1256::autocal_convert] but exercises fine-grained control
|
||||
/// over the [Mutex] of a [SpiBus] in cases where one is used. This function will unlock the
|
||||
/// [Mutex] while it is waiting for data from the ADS1256.
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Switch inputs and optionally adjust different configuration parameters.
|
||||
/// 1. If only the input was switched without configuration changes.
|
||||
/// 1. Issue sync command followed by wakeup command
|
||||
/// 1. Else, auto-calibration will take place
|
||||
/// 1. Wait for data_ready low
|
||||
/// 1. RDATA command (read the conversion value)
|
||||
/// 1. Optionally enter standby mode
|
||||
///
|
||||
/// **WARNING:** Auto-calibration must be enabled for intended functionality when changing
|
||||
/// [Status], [AdControl], or [DataRate]. Furthermore if setting [Status] or [AdControl], their
|
||||
/// [Buffer] and [Gain] settings must be modified respectively to trigger auto-calibration.
|
||||
#[inline]
|
||||
pub async fn autocal_convert_m<MutexT: RawMutex, SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi_mutex: &Mutex<MutexT, SpiT>,
|
||||
input: Multiplexer,
|
||||
status: Option<Status>,
|
||||
ad_control: Option<AdControl>,
|
||||
data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
// Acquire SPI lock
|
||||
let mut spi_guard = spi_mutex.lock().await;
|
||||
let spi = spi_guard.deref_mut();
|
||||
// Slave select low
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
match (status, ad_control, data_rate) {
|
||||
// Only modifying the multiplexer, not changing any configuration
|
||||
(None, None, None) => {
|
||||
self._none_config(spi, input)?;
|
||||
self._manual_conversion_init(spi)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer) and changing multiplexer
|
||||
(Some(status), None, None) => {
|
||||
self._status_config(spi, input, status)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying AD control (gain) and changing multiplexer
|
||||
(None, Some(ad_control), None) => {
|
||||
self._ad_config(spi, input, ad_control)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying data rate and change multiplexer
|
||||
(None, None, Some(data_rate)) => {
|
||||
self._drate_config(spi, input, data_rate)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), and changing multiplexer
|
||||
(Some(status), Some(ad_control), None) => {
|
||||
self._status_ad_config(spi, input, status, ad_control)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), data rate, and changing multiplexer
|
||||
(Some(status), None, Some(data_rate)) => {
|
||||
self._status_drate_config(spi, input, status, data_rate)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying AD control (gain), data rate, and changing multiplexer
|
||||
(None, Some(ad_control), Some(data_rate)) => {
|
||||
self._ad_drate_config(spi, input, ad_control, data_rate)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), data rate, and changing
|
||||
// multiplexer
|
||||
(Some(status), Some(ad_control), Some(data_rate)) => {
|
||||
self._all_config(spi, input, status, ad_control, data_rate)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionally the same as [Ads1256::loadcal_convert] but exercises fine-grained control
|
||||
/// over the [Mutex] of a [SpiBus] in cases where one is used. This function will unlock the
|
||||
/// [Mutex] while it is waiting for data from the ADS1256.
|
||||
///
|
||||
/// Action sequence:
|
||||
/// 1. Switch inputs and optionally adjust different configuration parameters.
|
||||
/// 1. Issue sync command followed by wakeup command
|
||||
/// 1. Wait for data_ready low
|
||||
/// 1. RDATA command (read the conversion value)
|
||||
/// 1. Optionally enter standby mode
|
||||
///
|
||||
/// **WARNING:** Auto-calibration must be disabled for intended functionality when changing
|
||||
/// [Status], [AdControl], or [DataRate].
|
||||
#[inline]
|
||||
pub async fn loadcal_convert_m<MutexT: RawMutex, SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi_mutex: &Mutex<MutexT, SpiT>,
|
||||
input: Multiplexer,
|
||||
calibration: Option<&CalibrationCommand>,
|
||||
status: Option<Status>,
|
||||
ad_control: Option<AdControl>,
|
||||
data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
// Acquire SPI lock
|
||||
let mut spi_guard = spi_mutex.lock().await;
|
||||
let spi = spi_guard.deref_mut();
|
||||
// Slave select low
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
match (status, ad_control, data_rate) {
|
||||
// Only modifying the multiplexer, not changing any configuration
|
||||
(None, None, None) => {
|
||||
self._none_config(spi, input)?;
|
||||
self._manual_conversion_init(spi)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer) and changing multiplexer
|
||||
(Some(status), None, None) => {
|
||||
self._status_config(spi, input, status)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying AD control (gain) and changing multiplexer
|
||||
(None, Some(ad_control), None) => {
|
||||
self._ad_config(spi, input, ad_control)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying data rate and change multiplexer
|
||||
(None, None, Some(data_rate)) => {
|
||||
self._drate_config(spi, input, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), and changing multiplexer
|
||||
(Some(status), Some(ad_control), None) => {
|
||||
self._status_ad_config(spi, input, status, ad_control)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), data rate, and changing multiplexer
|
||||
(Some(status), None, Some(data_rate)) => {
|
||||
self._status_drate_config(spi, input, status, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying AD control (gain), data rate, and changing multiplexer
|
||||
(None, Some(ad_control), Some(data_rate)) => {
|
||||
self._ad_drate_config(spi, input, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
// Modifying status (toggle buffer), AD control (gain), data rate, and changing
|
||||
// multiplexer
|
||||
(Some(status), Some(ad_control), Some(data_rate)) => {
|
||||
self._all_config(spi, input, status, ad_control, data_rate)?;
|
||||
self._loadcal_init(spi, calibration)?;
|
||||
spi.flush()?;
|
||||
drop(spi_guard);
|
||||
self._read_when_rdy_m(spi_mutex, standby).await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn loadcal_convert_next_m<MutexT: RawMutex, SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi_mutex: &Mutex<MutexT, SpiT>,
|
||||
next_input: Multiplexer,
|
||||
next_calibration: Option<&CalibrationCommand>,
|
||||
next_status: Option<Status>,
|
||||
next_ad_control: Option<AdControl>,
|
||||
next_data_rate: Option<DataRate>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self._loadcal_convert_next(
|
||||
spi_mutex.lock().await.deref_mut(),
|
||||
next_input,
|
||||
next_calibration,
|
||||
next_status,
|
||||
next_ad_control,
|
||||
next_data_rate,
|
||||
standby,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn _read_when_rdy_m<MutexT: RawMutex, SpiT: SpiBus>(
|
||||
&mut self,
|
||||
spi_mutex: &Mutex<MutexT, SpiT>,
|
||||
standby: bool,
|
||||
) -> Result<Conversion, <SpiT as spi::ErrorType>::Error> {
|
||||
self.slave_select.set_high().expect(GPIO_ERROR_MSG);
|
||||
// Wait for data ready low
|
||||
self.data_ready.wait_for_low().await.expect(GPIO_ERROR_MSG);
|
||||
self.slave_select.set_low().expect(GPIO_ERROR_MSG);
|
||||
// Reacquire lock on SPI mutex and read mux conversion
|
||||
self._read_mux_conversion(spi_mutex.lock().await.deref_mut(), standby)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "ads1256-types"
|
||||
description = "ADS1256 data types."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies.physical]
|
||||
path = "../../.."
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
optional = true
|
||||
@@ -1,304 +0,0 @@
|
||||
pub const REFERENCE_VOLTS: f32 = 2.5;
|
||||
pub const MAX_CONVERSION_VALUE: i32 = 8_388_607;
|
||||
|
||||
pub mod defaults {
|
||||
//----- Public ----------------------------------
|
||||
pub const SPI_CLK_HZ: u32 = 1_920_000;
|
||||
}
|
||||
|
||||
pub mod opcodes {
|
||||
/// Completes SYNC and Exits Standby Mode.
|
||||
pub const WAKEUP: u8 = 0b00000000;
|
||||
/// Read Data.
|
||||
pub const RDATA: u8 = 0b00000001;
|
||||
/// Read Data Continuously.
|
||||
pub const RDATAC: u8 = 0b00000011;
|
||||
/// Stop Read Data Continuously
|
||||
pub const SDATAC: u8 = 0b00001111;
|
||||
/// Read from register at rrrr. The command is only the first 4 bits, the last 4 bits need to be
|
||||
/// changed to the register address.
|
||||
pub const RREG: u8 = 0b0001_0000;
|
||||
/// Write to register at rrrr. The command is only the first 4 bits, the last 4 bits need to be
|
||||
/// changed to the register address.
|
||||
pub const WREG: u8 = 0b0101_0000;
|
||||
/// Offset and Gain Self-Calibration.
|
||||
pub const SELFCAL: u8 = 0b11110000;
|
||||
/// Offset Self-Calibration.
|
||||
pub const SELFOCAL: u8 = 0b11110001;
|
||||
/// Gain Self-Calibration.
|
||||
pub const SELFGCAL: u8 = 0b11110010;
|
||||
/// System Offset Calibration.
|
||||
pub const SYSOCAL: u8 = 0b11110011;
|
||||
/// System Gain Calibration.
|
||||
pub const SYSGCAL: u8 = 0b11110100;
|
||||
/// Synchronize the A/D Conversion.
|
||||
pub const SYNC: u8 = 0b11111100;
|
||||
/// Begin Standby Mode.
|
||||
pub const STANDBY: u8 = 0b11111101;
|
||||
/// Reset to Power-Up Values.
|
||||
pub const RESET: u8 = 0b11111110;
|
||||
}
|
||||
|
||||
/// Status register.
|
||||
pub mod status {
|
||||
/// Address of the STATUS register.
|
||||
pub const ADDRESS: u8 = 0b0000_0000;
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Buffer {
|
||||
Enabled = 0b000000_1_0,
|
||||
Disabled = 0,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub const MASK: u8 = 0b000000_1_0;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum AutoCal {
|
||||
Enabled = 0b00000_1_00,
|
||||
Disabled = 0,
|
||||
}
|
||||
|
||||
impl AutoCal {
|
||||
pub const MASK: u8 = 0b00000_1_00;
|
||||
}
|
||||
|
||||
/// Input data is always shifted in most significant byte and bit first. Output data is always
|
||||
/// shifted out most significant byte first. The [BitOrder] only controls the bit order of the
|
||||
/// output data within the byte.
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum BitOrder {
|
||||
/// Most significant bit first.
|
||||
MostSigFirst = 0,
|
||||
/// Least significant bit first.
|
||||
LeastSigFirst = 0b0000_1_000,
|
||||
}
|
||||
|
||||
impl BitOrder {
|
||||
pub const MASK: u8 = 0b0000_1_000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Input multiplexer control register.
|
||||
pub mod mux {
|
||||
/// address of the MUX register.
|
||||
pub const ADDRESS: u8 = 0b0000_0001;
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum MuxInput {
|
||||
AIn0 = 0b0000_0000,
|
||||
AIn1 = 0b0000_0001,
|
||||
AIn2 = 0b0000_0010,
|
||||
AIn3 = 0b0000_0011,
|
||||
AIn4 = 0b0000_0100,
|
||||
AIn5 = 0b0000_0101,
|
||||
AIn6 = 0b0000_0110,
|
||||
AIn7 = 0b0000_0111,
|
||||
Common = 0b0000_1000,
|
||||
}
|
||||
|
||||
impl MuxInput {
|
||||
pub const NEGATIVE_MASK: u8 = 0b0000_1111;
|
||||
pub const POSITIVE_MASK: u8 = 0b1111_0000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A/D control register.
|
||||
pub mod adcon {
|
||||
/// Address of the ADCON register.
|
||||
pub const ADDRESS: u8 = 0b0000_0010;
|
||||
|
||||
//TODO: Fitting the value and encoding in 16 bits can probably be done more cleanly if enum
|
||||
// variants become types in a later version of Rust.
|
||||
/// **Warning:** casting to an integer will likely not yield expected result. Use `value()` or
|
||||
/// `encoding()` methods instead.
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Gain {
|
||||
X1 = 0b00000_000,
|
||||
X2 = 0b00000_001,
|
||||
X4 = 0b00000_010,
|
||||
X8 = 0b00000_011,
|
||||
X16 = 0b00000_100,
|
||||
X32 = 0b00000_101,
|
||||
X64 = 0b00000_110,
|
||||
}
|
||||
|
||||
impl Gain {
|
||||
/// Value to use binary & operator on the full ADCON byte to isolate the gain.
|
||||
pub const MASK: u8 = 0b00000_111;
|
||||
pub const ALT_X64: u8 = 0b00000_111;
|
||||
|
||||
/// The integer value of this [Gain].
|
||||
///
|
||||
/// ```
|
||||
/// use ads1256_types::Gain;
|
||||
///
|
||||
/// assert_eq!(Gain::X1.value(), 1);
|
||||
/// assert_eq!(Gain::X2.value(), 2);
|
||||
/// assert_eq!(Gain::X4.value(), 4);
|
||||
/// assert_eq!(Gain::X8.value(), 8);
|
||||
/// assert_eq!(Gain::X16.value(), 16);
|
||||
/// assert_eq!(Gain::X32.value(), 32);
|
||||
/// assert_eq!(Gain::X64.value(), 64);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub const fn value(self) -> u8 {
|
||||
// Multiply 1 by 2 the number of times specified in the encoding (e.g. for gain of 32,
|
||||
// (1 * 2) 5 times (5 is the encoding for 32) is 32).
|
||||
1 << (self as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sensor detect current sources.
|
||||
/// For testing that sensor is still connected and able to pass current.
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Sdcs {
|
||||
Off = 0b000_00_000,
|
||||
/// 0.5µA
|
||||
C05 = 0b000_01_000,
|
||||
/// 2µA
|
||||
C2 = 0b000_10_000,
|
||||
/// 10µA
|
||||
C10 = 0b000_11_000,
|
||||
}
|
||||
|
||||
impl Sdcs {
|
||||
/// Value to use binary & operator on the full ADCON byte to isolate the SDCS.
|
||||
pub const MASK: u8 = 0b000_11_000;
|
||||
}
|
||||
|
||||
/// Ads1256 master clock cycle frequency outputted through GPIO.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ClockOut {
|
||||
Off = 0b0_00_00000,
|
||||
/// Equal to clock in frequency.
|
||||
ClkIn = 0b0_01_00000,
|
||||
/// Equal to clock in frequency / 2.
|
||||
ClkIn_2 = 0b0_10_00000,
|
||||
/// Equal to clock in frequency / 4.
|
||||
ClkIn_4 = 0b0_11_00000,
|
||||
}
|
||||
|
||||
impl ClockOut {
|
||||
/// Value to use binary & operator on the full ADCON byte to isolate the clock out
|
||||
/// frequency.
|
||||
pub const MASK: u8 = 0b0_11_00000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A/D data rate register.
|
||||
pub mod drate {
|
||||
/// Address of the DRATE register.
|
||||
pub const ADDRESS: u8 = 0b0000_0011;
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DataRate {
|
||||
Sps2_5 = 0b00000011,
|
||||
Sps5 = 0b00010011,
|
||||
Sps10 = 0b00100011,
|
||||
Sps15 = 0b00110011,
|
||||
Sps25 = 0b01000011,
|
||||
Sps30 = 0b01010011,
|
||||
Sps50 = 0b01100011,
|
||||
Sps60 = 0b01110010,
|
||||
Sps100 = 0b10000010,
|
||||
Sps500 = 0b10010010,
|
||||
Sps1000 = 0b10100001,
|
||||
Sps2000 = 0b10110000,
|
||||
Sps3750 = 0b11000000,
|
||||
Sps7500 = 0b11010000,
|
||||
Sps15000 = 0b11100000,
|
||||
Sps30000 = 0b11110000,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod gpio {
|
||||
/// Address of the IO register.
|
||||
pub const ADDRESS: u8 = 0b0000_0100;
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DioDirection {
|
||||
Output = 0,
|
||||
Input = 1,
|
||||
}
|
||||
|
||||
impl DioDirection {
|
||||
pub const ALL_MASK: u8 = 0b1111_0000;
|
||||
pub const IO0_MASK: u8 = 0b10000;
|
||||
pub const IO1_MASK: u8 = 0b100000;
|
||||
pub const IO2_MASK: u8 = 0b1000000;
|
||||
pub const IO3_MASK: u8 = 0b10000000;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DState {
|
||||
Low = 0,
|
||||
High = 1,
|
||||
}
|
||||
|
||||
impl DState {
|
||||
pub const ALL_MASK: u8 = 0b0000_1111;
|
||||
pub const IO0_MASK: u8 = 0b1;
|
||||
pub const IO1_MASK: u8 = 0b10;
|
||||
pub const IO2_MASK: u8 = 0b100;
|
||||
pub const IO3_MASK: u8 = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Offset calibration byte 0.
|
||||
pub mod ofc0 {
|
||||
/// Address of the OFC0 register.
|
||||
pub const ADDRESS: u8 = 0b0000_0101;
|
||||
}
|
||||
|
||||
/// Offset calibration byte 1.
|
||||
pub mod ofc1 {
|
||||
/// Address of the OFC1 register.
|
||||
pub const ADDRESS: u8 = 0b0000_0110;
|
||||
}
|
||||
|
||||
/// Offset calibration byte 2.
|
||||
pub mod ofc2 {
|
||||
/// Address of the OFC2 register.
|
||||
pub const ADDRESS: u8 = 0b0000_0111;
|
||||
}
|
||||
|
||||
/// Full scale calibration byte 0
|
||||
pub mod fsc0 {
|
||||
/// Address of the FSC0 register.
|
||||
pub const ADDRESS: u8 = 0b0000_1000;
|
||||
}
|
||||
|
||||
/// Full scale calibration byte 0
|
||||
pub mod fsc1 {
|
||||
/// Address of the FSC1 register.
|
||||
pub const ADDRESS: u8 = 0b0000_1001;
|
||||
}
|
||||
|
||||
/// Full scale calibration byte 0
|
||||
pub mod fsc2 {
|
||||
/// Address of the FSC2 register.
|
||||
pub const ADDRESS: u8 = 0b0000_1010;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use crate::{Gain, MAX_CONVERSION_VALUE, REFERENCE_VOLTS};
|
||||
use physical::quantity::Volts;
|
||||
|
||||
/// Raw digital value resulting from the conversion of an analog signal by the ADS1256.
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Conversion(pub i32);
|
||||
|
||||
impl Conversion {
|
||||
/// Process the conversion byte reading and return the raw conversion value.
|
||||
#[inline]
|
||||
pub fn from_reading(reading: [u8; 3]) -> Self {
|
||||
let lsb = reading[2] as i32;
|
||||
let mb = reading[1] as i32;
|
||||
let msb = (reading[0] as i8) as i32; // double cast for sign extension
|
||||
|
||||
Self((msb << 16) | (mb << 8) | lsb)
|
||||
}
|
||||
|
||||
pub fn to_voltage(self, gain: Gain) -> Volts<f32> {
|
||||
let volts = ((2.0 * REFERENCE_VOLTS) / (MAX_CONVERSION_VALUE as f32))
|
||||
* (self.0 as f32 / gain.value() as f32);
|
||||
|
||||
Volts(volts)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Conversion {
|
||||
#[inline(always)]
|
||||
fn from(value: i32) -> Self {
|
||||
Conversion(value)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#![no_std]
|
||||
|
||||
mod constants;
|
||||
mod registers;
|
||||
pub mod standard;
|
||||
mod conversion;
|
||||
|
||||
pub use crate::constants::adcon::{ClockOut, Gain, Sdcs};
|
||||
pub use crate::constants::drate::DataRate;
|
||||
pub use crate::constants::gpio::{DState, DioDirection};
|
||||
pub use crate::constants::mux::MuxInput;
|
||||
pub use crate::constants::status::{AutoCal, BitOrder, Buffer};
|
||||
pub use crate::constants::*;
|
||||
pub use crate::registers::*;
|
||||
pub use crate::conversion::*;
|
||||
@@ -1,645 +0,0 @@
|
||||
use crate::{
|
||||
ofc0, opcodes, AutoCal, BitOrder, Buffer, ClockOut, DState, DataRate, DioDirection, Gain,
|
||||
MuxInput, Sdcs,
|
||||
};
|
||||
use core::mem;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Calibration ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct OffsetCalibration(pub [u8; 3]);
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct GainCalibration(pub [u8; 3]);
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct AllCalibration {
|
||||
pub offset: OffsetCalibration,
|
||||
pub gain: GainCalibration,
|
||||
}
|
||||
|
||||
impl From<[u8; 6]> for AllCalibration {
|
||||
/// This array must be formatted as offset cal bytes [0..2] followed by gain cal bytes [0..2]
|
||||
/// for resulting [AllCalibration] to be meaningful.
|
||||
#[inline(always)]
|
||||
fn from(array: [u8; 6]) -> Self {
|
||||
unsafe { mem::transmute::<[u8; 6], Self>(array) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; 6]> for AllCalibration {
|
||||
#[inline(always)]
|
||||
fn into(self) -> [u8; 6] {
|
||||
unsafe { mem::transmute::<Self, [u8; 6]>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
/// The complete formatted command to write all calibration values to ADS1256 registers.
|
||||
/// Prepending the write command bytes to the calibration value is a fairly costly operation and in
|
||||
/// practice the only real use of calibration values is to store them and write them to the ADS1256.
|
||||
/// When multiplexing inputs we store calibration infrequently and write it frequently so it's
|
||||
/// better to take the overhead of prepending the command bytes when we store, not when we write.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct CalibrationCommand {
|
||||
command: [u8; 2],
|
||||
calibration: AllCalibration,
|
||||
}
|
||||
|
||||
impl From<AllCalibration> for CalibrationCommand {
|
||||
fn from(calibration: AllCalibration) -> Self {
|
||||
Self {
|
||||
command: [opcodes::WREG | ofc0::ADDRESS, 5],
|
||||
calibration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; 8]> for CalibrationCommand {
|
||||
#[inline(always)]
|
||||
fn into(self) -> [u8; 8] {
|
||||
unsafe { mem::transmute::<Self, [u8; 8]>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<&'a [u8]> for &'a CalibrationCommand {
|
||||
#[inline(always)]
|
||||
fn into(self) -> &'a [u8] {
|
||||
unsafe { mem::transmute::<Self, &'a [u8; 8]>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Config ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Config {
|
||||
pub status: Status,
|
||||
pub multiplexer: Multiplexer,
|
||||
pub ad_control: AdControl,
|
||||
pub data_rate: DataRate,
|
||||
pub digital_io: DigitalIo,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// We want this internal and are only using it in [Ads1256::read_config].
|
||||
#[inline]
|
||||
pub const fn from_bytes(bytes: [u8; 5]) -> Self {
|
||||
Self {
|
||||
status: Status(bytes[0]),
|
||||
multiplexer: Multiplexer(bytes[1]),
|
||||
ad_control: AdControl(bytes[2]),
|
||||
data_rate: DataRate::from_byte(bytes[3]),
|
||||
digital_io: DigitalIo(bytes[4]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Config {
|
||||
#[rustfmt::skip]
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"Config {{\n\
|
||||
\t{},\n\
|
||||
\t{},\n\
|
||||
\t{},\n\
|
||||
\tdate_rate: {},\n\
|
||||
\t{}\n\
|
||||
}}",
|
||||
self.status,
|
||||
self.multiplexer,
|
||||
self.ad_control,
|
||||
self.data_rate,
|
||||
self.digital_io
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Status ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Status(pub u8);
|
||||
|
||||
impl Status {
|
||||
/// Creates a new [Status] with all writable properties explicitly set.
|
||||
#[inline(always)]
|
||||
pub const fn setting(buffer: Buffer, auto_calibration: AutoCal, bit_order: BitOrder) -> Status {
|
||||
Status(buffer as u8 | auto_calibration as u8 | bit_order as u8)
|
||||
}
|
||||
|
||||
/// returns a copy of the [Status] with the buffer setting replaced by the [setting] parameter.
|
||||
#[inline(always)]
|
||||
pub const fn with_buffer(self, setting: Buffer) -> Status {
|
||||
let zeroed_setting = self.0 & !Buffer::MASK;
|
||||
Status(zeroed_setting | setting as u8)
|
||||
}
|
||||
|
||||
/// returns a copy of the [Status] with the auto-calibration setting replaced by the [setting]
|
||||
/// parameter.
|
||||
#[inline(always)]
|
||||
pub const fn with_auto_cal(self, setting: AutoCal) -> Status {
|
||||
let zeroed_setting = self.0 & !AutoCal::MASK;
|
||||
Status(zeroed_setting | setting as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn data_ready(self) -> bool {
|
||||
const MASK: u8 = 0b1;
|
||||
unsafe { mem::transmute::<u8, bool>(self.0 & MASK) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn buffer(self) -> Buffer {
|
||||
unsafe { mem::transmute::<u8, Buffer>(self.0 & Buffer::MASK) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn auto_calibration(self) -> AutoCal {
|
||||
unsafe { mem::transmute::<u8, AutoCal>(self.0 & AutoCal::MASK) }
|
||||
}
|
||||
|
||||
/// Data output bit order.
|
||||
///
|
||||
/// Input data is always shifted in most significant byte and bit first. Output data is always
|
||||
/// shifted out most significant byte first. The ORDER bit only controls the bit order of the
|
||||
/// output data within the byte.
|
||||
#[inline(always)]
|
||||
pub const fn data_output_bit_order(self) -> BitOrder {
|
||||
unsafe { mem::transmute::<u8, BitOrder>(self.0 & BitOrder::MASK) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn id(self) -> u8 {
|
||||
self.0 >> 4
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Status {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Status {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"Status(data_ready: {}, buffer: {}, auto_calibration: {}, data_output_bit_order: {}, \
|
||||
id: {})",
|
||||
self.data_ready(),
|
||||
self.buffer(),
|
||||
self.auto_calibration(),
|
||||
self.data_output_bit_order(),
|
||||
self.id()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
#[inline(always)]
|
||||
pub const fn is_enabled(self) -> bool {
|
||||
match self {
|
||||
Buffer::Enabled => true,
|
||||
Buffer::Disabled => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AutoCal {
|
||||
#[inline(always)]
|
||||
pub const fn is_enabled(self) -> bool {
|
||||
match self {
|
||||
AutoCal::Enabled => true,
|
||||
AutoCal::Disabled => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Input Multiplexer ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Multiplexer(pub u8);
|
||||
|
||||
impl Multiplexer {
|
||||
#[inline(always)]
|
||||
pub const fn setting(positive_input: MuxInput, negative_input: MuxInput) -> Multiplexer {
|
||||
Multiplexer(((positive_input as u8) << 4) | negative_input as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_positive(self, input: MuxInput) -> Multiplexer {
|
||||
let zeroed_positive = self.0 & !MuxInput::POSITIVE_MASK;
|
||||
Multiplexer(zeroed_positive | ((input as u8) << 4))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_negative(self, input: MuxInput) -> Multiplexer {
|
||||
let zeroed_negative = self.0 & !MuxInput::NEGATIVE_MASK;
|
||||
Multiplexer(zeroed_negative | input as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn positive_input(self) -> MuxInput {
|
||||
const A_IN0: u8 = (MuxInput::AIn0 as u8) << 4;
|
||||
const A_IN1: u8 = (MuxInput::AIn1 as u8) << 4;
|
||||
const A_IN2: u8 = (MuxInput::AIn2 as u8) << 4;
|
||||
const A_IN3: u8 = (MuxInput::AIn3 as u8) << 4;
|
||||
const A_IN4: u8 = (MuxInput::AIn4 as u8) << 4;
|
||||
const A_IN5: u8 = (MuxInput::AIn5 as u8) << 4;
|
||||
const A_IN6: u8 = (MuxInput::AIn6 as u8) << 4;
|
||||
const A_IN7: u8 = (MuxInput::AIn7 as u8) << 4;
|
||||
|
||||
match self.0 & MuxInput::POSITIVE_MASK {
|
||||
A_IN0 => MuxInput::AIn0,
|
||||
A_IN1 => MuxInput::AIn1,
|
||||
A_IN2 => MuxInput::AIn2,
|
||||
A_IN3 => MuxInput::AIn3,
|
||||
A_IN4 => MuxInput::AIn4,
|
||||
A_IN5 => MuxInput::AIn5,
|
||||
A_IN6 => MuxInput::AIn6,
|
||||
A_IN7 => MuxInput::AIn7,
|
||||
_ => MuxInput::Common,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn negative_input(self) -> MuxInput {
|
||||
const A_IN0: u8 = MuxInput::AIn0 as u8;
|
||||
const A_IN1: u8 = MuxInput::AIn1 as u8;
|
||||
const A_IN2: u8 = MuxInput::AIn2 as u8;
|
||||
const A_IN3: u8 = MuxInput::AIn3 as u8;
|
||||
const A_IN4: u8 = MuxInput::AIn4 as u8;
|
||||
const A_IN5: u8 = MuxInput::AIn5 as u8;
|
||||
const A_IN6: u8 = MuxInput::AIn6 as u8;
|
||||
const A_IN7: u8 = MuxInput::AIn7 as u8;
|
||||
|
||||
match self.0 & MuxInput::NEGATIVE_MASK {
|
||||
A_IN0 => MuxInput::AIn0,
|
||||
A_IN1 => MuxInput::AIn1,
|
||||
A_IN2 => MuxInput::AIn2,
|
||||
A_IN3 => MuxInput::AIn3,
|
||||
A_IN4 => MuxInput::AIn4,
|
||||
A_IN5 => MuxInput::AIn5,
|
||||
A_IN6 => MuxInput::AIn6,
|
||||
A_IN7 => MuxInput::AIn7,
|
||||
_ => MuxInput::Common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Multiplexer {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Multiplexer {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"Multiplexer(positive_input: {}, negative_input: {})",
|
||||
self.positive_input(),
|
||||
self.negative_input()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- A/D Control ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct AdControl(pub u8);
|
||||
|
||||
impl AdControl {
|
||||
#[inline(always)]
|
||||
pub const fn setting(
|
||||
gain: Gain,
|
||||
sensor_detect_current: Sdcs,
|
||||
clock_out: ClockOut,
|
||||
) -> AdControl {
|
||||
AdControl(gain as u8 | sensor_detect_current as u8 | clock_out as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_gain(self, setting: Gain) -> AdControl {
|
||||
let zeroed_setting = self.0 & !Gain::MASK;
|
||||
AdControl(zeroed_setting | setting as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_sensor_detect_current(self, setting: Sdcs) -> AdControl {
|
||||
let zeroed_setting = self.0 & !Sdcs::MASK;
|
||||
AdControl(zeroed_setting | setting as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_clock_out(self, setting: ClockOut) -> AdControl {
|
||||
let zeroed_setting = self.0 & !ClockOut::MASK;
|
||||
AdControl(zeroed_setting | setting as u8)
|
||||
}
|
||||
|
||||
/// Programmable gain amplifier setting.
|
||||
/// *Note: On the ADS1256 0b110 and 0b111 both represent a gain amplification setting of X64 but
|
||||
/// in this driver it will always be stored as 0b110.*
|
||||
#[inline]
|
||||
pub const fn gain(self) -> Gain {
|
||||
let masked = self.0 & Gain::MASK;
|
||||
if masked == Gain::ALT_X64 {
|
||||
Gain::X64
|
||||
} else {
|
||||
unsafe { mem::transmute::<u8, Gain>(masked) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Sensor detect current sources. For testing that sensor is still connected and able to pass
|
||||
/// current.
|
||||
#[inline(always)]
|
||||
pub const fn sensor_detect_current(self) -> Sdcs {
|
||||
unsafe { mem::transmute::<u8, Sdcs>(self.0 & Sdcs::MASK) }
|
||||
}
|
||||
|
||||
/// Ads1256 master clock cycle frequency outputted through GPIO.
|
||||
#[inline(always)]
|
||||
pub const fn clock_out(self) -> ClockOut {
|
||||
unsafe { mem::transmute::<u8, ClockOut>(self.0 & ClockOut::MASK) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for AdControl {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for AdControl {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"AdControl(gain: {}, sensor_detect_current: {}, clock_out: {})",
|
||||
self.gain(),
|
||||
self.sensor_detect_current(),
|
||||
self.clock_out()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Data Rate ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
impl DataRate {
|
||||
pub const fn from_byte(byte: u8) -> DataRate {
|
||||
const SPS2_5: u8 = DataRate::Sps2_5 as u8;
|
||||
const SPS5: u8 = DataRate::Sps5 as u8;
|
||||
const SPS10: u8 = DataRate::Sps10 as u8;
|
||||
const SPS15: u8 = DataRate::Sps15 as u8;
|
||||
const SPS25: u8 = DataRate::Sps25 as u8;
|
||||
const SPS30: u8 = DataRate::Sps30 as u8;
|
||||
const SPS50: u8 = DataRate::Sps50 as u8;
|
||||
const SPS60: u8 = DataRate::Sps60 as u8;
|
||||
const SPS100: u8 = DataRate::Sps100 as u8;
|
||||
const SPS500: u8 = DataRate::Sps500 as u8;
|
||||
const SPS1000: u8 = DataRate::Sps1000 as u8;
|
||||
const SPS2000: u8 = DataRate::Sps2000 as u8;
|
||||
const SPS3750: u8 = DataRate::Sps3750 as u8;
|
||||
const SPS7500: u8 = DataRate::Sps7500 as u8;
|
||||
const SPS15000: u8 = DataRate::Sps15000 as u8;
|
||||
const SPS30000: u8 = DataRate::Sps30000 as u8;
|
||||
|
||||
match byte {
|
||||
SPS2_5 => DataRate::Sps2_5,
|
||||
SPS5 => DataRate::Sps5,
|
||||
SPS10 => DataRate::Sps10,
|
||||
SPS15 => DataRate::Sps15,
|
||||
SPS25 => DataRate::Sps25,
|
||||
SPS30 => DataRate::Sps30,
|
||||
SPS50 => DataRate::Sps50,
|
||||
SPS60 => DataRate::Sps60,
|
||||
SPS100 => DataRate::Sps100,
|
||||
SPS500 => DataRate::Sps500,
|
||||
SPS1000 => DataRate::Sps1000,
|
||||
SPS2000 => DataRate::Sps2000,
|
||||
SPS3750 => DataRate::Sps3750,
|
||||
SPS7500 => DataRate::Sps7500,
|
||||
SPS15000 => DataRate::Sps15000,
|
||||
SPS30000 => DataRate::Sps30000,
|
||||
_ => panic!("Invalid ADS1256 data rate."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- GPIO ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DigitalIo(pub u8);
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DigitalIoState(pub u8);
|
||||
|
||||
impl DigitalIoState {
|
||||
#[inline]
|
||||
pub const fn new(io0: DState, io1: DState, io2: DState, io3: DState) -> DigitalIoState {
|
||||
DigitalIoState(io0 as u8 | ((io1 as u8) << 1) | ((io2 as u8) << 2) | ((io3 as u8) << 3))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn default() -> DigitalIoState {
|
||||
DigitalIoState::new(DState::Low, DState::Low, DState::Low, DState::Low)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for DigitalIoState {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DigitalIoDirection(pub u8);
|
||||
|
||||
impl DigitalIoDirection {
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
io0: DioDirection,
|
||||
io1: DioDirection,
|
||||
io2: DioDirection,
|
||||
io3: DioDirection,
|
||||
) -> DigitalIoDirection {
|
||||
DigitalIoDirection(
|
||||
((io0 as u8) << 4) | ((io1 as u8) << 5) | ((io2 as u8) << 6) | ((io3 as u8) << 7),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn default() -> DigitalIoDirection {
|
||||
DigitalIoDirection::new(
|
||||
DioDirection::Output,
|
||||
DioDirection::Input,
|
||||
DioDirection::Input,
|
||||
DioDirection::Input,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for DigitalIoDirection {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DigitalIo {
|
||||
#[inline(always)]
|
||||
pub const fn setting(io_state: DigitalIoState, io_direction: DigitalIoDirection) -> DigitalIo {
|
||||
DigitalIo(io_state.0 | io_direction.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io_state(self, state: DigitalIoState) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DState::ALL_MASK;
|
||||
DigitalIo(zeroed_setting | state.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io_direction(self, direction: DigitalIoDirection) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DioDirection::ALL_MASK;
|
||||
DigitalIo(zeroed_setting | direction.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io0_state(self, state: DState) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DState::IO0_MASK;
|
||||
DigitalIo(zeroed_setting | state as u8)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io1_state(self, state: DState) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DState::IO1_MASK;
|
||||
DigitalIo(zeroed_setting | ((state as u8) << 1))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io2_state(self, state: DState) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DState::IO2_MASK;
|
||||
DigitalIo(zeroed_setting | ((state as u8) << 2))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn with_io3_state(self, state: DState) -> DigitalIo {
|
||||
let zeroed_setting = self.0 & !DState::IO3_MASK;
|
||||
DigitalIo(zeroed_setting | ((state as u8) << 3))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io0_state(self) -> DState {
|
||||
match self.0 & DState::IO0_MASK {
|
||||
DState::IO0_MASK => DState::High,
|
||||
_ => DState::Low,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io1_state(self) -> DState {
|
||||
match self.0 & DState::IO1_MASK {
|
||||
DState::IO1_MASK => DState::High,
|
||||
_ => DState::Low,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io2_state(self) -> DState {
|
||||
match self.0 & DState::IO2_MASK {
|
||||
DState::IO2_MASK => DState::High,
|
||||
_ => DState::Low,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io3_state(self) -> DState {
|
||||
match self.0 & DState::IO3_MASK {
|
||||
DState::IO3_MASK => DState::High,
|
||||
_ => DState::Low,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io0_direction(self) -> DioDirection {
|
||||
match self.0 & DioDirection::IO0_MASK {
|
||||
DioDirection::IO0_MASK => DioDirection::Input,
|
||||
_ => DioDirection::Output,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io1_direction(self) -> DioDirection {
|
||||
match self.0 & DioDirection::IO1_MASK {
|
||||
DioDirection::IO1_MASK => DioDirection::Input,
|
||||
_ => DioDirection::Output,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io2_direction(self) -> DioDirection {
|
||||
match self.0 & DioDirection::IO2_MASK {
|
||||
DioDirection::IO2_MASK => DioDirection::Input,
|
||||
_ => DioDirection::Output,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn io3_direction(self) -> DioDirection {
|
||||
match self.0 & DioDirection::IO3_MASK {
|
||||
DioDirection::IO3_MASK => DioDirection::Input,
|
||||
_ => DioDirection::Output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for DigitalIo {
|
||||
#[inline(always)]
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for DigitalIo {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"DigitalIo(io0: {}, {}, io1: {}, {}, io2: {}, {}, io3: {}, {})",
|
||||
self.io0_direction(),
|
||||
self.io0_state(),
|
||||
self.io1_direction(),
|
||||
self.io1_state(),
|
||||
self.io2_direction(),
|
||||
self.io2_state(),
|
||||
self.io3_direction(),
|
||||
self.io3_state()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
pub mod input {
|
||||
use crate::{Buffer, Config, DataRate, Gain, Multiplexer, MuxInput};
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Differential {
|
||||
AIn0 = Multiplexer::setting(MuxInput::AIn0, MuxInput::AIn1).0,
|
||||
AIn1 = Multiplexer::setting(MuxInput::AIn2, MuxInput::AIn3).0,
|
||||
AIn2 = Multiplexer::setting(MuxInput::AIn4, MuxInput::AIn5).0,
|
||||
AIn3 = Multiplexer::setting(MuxInput::AIn6, MuxInput::AIn7).0,
|
||||
}
|
||||
|
||||
impl Into<Multiplexer> for Differential {
|
||||
#[inline(always)]
|
||||
fn into(self) -> Multiplexer {
|
||||
Multiplexer(self as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum SingleEnded {
|
||||
AIn0 = Multiplexer::setting(MuxInput::AIn0, MuxInput::Common).0,
|
||||
AIn1 = Multiplexer::setting(MuxInput::AIn1, MuxInput::Common).0,
|
||||
AIn2 = Multiplexer::setting(MuxInput::AIn2, MuxInput::Common).0,
|
||||
AIn3 = Multiplexer::setting(MuxInput::AIn3, MuxInput::Common).0,
|
||||
AIn4 = Multiplexer::setting(MuxInput::AIn4, MuxInput::Common).0,
|
||||
AIn5 = Multiplexer::setting(MuxInput::AIn5, MuxInput::Common).0,
|
||||
AIn6 = Multiplexer::setting(MuxInput::AIn6, MuxInput::Common).0,
|
||||
AIn7 = Multiplexer::setting(MuxInput::AIn7, MuxInput::Common).0,
|
||||
}
|
||||
|
||||
impl Into<Multiplexer> for SingleEnded {
|
||||
#[inline(always)]
|
||||
fn into(self) -> Multiplexer {
|
||||
Multiplexer(self as u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# replace STM32F411CEUx with your chip as listed in `probe-rs chip list`
|
||||
# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
|
||||
runner = "probe-rs run --chip STM32F411CEUx"
|
||||
|
||||
[build]
|
||||
|
||||
@@ -7,11 +7,21 @@ repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies.ads1256]
|
||||
path = "../../drivers/ads1256/driver"
|
||||
[dependencies.physical]
|
||||
path = "../.."
|
||||
features = ["defmt"]
|
||||
features = ["thermocouple_k", "lm35"]
|
||||
[dependencies.physical-node]
|
||||
path = "../../node"
|
||||
features = ["embassy-sync"]
|
||||
[dependencies.physical-ads1256]
|
||||
path = "../../peripheral-components/ads1256/node"
|
||||
features = ["poll"]
|
||||
[dependencies.static_cell]
|
||||
workspace = true
|
||||
[dependencies.ads1256]
|
||||
workspace = true
|
||||
[dependencies.uom]
|
||||
workspace = true
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
[dependencies.defmt-rtt]
|
||||
@@ -31,7 +41,7 @@ workspace = true
|
||||
[dependencies.embassy-time]
|
||||
workspace = true
|
||||
features = ["tick-hz-16_000_000"]
|
||||
[dependencies.embassy-sync]
|
||||
workspace = true
|
||||
[dependencies.panic-probe]
|
||||
workspace = true
|
||||
[dependencies]
|
||||
log = "0.4.20"
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
#![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
|
||||
};
|
||||
use embassy_time::{Delay, Timer};
|
||||
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::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use defmt::{debug, error, info, trace, unwrap};
|
||||
|
||||
const AUTOCAL_CONF: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
multiplexer: Multiplexer::setting(MuxInput::AIn0, MuxInput::Common),
|
||||
ad_control: AdControl::setting(Gain::X2, Sdcs::Off, ClockOut::Off),
|
||||
data_rate: DataRate::Sps2_5,
|
||||
digital_io: DigitalIo::setting(DigitalIoState::default(), DigitalIoDirection::default()),
|
||||
};
|
||||
|
||||
const ADS1256_DELAY: ads1256::DefaultDelay<Delay> = ads1256::DefaultDelay::new(Delay);
|
||||
|
||||
#[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);
|
||||
});
|
||||
}
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_conf = spi::Config::default();
|
||||
spi_conf.mode = spi::MODE_1;
|
||||
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 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 ads_1256 = Ads1256::new(ADS1256_DELAY, select_ads1256, ads1256_data_ready);
|
||||
// single_conversion(&mut spi, &mut ads_1256).await;
|
||||
// ads_1256.delayer.t11_1_delay();
|
||||
// read_continuous(&mut spi, &mut ads_1256).await;
|
||||
cycle_multiplexer(&mut spi, &mut ads_1256).await;
|
||||
}
|
||||
|
||||
async fn single_conversion<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
ads_1256.write_config(spi, AUTOCAL_CONF).unwrap();
|
||||
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)));
|
||||
}
|
||||
|
||||
async fn read_continuous<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
ads_1256.write_config(spi, AUTOCAL_CONF).unwrap();
|
||||
ads_1256.delayer.t11_1_delay();
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
async fn cycle_multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
const INPUT_1: Multiplexer = Multiplexer::setting(MuxInput::AIn1, MuxInput::Common);
|
||||
const INPUT_2: Multiplexer = Multiplexer::setting(MuxInput::AIn2, MuxInput::Common);
|
||||
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();
|
||||
loop {
|
||||
let ad_control = ad_control.with_gain(Gain::X4);
|
||||
let data = ads_1256
|
||||
.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)));
|
||||
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)));
|
||||
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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::cell::Cell;
|
||||
use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
use {embassy_executor as executor, embassy_stm32 as stm32};
|
||||
|
||||
use ads1256::standard::input::SingleEnded;
|
||||
use ads1256::{
|
||||
AdControl, Ads1256, AutoCal, BitOrder, Buffer, ClockOut, Config, DataRate, DigitalIo,
|
||||
DigitalIoDirection, DigitalIoState, Gain, Multiplexer, MuxInput, Sdcs, Status,
|
||||
};
|
||||
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::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::f32;
|
||||
|
||||
use defmt::{debug, error, info, trace, unwrap};
|
||||
use static_cell::StaticCell;
|
||||
use embassy_stm32::peripherals::{PA1, PA3, SPI1};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::pubsub::PubSubChannel;
|
||||
use physical_node::transducer::{Publish, StatefulPublisher};
|
||||
|
||||
const AUTOCAL_CONF: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
multiplexer: Multiplexer::setting(MuxInput::AIn0, MuxInput::Common),
|
||||
ad_control: AdControl::setting(Gain::X2, Sdcs::Off, ClockOut::Off),
|
||||
data_rate: DataRate::Sps2_5,
|
||||
digital_io: DigitalIo::setting(DigitalIoState::default(), DigitalIoDirection::default()),
|
||||
};
|
||||
|
||||
struct Ads1256Delay;
|
||||
|
||||
impl ads1256::BlockingDelay for Ads1256Delay {
|
||||
#[inline]
|
||||
fn t6_delay(&mut self) {
|
||||
Delay.delay_us(1u32);
|
||||
}
|
||||
|
||||
fn t11_1_delay(&mut self) {}
|
||||
|
||||
fn t11_2_delay(&mut self) {}
|
||||
}
|
||||
|
||||
struct Inputs {
|
||||
ai0: StatefulPublisher<f32::ElectricPotential, NoopRawMutex, 10, 1>,
|
||||
ai1: StatefulPublisher<f32::ElectricPotential, NoopRawMutex, 10, 1>,
|
||||
ai2: StatefulPublisher<f32::ElectricPotential, NoopRawMutex, 10, 1>,
|
||||
}
|
||||
|
||||
// Inputs
|
||||
static ANALOG_INPUTS: StaticCell<Inputs> = StaticCell::new();
|
||||
static ADS_1256: StaticCell<Ads1256<Ads1256Delay, Output<PA1>, ExtiInput<PA3>>> = StaticCell::new();
|
||||
static SPI: StaticCell<Spi<SPI1, NoDma, NoDma>> = StaticCell::new();
|
||||
|
||||
#[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);
|
||||
});
|
||||
}
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_conf = spi::Config::default();
|
||||
spi_conf.mode = spi::MODE_1;
|
||||
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 select_ads1256 = Output::new(p.PA1, Level::High, Speed::VeryHigh);
|
||||
|
||||
let spi = SPI.init(Spi::new(
|
||||
p.SPI1,
|
||||
p.PA5,
|
||||
p.PA7,
|
||||
p.PA6,
|
||||
NoDma,
|
||||
NoDma,
|
||||
spi_conf,
|
||||
));
|
||||
|
||||
let ads_1256 = ADS_1256.init(Ads1256::new(Ads1256Delay, select_ads1256, ads1256_data_ready));
|
||||
ads_1256.write_config(spi, AUTOCAL_CONF).unwrap();
|
||||
|
||||
let inputs = &*ANALOG_INPUTS.init(Inputs {
|
||||
ai0: StatefulPublisher::new(
|
||||
Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
PubSubChannel::new().into(),
|
||||
),
|
||||
ai1: StatefulPublisher::new(
|
||||
Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
PubSubChannel::new().into(),
|
||||
),
|
||||
ai2: StatefulPublisher::new(
|
||||
Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
PubSubChannel::new().into(),
|
||||
),
|
||||
});
|
||||
|
||||
spawner.spawn(log_task(inputs)).unwrap();
|
||||
spawner
|
||||
.spawn(drive_inputs_task(ads_1256, spi, inputs))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn drive_inputs_task(
|
||||
ads_1256: &'static mut Ads1256<Ads1256Delay, Output<'static, PA1>, ExtiInput<'static, PA3>>,
|
||||
spi: &'static mut Spi<'static, SPI1, NoDma, NoDma>,
|
||||
inputs: &'static Inputs,
|
||||
) {
|
||||
let Inputs { ai0, ai1, ai2 } = inputs;
|
||||
|
||||
loop {
|
||||
let mut accumulator = f32::ElectricPotential::new::<volt>(0.0);
|
||||
|
||||
let voltage = ads_1256
|
||||
.autocal_convert(spi, SingleEnded::AIn0.into(), None, None, None, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_voltage(AUTOCAL_CONF.ad_control.gain());
|
||||
ai0.update(voltage);
|
||||
accumulator += voltage;
|
||||
|
||||
let voltage = ads_1256
|
||||
.autocal_convert(spi, SingleEnded::AIn1.into(), None, None, None, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_voltage(AUTOCAL_CONF.ad_control.gain());
|
||||
ai1.update(voltage);
|
||||
accumulator += voltage;
|
||||
|
||||
let voltage = ads_1256
|
||||
.autocal_convert(spi, SingleEnded::AIn2.into(), None, None, None, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_voltage(AUTOCAL_CONF.ad_control.gain());
|
||||
ai2.update(voltage);
|
||||
accumulator += voltage;
|
||||
|
||||
let accum_volts = accumulator.get::<volt>();
|
||||
info!("Immediate loop iteration result, combined volts: {}", accum_volts);
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn log_task(inputs: &'static Inputs) {
|
||||
let Inputs { ai0, ai1, ai2 } = inputs;
|
||||
let mut ai0_sub = ai0.subscribe().unwrap();
|
||||
let mut ai1_sub = ai1.subscribe().unwrap();
|
||||
let mut ai2_sub = ai2.subscribe().unwrap();
|
||||
|
||||
loop {
|
||||
let msg = ai0_sub.next_message_pure().await.get::<volt>();
|
||||
info!("Log task ai0: {}", msg);
|
||||
|
||||
let msg = ai1_sub.next_message_pure().await.get::<volt>();
|
||||
info!("Log task ai1: {}", msg);
|
||||
|
||||
let msg = ai2_sub.next_message_pure().await.get::<volt>();
|
||||
info!("Log task ai2: {}", msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::cell::Cell;
|
||||
|
||||
use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
use {embassy_executor as executor, embassy_stm32 as stm32};
|
||||
|
||||
use ads1256::standard::input::SingleEnded;
|
||||
use ads1256::{
|
||||
AdControl, Ads1256, AutoCal, BitOrder, Buffer, ClockOut, Config, DState, DataRate, DigitalIo,
|
||||
DigitalIoDirection, DigitalIoState, Gain, Multiplexer, MuxInput, Sdcs, Status,
|
||||
};
|
||||
use embassy_time::{Delay, Duration, Timer};
|
||||
use executor::Spawner;
|
||||
use stm32::dma::NoDma;
|
||||
use stm32::exti::ExtiInput;
|
||||
use stm32::gpio::{Input, Level, Output, Pull, Speed};
|
||||
use stm32::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::f32;
|
||||
|
||||
use defmt::info;
|
||||
use static_cell::StaticCell;
|
||||
use embassy_stm32::peripherals::{PA1, PA3, SPI1};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_sync::pubsub::PubSubChannel;
|
||||
use physical_ads1256::standard::multiplexer::poll::{
|
||||
AutocalPoll, AutocalPollStatePub, ModInputOnly,
|
||||
};
|
||||
use physical_node::transducer::input::Poll;
|
||||
use physical_node::transducer::{Publish, Publisher, StatefulPublisher};
|
||||
|
||||
const AUTOCAL_CONF: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
multiplexer: Multiplexer::setting(MuxInput::AIn0, MuxInput::Common),
|
||||
ad_control: AdControl::setting(Gain::X2, Sdcs::Off, ClockOut::Off),
|
||||
data_rate: DataRate::Sps2_5,
|
||||
digital_io: DigitalIo::setting(DigitalIoState::default(), DigitalIoDirection::default()),
|
||||
};
|
||||
|
||||
struct Ads1256Delay;
|
||||
|
||||
impl ads1256::BlockingDelay for Ads1256Delay {
|
||||
#[inline]
|
||||
fn t6_delay(&mut self) {
|
||||
Delay.delay_us(1u32);
|
||||
}
|
||||
|
||||
fn t11_1_delay(&mut self) {}
|
||||
|
||||
fn t11_2_delay(&mut self) {}
|
||||
}
|
||||
|
||||
type ExampleInput = AutocalPollStatePub<
|
||||
'static,
|
||||
NoopRawMutex,
|
||||
NoopRawMutex,
|
||||
ModInputOnly,
|
||||
Ads1256Delay,
|
||||
Output<'static, PA1>,
|
||||
ExtiInput<'static, PA3>,
|
||||
Spi<'static, SPI1, NoDma, NoDma>,
|
||||
10,
|
||||
1,
|
||||
>;
|
||||
|
||||
struct Inputs {
|
||||
ai1: ExampleInput,
|
||||
ai2: ExampleInput,
|
||||
ai3: ExampleInput,
|
||||
}
|
||||
|
||||
// Inputs
|
||||
static ANALOG_INPUTS: StaticCell<Inputs> = StaticCell::new();
|
||||
static ADS_1256: StaticCell<
|
||||
Mutex<NoopRawMutex, Ads1256<Ads1256Delay, Output<PA1>, ExtiInput<PA3>>>,
|
||||
> = StaticCell::new();
|
||||
static SPI: StaticCell<Mutex<NoopRawMutex, Spi<SPI1, NoDma, NoDma>>> = StaticCell::new();
|
||||
|
||||
#[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);
|
||||
});
|
||||
}
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_conf = spi::Config::default();
|
||||
spi_conf.mode = spi::MODE_1;
|
||||
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 select_ads1256 = Output::new(p.PA1, Level::High, Speed::VeryHigh);
|
||||
|
||||
let spi = SPI.init(Mutex::new(
|
||||
Spi::new(
|
||||
p.SPI1,
|
||||
p.PA5,
|
||||
p.PA7,
|
||||
p.PA6,
|
||||
NoDma,
|
||||
NoDma,
|
||||
spi_conf,
|
||||
)
|
||||
));
|
||||
|
||||
let ads_1256 =
|
||||
ADS_1256.init(Mutex::new(Ads1256::new(Ads1256Delay, select_ads1256, ads1256_data_ready)));
|
||||
|
||||
ads_1256
|
||||
.get_mut()
|
||||
.write_config(spi.get_mut(), AUTOCAL_CONF)
|
||||
.unwrap();
|
||||
|
||||
let inputs = &*ANALOG_INPUTS.init(Inputs {
|
||||
ai1: AutocalPollStatePub {
|
||||
poll: AutocalPoll::new(
|
||||
ModInputOnly {
|
||||
gain: Gain::X2,
|
||||
multiplexer: SingleEnded::AIn1.into(),
|
||||
},
|
||||
ads_1256,
|
||||
spi,
|
||||
),
|
||||
publisher: PubSubChannel::new().into(),
|
||||
state: Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
},
|
||||
ai2: AutocalPollStatePub {
|
||||
poll: AutocalPoll::new(
|
||||
ModInputOnly {
|
||||
gain: Gain::X2,
|
||||
multiplexer: SingleEnded::AIn2.into(),
|
||||
},
|
||||
ads_1256,
|
||||
spi,
|
||||
),
|
||||
publisher: PubSubChannel::new().into(),
|
||||
state: Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
},
|
||||
ai3: AutocalPollStatePub {
|
||||
poll: AutocalPoll::new(
|
||||
ModInputOnly {
|
||||
gain: Gain::X2,
|
||||
multiplexer: SingleEnded::AIn3.into(),
|
||||
},
|
||||
ads_1256,
|
||||
spi,
|
||||
),
|
||||
publisher: PubSubChannel::new().into(),
|
||||
state: Cell::new(f32::ElectricPotential::new::<volt>(f32::NAN)).into(),
|
||||
},
|
||||
});
|
||||
spawner.spawn(log_task(&inputs.ai1, 1)).unwrap();
|
||||
spawner.spawn(log_task(&inputs.ai2, 2)).unwrap();
|
||||
spawner.spawn(log_task(&inputs.ai3, 3)).unwrap();
|
||||
spawner
|
||||
.spawn(poll_task(&inputs.ai1, 1, Duration::from_secs(5)))
|
||||
.unwrap();
|
||||
spawner
|
||||
.spawn(poll_task(&inputs.ai2, 2, Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
spawner
|
||||
.spawn(poll_task(&inputs.ai3, 3, Duration::from_secs(20)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = 3)]
|
||||
async fn poll_task(input: &'static ExampleInput, input_num: u8, every: Duration) {
|
||||
loop {
|
||||
Timer::after(every).await;
|
||||
let result = input.poll().await.unwrap().get::<volt>();
|
||||
info!("ai{} poll result: {} volts", input_num, result);
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = 3)]
|
||||
async fn log_task(input: &'static ExampleInput, input_num: u8) {
|
||||
let mut subscriber = input.subscribe().unwrap();
|
||||
loop {
|
||||
let msg = subscriber.next_message_pure().await.get::<volt>();
|
||||
info!("Log task ai{}: {}", input_num, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::prelude::_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,
|
||||
};
|
||||
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::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use defmt::{debug, error, info, trace, unwrap};
|
||||
|
||||
const ADS1256_DELAY: ads1256::DefaultDelay<Delay> = ads1256::DefaultDelay::new(Delay);
|
||||
|
||||
#[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);
|
||||
});
|
||||
}
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_conf = spi::Config::default();
|
||||
spi_conf.mode = spi::MODE_1;
|
||||
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 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 ads_1256 = Ads1256::new(ADS1256_DELAY, select_ads1256, ads1256_data_ready);
|
||||
// status(&mut spi, &mut ads_1256);
|
||||
// multiplexer(&mut spi, &mut ads_1256);
|
||||
// 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);
|
||||
}
|
||||
|
||||
fn status<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("Status register:");
|
||||
const STATUS_SETTING: Status =
|
||||
Status::setting(Buffer::Disabled, AutoCal::Disabled, BitOrder::MostSigFirst);
|
||||
ads_1256.write_status(spi, STATUS_SETTING).unwrap();
|
||||
let status = ads_1256.read_status(spi).unwrap();
|
||||
info!("ADS1256 starting status: {}", status);
|
||||
let new_status_setting = status.with_buffer(Buffer::Enabled);
|
||||
ads_1256.write_status(spi, new_status_setting).unwrap();
|
||||
let status = ads_1256.read_status(spi).unwrap();
|
||||
info!("ADS1256 new status: {}", status);
|
||||
}
|
||||
|
||||
fn multiplexer<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("Multiplexer register:");
|
||||
const MULTIPLEXER_SETTING: Multiplexer = Multiplexer::setting(MuxInput::AIn0, MuxInput::Common);
|
||||
ads_1256
|
||||
.write_multiplexer(spi, MULTIPLEXER_SETTING)
|
||||
.unwrap();
|
||||
let multiplexer = ads_1256.read_multiplexer(spi).unwrap();
|
||||
info!("ADS1256 starting multiplexer: {}", multiplexer);
|
||||
let new_multiplexer_setting = multiplexer.with_positive(MuxInput::AIn1);
|
||||
ads_1256
|
||||
.write_multiplexer(spi, new_multiplexer_setting)
|
||||
.unwrap();
|
||||
let multiplexer = ads_1256.read_multiplexer(spi).unwrap();
|
||||
info!("ADS1256 new ad_control: {}", multiplexer);
|
||||
}
|
||||
|
||||
fn ad_control<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("AD control register:");
|
||||
const AD_CONTROL_SETTING: AdControl = AdControl::setting(Gain::X64, Sdcs::Off, ClockOut::Off);
|
||||
ads_1256.write_ad_control(spi, AD_CONTROL_SETTING).unwrap();
|
||||
let ad_control = ads_1256.read_ad_control(spi).unwrap();
|
||||
info!("ADS1256 starting ad_control: {}", ad_control);
|
||||
let new_ad_control_setting = ad_control.with_gain(Gain::X1);
|
||||
ads_1256
|
||||
.write_ad_control(spi, new_ad_control_setting)
|
||||
.unwrap();
|
||||
let ad_control = ads_1256.read_ad_control(spi).unwrap();
|
||||
info!("ADS1256 new ad_control: {}", ad_control);
|
||||
}
|
||||
|
||||
fn data_rate<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("Data rate register:");
|
||||
const DATA_RATE: DataRate = DataRate::Sps2_5;
|
||||
ads_1256.write_data_rate(spi, DATA_RATE).unwrap();
|
||||
let data_rate = ads_1256.read_data_rate(spi).unwrap();
|
||||
info!("ADS1256 starting data rate: {}", data_rate);
|
||||
ads_1256.write_data_rate(spi, DataRate::Sps60).unwrap();
|
||||
let data_rate = ads_1256.read_data_rate(spi).unwrap();
|
||||
info!("ADS1256 new data rate: {}", data_rate);
|
||||
}
|
||||
|
||||
fn gpio<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("GPIO register:");
|
||||
const GPIO: DigitalIo = DigitalIo::setting(
|
||||
DigitalIoState::new(DState::Low, DState::Low, DState::Low, DState::Low),
|
||||
DigitalIoDirection::new(
|
||||
DioDirection::Output,
|
||||
DioDirection::Input,
|
||||
DioDirection::Input,
|
||||
DioDirection::Output,
|
||||
),
|
||||
);
|
||||
ads_1256.write_gpio(spi, GPIO).unwrap();
|
||||
let gpio = ads_1256.read_gpio(spi).unwrap();
|
||||
info!("ADS1256 starting gpio: {}", gpio);
|
||||
let new_gpio_setting = gpio.with_io3_state(DState::High);
|
||||
ads_1256.write_gpio(spi, new_gpio_setting).unwrap();
|
||||
let gpio = ads_1256.read_gpio(spi).unwrap();
|
||||
info!("ADS1256 new gpio: {}", gpio);
|
||||
}
|
||||
|
||||
fn config<DelayerT: ads1256::BlockingDelay, SST: OutputPin, DrdyT: Wait>(
|
||||
spi: &mut impl SpiBus,
|
||||
ads_1256: &mut Ads1256<DelayerT, SST, DrdyT>,
|
||||
) {
|
||||
info!("Config:");
|
||||
let config = ads_1256.read_config(spi).unwrap();
|
||||
info!("ADS1256 starting config: {}", config);
|
||||
const CONFIG: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
multiplexer: Multiplexer::setting(MuxInput::AIn4, MuxInput::AIn5),
|
||||
ad_control: AdControl::setting(Gain::X16, Sdcs::Off, ClockOut::Off),
|
||||
data_rate: DataRate::Sps10,
|
||||
digital_io: DigitalIo::setting(
|
||||
DigitalIoState::new(DState::Low, DState::Low, DState::Low, DState::Low),
|
||||
DigitalIoDirection::new(
|
||||
DioDirection::Output,
|
||||
DioDirection::Input,
|
||||
DioDirection::Input,
|
||||
DioDirection::Input,
|
||||
),
|
||||
),
|
||||
};
|
||||
ads_1256.write_config(spi, CONFIG).unwrap();
|
||||
let config = ads_1256.read_config(spi).unwrap();
|
||||
info!("ADS1256 new config: {:#?}", config);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||
// Necessary unused import.
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
use {embassy_executor as executor, embassy_stm32 as stm32};
|
||||
|
||||
use ads1256::standard::input::{Differential, SingleEnded};
|
||||
use ads1256::{
|
||||
AdControl, Ads1256, AutoCal, BitOrder, Buffer, ClockOut, Config, DataRate, DigitalIo,
|
||||
DigitalIoDirection, DigitalIoState, Gain, Multiplexer, MuxInput, Sdcs, Status,
|
||||
};
|
||||
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::spi::Spi;
|
||||
use stm32::time::Hertz;
|
||||
use stm32::{pac, spi};
|
||||
|
||||
use uom::si::electric_potential::{millivolt, volt};
|
||||
|
||||
use defmt::info;
|
||||
use physical::transducer::{lm35, thermocouple_k};
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
const AUTOCAL_CONF: Config = Config {
|
||||
status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst),
|
||||
multiplexer: Multiplexer::setting(MuxInput::AIn0, MuxInput::AIn1),
|
||||
ad_control: AdControl::setting(Gain::X64, Sdcs::Off, ClockOut::Off),
|
||||
data_rate: DataRate::Sps2_5,
|
||||
digital_io: DigitalIo::setting(DigitalIoState::default(), DigitalIoDirection::default()),
|
||||
};
|
||||
|
||||
struct Ads1256Delay;
|
||||
|
||||
impl ads1256::BlockingDelay for Ads1256Delay {
|
||||
#[inline]
|
||||
fn t6_delay(&mut self) {
|
||||
Delay.delay_us(1u32);
|
||||
}
|
||||
|
||||
fn t11_1_delay(&mut self) {}
|
||||
|
||||
fn t11_2_delay(&mut self) {}
|
||||
}
|
||||
|
||||
#[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);
|
||||
});
|
||||
}
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_conf = spi::Config::default();
|
||||
spi_conf.mode = spi::MODE_1;
|
||||
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 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 ads_1256 = Ads1256::new(Ads1256Delay, select_ads1256, ads1256_data_ready);
|
||||
ads_1256.write_config(&mut spi, AUTOCAL_CONF).unwrap();
|
||||
ads_1256.self_calibrate(&mut spi).await.unwrap();
|
||||
|
||||
loop {
|
||||
let gain = Gain::X2;
|
||||
let reference = ads_1256
|
||||
.autocal_convert(
|
||||
&mut spi,
|
||||
SingleEnded::AIn2.into(),
|
||||
None,
|
||||
Some(AUTOCAL_CONF.ad_control.with_gain(gain)),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_voltage(gain);
|
||||
let reference_volts = reference.get::<volt>();
|
||||
let reference = lm35::convert(reference).unwrap();
|
||||
let reference_celsius = reference.get::<degree_celsius>();
|
||||
info!(
|
||||
"Reference junction temperature: {}°C, from voltage reading: {}V",
|
||||
reference_celsius, reference_volts
|
||||
);
|
||||
|
||||
let gain = Gain::X64;
|
||||
let voltage = ads_1256
|
||||
.autocal_convert(
|
||||
&mut spi,
|
||||
Differential::AIn0.into(),
|
||||
None,
|
||||
Some(AUTOCAL_CONF.ad_control.with_gain(gain)),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_voltage(AUTOCAL_CONF.ad_control.gain());
|
||||
let mv = voltage.get::<millivolt>();
|
||||
let temperature = thermocouple_k::convert_direct(voltage, reference).unwrap();
|
||||
let celsius = temperature.get::<degree_celsius>();
|
||||
info!("Thermocouple temperature: {}°C, from reading: {}mV", celsius, mv);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "generate-quantity"
|
||||
description = "Macros for generating physical quantity type"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.quote]
|
||||
workspace = true
|
||||
[dependencies.syn]
|
||||
workspace = true
|
||||
[dependencies.proc-macro2]
|
||||
workspace = true
|
||||
@@ -1,143 +0,0 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
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};
|
||||
|
||||
const NUMBER_TYPES: &[&str] = &[
|
||||
"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", "f32",
|
||||
"f64",
|
||||
];
|
||||
|
||||
// Define the input structure for the macro
|
||||
struct QuantityInput {
|
||||
struct_name: Ident,
|
||||
symbol: LitStr,
|
||||
}
|
||||
|
||||
// Implement the parsing for the input structure
|
||||
impl Parse for QuantityInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let struct_name: Ident = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let symbol: LitStr = input.parse()?;
|
||||
Ok(QuantityInput {
|
||||
struct_name,
|
||||
symbol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Remove requirement for physical::quantity::{Quantity, Value}
|
||||
/// The following imports must be in scope for this macro to work
|
||||
/// ```
|
||||
/// use generate_quantity::quantity_type;
|
||||
/// use physical::quantity::{Quantity, Value};
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn quantity_type(input: TokenStream) -> TokenStream {
|
||||
// Parse the input tokens into the QuantityInput structure
|
||||
let QuantityInput {
|
||||
struct_name,
|
||||
symbol,
|
||||
} = parse_macro_input!(input as QuantityInput);
|
||||
|
||||
//----- Value Extension ----------------------------------
|
||||
let mut val_ext_name = String::new();
|
||||
let struct_name_str = struct_name.to_string();
|
||||
let mut struct_name_chars = struct_name_str.chars();
|
||||
let first = struct_name_chars
|
||||
.next()
|
||||
.expect("Struct name cannot be 0 length");
|
||||
val_ext_name.push(first.to_ascii_lowercase());
|
||||
for character in struct_name_chars {
|
||||
if character.is_uppercase() {
|
||||
val_ext_name.push('_');
|
||||
val_ext_name.push(character.to_ascii_lowercase());
|
||||
} else {
|
||||
val_ext_name.push(character);
|
||||
}
|
||||
}
|
||||
let val_ext_fn_name = Ident::new(val_ext_name.deref(), struct_name.span());
|
||||
let val_ext_trait_name =
|
||||
Ident::new(format!("{struct_name_str}Val").deref(), struct_name.span());
|
||||
|
||||
//----- Conversion impls ----------------------------------
|
||||
let mut conversion_impls: Vec<TokenStream2> = Vec::new();
|
||||
for ¤t_type in NUMBER_TYPES {
|
||||
// Generate conversion methods for all primitive types except the current_type
|
||||
let conversions = NUMBER_TYPES
|
||||
.iter()
|
||||
.filter(|&&t| t != current_type)
|
||||
.map(|&target_type| {
|
||||
let method_name = Ident::new(&format!("as_{}", target_type), Span::call_site());
|
||||
let target_type = Ident::new(target_type, Span::call_site());
|
||||
|
||||
quote! {
|
||||
/// Directly [as] cast this quantities value while maintaining the type of the quantity.
|
||||
#[inline(always)]
|
||||
pub fn #method_name(self) -> #struct_name<#target_type> {
|
||||
#struct_name(self.0 as #target_type)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<TokenStream2>>();
|
||||
|
||||
let current_type = Ident::new(current_type, Span::call_site());
|
||||
// Generate the impl block for the struct with the current_type
|
||||
let expanded = quote! {
|
||||
impl #struct_name<#current_type> {
|
||||
#(#conversions)*
|
||||
}
|
||||
};
|
||||
|
||||
conversion_impls.push(expanded);
|
||||
}
|
||||
|
||||
//----- Output Code ----------------------------------
|
||||
let expanded = quote! {
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Debug,
|
||||
derive_more::Add,
|
||||
derive_more::AddAssign,
|
||||
derive_more::Sub,
|
||||
derive_more::SubAssign,
|
||||
derive_more::Display
|
||||
)]
|
||||
#[display(fmt = "{} {}", _0, "Self::symbol()")]
|
||||
#[repr(transparent)]
|
||||
pub struct #struct_name<V: Value>(pub V);
|
||||
|
||||
impl<V: Value> Quantity<V> for #struct_name<V> {
|
||||
#[inline(always)]
|
||||
fn value(self) -> V {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn symbol() -> &'static str {
|
||||
#symbol
|
||||
}
|
||||
}
|
||||
|
||||
pub trait #val_ext_trait_name : Value {
|
||||
/// Create a quantity in the given unit from this [Value]
|
||||
#[inline(always)]
|
||||
fn #val_ext_fn_name(self) -> #struct_name<Self> {
|
||||
#struct_name(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl <V: Value> #val_ext_trait_name for V {}
|
||||
|
||||
#(#conversion_impls)*
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "node-poll-variants"
|
||||
description = "Macros for physical nodes."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "tests/test_build.rs"
|
||||
|
||||
[dependencies.syn]
|
||||
workspace = true
|
||||
[dependencies.quote]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies.trybuild]
|
||||
workspace = true
|
||||
[dev-dependencies.physical-node]
|
||||
path = "../../node"
|
||||
features = ["embassy-sync"]
|
||||
[dev-dependencies.embassy-sync]
|
||||
workspace = true
|
||||
@@ -0,0 +1,294 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::ops::Deref;
|
||||
use quote::__private::parse_spanned;
|
||||
use syn::__private::{str, Span};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::{Colon, Comma, PathSep, Plus};
|
||||
use syn::{parse_macro_input, parse_quote, parse_quote_spanned, parse_str, Data, DeriveInput, Expr, GenericParam, Generics, Ident, Lit, LitStr, Meta, Path, PathSegment, Token, TraitBound, TraitBoundModifier, TypeParam, TypeParamBound, Type};
|
||||
|
||||
#[proc_macro_derive(PollVariants, attributes(value_type, error_type))]
|
||||
pub fn poll_variant_macro(input: TokenStream) -> TokenStream {
|
||||
// ----- Parse input ----------------------------------
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let ident = &input.ident;
|
||||
let data = &input.data;
|
||||
let og_generics = &input.generics;
|
||||
let (og_impl_generics, og_type_generics, og_where_clause) = &og_generics.split_for_impl();
|
||||
|
||||
// ----- Check that item the macro was used on is a struct ----------------------------------
|
||||
match data {
|
||||
Data::Struct(struct_data) => struct_data,
|
||||
Data::Enum(_) => panic!("Stateful struct cannot be derived from an enum."),
|
||||
Data::Union(_) => panic!("Stateful struct cannot be derived from a union."),
|
||||
};
|
||||
|
||||
// ----- Extract attribute information ----------------------------------
|
||||
const VALUE_T_NAME: &str = "value_type";
|
||||
const ERROR_T_NAME: &str = "error_type";
|
||||
let mut value_type: Option<Type> = None;
|
||||
let mut error_type: Option<Type> = None;
|
||||
for attribute in attrs.iter() {
|
||||
// if the attribute is a named value
|
||||
if let Meta::NameValue(meta) = &attribute.meta {
|
||||
// if the name of the attribute is value_type
|
||||
if meta.path.segments[0].ident == VALUE_T_NAME {
|
||||
// if the value of the attribute is a literal
|
||||
if let Expr::Lit(lit) = &meta.value {
|
||||
// if the literal is a string
|
||||
if let Lit::Str(lit) = &lit.lit {
|
||||
let span = lit.span();
|
||||
let string = lit.token().to_string();
|
||||
let string = string.trim_matches('"').to_string();
|
||||
let _value_type: Type = parse_str(string.deref()).unwrap();
|
||||
let _value_type: Type = parse_quote_spanned!(span=> #_value_type);
|
||||
|
||||
value_type = Some(_value_type);
|
||||
|
||||
} else {
|
||||
panic!("{VALUE_T_NAME} must be set with a string literal.")
|
||||
}
|
||||
} else {
|
||||
panic!("{VALUE_T_NAME} must be set with a literal.")
|
||||
}
|
||||
}
|
||||
|
||||
// if the name of the attribute is error_type
|
||||
if meta.path.segments[0].ident == ERROR_T_NAME {
|
||||
// if the value of the attribute is a literal
|
||||
if let Expr::Lit(lit) = &meta.value {
|
||||
// if the literal is a string
|
||||
if let Lit::Str(lit) = &lit.lit {
|
||||
let span = lit.span();
|
||||
let string = lit.token().to_string();
|
||||
let string = string.trim_matches('"').to_string();
|
||||
let _error_type: Type = parse_str(string.deref()).unwrap();
|
||||
let _error_type: Type = parse_quote_spanned!(span=> #_error_type);
|
||||
|
||||
error_type = Some(_error_type);
|
||||
} else {
|
||||
panic!("{ERROR_T_NAME} must be set with a string literal.")
|
||||
}
|
||||
} else {
|
||||
panic!("{ERROR_T_NAME} must be set with a literal.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let value_type = value_type
|
||||
.expect(format!("Need attribute {VALUE_T_NAME}: #[{VALUE_T_NAME} = \"type\"]").deref());
|
||||
let error_type = error_type
|
||||
.expect(format!("Need attribute {ERROR_T_NAME}: #[{ERROR_T_NAME} = \"type\"]").deref());
|
||||
|
||||
// ----- Add publisher generics ----------------------------------
|
||||
// MutexT
|
||||
const MUTEX_T_NAME: &str = "PublishMutexT";
|
||||
let mutex_t_ident = Ident::new(MUTEX_T_NAME, Span::call_site());
|
||||
const CAPACITY_NAME: &str = "CAPACITY";
|
||||
let capacity_ident = Ident::new(CAPACITY_NAME, Span::call_site());
|
||||
const NUM_SUBS_NAME: &str = "NUM_SUBS";
|
||||
let num_subs_ident = Ident::new(NUM_SUBS_NAME, Span::call_site());
|
||||
let mut num_lifetimes: usize = 0;
|
||||
let mut num_types: usize = 0;
|
||||
let mut num_const: usize = 0;
|
||||
let mut has_mutex_t = false;
|
||||
for param in og_generics.params.iter() {
|
||||
match param {
|
||||
GenericParam::Lifetime(_) => num_lifetimes += 1,
|
||||
GenericParam::Type(param) => {
|
||||
num_types += 1;
|
||||
|
||||
// If the generic parameter is MutexT
|
||||
if param.ident == MUTEX_T_NAME {
|
||||
has_mutex_t = true;
|
||||
}
|
||||
},
|
||||
GenericParam::Const(_) => num_const += 1,
|
||||
}
|
||||
}
|
||||
|
||||
let mut publish_generics = og_generics.clone();
|
||||
// If MutexT is not a generic parameter, add it
|
||||
if !has_mutex_t {
|
||||
let mutex_t_param: GenericParam =
|
||||
parse_quote!(#mutex_t_ident: embassy_sync::blocking_mutex::raw::RawMutex);
|
||||
|
||||
let num_generics = num_lifetimes + num_types + num_const;
|
||||
// If there are generics
|
||||
if num_generics > 0 {
|
||||
// If all generics are lifetimes
|
||||
if num_lifetimes == num_generics {
|
||||
// Add MutexT after the lifetimes
|
||||
publish_generics.params.push(mutex_t_param);
|
||||
// If no generics are lifetimes
|
||||
} else if num_lifetimes == 0 {
|
||||
// Insert MutexT at the front
|
||||
publish_generics.params.insert(0, mutex_t_param);
|
||||
// If there are lifetimes and other generics
|
||||
} else {
|
||||
// Insert MutexT after the lifetimes
|
||||
publish_generics.params.insert(num_lifetimes, mutex_t_param);
|
||||
}
|
||||
// If there are no generics
|
||||
} else {
|
||||
// Add MutexT
|
||||
publish_generics.params.push(mutex_t_param);
|
||||
}
|
||||
}
|
||||
// const generics
|
||||
let capacity_param: GenericParam = parse_quote!(const #capacity_ident: usize);
|
||||
let num_subs_param: GenericParam = parse_quote!(const #num_subs_ident: usize);
|
||||
publish_generics.params.push(capacity_param);
|
||||
publish_generics.params.push(num_subs_param);
|
||||
|
||||
let (publ_impl_generics, publ_type_generics, publ_where_clause) =
|
||||
&publish_generics.split_for_impl();
|
||||
|
||||
let pubsub_error_path: Path = parse_quote!(embassy_sync::pubsub::Error);
|
||||
let pubsub_sub_path: Path = parse_quote!(embassy_sync::pubsub::Subscriber);
|
||||
|
||||
let stateful_variant_ident = Ident::new(format!("{ident}Stateful").deref(), ident.span());
|
||||
let publish_variant_ident = Ident::new(format!("{ident}Publish").deref(), ident.span());
|
||||
let state_pub_variant_ident = Ident::new(format!("{ident}StatePub").deref(), ident.span());
|
||||
|
||||
let poll_path: Path = parse_quote!(physical_node::transducer::input::Poll);
|
||||
let stateful_path: Path = parse_quote!(physical_node::transducer::Stateful);
|
||||
let publish_path: Path = parse_quote!(physical_node::transducer::Publish);
|
||||
|
||||
let state_path: Path = parse_quote!(physical_node::transducer::State);
|
||||
let publisher_path: Path = parse_quote!(physical_node::transducer::Publisher);
|
||||
let cellview_path: Path = parse_quote!(physical_node::cell::CellView);
|
||||
|
||||
let error_path: Path = parse_quote!(physical_node::CriticalError);
|
||||
|
||||
let expanded = quote! {
|
||||
// ----- Stateful struct ----------------------------------
|
||||
#vis struct #stateful_variant_ident #og_generics #og_where_clause {
|
||||
pub poll: #ident #og_type_generics,
|
||||
pub state: #state_path<#value_type>,
|
||||
}
|
||||
|
||||
// ----- Stateful impls ----------------------------------
|
||||
impl #og_impl_generics #poll_path for #stateful_variant_ident #og_type_generics #og_where_clause {
|
||||
type Value = #value_type;
|
||||
type Error = #error_type;
|
||||
|
||||
#[inline]
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error> {
|
||||
let result = self.poll.poll().await;
|
||||
if let Ok(value) = result {
|
||||
self.state.update(value);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl #og_impl_generics #stateful_path for #stateful_variant_ident #og_type_generics #og_where_clause {
|
||||
type Value = #value_type;
|
||||
|
||||
#[inline(always)]
|
||||
fn state_cell(&self) -> #cellview_path<Self::Value> {
|
||||
self.state.state_cell()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state(&self) -> Self::Value {
|
||||
self.state.state()
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Publish struct ----------------------------------
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
#vis struct #publish_variant_ident #publish_generics #publ_where_clause {
|
||||
pub poll: #ident #og_type_generics,
|
||||
pub publisher: #publisher_path<#value_type, #mutex_t_ident, #capacity_ident, #num_subs_ident>,
|
||||
}
|
||||
|
||||
// ----- Publish impl ----------------------------------
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
impl #publ_impl_generics #poll_path for #publish_variant_ident #publ_type_generics #publ_where_clause {
|
||||
type Value = #value_type;
|
||||
type Error = #error_type;
|
||||
|
||||
#[inline]
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error> {
|
||||
let result = self.poll.poll().await;
|
||||
if let Ok(value) = result {
|
||||
self.publisher.update(value);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
impl #publ_impl_generics #publish_path<#capacity_ident, #num_subs_ident> for #publish_variant_ident #publ_type_generics #publ_where_clause {
|
||||
type Value = #value_type;
|
||||
type Mutex = #mutex_t_ident;
|
||||
|
||||
#[inline(always)]
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> Result<#pubsub_sub_path<Self::Mutex, Self::Value, #capacity_ident, #num_subs_ident, 0>, #pubsub_error_path> {
|
||||
self.publisher.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
// ----- StatePub struct ----------------------------------
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
#vis struct #state_pub_variant_ident #publish_generics #publ_where_clause {
|
||||
pub poll: #ident #og_type_generics,
|
||||
pub state: #state_path<#value_type>,
|
||||
pub publisher: #publisher_path<#value_type, #mutex_t_ident, #capacity_ident, #num_subs_ident>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
impl #publ_impl_generics #poll_path for #state_pub_variant_ident #publ_type_generics #publ_where_clause {
|
||||
type Value = #value_type;
|
||||
type Error = #error_type;
|
||||
|
||||
#[inline]
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error> {
|
||||
let result = self.poll.poll().await;
|
||||
if let Ok(value) = result {
|
||||
self.state.update(value);
|
||||
self.publisher.update(value);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
impl #publ_impl_generics #stateful_path for #state_pub_variant_ident #publ_type_generics #publ_where_clause {
|
||||
type Value = #value_type;
|
||||
|
||||
#[inline(always)]
|
||||
fn state_cell(&self) -> #cellview_path<Self::Value> {
|
||||
self.state.state_cell()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state(&self) -> Self::Value {
|
||||
self.state.state()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
impl #publ_impl_generics #publish_path<#capacity_ident, #num_subs_ident> for #state_pub_variant_ident #publ_type_generics #publ_where_clause {
|
||||
type Value = #value_type;
|
||||
type Mutex = #mutex_t_ident;
|
||||
|
||||
#[inline(always)]
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> Result<#pubsub_sub_path<Self::Mutex, Self::Value, #capacity_ident, #num_subs_ident, 0>, #pubsub_error_path> {
|
||||
self.publisher.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#![feature(async_fn_in_trait, impl_trait_projections, never_type)]
|
||||
|
||||
use node_poll_variants::PollVariants;
|
||||
use physical_node::transducer::input::Poll;
|
||||
|
||||
#[derive(PollVariants)]
|
||||
#[value_type = "SecondT"]
|
||||
#[error_type = "!"]
|
||||
struct ExamplePoll<'a, FirstT, SecondT>
|
||||
where
|
||||
SecondT: Copy,
|
||||
{
|
||||
a: &'a i32,
|
||||
b: i32,
|
||||
first: FirstT,
|
||||
second: SecondT,
|
||||
}
|
||||
|
||||
impl<'a, FirstT, SecondT> Poll for ExamplePoll<'a, FirstT, SecondT>
|
||||
where
|
||||
SecondT: Copy,
|
||||
{
|
||||
type Value = SecondT;
|
||||
type Error = !;
|
||||
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error> {
|
||||
Ok(self.second)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
#[test]
|
||||
fn tests() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/generate.rs");
|
||||
}
|
||||
+3
-10
@@ -7,12 +7,6 @@ repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
comms = []
|
||||
single-packet-msgs = []
|
||||
usb = ["embassy-usb"]
|
||||
stm32 = ["embassy-stm32", "physical/stm32"]
|
||||
|
||||
[dependencies.physical]
|
||||
path = ".."
|
||||
[dependencies.embedded-hal]
|
||||
@@ -21,9 +15,8 @@ workspace = true
|
||||
workspace = true
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
[dependencies.embassy-stm32]
|
||||
workspace = true
|
||||
optional = true
|
||||
[dependencies.embassy-usb]
|
||||
[dependencies.uom]
|
||||
workspace = true
|
||||
[dependencies.embassy-sync]
|
||||
workspace = true
|
||||
optional = true
|
||||
@@ -1,17 +0,0 @@
|
||||
pub trait Sender {
|
||||
async fn send(&mut self, msg: &[u8]) -> Result<(), Reset>;
|
||||
}
|
||||
|
||||
pub trait Receiver {
|
||||
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Reset>;
|
||||
}
|
||||
|
||||
//TODO: Replace with !
|
||||
pub struct Never;
|
||||
|
||||
/// Communication errors indicates either:
|
||||
/// Our connection was already disconnected, in which case we should reset and wait for new connection to made.
|
||||
/// or
|
||||
/// There was an unexpected, irrecoverable error in communication, in which case we don't want to enter a terminal error
|
||||
/// safe mode, because there is no indication the actual control is broken, so all we can really do is reset the connection.
|
||||
pub struct Reset;
|
||||
+5
-8
@@ -1,12 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
#[cfg(feature = "comms")]
|
||||
pub mod comms;
|
||||
pub mod spi;
|
||||
#[cfg(feature = "stm32")]
|
||||
pub mod stm32;
|
||||
pub mod transducer;
|
||||
|
||||
pub mod cell {
|
||||
pub use physical::cell::*;
|
||||
}
|
||||
|
||||
pub use physical::CriticalError;
|
||||
|
||||
pub const GPIO_ERROR_MSG: &'static str =
|
||||
"Driver does not support GPIO pins with expected failure states";
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
use crate::GPIO_ERROR_MSG;
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi;
|
||||
use embedded_hal::spi::SpiBus;
|
||||
|
||||
/// End the SPI operation if the result was an error.
|
||||
/// This function will attempt to flush the SPI bus if the result being inspected was an error.
|
||||
/// If the flush fails, the flush error will be ignored and the original error will be returned.
|
||||
#[inline]
|
||||
pub fn end_spi_if_err<OkT, SpiT: SpiBus>(
|
||||
slave_select: &mut impl OutputPin,
|
||||
spi: &mut SpiT,
|
||||
result: Result<OkT, <SpiT as spi::ErrorType>::Error>,
|
||||
) -> Result<OkT, <SpiT as spi::ErrorType>::Error> {
|
||||
match result {
|
||||
Ok(_) => result,
|
||||
Err(_) => {
|
||||
// Ignore flush error and return the original error
|
||||
slave_select.set_high().expect(GPIO_ERROR_MSG);
|
||||
let _ = spi.flush();
|
||||
result
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// End the series of SPI operations and forward the error of the final operation if there was
|
||||
/// one. Error handling:
|
||||
/// * If there was an error in the SPI operation and an error flushing the bus, returns the original
|
||||
/// SPI error, not the bus error.
|
||||
/// * If there was not an error in the SPI operation but was an error flushing the bus, returns the
|
||||
/// bus flush error.
|
||||
/// * If there was an error in the SPI operation and no error flushing the bus, returns the original
|
||||
/// SPI error.
|
||||
#[inline]
|
||||
pub fn end_spi<OkT, SpiT: SpiBus>(
|
||||
slave_select: &mut impl OutputPin,
|
||||
spi: &mut SpiT,
|
||||
result: Result<OkT, <SpiT as spi::ErrorType>::Error>,
|
||||
) -> Result<OkT, <SpiT as spi::ErrorType>::Error> {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
slave_select.set_high().expect(GPIO_ERROR_MSG);
|
||||
spi.flush()?;
|
||||
result
|
||||
},
|
||||
Err(_) => {
|
||||
slave_select.set_high().expect(GPIO_ERROR_MSG);
|
||||
let _ = spi.flush();
|
||||
result
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "usb")]
|
||||
pub mod usb;
|
||||
@@ -1,41 +0,0 @@
|
||||
// The library will have build errors when built on its own (due to not having embassy-stm32 feature for a specific microcontroller)
|
||||
// but it will work fine when used as a dependency for firmware that has the feature for a specific stm32 microcontroller.
|
||||
|
||||
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_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>;
|
||||
|
||||
pub struct UsbIO {
|
||||
/// Send to master
|
||||
pub interrupt_in: TypedInterIn,
|
||||
/// Recieve from master
|
||||
pub interrupt_out: TypedInterOut,
|
||||
}
|
||||
|
||||
impl comms::Sender for TypedInterIn {
|
||||
async fn send(&mut self, msg: &[u8]) -> Result<(), comms::Reset> {
|
||||
self.write(msg).await.map_err(|_| comms::Reset)
|
||||
}
|
||||
}
|
||||
|
||||
impl comms::Receiver for TypedInterOut {
|
||||
#[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.
|
||||
self.read(buffer)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| comms::Reset)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "single-packet-msgs"))]
|
||||
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), comms::Reset> {
|
||||
todo!("Decide if we want a general purpose multi-packet message receive")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub use physical::transducer::input::*;
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
mod sync;
|
||||
|
||||
pub use physical::transducer::*;
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
pub use sync::*;
|
||||
@@ -0,0 +1 @@
|
||||
pub use physical::transducer::output::*;
|
||||
@@ -0,0 +1,61 @@
|
||||
use crate::cell::CellView;
|
||||
use crate::transducer::{Publish, Publisher};
|
||||
use core::cell::Cell;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::pubsub::{Error, PubSubChannel, Subscriber};
|
||||
use physical::transducer::{State, Stateful};
|
||||
|
||||
pub struct StatefulPublisher<
|
||||
T: Copy,
|
||||
MutexT: RawMutex,
|
||||
const CAPACITY: usize,
|
||||
const NUM_SUBS: usize,
|
||||
> {
|
||||
pub state: State<T>,
|
||||
pub publisher: Publisher<T, MutexT, CAPACITY, NUM_SUBS>,
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
|
||||
StatefulPublisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn new(state: State<T>, publisher: Publisher<T, MutexT, CAPACITY, NUM_SUBS>) -> Self {
|
||||
Self { state, publisher }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&self, value: T) {
|
||||
self.state.update(value);
|
||||
self.publisher.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize> Stateful
|
||||
for StatefulPublisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn state_cell(&self) -> CellView<Self::Value> {
|
||||
self.state.state_cell()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state(&self) -> Self::Value {
|
||||
self.state.state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
|
||||
Publish<CAPACITY, NUM_SUBS> for StatefulPublisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
type Value = T;
|
||||
type Mutex = MutexT;
|
||||
|
||||
#[inline(always)]
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, Error> {
|
||||
self.publisher.subscribe()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
mod input;
|
||||
|
||||
pub use input::*;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::pubsub;
|
||||
use embassy_sync::pubsub::{PubSubBehavior, PubSubChannel, Subscriber};
|
||||
|
||||
pub trait Publish<const CAPACITY: usize, const NUM_SUBS: usize> {
|
||||
type Value: Copy;
|
||||
type Mutex: RawMutex;
|
||||
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, pubsub::Error>;
|
||||
}
|
||||
|
||||
pub struct Publisher<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize> {
|
||||
channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>,
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
|
||||
Publisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn new(channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>) -> Self {
|
||||
Self { channel }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&self, value: T) {
|
||||
self.channel.publish_immediate(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
|
||||
Publish<CAPACITY, NUM_SUBS> for Publisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
type Value = T;
|
||||
type Mutex = MutexT;
|
||||
|
||||
#[inline(always)]
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, pubsub::Error> {
|
||||
self.channel.subscriber()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
|
||||
From<PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>>
|
||||
for Publisher<T, MutexT, CAPACITY, NUM_SUBS>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>) -> Self {
|
||||
Publisher::new(channel)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "physical-ads1256"
|
||||
description = "Shared node abstractions for ADS1256 components."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
embassy-sync = ["dep:embassy-sync", "ads1256/embassy-sync", "physical-node/embassy-sync"]
|
||||
poll = ["standard-multiplexer", "embassy-sync"]
|
||||
standard-multiplexer = []
|
||||
|
||||
[dependencies.physical-node]
|
||||
path = "../../../node"
|
||||
[dependencies.node-poll-variants]
|
||||
path = "../../../macros/node-poll-variants"
|
||||
[dependencies.ads1256]
|
||||
workspace = true
|
||||
[dependencies.embedded-hal]
|
||||
workspace = true
|
||||
[dependencies.embedded-hal-async]
|
||||
workspace = true
|
||||
[dependencies.defmt]
|
||||
workspace = true
|
||||
[dependencies.uom]
|
||||
workspace = true
|
||||
[dependencies.embassy-sync]
|
||||
workspace = true
|
||||
optional = true
|
||||
@@ -0,0 +1,3 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod standard;
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "standard-multiplexer")]
|
||||
pub mod multiplexer;
|
||||
@@ -0,0 +1,4 @@
|
||||
mod sync;
|
||||
|
||||
#[cfg(feature = "embassy-sync")]
|
||||
pub use sync::*;
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "poll")]
|
||||
pub mod poll;
|
||||
@@ -0,0 +1,382 @@
|
||||
use ads1256::{
|
||||
AdControl, Ads1256, BlockingDelay, DataRate, Gain, Multiplexer, OutputPin, SpiBus, Status, Wait,
|
||||
};
|
||||
use core::ops::DerefMut;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use node_poll_variants::PollVariants;
|
||||
use physical_node::transducer::input::Poll;
|
||||
use physical_node::CriticalError;
|
||||
use uom::si::f32;
|
||||
|
||||
#[derive(PollVariants)]
|
||||
#[value_type = "f32::ElectricPotential"]
|
||||
#[error_type = "CriticalError"]
|
||||
pub struct AutocalPoll<'a, DeviceMutexT, ModInT, DelayerT, SST, DrdyT, SpiT>
|
||||
where
|
||||
DeviceMutexT: RawMutex,
|
||||
ModInT: ModInput,
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
SpiT: SpiBus,
|
||||
{
|
||||
input_mod: ModInT,
|
||||
ads1256: &'a Mutex<DeviceMutexT, Ads1256<DelayerT, SST, DrdyT>>,
|
||||
spi: &'a Mutex<DeviceMutexT, SpiT>,
|
||||
}
|
||||
|
||||
impl<'a, DeviceMutexT, ModInT, DelayerT, SST, DrdyT, SpiT>
|
||||
AutocalPoll<'a, DeviceMutexT, ModInT, DelayerT, SST, DrdyT, SpiT>
|
||||
where
|
||||
DeviceMutexT: RawMutex,
|
||||
ModInT: ModInput,
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
SpiT: SpiBus,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn new(
|
||||
input_mod: ModInT,
|
||||
ads1256: &'a Mutex<DeviceMutexT, Ads1256<DelayerT, SST, DrdyT>>,
|
||||
spi: &'a Mutex<DeviceMutexT, SpiT>,
|
||||
) -> Self {
|
||||
Self {
|
||||
input_mod,
|
||||
ads1256,
|
||||
spi,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, DeviceMutexT, ModInT, DelayerT, SST, DrdyT, SpiT> Poll
|
||||
for AutocalPoll<'a, DeviceMutexT, ModInT, DelayerT, SST, DrdyT, SpiT>
|
||||
where
|
||||
DeviceMutexT: RawMutex,
|
||||
ModInT: ModInput,
|
||||
DelayerT: BlockingDelay,
|
||||
SST: OutputPin,
|
||||
DrdyT: Wait,
|
||||
SpiT: SpiBus,
|
||||
{
|
||||
type Value = f32::ElectricPotential;
|
||||
type Error = CriticalError;
|
||||
|
||||
async fn poll(&self) -> Result<Self::Value, CriticalError> {
|
||||
let mut ads1256_guard = self.ads1256.lock().await;
|
||||
let ads1256 = ads1256_guard.deref_mut();
|
||||
//TODO: ADS1256 documentation seems to say we should be waiting for drdy low after
|
||||
// issuing standby command but it never goes low.
|
||||
|
||||
let result = ads1256
|
||||
.autocal_convert_m(
|
||||
self.spi,
|
||||
self.input_mod.multiplexer(),
|
||||
self.input_mod.status(),
|
||||
self.input_mod.ad_control(),
|
||||
self.input_mod.data_rate(),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(conversion) => Ok(conversion.to_voltage(self.input_mod.gain())),
|
||||
Err(_) => Err(CriticalError::Communication),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ModInput: Copy {
|
||||
fn multiplexer(self) -> Multiplexer;
|
||||
|
||||
fn gain(self) -> Gain;
|
||||
|
||||
fn status(self) -> Option<Status>;
|
||||
|
||||
fn ad_control(self) -> Option<AdControl>;
|
||||
|
||||
fn data_rate(self) -> Option<DataRate>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInputOnly {
|
||||
pub multiplexer: Multiplexer,
|
||||
/// Only used for converting to voltage, this value must match what is set on the ADS1256, it
|
||||
/// will not *be* set unlike the other fields.
|
||||
pub gain: Gain,
|
||||
}
|
||||
|
||||
impl ModInput for ModInputOnly {
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
fn gain(self) -> Gain {
|
||||
self.gain
|
||||
}
|
||||
|
||||
fn status(self) -> Option<Status> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
None
|
||||
}
|
||||
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// buffer
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInStatus {
|
||||
pub multiplexer: Multiplexer,
|
||||
/// Only used for converting to voltage, this value must match what is set on the ADS1256, it
|
||||
/// will not *be* set unlike the other fields.
|
||||
pub gain: Gain,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
impl ModInput for ModInStatus {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.gain
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
Some(self.status)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// gain
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInAdControl {
|
||||
pub multiplexer: Multiplexer,
|
||||
pub ad_control: AdControl,
|
||||
}
|
||||
|
||||
impl ModInput for ModInAdControl {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.ad_control.gain()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
Some(self.ad_control)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// data rate
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInDataRate {
|
||||
pub multiplexer: Multiplexer,
|
||||
/// Only used for converting to voltage, this value must match what is set on the ADS1256, it
|
||||
/// will not *be* set unlike the other fields.
|
||||
pub gain: Gain,
|
||||
pub data_rate: DataRate,
|
||||
}
|
||||
|
||||
impl ModInput for ModInDataRate {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.gain
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
Some(self.data_rate)
|
||||
}
|
||||
}
|
||||
|
||||
/// buffer, gain
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInStatAdc {
|
||||
pub multiplexer: Multiplexer,
|
||||
pub status: Status,
|
||||
pub ad_control: AdControl,
|
||||
}
|
||||
|
||||
impl ModInput for ModInStatAdc {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.ad_control.gain()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
Some(self.status)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
Some(self.ad_control)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// buffer, data rate
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInStatDrate {
|
||||
pub multiplexer: Multiplexer,
|
||||
/// Only used for converting to voltage, this value must match what is set on the ADS1256, it
|
||||
/// will not *be* set unlike the other fields.
|
||||
pub gain: Gain,
|
||||
pub status: Status,
|
||||
pub data_rate: DataRate,
|
||||
}
|
||||
|
||||
impl ModInput for ModInStatDrate {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.gain
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
Some(self.status)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
Some(self.data_rate)
|
||||
}
|
||||
}
|
||||
|
||||
// gain, data rate
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInAdcDrate {
|
||||
pub multiplexer: Multiplexer,
|
||||
pub ad_control: AdControl,
|
||||
pub data_rate: DataRate,
|
||||
}
|
||||
|
||||
impl ModInput for ModInAdcDrate {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.ad_control.gain()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
Some(self.ad_control)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
Some(self.data_rate)
|
||||
}
|
||||
}
|
||||
|
||||
/// buffer, gain, data rate
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ModInAll {
|
||||
pub multiplexer: Multiplexer,
|
||||
pub status: Status,
|
||||
pub ad_control: AdControl,
|
||||
pub data_rate: DataRate,
|
||||
}
|
||||
|
||||
impl ModInput for ModInAll {
|
||||
#[inline(always)]
|
||||
fn multiplexer(self) -> Multiplexer {
|
||||
self.multiplexer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gain(self) -> Gain {
|
||||
self.ad_control.gain()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn status(self) -> Option<Status> {
|
||||
Some(self.status)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ad_control(self) -> Option<AdControl> {
|
||||
Some(self.ad_control)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_rate(self) -> Option<DataRate> {
|
||||
Some(self.data_rate)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Before upgrading check that everything is available on all tier1 targets here:
|
||||
# https://rust-lang.github.io/rustup-components-history
|
||||
[toolchain]
|
||||
channel = "1.82"
|
||||
channel = "1.75"
|
||||
components = [ "rust-src", "rustfmt", "llvm-tools" ]
|
||||
targets = [
|
||||
"thumbv7em-none-eabi",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "stm32")]
|
||||
pub mod stm32;
|
||||
@@ -1,9 +0,0 @@
|
||||
use crate::quantity::{MilliVolts, Quantity};
|
||||
|
||||
pub fn reading_to_voltage(
|
||||
reading: u32,
|
||||
reference_voltage: MilliVolts<u32>,
|
||||
v_ref_int_scale: u32,
|
||||
) -> MilliVolts<u16> {
|
||||
MilliVolts((reading * v_ref_int_scale / reference_voltage.value()) as u16)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
/// Provides a view only reference to a [Cell].
|
||||
/// Useful alternative to `&Cell` when an API wants to control where a [Cell]s value can be set.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CellView<'a, T: Copy>(&'a Cell<T>);
|
||||
|
||||
impl<T: Copy> CellView<'_, T> {
|
||||
#[inline(always)]
|
||||
pub fn get(self) -> T {
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> From<&'a Cell<T>> for CellView<'a, T> {
|
||||
#[inline(always)]
|
||||
fn from(value: &'a Cell<T>) -> Self {
|
||||
CellView(value)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "resistive-divider")]
|
||||
pub mod resistive_divider;
|
||||
@@ -1,43 +0,0 @@
|
||||
use crate::quantity::{Ohms, Volts};
|
||||
|
||||
/// Given the resistance of the second resistor in a resistive voltage divider, calculate the resistance of the first resistor.
|
||||
pub fn solve_r1(
|
||||
voltage_src: Volts<f32>,
|
||||
voltage_read: Volts<f32>,
|
||||
r2: Ohms<f32>,
|
||||
) -> Ohms<f32> {
|
||||
Ohms(r2.0 * (voltage_src.0 / voltage_read.0 - 1.0))
|
||||
}
|
||||
|
||||
/// Given the resistance of the first resistor in a resistive voltage divider, calculate the resistance of the second resistor.
|
||||
pub fn solve_r2(
|
||||
voltage_src: Volts<f32>,
|
||||
voltage_read: Volts<f32>,
|
||||
r1: Ohms<f32>,
|
||||
) -> Ohms<f32> {
|
||||
Ohms((r1.0 * voltage_read.0) / (voltage_src.0 - voltage_read.0))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::quantity::{OhmsVal, VoltsVal};
|
||||
use super::*;
|
||||
use float_cmp::assert_approx_eq;
|
||||
|
||||
#[test]
|
||||
fn solve_r1_test() {
|
||||
let resistance = solve_r1(3.3.volts(), 2.0.volts(), 1_000.0.ohms());
|
||||
|
||||
assert_approx_eq!(f32, 650.0, resistance.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_r2_test() {
|
||||
let resistance = solve_r2(3.3.volts(), 2.0.volts(), 1_000.0.ohms());
|
||||
|
||||
assert_approx_eq!(f32, 1538.462, resistance.0);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "pid")]
|
||||
pub mod pid;
|
||||
@@ -1,363 +0,0 @@
|
||||
//! A proportional-integral-derivative (PID) controller library.
|
||||
//!
|
||||
//! See [Pid] for the adjustable controller itself, as well as [ControlOutput] for the outputs and weights which you can use after setting up your controller. Follow the complete example below to setup your first controller!
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use physical::control::pid::Pid;
|
||||
//!
|
||||
//! // Create a new proportional-only PID controller with a setpoint of 15
|
||||
//! let mut pid = Pid::new(15.0, 100.0);
|
||||
//! pid.proportional_gain = 10.0;
|
||||
//! pid.proportional_limit = 100.0;
|
||||
//!
|
||||
//! // Input a measurement with an error of 5.0 from our setpoint
|
||||
//! let output = pid.next_control_output(10.0);
|
||||
//!
|
||||
//! // Show that the error is correct by multiplying by our kp
|
||||
//! assert_eq!(output, 50.0); // <--
|
||||
//!
|
||||
//! // It won't change on repeat; the controller is proportional-only
|
||||
//! let output = pid.next_control_output(10.0);
|
||||
//! assert_eq!(output, 50.0); // <--
|
||||
//!
|
||||
//! // Add a new integral term to the controller and input again
|
||||
//! pid.integral_gain = 1.0;
|
||||
//! pid.integral_limit = 100.0;
|
||||
//! let output = pid.next_control_output(10.0);
|
||||
//!
|
||||
//! // Now that the integral makes the controller stateful, it will change
|
||||
//! assert_eq!(output, 55.0); // <--
|
||||
//!
|
||||
//! // Add our final derivative term and match our setpoint target
|
||||
//! pid.derivative_gain = 2.0;
|
||||
//! pid.derivative_limit = 100.0;
|
||||
//! let output = pid.next_control_output(15.0);
|
||||
//!
|
||||
//! // The output will now say to go down due to the derivative
|
||||
//! assert_eq!(output, -5.0); // <--//!
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A trait for any numeric type usable in the PID controller
|
||||
///
|
||||
/// This trait is automatically implemented for all types that satisfy `PartialOrd + num_traits::Signed + Copy`. This includes all of the signed float types and builtin integer except for [isize]:
|
||||
/// - [i8]
|
||||
/// - [i16]
|
||||
/// - [i32]
|
||||
/// - [i64]
|
||||
/// - [i128]
|
||||
/// - [f32]
|
||||
/// - [f64]
|
||||
///
|
||||
/// As well as any user type that matches the requirements
|
||||
pub trait Number: PartialOrd + num_traits::Signed + Copy {}
|
||||
|
||||
// Implement `Number` for all types that
|
||||
// satisfy `PartialOrd + num_traits::Signed + Copy`.
|
||||
impl<T: PartialOrd + num_traits::Signed + Copy> Number for T {}
|
||||
|
||||
/// Adjustable proportional-integral-derivative (PID) controller.
|
||||
///
|
||||
/// This [`next_control_output`](Self::next_control_output) method is what's used to input new values into the controller to tell it what the current state of the system is. In the examples above it's only being used once, but realistically this will be a hot method. Please see [ControlOutput] for examples of how to handle these outputs; it's quite straight forward and mirrors the values of this structure in some ways.
|
||||
///
|
||||
/// The last item of note is that the gain and limit fields can be safely modified during use.
|
||||
///
|
||||
/// # Type Warning
|
||||
/// [Number] is abstract and can be used with anything from a [i32] to an [i128] (as well as user-defined types). Because of this, very small types might overflow during calculation in [`next_control_output`](Self::next_control_output). You probably don't want to use [i8] or user-defined types around that size so keep that in mind when designing your controller.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct Pid<T: Number> {
|
||||
/// Ideal setpoint to strive for.
|
||||
pub setpoint: T,
|
||||
/// Defines the overall output filter limit.
|
||||
pub output_limit: T,
|
||||
/// Proportional gain. The proportional component is dependant only on the error.
|
||||
/// It is the error * this value.
|
||||
pub proportional_gain: T,
|
||||
/// Integral gain. The integral component is dependent on the error and the integral term from the previous iteration.
|
||||
/// It is the previous integral + (error * this value).
|
||||
pub integral_gain: T,
|
||||
/// Derivative gain. The derivative component is dependent on the rate of change of the measurement.
|
||||
/// It is the (current measurement - previous measurement) * this value.
|
||||
pub derivative_gain: T,
|
||||
/// Limiter for the proportional term: `-p_limit <= P <= p_limit`.
|
||||
pub proportional_limit: T,
|
||||
/// Limiter for the integral term: `-i_limit <= I <= i_limit`.
|
||||
pub integral_limit: T,
|
||||
/// Limiter for the derivative term: `-d_limit <= D <= d_limit`.
|
||||
pub derivative_limit: T,
|
||||
/// Last calculated integral value if [Pid::i_gain] is used.
|
||||
integral_term: T,
|
||||
/// Previously found measurement whilst using the [Pid::next_control_output] method.
|
||||
prev_measurement: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Pid<T>
|
||||
where
|
||||
T: Number,
|
||||
{
|
||||
/// Creates a new controller with the target setpoint and the output limit
|
||||
pub fn new(setpoint: T, output_limit: T) -> Self {
|
||||
Self {
|
||||
setpoint,
|
||||
output_limit,
|
||||
proportional_gain: T::zero(),
|
||||
integral_gain: T::zero(),
|
||||
derivative_gain: T::zero(),
|
||||
proportional_limit: T::zero(),
|
||||
integral_limit: T::zero(),
|
||||
derivative_limit: T::zero(),
|
||||
integral_term: T::zero(),
|
||||
prev_measurement: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [Pid::setpoint] to target for this controller.
|
||||
pub fn setpoint(&mut self, setpoint: T) -> &mut Self {
|
||||
self.setpoint = setpoint;
|
||||
self
|
||||
}
|
||||
|
||||
/// Given a new measurement, calculates the next control setting.
|
||||
pub fn next_control_output(&mut self, measurement: T) -> T {
|
||||
// Calculate the error between the ideal setpoint and the current
|
||||
// measurement to compare against
|
||||
let error = self.setpoint - measurement;
|
||||
|
||||
// Calculate the proportional term and limit to it's individual limit
|
||||
let p_unbounded = error * self.proportional_gain;
|
||||
let p = apply_limit(self.proportional_limit, p_unbounded);
|
||||
|
||||
// Mitigate output jumps when ki(t) != ki(t-1).
|
||||
// While it's standard to use an error_integral that's a running sum of
|
||||
// just the error (no ki), because we support ki changing dynamically,
|
||||
// we store the entire term so that we don't need to remember previous
|
||||
// ki values.
|
||||
self.integral_term = self.integral_term + error * self.integral_gain;
|
||||
|
||||
// Mitigate integral windup: Don't want to keep building up error
|
||||
// beyond what i_limit will allow.
|
||||
self.integral_term = apply_limit(self.integral_limit, self.integral_term);
|
||||
|
||||
// Mitigate derivative kick: Use the derivative of the measurement
|
||||
// rather than the derivative of the error.
|
||||
let d_unbounded = -match self.prev_measurement {
|
||||
Some(prev_measurement) => measurement - prev_measurement,
|
||||
None => T::zero(),
|
||||
} * self.derivative_gain;
|
||||
self.prev_measurement = Some(measurement);
|
||||
let d = apply_limit(self.derivative_limit, d_unbounded);
|
||||
|
||||
// Calculate the final output by adding together the PID terms, then
|
||||
// apply the final defined output limit
|
||||
let output = p + self.integral_term + d;
|
||||
let output = apply_limit(self.output_limit, output);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Resets the integral term back to zero, this may drastically change the
|
||||
/// control output.
|
||||
pub fn reset_integral_term(&mut self) {
|
||||
self.integral_term = T::zero();
|
||||
}
|
||||
}
|
||||
|
||||
/// Saturating the input `value` according the absolute `limit` (`-abs(limit) <= output <= abs(limit)`).
|
||||
fn apply_limit<T: Number>(limit: T, value: T) -> T {
|
||||
num_traits::clamp(value, -limit.abs(), limit.abs())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Pid;
|
||||
|
||||
/// Proportional-only controller operation and limits
|
||||
#[test]
|
||||
fn proportional() {
|
||||
let mut pid = Pid::new(10.0, 100.0);
|
||||
pid.proportional_gain = 2.0;
|
||||
pid.proportional_limit = 100.0;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
assert_eq!(pid.setpoint, 10.0);
|
||||
|
||||
// Test simple proportional
|
||||
assert_eq!(pid.next_control_output(0.0), 20.0);
|
||||
|
||||
// Test proportional limit
|
||||
pid.proportional_limit = 10.0;
|
||||
assert_eq!(pid.next_control_output(0.0), 10.0);
|
||||
}
|
||||
|
||||
/// Derivative-only controller operation and limits
|
||||
#[test]
|
||||
fn derivative() {
|
||||
let mut pid = Pid::new(10.0, 100.0);
|
||||
pid.proportional_limit = 100.0;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
pid.derivative_gain = 2.0;
|
||||
|
||||
// Test that there's no derivative since it's the first measurement
|
||||
assert_eq!(pid.next_control_output(0.0), 0.0);
|
||||
|
||||
// Test that there's now a derivative
|
||||
assert_eq!(pid.next_control_output(5.0), -10.0);
|
||||
|
||||
// Test derivative limit
|
||||
pid.derivative_limit = 5.0;
|
||||
assert_eq!(pid.next_control_output(10.0), -5.0);
|
||||
}
|
||||
|
||||
/// Integral-only controller operation and limits
|
||||
#[test]
|
||||
fn integral() {
|
||||
let mut pid = Pid::new(10.0, 100.0);
|
||||
pid.proportional_limit = 0.0;
|
||||
pid.integral_gain = 2.0;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
|
||||
// Test basic integration
|
||||
assert_eq!(pid.next_control_output(0.0), 20.0);
|
||||
assert_eq!(pid.next_control_output(0.0), 40.0);
|
||||
assert_eq!(pid.next_control_output(5.0), 50.0);
|
||||
|
||||
// Test limit
|
||||
pid.integral_limit = 50.0;
|
||||
assert_eq!(pid.next_control_output(5.0), 50.0);
|
||||
// Test that limit doesn't impede reversal of error integral
|
||||
assert_eq!(pid.next_control_output(15.0), 40.0);
|
||||
|
||||
// Test that error integral accumulates negative values
|
||||
let mut pid2 = Pid::new(-10.0, 100.0);
|
||||
pid2.proportional_limit = 100.0;
|
||||
pid2.integral_gain = 2.0;
|
||||
pid2.integral_limit = 100.0;
|
||||
pid2.derivative_limit = 100.0;
|
||||
|
||||
assert_eq!(pid2.next_control_output(0.0), -20.0);
|
||||
assert_eq!(pid2.next_control_output(0.0), -40.0);
|
||||
|
||||
pid2.integral_limit = 50.0;
|
||||
assert_eq!(pid2.next_control_output(-5.0), -50.0);
|
||||
// Test that limit doesn't impede reversal of error integral
|
||||
assert_eq!(pid2.next_control_output(-15.0), -40.0);
|
||||
}
|
||||
|
||||
/// Checks that a full PID controller's limits work properly through multiple output iterations
|
||||
#[test]
|
||||
fn output_limit() {
|
||||
let mut pid = Pid::new(10.0, 1.0);
|
||||
pid.proportional_gain = 2.0;
|
||||
pid.proportional_limit = 100.0;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
|
||||
let out = pid.next_control_output(0.0);
|
||||
assert_eq!(out, 1.0);
|
||||
|
||||
let out = pid.next_control_output(20.0);
|
||||
assert_eq!(out, -1.0);
|
||||
}
|
||||
|
||||
/// Combined PID operation
|
||||
#[test]
|
||||
fn pid() {
|
||||
let mut pid = Pid::new(10.0, 100.0);
|
||||
pid.proportional_gain = 1.0;
|
||||
pid.proportional_limit = 100.0;
|
||||
pid.integral_gain = 0.1;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_gain = 1.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
|
||||
let out = pid.next_control_output(0.0);
|
||||
assert_eq!(out, 11.0);
|
||||
|
||||
let out = pid.next_control_output(5.0);
|
||||
assert_eq!(out, 1.5);
|
||||
|
||||
let out = pid.next_control_output(11.0);
|
||||
assert_eq!(out, -5.6);
|
||||
|
||||
let out = pid.next_control_output(10.0);
|
||||
assert_eq!(out, 2.4);
|
||||
}
|
||||
|
||||
// NOTE: use for new test in future: /// Full PID operation with mixed float checking to make sure they're equal
|
||||
/// PID operation with zero'd values, checking to see if different floats equal each other
|
||||
#[test]
|
||||
fn floats_zeros() {
|
||||
let mut pid_f32 = Pid::new(10.0f32, 100.0);
|
||||
pid_f32.proportional_limit = 100.0;
|
||||
pid_f32.integral_limit = 100.0;
|
||||
pid_f32.derivative_limit = 100.0;
|
||||
|
||||
let mut pid_f64 = Pid::new(10.0, 100.0f64);
|
||||
pid_f64.proportional_limit = 100.0;
|
||||
pid_f64.integral_limit = 100.0;
|
||||
pid_f64.derivative_limit = 100.0;
|
||||
|
||||
for _ in 0..5 {
|
||||
assert_eq!(pid_f32.next_control_output(0.0), pid_f64.next_control_output(0.0) as f32);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: use for new test in future: /// Full PID operation with mixed signed integer checking to make sure they're equal
|
||||
/// PID operation with zero'd values, checking to see if different floats equal each other
|
||||
#[test]
|
||||
fn signed_integers_zeros() {
|
||||
let mut pid_i8 = Pid::new(10i8, 100);
|
||||
pid_i8.proportional_limit = 100;
|
||||
pid_i8.integral_limit = 100;
|
||||
pid_i8.derivative_limit = 100;
|
||||
|
||||
let mut pid_i32 = Pid::new(10i32, 100);
|
||||
pid_i32.proportional_limit = 100;
|
||||
pid_i32.integral_limit = 100;
|
||||
pid_i32.derivative_limit = 100;
|
||||
|
||||
for _ in 0..5 {
|
||||
assert_eq!(pid_i32.next_control_output(0), pid_i8.next_control_output(0i8) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
/// See if the controller can properly target to the setpoint after 2 output iterations
|
||||
#[test]
|
||||
fn setpoint() {
|
||||
let mut pid = Pid::new(10.0, 100.0);
|
||||
pid.proportional_gain = 1.0;
|
||||
pid.proportional_limit = 100.0;
|
||||
pid.integral_gain = 0.1;
|
||||
pid.integral_limit = 100.0;
|
||||
pid.derivative_gain = 1.0;
|
||||
pid.derivative_limit = 100.0;
|
||||
|
||||
let out = pid.next_control_output(0.0);
|
||||
assert_eq!(out, 11.0);
|
||||
|
||||
pid.setpoint(0.0);
|
||||
|
||||
assert_eq!(pid.next_control_output(0.0), 1.0);
|
||||
}
|
||||
|
||||
/// Make sure negative limits don't break the controller
|
||||
#[test]
|
||||
fn negative_limits() {
|
||||
let mut pid = Pid::new(10.0f32, -10.0);
|
||||
pid.proportional_gain = 1.0;
|
||||
pid.proportional_limit = -50.0;
|
||||
pid.integral_gain = 1.0;
|
||||
pid.integral_limit = -50.0;
|
||||
pid.derivative_gain = 1.0;
|
||||
pid.derivative_limit = -50.0;
|
||||
|
||||
let out = pid.next_control_output(0.0);
|
||||
assert_eq!(out, 10.0);
|
||||
}
|
||||
}
|
||||
+24
-19
@@ -1,10 +1,5 @@
|
||||
/// Indicates the transducer value is known to be impossible.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InvalidValue;
|
||||
use crate::transducer::InvalidValue;
|
||||
|
||||
/// Indicates that the encoded data is not valid for the type.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
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
|
||||
/// should consist of attempting to alert another system for maintenance and attempting to shut down
|
||||
@@ -19,23 +14,33 @@ pub enum CriticalError {
|
||||
InvalidValue(InvalidValue),
|
||||
}
|
||||
|
||||
/// A state of this type may mean the program has encountered an error that prevents it from continuing to run
|
||||
/// and should attempt to enter a safe terminal state.
|
||||
/// e.g. Certain [Err]s
|
||||
pub trait Terminal<T, E> {
|
||||
impl CriticalError {
|
||||
//TODO: Switch to using ! as the return type for the FnOnce when the feature is stabilized
|
||||
fn terminal(self, terminate: impl FnOnce(E)) -> T;
|
||||
pub fn emergency_procedure(self, procedure: impl FnOnce(CriticalError)) -> ! {
|
||||
procedure(self);
|
||||
//TODO: Remove this panic when we switch to ! return type
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
impl <T, E> Terminal<T, E> for Result<T, E> {
|
||||
fn terminal(self, terminate: impl FnOnce(E)) -> T {
|
||||
/// [Result] where error type is [CriticalError].
|
||||
pub trait CriticalErrResult: Copy {
|
||||
type Value: Copy;
|
||||
|
||||
//TODO: Switch to using ! as the return type for the FnOnce when the feature is stabilized
|
||||
/// Execute emergency procedure in the event of a critical, the emergency procedure cannot
|
||||
/// return. It should usually terminate the program, potentially rebooting the device in some sort of recovery mode.
|
||||
fn err_emproc(self, procedure: impl FnOnce(CriticalError)) -> Self::Value;
|
||||
}
|
||||
|
||||
impl<T: Copy> CriticalErrResult for Result<T, CriticalError> {
|
||||
type Value = T;
|
||||
|
||||
//TODO: Switch to using ! as the return type for the FnOnce when the ! feature is stabilized
|
||||
fn err_emproc(self, procedure: impl FnOnce(CriticalError)) -> Self::Value {
|
||||
match self {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
//TODO: Remove panic when terminate returns -> !
|
||||
terminate(error);
|
||||
panic!()
|
||||
},
|
||||
Ok(val) => val,
|
||||
Err(error) => error.emergency_procedure(procedure),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-7
@@ -1,11 +1,7 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![no_std]
|
||||
|
||||
pub mod transducer;
|
||||
pub mod control;
|
||||
pub mod error;
|
||||
|
||||
pub mod adc;
|
||||
pub mod circuit;
|
||||
pub mod quantity;
|
||||
pub mod cell;
|
||||
mod error;
|
||||
|
||||
pub use error::CriticalError;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
//----- Watts per Square Meter ----------------------------------
|
||||
quantity_type! {WattsPerSquareMeter, "W/m²"}
|
||||
@@ -1,170 +0,0 @@
|
||||
mod irradiance;
|
||||
mod resistance;
|
||||
mod temperature;
|
||||
mod voltage;
|
||||
mod volume;
|
||||
mod volume_rate;
|
||||
mod pressure;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
pub use defmt_impl::*;
|
||||
|
||||
pub use irradiance::*;
|
||||
pub use resistance::*;
|
||||
pub use temperature::*;
|
||||
pub use voltage::*;
|
||||
pub use volume::*;
|
||||
pub use volume_rate::*;
|
||||
pub use pressure::*;
|
||||
|
||||
use core::fmt::Display;
|
||||
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 trait Quantity<V: Value>: Copy + PartialEq + PartialOrd + Add + Sub {
|
||||
fn value(self) -> V;
|
||||
|
||||
fn symbol() -> &'static str;
|
||||
|
||||
/// Returns a wrapper that implements [Display] and [defmt::Format] for [Quantity]s with core number values.
|
||||
///
|
||||
/// - `rounding` - Sets the number of decimal places to round to when formatting (currently ignored with defmt).
|
||||
#[inline(always)]
|
||||
fn fmt(self, rounding: Option<usize>) -> Fmt<V, Self> {
|
||||
Fmt::new(self, rounding)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
|
||||
impl<V> Value for V where V: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Fmt<V: Value, Q: Quantity<V>> {
|
||||
quantity: Q,
|
||||
rounding: Option<usize>,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V: Value, Q: Quantity<V>> Fmt<V, Q> {
|
||||
#[inline(always)]
|
||||
fn new(quantity: Q, rounding: Option<usize>) -> Self {
|
||||
Self {
|
||||
quantity,
|
||||
rounding,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Value, Q: Quantity<V>> Display for Fmt<V, Q> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self.rounding {
|
||||
Some(places) => {
|
||||
write!(f, "{:.prec$} {}", self.quantity.value(), Q::symbol(), prec = places)
|
||||
},
|
||||
None => write!(f, "{} {}", self.quantity.value(), Q::symbol()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
mod defmt_impl {
|
||||
use super::*;
|
||||
use defmt::{write, Format, Formatter};
|
||||
|
||||
impl<Q: Quantity<u8>> Format for Fmt<u8, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u16>> Format for Fmt<u16, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u32>> Format for Fmt<u32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u64>> Format for Fmt<u64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u128>> Format for Fmt<u128, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<usize>> Format for Fmt<usize, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i8>> Format for Fmt<i8, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i16>> Format for Fmt<i16, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i32>> Format for Fmt<i32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i64>> Format for Fmt<i64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i128>> Format for Fmt<i128, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<isize>> Format for Fmt<isize, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<f32>> Format for Fmt<f32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<f64>> Format for Fmt<f64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
use crate::quantity::{KILO, Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
const PASCALS_PER_TORR: f64 = 101325.0 / 760.0;
|
||||
const KILO_PASCALS_PER_PSI: f64 = 6.894757293168364;
|
||||
|
||||
//----- Pascals ----------------------------------
|
||||
quantity_type! {Pascals, "Pa"}
|
||||
|
||||
impl<V: Value> Pascals<V> {
|
||||
#[inline]
|
||||
pub fn to_kilo_pascals(self) -> KiloPascals<V> {
|
||||
let divisor = V::from_u16(KILO).unwrap();
|
||||
KiloPascals(self.0 / divisor)
|
||||
}
|
||||
|
||||
// Should this be in separate imple with `V` bound to `num_traits::Float`?
|
||||
#[inline]
|
||||
pub fn to_torr(self) -> Torr<V> {
|
||||
let divisor = V::from_f64(PASCALS_PER_TORR).unwrap();
|
||||
Torr(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Kilopascals ----------------------------------
|
||||
quantity_type! {KiloPascals, "kPa"}
|
||||
|
||||
impl<V: Value> KiloPascals<V> {
|
||||
#[inline]
|
||||
pub fn to_pascals(self) -> Pascals<V> {
|
||||
let multiplier = V::from_u16(KILO).unwrap();
|
||||
Pascals(self.0 * multiplier)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_psi(self) -> Psi<V> {
|
||||
let divisor = V::from_f64(KILO_PASCALS_PER_PSI).unwrap();
|
||||
Psi(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Torr ----------------------------------
|
||||
quantity_type! {Torr, "Torr"}
|
||||
|
||||
// Should this bound `V` to `num_traits::Float`?
|
||||
impl<V: Value> Torr<V> {
|
||||
#[inline]
|
||||
pub fn to_pascals(self) -> Pascals<V> {
|
||||
let multiplier = V::from_f64(PASCALS_PER_TORR).unwrap();
|
||||
Pascals(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- PSI ----------------------------------
|
||||
quantity_type! {Psi, "PSI"}
|
||||
|
||||
impl<V: Value> Psi<V> {
|
||||
#[inline]
|
||||
pub fn to_kilo_pascals(self) -> KiloPascals<V> {
|
||||
let multiplier = V::from_f64(KILO_PASCALS_PER_PSI).unwrap();
|
||||
KiloPascals(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use float_cmp::assert_approx_eq;
|
||||
|
||||
#[test]
|
||||
fn pascals_kilo_pascals() {
|
||||
let pascals: Pascals<u32> = 1_000.pascals();
|
||||
let kilo_pascals: KiloPascals<u32> = 1.kilo_pascals();
|
||||
|
||||
assert_eq!(pascals.0, kilo_pascals.to_pascals().0);
|
||||
assert_eq!(kilo_pascals.0, pascals.to_kilo_pascals().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn torr_pascals() {
|
||||
let torr: Torr<f32> = 7.5.torr();
|
||||
let pascals: Pascals<f32> = 999.9177631578947.pascals();
|
||||
|
||||
assert_approx_eq!(f32, pascals.to_torr().0, torr.0);
|
||||
assert_approx_eq!(f32, torr.to_pascals().0, pascals.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psi_kilo_pascals() {
|
||||
let psi: Psi<f32> = 2.5.psi();
|
||||
let kilo_pascals: KiloPascals<f32> = 17.23689323292091.kilo_pascals();
|
||||
|
||||
assert_approx_eq!(f32, psi.to_kilo_pascals().0, kilo_pascals.0);
|
||||
assert_approx_eq!(f32, kilo_pascals.to_psi().0, psi.0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{Quantity, Value};
|
||||
|
||||
//----- Ohms ----------------------------------
|
||||
quantity_type! {Ohms, "Ω"}
|
||||
@@ -1,96 +0,0 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{DECI, Quantity, Value};
|
||||
|
||||
const KELVIN_CELSIUS_OFFSET: f64 = 273.15;
|
||||
|
||||
//----- Kelvins ----------------------------------
|
||||
quantity_type! {Kelvins, "K"}
|
||||
|
||||
impl <V: Value> Kelvins<V> {
|
||||
#[inline]
|
||||
pub fn to_celsius(self) -> Celsius<V> {
|
||||
//TODO: Make const
|
||||
let offset = V::from_f64(KELVIN_CELSIUS_OFFSET).unwrap();
|
||||
Celsius(self.0 - offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_deci_kelvins(self) -> DeciKelvins<V> {
|
||||
let multiplier = V::from_u8(DECI).unwrap();
|
||||
DeciKelvins(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Decikelvins ----------------------------------
|
||||
quantity_type! {DeciKelvins, "dK"}
|
||||
|
||||
impl<V: Value> DeciKelvins<V> {
|
||||
#[inline]
|
||||
pub fn to_kelvins(self) -> Kelvins<V> {
|
||||
let divisor = V::from_u8(DECI).unwrap();
|
||||
Kelvins(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Degrees Celsius ----------------------------------
|
||||
quantity_type! {Celsius, "℃"}
|
||||
|
||||
impl <V: Value> Celsius<V> {
|
||||
#[inline]
|
||||
pub fn to_kelvins(self) -> Kelvins<V> {
|
||||
//TODO: Make const
|
||||
let offset = V::from_f64(KELVIN_CELSIUS_OFFSET).unwrap();
|
||||
Kelvins(self.0 + offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_deci_celsius(self) -> DeciCelsius<V> {
|
||||
let multiplier = V::from_u8(DECI).unwrap();
|
||||
DeciCelsius(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Deci Degrees Celsius ----------------------------------
|
||||
quantity_type! {DeciCelsius, "d℃"}
|
||||
|
||||
impl<V: Value> DeciCelsius<V> {
|
||||
#[inline]
|
||||
pub fn to_celsius(self) -> Celsius<V> {
|
||||
let divisor = V::from_u8(DECI).unwrap();
|
||||
Celsius(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use float_cmp::assert_approx_eq;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_f32() {
|
||||
let kelvins: Kelvins<f32> = 298.15.kelvins();
|
||||
let deci_kelvins: DeciKelvins<f32> = 2_981.5.deci_kelvins();
|
||||
let celsius: Celsius<f32> = 25.0.celsius();
|
||||
let deci_celsius: DeciCelsius<f32> = 250.0.deci_celsius();
|
||||
|
||||
assert_approx_eq!(f32, kelvins.to_celsius().0, celsius.0);
|
||||
assert_approx_eq!(f32, celsius.to_kelvins().0, kelvins.0);
|
||||
|
||||
assert_approx_eq!(f32, deci_kelvins.to_kelvins().0, kelvins.0);
|
||||
assert_approx_eq!(f32, kelvins.to_deci_kelvins().0, deci_kelvins.0);
|
||||
|
||||
assert_approx_eq!(f32, deci_celsius.to_celsius().0, celsius.0);
|
||||
assert_approx_eq!(f32, celsius.to_deci_celsius().0, deci_celsius.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_u16() {
|
||||
let kelvins: Kelvins<u16> = 298.kelvins();
|
||||
let celsius: Celsius<u16> = 25.celsius();
|
||||
|
||||
assert_eq!(celsius.0, kelvins.to_celsius().0);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{Quantity, Value};
|
||||
|
||||
const VOLT_MV_RATIO: u16 = 1_000;
|
||||
|
||||
//----- Volts ----------------------------------
|
||||
quantity_type! {Volts, "V"}
|
||||
|
||||
impl<V: Value> Volts<V> {
|
||||
#[inline]
|
||||
pub fn to_milli_volts(self) -> MilliVolts<V> {
|
||||
let multiplier = V::from_u16(VOLT_MV_RATIO).unwrap();
|
||||
MilliVolts(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Millivolts ----------------------------------
|
||||
quantity_type! {MilliVolts, "mV"}
|
||||
|
||||
impl<V: Value> MilliVolts<V> {
|
||||
pub fn to_volts(self) -> Volts<V> {
|
||||
let divisor = V::from_u16(VOLT_MV_RATIO).unwrap();
|
||||
Volts(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use float_cmp::assert_approx_eq;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_u32() {
|
||||
let volts: Volts<u32> = 3.volts();
|
||||
let millivolts: MilliVolts<u32> = 3_000.milli_volts();
|
||||
|
||||
assert_eq!(volts.to_milli_volts().0, millivolts.0);
|
||||
assert_eq!(millivolts.to_volts().0, volts.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_f64() {
|
||||
let volts: Volts<f64> = 3.0.volts();
|
||||
let millivolts: MilliVolts<f64> = 3_000.0.milli_volts();
|
||||
|
||||
assert_approx_eq!(f64, volts.to_milli_volts().0, millivolts.0);
|
||||
assert_approx_eq!(f64, millivolts.to_volts().0, volts.0);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
use crate::quantity::MILLI;
|
||||
|
||||
//----- Liters ----------------------------------
|
||||
quantity_type! {Liters, "L"}
|
||||
|
||||
impl<V: Value> Liters<V> {
|
||||
#[inline]
|
||||
pub fn to_milli_liters(self) -> MilliLiters<V> {
|
||||
let multiplier = V::from_u16(MILLI).unwrap();
|
||||
MilliLiters(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Milliliters ----------------------------------
|
||||
quantity_type! {MilliLiters, "mL"}
|
||||
|
||||
impl<V: Value> MilliLiters<V> {
|
||||
#[inline]
|
||||
pub fn to_liters(self) -> MilliLiters<V> {
|
||||
let divisor = V::from_u16(MILLI).unwrap();
|
||||
MilliLiters(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_u32() {
|
||||
let liters: Liters<u32> = 12.liters();
|
||||
let milli_liters: MilliLiters<u32> = 12_000.milli_liters();
|
||||
|
||||
assert_eq!(liters.0, milli_liters.to_liters().0);
|
||||
assert_eq!(milli_liters.0, liters.to_milli_liters().0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
//----- Liters per Minute ----------------------------------
|
||||
quantity_type! {LitersPerMinute, "L/min"}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub trait Poll {
|
||||
type Value: Copy;
|
||||
type Error: Copy;
|
||||
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error>;
|
||||
}
|
||||
@@ -1,3 +1,62 @@
|
||||
use core::cell::Cell;
|
||||
use crate::cell::CellView;
|
||||
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
mod part;
|
||||
|
||||
pub use part::*;
|
||||
|
||||
// Initialisation will always be async and won't complete until a state is available for all
|
||||
// stateful transducers.
|
||||
pub trait Stateful {
|
||||
type Value: Copy;
|
||||
|
||||
fn state_cell(&self) -> CellView<Self::Value>;
|
||||
|
||||
fn state(&self) -> Self::Value;
|
||||
}
|
||||
|
||||
pub struct State<T: Copy> {
|
||||
state_cell: Cell<T>,
|
||||
}
|
||||
|
||||
impl<T: Copy> State<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(state_cell: Cell<T>) -> Self {
|
||||
Self { state_cell }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&self, value: T) {
|
||||
self.state_cell.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Stateful for State<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn state_cell(&self) -> CellView<Self::Value> {
|
||||
(&self.state_cell).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state(&self) -> Self::Value {
|
||||
self.state_cell.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> From<Cell<T>> for State<T> {
|
||||
#[inline(always)]
|
||||
fn from(state_cell: Cell<T>) -> Self {
|
||||
State::new(state_cell)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Error ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
/// Indicates the transducer value is statically known to be impossible.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InvalidValue;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use crate::transducer::InvalidValue;
|
||||
|
||||
pub trait Output {
|
||||
type Value: Copy;
|
||||
|
||||
//TODO: Should this be maybe async?
|
||||
fn output(&mut self, setting: Self::Value) -> Result<(), InvalidValue>;
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
use crate::error::InvalidValue;
|
||||
use crate::quantity::{DeciCelsius, MilliVolts, Quantity};
|
||||
use crate::transducer::InvalidValue;
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::f32;
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
const MIN_VOLTS: f32 = -0.55;
|
||||
const MAX_VOLTS: f32 = 1.50;
|
||||
const SCALE_FACTOR: f32 = 100.0;
|
||||
|
||||
#[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);
|
||||
voltage: f32::ElectricPotential,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let volts = voltage.get::<volt>();
|
||||
|
||||
if voltage >= MIN_VOLTAGE && voltage <= MAX_VOLTAGE {
|
||||
Ok(DeciCelsius(voltage.value()))
|
||||
if volts >= MIN_VOLTS && volts <= MAX_VOLTS {
|
||||
let celsius = volts * SCALE_FACTOR;
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,5 @@ mod thermocouple;
|
||||
#[cfg(feature = "lm35")]
|
||||
pub mod lm35;
|
||||
|
||||
#[cfg(feature = "thermistor")]
|
||||
pub mod thermistor;
|
||||
|
||||
#[cfg(feature = "thermocouple-k")]
|
||||
#[cfg(feature = "thermocouple_k")]
|
||||
pub use thermocouple::type_k as thermocouple_k;
|
||||
@@ -1,38 +0,0 @@
|
||||
use crate::quantity::{Kelvins, Ohms};
|
||||
use libm::{log, logf};
|
||||
|
||||
/// Convert thermistor resistance to a temperature using beta parameter equation
|
||||
pub fn convert_beta(
|
||||
resistance: Ohms<f32>,
|
||||
beta: f32,
|
||||
reference_temp: Kelvins<f32>,
|
||||
reference_resist: Ohms<f32>,
|
||||
) -> Kelvins<f32> {
|
||||
let kelvins = 1.0 / ((logf(resistance.0 / reference_resist.0) / beta) + 1.0 / reference_temp.0);
|
||||
Kelvins(kelvins)
|
||||
}
|
||||
|
||||
/// Convert thermistor resistance to a temperature using Steinhart-Hart equation
|
||||
pub fn convert_steinhart(resistance: Ohms<f64>, a: f64, b: f64, c: f64) -> Kelvins<f32> {
|
||||
let log_omhs = log(resistance.0);
|
||||
let kelvins = 1.0 / (a + b * log_omhs + c * log_omhs * log_omhs * log_omhs);
|
||||
Kelvins(kelvins as f32)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use float_cmp::assert_approx_eq;
|
||||
|
||||
use crate::quantity::{OhmsVal, KelvinsVal};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_beta_test() {
|
||||
let temperature = convert_beta(1538.462.ohms(), 3950.0, 298.15.kelvins(), 100_000.0.ohms());
|
||||
|
||||
assert_approx_eq!(f32, 435.31073, temperature.0);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
#[cfg(feature = "thermocouple-k")]
|
||||
#[cfg(feature = "thermocouple_k")]
|
||||
pub mod type_k;
|
||||
@@ -1,13 +1,13 @@
|
||||
//! Note - Thermocouple conversion uses [f64] arithmetic internally.
|
||||
|
||||
use libm::pow;
|
||||
use crate::error::InvalidValue;
|
||||
use crate::quantity::{Celsius, MilliVolts, Quantity};
|
||||
use crate::transducer::InvalidValue;
|
||||
use libm::powf;
|
||||
use uom::si::electric_potential::{millivolt, volt};
|
||||
use uom::si::f32;
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
fn _convert(
|
||||
voltage: MilliVolts<f64>,
|
||||
) -> Result<Celsius<f32>, InvalidValue> {
|
||||
let mv = voltage.value();
|
||||
voltage: f32::ElectricPotential,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let mv = voltage.get::<millivolt>();
|
||||
let mv_pow2 = mv * mv;
|
||||
let mv_pow3 = mv_pow2 * mv;
|
||||
let mv_pow4 = mv_pow3 * mv;
|
||||
@@ -27,7 +27,7 @@ fn _convert(
|
||||
+ -1.0450598E-2 * mv_pow7
|
||||
+ -5.1920577E-4 * mv_pow8;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else if mv > 0.0 && mv < 20.644 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
@@ -43,7 +43,7 @@ fn _convert(
|
||||
+ 1.057734E-6 * mv_pow8
|
||||
+ -1.052755E-8 * mv_pow9;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else if mv >= 20.644 && mv <= 54.886 {
|
||||
let celsius = 1.318058e2
|
||||
+ 4.830222E+1 * mv
|
||||
@@ -53,7 +53,7 @@ fn _convert(
|
||||
+ 8.802193E-6 * mv_pow5
|
||||
+ -3.110810E-8 * mv_pow6;
|
||||
|
||||
Ok(Celsius(celsius as f32))
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
@@ -68,12 +68,13 @@ fn _convert(
|
||||
/// 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)?;
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let base_celsius = _convert(voltage)?.get::<degree_celsius>();
|
||||
let r_junction_celsius = r_junction.get::<degree_celsius>();
|
||||
|
||||
Ok(base_temp + r_junction)
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(base_celsius + r_junction_celsius))
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
@@ -84,11 +85,11 @@ pub fn convert_direct(
|
||||
/// 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> {
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_seebeck(r_junction)?;
|
||||
_convert(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
_convert(voltage + voltage_correction)
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
@@ -99,17 +100,18 @@ pub fn convert_seebeck(
|
||||
/// 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> {
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_poly(r_junction)?;
|
||||
_convert(MilliVolts(voltage.0 + voltage_correction.0 as f64))
|
||||
_convert(voltage + voltage_correction)
|
||||
}
|
||||
|
||||
//TODO: This is not working, check libm pow.
|
||||
pub fn temp_to_voltage_poly(
|
||||
temperature: Celsius<f64>,
|
||||
) -> Result<MilliVolts<f32>, InvalidValue> {
|
||||
let celsius = temperature.value();
|
||||
temperature: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ElectricPotential, InvalidValue> {
|
||||
let celsius = temperature.get::<degree_celsius>();
|
||||
let cel_pow2 = celsius * celsius;
|
||||
let cel_pow3 = cel_pow2 * celsius;
|
||||
let cel_pow4 = cel_pow3 * celsius;
|
||||
@@ -133,11 +135,11 @@ pub fn temp_to_voltage_poly(
|
||||
+ -0.198892668780E-19 * cel_pow9
|
||||
+ -0.163226974860E-22 * cel_pow10;
|
||||
|
||||
Ok(MilliVolts(mv as f32))
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} 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 addition = powf(0.1185976, exp);
|
||||
|
||||
let mv = -0.176004136860E-01
|
||||
+ 0.389212049750E-01 * celsius
|
||||
@@ -151,7 +153,7 @@ pub fn temp_to_voltage_poly(
|
||||
+ -0.121047212750E-25 * cel_pow9
|
||||
+ addition;
|
||||
|
||||
Ok(MilliVolts(mv as f32))
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
@@ -159,10 +161,12 @@ pub fn temp_to_voltage_poly(
|
||||
|
||||
#[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()))
|
||||
temperature: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ElectricPotential, InvalidValue> {
|
||||
let celsius = temperature.get::<degree_celsius>();
|
||||
if celsius >= -2.0 && celsius <= 800.0 {
|
||||
let mv = 0.041 * celsius;
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user