Skip to content

Commit 686a087

Browse files
committed
[ISSUE-24]: TM1637 driver and example
1 parent 323e0bb commit 686a087

File tree

8 files changed

+383
-9
lines changed

8 files changed

+383
-9
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,14 @@ THis list is supposed to grow longer and longer.
8383
* Low level peripheral access (via registers).
8484
* Peripheral controller drivers
8585
* I2C
86-
* RMT (TODO: example on RX)
86+
* RMT
8787
* _TODO_ SPI
8888
* _TODO_ etc.
8989
* Device drivers
9090
* BME280 Temperature / Pressure / Humidity sensor
9191
* (_TODO_: cleanup) BH1750 Light sensor
92+
* DHT22 Temperature / Humidity sensor
93+
* TM1637 4x7 segment display
9294
* WS2812B LED strip
9395
* etc (_TODO_)
9496
@@ -119,6 +121,6 @@ Feel free to fork this project and play around with it integrating your own sens
119121
120122
## About license
121123
122-
The memory arrangement (see esp32.ld) and start-up code (main.c) is based on [esp32-minimal](https://github.com/aykevl/esp32-minimal).
123-
This project also contains code (see xtutils.h) directly taken from the official [ESP32 SDK](https://github.com/espressif/esp-idf),
124+
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).
125+
This project also contains code (see [xtutils.h](src/xtutils.h)) directly taken from the official [ESP32 SDK](https://github.com/espressif/esp-idf),
124126
which is released under Apache-2.0.

configure.ac

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ AC_CONFIG_SUBDIRS([examples/1rmtblink])
2222
AC_CONFIG_SUBDIRS([examples/1rmtdht])
2323
AC_CONFIG_SUBDIRS([examples/1rmtmorse])
2424
AC_CONFIG_SUBDIRS([examples/1rmtmusic])
25+
AC_CONFIG_SUBDIRS([examples/1rmttm1637])
2526
AC_CONFIG_SUBDIRS([examples/1rmtws2812])
2627
AC_CONFIG_SUBDIRS([examples/3prog1])
2728
AC_CONFIG_SUBDIRS([ld])
@@ -39,6 +40,7 @@ AC_CONFIG_FILES([
3940
examples/1rmtdht/Makefile
4041
examples/1rmtmorse/Makefile
4142
examples/1rmtmusic/Makefile
43+
examples/1rmttm1637/Makefile
4244
examples/1rmtws2812/Makefile
4345
examples/3prog1/Makefile
4446
ld/Makefile

examples/1rmtdht/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ The 1-wire protocol is implemented in `modules/dht22.c`.
1212
#### Connections
1313

1414
```
15-
ESP32.GPIO2 -- S1.OUT
16-
ESP32.GND -- S1.-
17-
ESP32.VCC -- S1.+
15+
ESP32.GPIO21 -- S1.OUT
16+
ESP32.GND -- S1.-
17+
ESP32.VCC -- S1.+
1818
```
1919

2020
#### Practices

examples/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
AUTOMAKE_OPTIONS =
2-
SUBDIRS=0blink 0button 0hello 0ledctrl 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 3prog1 1rmtws2812
2+
SUBDIRS=0blink 0button 0hello 0ledctrl 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 1rmttm1637 3prog1 1rmtws2812

modules/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ libesp32modules_a_AR=$(AR) rcs
55

66
lib_LIBRARIES = libesp32modules.a
77

8-
include_HEADERS = bh1750.h bme280.h dht22.h ws2812.h
8+
include_HEADERS = bh1750.h bme280.h dht22.h tm1637.h ws2812.h
99
nodist_include_HEADERS =
1010

11-
libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c ws2812.c
11+
libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c tm1637.c ws2812.c
1212
nodist_libesp32modules_a_SOURCES =
1313

1414
CLEANFILES =

modules/tm1637.c

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
* Copyright 2025 SZIGETI János
3+
*
4+
* This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
5+
* See LICENSE or <https://www.gnu.org/licenses/> for full license details.
6+
*/
7+
#include <stdlib.h>
8+
#include <stdbool.h>
9+
#include <string.h>
10+
#include "esp_attr.h"
11+
#include "esp32types.h"
12+
#include "gpio.h"
13+
#include "rmt.h"
14+
#include "tm1637.h"
15+
16+
// Timings
17+
/*
18+
* Timing table (with CLK_PERIOD_TICKS=6 the full data transmission length is 411 RMT cycles)
19+
* RMT_FREQ TM1637_FREQ Full transmission time
20+
* 1 MHz 166 KHz 411 µs
21+
* 2 MHz 333 KHz 205 µs
22+
* 2.5 MHz 416 KHz 164 µs
23+
* 3 MHz 500 KHz 137 µs
24+
* 3.2 MHz 533 KHz 129 µs
25+
* 4 MHz 666 KHz 103 µs
26+
*/
27+
#define RMT_FREQ_KHZ 3000U
28+
#define CLK_PERIOD_TICKS 6U
29+
30+
#define CLK_HALFPERIOD_TICKS (CLK_PERIOD_TICKS / 2)
31+
#define DIO_DELAY_TICKS 1U ///< delay of DIO signal change to CLK falling edge
32+
33+
#define PREDIO_TICKS 2U ///< wait time (rmt ticks) before DIO change in START/STOP
34+
#define PRECLK_TICKS 2U ///< wait time (rmt ticks) before command/data byte transfer: at the end of START/STOP/ACK
35+
36+
// Sizes and limits
37+
#define SEQUENCE_SIZE 128
38+
39+
// ================= Internal Types ==================
40+
41+
typedef struct {
42+
uint16_t au16Dat[SEQUENCE_SIZE];
43+
uint8_t u8Idx;
44+
} S_RMT_SEQUENCE;
45+
46+
// ================ Local function declarations =================
47+
// Internal function declarations
48+
static inline S_RMT_SEQUENCE *seq_reset_(S_RMT_SEQUENCE *psSeq);
49+
static inline S_RMT_SEQUENCE *seq_append_(S_RMT_SEQUENCE *psSeq, uint16_t u16Value);
50+
static void gen_endseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq);
51+
static void gen_writebyteseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq, uint8_t u8Dat);
52+
static void gen_startseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq);
53+
static void gen_stopseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq);
54+
static void _clktxend_isr(void *pvParam);
55+
static void _reset_state(STm1637State *psState);
56+
static void _next_byte(void *pvParam);
57+
58+
// =================== Global constants ================
59+
60+
// ==================== Local Data ================
61+
62+
// ==================== Implementation ================
63+
64+
/**
65+
* Simply resets the write cursor of the sequence.
66+
* @param psSeq
67+
* @return
68+
*/
69+
static inline S_RMT_SEQUENCE *seq_reset_(S_RMT_SEQUENCE *psSeq) {
70+
psSeq->u8Idx = 0;
71+
return psSeq;
72+
}
73+
74+
/**
75+
* Appends a single entry to the sequence.
76+
* @param psSeq
77+
* @param u16Value
78+
* @return
79+
*/
80+
static inline S_RMT_SEQUENCE *seq_append_(S_RMT_SEQUENCE *psSeq, uint16_t u16Value) {
81+
psSeq->au16Dat[psSeq->u8Idx++] = u16Value;
82+
return psSeq;
83+
}
84+
85+
/**
86+
* In RMT RAM the TX entry sequence must be terminated by a 0 long entry.
87+
* This function additionally guarranties that there will be even number of RMT RAM entries
88+
* in both Clk and Dio RMT RAM.
89+
* @param psClkSeq
90+
* @param psDioSeq
91+
*/
92+
static void gen_endseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq) {
93+
if (seq_append_(psClkSeq, 0)->u8Idx & 1) {
94+
seq_append_(psClkSeq, 0);
95+
}
96+
if (seq_append_(psDioSeq, 0)->u8Idx & 1) {
97+
seq_append_(psDioSeq, 0);
98+
}
99+
}
100+
101+
/**
102+
* PreCond: CLK and DIO are synchronous (c:HI. d:?/LO)
103+
* At return CLK is at the end of the ACK bit,
104+
* whereas DIO is just behind the 8th bit (c:HI, d:??).
105+
* Internally DIO is delayed so that DIO level changes occur when CLK is LO.
106+
* CLK: {LLLHHH}*9, DIO: L{XXXXXX}*8.
107+
* @param psClkSeq
108+
* @param psDioSeq
109+
* @param u8Dat
110+
*/
111+
static void gen_writebyteseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq, uint8_t u8Dat) {
112+
seq_append_(psDioSeq, RMT_SIGNAL0 | (DIO_DELAY_TICKS));
113+
for (int i = 0; i < 9; i++) {
114+
seq_append_(psClkSeq, RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS));
115+
seq_append_(psClkSeq, RMT_SIGNAL1 | (CLK_HALFPERIOD_TICKS));
116+
117+
if (i < 8) {
118+
seq_append_(psDioSeq, ((u8Dat & 0x01) ? RMT_SIGNAL1 : RMT_SIGNAL0) | (CLK_PERIOD_TICKS));
119+
}
120+
u8Dat >>= 1;
121+
}
122+
}
123+
124+
/**
125+
* Precond: DIO and CLK are synchronous. (c:??, d:HI)
126+
* At return DIO is still synchronous (c:HI, d:LO).
127+
* CLK: HHHH, DIO: HHLL
128+
* @param psClkSeq
129+
* @param psDioSeq
130+
*/
131+
static void gen_startseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq) {
132+
133+
seq_append_(psClkSeq, RMT_SIGNAL1 | (PREDIO_TICKS + PRECLK_TICKS));
134+
135+
seq_append_(psDioSeq, RMT_SIGNAL1 | (PREDIO_TICKS));
136+
seq_append_(psDioSeq, RMT_SIGNAL0 | (PRECLK_TICKS));
137+
}
138+
139+
/**
140+
* Precond: Directly after ACK (sync, c:HI, d:LO).
141+
* At return DIO and CLK are synchronous (and START may follow) (c:HI, d:HI)
142+
* CLK: LLLHHHH, DIO: LLLLHHH
143+
* @param psClkSeq
144+
* @param psDioSeq
145+
*/
146+
static void gen_stopseq(S_RMT_SEQUENCE *psClkSeq, S_RMT_SEQUENCE *psDioSeq) {
147+
148+
seq_append_(psClkSeq, RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS));
149+
seq_append_(psClkSeq, RMT_SIGNAL1 | (PREDIO_TICKS + PRECLK_TICKS));
150+
151+
seq_append_(psDioSeq, RMT_SIGNAL0 | (CLK_HALFPERIOD_TICKS + DIO_DELAY_TICKS));
152+
seq_append_(psDioSeq, RMT_SIGNAL1 | (PREDIO_TICKS + PRECLK_TICKS - DIO_DELAY_TICKS));
153+
}
154+
155+
/**
156+
* There are two cases when CLK TXEND happens:
157+
* 1.) waiting for ACK (9th clk)
158+
* 2.) the whole communication process is done.
159+
* @param pvParam
160+
*/
161+
static void IRAM_ATTR _clktxend_isr(void *pvParam) {
162+
STm1637State *psParam = (STm1637State*) pvParam;
163+
164+
if (TM1637_DATA_LEN < psParam->nDataI) {
165+
psParam->fReadyCb(psParam->pvReadyCbArg);
166+
} else {
167+
bool bDioHi = gpio_pin_read(psParam->sIface.u8DioPin);
168+
if (bDioHi) {
169+
psParam->u32Internals |= (1 << psParam->nDataI);
170+
}
171+
_next_byte(pvParam);
172+
}
173+
}
174+
175+
/**
176+
* Constructs CLK and DIO RMT sequence.
177+
* Entry points: a.) Start of the whole communication process;
178+
* b.) ACK value is read from DIO when CLK is HI.
179+
* Exit points: a.) End of the whole communication process;
180+
* b.) ACK value must be read from DIO when CLK is HI.
181+
* @param pvParam TM1637 state descriptor.
182+
*/
183+
static void _next_byte(void *pvParam) {
184+
STm1637State *psParam = (STm1637State*) pvParam;
185+
S_RMT_SEQUENCE sClkSeq;
186+
S_RMT_SEQUENCE sDioSeq;
187+
188+
bool bFirst = (psParam->nDataI == 0); // first run CmdStart is not preceeded by CmdStop
189+
bool bDone = (psParam->nDataI == TM1637_DATA_LEN);
190+
bool bCmdStart = (psParam->nCmdIdxI < TM1637_CMDIDX_LEN && psParam->anCmdIdx[psParam->nCmdIdxI] == psParam->nDataI);
191+
bool bCmdStop = !bFirst && (bCmdStart || bDone);
192+
193+
seq_reset_(&sClkSeq);
194+
seq_reset_(&sDioSeq);
195+
196+
if (bCmdStop) {
197+
gen_stopseq(&sClkSeq, &sDioSeq);
198+
}
199+
if (bCmdStart) {
200+
gen_startseq(&sClkSeq, &sDioSeq);
201+
++psParam->nCmdIdxI;
202+
}
203+
if (!bDone) {
204+
gen_writebyteseq(&sClkSeq, &sDioSeq, psParam->au8Data[psParam->nDataI]);
205+
}
206+
gen_endseq(&sClkSeq, &sDioSeq);
207+
208+
memcpy((void*) rmt_ram_addr(psParam->sIface.eClkCh, 1, 0), (void*) sClkSeq.au16Dat, 2 * sClkSeq.u8Idx);
209+
memcpy((void*) rmt_ram_addr(psParam->sIface.eDioCh, 1, 0), (void*) sDioSeq.au16Dat, 2 * sDioSeq.u8Idx);
210+
211+
++psParam->nDataI;
212+
213+
rmt_start_tx(psParam->sIface.eClkCh, true);
214+
rmt_start_tx(psParam->sIface.eDioCh, true);
215+
216+
}
217+
218+
static void _reset_state(STm1637State *psState) {
219+
psState->nDataI = 0;
220+
psState->nCmdIdxI = 0;
221+
psState->u32Internals = 0;
222+
}
223+
224+
static void _rmt_config_channel(const STm1637Iface *psIface, uint8_t u8Divisor) {
225+
// rmt channel config
226+
SRmtChConf rChConf = {
227+
.r0 =
228+
{.u8DivCnt = u8Divisor, .u4MemSize = 1,
229+
.bCarrierEn = false, .bCarrierOutLvl = 1},
230+
.r1 =
231+
{.bRefAlwaysOn = 1, .bRefCntRst = 1, .bMemRdRst = 1,
232+
.bIdleOutLvl = 1, .bIdleOutEn = 1, .bMemOwner = 0}
233+
};
234+
235+
gpsRMT->asChConf[psIface->eClkCh] = rChConf;
236+
gpsRMT->asChConf[psIface->eDioCh] = rChConf;
237+
238+
gpsRMT->arTxLim[psIface->eClkCh].u9Val = 256; // currently unused
239+
gpsRMT->arTxLim[psIface->eDioCh].u9Val = 256; // currently unused
240+
}
241+
242+
// ============== Interface functions ==============
243+
244+
STm1637State tm1637_config(const STm1637Iface *psIface) {
245+
return (STm1637State){
246+
.sIface = *psIface,
247+
.au8Data =
248+
{0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x88},
249+
.nDataI = 0,
250+
.anCmdIdx =
251+
{0, 1, 6},
252+
.nCmdIdxI = 0,
253+
.fReadyCb = NULL,
254+
.pvReadyCbArg = NULL};
255+
}
256+
257+
void tm1637_init(STm1637State *psState, uint32_t u32ApbClkFreq) {
258+
rmt_init_channel(psState->sIface.eClkCh, psState->sIface.u8ClkPin, true);
259+
rmt_init_channel(psState->sIface.eDioCh, psState->sIface.u8DioPin, true);
260+
_rmt_config_channel(&psState->sIface, u32ApbClkFreq / (1000 * RMT_FREQ_KHZ));
261+
rmt_isr_register(psState->sIface.eClkCh, RMT_INT_TXEND, _clktxend_isr, psState);
262+
}
263+
264+
/**
265+
* TODO
266+
* @param psState
267+
*/
268+
void tm1637_deinit(STm1637State *psState) {
269+
// remove interrupts
270+
271+
// release GPIO-RMT bindings
272+
273+
// conditionally power down RMT peripheral
274+
275+
// release GPIO
276+
}
277+
278+
void tm1637_set_data(STm1637State *psState, uint8_t *au8Data) {
279+
memcpy((psState->au8Data + 2), au8Data, 4);
280+
}
281+
282+
void tm1637_set_brightness(STm1637State *psState, bool bOn, uint8_t u8Value) {
283+
psState->au8Data[6] = 0x80 | (bOn ? 0x08 : 0x00) | (u8Value & 0x07);
284+
}
285+
286+
void tm1637_set_readycb(STm1637State *psState, Isr fHandler, void *pvArg) {
287+
psState->fReadyCb = fHandler;
288+
psState->pvReadyCbArg = pvArg;
289+
}
290+
291+
void tm1637_display(STm1637State *psState) {
292+
_reset_state(psState);
293+
_next_byte(psState);
294+
}

0 commit comments

Comments
 (0)