#include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.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" #include "driver/gpio.h" #include "ssd1306/ssd1306.h" #include "esp_wifi.h" #include "esp_http_server.h" #include "esp_netif.h" #include "nvs_flash.h" #include "esp_event.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 #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 #define BUTTON_PIN GPIO_NUM_5 #define LED_PIN GPIO_NUM_19 #define SWAP_BYTES(x) ((x >> 8) | (x << 8)) typedef struct { // 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; static pms7003_data_t sensor_data; static SemaphoreHandle_t sensor_data_mutex = NULL; 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 <= 28; 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)); pms7003_data_t new_sensor_data = {0}; memcpy(&new_sensor_data, &buf[4], sizeof(pms7003_data_t)); uint16_t *sd = (uint16_t *)&new_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", // new_sensor_data.pm1_0_atm, new_sensor_data.pm2_5_atm, // new_sensor_data.pm10_atm); if (xSemaphoreTake(sensor_data_mutex, portMAX_DELAY)) { sensor_data = new_sensor_data; xSemaphoreGive(sensor_data_mutex); } } 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..."); uart_config_t uart_config = { .baud_rate = 9600, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // .rx_flow_ctrl_thresh = 122, }; ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config)); 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)); 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]; while (1) { 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) { if (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 if (len == 32) { read_pm_data(uart_data); } else { ESP_LOGW(TAG, "Unexpected frame length: %d", len); } } uart_flush(UART_NUM); vTaskDelay(pdMS_TO_TICKS(5000)); } } void oled_display_task_manual(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); if (!fb) { ESP_LOGE(TAG, "Failed to initialize (fb), exiting OLED Display task"); return; } int idx = 0; while (1) { memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8); 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(16)); } memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8); free(fb); esp_lcd_panel_del(panel_handle); esp_lcd_panel_io_del(io_handle); i2c_del_master_bus(i2c_bus); } void oled_display_task(void *pvParameters) { init_ssd1306(); char pm25_str[128]; ssd1306_fill_screen(); vTaskDelay(pdMS_TO_TICKS(3000)); while (1) { uint16_t pm25_value = 0; if (xSemaphoreTake(sensor_data_mutex, pdMS_TO_TICKS(10))) { pm25_value = sensor_data.pm2_5_atm; xSemaphoreGive(sensor_data_mutex); } ssd1306_clear_screen(); snprintf(pm25_str, sizeof(pm25_str), "Curr: %d ug/m3", pm25_value); ssd1306_print_str(2, 2, "PM Me 2.5", false); ssd1306_print_str(2, 15, "by Joe", false); ssd1306_print_str(5, 40, pm25_str, false); ssd1306_display(); vTaskDelay(3000 / portTICK_PERIOD_MS); } } void button_led_task(void *pvParameters) { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << LED_PIN), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; gpio_config(&io_conf); io_conf.pin_bit_mask = (1ULL << BUTTON_PIN); io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_up_en = GPIO_PULLUP_ENABLE; gpio_config(&io_conf); gpio_set_level(LED_PIN, 0); int curr_state = 1; // HIGH bool is_pressed = false; while (1) { int level = gpio_get_level(BUTTON_PIN); is_pressed = false; if (curr_state == 1 && level == 0) { gpio_set_level(LED_PIN, 1); curr_state = 0; is_pressed = true; } else if (curr_state == 0 && level == 1) { gpio_set_level(LED_PIN, 0); curr_state = 1; is_pressed = true; } vTaskDelay(pdMS_TO_TICKS(is_pressed ? 100 : 10)); } } static esp_err_t provision_handler(httpd_req_t *req) { const char *response = "Hello world!"; httpd_resp_send(req, response, strlen(response)); return ESP_OK; } void wifi_task(void *pvParameters) { // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); } ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_ap(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); wifi_config_t wifi_config = { .ap = { .ssid = "PMME-Wifi", .password = "88888888", .ssid_len = strlen("PMME-Wifi"), .channel = 1, .authmode = WIFI_AUTH_WPA2_PSK, .max_connection = 2, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "WiFi AP started: SSID=PMME-Wifi"); httpd_handle_t server = NULL; httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); ESP_ERROR_CHECK(httpd_start(&server, &server_config)); httpd_uri_t provision_uri = { .uri = "/api/provision", .method = HTTP_GET, .handler = provision_handler, .user_ctx = NULL, }; ESP_ERROR_CHECK(httpd_register_uri_handler(server, &provision_uri)); ESP_LOGI(TAG, "HTTP server started on port %d", server_config.server_port); while (1) { vTaskDelay(pdMS_TO_TICKS(500)); } } void app_main(void) { sensor_data_mutex = xSemaphoreCreateMutex(); if (sensor_data_mutex == NULL) { ESP_LOGE(TAG, "Failed to create mutex"); return; } xTaskCreate(pm_sensor_task, "pm_sensor_task", 4096, NULL, 5, NULL); xTaskCreate(oled_display_task, "oled_display_task", 4096, NULL, 5, NULL); xTaskCreate(button_led_task, "button_led_task", 2048, NULL, 5, NULL); xTaskCreate(wifi_task, "wifi_task", 8192, NULL, 5, NULL); }