diff --git a/Controllers/CHumidityController.cpp b/Controllers/CHumidityController.cpp new file mode 100644 index 00000000..8cb23f90 --- /dev/null +++ b/Controllers/CHumidityController.cpp @@ -0,0 +1,179 @@ +/* + * CHumidityController.cpp + * + * Created on: Aug. 11, 2022 + * Author: samk + */ + +#include "CHumidityController.h" +#include + +CHumidityController::CHumidityController(IHardwareMap *p_hardware, + etl::string name, + uint32_t run_period_ms, + SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin) + : CController(name, run_period_ms), + mp_hw(p_hardware) +{ + // TODO Auto-generated constructor stub + reset(); + + // TODO Fix this constructor + // CBME280 humidity_sensor(p_spi, p_slave_select_port, slave_select_pin); + // mp_humidity_sensor = &humidity_sensor; +} + +/** + * @brief Run all repeated humidity control activities. + * + */ +void CHumidityController::run() +{ + float power = 0; + if (m_power_override == DISABLE_OVERRIDE) + { + if (m_target_humidity != DISABLE_TARGET) + { + float actual_humidity = mp_humidity_sensor->getHumidity(); + power = m_control_loop.run(m_target_humidity, actual_humidity); + } + } + else + { + power = m_power_override; + } + mp_hw->setHumidifierPower(power); +} + +bool CHumidityController::newCommand(ICommand *p_command, + IComChannel *p_comchannel) +{ + bool b_command_recognised = false; + ICommand::command_error_code_t result = ICommand::COMMAND_OK; + if (p_command->getName()->compare("?humidity") == 0) + { + sendStatus(p_comchannel); + b_command_recognised = true; + } + + if (p_command->getName()->compare("humidity") == 0) + { + result = setHumidity(p_command); + b_command_recognised = true; + } + + if (p_command->getName()->compare("heater") == 0) + { + result = overrideHumidifier(p_command); + b_command_recognised = true; + } + if (b_command_recognised) + { + switch (result) + { + case ICommand::COMMAND_OK: + // p_comchannel->send("OK.\n"); + break; + case ICommand::ERROR_ARG_COUNT: + p_comchannel->send("Wrong number of arguments.\n"); + break; + case ICommand::ERROR_OUT_OF_BOUNDS: + p_comchannel->send("Argument out of bounds.\n"); + break; + case ICommand::ERROR_TYPE_MISMATCH: + p_comchannel->send("Argument type mismatch.\n"); + break; + default: + p_comchannel->send("Non-specific error with the command."); + } + } + return b_command_recognised; +} + +void CHumidityController::reset() +{ + m_target_humidity = DISABLE_TARGET; + m_power_override = DISABLE_OVERRIDE; + m_control_loop.reset(); +} + +void CHumidityController::sendStatus(IComChannel *p_comchannel) +{ + etl::string message; + char value[10]; + // send target humidity + message.assign("Target: "); + sprintf(value, "%2.1f", m_target_humidity); + message.append(value); + p_comchannel->send(message); + // Send actual humidity + message.assign("Humidity: "); + sprintf(value, "%2.1f, ", mp_humidity_sensor->getHumidity()); + message.append(value); + p_comchannel->send(message); + // Send heater power + message.assign("Power: "); + float power; + if (m_power_override == DISABLE_OVERRIDE) + { + power = mp_hw->getHardPwmOutput(CHANNEL_NUMBER - 1); + } + else + { + power = m_power_override; + } + sprintf(value, "%4.1f, ", power); + message.append(value); + p_comchannel->send(message); +} + +/** + * @brief Set humidity value + */ +ICommand::command_error_code_t CHumidityController::setHumidity( + ICommand *p_command) +{ + // Sanitise command arguments + if (p_command->getArgumentCount() != 1) + { + return ICommand::ERROR_ARG_COUNT; + } + m_target_humidity = (*p_command)[0]; + + if (m_target_humidity > MAX_HUMIDITY) + { + m_target_humidity = MAX_HUMIDITY; + } + if ((m_target_humidity < MIN_HUMIDITY) && + (m_target_humidity != DISABLE_TARGET)) + { + m_target_humidity = MIN_HUMIDITY; + } + + return ICommand::COMMAND_OK; +} + +ICommand::command_error_code_t CHumidityController::overrideHumidifier( + ICommand *p_command) +{ + // Sanitise command arguments + if (p_command->getArgumentCount() != 1) + { + return ICommand::ERROR_ARG_COUNT; + } + float power = (*p_command)[0]; + + // Execute command + if (power > MAX_POWER) + { + power = MAX_POWER; + } + if ((power < MIN_POWER) && (power != DISABLE_OVERRIDE)) + { + power = MIN_POWER; + } + m_power_override = power; + return ICommand::COMMAND_OK; +} diff --git a/Controllers/CHumidityController.h b/Controllers/CHumidityController.h new file mode 100644 index 00000000..798cebc4 --- /dev/null +++ b/Controllers/CHumidityController.h @@ -0,0 +1,51 @@ +/* + * CHumidityController.h + * + * Created on: Aug. 11, 2022 + * Author: samk + */ + +#ifndef CHUMIDITYCONTROLLER_H_ +#define CHUMIDITYCONTROLLER_H_ + +#include "CBME280.h" +#include "CController.h" +#include "CHumidifier.h" +#include "CPIDLoop.h" +#include "IHardwareMap.h" + +class CHumidityController : public CController +{ +public: + CHumidityController(IHardwareMap *p_hardwaremap, + etl::string name, + uint32_t run_period_ms, + SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin); + virtual void run(); + virtual bool newCommand(ICommand *p_command, IComChannel *p_comchannel); + virtual void reset(); + +private: + static constexpr uint8_t CHANNEL_NUMBER = 1; + static constexpr uint8_t MAX_HUMIDITY = 95; + static constexpr uint8_t MIN_HUMIDITY = 85; + static constexpr uint8_t MAX_POWER = 100; + static constexpr uint8_t MIN_POWER = 0; + static constexpr uint8_t DISABLE_OVERRIDE = -1; + static constexpr uint8_t DISABLE_TARGET = 0; + void sendStatus(IComChannel *p_comchannel); + ICommand::command_error_code_t setHumidity(ICommand *p_command); + ICommand::command_error_code_t overrideHumidifier(ICommand *p_command); + + IHardwareMap *mp_hw; + + CBME280 *mp_humidity_sensor; + + float m_target_humidity; + float m_power_override; + CPIDLoop m_control_loop; +}; + +#endif /* CHUMIDITYCONTROLLER_H_ */ 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/Hardware/CMockHardwareMap.cpp b/Hardware/CMockHardwareMap.cpp index 9d17f32f..976753f1 100644 --- a/Hardware/CMockHardwareMap.cpp +++ b/Hardware/CMockHardwareMap.cpp @@ -115,6 +115,11 @@ void CMockHardwareMap::enableControlPower(bool b_enable) mb_power_enable = b_enable; } +void CMockHardwareMap::setHumidifierPower(float power) +{ + // TODO: This should modify internal model variables for humidity. +} + void CMockHardwareMap::run() { float total_radiator_flow = 0; diff --git a/Hardware/CMockHardwareMap.h b/Hardware/CMockHardwareMap.h index d280619b..e2573f0b 100644 --- a/Hardware/CMockHardwareMap.h +++ b/Hardware/CMockHardwareMap.h @@ -34,6 +34,8 @@ class CMockHardwareMap : public IHardwareMap, public CController #endif virtual void setBreathingLight(float duty_cycle); virtual void enableControlPower(bool b_enable); + virtual void setHumidifierPower(float power); + virtual bool getHumidifierPower(); /* CController methods. */ // etl::string getName() const; // virtual bool tick(uint32_t current_time); diff --git a/Hardware/CRealHardwareMap.cpp b/Hardware/CRealHardwareMap.cpp index 4a16f8bf..4ee9b1b1 100644 --- a/Hardware/CRealHardwareMap.cpp +++ b/Hardware/CRealHardwareMap.cpp @@ -33,6 +33,12 @@ #define AD22100_SCALE ((V_REF / 5.0) * (1 / 22.5E-3)) #define AD22100_OFFSET ((V_REF / 5.0) * (-1.375 / 22.5E-3)) +/* This section maps generic names of the pins to something that makes sense. + * We'll do it this way for now until hardware configuration settles down at + * which point the pin names will be renamed. */ +#define EVAPORATOR_GPIO_Port ENABLE_8_GPIO_Port +#define EVAPORATOR_Pin ENABLE_8_Pin + const CRealHardwareMap::timer_init_map_t CRealHardwareMap::s_timer_init_map[] = {{&htim1, TIM_CHANNEL_1}, {&htim4, TIM_CHANNEL_1}, @@ -76,7 +82,8 @@ void CRealHardwareMap::init() s_gpio_init_map[i].pin); } m_breathing_light.init(&htim2, TIM_CHANNEL_1); - m_power_enable.init(BREATHING_GPIO_Port, BREATHING_Pin); + m_power_enable.init(PWR_EN_GPIO_Port, PWR_EN_Pin); + m_humidifier_enable.init(EVAPORATOR_GPIO_Port, EVAPORATOR_Pin); } /** @@ -167,3 +174,33 @@ void CRealHardwareMap::enableControlPower(bool b_enable) { m_power_enable.set(b_enable); } + +/** + * @brief This assumes that the humidifier is implemented as an atomiser. + * @note The longer the atomiser is enabled the more vapour it'll generate. This + * way PID control is still possible, but care should be taken to not set + * integral component too high since atomiser itself will work as an integrator. + * + * @param power Is essentially a boolean value. Anything above zero enables + * atomiser. + */ +void CRealHardwareMap::setHumidifierPower(float power) +{ + if (power > 0) + { + m_humidifier_enable.set(true); + } + else + { + m_humidifier_enable.set(false); + } +} +/** + * @brief Get the power state of the humidifier. + * + * @return True: Humidifier active. False: Humidifier inactive. + */ +bool CRealHardwareMap::getHumidifierPower() +{ + return m_humidifier_enable.get(); +} diff --git a/Hardware/CRealHardwareMap.h b/Hardware/CRealHardwareMap.h index 75856b15..e5e57c21 100644 --- a/Hardware/CRealHardwareMap.h +++ b/Hardware/CRealHardwareMap.h @@ -40,6 +40,8 @@ class CRealHardwareMap : public IHardwareMap #endif virtual void setBreathingLight(float duty_cycle); virtual void enableControlPower(bool b_enable); + virtual void setHumidifierPower(float power); + virtual bool getHumidifierPower(); private: typedef struct TIMER_INIT_MAP_T @@ -60,6 +62,7 @@ class CRealHardwareMap : public IHardwareMap CGpioWrapper m_polarity_switch[HARD_PWM_OUTPUTS]; CHardPwmOutput m_breathing_light; CGpioWrapper m_power_enable; + CGpioWrapper m_humidifier_enable; #ifdef SOFT_PWM_OUTPUTS CSoftPwmOutput m_soft_pwm_output[SOFT_PWM_OUTPUTS]; #endif diff --git a/Hardware/IHardwareMap.h b/Hardware/IHardwareMap.h index e2675438..5ad2281f 100644 --- a/Hardware/IHardwareMap.h +++ b/Hardware/IHardwareMap.h @@ -19,7 +19,7 @@ /** * @note Declare this value to the number of soft PWM outputs that are needed. */ -//#define SOFT_PWM_OUTPUTS 0 +// #define SOFT_PWM_OUTPUTS 0 class IHardwareMap { @@ -117,6 +117,19 @@ class IHardwareMap * @param b_enable Set to true to enable, false to disable. */ virtual void enableControlPower(bool b_enable) = 0; + /** + * @brief Set power to the humidifier. This controls either boiler power or + * ultrasonic atomiser depending on exact hardware implementation. + * + * @param power Value in percent between 0 and 100. + */ + virtual void setHumidifierPower(float power) = 0; + /** + * @brief Get power status of the humidifier + * + * @return Value of power output + */ + virtual bool getHumidifierPower() = 0; }; #endif /* IHARDWAREMAP_H_ */ diff --git a/Lib/Inc/CBME280.h b/Lib/Inc/CBME280.h new file mode 100644 index 00000000..99902802 --- /dev/null +++ b/Lib/Inc/CBME280.h @@ -0,0 +1,149 @@ +/** + *@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(); + virtual ~CBME280(); + + bool init(SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin); + 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(); + bool init(); + + 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/Inc/CHumidifier.h b/Lib/Inc/CHumidifier.h new file mode 100644 index 00000000..12530d2c --- /dev/null +++ b/Lib/Inc/CHumidifier.h @@ -0,0 +1,27 @@ +/** + * @file CHumidifier.h + * + */ + +/* + * CHumidifier.h + * + * Created on: 26 Sep 2022 + * Author: salavat.magazov + */ + +#ifndef CHUMIDIFIER_H_ +#define CHUMIDIFIER_H_ + +class CHumidifier +{ +public: + CHumidifier(); + virtual ~CHumidifier(); + + void run(); + + void addVapour(float quantity); +}; + +#endif /* CHUMIDIFIER_H_ */ diff --git a/Lib/Src/CBME280.cpp b/Lib/Src/CBME280.cpp new file mode 100644 index 00000000..06e0796c --- /dev/null +++ b/Lib/Src/CBME280.cpp @@ -0,0 +1,393 @@ +/** + *@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. + * + */ +CBME280::CBME280() : m_state(INIT_PENDING) {} + +CBME280::~CBME280() +{ + // TODO Auto-generated destructor stub +} +/** + * @brief Begin sensor initialization + * + * @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. + * @return + */ +bool CBME280::init(SPI_HandleTypeDef *p_spi, + GPIO_TypeDef *p_slave_select_port, + uint16_t slave_select_pin) +{ + mp_spi = p_spi; + m_slave_select.init(p_slave_select_port, slave_select_pin); + if (mp_spi == nullptr) + { + return false; + } + m_slave_select.set(true); + if (s_sensor_count < MAX_SENSORS) + { + sp_sensors[s_sensor_count] = this; + s_sensor_count++; + } + else + { + return false; + } + return true; +} +/** + * @brief Finish sensor initialization. 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 + + 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/Lib/Src/CHumidifier.cpp b/Lib/Src/CHumidifier.cpp new file mode 100644 index 00000000..d31a1ba1 --- /dev/null +++ b/Lib/Src/CHumidifier.cpp @@ -0,0 +1,43 @@ +/** + * @file CHumidifier.cpp + * + */ + +/* + * CHumidifier.cpp + * + * Created on: 26 Sep 2022 + * Author: salavat.magazov + */ + +#include "CHumidifier.h" + +CHumidifier::CHumidifier() +{ + // TODO Auto-generated constructor stub +} + +CHumidifier::~CHumidifier() +{ + // TODO Auto-generated destructor stub +} + +/** + * @brief Maintenance routine that monitors the humidifier operation and adjusts + * behaviour as necessary. + * + */ +void CHumidifier::run() {} + +/** + * @brief Add some quantity of vapour to the system. + * + * @param quantity Amount of vapour that is required. + * + * @note At this point it is unclear how the humidifier will work, so quantity + * is somewhat ill-defined. + */ +void CHumidifier::addVapour(float quantity) +{ + return; +} 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