diff --git a/README.md b/README.md
index c044af8..e14192d 100644
--- a/README.md
+++ b/README.md
@@ -83,12 +83,14 @@ THis list is supposed to grow longer and longer.
* Low level peripheral access (via registers).
* Peripheral controller drivers
* I2C
- * RMT (TODO: example on RX)
+ * RMT
* _TODO_ SPI
* _TODO_ etc.
* Device drivers
* BME280 Temperature / Pressure / Humidity sensor
* (_TODO_: cleanup) BH1750 Light sensor
+ * DHT22 Temperature / Humidity sensor
+ * TM1637 4x7 segment display
* WS2812B LED strip
* etc (_TODO_)
@@ -119,6 +121,6 @@ Feel free to fork this project and play around with it integrating your own sens
## About license
-The memory arrangement (see esp32.ld) and start-up code (main.c) is based on [esp32-minimal](https://github.com/aykevl/esp32-minimal).
-This project also contains code (see xtutils.h) directly taken from the official [ESP32 SDK](https://github.com/espressif/esp-idf),
+The memory arrangement (see [esp32.ld](ld/esp32.ld)) and start-up code ([main.c](src/main.c)) is based on [esp32-minimal](https://github.com/aykevl/esp32-minimal).
+This project also contains code (see [xtutils.h](src/xtutils.h)) directly taken from the official [ESP32 SDK](https://github.com/espressif/esp-idf),
which is released under Apache-2.0.
diff --git a/configure.ac b/configure.ac
index f09cec0..78de23b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,6 +22,7 @@ AC_CONFIG_SUBDIRS([examples/1rmtblink])
AC_CONFIG_SUBDIRS([examples/1rmtdht])
AC_CONFIG_SUBDIRS([examples/1rmtmorse])
AC_CONFIG_SUBDIRS([examples/1rmtmusic])
+AC_CONFIG_SUBDIRS([examples/1rmttm1637])
AC_CONFIG_SUBDIRS([examples/1rmtws2812])
AC_CONFIG_SUBDIRS([examples/3prog1])
AC_CONFIG_SUBDIRS([ld])
@@ -39,6 +40,7 @@ AC_CONFIG_FILES([
examples/1rmtdht/Makefile
examples/1rmtmorse/Makefile
examples/1rmtmusic/Makefile
+ examples/1rmttm1637/Makefile
examples/1rmtws2812/Makefile
examples/3prog1/Makefile
ld/Makefile
diff --git a/examples/1rmtdht/README.md b/examples/1rmtdht/README.md
index 2c23cc0..de2ba91 100644
--- a/examples/1rmtdht/README.md
+++ b/examples/1rmtdht/README.md
@@ -12,9 +12,9 @@ The 1-wire protocol is implemented in `modules/dht22.c`.
#### Connections
```
-ESP32.GPIO2 -- S1.OUT
-ESP32.GND -- S1.-
-ESP32.VCC -- S1.+
+ESP32.GPIO21 -- S1.OUT
+ESP32.GND -- S1.-
+ESP32.VCC -- S1.+
```
#### Practices
diff --git a/examples/1rmttm1637/Makefile.am b/examples/1rmttm1637/Makefile.am
new file mode 100644
index 0000000..edde377
--- /dev/null
+++ b/examples/1rmttm1637/Makefile.am
@@ -0,0 +1,37 @@
+include $(top_srcdir)/scripts/elf2bin.mk
+include $(top_srcdir)/ld/flags.mk
+
+noinst_HEADERS = defines.h
+
+AM_CFLAGS = -std=c11 -flto
+
+if WITH_BINARIES
+AM_LDFLAGS += \
+ -T $(top_srcdir)/ld/esp32.rom.ld \
+ -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-data.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-locale.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-nano.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-time.ld \
+ -T $(top_srcdir)/ld/esp32.rom.redefined.ld \
+ -T $(top_srcdir)/ld/esp32.rom.syscalls.ld
+else
+AM_LDFLAGS += \
+ -T $(top_srcdir)/ld/esp32.rom.ld \
+ -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \
+ -T $(top_srcdir)/ld/esp32.rom.redefined.ld \
+ -T $(top_srcdir)/ld/esp32.rom.syscalls.ld
+endif
+
+AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/modules
+LDADD = $(top_builddir)/src/libesp32basic.a $(top_builddir)/modules/libesp32modules.a
+
+bin_PROGRAMS = \
+ rmttm1637.elf
+
+if WITH_BINARIES
+CLEANFILES = \
+ rmttm1637.bin
+endif
+
+BUILT_SOURCES = $(CLEANFILES)
diff --git a/examples/1rmttm1637/README.md b/examples/1rmttm1637/README.md
new file mode 100644
index 0000000..15fc053
--- /dev/null
+++ b/examples/1rmttm1637/README.md
@@ -0,0 +1,24 @@
+### TM1637 display example
+
+In this example the TM1637 displays hexadecimal numbers form `0` to `F`.
+Each digit displays the same number.
+The colon between the 2nd and the 3rd digits is blinking every second.
+Each number is displayed for 3 seconds:
+1 second full brightness, 1 second medium brightness and 1 second low brightness.
+
+
+#### Hardware components
+
+* X1: TM1637 display
+
+#### Connections
+
+```
+ESP32.GPIO21 -- X1.CLK
+ESP32.GPIO19 -- X1.DIO
+ESP32.GND -- X1.GND
+ESP32.VCC -- X1.VCC
+```
+
+#### Practices
+
diff --git a/examples/1rmttm1637/defines.h b/examples/1rmttm1637/defines.h
new file mode 100644
index 0000000..a767e29
--- /dev/null
+++ b/examples/1rmttm1637/defines.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+#ifndef DEFINES_H
+#define DEFINES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ // TIMINGS
+ // const -- do not change this value
+#define APB_FREQ_HZ 80000000U // 80 MHz
+
+ // variables
+#define TIM0_0_DIVISOR 2U
+#define START_APP_CPU 0U
+#define SCHEDULE_FREQ_HZ 1000U // 1KHz
+
+ // derived invariants
+#define CLK_FREQ_HZ (APB_FREQ_HZ / TIM0_0_DIVISOR) // 40 MHz
+#define TICKS_PER_MS (CLK_FREQ_HZ / 1000U) // 40000
+#define TICKS_PER_US (CLK_FREQ_HZ / 1000000U) // 40
+#define NS_PER_TICKS (1000000000 / CLK_FREQ_HZ)
+
+#define TICKS2NS(X) ((X) * NS_PER_TICKS)
+#define TICKS2US(X) ((X) / TICKS_PER_US)
+#define MS2TICKS(X) ((X) * TICKS_PER_MS)
+#define HZ2APBTICKS(X) (APB_FREQ_HZ / (X))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEFINES_H */
+
diff --git a/examples/1rmttm1637/rmttm1637.c b/examples/1rmttm1637/rmttm1637.c
new file mode 100644
index 0000000..26d9161
--- /dev/null
+++ b/examples/1rmttm1637/rmttm1637.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+#include
+#include
+#include
+#include
+#include
+
+#include "tm1637.h"
+#include "gpio.h"
+#include "main.h"
+#include "rmt.h"
+#include "defines.h"
+#include "romfunctions.h"
+#include "iomux.h"
+#include "dport.h"
+#include "timg.h"
+#include "typeaux.h"
+#include "utils/uartutils.h"
+
+// =================== Hard constants =================
+// #1: Timings
+#define RMTTM1637_PERIOD_MS 500U
+
+// #2: Channels / wires / addresses
+#define CLK_GPIO 21U
+#define DIO_GPIO 19U
+#define CLK_CH RMT_CH1
+#define DIO_CH RMT_CH0
+
+#define RMTINT_CH 23U
+
+// ============= Local types ===============
+typedef struct {
+ STm1637State *psState;
+ uint64_t u64tckStart;
+} SReadyCbParam;
+
+// ================ Local function declarations =================
+static void _rmttm1637_ready(void *pvParam);
+static void _rmttm1637_init();
+static void _rmttm1637_cycle(uint64_t u64Ticks);
+
+// =================== Global constants ================
+const bool gbStartAppCpu = START_APP_CPU;
+const uint16_t gu16Tim00Divisor = TIM0_0_DIVISOR;
+const uint64_t gu64tckSchedulePeriod = (CLK_FREQ_HZ / SCHEDULE_FREQ_HZ);
+
+// ==================== Local Data ================
+static const TimerId gsTimer = {.eTimg = TIMG_0, .eTimer = TIMER0};
+const uint8_t gau8NumToSeg[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
+
+static STm1637State gsTm1637State;
+static SReadyCbParam gsReadyData;
+
+// ==================== Implementation ================
+
+static void _rmttm1637_ready(void *pvParam) {
+ SReadyCbParam *psParam = (SReadyCbParam*) pvParam;
+ uint64_t u64TckStop = timg_ticks(gsTimer);
+ uart_printf(&gsUART0, "Display ready (failed ACKs: %08X)\tDt: %d ns\n",
+ psParam->psState->u32Internals,
+ TICKS2NS((uint32_t) (u64TckStop - psParam->u64tckStart)));
+}
+
+static void _rmttm1637_init() {
+ rmt_isr_init();
+ rmt_init_controller(true, true);
+ STm1637Iface sIface = { CLK_GPIO, DIO_GPIO, CLK_CH, DIO_CH};
+ gsTm1637State = tm1637_config(&sIface);
+ tm1637_init(&gsTm1637State, APB_FREQ_HZ);
+ tm1637_set_readycb(&gsTm1637State, _rmttm1637_ready, &gsReadyData);
+ gsReadyData.psState = &gsTm1637State;
+ rmt_isr_start(CPU_PRO, RMTINT_CH);
+}
+
+static void _rmttm1637_cycle(uint64_t u64Ticks) {
+ static uint64_t u64NextTick = MS2TICKS(RMTTM1637_PERIOD_MS);
+ static uint8_t au8Data[4];
+ static uint8_t u8DatIdx = 0; // What number to display (put into au8Data)? Cycling through gau8NumToSeg: [0..15]
+ static uint8_t u8Phase = 0; // Display phase: last bit is 0: display dot (8th segment of the digits), bits 1, 2: brightness level
+
+ if (u64NextTick <= u64Ticks) {
+ // local, derived variables
+ bool bDot = (0 == (u8Phase & 1));
+ uint8_t u8Brightness = 7 - (3 * (u8Phase / 2)); // three levels: 7, 4, 1
+
+ uart_printf(&gsUART0, "Cycle %u %u\t", u8DatIdx, u8Phase);
+ tm1637_set_brightness(&gsTm1637State, true, u8Brightness);
+ for (int i = 0; i < 4; ++i) {
+ au8Data[i] = gau8NumToSeg[u8DatIdx] | (bDot ? 0x80 : 0x00);
+ }
+ tm1637_set_data(&gsTm1637State, au8Data);
+ gsReadyData.u64tckStart = timg_ticks(gsTimer);
+ tm1637_display(&gsTm1637State);
+
+ // jump to next state
+ ++u8Phase;
+ if (6 == u8Phase) {
+ u8Phase = 0;
+ ++u8DatIdx;
+ if (u8DatIdx == ARRAY_SIZE(gau8NumToSeg)) {
+ u8DatIdx = 0;
+ }
+ }
+ u64NextTick += MS2TICKS(RMTTM1637_PERIOD_MS);
+ }
+}
+
+// ====================== Interface functions =========================
+
+void prog_init_pro_pre() {
+ gsUART0.CLKDIV = APB_FREQ_HZ / 115200;
+
+ _rmttm1637_init();
+}
+
+void prog_init_app() {
+}
+
+void prog_init_pro_post() {
+}
+
+void prog_cycle_app(uint64_t u64tckNow) {
+}
+
+void prog_cycle_pro(uint64_t u64tckNow) {
+ _rmttm1637_cycle(u64tckNow);
+}
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 2d3ccfd..91bbc2e 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,2 +1,2 @@
AUTOMAKE_OPTIONS =
-SUBDIRS=0blink 0button 0hello 0ledctrl 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 3prog1 1rmtws2812
+SUBDIRS=0blink 0button 0hello 0ledctrl 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 1rmttm1637 3prog1 1rmtws2812
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 8556471..16687e3 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -5,10 +5,10 @@ libesp32modules_a_AR=$(AR) rcs
lib_LIBRARIES = libesp32modules.a
-include_HEADERS = bh1750.h bme280.h dht22.h ws2812.h
+include_HEADERS = bh1750.h bme280.h dht22.h tm1637.h ws2812.h
nodist_include_HEADERS =
-libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c ws2812.c
+libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c tm1637.c ws2812.c
nodist_libesp32modules_a_SOURCES =
CLEANFILES =
diff --git a/modules/tm1637.c b/modules/tm1637.c
new file mode 100644
index 0000000..4b5ccb6
--- /dev/null
+++ b/modules/tm1637.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2025 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+#include
+#include
+#include
+#include "esp_attr.h"
+#include "esp32types.h"
+#include "gpio.h"
+#include "rmt.h"
+#include "tm1637.h"
+#include "utils/uartutils.h"
+// Timings
+/*
+ * Timing table (The full data transmission length is 7*(9*(CLK_PERIOD_TICKS)-2)+3*(8+CLK_HALFPERIOD_TICKS))
+ * A: with CLK_PERIOD_TICKS=6 the full length is 397 RMT cycles
+ * B: with CLK_PERIOD_TICKS=8 the full length is 528 RMT cycles
+ * RMT_FREQ TM1637_FREQ(A) dt(A) TM1637_FREQ(B) dt(B)
+ * 1 MHz 166 KHz 397 µs 125 KHz 528 µs
+ * 2 MHz 333 KHz 198 µs 250 KHz 274 µs
+ * 2.5 MHz 416 KHz 159 µs
+ * 3 MHz 500 KHz 133 µs
+ * 3.2 MHz 533 KHz 124 µs
+ * 4 MHz 666 KHz 100 µs 500 KHz 132 µs
+ * 5 MHz 625 KHz 106 µs
+ */
+#define RMT_FREQ_KHZ 3000U
+#define CLK_PERIOD_TICKS 6U
+
+#define CLK_HALFPERIOD_TICKS (CLK_PERIOD_TICKS / 2)
+#define DIO_DELAY_TICKS 1U ///< delay of DIO signal change to CLK falling edge
+
+#define PREDIO_TICKS 2U ///< wait time (rmt ticks) before DIO change in START/STOP
+#define PRECLK_TICKS 2U ///< wait time (rmt ticks) before command/data byte transfer: at the end of START/STOP/ACK
+
+// Sizes and limits
+#define SEQUENCE_SIZE 128
+
+// ================= Internal Types ==================
+
+typedef struct {
+ RegAddr prDat;
+ uint8_t u8Idx;
+} SRmtRamWriter;
+
+// ================ Local function declarations =================
+// Internal function declarations
+static inline SRmtRamWriter _seq_init(RegAddr prBase);
+static inline SRmtRamWriter *_seq_append(SRmtRamWriter *psSeq, uint16_t u16ValLo, uint16_t u16ValHi);
+static void gen_endseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq);
+static void gen_writebyteseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq, uint8_t u8Dat);
+static void gen_startseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq);
+static void gen_stopseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq);
+static void _clktxend_isr(void *pvParam);
+static void _reset_state(STm1637State *psState);
+static void _next_byte(void *pvParam);
+
+// =================== Global constants ================
+
+// ==================== Local Data ================
+
+// ==================== Implementation ================
+
+/**
+ * Initializes object.
+ * @param prBase RMT RAM block base address
+ * @return Initialized object.
+ */
+static inline SRmtRamWriter _seq_init(RegAddr prBase) {
+ return (SRmtRamWriter){.prDat = prBase, .u8Idx = 0};
+}
+
+/**
+ * Writes RMT entry pair into RMT RAM at the current offset.
+ * @param psSeq RMT RAM cursor.
+ * @param u16ValLo First entry.
+ * @param u16ValHi Second entry.
+ * @return Updated RMT RAM cursor (offset increased).
+ */
+static inline SRmtRamWriter *_seq_append(SRmtRamWriter *psSeq, uint16_t u16ValLo, uint16_t u16ValHi) {
+ psSeq->prDat[psSeq->u8Idx++] = u16ValLo | (u16ValHi<<16);
+ return psSeq;
+}
+
+/**
+ * In RMT RAM the TX entry sequence must be terminated by a 0 long entry.
+ * @param psClkSeq
+ * @param psDioSeq
+ */
+static void gen_endseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq) {
+ _seq_append(psClkSeq, 0, 0);
+ _seq_append(psDioSeq, 0, 0);
+}
+/**
+ * PreCond: CLK and DIO are synchronous (c:HI. d:?/LO)
+ * At return CLK is at the end of the ACK bit,
+ * whereas DIO is just behind the 8th bit (c:HI, d:??).
+ * Internally DIO is delayed so that DIO level changes occur when CLK is LO.
+ * CLK: {LLHHH}{LLLHHH}*7{LLLHH}, DIO: {XXXXXX}*8.
+ * @param psClkSeq
+ * @param psDioSeq
+ * @param u8Dat
+ */
+static void gen_writebyteseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq, uint8_t u8Dat) {
+ _seq_append(psClkSeq,
+ RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS - DIO_DELAY_TICKS),
+ RMT_SIGNAL1 | (CLK_HALFPERIOD_TICKS));
+ for (int i = 1; i < 8; i++) {
+ _seq_append(psClkSeq,
+ RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS),
+ RMT_SIGNAL1 | (CLK_HALFPERIOD_TICKS));
+ }
+ _seq_append(psClkSeq,
+ RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS),
+ RMT_SIGNAL1 | (CLK_HALFPERIOD_TICKS - DIO_DELAY_TICKS));
+
+ for (int i = 0; i < 4; ++i) {
+ _seq_append(psDioSeq,
+ ((u8Dat & 0x01) ? RMT_SIGNAL1 : RMT_SIGNAL0) | (CLK_PERIOD_TICKS),
+ ((u8Dat & 0x02) ? RMT_SIGNAL1 : RMT_SIGNAL0) | (CLK_PERIOD_TICKS));
+ u8Dat >>= 2;
+ }
+}
+
+/**
+ * Precond: DIO and CLK are synchronous. (c:??, d:HI)
+ * At return DIO is still synchronous (c:HI, d:LO).
+ * CLK: HHHH, DIO: HHLL
+ * @param psClkSeq
+ * @param psDioSeq
+ */
+static void gen_startseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq) {
+
+ _seq_append(psClkSeq,
+ RMT_SIGNAL1 | (PREDIO_TICKS),
+ RMT_SIGNAL1 | (PRECLK_TICKS));
+ _seq_append(psDioSeq,
+ RMT_SIGNAL1 | (PREDIO_TICKS),
+ RMT_SIGNAL0 | (PRECLK_TICKS));
+}
+
+/**
+ * Precond: Directly after ACK (sync, c:HI, d:LO).
+ * At return DIO and CLK are synchronous (and START may follow) (c:HI, d:HI)
+ * CLK: LLLHHHH, DIO: LLLLHHH
+ * @param psClkSeq
+ * @param psDioSeq
+ */
+static void gen_stopseq(SRmtRamWriter *psClkSeq, SRmtRamWriter *psDioSeq) {
+
+ _seq_append(psClkSeq,
+ RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS),
+ RMT_SIGNAL1 | (PREDIO_TICKS + PRECLK_TICKS));
+ _seq_append(psDioSeq,
+ RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS + DIO_DELAY_TICKS),
+ RMT_SIGNAL1 | (PREDIO_TICKS + PRECLK_TICKS - DIO_DELAY_TICKS));
+}
+
+/**
+ * There are two cases when CLK TXEND happens:
+ * 1.) waiting for ACK (9th clk)
+ * 2.) the whole communication process is done.
+ * @param pvParam
+ */
+static void IRAM_ATTR _clktxend_isr(void *pvParam) {
+ STm1637State *psParam = (STm1637State*) pvParam;
+// uart_printf(&gsUART0, "S%08X ",gpsRMT->asStatus[psParam->sIface.eClkCh]);
+
+ if (TM1637_DATA_LEN < psParam->nDataI) {
+ psParam->fReadyCb(psParam->pvReadyCbArg);
+ } else {
+ bool bDioHi = gpio_pin_read(psParam->sIface.u8DioPin);
+ if (bDioHi) {
+ psParam->u32Internals |= (1 << psParam->nDataI);
+ }
+ _next_byte(pvParam);
+ }
+}
+
+/**
+ * Constructs CLK and DIO RMT sequence.
+ * Entry points: a.) Start of the whole communication process;
+ * b.) ACK value is read from DIO when CLK is HI.
+ * Exit points: a.) End of the whole communication process;
+ * b.) ACK value must be read from DIO when CLK is HI.
+ * @param pvParam TM1637 state descriptor.
+ */
+static void _next_byte(void *pvParam) {
+ STm1637State *psParam = (STm1637State*) pvParam;
+ SRmtRamWriter sClkSeq = _seq_init(rmt_ram_addr(psParam->sIface.eClkCh, 1, 0));
+ SRmtRamWriter sDioSeq = _seq_init(rmt_ram_addr(psParam->sIface.eDioCh, 1, 0));
+
+ bool bFirst = (psParam->nDataI == 0); // first run CmdStart is not preceeded by CmdStop
+ bool bDone = (psParam->nDataI == TM1637_DATA_LEN);
+ bool bCmdStart = (psParam->nCmdIdxI < TM1637_CMDIDX_LEN && psParam->anCmdIdx[psParam->nCmdIdxI] == psParam->nDataI);
+ bool bCmdStop = !bFirst && (bCmdStart || bDone);
+
+ if (bCmdStop) {
+ gen_stopseq(&sClkSeq, &sDioSeq);
+ }
+ if (bCmdStart) {
+ gen_startseq(&sClkSeq, &sDioSeq);
+ ++psParam->nCmdIdxI;
+ }
+ if (!bDone) {
+ gen_writebyteseq(&sClkSeq, &sDioSeq, psParam->au8Data[psParam->nDataI]);
+ }
+ gen_endseq(&sClkSeq, &sDioSeq);
+
+ ++psParam->nDataI;
+
+ rmt_start_tx(psParam->sIface.eClkCh, true);
+ rmt_start_tx(psParam->sIface.eDioCh, true);
+
+}
+
+static void _reset_state(STm1637State *psState) {
+ psState->nDataI = 0;
+ psState->nCmdIdxI = 0;
+ psState->u32Internals = 0;
+}
+
+static void _rmt_config_channel(const STm1637Iface *psIface, uint8_t u8Divisor) {
+ // rmt channel config
+ SRmtChConf rChConf = {
+ .r0 =
+ {.u8DivCnt = u8Divisor, .u4MemSize = 1,
+ .bCarrierEn = false, .bCarrierOutLvl = 1},
+ .r1 =
+ {.bRefAlwaysOn = 1, .bRefCntRst = 1, .bMemRdRst = 1,
+ .bIdleOutLvl = 1, .bIdleOutEn = 1, .bMemOwner = 0}
+ };
+
+ gpsRMT->asChConf[psIface->eClkCh] = rChConf;
+ gpsRMT->asChConf[psIface->eDioCh] = rChConf;
+
+ gpsRMT->arTxLim[psIface->eClkCh].u9Val = 256; // currently unused
+ gpsRMT->arTxLim[psIface->eDioCh].u9Val = 256; // currently unused
+}
+
+// ============== Interface functions ==============
+
+STm1637State tm1637_config(const STm1637Iface *psIface) {
+ return (STm1637State){
+ .sIface = *psIface,
+ .au8Data =
+ {0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x88},
+ .nDataI = 0,
+ .anCmdIdx =
+ {0, 1, 6},
+ .nCmdIdxI = 0,
+ .fReadyCb = NULL,
+ .pvReadyCbArg = NULL};
+}
+
+void tm1637_init(STm1637State *psState, uint32_t u32ApbClkFreq) {
+ rmt_init_channel(psState->sIface.eClkCh, psState->sIface.u8ClkPin, true);
+ rmt_init_channel(psState->sIface.eDioCh, psState->sIface.u8DioPin, true);
+ _rmt_config_channel(&psState->sIface, u32ApbClkFreq / (1000 * RMT_FREQ_KHZ));
+ rmt_isr_register(psState->sIface.eClkCh, RMT_INT_TXEND, _clktxend_isr, psState);
+}
+
+/**
+ * TODO
+ * @param psState
+ */
+void tm1637_deinit(STm1637State *psState) {
+ // remove interrupts
+
+ // release GPIO-RMT bindings
+
+ // conditionally power down RMT peripheral
+
+ // release GPIO
+}
+
+void tm1637_set_data(STm1637State *psState, uint8_t *au8Data) {
+ memcpy((psState->au8Data + 2), au8Data, 4);
+}
+
+void tm1637_set_brightness(STm1637State *psState, bool bOn, uint8_t u8Value) {
+ psState->au8Data[6] = 0x80 | (bOn ? 0x08 : 0x00) | (u8Value & 0x07);
+}
+
+void tm1637_set_readycb(STm1637State *psState, Isr fHandler, void *pvArg) {
+ psState->fReadyCb = fHandler;
+ psState->pvReadyCbArg = pvArg;
+}
+
+void tm1637_display(STm1637State *psState) {
+ _reset_state(psState);
+ _next_byte(psState);
+}
diff --git a/modules/tm1637.h b/modules/tm1637.h
new file mode 100644
index 0000000..a273794
--- /dev/null
+++ b/modules/tm1637.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+
+#ifndef TM1637_H
+#define TM1637_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TM1637_DATA_LEN 7
+#define TM1637_CMDIDX_LEN 3
+
+#include "rmt.h"
+
+ typedef struct {
+ uint8_t u8ClkPin; ///< CLK GPIO pin.
+ uint8_t u8DioPin; ///< DIO GPIO pin.
+ ERmtChannel eClkCh; ///< CLK RMT channel.
+ ERmtChannel eDioCh; ///< DIO RMT channel.
+ } STm1637Iface; ///< Describes TM1637 interface.
+
+ typedef struct {
+ STm1637Iface sIface; ///< TM1637 RMT/GPIO interface.
+ uint8_t au8Data[TM1637_DATA_LEN]; ///< Outgoing Command/Data bytes.
+ size_t nDataI; ///< Current au8Data index.
+ size_t anCmdIdx[TM1637_CMDIDX_LEN]; ///< au8Data indices of commands.
+ size_t nCmdIdxI; ///< Current anCmdIdx index.
+ uint32_t u32Internals; ///< Internal attributes, e.g., ACKs.
+ Isr fReadyCb; ///< Callback function to invoke when the whole transfer is complete.
+ void *pvReadyCbArg; ///< Argument to pass to fReadyCb function.
+ } STm1637State;
+
+
+ STm1637State tm1637_config(const STm1637Iface *psIface);
+ void tm1637_init(STm1637State *psState, uint32_t u32ApbClkFreq);
+ void tm1637_deinit(STm1637State *psState);
+ void tm1637_set_data(STm1637State *psState, uint8_t *au8Data);
+ void tm1637_set_brightness(STm1637State *psState, bool bOn, uint8_t u8Value);
+ void tm1637_set_readycb(STm1637State *psState, Isr fHandler, void *pvArg);
+ void tm1637_display(STm1637State *psState);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TM1637_H */
+
diff --git a/src/gpio.h b/src/gpio.h
index 9aa35fc..8d2a7a5 100644
--- a/src/gpio.h
+++ b/src/gpio.h
@@ -78,6 +78,30 @@ extern "C" {
return &gsGPIO;
}
+ /**
+ * Tells the register of any gpio pin in range [0..39].
+ * Generally, there is a base register for gpio pins [0..31],
+ * and there is another register for pins [32..39].
+ * This second register is either directly after the base register, e.g., .IN1 follows .IN,
+ * or a group of base register is followed by a group of second registers, e.g., OUT, OUT_W1TS, OUT_W1TC followed by their pairs.
+ * @param prRegBase Base register.
+ * @param u8Shift Difference between the base and the second register addresses (measured in register width).
+ * @param u8Pin GPIO pin. The only thing that matters here is whether u8Pin is less than 32 or not.
+ * @return Register to use for the given GPIO pin.
+ */
+ static inline RegAddr gpio_reg_anypin(RegAddr prRegBase, uint8_t u8Shift, uint8_t u8Pin) {
+ return prRegBase + (u8Pin < 32 ? 0 : u8Shift);
+ }
+
+ static inline uint8_t gpio_pin_read(uint8_t u8Pin) {
+ return 0 < (*gpio_reg_anypin(&gsGPIO.IN, 1, u8Pin) & (1 << (u8Pin & 0x1f)));
+ }
+
+ /**
+ * Provides bit access for GPIO pins in range [0..39] for the following register groups: OUT, ENABLE, STATUS
+ * @param prReg Base register, e.g., OUT, OUT_W1TS, OUT_W1TS, etc.
+ * @param u8Pin GPIO pin.
+ */
static inline void gpio_reg_setbit(RegAddr prReg, uint8_t u8Pin) {
prReg[u8Pin < 32 ? 0 : 3] = 1 << (u8Pin & 0x1f);
}