- 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
226 lines
7.2 KiB
C
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));
|
|
}
|