From 5f7b005950e6e9f59a70df583a2a1ac217c878cb Mon Sep 17 00:00:00 2001 From: Zachary Sunforge Date: Thu, 14 Nov 2024 12:39:06 -0800 Subject: [PATCH] Moved BFPOWER drivers to Physical --- Cargo.toml | 9 +- drivers/ads1256/driver/Cargo.toml | 28 + drivers/ads1256/driver/src/adc.rs | 562 +++++++++++++++++++++ drivers/ads1256/driver/src/delay.rs | 44 ++ drivers/ads1256/driver/src/io.rs | 386 ++++++++++++++ drivers/ads1256/driver/src/lib.rs | 52 ++ drivers/ads1256/driver/src/mutex.rs | 271 ++++++++++ drivers/ads1256/types/Cargo.toml | 14 + drivers/ads1256/types/src/constants.rs | 304 +++++++++++ drivers/ads1256/types/src/conversion.rs | 34 ++ drivers/ads1256/types/src/lib.rs | 15 + drivers/ads1256/types/src/registers.rs | 645 ++++++++++++++++++++++++ drivers/ads1256/types/src/standard.rs | 41 ++ examples/ads1256/.cargo/config.toml | 9 + examples/ads1256/Cargo.toml | 37 ++ examples/ads1256/build.rs | 5 + examples/ads1256/src/bin/adc.rs | 131 +++++ examples/ads1256/src/bin/registers.rs | 180 +++++++ node/src/lib.rs | 6 +- node/src/spi.rs | 52 ++ 20 files changed, 2822 insertions(+), 3 deletions(-) create mode 100644 drivers/ads1256/driver/Cargo.toml create mode 100644 drivers/ads1256/driver/src/adc.rs create mode 100644 drivers/ads1256/driver/src/delay.rs create mode 100644 drivers/ads1256/driver/src/io.rs create mode 100644 drivers/ads1256/driver/src/lib.rs create mode 100644 drivers/ads1256/driver/src/mutex.rs create mode 100644 drivers/ads1256/types/Cargo.toml create mode 100644 drivers/ads1256/types/src/constants.rs create mode 100644 drivers/ads1256/types/src/conversion.rs create mode 100644 drivers/ads1256/types/src/lib.rs create mode 100644 drivers/ads1256/types/src/registers.rs create mode 100644 drivers/ads1256/types/src/standard.rs create mode 100644 examples/ads1256/.cargo/config.toml create mode 100644 examples/ads1256/Cargo.toml create mode 100644 examples/ads1256/build.rs create mode 100644 examples/ads1256/src/bin/adc.rs create mode 100644 examples/ads1256/src/bin/registers.rs create mode 100644 node/src/spi.rs diff --git a/Cargo.toml b/Cargo.toml index cb6cc1c..cb9fdeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,17 @@ members = [ # Device types "node", "commander", + # Drivers + "drivers/ads1256/types", + "drivers/ads1256/driver", # Meta - "generate-quantity" + "generate-quantity", + # Examples + "examples/ads1256" ] [workspace.package] -version = "0.3.13" +version = "0.4.0" edition = "2021" repository = "https://git.bfpower.io/BFPOWER/physical" readme = "README.md" diff --git a/drivers/ads1256/driver/Cargo.toml b/drivers/ads1256/driver/Cargo.toml new file mode 100644 index 0000000..1229462 --- /dev/null +++ b/drivers/ads1256/driver/Cargo.toml @@ -0,0 +1,28 @@ +[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 diff --git a/drivers/ads1256/driver/src/adc.rs b/drivers/ads1256/driver/src/adc.rs new file mode 100644 index 0000000..bd7caf3 --- /dev/null +++ b/drivers/ads1256/driver/src/adc.rs @@ -0,0 +1,562 @@ +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 Ads1256 +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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + status: Option, + ad_control: Option, + data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + calibration: Option<&CalibrationCommand>, + status: Option, + ad_control: Option, + data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + next_input: Multiplexer, + next_calibration: Option<&CalibrationCommand>, + next_status: Option, + next_ad_control: Option, + next_data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + next_input: Multiplexer, + next_calibration: Option<&CalibrationCommand>, + next_status: Option, + next_ad_control: Option, + next_data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + ) -> Result<[Conversion; NUM_SAMPLES], ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + calibration: Option<&CalibrationCommand>, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + status: Status, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + ad_control: AdControl, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + data_rate: DataRate, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + status: Status, + ad_control: AdControl, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + status: Status, + data_rate: DataRate, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + ad_control: AdControl, + data_rate: DataRate, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + status: Status, + ad_control: AdControl, + data_rate: DataRate, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + standby: bool, + ) -> Result::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( + &mut self, + spi: &mut SpiT, + standby: bool, + ) -> Result::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) + } +} diff --git a/drivers/ads1256/driver/src/delay.rs b/drivers/ads1256/driver/src/delay.rs new file mode 100644 index 0000000..7d66e13 --- /dev/null +++ b/drivers/ads1256/driver/src/delay.rs @@ -0,0 +1,44 @@ +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 { + delayer: DelayT, +} + +impl DefaultDelay { + #[inline(always)] + pub const fn new(delayer: DelayT) -> Self { + Self { delayer } + } +} + +impl BlockingDelay for DefaultDelay { + #[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); + } +} \ No newline at end of file diff --git a/drivers/ads1256/driver/src/io.rs b/drivers/ads1256/driver/src/io.rs new file mode 100644 index 0000000..f715271 --- /dev/null +++ b/drivers/ads1256/driver/src/io.rs @@ -0,0 +1,386 @@ +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 Ads1256 +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( + &mut self, + spi: &mut SpiT, + start_address: u8, + mut buffer: [u8; BUF_SIZE], + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + start_address: u8, + buffer: [u8; BUF_SIZE], + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + start_address: u8, + ) -> Result<[u8; NUM], ::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( + &mut self, + spi: &mut SpiT, + opcode: u8, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::Error> { + self.standalone_command(spi, opcodes::RESET) + } + + #[inline(always)] + pub fn wake( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::Error> { + self.standalone_command(spi, opcodes::WAKEUP) + } + + /// Perform self offset and gain calibration. + #[inline] + pub async fn self_calibrate( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(Status(self.read_registers::<_, 1>(spi, status::ADDRESS)?[0])) + } + + #[inline] + pub fn write_status( + &mut self, + spi: &mut SpiT, + setting: Status, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(Multiplexer(self.read_registers::<_, 1>(spi, mux::ADDRESS)?[0])) + } + + #[inline] + pub fn write_multiplexer( + &mut self, + spi: &mut SpiT, + setting: Multiplexer, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(AdControl(self.read_registers::<_, 1>(spi, adcon::ADDRESS)?[0])) + } + + #[inline] + pub fn write_ad_control( + &mut self, + spi: &mut SpiT, + setting: AdControl, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + input: Multiplexer, + ad_control: AdControl, + ) -> Result<(), ::Error> { + let buffer = [0u8, 0u8, input.0, ad_control.0]; + self.write_registers(spi, mux::ADDRESS, buffer) + } + + #[inline] + pub fn read_data_rate( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(DataRate::from_byte(self.read_registers::<_, 1>(spi, drate::ADDRESS)?[0])) + } + + #[inline] + pub fn write_data_rate( + &mut self, + spi: &mut SpiT, + setting: DataRate, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(DigitalIo(self.read_registers::<_, 1>(spi, gpio::ADDRESS)?[0])) + } + + #[inline] + pub fn write_gpio( + &mut self, + spi: &mut SpiT, + setting: DigitalIo, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(OffsetCalibration(self.read_registers(spi, ofc0::ADDRESS)?)) + } + + #[inline] + pub fn write_offset_calibration( + &mut self, + spi: &mut SpiT, + setting: OffsetCalibration, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(GainCalibration(self.read_registers(spi, fsc0::ADDRESS)?)) + } + + #[inline] + pub fn write_gain_calibration( + &mut self, + spi: &mut SpiT, + setting: GainCalibration, + ) -> Result<(), ::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( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + Ok(self.read_registers(spi, ofc0::ADDRESS)?.into()) + } + + #[inline] + pub fn exec_cal_command( + &mut self, + spi: &mut SpiT, + command: &CalibrationCommand, + ) -> Result<(), ::Error> { + let spi_op_result = spi.write(command.into()); + end_spi(&mut self.slave_select, spi, spi_op_result) + } + + #[inline] + pub fn read_config( + &mut self, + spi: &mut SpiT, + ) -> Result::Error> { + let bytes = self.read_registers::<_, 5>(spi, status::ADDRESS)?; + Ok(Config::from_bytes(bytes)) + } + + #[inline] + pub fn write_config( + &mut self, + spi: &mut SpiT, + setting: Config, + ) -> Result<(), ::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) + } +} diff --git a/drivers/ads1256/driver/src/lib.rs b/drivers/ads1256/driver/src/lib.rs new file mode 100644 index 0000000..d242ab8 --- /dev/null +++ b/drivers/ads1256/driver/src/lib.rs @@ -0,0 +1,52 @@ +#![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 { + pub delayer: DelayerT, + slave_select: SST, + pub data_ready: DrdyT, +} + +impl Ads1256 +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, + } + } +} diff --git a/drivers/ads1256/driver/src/mutex.rs b/drivers/ads1256/driver/src/mutex.rs new file mode 100644 index 0000000..c7e9b10 --- /dev/null +++ b/drivers/ads1256/driver/src/mutex.rs @@ -0,0 +1,271 @@ +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 Ads1256 +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( + &mut self, + spi: &Mutex, + ) -> Result::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( + &mut self, + spi_mutex: &Mutex, + input: Multiplexer, + status: Option, + ad_control: Option, + data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi_mutex: &Mutex, + input: Multiplexer, + calibration: Option<&CalibrationCommand>, + status: Option, + ad_control: Option, + data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi_mutex: &Mutex, + next_input: Multiplexer, + next_calibration: Option<&CalibrationCommand>, + next_status: Option, + next_ad_control: Option, + next_data_rate: Option, + standby: bool, + ) -> Result::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( + &mut self, + spi_mutex: &Mutex, + standby: bool, + ) -> Result::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) + } +} diff --git a/drivers/ads1256/types/Cargo.toml b/drivers/ads1256/types/Cargo.toml new file mode 100644 index 0000000..faeeb3c --- /dev/null +++ b/drivers/ads1256/types/Cargo.toml @@ -0,0 +1,14 @@ +[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 diff --git a/drivers/ads1256/types/src/constants.rs b/drivers/ads1256/types/src/constants.rs new file mode 100644 index 0000000..f35d7c6 --- /dev/null +++ b/drivers/ads1256/types/src/constants.rs @@ -0,0 +1,304 @@ +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; +} diff --git a/drivers/ads1256/types/src/conversion.rs b/drivers/ads1256/types/src/conversion.rs new file mode 100644 index 0000000..5359d59 --- /dev/null +++ b/drivers/ads1256/types/src/conversion.rs @@ -0,0 +1,34 @@ +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 { + let volts = ((2.0 * REFERENCE_VOLTS) / (MAX_CONVERSION_VALUE as f32)) + * (self.0 as f32 / gain.value() as f32); + + Volts(volts) + } +} + +impl From for Conversion { + #[inline(always)] + fn from(value: i32) -> Self { + Conversion(value) + } +} diff --git a/drivers/ads1256/types/src/lib.rs b/drivers/ads1256/types/src/lib.rs new file mode 100644 index 0000000..c54daf6 --- /dev/null +++ b/drivers/ads1256/types/src/lib.rs @@ -0,0 +1,15 @@ +#![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::*; \ No newline at end of file diff --git a/drivers/ads1256/types/src/registers.rs b/drivers/ads1256/types/src/registers.rs new file mode 100644 index 0000000..4968f61 --- /dev/null +++ b/drivers/ads1256/types/src/registers.rs @@ -0,0 +1,645 @@ +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) } + } +} + +/// 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 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) } + } +} + +impl<'a> Into<&'a [u8]> for &'a CalibrationCommand { + #[inline(always)] + fn into(self) -> &'a [u8] { + unsafe { mem::transmute::(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::(self.0 & MASK) } + } + + #[inline(always)] + pub const fn buffer(self) -> Buffer { + unsafe { mem::transmute::(self.0 & Buffer::MASK) } + } + + #[inline(always)] + pub const fn auto_calibration(self) -> AutoCal { + unsafe { mem::transmute::(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::(self.0 & BitOrder::MASK) } + } + + #[inline(always)] + pub const fn id(self) -> u8 { + self.0 >> 4 + } +} + +impl Into 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 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::(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::(self.0 & Sdcs::MASK) } + } + + /// Ads1256 master clock cycle frequency outputted through GPIO. + #[inline(always)] + pub const fn clock_out(self) -> ClockOut { + unsafe { mem::transmute::(self.0 & ClockOut::MASK) } + } +} + +impl Into 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 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 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 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() + ) + } +} diff --git a/drivers/ads1256/types/src/standard.rs b/drivers/ads1256/types/src/standard.rs new file mode 100644 index 0000000..14c480c --- /dev/null +++ b/drivers/ads1256/types/src/standard.rs @@ -0,0 +1,41 @@ +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 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 for SingleEnded { + #[inline(always)] + fn into(self) -> Multiplexer { + Multiplexer(self as u8) + } + } +} diff --git a/examples/ads1256/.cargo/config.toml b/examples/ads1256/.cargo/config.toml new file mode 100644 index 0000000..4d657a2 --- /dev/null +++ b/examples/ads1256/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F411CEUx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F411CEUx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" \ No newline at end of file diff --git a/examples/ads1256/Cargo.toml b/examples/ads1256/Cargo.toml new file mode 100644 index 0000000..9adbdf9 --- /dev/null +++ b/examples/ads1256/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "ads1256-examples" +description = "Examples using the ads1256." +version.workspace = true +edition.workspace = true +repository.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies.ads1256] +path = "../../drivers/ads1256/driver" +[dependencies.physical] +path = "../.." +features = ["defmt"] +[dependencies.defmt] +workspace = true +[dependencies.defmt-rtt] +workspace = true +[dependencies.cortex-m] +workspace = true +features = ["critical-section-single-core"] +[dependencies.cortex-m-rt] +workspace = true +[dependencies.embassy-stm32] +workspace = true +features = ["stm32f411ce", "memory-x", "time", "exti", "time-driver-any"] +[dependencies.embassy-executor] +workspace = true +[dependencies.embassy-futures] +workspace = true +[dependencies.embassy-time] +workspace = true +features = ["tick-hz-16_000_000"] +[dependencies.panic-probe] +workspace = true +[dependencies] +log = "0.4.20" diff --git a/examples/ads1256/build.rs b/examples/ads1256/build.rs new file mode 100644 index 0000000..56127fd --- /dev/null +++ b/examples/ads1256/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} \ No newline at end of file diff --git a/examples/ads1256/src/bin/adc.rs b/examples/ads1256/src/bin/adc.rs new file mode 100644 index 0000000..6880e07 --- /dev/null +++ b/examples/ads1256/src/bin/adc.rs @@ -0,0 +1,131 @@ +#![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 = 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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))); + } + +} diff --git a/examples/ads1256/src/bin/registers.rs b/examples/ads1256/src/bin/registers.rs new file mode 100644 index 0000000..4e959b2 --- /dev/null +++ b/examples/ads1256/src/bin/registers.rs @@ -0,0 +1,180 @@ +#![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 = 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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( + spi: &mut impl SpiBus, + ads_1256: &mut Ads1256, +) { + 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); +} diff --git a/node/src/lib.rs b/node/src/lib.rs index 409644a..a876374 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -2,7 +2,11 @@ #[cfg(feature = "comms")] pub mod comms; +pub mod spi; #[cfg(feature = "stm32")] pub mod stm32; -pub use physical::CriticalError; \ No newline at end of file +pub use physical::CriticalError; + +pub const GPIO_ERROR_MSG: &'static str = + "Driver does not support GPIO pins with expected failure states"; diff --git a/node/src/spi.rs b/node/src/spi.rs new file mode 100644 index 0000000..efcd51b --- /dev/null +++ b/node/src/spi.rs @@ -0,0 +1,52 @@ +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( + slave_select: &mut impl OutputPin, + spi: &mut SpiT, + result: Result::Error>, +) -> Result::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( + slave_select: &mut impl OutputPin, + spi: &mut SpiT, + result: Result::Error>, +) -> Result::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 + }, + } +}