📟 pmme-device: Reading PM 1.0, 2.5, and 10.0. Split sensor and OLED display to separate tasks
This commit is contained in:
parent
52b7a7258c
commit
2c1ab09770
@ -1,25 +1,97 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <string.h>
|
||||
#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);
|
||||
}
|
||||
|
135
pmme-device/pms7003-spec.txt
Normal file
135
pmme-device/pms7003-spec.txt
Normal file
@ -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.
|
Loading…
x
Reference in New Issue
Block a user