Compare commits
18 Commits
master
..
d37d99537e
| Author | SHA1 | Date | |
|---|---|---|---|
| d37d99537e | |||
| 46abb2150c | |||
| f1f5947f22 | |||
| a3d03ce29b | |||
| e0757847cb | |||
| 753aa6c56e | |||
| 38044cb945 | |||
| 581be0f71d | |||
| 01280c79fe | |||
| ffe0efede0 | |||
| 346c52e617 | |||
| babfb92222 | |||
| b299f59d9f | |||
| 93efdd247f | |||
| 6fc828e864 | |||
| 886fbf0020 | |||
| 407aaa951c | |||
| 1a44ea892b |
+26
-40
@@ -6,17 +6,18 @@ members = [
|
|||||||
# Device types
|
# Device types
|
||||||
"node",
|
"node",
|
||||||
"commander",
|
"commander",
|
||||||
# Drivers
|
# Peripherals
|
||||||
"drivers/ads1256/types",
|
|
||||||
"drivers/ads1256/driver",
|
# Peripheral components
|
||||||
# Meta
|
"peripheral-components/ads1256/*",
|
||||||
"generate-quantity",
|
# Macros
|
||||||
|
"macros/node-poll-variants",
|
||||||
# Examples
|
# Examples
|
||||||
"examples/ads1256"
|
"examples/ads1256"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.6"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.bfpower.io/BFPOWER/physical"
|
repository = "https://git.bfpower.io/BFPOWER/physical"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -29,8 +30,11 @@ version = "0.2.*"
|
|||||||
default-features = false
|
default-features = false
|
||||||
[workspace.dependencies.libm]
|
[workspace.dependencies.libm]
|
||||||
version = "0.2.*"
|
version = "0.2.*"
|
||||||
[workspace.dependencies.float-cmp]
|
# Units of measurement
|
||||||
version = "0.9.*"
|
[workspace.dependencies.uom]
|
||||||
|
version = "0.36.*"
|
||||||
|
default-features = false
|
||||||
|
features = ["f32", "si"]
|
||||||
# Logging
|
# Logging
|
||||||
[workspace.dependencies.tracing]
|
[workspace.dependencies.tracing]
|
||||||
version = "0.1.*"
|
version = "0.1.*"
|
||||||
@@ -59,6 +63,13 @@ version = "0.7.*"
|
|||||||
[workspace.dependencies.panic-probe]
|
[workspace.dependencies.panic-probe]
|
||||||
version = "0.3.*"
|
version = "0.3.*"
|
||||||
features = ["print-defmt"]
|
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
|
# Embassy
|
||||||
[workspace.dependencies.embassy-futures]
|
[workspace.dependencies.embassy-futures]
|
||||||
version = "0.1.*"
|
version = "0.1.*"
|
||||||
@@ -73,25 +84,18 @@ version = "0.1.*"
|
|||||||
[workspace.dependencies.embassy-executor]
|
[workspace.dependencies.embassy-executor]
|
||||||
version = "0.5.*"
|
version = "0.5.*"
|
||||||
features = ["defmt", "arch-cortex-m", "integrated-timers", "executor-interrupt", "executor-thread"]
|
features = ["defmt", "arch-cortex-m", "integrated-timers", "executor-interrupt", "executor-thread"]
|
||||||
[workspace.dependencies.embassy-usb]
|
|
||||||
version = "0.2.*"
|
|
||||||
features = ["defmt"]
|
|
||||||
[workspace.dependencies.embassy-stm32]
|
[workspace.dependencies.embassy-stm32]
|
||||||
version = "0.1.*"
|
version = "0.1.*"
|
||||||
features = ["defmt", "unstable-pac"]
|
features = ["defmt", "unstable-pac"]
|
||||||
[workspace.dependencies.embassy-nrf]
|
[workspace.dependencies.embassy-nrf]
|
||||||
version = "0.1.*"
|
version = "0.1.*"
|
||||||
features = ["defmt"]
|
features = ["defmt"]
|
||||||
# Meta
|
# Macros
|
||||||
[workspace.dependencies.derive_more]
|
|
||||||
version = "0.99.*"
|
|
||||||
[workspace.dependencies.syn]
|
[workspace.dependencies.syn]
|
||||||
version = "2.0.*"
|
version = "2.0.*"
|
||||||
features = ["extra-traits", "parsing"]
|
features = ["extra-traits", "parsing"]
|
||||||
[workspace.dependencies.quote]
|
[workspace.dependencies.quote]
|
||||||
version = "1.0.*"
|
version = "1.0.*"
|
||||||
[workspace.dependencies.proc-macro2]
|
|
||||||
version = "1.0.*"
|
|
||||||
[workspace.dependencies.trybuild]
|
[workspace.dependencies.trybuild]
|
||||||
version = "1.0.*"
|
version = "1.0.*"
|
||||||
|
|
||||||
@@ -108,33 +112,15 @@ readme.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["num-traits/std"]
|
thermocouple_k = []
|
||||||
libm = ["dep:libm", "num-traits/libm"]
|
|
||||||
resistive-divider = []
|
|
||||||
thermocouple-k = ["libm"]
|
|
||||||
thermistor = ["libm"]
|
|
||||||
lm35 = []
|
lm35 = []
|
||||||
pid = []
|
pid = []
|
||||||
stm32 = []
|
|
||||||
|
|
||||||
[dependencies.generate-quantity]
|
[dependencies]
|
||||||
path = "generate-quantity"
|
uom = { workspace = true }
|
||||||
[dependencies.num-traits]
|
num-traits = { workspace = true }
|
||||||
workspace = true
|
libm = { workspace = true }
|
||||||
[dependencies.derive_more]
|
serde = { workspace = true, optional = true }
|
||||||
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
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------------------------------------------
|
||||||
#----- Profiles ------------------------
|
#----- 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
|
* 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
|
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
|
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.
|
||||||
* Commander: A commander hosts nodes. It performs long running computations and directs nodes based on the results.
|
* 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"))']
|
[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"
|
runner = "probe-rs run --chip STM32F411CEUx"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|||||||
@@ -7,11 +7,21 @@ repository.workspace = true
|
|||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies.ads1256]
|
|
||||||
path = "../../drivers/ads1256/driver"
|
|
||||||
[dependencies.physical]
|
[dependencies.physical]
|
||||||
path = "../.."
|
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]
|
[dependencies.defmt]
|
||||||
workspace = true
|
workspace = true
|
||||||
[dependencies.defmt-rtt]
|
[dependencies.defmt-rtt]
|
||||||
@@ -31,7 +41,7 @@ workspace = true
|
|||||||
[dependencies.embassy-time]
|
[dependencies.embassy-time]
|
||||||
workspace = true
|
workspace = true
|
||||||
features = ["tick-hz-16_000_000"]
|
features = ["tick-hz-16_000_000"]
|
||||||
|
[dependencies.embassy-sync]
|
||||||
|
workspace = true
|
||||||
[dependencies.panic-probe]
|
[dependencies.panic-probe]
|
||||||
workspace = true
|
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
|
readme.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[features]
|
|
||||||
comms = []
|
|
||||||
single-packet-msgs = []
|
|
||||||
usb = ["embassy-usb"]
|
|
||||||
stm32 = ["embassy-stm32", "physical/stm32"]
|
|
||||||
|
|
||||||
[dependencies.physical]
|
[dependencies.physical]
|
||||||
path = ".."
|
path = ".."
|
||||||
[dependencies.embedded-hal]
|
[dependencies.embedded-hal]
|
||||||
@@ -21,9 +15,8 @@ workspace = true
|
|||||||
workspace = true
|
workspace = true
|
||||||
[dependencies.defmt]
|
[dependencies.defmt]
|
||||||
workspace = true
|
workspace = true
|
||||||
[dependencies.embassy-stm32]
|
[dependencies.uom]
|
||||||
workspace = true
|
workspace = true
|
||||||
optional = true
|
[dependencies.embassy-sync]
|
||||||
[dependencies.embassy-usb]
|
|
||||||
workspace = true
|
workspace = true
|
||||||
optional = 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]
|
#![no_std]
|
||||||
|
|
||||||
#[cfg(feature = "comms")]
|
pub mod transducer;
|
||||||
pub mod comms;
|
|
||||||
pub mod spi;
|
pub mod cell {
|
||||||
#[cfg(feature = "stm32")]
|
pub use physical::cell::*;
|
||||||
pub mod stm32;
|
}
|
||||||
|
|
||||||
pub use physical::CriticalError;
|
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:
|
# Before upgrading check that everything is available on all tier1 targets here:
|
||||||
# https://rust-lang.github.io/rustup-components-history
|
# https://rust-lang.github.io/rustup-components-history
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.82"
|
channel = "1.75"
|
||||||
components = [ "rust-src", "rustfmt", "llvm-tools" ]
|
components = [ "rust-src", "rustfmt", "llvm-tools" ]
|
||||||
targets = [
|
targets = [
|
||||||
"thumbv7em-none-eabi",
|
"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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+24
-19
@@ -1,10 +1,5 @@
|
|||||||
/// Indicates the transducer value is known to be impossible.
|
use crate::transducer::InvalidValue;
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct 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
|
/// 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
|
/// 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
|
/// should consist of attempting to alert another system for maintenance and attempting to shut down
|
||||||
@@ -19,23 +14,33 @@ pub enum CriticalError {
|
|||||||
InvalidValue(InvalidValue),
|
InvalidValue(InvalidValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A state of this type may mean the program has encountered an error that prevents it from continuing to run
|
impl CriticalError {
|
||||||
/// and should attempt to enter a safe terminal state.
|
|
||||||
/// e.g. Certain [Err]s
|
|
||||||
pub trait Terminal<T, E> {
|
|
||||||
//TODO: Switch to using ! as the return type for the FnOnce when the feature is stabilized
|
//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> {
|
/// [Result] where error type is [CriticalError].
|
||||||
fn terminal(self, terminate: impl FnOnce(E)) -> T {
|
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 {
|
match self {
|
||||||
Ok(value) => value,
|
Ok(val) => val,
|
||||||
Err(error) => {
|
Err(error) => error.emergency_procedure(procedure),
|
||||||
//TODO: Remove panic when terminate returns -> !
|
|
||||||
terminate(error);
|
|
||||||
panic!()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-6
@@ -1,11 +1,8 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![no_std]
|
||||||
|
|
||||||
pub mod transducer;
|
pub mod transducer;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
pub mod error;
|
pub mod cell;
|
||||||
|
mod error;
|
||||||
pub mod adc;
|
|
||||||
pub mod circuit;
|
|
||||||
pub mod quantity;
|
|
||||||
|
|
||||||
pub use error::CriticalError;
|
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;
|
mod part;
|
||||||
|
|
||||||
pub use 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::transducer::InvalidValue;
|
||||||
use crate::quantity::{DeciCelsius, MilliVolts, Quantity};
|
use uom::si::electric_potential::volt;
|
||||||
|
use uom::si::quantities::{ElectricPotential, ThermodynamicTemperature};
|
||||||
|
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn convert(
|
pub fn convert(
|
||||||
voltage: MilliVolts<i16>,
|
voltage: ElectricPotential<f32>,
|
||||||
) -> Result<DeciCelsius<i16>, InvalidValue> {
|
) -> Result<ThermodynamicTemperature<f32>, InvalidValue> {
|
||||||
const MIN_VOLTAGE: MilliVolts<i16> = MilliVolts(-550);
|
const MIN_VOLTS: f32 = -0.55;
|
||||||
const MAX_VOLTAGE: MilliVolts<i16> = MilliVolts(1_500);
|
const MAX_VOLTS: f32 = 1.50;
|
||||||
|
const SCALE_FACTOR: f32 = 100.0;
|
||||||
|
|
||||||
if voltage >= MIN_VOLTAGE && voltage <= MAX_VOLTAGE {
|
let volts = voltage.get::<volt>();
|
||||||
Ok(DeciCelsius(voltage.value()))
|
|
||||||
|
if volts >= MIN_VOLTS && volts <= MAX_VOLTS {
|
||||||
|
let celsius = volts * SCALE_FACTOR;
|
||||||
|
Ok(ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||||
} else {
|
} else {
|
||||||
Err(InvalidValue)
|
Err(InvalidValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,5 @@ mod thermocouple;
|
|||||||
#[cfg(feature = "lm35")]
|
#[cfg(feature = "lm35")]
|
||||||
pub mod lm35;
|
pub mod lm35;
|
||||||
|
|
||||||
#[cfg(feature = "thermistor")]
|
#[cfg(feature = "thermocouple_k")]
|
||||||
pub mod thermistor;
|
|
||||||
|
|
||||||
#[cfg(feature = "thermocouple-k")]
|
|
||||||
pub use thermocouple::type_k as 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;
|
pub mod type_k;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
//! Note - Thermocouple conversion uses [f64] arithmetic internally.
|
use crate::transducer::InvalidValue;
|
||||||
|
use libm::powf;
|
||||||
use libm::pow;
|
use uom::si::electric_potential::millivolt;
|
||||||
use crate::error::InvalidValue;
|
use uom::si::quantities::{ElectricPotential, ThermodynamicTemperature};
|
||||||
use crate::quantity::{Celsius, MilliVolts, Quantity};
|
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||||
|
|
||||||
fn _convert(
|
fn _convert(
|
||||||
voltage: MilliVolts<f64>,
|
voltage: ElectricPotential<f32>,
|
||||||
) -> Result<Celsius<f32>, InvalidValue> {
|
) -> Result<ThermodynamicTemperature<f32>, InvalidValue> {
|
||||||
let mv = voltage.value();
|
let mv = voltage.get::<millivolt>();
|
||||||
let mv_pow2 = mv * mv;
|
let mv_pow2 = mv * mv;
|
||||||
let mv_pow3 = mv_pow2 * mv;
|
let mv_pow3 = mv_pow2 * mv;
|
||||||
let mv_pow4 = mv_pow3 * mv;
|
let mv_pow4 = mv_pow3 * mv;
|
||||||
@@ -27,7 +27,7 @@ fn _convert(
|
|||||||
+ -1.0450598E-2 * mv_pow7
|
+ -1.0450598E-2 * mv_pow7
|
||||||
+ -5.1920577E-4 * mv_pow8;
|
+ -5.1920577E-4 * mv_pow8;
|
||||||
|
|
||||||
Ok(Celsius(celsius as f32))
|
Ok(ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||||
} else if mv > 0.0 && mv < 20.644 {
|
} else if mv > 0.0 && mv < 20.644 {
|
||||||
let mv_pow7 = mv_pow6 * mv;
|
let mv_pow7 = mv_pow6 * mv;
|
||||||
let mv_pow8 = mv_pow7 * mv;
|
let mv_pow8 = mv_pow7 * mv;
|
||||||
@@ -43,7 +43,7 @@ fn _convert(
|
|||||||
+ 1.057734E-6 * mv_pow8
|
+ 1.057734E-6 * mv_pow8
|
||||||
+ -1.052755E-8 * mv_pow9;
|
+ -1.052755E-8 * mv_pow9;
|
||||||
|
|
||||||
Ok(Celsius(celsius as f32))
|
Ok(ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||||
} else if mv >= 20.644 && mv <= 54.886 {
|
} else if mv >= 20.644 && mv <= 54.886 {
|
||||||
let celsius = 1.318058e2
|
let celsius = 1.318058e2
|
||||||
+ 4.830222E+1 * mv
|
+ 4.830222E+1 * mv
|
||||||
@@ -53,7 +53,7 @@ fn _convert(
|
|||||||
+ 8.802193E-6 * mv_pow5
|
+ 8.802193E-6 * mv_pow5
|
||||||
+ -3.110810E-8 * mv_pow6;
|
+ -3.110810E-8 * mv_pow6;
|
||||||
|
|
||||||
Ok(Celsius(celsius as f32))
|
Ok(ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||||
} else {
|
} else {
|
||||||
Err(InvalidValue)
|
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).
|
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn convert_direct(
|
pub fn convert_direct(
|
||||||
voltage: MilliVolts<f64>,
|
voltage: ElectricPotential<f32>,
|
||||||
r_junction: Celsius<f32>,
|
r_junction: ThermodynamicTemperature<f32>,
|
||||||
) -> Result<Celsius<f32>, InvalidValue> {
|
) -> Result<ThermodynamicTemperature<f32>, InvalidValue> {
|
||||||
let base_temp = _convert(voltage)?;
|
let base_celsius = _convert(voltage)?.get::<degree_celsius>();
|
||||||
|
let r_junction_celsius = r_junction.get::<degree_celsius>();
|
||||||
|
|
||||||
Ok(base_temp + r_junction)
|
Ok(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
|
/// 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).
|
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn convert_seebeck(
|
pub fn convert_seebeck(
|
||||||
voltage: MilliVolts<f64>,
|
voltage: ElectricPotential<f32>,
|
||||||
r_junction: Celsius<f32>,
|
r_junction: ThermodynamicTemperature<f32>,
|
||||||
) -> Result<Celsius<f32>, InvalidValue> {
|
) -> Result<ThermodynamicTemperature<f32>, InvalidValue> {
|
||||||
let voltage_correction = temp_to_voltage_seebeck(r_junction)?;
|
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
|
/// 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).
|
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn convert_polynomial(
|
pub fn convert_polynomial(
|
||||||
voltage: MilliVolts<f64>,
|
voltage: ElectricPotential<f32>,
|
||||||
r_junction: Celsius<f64>,
|
r_junction: ThermodynamicTemperature<f32>,
|
||||||
) -> Result<Celsius<f32>, InvalidValue> {
|
) -> Result<ThermodynamicTemperature<f32>, InvalidValue> {
|
||||||
let voltage_correction = temp_to_voltage_poly(r_junction)?;
|
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(
|
pub fn temp_to_voltage_poly(
|
||||||
temperature: Celsius<f64>,
|
temperature: ThermodynamicTemperature<f32>,
|
||||||
) -> Result<MilliVolts<f32>, InvalidValue> {
|
) -> Result<ElectricPotential<f32>, InvalidValue> {
|
||||||
let celsius = temperature.value();
|
let celsius = temperature.get::<degree_celsius>();
|
||||||
let cel_pow2 = celsius * celsius;
|
let cel_pow2 = celsius * celsius;
|
||||||
let cel_pow3 = cel_pow2 * celsius;
|
let cel_pow3 = cel_pow2 * celsius;
|
||||||
let cel_pow4 = cel_pow3 * celsius;
|
let cel_pow4 = cel_pow3 * celsius;
|
||||||
@@ -133,11 +135,11 @@ pub fn temp_to_voltage_poly(
|
|||||||
+ -0.198892668780E-19 * cel_pow9
|
+ -0.198892668780E-19 * cel_pow9
|
||||||
+ -0.163226974860E-22 * cel_pow10;
|
+ -0.163226974860E-22 * cel_pow10;
|
||||||
|
|
||||||
Ok(MilliVolts(mv as f32))
|
Ok(ElectricPotential::new::<millivolt>(mv))
|
||||||
} else if celsius >= 0.0 && celsius <= 1372.0 {
|
} else if celsius >= 0.0 && celsius <= 1372.0 {
|
||||||
let base = celsius - 0.126968600000E+03;
|
let base = celsius - 0.126968600000E+03;
|
||||||
let exp = -0.118343200000E-03 * (base * base);
|
let exp = -0.118343200000E-03 * (base * base);
|
||||||
let addition = pow(0.1185976, exp);
|
let addition = powf(0.1185976, exp);
|
||||||
|
|
||||||
let mv = -0.176004136860E-01
|
let mv = -0.176004136860E-01
|
||||||
+ 0.389212049750E-01 * celsius
|
+ 0.389212049750E-01 * celsius
|
||||||
@@ -151,7 +153,7 @@ pub fn temp_to_voltage_poly(
|
|||||||
+ -0.121047212750E-25 * cel_pow9
|
+ -0.121047212750E-25 * cel_pow9
|
||||||
+ addition;
|
+ addition;
|
||||||
|
|
||||||
Ok(MilliVolts(mv as f32))
|
Ok(ElectricPotential::new::<millivolt>(mv))
|
||||||
} else {
|
} else {
|
||||||
Err(InvalidValue)
|
Err(InvalidValue)
|
||||||
}
|
}
|
||||||
@@ -159,10 +161,12 @@ pub fn temp_to_voltage_poly(
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn temp_to_voltage_seebeck(
|
pub fn temp_to_voltage_seebeck(
|
||||||
temperature: Celsius<f32>,
|
temperature: ThermodynamicTemperature<f32>,
|
||||||
) -> Result<MilliVolts<f32>, InvalidValue> {
|
) -> Result<ElectricPotential<f32>, InvalidValue> {
|
||||||
if temperature.value() >= -2.0 && temperature.value() <= 800.0 {
|
let celsius = temperature.get::<degree_celsius>();
|
||||||
Ok(MilliVolts(0.041 * temperature.value()))
|
if celsius >= -2.0 && celsius <= 800.0 {
|
||||||
|
let mv = 0.041 * celsius;
|
||||||
|
Ok(ElectricPotential::new::<millivolt>(mv))
|
||||||
} else {
|
} else {
|
||||||
Err(InvalidValue)
|
Err(InvalidValue)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user