📟 pmme-device: Use ssd1306 lib to display text, read/write pm 2.5 with mutex
This commit is contained in:
parent
2c1ab09770
commit
f0685f9ce3
@ -1,2 +1,2 @@
|
|||||||
idf_component_register(SRCS "pmme.c"
|
idf_component_register(SRCS "pmme.c" "ssd1306/ssd1306.c"
|
||||||
INCLUDE_DIRS ".")
|
INCLUDE_DIRS ".")
|
||||||
|
@ -12,13 +12,14 @@
|
|||||||
|
|
||||||
#include "driver/i2c_master.h"
|
#include "driver/i2c_master.h"
|
||||||
#include "driver/uart.h"
|
#include "driver/uart.h"
|
||||||
|
#include "ssd1306/ssd1306.h"
|
||||||
|
|
||||||
static const char *TAG = "PMME-Device";
|
static const char *TAG = "PMME-Device";
|
||||||
|
|
||||||
#define I2C_HOST 0
|
#define I2C_HOST 0
|
||||||
#define I2C_SDA_PIN 21
|
#define I2C_SDA_PIN 21
|
||||||
#define I2C_SCL_PIN 22
|
#define I2C_SCL_PIN 22
|
||||||
#define I2C_CLOCK_HZ 400000 // Reduced from 400000 to a safer 100KHz
|
#define I2C_CLOCK_HZ 400000
|
||||||
#define SSD1306_ADDR 0x3C
|
#define SSD1306_ADDR 0x3C
|
||||||
#define SSD1306_WIDTH 128
|
#define SSD1306_WIDTH 128
|
||||||
#define SSD1306_HEIGHT 64
|
#define SSD1306_HEIGHT 64
|
||||||
@ -31,7 +32,7 @@ static const char *TAG = "PMME-Device";
|
|||||||
|
|
||||||
#define SWAP_BYTES(x) ((x >> 8) | (x << 8))
|
#define SWAP_BYTES(x) ((x >> 8) | (x << 8))
|
||||||
|
|
||||||
typedef struct __attribute__((packed)) {
|
typedef struct {
|
||||||
// PM concentrations (CF=1, standard particle) in μg/m³
|
// PM concentrations (CF=1, standard particle) in μg/m³
|
||||||
uint16_t pm1_0_cf1;
|
uint16_t pm1_0_cf1;
|
||||||
uint16_t pm2_5_cf1;
|
uint16_t pm2_5_cf1;
|
||||||
@ -51,6 +52,9 @@ typedef struct __attribute__((packed)) {
|
|||||||
uint16_t particles_10um;
|
uint16_t particles_10um;
|
||||||
} pms7003_data_t;
|
} 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) {
|
void send_pms7003_command(uint8_t cmd, uint8_t datah, uint8_t datal) {
|
||||||
uint8_t command[7] = {0x42, 0x4D, cmd, datah, datal, 0, 0};
|
uint8_t command[7] = {0x42, 0x4D, cmd, datah, datal, 0, 0};
|
||||||
|
|
||||||
@ -73,21 +77,19 @@ void read_pm_data(uint8_t *buf) {
|
|||||||
ESP_LOGW(TAG, "Checksum from READ does not match, data corrupted");
|
ESP_LOGW(TAG, "Checksum from READ does not match, data corrupted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "SIZEOF pms7003_data_t: %d", sizeof(pms7003_data_t));
|
// ESP_LOGI(TAG, "SIZEOF pms7003_data_t: %d", sizeof(pms7003_data_t));
|
||||||
if (sizeof(pms7003_data_t) == 24) {
|
pms7003_data_t new_sensor_data = {0};
|
||||||
pms7003_data_t sensor_data = {0};
|
memcpy(&new_sensor_data, &buf[4], sizeof(pms7003_data_t));
|
||||||
memcpy(&sensor_data, &buf[4], sizeof(pms7003_data_t));
|
uint16_t *sd = (uint16_t *)&new_sensor_data;
|
||||||
uint16_t *sd = (uint16_t*)&sensor_data;
|
for (int i = 0; i < sizeof(pms7003_data_t) / sizeof(uint16_t); i++) {
|
||||||
for (int i = 0; i < sizeof(pms7003_data_t) / sizeof(uint16_t); i++) {
|
sd[i] = SWAP_BYTES(sd[i]);
|
||||||
sd[i] = SWAP_BYTES(sd[i]);
|
}
|
||||||
}
|
// ESP_LOGI(TAG, "PM Readings:\nPM 1.0: %d\nPM 2.5: %d\nPM 10.0:%d",
|
||||||
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,
|
||||||
sensor_data.pm1_0_atm,
|
// new_sensor_data.pm10_atm);
|
||||||
sensor_data.pm2_5_atm,
|
if (xSemaphoreTake(sensor_data_mutex, portMAX_DELAY)) {
|
||||||
sensor_data.pm10_atm);
|
sensor_data = new_sensor_data;
|
||||||
|
xSemaphoreGive(sensor_data_mutex);
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Misaligned struct, ignoring memcpy");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,16 +113,15 @@ void pm_sensor_task(void *pvParameters) {
|
|||||||
send_pms7003_command(0xE1, 0x00, 0x00);
|
send_pms7003_command(0xE1, 0x00, 0x00);
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Alloc %d buffer for UART data", BUF_SIZE);
|
ESP_LOGI(TAG, "Alloc %d buffer for UART data", BUF_SIZE);
|
||||||
uint8_t uart_data[BUF_SIZE];
|
uint8_t uart_data[BUF_SIZE];
|
||||||
while (1) {
|
while (1) {
|
||||||
send_pms7003_command(0xE2, 0x00, 0x00);
|
send_pms7003_command(0xE2, 0x00, 0x00);
|
||||||
|
|
||||||
int len = uart_read_bytes(UART_NUM, uart_data, BUF_SIZE, 100 / portTICK_PERIOD_MS);
|
int len = uart_read_bytes(UART_NUM, uart_data, BUF_SIZE, 100 / portTICK_PERIOD_MS);
|
||||||
ESP_LOGI(TAG, "Received %d bytes from sensor", len);
|
// ESP_LOGI(TAG, "Received %d bytes from sensor", len);
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
if (len == 32 && uart_data[0] != 0x42 && uart_data[1] != 0x4D) {
|
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]);
|
ESP_LOGW(TAG, "Frame Start does not match 0x42, 0x4D, instead got %x %x", uart_data[0], uart_data[1]);
|
||||||
} else {
|
} else {
|
||||||
read_pm_data(uart_data);
|
read_pm_data(uart_data);
|
||||||
@ -134,7 +135,7 @@ void pm_sensor_task(void *pvParameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void oled_display_task(void *pvParameters) {
|
void oled_display_task_manual(void *pvParameters) {
|
||||||
ESP_LOGI(TAG, "Initialize I2C bus");
|
ESP_LOGI(TAG, "Initialize I2C bus");
|
||||||
i2c_master_bus_handle_t i2c_bus = NULL;
|
i2c_master_bus_handle_t i2c_bus = NULL;
|
||||||
i2c_master_bus_config_t bus_config = {
|
i2c_master_bus_config_t bus_config = {
|
||||||
@ -177,22 +178,53 @@ void oled_display_task(void *pvParameters) {
|
|||||||
ESP_LOGI(TAG, "Create a simple pattern");
|
ESP_LOGI(TAG, "Create a simple pattern");
|
||||||
|
|
||||||
uint8_t *fb = heap_caps_malloc(SSD1306_WIDTH * SSD1306_HEIGHT / 8, MALLOC_CAP_DMA);
|
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;
|
int idx = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
||||||
taskYIELD();
|
|
||||||
fb[idx] = 0xFF;
|
fb[idx] = 0xFF;
|
||||||
|
|
||||||
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, SSD1306_WIDTH, SSD1306_HEIGHT, fb);
|
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, SSD1306_WIDTH, SSD1306_HEIGHT, fb);
|
||||||
idx = (idx + 4) % (SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
idx = (idx + 4) % (SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
||||||
// shift = (shift + 1) % 8;
|
// shift = (shift + 1) % 8;
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(16));
|
||||||
}
|
}
|
||||||
memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
memset(fb, 0, SSD1306_WIDTH * SSD1306_HEIGHT / 8);
|
||||||
free(fb);
|
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];
|
||||||
|
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 app_main(void) {
|
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(pm_sensor_task, "pm_sensor_task", 4096, NULL, 5, NULL);
|
||||||
// xTaskCreate(oled_display_task, "oled_display_task", 4096, NULL, 10, NULL);
|
xTaskCreate(oled_display_task, "oled_display_task", 4096, NULL, 5, NULL);
|
||||||
}
|
}
|
||||||
|
561
pmme-device/c-version/main/ssd1306/ssd1306.c
Normal file
561
pmme-device/c-version/main/ssd1306/ssd1306.c
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
#include "ssd1306.h"
|
||||||
|
#include "ssd1306_const.h"
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t ssd1306_logo[8][64] = {
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0xEF, 0xEF, 0xEF, 0xEF, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x9F, 0x5F, 0x6F, 0xE7, 0xF3, 0xF9, 0xF9, 0xFB,
|
||||||
|
0xF7, 0xE7, 0xCF, 0x9F, 0xBF, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0x3F, 0x9F, 0xDF,
|
||||||
|
0xCF, 0xE7, 0xF3, 0xF9, 0xFD, 0xFE, 0xFE, 0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC, 0xF9, 0xF3, 0xF7, 0x67, 0x0F, 0x1F, 0x3F, 0x7F,
|
||||||
|
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFF, 0x7F, 0x3F, 0x80, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC, 0xFE, 0x3F, 0x1F, 0x1F,
|
||||||
|
0x0F, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x03, 0x03, 0x07, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFB, 0xF9, 0xFC, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFE, 0xFC, 0xF9, 0xF3, 0xF7, 0xEF, 0xCF, 0x9F, 0x3F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0x00, 0xFE, 0x7F, 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0x7F, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xE0, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F,
|
||||||
|
0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0x7F, 0x7F, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFC, 0xFC, 0xFE, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE,
|
||||||
|
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xF8, 0xF0, 0xE0,
|
||||||
|
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xFC, 0x9E, 0x1F, 0x3F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFE, 0xFC, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
|
||||||
|
0x78, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFC, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF,
|
||||||
|
0xDF, 0xDF, 0xDF, 0xDF, 0xD0, 0xC0, 0xC0, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC0, 0xC0,
|
||||||
|
0xD0, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF,
|
||||||
|
0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
|
||||||
|
};
|
||||||
|
|
||||||
|
static i2c_ssd1306_handle_t i2c_ssd1306;
|
||||||
|
static i2c_master_bus_handle_t i2c_master_bus;
|
||||||
|
|
||||||
|
/* I2C Master */
|
||||||
|
static const i2c_master_bus_config_t i2c_master_bus_config = {
|
||||||
|
.i2c_port = I2C_NUM_0,
|
||||||
|
.scl_io_num = GPIO_NUM_22,
|
||||||
|
.sda_io_num = GPIO_NUM_21,
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
.glitch_ignore_cnt = 7,
|
||||||
|
.flags.enable_internal_pullup = true};
|
||||||
|
|
||||||
|
|
||||||
|
/* SSD1306 */
|
||||||
|
static const i2c_ssd1306_config_t i2c_ssd1306_config = {
|
||||||
|
.i2c_device_address = 0x3C,
|
||||||
|
.i2c_scl_speed_hz = 400000,
|
||||||
|
.width = 128,
|
||||||
|
.height = 64,
|
||||||
|
.wise = SSD1306_BOTTOM_TO_TOP};
|
||||||
|
|
||||||
|
|
||||||
|
void init_ssd1306(void)
|
||||||
|
{
|
||||||
|
i2c_new_master_bus(&i2c_master_bus_config, &i2c_master_bus);
|
||||||
|
i2c_ssd1306_init(i2c_master_bus, i2c_ssd1306_config, &i2c_ssd1306);
|
||||||
|
i2c_ssd1306_buffer_image(&i2c_ssd1306, 32, 0, (const uint8_t *)ssd1306_logo, 64, 64, false);
|
||||||
|
i2c_ssd1306_buffer_to_ram(&i2c_ssd1306);
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
i2c_ssd1306_buffer_clear(&i2c_ssd1306);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ssd1306_print_str(uint8_t x, uint8_t y, const char *text, bool invert)
|
||||||
|
{
|
||||||
|
return (i2c_ssd1306_buffer_text(&i2c_ssd1306, x, y, text, invert));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ssd1306_display(void)
|
||||||
|
{
|
||||||
|
return (i2c_ssd1306_buffer_to_ram(&i2c_ssd1306));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ssd1306_clear_screen(void)
|
||||||
|
{
|
||||||
|
return (i2c_ssd1306_buffer_clear(&i2c_ssd1306));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_init(i2c_master_bus_handle_t i2c_master_bus, i2c_ssd1306_config_t i2c_ssd1306_config, i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
if (i2c_ssd1306_config.i2c_scl_speed_hz > 400000 || i2c_ssd1306_config.width > 128 || i2c_ssd1306_config.height % 8 != 0 || i2c_ssd1306_config.height < 16 || i2c_ssd1306_config.height > 64)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid SSD1306 configuration, 'i2c_scl_speed_hz' must be less than or equal to 400000, 'width' must be less than or equal to 128, 'height' must be between 16 and 64 and multiple of 8");
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(SSD1306_TAG, "Initializing I2C SSD1306...");
|
||||||
|
esp_err_t ret = i2c_master_probe(i2c_master_bus, i2c_ssd1306_config.i2c_device_address, I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
switch (ret)
|
||||||
|
{
|
||||||
|
case ESP_ERR_NOT_FOUND:
|
||||||
|
ESP_LOGE(SSD1306_TAG, "I2C SSD1306 device not found in address 0x%02X", i2c_ssd1306_config.i2c_device_address);
|
||||||
|
break;
|
||||||
|
case ESP_ERR_TIMEOUT:
|
||||||
|
ESP_LOGE(SSD1306_TAG, "I2C SSD1306 device timeout in address 0x%02X", i2c_ssd1306_config.i2c_device_address);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(SSD1306_TAG, "I2C SSD1306 device error in address 0x%02X", i2c_ssd1306_config.i2c_device_address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_device_config_t i2c_device_config = {
|
||||||
|
.dev_addr_length = I2C_ADDR_BIT_7,
|
||||||
|
.device_address = i2c_ssd1306_config.i2c_device_address,
|
||||||
|
.scl_speed_hz = i2c_ssd1306_config.i2c_scl_speed_hz};
|
||||||
|
ret = i2c_master_bus_add_device(i2c_master_bus, &i2c_device_config, &i2c_ssd1306->i2c_master_dev);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to add I2C SSD1306 device");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
uint8_t ssd1306_init_cmd[] = {
|
||||||
|
OLED_CONTROL_BYTE_CMD,
|
||||||
|
OLED_CMD_DISPLAY_OFF,
|
||||||
|
OLED_CMD_SET_MUX_RATIO, (i2c_ssd1306_config.height - 1),
|
||||||
|
OLED_CMD_SET_VERT_DISPLAY_OFFSET, 0x00,
|
||||||
|
OLED_MASK_DISPLAY_START_LINE | 0x00,
|
||||||
|
OLED_CMD_COM_SCAN_DIRECTION_NORMAL,
|
||||||
|
OLED_CMD_SEGMENT_REMAP_LEFT_TO_RIGHT,
|
||||||
|
OLED_CMD_SET_COM_PIN_HARDWARE_MAP, 0x12,
|
||||||
|
OLED_CMD_SET_MEMORY_ADDR_MODE, 0x02,
|
||||||
|
OLED_CMD_SET_CONTRAST_CONTROL, 0xFF,
|
||||||
|
OLED_CMD_SET_DISPLAY_CLK_DIVIDE, 0x80,
|
||||||
|
OLED_CMD_ENABLE_DISPLAY_RAM,
|
||||||
|
OLED_CMD_NORMAL_DISPLAY,
|
||||||
|
OLED_CMD_SET_CHARGE_PUMP, 0x14,
|
||||||
|
OLED_CMD_DISPLAY_ON};
|
||||||
|
if (i2c_ssd1306_config.wise == SSD1306_BOTTOM_TO_TOP)
|
||||||
|
{
|
||||||
|
ssd1306_init_cmd[7] = OLED_CMD_COM_SCAN_DIRECTION_REMAP;
|
||||||
|
ssd1306_init_cmd[8] = OLED_CMD_SEGMENT_REMAP_RIGHT_TO_LEFT;
|
||||||
|
}
|
||||||
|
ret = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ssd1306_init_cmd, sizeof(ssd1306_init_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to initialize I2C SSD1306 device");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ssd1306->width = i2c_ssd1306_config.width;
|
||||||
|
i2c_ssd1306->height = i2c_ssd1306_config.height;
|
||||||
|
i2c_ssd1306->total_pages = i2c_ssd1306_config.height / 8;
|
||||||
|
|
||||||
|
i2c_ssd1306->page = (ssd1306_page_t *)calloc(i2c_ssd1306->total_pages, sizeof(ssd1306_page_t));
|
||||||
|
if (i2c_ssd1306->page == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to allocate memory for I2C SSD1306 device");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[i].segment = (uint8_t *)calloc(i2c_ssd1306->width, sizeof(uint8_t));
|
||||||
|
if (i2c_ssd1306->page[i].segment == NULL)
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
ESP_LOGI(SSD1306_TAG, "I2C SSD1306 initialized successfully");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_deinit(i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
ESP_LOGI(SSD1306_TAG, "Deinitializing I2C SSD1306...");
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
free(i2c_ssd1306->page[i].segment);
|
||||||
|
}
|
||||||
|
free(i2c_ssd1306->page);
|
||||||
|
esp_err_t ret = i2c_master_bus_rm_device(i2c_ssd1306->i2c_master_dev);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to remove I2C SSD1306 device");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ESP_LOGI(SSD1306_TAG, "I2C SSD1306 deinitialized successfully");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_check(i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
for (uint8_t j = 0; j < i2c_ssd1306->width; j++)
|
||||||
|
{
|
||||||
|
printf("%02X ", i2c_ssd1306->page[i].segment[j]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_clear(i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
memset(i2c_ssd1306->page[i].segment, 0x00, i2c_ssd1306->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill(i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
memset(i2c_ssd1306->page[i].segment, 0xFF, i2c_ssd1306->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill_pixel(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, bool fill)
|
||||||
|
{
|
||||||
|
if (x >= i2c_ssd1306->width || y >= i2c_ssd1306->height)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid pixel coordinates, 'x' must be between 0 and %d, 'y' must be between 0 and %d", i2c_ssd1306->width - 1, i2c_ssd1306->height - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
uint8_t page = y / 8;
|
||||||
|
uint8_t bit = 1 << (y % 8);
|
||||||
|
if (fill)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[page].segment[x] |= bit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[page].segment[x] &= ~bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill_space(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x1, uint8_t x2, uint8_t y1, uint8_t y2, bool fill)
|
||||||
|
{
|
||||||
|
if (x1 >= i2c_ssd1306->width || x2 >= i2c_ssd1306->width || y1 >= i2c_ssd1306->height || y2 >= i2c_ssd1306->height || x1 > x2 || y1 > y2)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid space coordinates, 'x1' and 'x2' must be between 0 and %d, 'y1' and 'y2' must be between 0 and %d, 'x1' must be less than 'x2', 'y1' must be less than 'y2'", i2c_ssd1306->width - 1, i2c_ssd1306->height - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t start_page = y1 / 8;
|
||||||
|
uint8_t end_page = y2 / 8;
|
||||||
|
uint8_t mask;
|
||||||
|
uint8_t offset_start = y1 % 8;
|
||||||
|
uint8_t offset_end = y2 % 8;
|
||||||
|
|
||||||
|
for (uint8_t page = start_page; page <= end_page; page++)
|
||||||
|
{
|
||||||
|
if (start_page == end_page)
|
||||||
|
{
|
||||||
|
mask = (0xFF << offset_start) & (0xFF >> (7 - offset_end));
|
||||||
|
}
|
||||||
|
else if (page == start_page)
|
||||||
|
{
|
||||||
|
mask = 0xFF << offset_start;
|
||||||
|
}
|
||||||
|
else if (page == end_page)
|
||||||
|
{
|
||||||
|
mask = 0xFF >> (7 - offset_end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mask = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t j = x1; j <= x2; j++)
|
||||||
|
{
|
||||||
|
if (fill)
|
||||||
|
i2c_ssd1306->page[page].segment[j] |= mask;
|
||||||
|
else
|
||||||
|
i2c_ssd1306->page[page].segment[j] &= ~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_text(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, const char *text, bool invert)
|
||||||
|
{
|
||||||
|
if (x >= i2c_ssd1306->width || y >= i2c_ssd1306->height || !text || strlen(text) == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid text or coordinates: x=%d (max %d), y=%d (max %d)", x, i2c_ssd1306->width - 1, y, i2c_ssd1306->height - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t len = strlen(text);
|
||||||
|
uint8_t page = y / 8;
|
||||||
|
uint8_t offset = y % 8;
|
||||||
|
bool has_next_page = (page + 1) < i2c_ssd1306->total_pages;
|
||||||
|
|
||||||
|
uint8_t max_chars = (i2c_ssd1306->width - x) / 8;
|
||||||
|
if (len > max_chars)
|
||||||
|
{
|
||||||
|
ESP_LOGW(SSD1306_TAG, "Text truncated: text columns exceed display width, lost %d columns", x + (len * 8) - i2c_ssd1306->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset != 0 && !has_next_page)
|
||||||
|
{
|
||||||
|
ESP_LOGW(SSD1306_TAG, "Vertical truncation: text exceeds display height, lost %d rows", offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < len && x < i2c_ssd1306->width; i++)
|
||||||
|
{
|
||||||
|
const uint8_t *char_data = font8x8[(uint8_t)text[i]];
|
||||||
|
uint8_t available_columns = i2c_ssd1306->width - x;
|
||||||
|
uint8_t columns_to_draw = (available_columns < 8) ? available_columns : 8;
|
||||||
|
|
||||||
|
for (uint8_t j = 0; j < columns_to_draw; j++)
|
||||||
|
{
|
||||||
|
uint8_t char_col = char_data[j];
|
||||||
|
|
||||||
|
if (invert)
|
||||||
|
{
|
||||||
|
char_col = ~char_col;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset == 0)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[page].segment[x + j] |= char_col;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t lower_part = char_col << offset;
|
||||||
|
uint8_t upper_part = char_col >> (8 - offset);
|
||||||
|
|
||||||
|
i2c_ssd1306->page[page].segment[x + j] |= lower_part;
|
||||||
|
if (has_next_page)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[page + 1].segment[x + j] |= upper_part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_int(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, int value, bool invert)
|
||||||
|
{
|
||||||
|
char text[16];
|
||||||
|
sprintf(text, "%d", value);
|
||||||
|
|
||||||
|
return i2c_ssd1306_buffer_text(i2c_ssd1306, x, y, text, invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_float(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, float value, uint8_t decimals, bool invert)
|
||||||
|
{
|
||||||
|
char text[16];
|
||||||
|
sprintf(text, "%.*f", decimals, value);
|
||||||
|
|
||||||
|
return i2c_ssd1306_buffer_text(i2c_ssd1306, x, y, text, invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_image(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, const uint8_t *image, uint8_t img_width, uint8_t img_height, bool invert)
|
||||||
|
{
|
||||||
|
if (image == NULL || img_width == 0 || img_height == 0 || x >= i2c_ssd1306->width || y >= i2c_ssd1306->height)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid image or coordinates: x=%d (max %d), y=%d (max %d)", x, i2c_ssd1306->width - 1, y, i2c_ssd1306->height - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t draw_width = (img_width < (i2c_ssd1306->width - x)) ? img_width : (i2c_ssd1306->width - x);
|
||||||
|
uint8_t draw_height = (img_height < (i2c_ssd1306->height - y)) ? img_height : (i2c_ssd1306->height - y);
|
||||||
|
|
||||||
|
uint8_t start_page = y / 8;
|
||||||
|
uint8_t vertical_offset = y % 8;
|
||||||
|
uint8_t num_pages = i2c_ssd1306->total_pages;
|
||||||
|
uint8_t draw_pages = (((draw_height + 7) / 8) < (num_pages - start_page)) ? ((draw_height + 7) / 8) : (num_pages - start_page);
|
||||||
|
|
||||||
|
if (img_height > draw_height)
|
||||||
|
{
|
||||||
|
ESP_LOGW(SSD1306_TAG, "Vertical truncation: Lost %d rows", img_height - draw_height);
|
||||||
|
}
|
||||||
|
if (draw_width < img_width)
|
||||||
|
{
|
||||||
|
ESP_LOGW(SSD1306_TAG, "Horizontal truncation: Lost %d columns", img_width - draw_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t col = 0; col < draw_width; col++)
|
||||||
|
{
|
||||||
|
for (uint8_t page = 0; page < draw_pages; page++)
|
||||||
|
{
|
||||||
|
uint8_t target_page = start_page + page;
|
||||||
|
if (target_page >= num_pages)
|
||||||
|
break;
|
||||||
|
|
||||||
|
uint8_t img_byte = image[page * img_width + col];
|
||||||
|
if (invert)
|
||||||
|
img_byte = ~img_byte;
|
||||||
|
|
||||||
|
if (vertical_offset == 0)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[target_page].segment[x + col] |= img_byte;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t lower = img_byte << vertical_offset;
|
||||||
|
uint8_t upper = img_byte >> (8 - vertical_offset);
|
||||||
|
|
||||||
|
i2c_ssd1306->page[target_page].segment[x + col] |= lower;
|
||||||
|
if (target_page + 1 < num_pages)
|
||||||
|
{
|
||||||
|
i2c_ssd1306->page[target_page + 1].segment[x + col] |= upper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_segment_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page, uint8_t segment)
|
||||||
|
{
|
||||||
|
if (page >= i2c_ssd1306->total_pages || segment >= i2c_ssd1306->width)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid page or segment number, 'page' must be between 0 and %d, 'segment' must be between 0 and %d", i2c_ssd1306->total_pages - 1, i2c_ssd1306->width - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ram_addr_cmd[] = {
|
||||||
|
OLED_CONTROL_BYTE_CMD,
|
||||||
|
OLED_MASK_PAGE_ADDR | page,
|
||||||
|
OLED_MASK_LSB_NIBBLE_SEG_ADDR | (segment & 0x0F),
|
||||||
|
OLED_MASK_HSB_NIBBLE_SEG_ADDR | (segment >> 4 & 0x0F)};
|
||||||
|
esp_err_t err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_addr_cmd, sizeof(ram_addr_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to address the segment to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
uint8_t ram_data_cmd[] = {
|
||||||
|
OLED_CONTROL_BYTE_DATA,
|
||||||
|
i2c_ssd1306->page[page].segment[segment]};
|
||||||
|
err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_data_cmd, sizeof(ram_data_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to transfer the segment to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_segments_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page, uint8_t initial_segment, uint8_t final_segment)
|
||||||
|
{
|
||||||
|
if (page >= i2c_ssd1306->total_pages || initial_segment >= i2c_ssd1306->width || final_segment >= i2c_ssd1306->width || initial_segment > final_segment)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid page or segment range, 'page' must be between 0 and %d, 'initial_segment' and 'final_segment' must be between 0 and %d, 'initial_segment' must be less than or equal to 'final_segment'", i2c_ssd1306->total_pages - 1, i2c_ssd1306->width - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ram_addr_cmd[] = {
|
||||||
|
OLED_CONTROL_BYTE_CMD,
|
||||||
|
OLED_MASK_PAGE_ADDR | page,
|
||||||
|
OLED_MASK_LSB_NIBBLE_SEG_ADDR | (initial_segment & 0x0F),
|
||||||
|
OLED_MASK_HSB_NIBBLE_SEG_ADDR | (initial_segment >> 4 & 0x0F)};
|
||||||
|
esp_err_t err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_addr_cmd, sizeof(ram_addr_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to address the initial segment to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
uint8_t ram_data_cmd[final_segment - initial_segment + 2];
|
||||||
|
ram_data_cmd[0] = OLED_CONTROL_BYTE_DATA;
|
||||||
|
for (uint8_t i = 0; i < final_segment - initial_segment + 1; i++)
|
||||||
|
{
|
||||||
|
ram_data_cmd[i + 1] = i2c_ssd1306->page[page].segment[initial_segment + i];
|
||||||
|
}
|
||||||
|
err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_data_cmd, sizeof(ram_data_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to transfer the segments to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_page_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page)
|
||||||
|
{
|
||||||
|
if (page >= i2c_ssd1306->total_pages)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid page number, must be between 0 and %d", i2c_ssd1306->total_pages - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ram_addr_cmd[] = {
|
||||||
|
OLED_CONTROL_BYTE_CMD,
|
||||||
|
OLED_MASK_PAGE_ADDR | page,
|
||||||
|
OLED_MASK_LSB_NIBBLE_SEG_ADDR | (0x00 & 0x0F),
|
||||||
|
OLED_MASK_HSB_NIBBLE_SEG_ADDR | (0x00 >> 4 & 0x0F)};
|
||||||
|
esp_err_t err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_addr_cmd, sizeof(ram_addr_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to address the page to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
uint8_t ram_data_cmd[i2c_ssd1306->width + 1];
|
||||||
|
ram_data_cmd[0] = OLED_CONTROL_BYTE_DATA;
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->width; i++)
|
||||||
|
{
|
||||||
|
ram_data_cmd[i + 1] = i2c_ssd1306->page[page].segment[i];
|
||||||
|
}
|
||||||
|
err = i2c_master_transmit(i2c_ssd1306->i2c_master_dev, ram_data_cmd, sizeof(ram_data_cmd), I2C_SSD1306_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Failed to transfer the page to the RAM of the SSD1306 device");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_pages_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t initial_page, uint8_t final_page)
|
||||||
|
{
|
||||||
|
if (initial_page >= i2c_ssd1306->total_pages || final_page >= i2c_ssd1306->total_pages || initial_page > final_page)
|
||||||
|
{
|
||||||
|
ESP_LOGE(SSD1306_TAG, "Invalid page range, 'initial_page' and 'final_page' must be between 0 and %d, 'initial_page' must be less than or equal to 'final_page'", i2c_ssd1306->total_pages - 1);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
for (uint8_t i = initial_page; i <= final_page; i++)
|
||||||
|
{
|
||||||
|
esp_err_t err = i2c_ssd1306_page_to_ram(i2c_ssd1306, i);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_ssd1306_buffer_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
for (uint8_t i = 0; i < i2c_ssd1306->total_pages; i++)
|
||||||
|
{
|
||||||
|
err = i2c_ssd1306_page_to_ram(i2c_ssd1306, i);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
284
pmme-device/c-version/main/ssd1306/ssd1306.h
Normal file
284
pmme-device/c-version/main/ssd1306/ssd1306.h
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/i2c_master.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
#define SSD1306_TAG "SSD1306"
|
||||||
|
|
||||||
|
#define I2C_SSD1306_TIMEOUT_MS 1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for SSD1306 display orientation.
|
||||||
|
*
|
||||||
|
* Defines the orientation (drawing order) used for the SSD1306 display.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SSD1306_TOP_TO_BOTTOM,
|
||||||
|
SSD1306_BOTTOM_TO_TOP
|
||||||
|
} ssd1306_wise_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure for an SSD1306 page segment.
|
||||||
|
*
|
||||||
|
* Contains a pointer to the segment data for a page in the SSD1306 buffer.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t *segment;
|
||||||
|
} ssd1306_page_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuration for the I2C SSD1306 display.
|
||||||
|
*
|
||||||
|
* Holds configuration parameters for both the SSD1306 display and the I2C master.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t i2c_device_address;
|
||||||
|
uint32_t i2c_scl_speed_hz;
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
ssd1306_wise_t wise;
|
||||||
|
} i2c_ssd1306_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle for the I2C SSD1306 display.
|
||||||
|
*
|
||||||
|
* Contains runtime information including the I2C device handle, display dimensions, and pointers to the page buffers.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
i2c_master_dev_handle_t i2c_master_dev;
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
uint8_t total_pages;
|
||||||
|
ssd1306_page_t *page;
|
||||||
|
} i2c_ssd1306_handle_t;
|
||||||
|
|
||||||
|
|
||||||
|
void init_ssd1306(void);
|
||||||
|
esp_err_t ssd1306_print_str(uint8_t x, uint8_t y, const char *text, bool invert);
|
||||||
|
esp_err_t ssd1306_display(void);
|
||||||
|
esp_err_t ssd1306_clear_screen(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the I2C SSD1306 display.
|
||||||
|
*
|
||||||
|
* Configures and initializes the SSD1306 display using the provided I2C bus and configuration.
|
||||||
|
*
|
||||||
|
* @param i2c_master_bus An initialized I2C master bus handle.
|
||||||
|
* @param i2c_ssd1306_config Configuration parameters for the SSD1306 display.
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle to be initialized.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success.
|
||||||
|
* - ESP_ERR_INVALID_ARG if an argument is invalid.
|
||||||
|
* - ESP_ERR_NO_MEM if memory allocation fails.
|
||||||
|
* - ESP_FAIL on other failures.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_init(i2c_master_bus_handle_t i2c_master_bus, i2c_ssd1306_config_t i2c_ssd1306_config, i2c_ssd1306_handle_t *i2c_ssd1306);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitialize the I2C SSD1306 display.
|
||||||
|
*
|
||||||
|
* Frees the memory allocated for the SSD1306 display handle.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_deinit(i2c_ssd1306_handle_t *i2c_ssd1306);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Display the contents of the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Prints the hexadecimal values of each segment in every page of the SSD1306 buffer
|
||||||
|
* for debugging purposes.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_check(i2c_ssd1306_handle_t *i2c_ssd1306);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the SSD1306 display buffer.
|
||||||
|
*
|
||||||
|
* Resets all segments in the SSD1306 buffer to 0x00.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_clear(i2c_ssd1306_handle_t *i2c_ssd1306);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fill the SSD1306 display buffer.
|
||||||
|
*
|
||||||
|
* Fills every segment in each page of the SSD1306 buffer with 0xFF.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill(i2c_ssd1306_handle_t *i2c_ssd1306);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set or clear an individual pixel in the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Modifies the state of a specific pixel in the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x X-coordinate of the pixel.
|
||||||
|
* @param y Y-coordinate of the pixel.
|
||||||
|
* @param fill If true, the pixel is set; if false, it is cleared.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill_pixel(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, bool fill);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fill or clear a rectangular area in the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Sets or clears a block of pixels within the specified coordinate range.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x1 X-coordinate of the starting pixel.
|
||||||
|
* @param x2 X-coordinate of the ending pixel.
|
||||||
|
* @param y1 Y-coordinate of the starting pixel.
|
||||||
|
* @param y2 Y-coordinate of the ending pixel.
|
||||||
|
* @param fill If true, pixels are set; if false, pixels are cleared.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_fill_space(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x1, uint8_t x2, uint8_t y1, uint8_t y2, bool fill);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render text into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Copies 8x8 font characters representing the provided string into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x X-coordinate for the text's starting position.
|
||||||
|
* @param y Y-coordinate for the text's starting position.
|
||||||
|
* @param text Null-terminated string to render.
|
||||||
|
* @param invert If true, the text is rendered inverted.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_text(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, const char *text, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render an integer into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Copies 8x8 font characters representing the given integer into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x X-coordinate for the integer's starting position.
|
||||||
|
* @param y Y-coordinate for the integer's starting position.
|
||||||
|
* @param value Integer value to render.
|
||||||
|
* @param invert If true, the integer is rendered inverted.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_int(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, int value, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render a floating-point number into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Copies 8x8 font characters representing the given float value into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x X-coordinate for the float's starting position.
|
||||||
|
* @param y Y-coordinate for the float's starting position.
|
||||||
|
* @param value Float value to render.
|
||||||
|
* @param decimals Number of decimal places to display.
|
||||||
|
* @param invert If true, the float is rendered inverted.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_float(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, float value, uint8_t decimals, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render an image into the SSD1306 buffer.
|
||||||
|
*
|
||||||
|
* Copies the provided image data into the SSD1306 buffer at an 8x8 pixel resolution.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param x X-coordinate for the image's starting position.
|
||||||
|
* @param y Y-coordinate for the image's starting position.
|
||||||
|
* @param image Pointer to the image data.
|
||||||
|
* @param width Width of the image in pixels.
|
||||||
|
* @param height Height of the image in pixels.
|
||||||
|
* @param invert If true, the image is rendered inverted.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_image(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t x, uint8_t y, const uint8_t *image, uint8_t width, uint8_t height, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfer a specific buffer segment to the SSD1306 display RAM.
|
||||||
|
*
|
||||||
|
* Moves the designated segment from the buffer to the corresponding location in the display's RAM.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param page Page number that contains the segment.
|
||||||
|
* @param segment Segment number within the page.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_segment_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page, uint8_t segment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfer a range of buffer segments to the SSD1306 display RAM.
|
||||||
|
*
|
||||||
|
* Moves a sequence of segments from the specified page in the buffer to the display's RAM.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param page Page number that contains the segments.
|
||||||
|
* @param initial_segment Starting segment number.
|
||||||
|
* @param final_segment Ending segment number.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_segments_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page, uint8_t initial_segment, uint8_t final_segment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfer an entire page from the buffer to the SSD1306 display RAM.
|
||||||
|
*
|
||||||
|
* Updates the display's RAM by transferring all segments of the specified page.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param page Page number to update.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_page_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfer a range of pages from the buffer to the SSD1306 display RAM.
|
||||||
|
*
|
||||||
|
* Updates the display's RAM by transferring all segments for pages within the specified range.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
* @param initial_page Starting page number.
|
||||||
|
* @param final_page Ending page number.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_pages_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306, uint8_t initial_page, uint8_t final_page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfer the entire buffer to the SSD1306 display RAM.
|
||||||
|
*
|
||||||
|
* Updates the display's RAM by transferring the contents of all pages in the buffer.
|
||||||
|
*
|
||||||
|
* @param i2c_ssd1306 Pointer to the SSD1306 handle.
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, or an error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t i2c_ssd1306_buffer_to_ram(i2c_ssd1306_handle_t *i2c_ssd1306);
|
315
pmme-device/c-version/main/ssd1306/ssd1306_const.h
Normal file
315
pmme-device/c-version/main/ssd1306/ssd1306_const.h
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf */
|
||||||
|
|
||||||
|
/* CONTROL BYTE
|
||||||
|
A control byte mainly consists of Co (Continuation Bit) and D/C# (Data / Command Selection Bit) bits following by six “0”.
|
||||||
|
Co: 0 = The transmission of the following information will contain data bytes only.
|
||||||
|
1 = NOT DEFINED
|
||||||
|
D/C#: 0 = The following data byte is a command.
|
||||||
|
1 = The following data byte is a data which will be stored at the GDDRAM. The GDDRAM column address pointer will be
|
||||||
|
increased by one automatically after each data write.
|
||||||
|
*/
|
||||||
|
#define OLED_CONTROL_BYTE_CMD 0x00 // Control Byte to transmit a command.
|
||||||
|
#define OLED_CONTROL_BYTE_DATA 0x40 // Control byte to transmit data.
|
||||||
|
|
||||||
|
/* FUNDAMENTAL COMMAND */
|
||||||
|
#define OLED_CMD_SET_CONTRAST_CONTROL 0x81 // Double byte command to set contrast setting of the display. [0x00 - 0xFF] (RESET: 0x7F)
|
||||||
|
#define OLED_CMD_ENABLE_DISPLAY_RAM 0xA4 // Enable display outputs according to the GDDRAM contents. (Default during reset)
|
||||||
|
#define OLED_CMD_FORCE_DISPLAY_ALLON 0xA5 // Forces the entire display to be “ON”.
|
||||||
|
#define OLED_CMD_NORMAL_DISPLAY 0xA6 // RAM data of 1 indicates an “ON” pixel. (Default during reset)
|
||||||
|
#define OLED_CMD_INVERTED_DISPLAY 0xA7 // RAM data of 0 indicates an “ON” pixel.
|
||||||
|
#define OLED_CMD_DISPLAY_OFF 0xAE // Display OFF in sleep mode. (Default during reset)
|
||||||
|
#define OLED_CMD_DISPLAY_ON 0xAF // Display ON in normal mode.
|
||||||
|
|
||||||
|
/* ADDRESSING SETTING */
|
||||||
|
#define OLED_CMD_SET_MEMORY_ADDR_MODE 0x20 // Double byte command to set memory addressing mode. [0x00 | HORZ, 0x01 | VERT, 0x02 | PAGE] (RESET: 0x02)
|
||||||
|
#define OLED_MASK_PAGE_ADDR 0xB0 // Mask to set the page address of pointer only in page addressing mode. [0xB0 - 0xB7]
|
||||||
|
#define OLED_MASK_LSB_NIBBLE_SEG_ADDR 0x00 // Mask to set the lower start column address of pointer only in page addressing mode. [0x00 - 0x0F]
|
||||||
|
#define OLED_MASK_HSB_NIBBLE_SEG_ADDR 0x10 // Mask to set the higher start column address of pointer only in page addressing mode. [0x10 - 0x1F]
|
||||||
|
#define OLED_CMD_SET_COLUMN_ADDR_RANGE 0x21 // Three byte command to set start and end column address only in horizontal/vertical mode. [0x00 - 0x7F & 0x00 - 0x7F] (RESET: 0x00 & 0x7F)
|
||||||
|
#define OLED_CMD_SET_PAGE_ADDR_RANGE 0x22 // Three byte command to set start and end page address only in horizontal/vertical mode. [0x00 - 0x07 & 0x00 - 0x07] (RESET: 0x00 & 0x07)
|
||||||
|
|
||||||
|
/* HARDWARE CONFIGURATION */
|
||||||
|
#define OLED_MASK_DISPLAY_START_LINE 0x40 // Mask to set the display start line register to determine starting address of display RAM. [0x40 - 0x7F] (RESET: 0x40)
|
||||||
|
#define OLED_CMD_SEGMENT_REMAP_LEFT_TO_RIGHT 0xA0 // Column address 0 is mapped to SEG0, indicating that the display is mapped from left to right. (Default during reset)
|
||||||
|
#define OLED_CMD_SEGMENT_REMAP_RIGHT_TO_LEFT 0xA1 // Column address 127 is mapped to SEG0, indicating that the display is mapped from right to left.
|
||||||
|
#define OLED_CMD_SET_MUX_RATIO 0xA8 // Double byte command to set MUX ratio. [0x0F - 0x3F] (RESET: 0x3F = 64MUX -1)
|
||||||
|
#define OLED_CMD_COM_SCAN_DIRECTION_NORMAL 0xC0 // Normal COM scan direction. (COM0 -> COM[MUX-1] | BOTTOM TO TOP)
|
||||||
|
#define OLED_CMD_COM_SCAN_DIRECTION_REMAP 0xC8 // Remapped/Flipped COM scan direction. (COM[MUX-1] -> COM0 | TOP TO BOTTOM)
|
||||||
|
#define OLED_CMD_SET_COM_PIN_HARDWARE_MAP 0xDA // Double byte command to set COM pins hardware configuration. [0x02 | DISABLE COM REMAP - SEQUENTIAL COM, 0x12 | DISABLE COM REMAP - ALTERNATIVE COM, 0x22 | ENABLE COM REMAP - SEQUENTIAL COM, 0x32 | ENABLE COM REMAP - ALTERNATIVE COM] (RESET: 0x12)
|
||||||
|
#define OLED_CMD_SET_VERT_DISPLAY_OFFSET 0xD3 // Double byte command to set vertical display offset by COM. [0x00 - 0x3F] (RESET: 0x00)
|
||||||
|
|
||||||
|
/* TIMING & DRIVING SCHEME SETTING */
|
||||||
|
#define OLED_CMD_SET_DISPLAY_CLK_DIVIDE 0xD5 // Double byte command to set display clock oscillator frequency and divide ratio. [0x00 - 0x0F << 4 & 0x00->0x0F] ((OSCFREQ) << 4 | (DIVIDE RATIO)) (RESET: 0x80)
|
||||||
|
#define OLED_CMD_SET_PRECHARGE_PERIOD 0xD9 // Double byte command to set pre-charge period. [0x00 - 0x0F << 4 & 0x00 - 0x0F](PHASE2/DOTS << 4 | PHASE1/DOTS) (RESET: 0x22)
|
||||||
|
#define OLED_CMD_SET_VCOMH_DESELECT_LEVEL 0xDB // Double byte command to set VCOMH deselect level regulator output. [0X00 | 0.65, 0X20 | 0.77, 0X30 | 0.83 VCC] (RESET: 0x20)
|
||||||
|
|
||||||
|
/* CHARGE PUMP */
|
||||||
|
#define OLED_CMD_SET_CHARGE_PUMP 0x8D // Double byte command to set charge pump. [0x10 | OFF, 0x14 | ON] (RESET: 0x14)
|
||||||
|
|
||||||
|
/* ADDITIONAL COMMANDS */
|
||||||
|
#define OLED_CMD_NO_OPERATION 0xE3 // NO OPERATION COMMAND
|
||||||
|
|
||||||
|
/* FONT DEFINED FROM ASCII 32 -> 127 */
|
||||||
|
static const uint8_t font8x8[256][8] = {
|
||||||
|
/* ASCII CONTROL CHARACTERS */
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 000 -> 0x00 [NULL]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 001 -> 0x01 [SOH]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 002 -> 0x02 [STX]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 003 -> 0x03 [ETX]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 004 -> 0x04 [EOT]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 005 -> 0x05 [ENQ]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 006 -> 0x06 [ACK]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 007 -> 0x07 [BEL]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 008 -> 0x08 [BS]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 009 -> 0x09 [TAB]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 010 -> 0x0A [LF]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 011 -> 0x0B [VT]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 012 -> 0x0C [FF]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 013 -> 0x0D [CR]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 014 -> 0x0E [SO]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 015 -> 0x0F [SI]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 016 -> 0x10 [DLE]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 017 -> 0x11 [DC1]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 018 -> 0x12 [DC2]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 019 -> 0x13 [DC3]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 020 -> 0x14 [DC4]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 021 -> 0x15 [NAK]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 022 -> 0x16 [SYN]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 023 -> 0x17 [ETB]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 024 -> 0x18 [CAN]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 025 -> 0x19 [EM]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 026 -> 0x1A [SUB]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 027 -> 0x1B [ESC]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 028 -> 0x1C [FS]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 029 -> 0x1D [GS]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 030 -> 0x1E [RS]
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 031 -> 0x1F [US]
|
||||||
|
/* ASCII PRINTABLE CHARACTERS */
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 032 -> 0x20 ( )
|
||||||
|
{0x00, 0x00, 0x00, 0x5F, 0x5F, 0x00, 0x00, 0x00}, // 033 -> 0x21 (!)
|
||||||
|
{0x00, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x00}, // 034 -> 0x22 (")
|
||||||
|
{0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14, 0x00}, // 035 -> 0x23 (#)
|
||||||
|
{0x00, 0x24, 0x2A, 0x7F, 0x7F, 0x2A, 0x12, 0x00}, // 036 -> 0x24 ($)
|
||||||
|
{0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, 0x00}, // 037 -> 0x25 (%)
|
||||||
|
{0x30, 0x7A, 0x4F, 0x5D, 0x37, 0x7A, 0x48, 0x00}, // 038 -> 0x26 (&)
|
||||||
|
{0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00}, // 039 -> 0x27 (')
|
||||||
|
{0x00, 0x00, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00}, // 040 -> 0x28 (()
|
||||||
|
{0x00, 0x00, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00}, // 041 -> 0x29 ())
|
||||||
|
{0x08, 0x2A, 0x3E, 0x1C, 0x1C, 0x3E, 0x2A, 0x08}, // 042 -> 0x2a (*)
|
||||||
|
{0x00, 0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00}, // 043 -> 0x2b (+)
|
||||||
|
{0x00, 0x00, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00}, // 044 -> 0x2c (,)
|
||||||
|
{0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, // 045 -> 0x2d (-)
|
||||||
|
{0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00}, // 046 -> 0x2e (.)
|
||||||
|
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // 047 -> 0x2f (/)
|
||||||
|
{0x3E, 0x7F, 0x51, 0x49, 0x45, 0x7F, 0x3E, 0x00}, // 048 -> 0x30 (0)
|
||||||
|
{0x00, 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00}, // 049 -> 0x31 (1)
|
||||||
|
{0x00, 0x72, 0x7B, 0x49, 0x49, 0x6F, 0x66, 0x00}, // 050 -> 0x32 (2)
|
||||||
|
{0x00, 0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00}, // 051 -> 0x33 (3)
|
||||||
|
{0x18, 0x1C, 0x16, 0x53, 0x7F, 0x7F, 0x50, 0x00}, // 052 -> 0x34 (4)
|
||||||
|
{0x00, 0x2F, 0x6F, 0x49, 0x49, 0x79, 0x33, 0x00}, // 053 -> 0x35 (5)
|
||||||
|
{0x00, 0x3E, 0x7F, 0x49, 0x49, 0x7B, 0x32, 0x00}, // 054 -> 0x36 (6)
|
||||||
|
{0x00, 0x03, 0x03, 0x71, 0x79, 0x0F, 0x07, 0x00}, // 055 -> 0x37 (7)
|
||||||
|
{0x00, 0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}, // 056 -> 0x38 (8)
|
||||||
|
{0x00, 0x26, 0x6F, 0x49, 0x49, 0x7F, 0x3E, 0x00}, // 057 -> 0x39 (9)
|
||||||
|
{0x00, 0x00, 0x00, 0x6C, 0x6C, 0x00, 0x00, 0x00}, // 058 -> 0x3a (:)
|
||||||
|
{0x00, 0x00, 0x80, 0xEC, 0x6C, 0x00, 0x00, 0x00}, // 059 -> 0x3b (;)
|
||||||
|
{0x00, 0x08, 0x1C, 0x36, 0x63, 0x41, 0x00, 0x00}, // 060 -> 0x3c (<)
|
||||||
|
{0x00, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00}, // 061 -> 0x3d (=)
|
||||||
|
{0x00, 0x41, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00}, // 062 -> 0x3e (>)
|
||||||
|
{0x00, 0x06, 0x07, 0x51, 0x59, 0x0F, 0x06, 0x00}, // 063 -> 0x3f (?)
|
||||||
|
{0x3E, 0x7F, 0x41, 0x5D, 0x5D, 0x5F, 0x1E, 0x00}, // 064 -> 0x40 (@)
|
||||||
|
{0x00, 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00}, // 065 -> 0x41 (A)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}, // 066 -> 0x42 (B)
|
||||||
|
{0x1C, 0x3E, 0x63, 0x41, 0x41, 0x63, 0x22, 0x00}, // 067 -> 0x43 (C)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x41, 0x63, 0x3E, 0x1C, 0x00}, // 068 -> 0x44 (D)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x49, 0x5D, 0x41, 0x63, 0x00}, // 069 -> 0x45 (E)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x49, 0x1D, 0x01, 0x03, 0x00}, // 070 -> 0x46 (F)
|
||||||
|
{0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 071 -> 0x47 (G)
|
||||||
|
{0x00, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00}, // 072 -> 0x48 (H)
|
||||||
|
{0x00, 0x41, 0x41, 0x7F, 0x7F, 0x41, 0x41, 0x00}, // 073 -> 0x49 (I)
|
||||||
|
{0x30, 0x70, 0x40, 0x41, 0x7F, 0x3F, 0x01, 0x00}, // 074 -> 0x4a (J)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x08, 0x1C, 0x77, 0x63, 0x00}, // 075 -> 0x4b (K)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x41, 0x40, 0x60, 0x70, 0x00}, // 076 -> 0x4c (L)
|
||||||
|
{0x7F, 0x7F, 0x0E, 0x1C, 0x0E, 0x7F, 0x7F, 0x00}, // 077 -> 0x4d (M)
|
||||||
|
{0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00}, // 078 -> 0x4e (N)
|
||||||
|
{0x1C, 0x3E, 0x63, 0x41, 0x63, 0x3E, 0x1C, 0x00}, // 079 -> 0x4f (O)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x49, 0x09, 0x0F, 0x06, 0x00}, // 080 -> 0x50 (P)
|
||||||
|
{0x3C, 0x7E, 0x43, 0x51, 0x33, 0x6E, 0x5C, 0x00}, // 081 -> 0x51 (Q)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 082 -> 0x52 (R)
|
||||||
|
{0x00, 0x26, 0x6F, 0x49, 0x49, 0x7B, 0x32, 0x00}, // 083 -> 0x53 (S)
|
||||||
|
{0x00, 0x03, 0x41, 0x7F, 0x7F, 0x41, 0x03, 0x00}, // 084 -> 0x54 (T)
|
||||||
|
{0x00, 0x3F, 0x7F, 0x40, 0x40, 0x7F, 0x3F, 0x00}, // 085 -> 0x55 (U)
|
||||||
|
{0x00, 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00}, // 086 -> 0x56 (V)
|
||||||
|
{0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F, 0x00}, // 087 -> 0x57 (W)
|
||||||
|
{0x61, 0x73, 0x1E, 0x0C, 0x1E, 0x73, 0x61, 0x00}, // 088 -> 0x58 (X)
|
||||||
|
{0x00, 0x07, 0x4F, 0x78, 0x78, 0x4F, 0x07, 0x00}, // 089 -> 0x59 (Y)
|
||||||
|
{0x47, 0x63, 0x71, 0x59, 0x4D, 0x67, 0x73, 0x00}, // 090 -> 0x5a (Z)
|
||||||
|
{0x00, 0x00, 0x7F, 0x7F, 0x41, 0x41, 0x00, 0x00}, // 091 -> 0x5b ([)
|
||||||
|
{0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00}, // 092 -> 0x5c (\)
|
||||||
|
{0x00, 0x00, 0x41, 0x41, 0x7F, 0x7F, 0x00, 0x00}, // 093 -> 0x5d (])
|
||||||
|
{0x08, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x08, 0x00}, // 094 -> 0x5e (^)
|
||||||
|
{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, // 095 -> 0x5f (_)
|
||||||
|
{0x00, 0x00, 0x01, 0x03, 0x06, 0x04, 0x00, 0x00}, // 096 -> 0x60 (`)
|
||||||
|
{0x20, 0x74, 0x54, 0x54, 0x3C, 0x78, 0x40, 0x00}, // 097 -> 0x61 (a)
|
||||||
|
{0x41, 0x7F, 0x3F, 0x44, 0x44, 0x7C, 0x38, 0x00}, // 098 -> 0x62 (b)
|
||||||
|
{0x00, 0x38, 0x7C, 0x44, 0x44, 0x6C, 0x28, 0x00}, // 099 -> 0x63 (c)
|
||||||
|
{0x38, 0x7C, 0x44, 0x45, 0x3F, 0x7F, 0x40, 0x00}, // 100 -> 0x64 (d)
|
||||||
|
{0x00, 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00}, // 101 -> 0x65 (e)
|
||||||
|
{0x00, 0x48, 0x7E, 0x7F, 0x49, 0x03, 0x02, 0x00}, // 102 -> 0x66 (f)
|
||||||
|
{0x00, 0x98, 0xBC, 0xA4, 0xA4, 0xFC, 0x7C, 0x00}, // 103 -> 0x67 (g)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x08, 0x04, 0x7C, 0x78, 0x00}, // 104 -> 0x68 (h)
|
||||||
|
{0x00, 0x00, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00}, // 105 -> 0x69 (i)
|
||||||
|
{0x00, 0x60, 0xE0, 0x80, 0x84, 0xFD, 0x7D, 0x00}, // 106 -> 0x6a (j)
|
||||||
|
{0x41, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00}, // 107 -> 0x6b (k)
|
||||||
|
{0x00, 0x00, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00}, // 108 -> 0x6c (l)
|
||||||
|
{0x78, 0x7C, 0x0C, 0x38, 0x0C, 0x7C, 0x78, 0x00}, // 109 -> 0x6d (m)
|
||||||
|
{0x04, 0x7C, 0x78, 0x04, 0x04, 0x7C, 0x78, 0x00}, // 110 -> 0x6e (n)
|
||||||
|
{0x00, 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00}, // 111 -> 0x6f (o)
|
||||||
|
{0x84, 0xFC, 0xF8, 0xA4, 0x24, 0x3C, 0x18, 0x00}, // 112 -> 0x70 (p)
|
||||||
|
{0x18, 0x3C, 0x24, 0xA4, 0xF8, 0xFC, 0x84, 0x00}, // 113 -> 0x71 (q)
|
||||||
|
{0x44, 0x7C, 0x78, 0x4C, 0x04, 0x0C, 0x08, 0x00}, // 114 -> 0x72 (r)
|
||||||
|
{0x00, 0x48, 0x5C, 0x54, 0x54, 0x74, 0x20, 0x00}, // 115 -> 0x73 (s)
|
||||||
|
{0x00, 0x04, 0x3F, 0x7F, 0x44, 0x64, 0x20, 0x00}, // 116 -> 0x74 (t)
|
||||||
|
{0x00, 0x3C, 0x7C, 0x40, 0x40, 0x7C, 0x7C, 0x00}, // 117 -> 0x75 (u)
|
||||||
|
{0x00, 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00}, // 118 -> 0x76 (v)
|
||||||
|
{0x3C, 0x7C, 0x60, 0x38, 0x60, 0x7C, 0x3C, 0x00}, // 119 -> 0x77 (w)
|
||||||
|
{0x44, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0x44, 0x00}, // 120 -> 0x78 (x)
|
||||||
|
{0x00, 0x9C, 0xBC, 0xA0, 0xA0, 0xFC, 0x7C, 0x00}, // 121 -> 0x79 (y)
|
||||||
|
{0x00, 0x4C, 0x64, 0x74, 0x5C, 0x4C, 0x64, 0x00}, // 122 -> 0x7a (z)
|
||||||
|
{0x00, 0x08, 0x08, 0x3E, 0x77, 0x41, 0x41, 0x00}, // 123 -> 0x7b ({)
|
||||||
|
{0x00, 0x00, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00}, // 124 -> 0x7c (|)
|
||||||
|
{0x00, 0x41, 0x41, 0x77, 0x3E, 0x08, 0x08, 0x00}, // 125 -> 0x7d (})
|
||||||
|
{0x10, 0x18, 0x08, 0x18, 0x10, 0x18, 0x08, 0x00}, // 126 -> 0x7e (~)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 127 -> 0x7F [DEL]
|
||||||
|
/* EXTENDED ASCII CHARACTERS */
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 128 -> 0x80 (Ç)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 129 -> 0x81 (ü)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 130 -> 0x82 (é)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 131 -> 0x83 (â)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 132 -> 0x84 (ä)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 133 -> 0x85 (à)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 134 -> 0x86 (å)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 135 -> 0x87 (ç)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 136 -> 0x88 (ê)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 137 -> 0x89 (ë)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 138 -> 0x8A (è)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 139 -> 0x8B (ï)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 140 -> 0x8C (î)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 141 -> 0x8D (ì)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 142 -> 0x8E (Ä)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 143 -> 0x8F (Å)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 144 -> 0x90 (É)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 145 -> 0x91 (æ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 146 -> 0x92 (Æ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 147 -> 0x93 (ô)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 148 -> 0x94 (ö)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 149 -> 0x95 (ò)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 150 -> 0x96 (û)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 151 -> 0x97 (ù)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 152 -> 0x98 (ÿ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 153 -> 0x99 (Ö)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 154 -> 0x9A (Ü)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 155 -> 0x9B (ø)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 156 -> 0x9C (£)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 157 -> 0x9D (Ø)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 158 -> 0x9E (×)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 159 -> 0x9F (ƒ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 160 -> 0xA0 (á)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 161 -> 0xA1 (í)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 162 -> 0xA2 (ó)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 163 -> 0xA3 (ú)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 164 -> 0xA4 (ñ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 165 -> 0xA5 (Ñ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 166 -> 0xA6 (ª)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 167 -> 0xA7 (º)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 168 -> 0xA8 (¿)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 169 -> 0xA9 (®)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 170 -> 0xAA (¬)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 171 -> 0xAB (½)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 172 -> 0xAC (¼)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 173 -> 0xAD (¡)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 174 -> 0xAE («)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 175 -> 0xAF (»)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 176 -> 0xB0 (░)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 177 -> 0xB1 (▒)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 178 -> 0xB2 (▓)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 179 -> 0xB3 (│)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 180 -> 0xB4 (┤)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 181 -> 0xB5 (Á)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 182 -> 0xB6 (Â)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 183 -> 0xB7 (À)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 184 -> 0xB8 (©)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 185 -> 0xB9 (╣)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 186 -> 0xBA (║)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 187 -> 0xBB (╗)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 188 -> 0xBC (╝)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 189 -> 0xBD (¢)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 190 -> 0xBE (¥)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 191 -> 0xBF (┐)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 192 -> 0xC0 (└)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 193 -> 0xC1 (┴)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 194 -> 0xC2 (┬)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 195 -> 0xC3 (├)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 196 -> 0xC4 (─)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 197 -> 0xC5 (┼)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 198 -> 0xC6 (ã)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 199 -> 0xC7 (Ã)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 200 -> 0xC8 (╚)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 201 -> 0xC9 (╔)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 202 -> 0xCA (╩)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 203 -> 0xCB (╦)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 204 -> 0xCC (╠)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 205 -> 0xCD (═)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 206 -> 0xCE (╬)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 207 -> 0xCF (¤)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 208 -> 0xD0 (ð)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 209 -> 0xD1 (Ð)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 210 -> 0xD2 (Ê)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 211 -> 0xD3 (Ë)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 212 -> 0xD4 (È)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 213 -> 0xD5 (ı)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 214 -> 0xD6 (Í)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 215 -> 0xD7 (Î)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 216 -> 0xD8 (Ï)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 217 -> 0xD9 (┘)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 218 -> 0xDA (┌)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 219 -> 0xDB (█)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 220 -> 0xDC (▄)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 221 -> 0xDD (¦)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 222 -> 0xDE (Ì)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 223 -> 0xDF (▀)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 224 -> 0xE0 (Ó)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 225 -> 0xE1 (ß)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 226 -> 0xE2 (Ô)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 227 -> 0xE3 (Ò)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 228 -> 0xE4 (õ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 229 -> 0xE5 (Õ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 230 -> 0xE6 (µ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 231 -> 0xE7 (þ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 232 -> 0xE8 (Þ)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 233 -> 0xE9 (Ú)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 234 -> 0xEA (Û)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 235 -> 0xEB (Ù)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 236 -> 0xEC (ý)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 237 -> 0xED (Ý)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 238 -> 0xEE (¯)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 239 -> 0xEF (´)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 240 -> 0xF0 (≡)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 241 -> 0xF1 (±)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 242 -> 0xF2 (‗)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 243 -> 0xF3 (¾)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 244 -> 0xF4 (¶)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 245 -> 0xF5 (§)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 246 -> 0xF6 (÷)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 247 -> 0xF7 (¸)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 248 -> 0xF8 (°)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 249 -> 0xF9 (¨)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 250 -> 0xFA (·)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 251 -> 0xFB (¹)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 252 -> 0xFC (³)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 253 -> 0xFD (²)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 254 -> 0xFE (■)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // 255 -> 0xFF ( )
|
||||||
|
};
|
83
random-info.org
Normal file
83
random-info.org
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
*** Which is the one I care about? Like which is the one I would use to know if Southeast Asia's burning season is getting bad?
|
||||||
|
|
||||||
|
You want *=pm2_5_atm=* - the PM2.5 concentration under atmospheric environment.
|
||||||
|
|
||||||
|
PM2.5 (particles ≤ 2.5 micrometers) is the key metric for air quality and health because these particles are small enough to penetrate deep into your lungs and bloodstream. During Southeast Asia's burning season, this is what gets dangerously high.
|
||||||
|
|
||||||
|
The "atmospheric environment" reading (=_atm=) is more accurate for real-world conditions compared to the "CF=1 standard particle" (=_cf1=) reading, which uses a correction factor.
|
||||||
|
|
||||||
|
*Reference levels:*
|
||||||
|
- 0-12 μg/m³: Good
|
||||||
|
- 13-35 μg/m³: Moderate
|
||||||
|
- 36-55 μg/m³: Unhealthy for sensitive groups
|
||||||
|
- 56-150 μg/m³: Unhealthy
|
||||||
|
- 151-250 μg/m³: Very unhealthy
|
||||||
|
- 251+ μg/m³: Hazardous
|
||||||
|
|
||||||
|
During bad haze episodes, you'll see =pm2_5_atm= values well into the hundreds or even 300+ μg/m³.
|
||||||
|
|
||||||
|
*** Ok, so why would I want to know about 1.0 and 10.0? What would those be useful for? This is for a home sensor project I'm building
|
||||||
|
|
||||||
|
Great question! Here's why you'd want the other PM readings for a home sensor:
|
||||||
|
|
||||||
|
*PM1.0 (=pm1_0_atm=):*
|
||||||
|
- *Ultra-fine particles* - these penetrate deepest into lungs and bloodstream
|
||||||
|
- *Indoor sources*: cooking (especially frying/grilling), candles, incense, vaping
|
||||||
|
- *Early warning*: Often spikes before PM2.5 gets bad
|
||||||
|
- *Health*: Most concerning for cardiovascular effects
|
||||||
|
|
||||||
|
*PM10 (=pm10_atm=):*
|
||||||
|
- *Coarse particles* - dust, pollen, mold spores
|
||||||
|
- *Indoor sources*: vacuuming, pets, dusty activities, open windows on windy days
|
||||||
|
- *Allergies*: Key metric for people with dust/pollen allergies
|
||||||
|
- *HVAC effectiveness*: Shows if your air filters are working
|
||||||
|
|
||||||
|
*For your home sensor project:*
|
||||||
|
- *PM1.0*: "Is my cooking/candle affecting air quality?"
|
||||||
|
- *PM2.5*: "Is the outdoor haze/pollution getting inside?"
|
||||||
|
- *PM10*: "Should I run the air purifier due to dust/allergens?"
|
||||||
|
|
||||||
|
You could set different alert thresholds:
|
||||||
|
#+begin_src c
|
||||||
|
if (data.pm1_0_atm > 15) printf("Cooking/indoor pollution detected\n");
|
||||||
|
if (data.pm2_5_atm > 35) printf("Outdoor pollution - close windows!\n");
|
||||||
|
if (data.pm10_atm > 50) printf("High dust/allergens - run air purifier\n");
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
All three together give you a complete picture of your indoor air quality!
|
||||||
|
|
||||||
|
*** How does AQI work though? Seems like the AQI score is different from the PM 2.5 reading
|
||||||
|
|
||||||
|
Yes! AQI (Air Quality Index) is a /calculated score/ that converts raw PM2.5 readings into a standardized 0-500 scale that's easier for people to understand.
|
||||||
|
|
||||||
|
*The conversion uses breakpoints:*
|
||||||
|
|
||||||
|
| AQI Range | PM2.5 (μg/m³) | Category |
|
||||||
|
|-----------|---------------|----------|
|
||||||
|
| 0-50 | 0.0-12.0 | Good |
|
||||||
|
| 51-100 | 12.1-35.4 | Moderate |
|
||||||
|
| 101-150 | 35.5-55.4 | Unhealthy for Sensitive |
|
||||||
|
| 151-200 | 55.5-150.4 | Unhealthy |
|
||||||
|
| 201-300 | 150.5-250.4 | Very Unhealthy |
|
||||||
|
| 301-500 | 250.5-500.4 | Hazardous |
|
||||||
|
|
||||||
|
*Example:*
|
||||||
|
- PM2.5 = 25 μg/m³ → AQI ≈ 79 (Moderate)
|
||||||
|
- PM2.5 = 100 μg/m³ → AQI ≈ 170 (Unhealthy)
|
||||||
|
|
||||||
|
*For your home sensor:*
|
||||||
|
#+begin_src c
|
||||||
|
int pm25_to_aqi(float pm25) {
|
||||||
|
if (pm25 <= 12.0) return (50.0/12.0) * pm25;
|
||||||
|
if (pm25 <= 35.4) return 51 + ((49.0/23.4) * (pm25 - 12.1));
|
||||||
|
if (pm25 <= 55.4) return 101 + ((49.0/19.9) * (pm25 - 35.5));
|
||||||
|
// ... continue for higher ranges
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*Why AQI is useful:*
|
||||||
|
- Color-coded (Green=Good, Red=Unhealthy)
|
||||||
|
- Same scale regardless of pollutant type
|
||||||
|
- Easy for non-technical users: "AQI 150 = stay indoors"
|
||||||
|
|
||||||
|
The raw PM2.5 number is more precise for data logging, but AQI is better for user alerts!
|
Loading…
x
Reference in New Issue
Block a user