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}