Skip to main content

bluenrg/
lib.rs

1//! Bluetooth HCI for STMicro's BlueNRG-MS Bluetooth controllers.
2//!
3//! *Note*: This crate does not provide support for the BlueNRG-1 or BlueNRG-2 SoCs.
4//!
5//! # Design
6//!
7//! The BlueNRG-MS is an external Bluetooth Radio Controller that communicates with the application
8//! processor over SPI and two dedicated pins:
9//!  1. A SPI chip select pin, and
10//!  2. A data ready signal.
11//!
12//! This crate defines a public struct, [`BlueNRG`] that owns the chip select and data ready
13//! pins, and a receive buffer for the data that comes from the controller. It also defines a
14//! private struct, [`ActiveBlueNRG`] that borrows a handle to the SPI bus. `ActiveBlueNRG`
15//! implements [`bluetooth_hci::Controller`], which provides access to the full Bluetooth HCI.
16//!
17//! BlueNRG-MS implements parts of version 4.1 of the Bluetooth [specification].
18//!
19//! The fundamental way to use the [`BlueNRG`] is its [`with_spi`](BlueNRG::with_spi) function,
20//! which invokes its closure on at [`ActiveBlueNRG`], so sending HCI commands and reading HCI
21//! events can only be done from within that closure.
22//!
23//! # Vendor-Specific Commands
24//!
25//! BlueNRG-MS provides several vendor-specific commands that control the behavior of the
26//! controller.
27//!
28//! # Vendor-Specific Events
29//!
30//! BlueNRG-MS provides several vendor-specific events that provide data related to the
31//! controller. Many of these events are forwarded from the link layer, and these are documented
32//! with a reference to the appropriate section of the Bluetooth specification.
33//!
34//! # Example
35//!
36//! TODO
37//!
38//! [specification]: https://www.bluetooth.com/specifications/bluetooth-core-specification
39
40#![no_std]
41#![deny(missing_docs)]
42
43#[macro_use]
44extern crate bitflags;
45#[macro_use]
46extern crate bluetooth_hci as hci;
47extern crate byteorder;
48extern crate embedded_hal as emhal;
49#[macro_use(block)]
50extern crate nb;
51
52use byteorder::{ByteOrder, LittleEndian};
53use core::cmp::min;
54use core::convert::TryFrom;
55use core::marker::PhantomData;
56use hci::host::HciHeader;
57use hci::Controller;
58
59mod cb;
60mod command;
61pub mod event;
62mod opcode;
63
64pub use command::gap;
65pub use command::gatt;
66pub use command::hal;
67pub use command::l2cap;
68
69pub use hci::host::{AdvertisingFilterPolicy, AdvertisingType, OwnAddressType};
70
71/// Enumeration of potential errors that may occur when reading from or writing to the chip.
72#[derive(Debug, PartialEq)]
73pub enum Error<SpiError, GpioError> {
74    /// SPI errors occur if there is an underlying error during a transfer.
75    Spi(SpiError),
76
77    /// GPIO errors occur if there is an underlying error resetting the pin, setting the chip select
78    /// pin, or reading if data is available.
79    Gpio(GpioError),
80}
81
82/// Handle for interfacing with the BlueNRG-MS.
83pub struct BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
84    /// Dedicated GPIO pin that is used to select the BlueNRG-MS chip on the SPI bus. This allows
85    /// multiple chips to share the same SPI bus.
86    chip_select: OutputPin1,
87
88    /// Dedicated GPIO pin to reset the controller.
89    reset: OutputPin2,
90
91    /// Dedicated GPIO pin that the controller uses to indicate that it has data to send to the
92    /// processor.
93    data_ready: InputPin,
94
95    /// Buffer used to hold bytes read from the controller until the application can process them.
96    /// Should be at least 257 bytes (to hold a header and maximum BLE payload of 255 bytes).
97    rx_buffer: cb::Buffer<'buf, u8>,
98
99    #[doc(hidden)]
100    _spi: PhantomData<SPI>,
101
102    #[doc(hidden)]
103    _gpio_error: PhantomData<GpioError>,
104}
105
106/// Handle for actively communicating with the controller over the SPI bus.
107///
108/// An `ActiveBlueNRG` should not be created by the application, but is passed to closures given to
109/// [`BlueNRG::with_spi`].  `ActiveBlueNRG` implements [`bluetooth_hci::Controller`], so it is used
110/// to access the HCI functions for the controller.
111pub struct ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
112    /// Mutably borrow the BlueNRG handle so we can access pin and buffer.
113    d: &'bnrg mut BlueNRG<'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>,
114
115    /// Mutably borrow the SPI bus so we can communicate with the controller.
116    spi: &'spi mut SPI,
117}
118
119/// Read the SPI header.
120///
121/// The SPI header is 5 bytes. Checks the header to ensure that the controller is ready, and if it
122/// is, returns the number of bytes the controller can receive and the number of bytes it has ready
123/// to transmit.
124///
125/// # Errors
126///
127/// - Returns `nb::Error::WouldBlock` if the first byte indicates that the controller is not yet
128///   ready.
129fn parse_spi_header<E>(header: &[u8; 5]) -> Result<(u16, u16), nb::Error<E>> {
130    const BNRG_READY: u8 = 0x02;
131    if header[0] == BNRG_READY {
132        Ok((
133            LittleEndian::read_u16(&header[1..]),
134            LittleEndian::read_u16(&header[3..]),
135        ))
136    } else {
137        Err(nb::Error::WouldBlock)
138    }
139}
140
141enum Access {
142    Read,
143    Write,
144}
145
146impl Access {
147    fn byte(&self) -> u8 {
148        match self {
149            Access::Read => 0x0b,
150            Access::Write => 0x0a,
151        }
152    }
153}
154
155impl<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, SpiError, GpioError>
156    ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
157where
158    SPI: emhal::blocking::spi::Transfer<u8, Error = SpiError>
159        + emhal::blocking::spi::Write<u8, Error = SpiError>,
160    OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
161    OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
162    InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
163{
164    /// Wait for the chip to respond that it is awake and ready.  The chip select line must be
165    /// toggled before sending another SPI header.
166    ///
167    /// On entry, the chip select line must be low. On exit, the chip select line is low.
168    ///
169    /// Empirically, the loop runs 2 to 4 times when the chip is not awake.
170    ///
171    /// Returns the number of bytes that can be written to the chip, and the number of bytes that
172    /// should be read from the chip.  Returns an error if there is an underlying SPI error.
173    fn block_until_ready(
174        &mut self,
175        access_byte: u8,
176    ) -> nb::Result<(u16, u16), Error<SpiError, GpioError>> {
177        loop {
178            let mut write_header = [access_byte, 0x00, 0x00, 0x00, 0x00];
179            self.spi
180                .transfer(&mut write_header)
181                .map_err(Error::Spi)
182                .map_err(nb::Error::Other)?;
183
184            match parse_spi_header(&write_header) {
185                Ok(lengths) => return Ok(lengths),
186                Err(nb::Error::WouldBlock) => {
187                    self.d
188                        .chip_select
189                        .set_high()
190                        .map_err(Error::Gpio)
191                        .map_err(nb::Error::Other)?;
192                    self.d
193                        .chip_select
194                        .set_low()
195                        .map_err(Error::Gpio)
196                        .map_err(nb::Error::Other)?;
197                }
198                Err(err) => return Err(err),
199            }
200        }
201    }
202
203    fn block_until_ready_for(
204        &mut self,
205        access: Access,
206    ) -> nb::Result<u16, Error<SpiError, GpioError>> {
207        let (write_len, read_len) = self.block_until_ready(access.byte())?;
208        Ok(match access {
209            Access::Read => read_len,
210            Access::Write => write_len,
211        })
212    }
213
214    /// Write data to the chip over the SPI bus. First writes a BlueNRG SPI header to the
215    /// controller, indicating the host wants to write. The controller returns one byte indicating
216    /// whether or not it is ready, followed by a pair of u16s in little endian: the first is the
217    /// number of bytes the controller can receive, and the second is the number of bytes the
218    /// controller has ready to transmit.
219    ///
220    /// If the controller claims to have enough room to receive the header and payload, this writes
221    /// the header immediately followed by the payload.
222    ///
223    /// # Errors
224    ///
225    /// - Returns nb::Error::WouldBlock if the controller is not ready to receive data or if it
226    ///   reports that it does not have enough space to accept the combined header and payload.
227    ///
228    /// - Returns a communication error if there is an error communicating over the SPI bus.
229    fn try_write(
230        &mut self,
231        header: &[u8],
232        payload: &[u8],
233    ) -> nb::Result<(), Error<SpiError, GpioError>> {
234        if !header.is_empty() {
235            self.spi
236                .write(header)
237                .map_err(Error::Spi)
238                .map_err(nb::Error::Other)?;
239        }
240        if !payload.is_empty() {
241            self.spi
242                .write(payload)
243                .map_err(Error::Spi)
244                .map_err(nb::Error::Other)?;
245        }
246
247        Ok(())
248    }
249
250    /// Read data from the chip over the SPI bus. First writes a BlueNRG SPI header to the
251    /// controller, indicating that the host wants to read. The controller returns one byte
252    /// indicating whether or not it is ready, followed by a pair of u16s in little endian: the
253    /// first is the number of bytes the controller can receive, and the second is the number of
254    /// bytes the controller has ready to transmit.
255    ///
256    /// If the controller is ready and has data available, reads the available data into the host's
257    /// RX buffer, until either there is no more data or the RX buffer is full, whichever comes
258    /// first.
259    ///
260    /// # Errors
261    ///
262    /// - Returns nb::Error::WouldBlock if the controller is not ready.
263    ///
264    /// - Returns a communication error if there is an error communicating over the SPI bus.
265    fn read_available_data(&mut self) -> nb::Result<(), Error<SpiError, GpioError>> {
266        if !self
267            .d
268            .data_ready()
269            .map_err(Error::Gpio)
270            .map_err(nb::Error::Other)?
271        {
272            return Err(nb::Error::WouldBlock);
273        }
274
275        let read_len = self.block_until_ready_for(Access::Read)?;
276        let mut bytes_available = read_len as usize;
277        while bytes_available > 0 && self.d.rx_buffer.next_contiguous_slice_len() > 0 {
278            let transfer_count = min(
279                bytes_available,
280                self.d.rx_buffer.next_contiguous_slice_len(),
281            );
282            {
283                let rx = self.d.rx_buffer.next_mut_slice(transfer_count);
284                for byte in rx.iter_mut() {
285                    *byte = 0;
286                }
287                self.spi
288                    .transfer(rx)
289                    .map_err(Error::Spi)
290                    .map_err(nb::Error::Other)?;
291            }
292            bytes_available -= transfer_count;
293        }
294
295        Ok(())
296    }
297
298    fn write_command(
299        &mut self,
300        opcode: opcode::Opcode,
301        params: &[u8],
302    ) -> nb::Result<(), Error<SpiError, GpioError>> {
303        const HEADER_LEN: usize = 4;
304        let mut header = [0; HEADER_LEN];
305        hci::host::uart::CommandHeader::new(opcode, params.len()).copy_into_slice(&mut header);
306
307        self.write(&header, &params)
308    }
309}
310
311impl<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, SpiError, GpioError> hci::Controller
312    for ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
313where
314    SPI: emhal::blocking::spi::Transfer<u8, Error = SpiError>
315        + emhal::blocking::spi::Write<u8, Error = SpiError>,
316    OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
317    OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
318    InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
319{
320    type Error = Error<SpiError, GpioError>;
321    type Header = hci::host::uart::CommandHeader;
322    type Vendor = BlueNRGTypes;
323
324    fn write(&mut self, header: &[u8], payload: &[u8]) -> nb::Result<(), Self::Error> {
325        self.d
326            .chip_select
327            .set_low()
328            .map_err(Error::Gpio)
329            .map_err(nb::Error::Other)?;
330        let write_len = self.block_until_ready_for(Access::Write)?;
331        if (write_len as usize) < header.len() + payload.len() {
332            return Err(nb::Error::WouldBlock);
333        }
334
335        let result = self.try_write(header, payload);
336        self.d
337            .chip_select
338            .set_high()
339            .map_err(Error::Gpio)
340            .map_err(nb::Error::Other)?;
341
342        result
343    }
344
345    fn read_into(&mut self, buffer: &mut [u8]) -> nb::Result<(), Self::Error> {
346        let result = if buffer.len() > self.d.rx_buffer.size() {
347            self.d
348                .chip_select
349                .set_low()
350                .map_err(Error::Gpio)
351                .map_err(nb::Error::Other)?;
352            let r = self.read_available_data();
353            self.d
354                .chip_select
355                .set_high()
356                .map_err(Error::Gpio)
357                .map_err(nb::Error::Other)?;
358
359            r
360        } else {
361            Ok(())
362        };
363
364        if buffer.len() <= self.d.rx_buffer.size() {
365            self.d.rx_buffer.take_slice(buffer.len(), buffer);
366            Ok(())
367        } else if let Err(e) = result {
368            Err(e)
369        } else {
370            Err(nb::Error::WouldBlock)
371        }
372    }
373
374    fn peek(&mut self, n: usize) -> nb::Result<u8, Self::Error> {
375        if n >= self.d.rx_buffer.size() {
376            if !self
377                .d
378                .data_ready()
379                .map_err(Error::Gpio)
380                .map_err(nb::Error::Other)?
381            {
382                return Err(nb::Error::WouldBlock);
383            }
384
385            self.d
386                .chip_select
387                .set_low()
388                .map_err(Error::Gpio)
389                .map_err(nb::Error::Other)?;
390            let result = self.read_available_data();
391            self.d
392                .chip_select
393                .set_high()
394                .map_err(Error::Gpio)
395                .map_err(nb::Error::Other)?;
396
397            if n >= self.d.rx_buffer.size() {
398                if let Err(e) = result {
399                    return Err(e);
400                }
401
402                // Returns WouldBlock below
403            }
404        }
405
406        if n < self.d.rx_buffer.size() {
407            Ok(self.d.rx_buffer.peek(n))
408        } else {
409            Err(nb::Error::WouldBlock)
410        }
411    }
412}
413
414/// Specify vendor-specific extensions for the BlueNRG.
415pub struct BlueNRGTypes;
416impl hci::Vendor for BlueNRGTypes {
417    type Status = event::Status;
418    type Event = event::BlueNRGEvent;
419}
420
421/// Master trait that encompasses all commands, and communicates over UART.
422pub trait UartController<E>:
423    crate::gap::Commands<Error = E>
424    + crate::gatt::Commands<Error = E>
425    + crate::hal::Commands<Error = E>
426    + crate::l2cap::Commands<Error = E>
427    + bluetooth_hci::host::uart::Hci<E, crate::event::BlueNRGEvent, crate::event::BlueNRGError>
428{
429}
430impl<T, E> UartController<E> for T where
431    T: crate::gap::Commands<Error = E>
432        + crate::gatt::Commands<Error = E>
433        + crate::hal::Commands<Error = E>
434        + crate::l2cap::Commands<Error = E>
435        + bluetooth_hci::host::uart::Hci<E, crate::event::BlueNRGEvent, crate::event::BlueNRGError>
436{
437}
438
439impl<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
440    BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
441where
442    OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
443    OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
444    InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
445{
446    /// Returns a new BlueNRG struct with the given RX Buffer and pins. Resets the controller.
447    pub fn new(
448        rx_buffer: &'buf mut [u8],
449        cs: OutputPin1,
450        dr: InputPin,
451        rst: OutputPin2,
452    ) -> BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
453        BlueNRG {
454            chip_select: cs,
455            rx_buffer: cb::Buffer::new(rx_buffer),
456            data_ready: dr,
457            reset: rst,
458            _spi: PhantomData,
459            _gpio_error: PhantomData,
460        }
461    }
462
463    /// Invokes the given body function with an ActiveBlueNRG that uses this BlueNRG struct and the
464    /// provided SPI bus handle.
465    ///
466    /// Returns the result of the invoked body.
467    pub fn with_spi<'spi, T, F, E>(&mut self, spi: &'spi mut SPI, body: F) -> T
468    where
469        F: FnOnce(&mut ActiveBlueNRG<SPI, OutputPin1, OutputPin2, InputPin, GpioError>) -> T,
470        SPI: emhal::blocking::spi::transfer::Default<u8, Error = E>
471            + emhal::blocking::spi::write::Default<u8, Error = E>,
472    {
473        let mut active =
474            ActiveBlueNRG::<SPI, OutputPin1, OutputPin2, InputPin, GpioError> { spi, d: self };
475        body(&mut active)
476    }
477
478    /// Resets the BlueNRG Controller. Uses the given timer to delay 1 cycle at `freq` Hz after
479    /// toggling the reset pin.
480    pub fn reset<T, Time>(&mut self, timer: &mut T, freq: Time) -> nb::Result<(), OutputPin2::Error>
481    where
482        T: emhal::timer::CountDown<Time = Time>,
483        Time: Copy,
484    {
485        self.reset.set_low().map_err(nb::Error::Other)?;
486        timer.start(freq);
487        block!(timer.wait()).unwrap();
488
489        self.reset.set_high().map_err(nb::Error::Other)?;
490        timer.start(freq);
491        block!(timer.wait()).unwrap();
492
493        Ok(())
494    }
495
496    /// Returns true if the controller has data ready to transmit to the host.
497    fn data_ready(&self) -> Result<bool, InputPin::Error> {
498        self.data_ready.is_high()
499    }
500}
501
502/// Vendor-specific interpretation of the local version information from the controller.
503#[derive(Clone)]
504pub struct Version {
505    /// Version of the controller hardware.
506    pub hw_version: u8,
507
508    /// Major version of the controller firmware
509    pub major: u8,
510
511    /// Minor version of the controller firmware
512    pub minor: u8,
513
514    /// Patch version of the controller firmware
515    pub patch: u8,
516}
517
518/// Extension trait to convert [`hci::event::command::LocalVersionInfo`] into the BlueNRG-specific
519/// [`Version`] struct.
520pub trait LocalVersionInfoExt {
521    /// Converts LocalVersionInfo as returned by the controller into a BlueNRG-specific [`Version`]
522    /// struct.
523    fn bluenrg_version(&self) -> Version;
524}
525
526impl<VS> LocalVersionInfoExt for hci::event::command::LocalVersionInfo<VS> {
527    fn bluenrg_version(&self) -> Version {
528        Version {
529            hw_version: (self.hci_revision >> 8) as u8,
530            major: (self.hci_revision & 0xFF) as u8,
531            minor: ((self.lmp_subversion >> 4) & 0xF) as u8,
532            patch: (self.lmp_subversion & 0xF) as u8,
533        }
534    }
535}
536
537/// Hardware event codes returned by the `HardwareError` HCI event.
538#[derive(Copy, Clone, Debug, PartialEq)]
539pub enum HardwareError {
540    /// Error on the SPI bus has been detected, most likely caused by incorrect SPI configuration on
541    /// the external micro-controller.
542    SpiFraming,
543
544    /// Caused by a slow crystal startup and they are an indication that the HS_STARTUP_TIME in the
545    /// device configuration needs to be tuned. After this event is recommended to hardware reset
546    /// the device.
547    RadioState,
548
549    /// Caused by a slow crystal startup and they are an indication that the HS_STARTUP_TIME in the
550    /// device configuration needs to be tuned. After this event is recommended to hardware reset
551    /// the device.
552    TimerOverrun,
553}
554
555/// Error type for `TryFrom<u8>` to `HardwareError`. Includes the invalid byte.
556#[derive(Copy, Clone, Debug, PartialEq)]
557pub struct InvalidHardwareError(pub u8);
558
559impl TryFrom<u8> for HardwareError {
560    type Error = InvalidHardwareError;
561    fn try_from(value: u8) -> Result<Self, Self::Error> {
562        match value {
563            0 => Ok(HardwareError::SpiFraming),
564            1 => Ok(HardwareError::RadioState),
565            2 => Ok(HardwareError::TimerOverrun),
566            _ => Err(InvalidHardwareError(value)),
567        }
568    }
569}