diff --git a/Core/Inc/stm32f4xx_it.h b/Core/Inc/stm32f4xx_it.h index deb0aa40..902a5f97 100644 --- a/Core/Inc/stm32f4xx_it.h +++ b/Core/Inc/stm32f4xx_it.h @@ -55,6 +55,7 @@ void SVC_Handler(void); void DebugMon_Handler(void); void PendSV_Handler(void); void SysTick_Handler(void); +void SPI2_IRQHandler(void); void USART1_IRQHandler(void); void USART2_IRQHandler(void); void DMA2_Stream0_IRQHandler(void); diff --git a/Core/Src/spi.c b/Core/Src/spi.c index 51b2da7a..2982ec6c 100644 --- a/Core/Src/spi.c +++ b/Core/Src/spi.c @@ -85,6 +85,9 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) GPIO_InitStruct.Alternate = GPIO_AF5_SPI2; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + /* SPI2 interrupt Init */ + HAL_NVIC_SetPriority(SPI2_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(SPI2_IRQn); /* USER CODE BEGIN SPI2_MspInit 1 */ /* USER CODE END SPI2_MspInit 1 */ @@ -110,6 +113,8 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle) */ HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_12|GPIO_PIN_14|GPIO_PIN_15); + /* SPI2 interrupt Deinit */ + HAL_NVIC_DisableIRQ(SPI2_IRQn); /* USER CODE BEGIN SPI2_MspDeInit 1 */ /* USER CODE END SPI2_MspDeInit 1 */ diff --git a/Core/Src/stm32f4xx_it.c b/Core/Src/stm32f4xx_it.c index 4aae7358..f093fd62 100644 --- a/Core/Src/stm32f4xx_it.c +++ b/Core/Src/stm32f4xx_it.c @@ -56,6 +56,7 @@ /* External variables --------------------------------------------------------*/ extern DMA_HandleTypeDef hdma_adc1; +extern SPI_HandleTypeDef hspi2; extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart2; /* USER CODE BEGIN EV */ @@ -200,6 +201,20 @@ void SysTick_Handler(void) /* please refer to the startup file (startup_stm32f4xx.s). */ /******************************************************************************/ +/** + * @brief This function handles SPI2 global interrupt. + */ +void SPI2_IRQHandler(void) +{ + /* USER CODE BEGIN SPI2_IRQn 0 */ + + /* USER CODE END SPI2_IRQn 0 */ + HAL_SPI_IRQHandler(&hspi2); + /* USER CODE BEGIN SPI2_IRQn 1 */ + + /* USER CODE END SPI2_IRQn 1 */ +} + /** * @brief This function handles USART1 global interrupt. */ diff --git a/Lib/Inc/CBME280.h b/Lib/Inc/CBME280.h new file mode 100644 index 00000000..76186f15 --- /dev/null +++ b/Lib/Inc/CBME280.h @@ -0,0 +1,148 @@ +/** + *@file CBME280.h + * + */ + +/* + * CBME280.h + * + * Created on: 24 Jul 2022 + * Author: salavat.magazov + */ + +#ifndef CBME280_H_ +#define CBME280_H_ + +#include "CGpioWrapper.h" +#include "gpio.h" +#include "spi.h" + +#define T_CALIBRATION_SIZE 3 +#define P_CALIBRATION_SIZE 9 +#define H_CALIBRATION_SIZE 6 +#define RAW_ADC_DATA_SIZE 8 +#define MAX_SENSORS 2 + +class CBME280 +{ + enum REGISTER_MAP + { + /* Calibration registers. */ + DIG_T1 = 0x88, + DIG_T2 = 0x8A, + DIG_T3 = 0x8C, + DIG_P1 = 0x8E, + DIG_P2 = 0x90, + DIG_P3 = 0x92, + DIG_P4 = 0x94, + DIG_P5 = 0x96, + DIG_P6 = 0x98, + DIG_P7 = 0x9A, + DIG_P8 = 0x9C, + DIG_P9 = 0x9E, + DIG_H1 = 0xA1, + DIG_H2 = 0xE1, + DIG_H3 = 0xE3, + DIG_H4 = 0xE4, + DIG_H5 = 0xE5, + /* Sensor data registers. */ + ID = 0xD0, + RESET = 0xE0, + CTRL_HUM = 0xF2, + STATUS = 0xF3, + CTRL_MEAS = 0xF4, + CONFIG = 0xF5, + PRESS_MSB = 0xF7, + PRESS_LSB = 0xF8, + PRESS_XLSB = 0xF9, + TEMP_MSB = 0xFA, + TEMP_LSB = 0xFB, + TEMP_XLSB = 0xFC, + HUM_MSB = 0xFD, + HUM_LSB = 0xFE + }; + +public: + CBME280(SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin); + virtual ~CBME280(); + + bool init(); + bool run(); + + /** + * @brief Access processed temperature data. + * + * @return Temperature in degrees Celsius. + */ + float getTemperature() const + { + return m_temperature; + }; + + /** + * @brief Access processed pressure data. + * + * @return Pressure in Pascals. + */ + float getPressure() const + { + return m_pressure; + }; + + /** + * @brief Access processed humidity data. + * + * @return Humidity in %RH. + */ + float getHumidity() const + { + return m_humidity; + }; + + bool isMeasuring() const + { + return m_state == MEASURING; + } + + static void processIrq(); + +private: + void calibrateSensor(uint8_t const *const p_calibration_data); + void convertRawData(); + bool startMeasurement(); + void calculateT(); + void calculateP(); + void calculateH(); + + SPI_HandleTypeDef *mp_spi; + CGpioWrapper m_slave_select; + + enum STATES + { + INIT_PENDING = 0, + READY, + MEASURING, + NEW_DATA + } m_state; + + static CBME280 *sp_sensors[MAX_SENSORS]; + static uint8_t s_sensor_count; + + float m_temperature_calibration[T_CALIBRATION_SIZE]; + float m_pressure_calibration[P_CALIBRATION_SIZE]; + float m_humidity_calibration[H_CALIBRATION_SIZE]; + + uint8_t m_raw_adc_data[RAW_ADC_DATA_SIZE]; + uint32_t m_raw_temperature_data; + uint32_t m_raw_pressure_data; + uint32_t m_raw_humidity_data; + + float m_t_fine; + float m_temperature; + float m_pressure; + float m_humidity; +}; + +#endif /* CBME280_H_ */ diff --git a/Lib/Src/CBME280.cpp b/Lib/Src/CBME280.cpp new file mode 100644 index 00000000..7d26ce80 --- /dev/null +++ b/Lib/Src/CBME280.cpp @@ -0,0 +1,388 @@ +/** + *@file CBME280.cpp + * + */ + +/* + * BME280.cpp + * + * Created on: 24 Jul 2022 + * Author: salavat.magazov + */ + +#include +#include + +#define BME280_ID 0x60 +#define BMP280_ID 0x58 +#define SOFT_RESET 0xB6 +#define SPI_WRITE_MASK 0x80 +#define ID_PACKET_SIZE 2 +#define CALIB1_PACKET_SIZE 24 +#define CALIB2_PACKET_SIZE 1 +#define CALIB3_PACKET_SIZE 8 +#define SPI_TIMEOUT_MS 100 +#define SENSOR_RESET_DELAY_MS 9 + +#define CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) + +#define HIGH true +#define LOW false + +/* These values were extracted from BME280 calibration source code. The original + * calibration source code was intended for use with integers to allow its use + * on resource constrained MCUs. This code will run on MCU with floating point + * unit, so converting into float calibration increases dynamic range and make + * code much more readable and maintainable. + */ +constexpr float T_CALIB_CORRECTION[] = {1, 1 / 1024, 1 / sqrt(8092)}; +constexpr float P_CALIB_CORRECTION[] = {1 / 6250, + 1 / 524288 / 32768, + 1 / 524288 / 524288 / 32768, + 65536 / 4096, + 1 / 2 / 4096, + 1 / 32768 / 4 / 4096, + 1 / 16, + 1 / 32768 / 16, + 1 / 2147483648 / 16}; +constexpr float H_CALIB_CORRECTION[] = + {-1 / 524288, 1 / 65536, 1 / 67108864, -1 / 64, 1 / 16384, 1 / 67108864}; + +constexpr float T_SCALE_FACTOR = 1 / 5120; +constexpr float P_OFFSET_1 = 64000; +constexpr float P_OFFSET_2 = 1048576; +constexpr float H_OFFSET = 76800; + +/* Static instance control variables. */ +CBME280 *CBME280::sp_sensors[] = {nullptr}; +uint8_t CBME280::s_sensor_count = 0; + +/** + * @brief Construct a sensor driver class instance. + * + * @param p_spi Pointer to HAL handler for SPI channel. + * @param p_slave_select_port Pointer to GPIO port for SS pin. + * @param slave_select_pin Mask of the SS pin. + */ +CBME280::CBME280(SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin) + : mp_spi(p_spi), + m_slave_select(p_slave_select_port, slave_select_pin), + m_state(INIT_PENDING) +{ + if (mp_spi == nullptr) + { + Error_Handler(); + } + m_slave_select.set(true); + if (s_sensor_count < MAX_SENSORS) + { + sp_sensors[s_sensor_count] = this; + s_sensor_count++; + } + else + { + Error_Handler(); + } +} + +CBME280::~CBME280() +{ + // TODO Auto-generated destructor stub +} + +/** + * @brief Initialise the sensor. This must be successfully completed before + * sensor can be accessed for data. + * + * @return True if initialisation is successful, false otherwise. + */ +bool CBME280::init() +{ + /* TODO: + * 1. Check SPI available. + * 2. Read chip ID to verify comms to sensor works. + * 3. Read calibration values and store in appropriate places. + */ + /* This communication via SPI is going to be done without interrupts.*/ + // TODO: how do we ensure SPI is available? Perhaps need a wrapper around + // SPI with a mutex. + if (HAL_SPI_GetState(mp_spi) != HAL_SPI_STATE_READY) + { + return false; + } + uint8_t register_address; + /* Soft reset. */ + register_address = RESET; + m_slave_select.set(false); + HAL_SPI_Transmit(mp_spi, ®ister_address, 1, SPI_TIMEOUT_MS); + m_slave_select.set(true); + HAL_Delay(SENSOR_RESET_DELAY_MS); + /* Read sensor ID to see if it is responding to comms. */ + register_address = ID; + uint8_t id_register[ID_PACKET_SIZE]; + m_slave_select.set(LOW); + HAL_SPI_TransmitReceive(mp_spi, + ®ister_address, + id_register, + ID_PACKET_SIZE, + SPI_TIMEOUT_MS); + m_slave_select.set(HIGH); + if ((id_register[1] != BME280_ID) && (id_register[1] != BMP280_ID)) + { + return false; + } + /* Read sensor calibration data. */ + register_address = DIG_T1; + uint8_t calibration_register[CALIB1_PACKET_SIZE + + CALIB2_PACKET_SIZE * CALIB3_PACKET_SIZE]; + m_slave_select.set(LOW); + HAL_SPI_TransmitReceive(mp_spi, + ®ister_address, + calibration_register, + CALIB1_PACKET_SIZE, + SPI_TIMEOUT_MS); + m_slave_select.set(HIGH); + register_address = DIG_H1; + m_slave_select.set(LOW); + HAL_SPI_TransmitReceive(mp_spi, + ®ister_address, + &(calibration_register[CALIB1_PACKET_SIZE - 1]), + CALIB2_PACKET_SIZE, + SPI_TIMEOUT_MS); + m_slave_select.set(HIGH); + register_address = DIG_H2; + m_slave_select.set(LOW); + HAL_SPI_TransmitReceive(mp_spi, + ®ister_address, + &(calibration_register[CALIB1_PACKET_SIZE]), + CALIB2_PACKET_SIZE, + SPI_TIMEOUT_MS); + m_slave_select.set(HIGH); + calibrateSensor(calibration_register); + // TODO: sensor requires setting up. + // set operational mode. + // set oversampling modes for all three measurements. + return true; +} + +bool CBME280::run() +{ + bool b_new_data = false; + switch (m_state) + { + case INIT_PENDING: + if (init()) + { + m_state = READY; + } + break; + case READY: + // TODO: perhaps need to have some sort of timeout here to only run + // this at regular intervals. + if (startMeasurement()) + { + m_state = MEASURING; + } + break; + case MEASURING: + // N.B. This state is a wait-state to allow class to know when it is + // busy waiting for SPI interrupt to fire. + break; + case NEW_DATA: + /* The sequence of these method calls must be preserved since + * temperature is used in pressure and humidity calculations. + * convertRawData();*/ + calculateT(); + calculateP(); + calculateH(); + b_new_data = true; + break; + default: + Error_Handler(); + } + return b_new_data; +} + +/** + * @brief Convert raw data from the sensor registers into usable calibration + * values. + * + * @param p_calibration_data Pointer to raw data stream of 33 8-bit values. + */ +void CBME280::calibrateSensor(uint8_t const *const p_calibration_data) +{ + uint8_t const *p_runner = p_calibration_data; + uint32_t raw_calibration; + for (int i = 0; i < T_CALIBRATION_SIZE; i++) + { + raw_calibration = m_temperature_calibration[i] = *p_runner; + raw_calibration |= *(++p_runner) << 8; + m_temperature_calibration[i] = raw_calibration * T_CALIB_CORRECTION[i]; + ++p_runner; + } + for (int i = 0; i < P_CALIBRATION_SIZE; i++) + { + raw_calibration = *p_runner; + raw_calibration |= *(++p_runner) << 8; + m_pressure_calibration[i] = raw_calibration * P_CALIB_CORRECTION[i]; + ++p_runner; + } + /* Humidity calibration data is spread around and is stored in a + * weird convoluted way. See the manual. This recovery was + * copied with modifications from the Bosch github repository. + * https://github.com/BoschSensortec/BME280_driver */ + /* I am certain the engineer at Bosch who came up with this had + * management breathing down their neck. One day they stopped + * caring and made this monstrocity.*/ + raw_calibration = p_calibration_data[25]; + m_humidity_calibration[0] = raw_calibration * H_CALIB_CORRECTION[0]; + raw_calibration = (p_calibration_data[27] << 8) + p_calibration_data[26]; + m_humidity_calibration[1] = raw_calibration * H_CALIB_CORRECTION[1]; + raw_calibration = p_calibration_data[28]; + m_humidity_calibration[2] = raw_calibration * H_CALIB_CORRECTION[2]; + raw_calibration = + (p_calibration_data[29] << 4) + (p_calibration_data[30] & 0x0F); + m_humidity_calibration[3] = raw_calibration * H_CALIB_CORRECTION[3]; + raw_calibration = + (p_calibration_data[31] << 4) + (p_calibration_data[30] >> 4); + m_humidity_calibration[4] = raw_calibration * H_CALIB_CORRECTION[4]; + raw_calibration = p_calibration_data[32]; + m_humidity_calibration[5] = raw_calibration * H_CALIB_CORRECTION[5]; +} + +/** + * @brief Convert raw data from sensor registers into corresponding + * unprocessed sensor values. + * + * @param p_raw_adc_data Pointer to raw data stream of 8 8-bit + * values. + */ +void CBME280::convertRawData() +{ + m_raw_pressure_data = m_raw_adc_data[0]; + m_raw_pressure_data = m_raw_pressure_data << 8; + m_raw_pressure_data += m_raw_adc_data[1]; + m_raw_pressure_data = m_raw_pressure_data << 4; + m_raw_pressure_data += m_raw_adc_data[2] >> 4; + + m_raw_temperature_data = m_raw_adc_data[0]; + m_raw_temperature_data = m_raw_temperature_data << 8; + m_raw_temperature_data += m_raw_adc_data[1]; + m_raw_temperature_data = m_raw_temperature_data << 4; + m_raw_temperature_data += m_raw_adc_data[2] >> 4; + + m_raw_humidity_data = m_raw_adc_data[6]; + m_raw_humidity_data = m_raw_humidity_data << 8; + m_raw_humidity_data += m_raw_adc_data[7]; +} + +/** + * @brief Initiate measurement process. + * + * @return True if measurement started successfully. + */ +bool CBME280::startMeasurement() +{ + if (HAL_SPI_GetState(mp_spi) != HAL_SPI_STATE_READY) + { + return false; + } + /* Configure SPI comms as required by the sensor. */ + // TODO: this type of channel sharing with different + // configurations should be handled by a wrapper class. It can + // also queue up traffic and call callback functions. + mp_spi->Init.CLKPolarity = SPI_POLARITY_HIGH; + mp_spi->Init.CLKPhase = SPI_PHASE_2EDGE; + mp_spi->Init.NSS = SPI_NSS_SOFT; + mp_spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; + if (HAL_SPI_Init(&hspi2) != HAL_OK) + { + return false; + } + m_raw_adc_data[0] = PRESS_MSB; + for (int i = 1; i < RAW_ADC_DATA_SIZE; i++) + { + m_raw_adc_data[i] = 0; + } + HAL_SPI_TransmitReceive_IT(mp_spi, + m_raw_adc_data, + m_raw_adc_data, + RAW_ADC_DATA_SIZE); + return true; +} + +/** + * @brief Apply temperature calibration to raw data. + * + */ +void CBME280::calculateT() +{ + float t; + t = (m_raw_temperature_data / 16 - m_temperature_calibration[0]); + m_t_fine = + t * m_temperature_calibration[1] + t * t * m_temperature_calibration[2]; + m_temperature = m_t_fine / T_SCALE_FACTOR; +} + +/** + * @brief Apply pressure calibration to raw data. + * + */ +void CBME280::calculateP() +{ + float t_p = m_t_fine / 2 - P_OFFSET_1; + float var_1 = m_pressure_calibration[3]; + var_1 += t_p * m_pressure_calibration[4]; + var_1 += t_p * t_p * m_pressure_calibration[5]; + float var_2 = 1; + var_2 += t_p * m_pressure_calibration[1]; + var_2 += t_p * t_p * m_pressure_calibration[2]; + var_2 *= m_pressure_calibration[0]; + float var_3 = P_OFFSET_2 - m_raw_pressure_data - var_1; + var_3 /= var_2; + m_pressure = m_pressure_calibration[6]; + m_pressure += var_3 * m_pressure_calibration[7]; + m_pressure += var_3 * var_3 * m_pressure_calibration[8]; +} + +/** + * @brief Apply humidity calibration to raw data. + * + */ +void CBME280::calculateH() +{ + float t_h = m_t_fine - H_OFFSET; + float var_1 = 1 + t_h * m_humidity_calibration[2]; + var_1 *= t_h * m_humidity_calibration[5]; + var_1 += 1; + var_1 *= m_humidity_calibration[1]; + var_1 *= m_raw_humidity_data + m_humidity_calibration[3] + + t_h * m_humidity_calibration[4]; + m_humidity = var_1; + m_humidity += var_1 * var_1 * m_humidity_calibration[0]; +} + +/** + * @brief Process IRQ call for all registered and active sensors. + * + */ +void CBME280::processIrq() +{ + uint8_t sensor = 0; + while ((sp_sensors[sensor] != nullptr) && sensor < s_sensor_count) + { + if (sp_sensors[sensor]->isMeasuring()) + { + sp_sensors[sensor]->m_state = NEW_DATA; + break; + } + sensor++; + } +} + +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) +{ + CBME280::processIrq(); +} diff --git a/Multichannel temperature sensor.ioc b/Multichannel temperature sensor.ioc index 0ac350fe..de1522cb 100644 --- a/Multichannel temperature sensor.ioc +++ b/Multichannel temperature sensor.ioc @@ -147,6 +147,7 @@ NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SPI2_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:true NVIC.USART1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true @@ -354,7 +355,7 @@ ProjectManager.FreePins=false ProjectManager.HalAssertFull=false ProjectManager.HeapSize=0x200 ProjectManager.KeepUserCode=true -ProjectManager.LastFirmware=true +ProjectManager.LastFirmware=false ProjectManager.LibraryCopy=1 ProjectManager.MainLocation=Core/Src ProjectManager.MultiThreaded=true