- temp sensor confirmed responsive to die heating (27.6 -> 28.6 C under load) - document quantization/calibration: readout sticks to ~1C steps ending in .6 (inherent to ESP32-H2 internal sensor, not a bug) - keep CPU_STRESS_TEST as a debug toggle (default off)
217 lines
7.6 KiB
C
217 lines
7.6 KiB
C
/**
|
|
* temphummon — ESP32-H2 + 1.9" Segment E-Paper (IST7134 via I²C)
|
|
* Shows internal CPU temperature on top line + fixed humidity on bottom.
|
|
*
|
|
* Frame layout (empirically verified, see PROJECT_MEMORY.md):
|
|
* f[0] = leading "1" glyph (bits 0-4)
|
|
* f[1],f[2] = top-line tens digit
|
|
* f[3],f[4] = top-line ones digit
|
|
* f[5],f[6] = bottom-line tens digit
|
|
* f[7],f[8] = bottom-line ones digit
|
|
* f[9],f[10] = bottom-line tenths digit
|
|
* f[11],f[12] = top-line tenths digit
|
|
* f[13] = °C (0x05) / °F (0x06) letter
|
|
*
|
|
* DECIMAL POINT / % (the hard-won bits!):
|
|
* f[4] bit5 = top decimal (after top ones) -> "188.8"
|
|
* f[8] bit5 = bottom decimal (after bottom ones) -> "88.8"
|
|
* f[10] bit5 = percent sign (after bottom tenths) -> "88.8%"
|
|
*
|
|
* Wiring: GPIO4→SDA, GPIO5→SCL, GPIO10→RST, GPIO11→BUSY, 3V3→VCC, GND→GND
|
|
*/
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/i2c_master.h"
|
|
#include "driver/gpio.h"
|
|
#include "driver/temperature_sensor.h"
|
|
#include "esp_log.h"
|
|
|
|
#define PIN_SDA GPIO_NUM_4
|
|
#define PIN_SCL GPIO_NUM_5
|
|
#define PIN_RST GPIO_NUM_10
|
|
#define PIN_BUSY GPIO_NUM_11
|
|
|
|
static i2c_master_bus_handle_t bus;
|
|
static i2c_master_dev_handle_t cmd_dev, dat_dev;
|
|
static temperature_sensor_handle_t tsens;
|
|
|
|
/* Digit glyphs from Waveshare reference (byte pair per digit). */
|
|
static const uint8_t DIG[10][2] = {
|
|
{0xbf,0x1f},{0x1f,0x00},{0xfd,0x17},{0xf5,0x1f},{0x47,0x1f},
|
|
{0xf7,0x1d},{0xff,0x1d},{0x21,0x1f},{0xff,0x1f},{0xf7,0x1f},
|
|
};
|
|
static const uint8_t DIG_L[10][2] = {
|
|
{0xbf,0x1f},{0x1f,0x00},{0xfd,0x37},{0xf5,0x1f},{0x47,0x1f},
|
|
{0xf7,0x1d},{0xff,0x3d},{0x21,0x1f},{0xff,0x3f},{0xf7,0x1f},
|
|
};
|
|
|
|
/* Decimal point & % bits (0x20) */
|
|
#define BIT_DP_TOP 0x20 /* f[4] */
|
|
#define BIT_DP_BOT 0x20 /* f[8] */
|
|
#define BIT_PERCENT 0x20 /* f[10] */
|
|
|
|
/* Status icons in f[13] (empirically verified, see PROJECT_MEMORY.md) */
|
|
#define ICON_DEGC 0x05 /* °C (degree + C) */
|
|
#define ICON_DEGF 0x06 /* °F */
|
|
#define ICON_BT 0x08 /* Bluetooth */
|
|
#define ICON_BAT 0x10 /* battery/power */
|
|
#define ICON_BT_BAT 0x1D /* °C + Bluetooth + battery */
|
|
|
|
/* Debug: burn CPU for a while to stress the die and verify the internal
|
|
* temp sensor responds. Set to 1 to spin in a busy loop for STRESS_MS
|
|
* after each read (should raise/perturb the die temp).
|
|
* Verified: die rises 27.6 -> 28.6°C under load (sensor is live). */
|
|
#define CPU_STRESS_TEST 0
|
|
#define STRESS_MS 3000
|
|
|
|
static void epd_wait_busy(void) {
|
|
/* BUSY_N: LOW = busy, HIGH = ready */
|
|
while (gpio_get_level(PIN_BUSY) == 0) vTaskDelay(1);
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
}
|
|
|
|
static void epd_lut_GC(void) {
|
|
uint8_t lut[] = {0x82,0x20,0x00,0xA0,0x80,0x40,0x63};
|
|
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
|
}
|
|
static void epd_lut_DU_WB(void) {
|
|
uint8_t lut[] = {0x82,0x80,0x00,0xC0,0x80,0x80,0x62};
|
|
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
static void epd_temperature(void) {
|
|
uint8_t a[] = {0x7E,0x81,0xB4};
|
|
i2c_master_transmit(cmd_dev, a, sizeof(a), pdMS_TO_TICKS(100));
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
uint8_t b[] = {0xE7,0x0E};
|
|
i2c_master_transmit(cmd_dev, b, sizeof(b), pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
static void epd_write(const uint8_t *frame, bool clear) {
|
|
uint8_t w[] = {0xAC,0x2B,0x40,0xA9,0xA8};
|
|
i2c_master_transmit(cmd_dev, w, 5, pdMS_TO_TICKS(100));
|
|
i2c_master_transmit(dat_dev, frame, 15, pdMS_TO_TICKS(100));
|
|
uint8_t e = clear ? 0x03 : 0x00;
|
|
i2c_master_transmit(dat_dev, &e, 1, pdMS_TO_TICKS(100));
|
|
uint8_t d[] = {0xAB,0xAA,0xAF};
|
|
i2c_master_transmit(cmd_dev, d, 3, pdMS_TO_TICKS(100));
|
|
epd_wait_busy();
|
|
uint8_t s[] = {0xAE,0x28,0xAD};
|
|
i2c_master_transmit(cmd_dev, s, 3, pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
static void epd_init(void) {
|
|
// Reset
|
|
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
|
gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(20));
|
|
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
|
// Power on
|
|
uint8_t p[] = {0x2B,0xA7,0xE0};
|
|
i2c_master_transmit(cmd_dev, p, 3, pdMS_TO_TICKS(100));
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
epd_temperature();
|
|
}
|
|
|
|
/* Render a value (0-199.9) to the top line: [lead1 ][tens][ones].[tenths] */
|
|
static void render_temp(uint8_t *f, float t) {
|
|
if (t < 0) t = 0;
|
|
if (t > 199.9) t = 199.9;
|
|
|
|
int hi = (int)t / 100;
|
|
int te = ((int)t / 10) % 10;
|
|
int on = (int)t % 10;
|
|
int tn = (int)(t * 10) % 10;
|
|
|
|
// tens (top)
|
|
if (hi || te) { f[1] = DIG[te][0]; f[2] = DIG[te][1]; }
|
|
// ones (top) + DECIMAL (f[4] bit5)
|
|
f[3] = DIG[on][0];
|
|
f[4] = DIG[on][1] | BIT_DP_TOP;
|
|
// tenths (top)
|
|
f[11] = DIG_L[tn][0];
|
|
f[12] = DIG_L[tn][1];
|
|
// leading "1"
|
|
if (hi) {
|
|
f[0] |= 0x1f; /* left column of the leading "1" glyph */
|
|
}
|
|
// °C
|
|
f[13] = ICON_DEGC;
|
|
}
|
|
|
|
/* Render a value (0-99.9) to the bottom line: [tens][ones].[tenths]% */
|
|
static void render_humidity(uint8_t *f, float h) {
|
|
if (h < 0) h = 0;
|
|
if (h > 99.9) h = 99.9;
|
|
|
|
int te = ((int)h / 10) % 10;
|
|
int on = (int)h % 10;
|
|
int tn = (int)(h * 10) % 10;
|
|
|
|
// tens (bottom)
|
|
if (te) { f[5] = DIG[te][0]; f[6] = DIG[te][1]; }
|
|
// ones (bottom) + DECIMAL (f[8] bit5)
|
|
f[7] = DIG[on][0];
|
|
f[8] = DIG[on][1] | BIT_DP_BOT;
|
|
// tenths (bottom) + PERCENT (f[10] bit5)
|
|
f[9] = DIG_L[tn][0];
|
|
f[10] = DIG_L[tn][1] | BIT_PERCENT;
|
|
}
|
|
|
|
void app_main(void) {
|
|
// Init I²C
|
|
i2c_master_bus_config_t bc = {.i2c_port=-1,.sda_io_num=PIN_SDA,.scl_io_num=PIN_SCL,
|
|
.clk_source=I2C_CLK_SRC_DEFAULT,.glitch_ignore_cnt=7,.flags.enable_internal_pullup=1};
|
|
ESP_ERROR_CHECK(i2c_new_master_bus(&bc, &bus));
|
|
i2c_device_config_t cc = {.dev_addr_length=I2C_ADDR_BIT_LEN_7,.device_address=0x3C,.scl_speed_hz=100000};
|
|
i2c_device_config_t dc = {.dev_addr_length=I2C_ADDR_BIT_LEN_7,.device_address=0x3D,.scl_speed_hz=100000};
|
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &cc, &cmd_dev));
|
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &dc, &dat_dev));
|
|
|
|
// Init GPIO
|
|
gpio_config_t rc = {.pin_bit_mask=(1ULL<<PIN_RST),.mode=GPIO_MODE_OUTPUT,
|
|
.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,.intr_type=GPIO_INTR_DISABLE};
|
|
gpio_config(&rc); gpio_set_level(PIN_RST, 1);
|
|
gpio_config_t bc2 = {.pin_bit_mask=(1ULL<<PIN_BUSY),.mode=GPIO_MODE_INPUT,
|
|
.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,.intr_type=GPIO_INTR_DISABLE};
|
|
gpio_config(&bc2);
|
|
|
|
// Init temp sensor
|
|
temperature_sensor_config_t tc = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 60);
|
|
ESP_ERROR_CHECK(temperature_sensor_install(&tc, &tsens));
|
|
ESP_ERROR_CHECK(temperature_sensor_enable(tsens));
|
|
|
|
// Init display
|
|
epd_init();
|
|
|
|
// Clear
|
|
uint8_t off[15]; memset(off, 0, 15);
|
|
epd_lut_GC();
|
|
epd_write(off, 0); vTaskDelay(pdMS_TO_TICKS(500));
|
|
|
|
float t;
|
|
while (1) {
|
|
ESP_ERROR_CHECK(temperature_sensor_get_celsius(tsens, &t));
|
|
ESP_LOGI("temphummon", "T: %.1f°C", (double)t);
|
|
|
|
#if CPU_STRESS_TEST
|
|
/* Burn CPU hard for STRESS_MS to heat the die. */
|
|
uint32_t ticks = pdMS_TO_TICKS(STRESS_MS);
|
|
uint32_t start = xTaskGetTickCount();
|
|
volatile uint32_t sink = 1;
|
|
while ((xTaskGetTickCount() - start) < ticks) {
|
|
for (uint32_t i = 0; i < 100000; i++) sink = sink * 131 + i; /* ~busy */
|
|
}
|
|
(void)sink;
|
|
#endif
|
|
|
|
uint8_t f[15]; memset(f, 0, 15);
|
|
render_temp(f, t);
|
|
render_humidity(f, 88.8f); /* fixed sample until a humidity sensor is wired */
|
|
|
|
epd_lut_DU_WB();
|
|
epd_write(f, 0);
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
}
|
|
}
|