diff --git a/pmme-device/c-version/main/pmme.c b/pmme-device/c-version/main/pmme.c index e90bd77..6f61366 100644 --- a/pmme-device/c-version/main/pmme.c +++ b/pmme-device/c-version/main/pmme.c @@ -1,25 +1,97 @@ #include +#include + #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_ops.h" -#include "driver/i2c_master.h" -#include "esp_lcd_panel_vendor.h" + #include "esp_log.h" -#include +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_vendor.h" + +#include "driver/i2c_master.h" #include "driver/uart.h" static const char *TAG = "PMME-Device"; +#define I2C_HOST 0 +#define I2C_SDA_PIN 21 +#define I2C_SCL_PIN 22 +#define I2C_CLOCK_HZ 400000 // Reduced from 400000 to a safer 100KHz +#define SSD1306_ADDR 0x3C +#define SSD1306_WIDTH 128 +#define SSD1306_HEIGHT 64 + #define UART_NUM UART_NUM_2 #define BUF_SIZE 32 #define UART_BUF_SIZE 128 #define TXD_PIN 17 #define RXD_PIN 16 -void app_main(void) -{ +#define SWAP_BYTES(x) ((x >> 8) | (x << 8)) + +typedef struct __attribute__((packed)) { + // PM concentrations (CF=1, standard particle) in μg/m³ + uint16_t pm1_0_cf1; + uint16_t pm2_5_cf1; + uint16_t pm10_cf1; + + // PM concentrations (atmospheric environment) in μg/m³ + uint16_t pm1_0_atm; + uint16_t pm2_5_atm; + uint16_t pm10_atm; + + // Particle counts (particles per 0.1L air) + uint16_t particles_0_3um; + uint16_t particles_0_5um; + uint16_t particles_1_0um; + uint16_t particles_2_5um; + uint16_t particles_5_0um; + uint16_t particles_10um; +} pms7003_data_t; + +void send_pms7003_command(uint8_t cmd, uint8_t datah, uint8_t datal) { + uint8_t command[7] = {0x42, 0x4D, cmd, datah, datal, 0, 0}; + + uint16_t checksum = 0; + for (int i = 0; i < 5; i++) { + checksum += command[i]; + } + command[5] = (checksum >> 8) & 0xFF; + command[6] = checksum & 0xFF; + uart_write_bytes(UART_NUM, command, 7); +} + +void read_pm_data(uint8_t *buf) { + uint16_t checksum = 0; + for (int i = 0; i <= 29; i++) { + checksum += buf[i]; + } + uint16_t c = (((uint16_t)buf[30]) << 8) | ((uint16_t)buf[31]); + if (checksum != c) { + ESP_LOGW(TAG, "Checksum from READ does not match, data corrupted"); + return; + } + ESP_LOGI(TAG, "SIZEOF pms7003_data_t: %d", sizeof(pms7003_data_t)); + if (sizeof(pms7003_data_t) == 24) { + pms7003_data_t sensor_data = {0}; + memcpy(&sensor_data, &buf[4], sizeof(pms7003_data_t)); + uint16_t *sd = (uint16_t*)&sensor_data; + for (int i = 0; i < sizeof(pms7003_data_t) / sizeof(uint16_t); i++) { + sd[i] = SWAP_BYTES(sd[i]); + } + ESP_LOGI(TAG, "PM Readings:\nPM 1.0: %d\nPM 2.5: %d\nPM 10.0:%d", + sensor_data.pm1_0_atm, + sensor_data.pm2_5_atm, + sensor_data.pm10_atm); + + } else { + ESP_LOGW(TAG, "Misaligned struct, ignoring memcpy"); + } +} + +void pm_sensor_task(void *pvParameters) { ESP_LOGI(TAG, "Initialize UART, Wait 1 second"); vTaskDelay(pdMS_TO_TICKS(1000)); ESP_LOGI(TAG, "Initialize UART, ok now go..."); @@ -35,18 +107,92 @@ void app_main(void) ESP_ERROR_CHECK(uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_driver_install(UART_NUM, UART_BUF_SIZE * 2, 0, 0, NULL, 0)); - ESP_LOGI(TAG, "Create a simple pattern"); + vTaskDelay(pdMS_TO_TICKS(100)); + send_pms7003_command(0xE1, 0x00, 0x00); + vTaskDelay(pdMS_TO_TICKS(100)); + + + ESP_LOGI(TAG, "Alloc %d buffer for UART data", BUF_SIZE); uint8_t uart_data[BUF_SIZE]; - int frequency_check = 2000; while (1) { - int len = uart_read_bytes(UART_NUM, uart_data, BUF_SIZE, frequency_check / portTICK_PERIOD_MS); + send_pms7003_command(0xE2, 0x00, 0x00); + + int len = uart_read_bytes(UART_NUM, uart_data, BUF_SIZE, 100 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "Received %d bytes from sensor", len); if (len > 0) { - for(int i = 0; i < len && i < 16; i++) { - ESP_LOGI(TAG, "0x%02X", uart_data[i]); + if (len == 32 && uart_data[0] != 0x42 && uart_data[1] != 0x4D) { + ESP_LOGW(TAG, "Frame Start does not match 0x42, 0x4D, instead got %x %x", uart_data[0], uart_data[1]); + } else { + read_pm_data(uart_data); + // for(int i = 0; i < len && i < BUF_SIZE; i++) { + // ESP_LOGI(TAG, "0x%02X", uart_data[i]); + // } } } - - vTaskDelay(pdMS_TO_TICKS(frequency_check)); + uart_flush(UART_NUM); + vTaskDelay(pdMS_TO_TICKS(5000)); } } + +void oled_display_task(void *pvParameters) { + ESP_LOGI(TAG, "Initialize I2C bus"); + i2c_master_bus_handle_t i2c_bus = NULL; + i2c_master_bus_config_t bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = I2C_HOST, + .sda_io_num = I2C_SDA_PIN, + .scl_io_num = I2C_SCL_PIN, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = SSD1306_ADDR, + .scl_speed_hz = I2C_CLOCK_HZ, // Added this line to set the clock speed + .control_phase_bytes = 1, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_bit_offset = 6, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install SSD1306 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_ssd1306_config_t vendor_config = { + .height = SSD1306_HEIGHT, + }; + esp_lcd_panel_dev_config_t panel_config = { + .bits_per_pixel = 1, + .reset_gpio_num = -1, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); + + ESP_LOGI(TAG, "Initialize the display"); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + ESP_LOGI(TAG, "Create a simple pattern"); + + uint8_t *fb = heap_caps_malloc(SSD1306_WIDTH * SSD1306_HEIGHT / 8, MALLOC_CAP_DMA); + int idx = 0; + while (1) { + memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8); + taskYIELD(); + fb[idx] = 0xFF; + + esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, SSD1306_WIDTH, SSD1306_HEIGHT, fb); + idx = (idx + 4) % (SSD1306_WIDTH * SSD1306_HEIGHT / 8); + // shift = (shift + 1) % 8; + vTaskDelay(pdMS_TO_TICKS(100)); + } + memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8); + free(fb); +} + +void app_main(void) { + xTaskCreate(pm_sensor_task, "pm_sensor_task", 4096, NULL, 5, NULL); + // xTaskCreate(oled_display_task, "oled_display_task", 4096, NULL, 10, NULL); +} diff --git a/pmme-device/pms7003-spec.txt b/pmme-device/pms7003-spec.txt new file mode 100644 index 0000000..b8d08b7 --- /dev/null +++ b/pmme-device/pms7003-spec.txt @@ -0,0 +1,135 @@ + +Output result +Mainly output as the quality and number of each particles with different size +per unit volume, the unit volume of particle number is 0.1L and the unit of +mass concentration is μ g/m³. +There are two options for digital output: passive and active. Default mode +is active after power up. In this mode sensor would send serial data to the +host automatically .The active mode is divided into two sub-modes: stable +mode and fast mode. If the concentration change is small the sensor +would run at stable mode with the real interval of 2.3s.And if the change is +big the sensor would be changed to fast mode automatically with the +interval of 200~800ms, the higher of the concentration, the shorter of the +interval. + +Circuit Attentions +1) DC 5V power supply is needed because the FAN should be driven by 5V. +But the high level of data pin is 3.3V. Level conversion unit should be +used if the power of host MCU is 5V. +2) The SET and RESET pins are pulled up inside so they should not be +connected if without usage. +3) PIN7 and PIN8 should not be connected. +4) Stable data should be got at least 30 seconds after the sensor wakeup +from the sleep mode because of the fan’s performance. +Installation Attentions +1) Metal shell is connected to the GND so be careful not to let it shorted with +the other parts of circuit except GND. +2) The best way of install is making the plane of inset and outset closely to +the plane of the host. Or some shield should be placed between inset and +outset in order to prevent the air flow from inner loop. +3) The blowhole in the shell of the host should not be smaller than the inset. +4) The sensor should not be installed in the air flow way of the air cleaner or +should be shielded by some structure. +5) The sensor should be installed at least 20cm higher than the grand in +order to prevent it from blocking by the flock dust. +6) Do not break up the sensor. +Other Attentions +1) Only the consistency of all the PM sensors of PLANTOWER is promised +and ensured. And the sensor should not be checked with any third party +equipment. +2) The sensor is usually used in the common indoor environment. So some +protection must be added if using in the conditions as followed: +a) The time of concentration ≥300μ g/m³ is longer than 50% of the +whole year or concentration≥500μ g/m³ is longer than20% of the +whole year. +b) Kitchen +c) Water mist condition such as bathroom or hot spring. +d) outdoor +Appendix I:PMS7003 transport protocol-Active Mode +Default baud rate:9600bps Check bit:None Stop bit:1 bit +32 Bytes +Start character 1 0x42 (Fixed) +Start character2 0x4d (Fixed) +Frame length high +8 bits +…… Frame length=2x13+2(data+check bytes) +Frame length low 8 +bits +…… +Data 1 high 8 bits …… Data1 refers to PM1.0 concentration unit +μ g/m3(CF=1,standard particle)* +Data 1 low 8 bits …… +Data2 high 8 bits …… Data2 refers to PM2.5 concentration unit +μ g/m3(CF=1,standard particle) +Data2 low 8 bits …… +Data3 high 8 bits …… Data3 refers to PM10 concentration unit +μ g/m3(CF=1,standard particle) +Data3 low 8 bits …… +Data4 high 8 bits …… Data4 refers to PM1.0 concentration unit * +μ g/m3(under atmospheric environment) +Data4 low 8 bits …… +Data5 high 8 bits …… Data 5 refers to PM2.5 concentration unit +μ g/m3(under atmospheric environment) +Data5 low 8 bits …… +Data6 high 8 bits ……. Data 6 refers to concentration unit (under +atmospheric environment) μ g/m3 Data6 low 8 bits …… +Data7 high 8 bits …… Data7 indicates the number of +particles with diameter beyond 0.3 um +in 0.1 L of air. Data7 low 8 bits …… +Data8 high 8 bits …… Data 8 indicates the number of +particles with diameter beyond 0.5 um +in 0.1 L of air. Data8 low 8 bits …… +Data9 high 8 bits …… Data 9 indicates the number of +particles with diameter beyond 1.0 um +in 0.1 L of air. +Data9 low 8 bits …… +2016 product data manual of PLANTOWER +Data10 high 8 bits …… Data10 indicates the number of +particles with diameter beyond 2.5 um +in 0.1 L of air. +Data10 low 8 bits …… +Data11 high 8 bits …… Data11 indicates the number of +particles with diameter beyond 5.0 um +Data11 low 8 bits …… in 0.1 L of air. +Data12 high 8 bits …… Data12 indicates the number of +particles with diameter beyond 10 um +Data12 low 8 bits …… in 0.1 L of air. +Data13 high 8 bits …… Data13 Reserved +Data13 low 8 bits …… +Data and check +high 8 bits +…… Check code=Start character1+ Start +character2+……..+data13 +Low 8 bits +Data and check +low 8 bits +…… +Note: CF=1 should be used in the factory environment +. +2016 product data manual of PLANTOWER +Appendix II:PMS7003 transport protocol-Passive Mode +Default baud rate:9600bps Check bit:None Stop bit:1 bit +Host Protocol +Start Byte +1 +Start Byte +2 +Command Data 1 Data 2 Verify Byte +1 +Verify Byte +2 +0x42 0x4d CMD DATAH DATAL LRCH LRCL +1. Command Definition +CMD DATAH DATAL +0xe2 X X Read in passive +mode +0xe1 X 00H-passive +01H-active +Change mode +0xe4 X 00H-sleep +01H-wakeup +Sleep set +2. Answer +0xe2: 32 bytes , same as appendix I +3. Verify Bytes : +Add of all the bytes except verify bytes.