1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![no_std]
use bitmatch::bitmatch;
use core::unimplemented;
use embedded_hal as hal;
use hal::blocking::spi;
use nb::{self, block};
// Bit pattern definitions for the communication with the hx711. All have to be bitwise negate
// for the ```invert-sdo``` feature
// patterns for mode
#[cfg(not(feature = "invert-sdo"))]
const GAIN128: u8 = 0b1000_0000;
#[cfg(feature = "invert-sdo")]
const GAIN128: u8 = 0b0111_1111;
#[cfg(not(feature = "invert-sdo"))]
const GAIN32: u8 = 0b1010_0000;
#[cfg(feature = "invert-sdo")]
const GAIN32: u8 = 0b0101_1111;
#[cfg(not(feature = "invert-sdo"))]
const GAIN64: u8 = 0b1010_1000;
#[cfg(feature = "invert-sdo")]
const GAIN64: u8 = 0b0101_0111;
// SDO provides clock to the HX711's shift register (binary 1010...)
// one clock cycle is '10'. The buffer needs to be double the size of the 4 bytes we want to read
#[cfg(not(feature = "invert-sdo"))]
const CLOCK: u8 = 0b10101010;
#[cfg(feature = "invert-sdo")]
const CLOCK: u8 = 0b01010101;
// Signal to send to the HX711 when checking for data ready to be read.
#[cfg(not(feature = "invert-sdo"))]
const SIGNAL_LOW: u8 = 0x0;
#[cfg(feature = "invert-sdo")]
const SIGNAL_LOW: u8 = 0xFF;
// reset signal
#[cfg(not(feature = "invert-sdo"))]
const RESET_SIGNAL: [u8; 301] = [0xFF; 301];
#[cfg(feature = "invert-sdo")]
const RESET_SIGNAL: [u8; 301] = [0x00; 301];
// End bit pattern definitions
/// The HX711 has two channels: `A` for the load cell and `B` for AD conversion of other signals.
/// Channel `A` supports gains of 128 (default) and 64, `B` has a fixed gain of 32.
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum Mode {
// bits have to be converted for correct transfer 1 -> 10, 0 -> 00
/// Convert channel A with a gain factor of 128
ChAGain128 = GAIN128,
/// Convert channel B with a gain factor of 32
ChBGain32 = GAIN32,
/// Convert channel A with a gain factor of 64
ChAGain64 = GAIN64, // there is a typo in the official datasheet: in Fig.2 it says channel B instead of A
}
/// Represents an instance of a HX711 device
#[derive(Debug)]
pub struct Hx711<SPI> {
// SPI specific
spi: SPI,
// device specific
mode: Mode,
}
impl<SPI, E> Hx711<SPI>
where
SPI: spi::Transfer<u8, Error = E> + spi::Write<u8, Error = E>,
{
/// opens a connection to a HX711 on a specified SPI.
///
/// The datasheet specifies PD_SCK high time and PD_SCK low time to be in the 0.2 to 50 us range,
/// therefore bus speed has to be between 5 MHz and 20 kHz. 1 MHz seems to be a good choice.
/// D is an embedded_hal implementation of DelayMs.
pub fn new(spi: SPI) -> Self {
Hx711 {
spi,
mode: Mode::ChAGain128,
}
}
/// reads a value from the HX711 and returns it
/// # Errors
/// Returns SPI errors and nb::Error::WouldBlock if data isn't ready to be read from hx711
pub fn read(&mut self) -> nb::Result<i32, E> {
// check if data is ready
// When output data is not ready for retrieval, digital output pin DOUT is high.
// Serial clock input PD_SCK should be low. When DOUT goes
// to low, it indicates data is ready for retrieval.
let mut txrx: [u8; 1] = [SIGNAL_LOW];
self.spi.transfer(&mut txrx)?;
if txrx[0] & 0b01 == 0b01 {
// as long as the lowest bit is high there is no data waiting
return Err(nb::Error::WouldBlock);
}
// the read has the same length as the write.
let mut buffer: [u8; 7] = [CLOCK, CLOCK, CLOCK, CLOCK, CLOCK, CLOCK, self.mode as u8];
self.spi.transfer(&mut buffer)?;
// value should be in range 0x800000 - 0x7fffff according to datasheet
Ok(decode_output(&buffer))
}
#[inline]
/// This is for compatibility only. Use [read]() instead.
pub fn retrieve(&mut self) -> nb::Result<i32, E> {
self.read()
}
/// Reset the chip to it's default state. Mode is set to convert channel A with a gain factor of 128.
/// # Errors
/// Returns SPI errors
#[inline]
pub fn reset(&mut self) -> Result<(), E> {
// when PD_SCK pin changes from low to high and stays at high for longer than 60µs,
// HX711 enters power down mode.
// When PD_SCK returns to low, chip will reset and enter normal operation mode.
// speed is the raw SPI speed -> half bits per second.
// max SPI clock frequency should be 5 MHz to satisfy the 0.2 us limit for the pulse length
// we have to output more than 300 bytes to keep the line for at least 60 us high.
let buffer: [u8; 301] = RESET_SIGNAL;
self.spi.write(&buffer)?;
self.mode = Mode::ChAGain128; // this is the default mode after reset
Ok(())
}
/// Set the mode to the value specified.
/// # Errors
/// Returns SPI errors
#[inline]
pub fn set_mode(&mut self, m: Mode) -> Result<Mode, E> {
self.mode = m;
block!(self.read())?; // read writes Mode for the next read()
Ok(m)
}
#[inline]
/// Get the current mode.
pub fn mode(&mut self) -> Mode {
self.mode
}
#[inline]
/// This is for compatibility only. Use [mode]() instead.
pub fn get_mode(&mut self) -> Mode {
self.mode
}
/// To power down the chip the PD_SCK line has to be held in a 'high' state. To do this we
/// would need to write a constant stream of binary '1' to the SPI bus which would totally defy
/// the purpose. Therefore it's not implemented.
// If the SDO pin would be idle high (and at least some MCU's seem to do that in mode 1) then the chip would automatically
// power down if not used. Cool!
pub fn disable(&mut self) -> Result<(), E> {
// when PD_SCK pin changes from low to high and stays at high for longer than 60µs, HX711 enters power down mode
// When PD_SCK returns to low, chip will reset and enter normal operation mode.
// this can't be implemented with SPI because we would have to write a constant stream
// of binary '1' which would block the process
unimplemented!("power_down is not possible with this driver implementation");
}
/// Power up / down is not implemented (see disable)
pub fn enable(&mut self) -> Result<(), E> {
// when PD_SCK pin changes from low to high and stays at high for longer than 60µs, HX711 enters power down mode
// When PD_SCK returns to low, chip will reset and enter normal operation mode.
// this can't be implemented with SPI because we would have to write a constant stream
// of binary '1' which would block the process
unimplemented!("power_down is not possible with this driver implementation");
}
}
#[bitmatch]
fn decode_output(buffer: &[u8; 7]) -> i32 {
// buffer contains the 2's complement of the reading with every bit doubled
// since the first byte is the most significant it's big endian
// we have to extract every second bit from the buffer
// only the upper 24 (doubled) bits are valid
#[bitmatch]
let "a?a?a?a?" = buffer[0];
#[bitmatch]
let "b?b?b?b?" = buffer[1];
#[bitmatch]
let "c?c?c?c?" = buffer[2];
#[bitmatch]
let "d?d?d?d?" = buffer[3];
#[bitmatch]
let "e?e?e?e?" = buffer[4];
#[bitmatch]
let "f?f?f?f?" = buffer[5];
let mut raw: [u8; 4] = [0; 4];
raw[0] = bitpack!("aaaabbbb");
raw[1] = bitpack!("ccccdddd");
raw[2] = bitpack!("eeeeffff");
raw[3] = 0;
i32::from_be_bytes(raw) / 0x100
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case(&[0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55] => 0; "alternating convert to zeros")]
#[test_case(&[0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] => -1; "alternating convert to ones")]
#[test_case(&[0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] => -1; "all ones")]
#[test_case(&[0b00100111, 0b00100111, 0b00100111, 0b00100111,
0b00100111, 0b00100111, 0b00100111] => 0b0000_0000_0101_0101_0101_0101_0101_0101i32; "test pattern")]
fn test_decode(buffer: &[u8; 7]) -> i32 {
decode_output(&buffer)
}
}