|
| 1 | +#![no_std] |
| 2 | +#![no_main] |
| 3 | + |
| 4 | +use defmt::info; |
| 5 | +use defmt_rtt as _; |
| 6 | +use embassy_executor::Spawner; |
| 7 | +use embassy_stm32::{ |
| 8 | + bind_interrupts, |
| 9 | + i2c::{self, I2c}, |
| 10 | + peripherals, |
| 11 | +}; |
| 12 | +use embassy_time::Timer; |
| 13 | +use panic_probe as _; |
| 14 | + |
| 15 | +// For I2C to work, we need to bind the interrupts to the correct handlers. |
| 16 | +bind_interrupts!(struct Irqs { |
| 17 | + I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>; |
| 18 | + I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>; |
| 19 | +}); |
| 20 | + |
| 21 | +/// I2C device address for the BMP390. This is the default address when A0 is |
| 22 | +/// connected to low. If A0 is connected to high, the address will be 0x77. |
| 23 | +/// |
| 24 | +/// This will likely work even if A0 is not connected at all, in theory this |
| 25 | +/// should be a "floating" pin, but in practice for our PM Lab board it will |
| 26 | +/// function as if it were connected to low. |
| 27 | +const BMP390_ADDR: u8 = 0x76; |
| 28 | + |
| 29 | +/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off |
| 30 | +/// measurements and sets the power mode.) |
| 31 | +/// |
| 32 | +/// | Bits | Description | |
| 33 | +/// |------|-------------------------------------------------| |
| 34 | +/// | 0 | Pressure measurement on/off. 1 = on, 0 = off | |
| 35 | +/// | 1 | Temperature measurement on/off. 1 = on, 0 = off | |
| 36 | +/// | 2-3 | Reserved, must be set to 0 | |
| 37 | +/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one |
| 38 | +/// measurement), 11 = normal | |
| 39 | +/// | 6-7 | Reserved, must be set to 0 | |
| 40 | +const REGISTER_PWR_CTRL: u8 = 0x1B; |
| 41 | +/// Bits to set in the `PWR_CTRL` register to set normal power mode. |
| 42 | +const PWR_MODE_ON: u8 = 0b0011_0000; |
| 43 | +/// Bits to set in the `PWR_CTRL` register to enable temperature measurement. |
| 44 | +const PWR_TEMP_EN: u8 = 0b0000_0010; |
| 45 | +/// Value to write to the `PWR_CTRL` register to enable temperature measurement |
| 46 | +/// and set normal power mode. |
| 47 | +const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; |
| 48 | + |
| 49 | +/// Register addresses and values for the `OSR` register. (Controls how many |
| 50 | +/// samples are taken and averaged for each measurement) |
| 51 | +/// |
| 52 | +/// | Bits | Description | |
| 53 | +/// |------|-------------------------------------------------| |
| 54 | +/// | 0-2 | Pressure oversampling. |
| 55 | +/// 000 = no oversampling (1 sample), |
| 56 | +/// 001 = x2 (2 samples), |
| 57 | +/// 010 = x4 (4 samples), |
| 58 | +/// 011 = x8 (8 samples), |
| 59 | +/// 100 = x16 (16 samples), |
| 60 | +/// 101 = x32 (32 samples), | |
| 61 | +/// | 3-5 | Temperature oversampling. |
| 62 | +/// 000 = no oversampling (1 sample), |
| 63 | +/// 001 = x2 (2 samples), |
| 64 | +/// 010 = x4 (4 samples), |
| 65 | +/// 011 = x8 (8 samples), |
| 66 | +/// 100 = x16 (16 samples), |
| 67 | +/// 101 = x32 (32 samples), | |
| 68 | +/// | 6-7 | Reserved, must be set to 0 | |
| 69 | +const REGISTER_OSR: u8 = 0x1C; |
| 70 | +/// Bits to set in the `OSR` register to set temperature oversampling to x2. |
| 71 | +const OSR_TEMP_X2: u8 = 0b0000_1000; |
| 72 | +/// Value to write to the `OSR` register to set temperature oversampling to x2 |
| 73 | +/// and pressure oversampling to none. |
| 74 | +const OSR_VAL: u8 = OSR_TEMP_X2; |
| 75 | + |
| 76 | +/// Register addresses for the raw temperature data (Least significant bits). |
| 77 | +const REGISTER_TEMP_XLSB: u8 = 0x07; |
| 78 | +/// Register addresses for the raw temperature data (Middle significant bits). |
| 79 | +const REGISTER_TEMP_LSB: u8 = 0x08; |
| 80 | +/// Register addresses for the raw temperature data (Most significant bits). |
| 81 | +const REGISTER_TEMP_MSB: u8 = 0x09; |
| 82 | + |
| 83 | +#[embassy_executor::main] |
| 84 | +async fn main(_spawner: Spawner) { |
| 85 | + // Initialize the device peripherals |
| 86 | + let peripherals = embassy_stm32::init(Default::default()); |
| 87 | + info!("Device started"); |
| 88 | + |
| 89 | + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which |
| 90 | + // is connected to PB6 (SCL) and PB7 (SDA). |
| 91 | + let scl = peripherals.PB6; |
| 92 | + let sda = peripherals.PB7; |
| 93 | + |
| 94 | + // I2C definition |
| 95 | + let mut i2c = I2c::new( |
| 96 | + peripherals.I2C1, |
| 97 | + scl, |
| 98 | + sda, |
| 99 | + Irqs, |
| 100 | + peripherals.GPDMA1_CH0, |
| 101 | + peripherals.GPDMA1_CH1, |
| 102 | + Default::default(), |
| 103 | + ); |
| 104 | + |
| 105 | + // Before we can read any data from the sensor, we need to configure the power. |
| 106 | + i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) |
| 107 | + .await |
| 108 | + .unwrap(); |
| 109 | + |
| 110 | + // And we also need to configure the oversampling settings. |
| 111 | + i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) |
| 112 | + .await |
| 113 | + .unwrap(); |
| 114 | + |
| 115 | + // Read the raw temperature data. There are 2 ways we can do this. |
| 116 | + loop { |
| 117 | + // The first way is to read all 3 bytes in one go. Since the registers |
| 118 | + // are consecutige, the BMP automatically increments the register |
| 119 | + // address after each byte, so we can just read from the first register, |
| 120 | + // and so long as we have more bytes to read, the main (us, the MCU) |
| 121 | + // will send ACK to continue reading. After we read the last byte (as |
| 122 | + // determined by the size of the array), we will send a NACK indicating |
| 123 | + // to the BMP that we are done reading. This will ensure that the |
| 124 | + // temperature data is consistent. |
| 125 | + let mut raw_temp_data = [0u8; 3]; |
| 126 | + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) |
| 127 | + .await |
| 128 | + .unwrap(); |
| 129 | + |
| 130 | + // Alternatively, we can read each byte one by one. This is more likely |
| 131 | + // to cause inconsistent data, since the BMP may update the temperature |
| 132 | + // data in between our reads, but it is still possible to get the |
| 133 | + // correct data if we are lucky. |
| 134 | + let mut raw_xlsb = [0u8; 1]; |
| 135 | + let mut raw_lsb = [0u8; 1]; |
| 136 | + let mut raw_msb = [0u8; 1]; |
| 137 | + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_xlsb) |
| 138 | + .await |
| 139 | + .unwrap(); |
| 140 | + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_LSB], &mut raw_lsb) |
| 141 | + .await |
| 142 | + .unwrap(); |
| 143 | + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_MSB], &mut raw_msb) |
| 144 | + .await |
| 145 | + .unwrap(); |
| 146 | + |
| 147 | + info!("Raw data: {:?}", raw_temp_data); |
| 148 | + info!( |
| 149 | + "Raw byte by byte: {:?}", |
| 150 | + [raw_xlsb[0], raw_lsb[0], raw_msb[0]] |
| 151 | + ); |
| 152 | + |
| 153 | + // The raw temperature data is a 24-bit signed integer |
| 154 | + let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) |
| 155 | + | ((raw_temp_data[1] as i32) << 8) |
| 156 | + | (raw_temp_data[0] as i32); |
| 157 | + info!("Raw temperature value: {}", raw_temp); |
| 158 | + |
| 159 | + Timer::after_millis(400).await; |
| 160 | + } |
| 161 | +} |
0 commit comments