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); }