temphummon/firmware/main/bitprobe.c
temphummon dev ebbeffa432 Add bit-probe results, decimal point/percentage bit map, and working temperature+humidity display
- bitprobe.c: interactive single-bit probe that discovered the display glyph
  layout and the decimal point / percent sign bit positions
- temphummon.c: render top line '188.8°C' and bottom '88.8%' using the
  verified decimal points (f[4], f[8] bit5) and percent sign (f[10] bit5)
- PROJECT_MEMORY.md: document full frame layout + empirical bit map
- README: add wiring table and project memory pointer
- add Waveshare reference zip and flake.lock
2026-09-24 17:33:12 +00:00

226 lines
7.2 KiB
C

/**
* dpfinder — ESP32-H2 Zero + 1.9" Segment E-Paper (IST7134 via I²C)
*
* Targeted decimal-point / % tester, guided by the manual:
* "The decimal point and % are the 5th place in the 4th, 8th, and 10th
* positions respectively." (positions = vertical columns, L→R, T→B)
*
* We don't trust our model for exactly which BYTE is column 4/8/10, so we
* just test bit-5 (0x20) at a handful of candidate bytes on a clear display
* (a few "8" digits for context) and the human reports which shows a decimal
* point or %.
*
* Wiring (GPIO4→SDA, GPIO5→SCL, GPIO10→RST, GPIO11→BUSY, 3V3→VCC, GND→GND)
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_timer.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
#define PIN_BOOT GPIO_NUM_9
#define FRAME_LEN 15
#define ADVANCE_MODE 1
#define AUTO_HOLD_MS 3000
#define BOOT_TIMEOUT_MS 0
/* Digit "1" = minimal glyph (0x1f sets bits 0-4 only; bit 5 stays DARK so
* an added bit-5 decimal point is clearly visible). */
static const uint8_t DIG1[2] = {0x1f, 0x00};
/* The 6 digit positions (byte pair). */
static const uint8_t POS[6][2] = {
{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12},
};
static i2c_master_bus_handle_t bus;
static i2c_master_dev_handle_t cmd_dev, dat_dev;
static void epd_reset(void)
{
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));
}
static void epd_wait_busy(void)
{
while (gpio_get_level(PIN_BUSY) == 0) vTaskDelay(pdMS_TO_TICKS(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_init(void)
{
epd_reset();
vTaskDelay(pdMS_TO_TICKS(100));
uint8_t p[] = {0x2B, 0xA7, 0xE0};
i2c_master_transmit(cmd_dev, p, sizeof(p), pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(10));
epd_temperature();
}
static void epd_write(const uint8_t *frame, uint8_t extra_byte)
{
uint8_t w[] = {0xAC, 0x2B, 0x40, 0xA9, 0xA8};
i2c_master_transmit(cmd_dev, w, sizeof(w), pdMS_TO_TICKS(100));
i2c_master_transmit(dat_dev, frame, FRAME_LEN, pdMS_TO_TICKS(100));
i2c_master_transmit(dat_dev, &extra_byte, 1, pdMS_TO_TICKS(100));
uint8_t d[] = {0xAB, 0xAA, 0xAF};
i2c_master_transmit(cmd_dev, d, sizeof(d), pdMS_TO_TICKS(100));
epd_wait_busy();
uint8_t s[] = {0xAE, 0x28, 0xAD};
i2c_master_transmit(cmd_dev, s, sizeof(s), pdMS_TO_TICKS(100));
}
/* Show a readout of "1"s (one in each position) + one extra bit.
* Base "1" leaves bit5 dark, so a decimal/% bit at bit5 is clearly seen. */
static void show_1s_plus(uint8_t frame_byte, uint8_t phys_bit, int tag)
{
uint8_t f[FRAME_LEN];
memset(f, 0, FRAME_LEN);
for (int p = 0; p < 6; p++) {
f[POS[p][0]] = DIG1[0];
f[POS[p][1]] = DIG1[1];
}
f[frame_byte] |= (uint8_t)(1u << phys_bit);
ESP_LOGI("dpfinder", "TEST %d: byte %d bit %d", tag, frame_byte, phys_bit);
uint8_t off[FRAME_LEN];
memset(off, 0, FRAME_LEN);
epd_lut_GC();
epd_write(off, 0x00);
epd_lut_DU_WB();
epd_write(f, 0x00);
}
/* ------------------------------------------------------------------ */
static void boot_init(void)
{
gpio_config_t bc = {
.pin_bit_mask = (1ULL << PIN_BOOT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&bc);
}
static void wait_for_advance(void)
{
if (ADVANCE_MODE == 0) {
vTaskDelay(pdMS_TO_TICKS(AUTO_HOLD_MS));
return;
}
ESP_LOGI("dpfinder", "press BOOT to advance next...");
int64_t t_start = esp_timer_get_time();
for (;;) {
if (gpio_get_level(PIN_BOOT) == 0) {
vTaskDelay(pdMS_TO_TICKS(30));
while (gpio_get_level(PIN_BOOT) == 0) vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelay(pdMS_TO_TICKS(30));
break;
}
if (BOOT_TIMEOUT_MS > 0 &&
esp_timer_get_time() - t_start > BOOT_TIMEOUT_MS * 1000LL) {
ESP_LOGW("dpfinder", "no BOOT press in %d ms, auto-advancing", BOOT_TIMEOUT_MS);
break;
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void app_main(void)
{
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));
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 busy_cfg = {
.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(&busy_cfg);
boot_init();
epd_init();
ESP_LOGI("dpfinder", "== Test decimal/% at bit5 (0x20) of candidate bytes ==");
/* Phase 0: plain all 1s, no extra bit (reference). */
{
uint8_t f[FRAME_LEN];
memset(f, 0, FRAME_LEN);
for (int p = 0; p < 6; p++) {
f[POS[p][0]] = DIG1[0];
f[POS[p][1]] = DIG1[1];
}
uint8_t off[FRAME_LEN];
memset(off, 0, FRAME_LEN);
epd_lut_GC(); epd_write(off, 0x00);
epd_lut_DU_WB(); epd_write(f, 0x00);
ESP_LOGI("dpfinder", "REF: all 1s (no extras)");
wait_for_advance();
}
/* Bit 5 (=0x20) at each candidate byte, 0-indexed: 3,4,7,8,9,10 */
const int cand_byte[] = {3, 4, 7, 8, 9, 10};
for (int i = 0; i < (int)(sizeof(cand_byte)/sizeof(cand_byte[0])); i++) {
show_1s_plus(cand_byte[i], 5, i + 1);
wait_for_advance();
}
ESP_LOGI("dpfinder", "== Done. ==");
uint8_t off[FRAME_LEN];
memset(off, 0, FRAME_LEN);
epd_lut_DU_WB();
epd_write(off, 0x00);
while (1) vTaskDelay(pdMS_TO_TICKS(1000));
}