-
Notifications
You must be signed in to change notification settings - Fork 835
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples/stm32g0: added ds18b20 temperature sensor on 1-wire bus
- Loading branch information
Showing
1 changed file
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
//! This examples shows how you can use buffered or DMA UART to read a DS18B20 temperature sensor on 1-Wire bus. | ||
//! Connect 5k pull-up resistor between PA9 and 3.3V. | ||
#![no_std] | ||
#![no_main] | ||
|
||
use cortex_m::singleton; | ||
use defmt::*; | ||
use embassy_executor::Spawner; | ||
use embassy_stm32::mode::Async; | ||
use embassy_stm32::usart::{ | ||
BufferedUartRx, BufferedUartTx, Config, ConfigError, HalfDuplexConfig, RingBufferedUartRx, UartTx, | ||
}; | ||
use embassy_stm32::{bind_interrupts, peripherals, usart}; | ||
use embassy_time::{Duration, Timer}; | ||
use {defmt_rtt as _, panic_probe as _}; | ||
|
||
/// Create onewire bus using DMA USART | ||
fn create_onewire(p: embassy_stm32::Peripherals) -> OneWire<UartTx<'static, Async>, RingBufferedUartRx<'static>> { | ||
use embassy_stm32::usart::Uart; | ||
bind_interrupts!(struct Irqs { | ||
USART1 => usart::InterruptHandler<peripherals::USART1>; | ||
}); | ||
|
||
let usart = Uart::new_half_duplex( | ||
p.USART1, | ||
p.PA9, | ||
Irqs, | ||
p.DMA1_CH1, | ||
p.DMA1_CH2, | ||
Config::default(), | ||
// Enable readback so we can read sensor pulling data low while transmission is in progress | ||
usart::HalfDuplexReadback::Readback, | ||
HalfDuplexConfig::OpenDrainExternal, | ||
) | ||
.unwrap(); | ||
|
||
const BUFFER_SIZE: usize = 16; | ||
let (tx, rx) = usart.split(); | ||
let rx_buf: &mut [u8; BUFFER_SIZE] = singleton!(TX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); | ||
let rx = rx.into_ring_buffered(rx_buf); | ||
OneWire::new(tx, rx) | ||
} | ||
|
||
/* | ||
/// Create onewire bus using buffered USART | ||
fn create_onewire(p: embassy_stm32::Peripherals) -> OneWire<BufferedUartTx<'static>, BufferedUartRx<'static>> { | ||
use embassy_stm32::usart::BufferedUart; | ||
bind_interrupts!(struct Irqs { | ||
USART1 => usart::BufferedInterruptHandler<peripherals::USART1>; | ||
}); | ||
const BUFFER_SIZE: usize = 16; | ||
let tx_buf: &mut [u8; BUFFER_SIZE] = singleton!(TX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); | ||
let rx_buf: &mut [u8; BUFFER_SIZE] = singleton!(RX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); | ||
let usart = BufferedUart::new_half_duplex( | ||
p.USART1, | ||
p.PA9, | ||
Irqs, | ||
tx_buf, | ||
rx_buf, | ||
Config::default(), | ||
// Enable readback so we can read sensor pulling data low while transmission is in progress | ||
usart::HalfDuplexReadback::Readback, | ||
HalfDuplexConfig::OpenDrainExternal, | ||
) | ||
.unwrap(); | ||
let (tx, rx) = usart.split(); | ||
OneWire::new(tx, rx) | ||
} | ||
*/ | ||
|
||
#[embassy_executor::main] | ||
async fn main(_spawner: Spawner) { | ||
let p = embassy_stm32::init(Default::default()); | ||
|
||
let onewire = create_onewire(p); | ||
let mut sensor = Ds18b20::new(onewire); | ||
|
||
loop { | ||
// Start a new temperature measurement | ||
sensor.start().await; | ||
// Wait for the measurement to finish | ||
Timer::after(Duration::from_secs(1)).await; | ||
match sensor.temperature().await { | ||
Ok(temp) => info!("temp = {:?} deg C", temp), | ||
_ => error!("sensor error"), | ||
} | ||
Timer::after(Duration::from_secs(1)).await; | ||
} | ||
} | ||
|
||
pub trait SetBaudrate { | ||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError>; | ||
} | ||
impl SetBaudrate for BufferedUartTx<'_> { | ||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { | ||
BufferedUartTx::set_baudrate(self, baudrate) | ||
} | ||
} | ||
impl SetBaudrate for BufferedUartRx<'_> { | ||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { | ||
BufferedUartRx::set_baudrate(self, baudrate) | ||
} | ||
} | ||
impl SetBaudrate for RingBufferedUartRx<'_> { | ||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { | ||
RingBufferedUartRx::set_baudrate(self, baudrate) | ||
} | ||
} | ||
impl SetBaudrate for UartTx<'_, Async> { | ||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { | ||
UartTx::set_baudrate(self, baudrate) | ||
} | ||
} | ||
|
||
/// Simplified OneWire bus driver | ||
pub struct OneWire<TX, RX> | ||
where | ||
TX: embedded_io_async::Write + SetBaudrate, | ||
RX: embedded_io_async::Read + SetBaudrate, | ||
{ | ||
tx: TX, | ||
rx: RX, | ||
} | ||
|
||
impl<TX, RX> OneWire<TX, RX> | ||
where | ||
TX: embedded_io_async::Write + SetBaudrate, | ||
RX: embedded_io_async::Read + SetBaudrate, | ||
{ | ||
// bitrate with one bit taking ~104 us | ||
const RESET_BUADRATE: u32 = 9600; | ||
// bitrate with one bit taking ~8.7 us | ||
const BAUDRATE: u32 = 115200; | ||
|
||
// startbit + 8 low bits = 9 * 1/115200 = 78 us low pulse | ||
const LOGIC_1_CHAR: u8 = 0xFF; | ||
// startbit only = 1/115200 = 8.7 us low pulse | ||
const LOGIC_0_CHAR: u8 = 0x00; | ||
|
||
// Address all devices on the bus | ||
const COMMAND_SKIP_ROM: u8 = 0xCC; | ||
|
||
pub fn new(tx: TX, rx: RX) -> Self { | ||
Self { tx, rx } | ||
} | ||
|
||
fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { | ||
self.tx.set_baudrate(baudrate)?; | ||
self.rx.set_baudrate(baudrate) | ||
} | ||
|
||
/// Reset the bus by at least 480 us low pulse. | ||
pub async fn reset(&mut self) { | ||
// Switch to 9600 baudrate, so one bit takes ~104 us | ||
self.set_baudrate(Self::RESET_BUADRATE).expect("set_baudrate failed"); | ||
// Low USART start bit + 4x low bits = 5 * 104 us = 520 us low pulse | ||
self.tx.write(&[0xF0]).await.expect("write failed"); | ||
|
||
// Read the value on the bus | ||
let mut buffer = [0; 1]; | ||
self.rx.read_exact(&mut buffer).await.expect("read failed"); | ||
|
||
// Switch back to 115200 baudrate, so one bit takes ~8.7 us | ||
self.set_baudrate(Self::BAUDRATE).expect("set_baudrate failed"); | ||
|
||
// read and expect sensor pulled some high bits to low (device present) | ||
if buffer[0] & 0xF != 0 || buffer[0] & 0xF0 == 0xF0 { | ||
warn!("No device present"); | ||
} | ||
} | ||
|
||
/// Send byte and read response on the bus. | ||
pub async fn write_read_byte(&mut self, byte: u8) -> u8 { | ||
// One byte is sent as 8 UART characters | ||
let mut tx = [0; 8]; | ||
for (pos, char) in tx.iter_mut().enumerate() { | ||
*char = if (byte >> pos) & 0x1 == 0x1 { | ||
Self::LOGIC_1_CHAR | ||
} else { | ||
Self::LOGIC_0_CHAR | ||
}; | ||
} | ||
self.tx.write_all(&tx).await.expect("write failed"); | ||
|
||
// Readback the value on the bus, sensors can pull logic 1 to 0 | ||
let mut rx = [0; 8]; | ||
self.rx.read_exact(&mut rx).await.expect("read failed"); | ||
let mut bus_byte = 0; | ||
for (pos, char) in rx.iter().enumerate() { | ||
// if its 0xFF, sensor didnt pull the bus to low level | ||
if *char == 0xFF { | ||
bus_byte |= 1 << pos; | ||
} | ||
} | ||
|
||
bus_byte | ||
} | ||
|
||
/// Read a byte from the bus. | ||
pub async fn read_byte(&mut self) -> u8 { | ||
self.write_read_byte(0xFF).await | ||
} | ||
} | ||
|
||
/// DS18B20 temperature sensor driver | ||
pub struct Ds18b20<TX, RX> | ||
where | ||
TX: embedded_io_async::Write + SetBaudrate, | ||
RX: embedded_io_async::Read + SetBaudrate, | ||
{ | ||
bus: OneWire<TX, RX>, | ||
} | ||
|
||
impl<TX, RX> Ds18b20<TX, RX> | ||
where | ||
TX: embedded_io_async::Write + SetBaudrate, | ||
RX: embedded_io_async::Read + SetBaudrate, | ||
{ | ||
/// Start a temperature conversion. | ||
const FN_CONVERT_T: u8 = 0x44; | ||
/// Read contents of the scratchpad containing the temperature. | ||
const FN_READ_SCRATCHPAD: u8 = 0xBE; | ||
|
||
pub fn new(bus: OneWire<TX, RX>) -> Self { | ||
Self { bus } | ||
} | ||
|
||
/// Start a new measurement. Allow at least 1000ms before getting `temperature`. | ||
pub async fn start(&mut self) { | ||
self.bus.reset().await; | ||
self.bus.write_read_byte(OneWire::<TX, RX>::COMMAND_SKIP_ROM).await; | ||
self.bus.write_read_byte(Self::FN_CONVERT_T).await; | ||
} | ||
|
||
/// Calculate CRC8 of the data | ||
fn crc8(data: &[u8]) -> u8 { | ||
let mut temp; | ||
let mut data_byte; | ||
let mut crc = 0; | ||
for b in data { | ||
data_byte = *b; | ||
for _ in 0..8 { | ||
temp = (crc ^ data_byte) & 0x01; | ||
crc >>= 1; | ||
if temp != 0 { | ||
crc ^= 0x8C; | ||
} | ||
data_byte >>= 1; | ||
} | ||
} | ||
crc | ||
} | ||
|
||
/// Read the temperature. Ensure >1000ms has passed since `start` before calling this. | ||
pub async fn temperature(&mut self) -> Result<f32, ()> { | ||
self.bus.reset().await; | ||
self.bus.write_read_byte(OneWire::<TX, RX>::COMMAND_SKIP_ROM).await; | ||
self.bus.write_read_byte(Self::FN_READ_SCRATCHPAD).await; | ||
|
||
let mut data = [0; 9]; | ||
for byte in data.iter_mut() { | ||
*byte = self.bus.read_byte().await; | ||
} | ||
|
||
match Self::crc8(&data) == 0 { | ||
true => Ok(((data[1] as u16) << 8 | data[0] as u16) as f32 / 16.), | ||
false => Err(()), | ||
} | ||
} | ||
} |