1 | //! Non-blocking Hardware Abstraction Layer (HAL) traits for embedded systems, using the `nb` crate. |
2 | //! |
3 | //! The `embedded-hal-nb` traits make use of the |
4 | //! [`nb`][] crate (*please go read that crate documentation before continuing*) to abstract over |
5 | //! the asynchronous model and to also provide a blocking operation mode. |
6 | //! |
7 | //! [`nb`]: https://crates.io/crates/nb |
8 | //! |
9 | //! Here's how a HAL trait may look like: |
10 | //! |
11 | //! ``` |
12 | //! use embedded_hal_nb; |
13 | //! |
14 | //! /// A serial interface |
15 | //! pub trait Serial { |
16 | //! /// Error type associated to this serial interface |
17 | //! type Error: core::fmt::Debug; |
18 | //! |
19 | //! /// Reads a single byte |
20 | //! fn read(&mut self) -> nb::Result<u8, Self::Error>; |
21 | //! |
22 | //! /// Writes a single byte |
23 | //! fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error>; |
24 | //! } |
25 | //! ``` |
26 | //! |
27 | //! The `nb::Result` enum is used to add a [`WouldBlock`] variant to the errors |
28 | //! of the serial interface. As explained in the documentation of the `nb` crate this single API, |
29 | //! when paired with the macros in the `nb` crate, can operate in a blocking manner, or be adapted |
30 | //! to other asynchronous execution schemes. |
31 | //! |
32 | //! [`WouldBlock`]: https://docs.rs/nb/1.0.0/nb/enum.Error.html |
33 | //! |
34 | //! Some traits, like the one shown below, may expose possibly blocking APIs that can't fail. In |
35 | //! those cases `nb::Result<_, Infallible>` is used. |
36 | //! |
37 | //! ``` |
38 | //! # use std as core; |
39 | //! use ::core::convert::Infallible; |
40 | //! |
41 | //! /// A count down timer |
42 | //! pub trait CountDown { |
43 | //! // .. |
44 | //! |
45 | //! /// "waits" until the count down is over |
46 | //! fn wait(&mut self) -> nb::Result<(), Infallible>; |
47 | //! } |
48 | //! |
49 | //! # fn main() {} |
50 | //! ``` |
51 | //! |
52 | //! ## Suggested implementation |
53 | //! |
54 | //! The HAL traits should be implemented for device crates generated via [`svd2rust`] to maximize |
55 | //! code reuse. |
56 | //! |
57 | //! [`svd2rust`]: https://crates.io/crates/svd2rust |
58 | //! |
59 | //! Shown below is an implementation of some of the HAL traits for the [`stm32f1xx-hal`] crate. This |
60 | //! single implementation will work for *any* microcontroller in the `STM32F1xx` family. |
61 | //! |
62 | //! [`stm32f1`]: https://crates.io/crates/stm32f1 |
63 | //! |
64 | //! ```no_run |
65 | //! // crate: stm32f1xx-hal |
66 | //! // An implementation of the `embedded-hal` traits for STM32F1xx microcontrollers |
67 | //! |
68 | //! use embedded_hal_nb::serial; |
69 | //! use nb; |
70 | //! |
71 | //! // device crate |
72 | //! use stm32f1::stm32f103::USART1; |
73 | //! |
74 | //! /// A serial interface |
75 | //! // NOTE generic over the USART peripheral |
76 | //! pub struct Serial<USART> { usart: USART } |
77 | //! |
78 | //! // convenience type alias |
79 | //! pub type Serial1 = Serial<USART1>; |
80 | //! |
81 | //! impl serial::ErrorType for Serial<USART1> { |
82 | //! type Error = serial::ErrorKind; |
83 | //! } |
84 | //! |
85 | //! impl embedded_hal_nb::serial::Read<u8> for Serial<USART1> { |
86 | //! fn read(&mut self) -> nb::Result<u8, Self::Error> { |
87 | //! // read the status register |
88 | //! let isr = self.usart.sr.read(); |
89 | //! |
90 | //! if isr.ore().bit_is_set() { |
91 | //! // Error: Buffer overrun |
92 | //! Err(nb::Error::Other(Self::Error::Overrun)) |
93 | //! } |
94 | //! // omitted: checks for other errors |
95 | //! else if isr.rxne().bit_is_set() { |
96 | //! // Data available: read the data register |
97 | //! Ok(self.usart.dr.read().bits() as u8) |
98 | //! } else { |
99 | //! // No data available yet |
100 | //! Err(nb::Error::WouldBlock) |
101 | //! } |
102 | //! } |
103 | //! } |
104 | //! |
105 | //! impl embedded_hal_nb::serial::Write<u8> for Serial<USART1> { |
106 | //! fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { |
107 | //! // Similar to the `read` implementation |
108 | //! # Ok(()) |
109 | //! } |
110 | //! |
111 | //! fn flush(&mut self) -> nb::Result<(), Self::Error> { |
112 | //! // Similar to the `read` implementation |
113 | //! # Ok(()) |
114 | //! } |
115 | //! } |
116 | //! |
117 | //! # fn main() {} |
118 | //! ``` |
119 | //! |
120 | //! ## Intended usage |
121 | //! |
122 | //! Thanks to the [`nb`] crate the HAL API can be used in a blocking manner |
123 | //! with the [`block!`] macro or with `futures`. |
124 | //! |
125 | //! [`block!`]: https://docs.rs/nb/1.0.0/nb/macro.block.html |
126 | //! |
127 | //! ### Blocking mode |
128 | //! |
129 | //! An example of writing a string over the serial interface in a blocking |
130 | //! fashion: |
131 | //! |
132 | //! ``` |
133 | //! use stm32f1xx_hal::Serial1; |
134 | //! use embedded_hal_nb::serial::Write; |
135 | //! use nb::block; |
136 | //! |
137 | //! # fn main() { |
138 | //! let mut serial: Serial1 = { |
139 | //! // .. |
140 | //! # Serial1 |
141 | //! }; |
142 | //! |
143 | //! for byte in b"Hello, world!" { |
144 | //! // NOTE `block!` blocks until `serial.write()` completes and returns |
145 | //! // `Result<(), Error>` |
146 | //! block!(serial.write(*byte)).unwrap(); |
147 | //! } |
148 | //! # } |
149 | //! |
150 | //! # mod stm32f1xx_hal { |
151 | //! # use embedded_hal_nb; |
152 | //! # use core::convert::Infallible; |
153 | //! # pub struct Serial1; |
154 | //! # impl Serial1 { |
155 | //! # pub fn write(&mut self, _: u8) -> nb::Result<(), Infallible> { |
156 | //! # Ok(()) |
157 | //! # } |
158 | //! # } |
159 | //! # } |
160 | //! ``` |
161 | //! |
162 | //! ## Generic programming and higher level abstractions |
163 | //! |
164 | //! The core of the HAL has been kept minimal on purpose to encourage building **generic** higher |
165 | //! level abstractions on top of it. Some higher level abstractions that pick an asynchronous model |
166 | //! or that have blocking behavior and that are deemed useful to build other abstractions can be |
167 | //! found in the `blocking` module. |
168 | //! |
169 | //! Some examples: |
170 | //! |
171 | //! **NOTE** All the functions shown below could have been written as trait |
172 | //! methods with default implementation to allow specialization, but they have |
173 | //! been written as functions to keep things simple. |
174 | //! |
175 | //! - Write a whole buffer to a serial device in blocking a fashion. |
176 | //! |
177 | //! ``` |
178 | //! use embedded_hal_nb::serial::Write; |
179 | //! use nb::block; |
180 | //! |
181 | //! fn write_all<S>(serial: &mut S, buffer: &[u8]) -> Result<(), S::Error> |
182 | //! where |
183 | //! S: Write<u8> |
184 | //! { |
185 | //! for &byte in buffer { |
186 | //! block!(serial.write(byte))?; |
187 | //! } |
188 | //! |
189 | //! Ok(()) |
190 | //! } |
191 | //! |
192 | //! # fn main() {} |
193 | //! ``` |
194 | //! |
195 | //! - Buffered serial interface with periodic flushing in interrupt handler |
196 | //! |
197 | //! ``` |
198 | //! # use std as core; |
199 | //! use embedded_hal_nb::serial::{ErrorKind, Write}; |
200 | //! use nb::block; |
201 | //! |
202 | //! fn flush<S>(serial: &mut S, cb: &mut CircularBuffer) |
203 | //! where |
204 | //! S: Write<u8, Error = ErrorKind>, |
205 | //! { |
206 | //! loop { |
207 | //! if let Some(byte) = cb.peek() { |
208 | //! match serial.write(*byte) { |
209 | //! Err(nb::Error::Other(_)) => unreachable!(), |
210 | //! Err(nb::Error::WouldBlock) => return, |
211 | //! Ok(()) => {}, // keep flushing data |
212 | //! } |
213 | //! } |
214 | //! |
215 | //! cb.pop(); |
216 | //! } |
217 | //! } |
218 | //! |
219 | //! // The stuff below could be in some other crate |
220 | //! |
221 | //! /// Global singleton |
222 | //! pub struct BufferedSerial1; |
223 | //! |
224 | //! // NOTE private |
225 | //! static BUFFER1: Mutex<CircularBuffer> = { |
226 | //! // .. |
227 | //! # Mutex(CircularBuffer) |
228 | //! }; |
229 | //! static SERIAL1: Mutex<Serial1> = { |
230 | //! // .. |
231 | //! # Mutex(Serial1) |
232 | //! }; |
233 | //! |
234 | //! impl BufferedSerial1 { |
235 | //! pub fn write(&self, byte: u8) { |
236 | //! self.write_all(&[byte]) |
237 | //! } |
238 | //! |
239 | //! pub fn write_all(&self, bytes: &[u8]) { |
240 | //! let mut buffer = BUFFER1.lock(); |
241 | //! for byte in bytes { |
242 | //! buffer.push(*byte).expect("buffer overrun" ); |
243 | //! } |
244 | //! // omitted: pend / enable interrupt_handler |
245 | //! } |
246 | //! } |
247 | //! |
248 | //! fn interrupt_handler() { |
249 | //! let mut serial = SERIAL1.lock(); |
250 | //! let mut buffer = BUFFER1.lock(); |
251 | //! |
252 | //! flush(&mut *serial, &mut buffer); |
253 | //! } |
254 | //! |
255 | //! # struct Mutex<T>(T); |
256 | //! # impl<T> Mutex<T> { |
257 | //! # fn lock(&self) -> RefMut<T> { unimplemented!() } |
258 | //! # } |
259 | //! # struct RefMut<'a, T>(&'a mut T) where T: 'a; |
260 | //! # impl<'a, T> ::core::ops::Deref for RefMut<'a, T> { |
261 | //! # type Target = T; |
262 | //! # fn deref(&self) -> &T { self.0 } |
263 | //! # } |
264 | //! # impl<'a, T> ::core::ops::DerefMut for RefMut<'a, T> { |
265 | //! # fn deref_mut(&mut self) -> &mut T { self.0 } |
266 | //! # } |
267 | //! # struct Serial1; |
268 | //! # impl embedded_hal_nb::serial::ErrorType for Serial1 { |
269 | //! # type Error = ErrorKind; |
270 | //! # } |
271 | //! # impl embedded_hal_nb::serial::Write<u8> for Serial1 { |
272 | //! # fn write(&mut self, _: u8) -> nb::Result<(), Self::Error> { Err(nb::Error::WouldBlock) } |
273 | //! # fn flush(&mut self) -> nb::Result<(), Self::Error> { Err(nb::Error::WouldBlock) } |
274 | //! # } |
275 | //! # struct CircularBuffer; |
276 | //! # impl CircularBuffer { |
277 | //! # pub fn peek(&mut self) -> Option<&u8> { None } |
278 | //! # pub fn pop(&mut self) -> Option<u8> { None } |
279 | //! # pub fn push(&mut self, _: u8) -> Result<(), ()> { Ok(()) } |
280 | //! # } |
281 | //! |
282 | //! # fn main() {} |
283 | //! ``` |
284 | |
285 | #![warn (missing_docs)] |
286 | #![no_std ] |
287 | |
288 | pub use nb; |
289 | |
290 | pub mod serial; |
291 | pub mod spi; |
292 | |