max6675_hal/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//! # max6675-hal
3//!
4//! An embedded-hal driver for the MAX6675 digital thermocouple converter.
5//!
6//! [<img alt="license badge" src="https://img.shields.io/github/license/onkoe/max6675-hal">](https://github.com/onkoe/max6675-hal)
7//! [<img alt="docs.rs badge" src="https://img.shields.io/docsrs/max6675-hal">](https://docs.rs/max6675-hal)
8//! [<img alt="crates.io badge" src="https://img.shields.io/crates/dv/max6675-hal?label=crates.io">](https://crates.io/crates/max6675-hal)
9//! [<img alt="GitHub badge" src="https://img.shields.io/badge/github-onkoe/max6675--hal-6e5494">](https://github.com/onkoe/max6675-hal)
10//! [<img alt="GitHub Actions badge" src="https://img.shields.io/github/actions/workflow/status/onkoe/max6675-hal/ci.yml?branch=main">](https://github.com/onkoe/max6675-hal/actions)
11//!
12//! ## Usage
13//!
14//! This example code will change depending on which HAL device driver you're
15//! using. An `arduino-hal` project's SPI isn't like that of an `esp32-hal`
16//! project.
17//!
18//! However, you only need to focus on your device's SPI implementation!
19//! (thanks, `embedded-hal` 1.0 ☺️)
20//!
21//! Your SPI settings should use MSB (most significant bit) first, target a clock speed of
22//! at least 4mhz, and utilize SPI Mode 1.
23//!
24//! ```ignore
25//! // first, define what pins you're connecting to
26//! let so_pin = pins.("your miso pin").into_pull_up_input();
27//! let cs_pin = pins.("your cs pin").into_output();
28//! let sck_pin = pins.("your sck/clock pin").into_output();
29//!
30//! // you may need a mosi pin for your device's SPI, though the max6675 doesn't use one.
31//! // if so, just pick some pin that you're not using ☺️
32//! let dummy_mosi = pins.("some pin you're not using").into_output();
33//!
34//! let (spi, _) = device-hal::spi::Spi::new(
35//!     sck_pin, dummy_mosi, so_pin, cs_pin,
36//!     device-hal::spi::Settings {
37//!         // pick some settings that roughly align like so:
38//!         data_order: MostSignificantFirst,
39//!         clock: 4MhzClockSpeed,
40//!         mode: embedded_hal::spi::MODE_1,
41//!     }
42//! );
43//! let mut max = Max6675::new(spi)?; // your spi and chip select here
44//!
45//! let temp = max.read_celsius()? // ayo! we got the temperature
46//! ```
47//!
48//! ## Note
49//!
50//! This crate re-exports a Temperature type from another crate, `simmer`.
51//! You can change and play with the temperatures in various ways, so feel free
52//! to [check out its docs](https://docs.rs/crate/simmer/latest) for more info.
53//! 
54//! ## Contributions
55//! 
56//! Contributions are welcome to this project! Since it's pretty small, feel
57//! free to submit a PR whenever. You can also make an issue - I'll likely get
58//! to it soon!
59//! 
60//! ## Help
61//! 
62//! Please don't hesitate to make an issue if you experience any problems!
63//! 
64//! If you can, please submit a [`hw-probe` report](https://linux-hardware.org/?view=howto)
65//! alongside any error messages or useful logs you have!
66
67use core::marker::PhantomData;
68use embedded_hal::spi::SpiDevice;
69
70pub mod error;
71pub use error::Max6675Error;
72
73/// A Temperature type from [`simmer`](https://docs.rs/crate/simmer/latest).
74pub use simmer::Temperature;
75
76/// # Max6675
77///
78/// A representation of the MAX6675 digital thermocouple converter.
79/// Maintains an SPI connection to the device.
80#[derive(Copy, Clone, Debug, PartialEq)]
81pub struct Max6675<Spi, SpiError>
82where
83    Spi: SpiDevice<Error = SpiError>,
84{
85    /// SPI connection
86    spi: Spi,
87
88    // we're using the generic spi error, but not here!
89    _spi_err: PhantomData<SpiError>,
90}
91
92impl<Spi, SpiError> Max6675<Spi, SpiError>
93where
94    Spi: SpiDevice<Error = SpiError>,
95{
96    /// Creates a new Max6675 representation.
97    ///
98    /// For the `spi` argument, you should pass in your `embedded-hal` device's
99    /// SPI implementation filled with appropriate details.
100    ///
101    /// # Usage
102    ///
103    /// Since the `Spi` (SPI) arguments is generic, you'll have to make some
104    /// decisions based on the hardware you're using!
105    ///
106    /// Please follow this general template:
107    ///
108    /// ```ignore
109    /// // first, define what pins you're connecting to
110    /// let so_pin = pins.("your miso pin").into_pull_up_input();
111    /// let cs_pin = pins.("your cs pin").into_output();
112    /// let sck_pin = pins.("your sck/clock pin").into_output();
113    ///
114    /// // you may need a mosi pin for your device's SPI, though the max6675 doesn't use one.
115    /// // if so, just pick some pin that you're not using ☺️
116    /// let dummy_mosi = pins.("some pin you're not using").into_output();
117    ///
118    /// let (spi, _) = device-hal::spi::Spi::new(
119    ///     sck_pin, dummy_mosi, so_pin, cs_pin,
120    ///     device-hal::spi::Settings {
121    ///         // pick some settings that roughly align like so:
122    ///         data_order: MostSignificantFirst,
123    ///         clock: 4MhzClockSpeed,
124    ///         mode: embedded_hal::spi::MODE_1,
125    ///     }
126    /// );
127    /// let mut max = Max6675::new(spi)?; // your spi here
128    /// ```
129    pub fn new(spi: Spi) -> Result<Self, Max6675Error<SpiError>> {
130        Ok(Self {
131            spi,
132            _spi_err: PhantomData,
133        })
134    }
135
136    /// Destructs the `MAX6675` into its bare components, as recommended by the
137    /// [HAL Design Patterns](https://doc.rust-lang.org/beta/embedded-book/design-patterns/hal/interoperability.html).
138    ///
139    /// ```
140    /// # use embedded_hal_mock::{
141    /// #     common::Generic,
142    /// #     eh1::spi::{Mock, Transaction},
143    /// # };
144    /// use max6675_hal::Max6675;
145    ///
146    /// # let spi = Mock::new(&[].to_vec());
147    /// // pretend there's some spi setup code above...
148    /// let mut max = Max6675::new(spi).unwrap();
149    /// let mut spi = max.free();
150    /// # spi.done();
151    pub fn free(self) -> Spi {
152        self.spi
153    }
154
155    /// Tries to read thermocouple temperature, leaving it as a raw ADC count.
156    ///
157    /// ```
158    /// # use embedded_hal_mock::{
159    /// #     common::Generic,
160    /// #     eh1::spi::{Mock, Transaction},
161    /// # };
162    /// use max6675_hal::Max6675;
163    /// #
164    /// # let temp = ((400 << 3) as u16).to_be_bytes().to_vec();
165    /// # let expected = [
166    /// #     Transaction::transaction_start(),
167    /// #     Transaction::read_vec(((400 << 3) as u16).to_be_bytes().to_vec()),
168    /// #     Transaction::transaction_end(),
169    /// # ]
170    /// # .to_vec();
171    ///
172    /// # let spi = Mock::new(&expected);
173    /// // pretend there's some spi setup code above...
174    /// let mut max = Max6675::new(spi).unwrap();
175    /// assert_eq!(max.read_raw().unwrap(), [0xc, 0x80]);
176    /// # max.free().done();
177    /// ```
178    pub fn read_raw(&mut self) -> Result<[u8; 2], Max6675Error<SpiError>> {
179        let mut buf: [u8; 2] = [0_u8; 2];
180        self.spi.read(&mut buf)?;
181
182        Ok(buf)
183    }
184
185    /// Internal function to convert a `read_raw()` into a parsable `u16`.
186    fn process_raw(&mut self) -> Result<u16, Max6675Error<SpiError>> {
187        Ok(u16::from_be_bytes(self.read_raw()?))
188    }
189
190    /// Tries to read the thermocouple's temperature in Celsius.
191    ///
192    /// ```
193    /// # use assert_approx_eq::assert_approx_eq;
194    /// # use embedded_hal_mock::{
195    /// #     common::Generic,
196    /// #     eh1::spi::{Mock, Transaction},
197    /// # };
198    /// use max6675_hal::Max6675;
199    /// #
200    /// # let temp = ((400 << 3) as u16).to_be_bytes().to_vec();
201    /// # let expected = [
202    /// #     Transaction::transaction_start(),
203    /// #     Transaction::read_vec(((400 << 3) as u16).to_be_bytes().to_vec()),
204    /// #     Transaction::transaction_end(),
205    /// # ]
206    /// # .to_vec();
207    ///
208    /// # let spi = Mock::new(&expected);
209    /// // pretend there's some spi setup code above...
210    /// let mut max = Max6675::new(spi).unwrap();
211    /// assert_approx_eq!(max.read_celsius().unwrap().into_inner(), 100_f32);
212    /// # max.free().done();
213    /// ```
214    pub fn read_celsius(&mut self) -> Result<Temperature, Max6675Error<SpiError>> {
215        let raw = self.process_raw()?;
216
217        if raw & 0x04 != 0 {
218            return Err(Max6675Error::OpenCircuitError);
219        }
220
221        let temp = ((raw >> 3) & 0x1FFF) as f32 * 0.25_f32;
222        Ok(Temperature::Celsius(temp))
223    }
224
225    /// Tries to read the thermocouple's temperature in Fahrenheit.
226    ///
227    /// ```
228    /// # use assert_approx_eq::assert_approx_eq;
229    /// # use embedded_hal_mock::{
230    /// #     common::Generic,
231    /// #     eh1::spi::{Mock, Transaction},
232    /// # };
233    /// use max6675_hal::Max6675;
234    /// #
235    /// # let temp = ((400 << 3) as u16).to_be_bytes().to_vec();
236    /// # let expected = [
237    /// #     Transaction::transaction_start(),
238    /// #     Transaction::read_vec(((80 << 3) as u16).to_be_bytes().to_vec()), // 68° F
239    /// #     Transaction::transaction_end(),
240    /// # ]
241    /// # .to_vec();
242    ///
243    /// # let spi = Mock::new(&expected);
244    /// // pretend there's some spi setup code above...
245    /// let mut max = Max6675::new(spi).unwrap();
246    /// assert_approx_eq!(max.read_fahrenheit().unwrap().into_inner(), 68_f32);
247    /// # max.free().done();
248    /// ```
249    pub fn read_fahrenheit(&mut self) -> Result<Temperature, Max6675Error<SpiError>> {
250        Ok(self.read_celsius()?.to_fahrenheit())
251    }
252
253    /// Tries to read the thermocouple's temperature in Kelvin.
254    ///
255    /// ```
256    /// # use assert_approx_eq::assert_approx_eq;
257    /// # use embedded_hal_mock::{
258    /// #     common::Generic,
259    /// #     eh1::spi::{Mock, Transaction},
260    /// # };
261    /// use max6675_hal::Max6675;
262    /// #
263    /// # let temp = ((400 << 3) as u16).to_be_bytes().to_vec();
264    /// # let expected = [
265    /// #     Transaction::transaction_start(),
266    /// #     Transaction::read_vec(((400 << 3) as u16).to_be_bytes().to_vec()),
267    /// #     Transaction::transaction_end(),
268    /// # ]
269    /// # .to_vec();
270    ///
271    /// # let spi = Mock::new(&expected);
272    /// // pretend there's some spi setup code above...
273    /// let mut max = Max6675::new(spi).unwrap();
274    /// assert_approx_eq!(max.read_kelvin().unwrap().into_inner(), 373.15_f32);
275    /// max.free().done();
276    /// ```
277    pub fn read_kelvin(&mut self) -> Result<Temperature, Max6675Error<SpiError>> {
278        Ok(self.read_celsius()?.to_kelvin())
279    }
280}