diff --git a/Enclosure/IAQ_Sensor_Enclosure.FCStd b/Enclosure/IAQ_Sensor_Enclosure.FCStd index 0f47d7a..b871cb2 100644 Binary files a/Enclosure/IAQ_Sensor_Enclosure.FCStd and b/Enclosure/IAQ_Sensor_Enclosure.FCStd differ diff --git a/Enclosure/IAQ_Sensor_Enclosure.FCStd1 b/Enclosure/IAQ_Sensor_Enclosure.FCStd1 index 0b5e708..41bd9ba 100644 Binary files a/Enclosure/IAQ_Sensor_Enclosure.FCStd1 and b/Enclosure/IAQ_Sensor_Enclosure.FCStd1 differ diff --git a/Enclosure/STLs/Top_rev3.stl b/Enclosure/STLs/Top_rev3.stl new file mode 100644 index 0000000..451b7cb Binary files /dev/null and b/Enclosure/STLs/Top_rev3.stl differ diff --git a/fw/Core/Inc/crc8.h b/fw/Core/Inc/crc8.h index e799f7e..6820352 100644 --- a/fw/Core/Inc/crc8.h +++ b/fw/Core/Inc/crc8.h @@ -14,8 +14,8 @@ * Definitions & macros */ -#define CRC8_POLYNOMIAL 0x31 -#define CRC8_INIT 0xFF +#define CRC8_POLYNOMIAL ((uint8_t)0x31) +#define CRC8_INIT ((uint8_t)0xFF) uint8_t crc8_calculate(const uint8_t *data, uint16_t count); diff --git a/fw/Core/Inc/sps30.h b/fw/Core/Inc/sps30.h index 9ca3c45..9b1b943 100644 --- a/fw/Core/Inc/sps30.h +++ b/fw/Core/Inc/sps30.h @@ -19,6 +19,7 @@ */ #define SPS30_I2C_ADDRESS 0x69 +#define SPS30_MEASURED_VALUES_COUNT 10 /* * Return values @@ -48,19 +49,34 @@ typedef enum { SPS30_READ_DEVICE_STATUS_REGISTER = 0xD206, SPS30_CLEAR_DEVICE_STATUS_REGISTER = 0xD210, SPS30_RESET = 0xD304 - } sps30_cmd_t; typedef enum { SPS30_FLOAT_FORMAT = 0x03, SPS30_UINT16_FORMAT = 0x05 -} sps30_data_format; +} sps30_data_format_t; + +typedef enum { + PM0_5 = 0, /* this category is used only for number concentration */ + PM1_0, + PM2_5, + PM4_0, + PM10_0, + SPS30_PM_CATEGORIES_COUNT +} sps30_pm_categories_t; + +typedef struct { + /* PM0.5 is skipped for mass concentration */ + uint16_t mass_concentration[SPS30_PM_CATEGORIES_COUNT]; /* ug / m^3 */ + uint16_t number_concentration[SPS30_PM_CATEGORIES_COUNT]; /* 1 / cm^3 */ + uint16_t typical_particle_size; /* nm */ +} sps30_data_t; int8_t sps30_send_cmd(sps30_cmd_t cmd); int8_t sps30_start_measurement( void ); int8_t sps30_stop_measurement( void ); -int8_t sps30_read_measured_values(uint16_t *measured_values, uint8_t measured_values_len); +int8_t sps30_read_measured_values(sps30_data_t *measured_data); int8_t sps30_sleep( void ); int8_t sps30_wake_up( void ); @@ -73,6 +89,4 @@ int8_t sps30_read_status_register ( void ); int8_t sps30_read_firmware_version ( uint8_t * fw_ver_hi, uint8_t * fw_ver_lo ); -uint8_t calculate_crc(uint8_t data[2]); - #endif /* INC_SPS30_H_ */ diff --git a/fw/Core/Src/main.c b/fw/Core/Src/main.c index a058d38..d7a138a 100644 --- a/fw/Core/Src/main.c +++ b/fw/Core/Src/main.c @@ -71,13 +71,23 @@ const uint16_t tim21_period = 1200-1; // 6s /* Input registers memory map implementation */ enum { - REGISTER_NUM_CO2 = 30010, - REGISTER_NUM_T_SHT4x = 30011, - REGISTER_NUM_RH_SHT4x = 30012, - REGISTER_NUM_T_SCD4x = 30013, - REGISTER_NUM_RH_SCD4x = 30014, - REGISTER_NUM_T_SHT4x_SIGNED = 30015, - REGISTER_NUM_T_SCD4x_SIGNED = 30016 + REGISTER_NUM_CO2 = 30010, /* ppm */ + REGISTER_NUM_T_SHT4x = 30011, /* deg C */ + REGISTER_NUM_RH_SHT4x = 30012, /* % */ + REGISTER_NUM_T_SCD4x = 30013, /* deg C */ + REGISTER_NUM_RH_SCD4x = 30014, /* % */ + REGISTER_NUM_T_SHT4x_SIGNED = 30015, /* deg C */ + REGISTER_NUM_T_SCD4x_SIGNED = 30016, /* deg C */ + REGISTER_NUM_PMC_MASS_1_0 = 30017, /* ug / m^3 */ + REGISTER_NUM_PMC_MASS_2_5 = 30018, /* ug / m^3 */ + REGISTER_NUM_PMC_MASS_4_0 = 30019, /* ug / m^3 */ + REGISTER_NUM_PMC_MASS_10_0 = 30020, /* ug / m^3 */ + REGISTER_NUM_PMC_NUMBER_0_5 = 30021, /* 1 / m^3 */ + REGISTER_NUM_PMC_NUMBER_1_0 = 30022, /* 1 / m^3 */ + REGISTER_NUM_PMC_NUMBER_2_5 = 30023, /* 1 / m^3 */ + REGISTER_NUM_PMC_NUMBER_4_0 = 30024, /* 1 / m^3 */ + REGISTER_NUM_PMC_NUMBER_10_0 = 30025, /* 1 / m^3 */ + REGISTER_NUM_TYPICAL_PARTICLE_SIZE = 30026 /* nm */ } data_registers_numbers; enum @@ -105,7 +115,7 @@ enum /* Variables to store the measured data */ int16_t T_SCD4x, T_SHT4x; uint16_t CO2, RH_SCD4x, RH_SHT4x; -uint16_t sps30_measured_data[10]; +sps30_data_t PM_SPS30; /* Struct to store the sensor config */ config_t sensor_config; @@ -242,10 +252,6 @@ int main(void) device_id.object_name.ModelName = "Hugo"; modbus_slave_init_device_id(&device_id); - - LL_mDelay(2000); - - scd4x_start_periodic_measurement(); uint8_t scd4x_is_connected = 0; uint8_t sps30_is_connected = 0; /* USER CODE END 2 */ @@ -259,6 +265,7 @@ int main(void) /* Attempt to start SPS30 measurement and check if it's connected */ sps30_reset(); + LL_mDelay(500); // should be less than 100 ms (we have 500 just to be sure) if (sps30_start_measurement() == SPS30_OK) { sps30_is_connected = 1; @@ -345,7 +352,7 @@ int main(void) /* Read SPS30 data (if connected) */ if (sps30_is_connected == 1) { - sps30_read_measured_values(sps30_measured_data, 10); + sps30_read_measured_values(&PM_SPS30); } /* TODO: Process data and light a desired color of LED */ /* TODO: Add hystheresis */ @@ -855,6 +862,36 @@ int8_t modbus_slave_callback(modbus_transaction_t *transaction) case REGISTER_NUM_T_SCD4x_SIGNED: transaction->input_registers_signed[i] = (int16_t)T_SCD4x; break; + case REGISTER_NUM_PMC_MASS_1_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.mass_concentration[PM1_0]; + break; + case REGISTER_NUM_PMC_MASS_2_5: + transaction->input_registers[i] = (uint16_t)PM_SPS30.mass_concentration[PM2_5]; + break; + case REGISTER_NUM_PMC_MASS_4_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.mass_concentration[PM4_0]; + break; + case REGISTER_NUM_PMC_MASS_10_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.mass_concentration[PM10_0]; + break; + case REGISTER_NUM_PMC_NUMBER_0_5: + transaction->input_registers[i] = (uint16_t)PM_SPS30.number_concentration[PM0_5]; + break; + case REGISTER_NUM_PMC_NUMBER_1_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.number_concentration[PM1_0]; + break; + case REGISTER_NUM_PMC_NUMBER_2_5: + transaction->input_registers[i] = (uint16_t)PM_SPS30.number_concentration[PM2_5]; + break; + case REGISTER_NUM_PMC_NUMBER_4_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.number_concentration[PM4_0]; + break; + case REGISTER_NUM_PMC_NUMBER_10_0: + transaction->input_registers[i] = (uint16_t)PM_SPS30.number_concentration[PM10_0]; + break; + case REGISTER_NUM_TYPICAL_PARTICLE_SIZE: + transaction->input_registers[i] = (uint16_t)PM_SPS30.typical_particle_size; + break; default: return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED; } diff --git a/fw/Core/Src/sps30.c b/fw/Core/Src/sps30.c index c05a273..db4c115 100644 --- a/fw/Core/Src/sps30.c +++ b/fw/Core/Src/sps30.c @@ -26,20 +26,17 @@ int8_t sps30_send_cmd(sps30_cmd_t cmd) int8_t sps30_start_measurement( void ) { - uint8_t i2c_tx_buffer[5]; - uint8_t data_for_crc = {SPS30_UINT16_FORMAT, 0x00}; - + uint8_t buffer[5]; uint8_t result; - i2c_tx_buffer[0] = SPS30_START_MEASUREMENT >> 8; - i2c_tx_buffer[1] = SPS30_START_MEASUREMENT & 0x00ff; - i2c_tx_buffer[2] = SPS30_UINT16_FORMAT; - i2c_tx_buffer[3] = 0x00; - i2c_tx_buffer[4] = calculate_crc(data_for_crc); + buffer[0] = SPS30_START_MEASUREMENT >> 8; + buffer[1] = SPS30_START_MEASUREMENT & 0x00ff; + buffer[2] = SPS30_UINT16_FORMAT; + buffer[3] = 0x00; + buffer[4] = crc8_calculate(buffer + 2, 2); - result = i2c_transmit(SPS30_I2C_ADDRESS<<1, i2c_tx_buffer, 5); + result = i2c_transmit(SPS30_I2C_ADDRESS<<1, buffer, 5); - // TODO: Proc to vraci NACK? Vyresit. if (result != I2C_OK) { return SPS30_ERROR; } @@ -51,47 +48,50 @@ int8_t sps30_stop_measurement( void ) return sps30_send_cmd(SPS30_STOP_MEASUREMENT); } -int8_t sps30_read_measured_values(uint16_t *measured_values, uint8_t measured_values_len) +int8_t sps30_read_measured_values(sps30_data_t *measured_data) { - - if (measured_values_len != 10) - { - return -5; - } - - uint8_t i2c_tx_buffer[2]; - uint8_t i2c_rx_buffer[30]; + uint8_t buffer[32]; uint8_t result; // start measurement - i2c_tx_buffer[0] = SPS30_READ_MEASURED_VALUES >> 8; - i2c_tx_buffer[1] = SPS30_READ_MEASURED_VALUES & 0x00ff; - result = i2c_transmit(SPS30_I2C_ADDRESS<<1, i2c_tx_buffer, 2); - - // TODO: Proc to vraci NACK? Vyresit. - /*if (result != I2C_OK) { + buffer[0] = SPS30_READ_MEASURED_VALUES >> 8; + buffer[1] = SPS30_READ_MEASURED_VALUES & 0xFF; + result = i2c_transmit(SPS30_I2C_ADDRESS<<1, buffer, 2); + if (result != I2C_OK) { return SPS30_ERROR; } - return SPS30_OK;*/ - - LL_mDelay(1); // 10 ms should be enough + LL_mDelay(10); // 10 ms should be enough // read out - result = i2c_receive(SPS30_I2C_ADDRESS<<1, i2c_rx_buffer, 30); + result = i2c_receive(SPS30_I2C_ADDRESS<<1, buffer, 3 * SPS30_MEASURED_VALUES_COUNT); if (result != I2C_OK) { return SPS30_ERROR; } - - uint8_t checksums[10]; - - uint8_t j = 0; - for (uint8_t i = 0; i < 10; i++) + /* check data integrity */ + for (uint8_t i = 0; i < SPS30_MEASURED_VALUES_COUNT; i++) { - - measured_values[i] = (i2c_rx_buffer[j++] << 8) + i2c_rx_buffer[j++]; - checksums[i] = i2c_rx_buffer[j++]; + uint8_t checksum_calculated = crc8_calculate(buffer + 3*i, 2); + uint8_t checksum_received = buffer[3*i + 2]; + if (checksum_calculated != checksum_received) { + return SPS30_CRC8_ERROR; + } } + /* copy to output struct */ + /* mass concencration [ug / m^3] */ + int pos = 0; + for (int i = 0; i < 4; i++) { + /* i + 1 because mass concentration starts at PM1.0 (there is no PM0.5) */ + measured_data->mass_concentration[i + 1] = (buffer[pos] << 8) + buffer[pos + 1]; + pos += 3; /* 2 B data, 1 B crc */ + } + /* number concentration [1 / cm^3] */ + for (int i = 0; i < 5; i++) { + measured_data->number_concentration[i] = (buffer[pos] << 8) + buffer[pos + 1]; + pos += 3; + } + /* typical particle size [nm] */ + measured_data->typical_particle_size = (buffer[pos] << 8) + buffer[pos + 1]; return SPS30_OK; } @@ -104,7 +104,6 @@ int8_t sps30_sleep( void ) int8_t sps30_wake_up( void ) { return sps30_send_cmd(SPS30_WAKE_UP); - return sps30_send_cmd(SPS30_WAKE_UP); } int8_t sps30_start_fan_cleaning( void ) @@ -117,30 +116,25 @@ int8_t sps30_reset( void ) return sps30_send_cmd(SPS30_RESET); } - int8_t sps30_read_status_register ( void ) { - uint8_t i2c_tx_buffer[2]; - uint8_t i2c_rx_buffer[6]; + uint8_t buffer[6]; uint8_t result; // start measurement - i2c_tx_buffer[0] = SPS30_READ_DEVICE_STATUS_REGISTER >> 8; - i2c_tx_buffer[1] = SPS30_READ_DEVICE_STATUS_REGISTER & 0x00ff; - result = i2c_transmit(SPS30_I2C_ADDRESS<<1, i2c_tx_buffer, 2); - - // TODO: Proc to vraci NACK? Vyresit. - /*if (result != I2C_OK) { + buffer[0] = SPS30_READ_DEVICE_STATUS_REGISTER >> 8; + buffer[1] = SPS30_READ_DEVICE_STATUS_REGISTER & 0x00ff; + result = i2c_transmit(SPS30_I2C_ADDRESS<<1, buffer, 2); + if (result != I2C_OK) { return SPS30_ERROR; } - return SPS30_OK;*/ - - LL_mDelay(1); // 10 ms should be enough + LL_mDelay(10); // 10 ms should be enough // read out - result = i2c_receive(SPS30_I2C_ADDRESS<<1, i2c_rx_buffer, 6); + result = i2c_receive(SPS30_I2C_ADDRESS<<1, buffer, 6); + // TODO - return 0; + return SPS30_OK; } int8_t sps30_read_firmware_version ( uint8_t * fw_ver_hi, uint8_t * fw_ver_lo ) @@ -174,20 +168,3 @@ int8_t sps30_read_firmware_version ( uint8_t * fw_ver_hi, uint8_t * fw_ver_lo ) return SPS30_OK; } - - -uint8_t calculate_crc(uint8_t data[2]) -{ - uint8_t crc = 0xFF; - for(uint8_t i = 0; i < 2; i++) { - crc ^= data[i]; - for(uint8_t bit = 8; bit > 0; --bit) { - if(crc & 0x80) { - crc = (crc << 1) ^ 0x31u; - } else { - crc = (crc << 1); - } - } - } - return crc; -} diff --git a/tests/sensor.py b/tests/sensor.py index a77aad4..c001137 100644 --- a/tests/sensor.py +++ b/tests/sensor.py @@ -17,7 +17,17 @@ class Sensor(): 'T_SCD4x': 30013, \ 'RH_SCD4x': 30014, \ 'T_SHT4x_signed': 30015, \ - 'T_SCD4x_signed': 30016 } + 'T_SCD4x_signed': 30016, \ + 'PM_mass_concentration_1.0': 30017, \ + 'PM_mass_concentration_2.5': 30018, \ + 'PM_mass_concentration_4.0': 30019, \ + 'PM_mass_concentration_10.0': 30020, \ + 'PM_number_concentration_0.5': 30021, \ + 'PM_number_concentration_1.0': 30022, \ + 'PM_number_concentration_2.5': 30023, \ + 'PM_number_concentration_4.0': 30024, \ + 'PM_number_concentration_10.0': 30025, \ + 'PM_typical_particle_size': 30026 } holding_registers = { \ 'LED_on': 40001, \ 'LED_brightness': 40002, \