|
| 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