TinyGPU
Loading...
Searching...
No Matches
EmulationIDF.h
Go to the documentation of this file.
1#pragma once
2/**
3 * @file EmulationIDF.h
4 * @brief ESP-IDF-native emulation of exactly the Arduino surface
5 * TinyGPU/Drivers and TinyGPU/Input actually call - delay()/millis(),
6 * pinMode()/digitalWrite()/digitalRead() (+ HIGH/LOW/OUTPUT/INPUT/
7 * INPUT_PULLUP), SPIClass/SPISettings (+ MSBFIRST/LSBFIRST/SPI_MODEn)
8 * and a global `SPI` (driver/spi_master.h), and TwoWire (+ a global
9 * `Wire`) on the legacy driver/i2c.h controller driver.
10 *
11 * This is the ESP-IDF branch of TinyGPU/Emulation.h's fallback - include
12 * that instead of this file directly; it decides whether a real Arduino
13 * core is available, and routes here only when it isn't and
14 * ESP_PLATFORM is defined (a plain ESP-IDF component build via
15 * `idf_component_register`, without arduino-esp32).
16 *
17 * Not a general Arduino-core replacement - only covers the calls this
18 * library's own Drivers/Input headers make (verified by grepping them),
19 * not the full Arduino API. A sketch built against this fallback that
20 * calls other Arduino functions will still need its own additions here,
21 * or the real Arduino core after all.
22 *
23 * Not compiled or hardware-tested as part of this repo's own CI (which
24 * only exercises the desktop/Arduino-Emulator and Arduino-IDE paths) -
25 * treat it as a reasonable-effort starting point and verify against your
26 * exact IDF version/target before relying on it on real hardware.
27 */
28
29#if !defined(ESP_PLATFORM)
30#error "TinyGPU/Abstractions/EmulationIDF.h is ESP-IDF-only - include TinyGPU/Emulation.h instead, which routes here only when ESP_PLATFORM is defined."
31#endif
32
33#include <stddef.h>
34#include <stdint.h>
35
36#include "driver/gpio.h"
37#include "driver/i2c.h"
38#include "driver/spi_master.h"
39#include "esp_timer.h"
40#include "freertos/FreeRTOS.h"
41#include "freertos/task.h"
42
43// Namespaced (not global) like the rest of this library - safe to do
44// even for delay()/pinMode()/SPIClass/... since every call site (all of
45// TinyGPU/Drivers and TinyGPU/Input) lives inside `namespace tinygpu`
46// itself, so unqualified lookup finds these the same way it would find
47// them in the global namespace on the real-Arduino-core branch of
48// TinyGPU/Emulation.h.
49namespace tinygpu {
50
51// ---------------------------------------------------------------------
52// delay() / millis()
53// ---------------------------------------------------------------------
54
55/// Blocks the calling task for `ms` milliseconds via FreeRTOS - the
56/// ESP-IDF-native equivalent of Arduino's delay().
57inline void delay(uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
58
59/// Milliseconds since boot, matching Arduino's millis() (wraps the same
60/// way a 32-bit millisecond counter would, since esp_timer's underlying
61/// 64-bit microsecond count is truncated to unsigned long here).
62inline unsigned long millis() {
63 return static_cast<unsigned long>(esp_timer_get_time() / 1000);
64}
65
66// ---------------------------------------------------------------------
67// Digital I/O
68// ---------------------------------------------------------------------
69
70// constexpr, not #define - a macro can't be namespaced (the preprocessor
71// runs before namespaces mean anything to it), so these are ordinary
72// namespaced constants instead.
73constexpr int HIGH = 1;
74constexpr int LOW = 0;
75constexpr int INPUT = 0x00;
76constexpr int OUTPUT = 0x01;
77constexpr int INPUT_PULLUP = 0x02;
78
79/// Configures a GPIO's direction/pull, matching Arduino's pinMode(). A
80/// negative pin (this library's "not wired up" sentinel throughout
81/// Drivers/Input) is silently ignored, exactly like the callers already
82/// assume when they guard on `pin >= 0` before calling at all - and
83/// harmlessly for the few call sites that don't bother guarding.
84inline void pinMode(int pin, int mode) {
85 if (pin < 0) return;
86 gpio_config_t cfg = {};
87 cfg.pin_bit_mask = (1ULL << pin);
88 cfg.mode = (mode == OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
89 cfg.pull_up_en =
90 (mode == INPUT_PULLUP) ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
91 cfg.pull_down_en = GPIO_PULLDOWN_DISABLE;
92 cfg.intr_type = GPIO_INTR_DISABLE;
93 gpio_config(&cfg);
94}
95
96/// Sets a GPIO output level, matching Arduino's digitalWrite().
97inline void digitalWrite(int pin, int value) {
98 if (pin < 0) return;
99 gpio_set_level(static_cast<gpio_num_t>(pin), value);
100}
101
102/// Reads a GPIO input level, matching Arduino's digitalRead().
103inline int digitalRead(int pin) {
104 if (pin < 0) return LOW;
105 return gpio_get_level(static_cast<gpio_num_t>(pin));
106}
107
108// ---------------------------------------------------------------------
109// SPI
110// ---------------------------------------------------------------------
111
112constexpr int MSBFIRST = 0;
113constexpr int LSBFIRST = 1;
114constexpr int SPI_MODE0 = 0;
115constexpr int SPI_MODE1 = 1;
116constexpr int SPI_MODE2 = 2;
117constexpr int SPI_MODE3 = 3;
118
119/// Matches Arduino's SPISettings(clockHz, bitOrder, dataMode) - carries
120/// per-transaction bus parameters into SPIClass::beginTransaction().
122 uint32_t clock;
123 uint8_t bitOrder;
124 uint8_t dataMode;
125
127 SPISettings(uint32_t clockHz, uint8_t order, uint8_t mode)
128 : clock(clockHz), bitOrder(order), dataMode(mode) {}
129};
130
131/// ESP-IDF driver/spi_master.h-backed stand-in for Arduino's SPIClass,
132/// covering exactly what this library's SPI-based display/touch drivers
133/// call: beginTransaction()/endTransaction() and both transfer()
134/// overloads (single byte, and in-place full-duplex buffer). Chip select
135/// is NOT driven by this class - callers here always toggle their own CS
136/// pin via digitalWrite() around each transfer, the same division of
137/// responsibility Arduino's own SPIClass uses - so the underlying
138/// esp_lcd/spi_master device is configured with no CS pin of its own
139/// (spics_io_num = -1).
140class SPIClass {
141 public:
142 SPIClass() = default;
143 explicit SPIClass(spi_host_device_t host) : host_(host) {}
144
145 /// Initializes the SPI bus pins. Matches arduino-esp32's
146 /// SPIClass::begin(sck, miso, mosi, ss) - `ss` is accepted for
147 /// signature compatibility but unused (see class comment: CS is always
148 /// caller-driven here). Safe to call more than once.
149 bool begin(int sckPin, int misoPin = -1, int mosiPin = -1, int ssPin = -1) {
150 (void)ssPin;
151 if (busInitialized_) return true;
152 spi_bus_config_t busCfg = {};
153 busCfg.sclk_io_num = sckPin;
154 busCfg.miso_io_num = misoPin;
155 busCfg.mosi_io_num = mosiPin;
156 busCfg.quadwp_io_num = -1;
157 busCfg.quadhd_io_num = -1;
158 busCfg.max_transfer_sz = 4096;
159 if (spi_bus_initialize(host_, &busCfg, SPI_DMA_CH_AUTO) != ESP_OK) {
160 return false;
161 }
162 busInitialized_ = true;
163 return true;
164 }
165
166 /// Attaches (or reattaches, if the settings changed) an SPI device with
167 /// the given clock/mode and begins a transaction. Every transfer() in
168 /// this library happens between a beginTransaction()/endTransaction()
169 /// pair, matching the real SPIClass contract.
171 if (device_ != nullptr &&
172 (settings.clock != lastClock_ || settings.dataMode != lastMode_)) {
173 spi_bus_remove_device(device_);
174 device_ = nullptr;
175 }
176 if (device_ == nullptr) {
177 spi_device_interface_config_t devCfg = {};
178 devCfg.clock_speed_hz = static_cast<int>(settings.clock);
179 devCfg.mode = settings.dataMode;
180 devCfg.spics_io_num = -1; // caller drives CS itself - see class comment
181 devCfg.queue_size = 1;
182 // Only MSBFIRST is ever requested by this library's own drivers;
183 // best-effort LSB-first support for any other caller.
184 devCfg.flags = (settings.bitOrder == LSBFIRST)
185 ? (SPI_DEVICE_TXBIT_LSBFIRST | SPI_DEVICE_RXBIT_LSBFIRST)
186 : 0;
187 if (spi_bus_add_device(host_, &devCfg, &device_) != ESP_OK) {
188 device_ = nullptr;
189 return;
190 }
191 lastClock_ = settings.clock;
192 lastMode_ = settings.dataMode;
193 }
194 }
195
196 /// No-op: the underlying device stays attached for reuse across
197 /// transactions (see beginTransaction()) - nothing needs releasing per
198 /// transaction with the spi_master driver.
200
201 /// Full-duplex single-byte transfer, matching Arduino's
202 /// SPIClass::transfer(uint8_t).
203 uint8_t transfer(uint8_t data) {
204 if (device_ == nullptr) return 0;
205 uint8_t rx = 0;
206 spi_transaction_t t = {};
207 t.length = 8;
208 t.tx_buffer = &data;
209 t.rx_buffer = &rx;
210 spi_device_transmit(device_, &t);
211 return rx;
212 }
213
214 /// In-place full-duplex bulk transfer, matching Arduino's
215 /// SPIClass::transfer(void*, size_t) - `buf` is both the data sent and
216 /// (overwritten with) the data received.
217 void transfer(void* buf, size_t count) {
218 if (device_ == nullptr || buf == nullptr || count == 0) return;
219 spi_transaction_t t = {};
220 t.length = count * 8;
221 t.tx_buffer = buf;
222 t.rx_buffer = buf;
223 spi_device_transmit(device_, &t);
224 }
225
226 private:
227 spi_host_device_t host_ = SPI2_HOST;
228 spi_device_handle_t device_ = nullptr;
229 uint32_t lastClock_ = 0;
230 uint8_t lastMode_ = 0xFF;
231 bool busInitialized_ = false;
232};
233
234/// Global default instance, matching Arduino's bare `SPI` object (used as
235/// e.g. TouchDriverXPT2046's default constructor argument).
237
238// ---------------------------------------------------------------------
239// Wire (I2C)
240// ---------------------------------------------------------------------
241
242/// Legacy driver/i2c.h-backed stand-in for Arduino's TwoWire, covering
243/// exactly what this library's I2C touch drivers call: beginTransmission/
244/// write/endTransmission/requestFrom/available/read. endTransmission(false)
245/// defers the actual bus write so a following requestFrom() can chain it
246/// into one repeated-start transaction (write register address, repeated
247/// start, read data) instead of two separate stop/start transactions -
248/// several of this library's touch controllers document that this
249/// matters for reliable register reads.
250class TwoWire {
251 public:
252 TwoWire() = default;
253 explicit TwoWire(i2c_port_t port) : port_(port) {}
254
255 /// Initializes the I2C bus pins/speed. Matches arduino-esp32's
256 /// TwoWire::begin(sda, scl, freq). Safe to call more than once.
257 bool begin(int sdaPin, int sclPin, uint32_t freqHz = 100000) {
258 if (installed_) return true;
259 i2c_config_t cfg = {};
260 cfg.mode = I2C_MODE_MASTER;
261 cfg.sda_io_num = sdaPin;
262 cfg.scl_io_num = sclPin;
263 cfg.sda_pullup_en = GPIO_PULLUP_ENABLE;
264 cfg.scl_pullup_en = GPIO_PULLUP_ENABLE;
265 cfg.master.clk_speed = freqHz;
266 if (i2c_param_config(port_, &cfg) != ESP_OK) return false;
267 if (i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, 0) != ESP_OK) {
268 return false;
269 }
270 installed_ = true;
271 return true;
272 }
273
274 /// Starts buffering a write to `address` - matches
275 /// TwoWire::beginTransmission().
276 void beginTransmission(uint8_t address) {
277 address_ = address;
278 txLen_ = 0;
279 pendingRestart_ = false;
280 }
281
282 /// Buffers one byte for the next endTransmission() - matches
283 /// TwoWire::write(uint8_t). Silently drops bytes past the small fixed
284 /// buffer (this library's own drivers write at most a few register-
285 /// address bytes per transaction).
286 void write(uint8_t value) {
287 if (txLen_ < sizeof(txBuf_)) txBuf_[txLen_++] = value;
288 }
289
290 /// Sends the buffered write. `sendStop = false` defers the actual I2C
291 /// transaction to the following requestFrom() call, so it becomes a
292 /// single repeated-start write-then-read - see class comment. Returns
293 /// 0 on success, matching Arduino's TwoWire::endTransmission() (which
294 /// also uses 0 for success).
295 uint8_t endTransmission(bool sendStop = true) {
296 if (!sendStop) {
297 pendingRestart_ = true;
298 return 0;
299 }
300 const esp_err_t err = runWriteOnly();
301 return (err == ESP_OK) ? 0 : 4;
302 }
303
304 /// Reads up to `length` bytes from `address` into the internal receive
305 /// buffer - matches TwoWire::requestFrom(). If a prior
306 /// endTransmission(false) is pending, folds it into this call as one
307 /// repeated-start write-then-read transaction; otherwise this is a
308 /// plain read. Returns the number of bytes actually read (0 on error).
310 length = clampLength(length);
311 esp_err_t err;
312 if (pendingRestart_) {
313 err = runWriteThenRead(static_cast<uint8_t>(address), length);
314 pendingRestart_ = false;
315 } else {
316 err = runReadOnly(static_cast<uint8_t>(address), length);
317 }
318 rxLen_ = (err == ESP_OK) ? static_cast<size_t>(length) : 0;
319 rxPos_ = 0;
320 return rxLen_;
321 }
322
323 /// Bytes still unread from the last requestFrom() - matches
324 /// TwoWire::available().
325 int available() const { return static_cast<int>(rxLen_ - rxPos_); }
326
327 /// Reads one byte from the last requestFrom(), or -1 if none remain -
328 /// matches TwoWire::read().
329 int read() {
330 if (rxPos_ >= rxLen_) return -1;
331 return rxBuf_[rxPos_++];
332 }
333
334 private:
335 static constexpr size_t kBufSize = 8;
336 static constexpr TickType_t kTimeout = pdMS_TO_TICKS(1000);
337
338 i2c_port_t port_ = I2C_NUM_0;
339 uint8_t address_ = 0;
340 uint8_t txBuf_[kBufSize] = {};
341 size_t txLen_ = 0;
342 bool pendingRestart_ = false;
343 uint8_t rxBuf_[kBufSize] = {};
344 size_t rxLen_ = 0;
345 size_t rxPos_ = 0;
346 bool installed_ = false;
347
348 static int clampLength(int length) {
349 if (length <= 0) return 0;
350 if (static_cast<size_t>(length) > kBufSize) return static_cast<int>(kBufSize);
351 return length;
352 }
353
354 void queueReadBytes(i2c_cmd_handle_t cmd, int length) {
355 if (length > 1) {
356 i2c_master_read(cmd, rxBuf_, length - 1, I2C_MASTER_ACK);
357 }
358 i2c_master_read_byte(cmd, rxBuf_ + (length - 1), I2C_MASTER_NACK);
359 }
360
361 esp_err_t runWriteOnly() {
362 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
363 i2c_master_start(cmd);
364 i2c_master_write_byte(
365 cmd, static_cast<uint8_t>((address_ << 1) | I2C_MASTER_WRITE), true);
366 if (txLen_ > 0) i2c_master_write(cmd, txBuf_, txLen_, true);
367 i2c_master_stop(cmd);
368 const esp_err_t err = i2c_master_cmd_begin(port_, cmd, kTimeout);
369 i2c_cmd_link_delete(cmd);
370 txLen_ = 0;
371 return err;
372 }
373
374 esp_err_t runWriteThenRead(uint8_t address, int length) {
375 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
376 i2c_master_start(cmd);
377 i2c_master_write_byte(
378 cmd, static_cast<uint8_t>((address_ << 1) | I2C_MASTER_WRITE), true);
379 if (txLen_ > 0) i2c_master_write(cmd, txBuf_, txLen_, true);
380 i2c_master_start(cmd); // repeated start
381 i2c_master_write_byte(
382 cmd, static_cast<uint8_t>((address << 1) | I2C_MASTER_READ), true);
383 queueReadBytes(cmd, length);
384 i2c_master_stop(cmd);
385 const esp_err_t err = i2c_master_cmd_begin(port_, cmd, kTimeout);
386 i2c_cmd_link_delete(cmd);
387 txLen_ = 0;
388 return err;
389 }
390
391 esp_err_t runReadOnly(uint8_t address, int length) {
392 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
393 i2c_master_start(cmd);
394 i2c_master_write_byte(
395 cmd, static_cast<uint8_t>((address << 1) | I2C_MASTER_READ), true);
396 queueReadBytes(cmd, length);
397 i2c_master_stop(cmd);
398 const esp_err_t err = i2c_master_cmd_begin(port_, cmd, kTimeout);
399 i2c_cmd_link_delete(cmd);
400 return err;
401 }
402};
403
404/// Global default instance, matching Arduino's bare `Wire` object (used
405/// as e.g. TouchDriverFT6236's default constructor argument).
407
408} // namespace tinygpu
Definition: EmulationIDF.h:140
void beginTransaction(SPISettings settings)
Definition: EmulationIDF.h:170
void transfer(void *buf, size_t count)
Definition: EmulationIDF.h:217
SPIClass(spi_host_device_t host)
Definition: EmulationIDF.h:143
SPIClass()=default
bool begin(int sckPin, int misoPin=-1, int mosiPin=-1, int ssPin=-1)
Definition: EmulationIDF.h:149
uint8_t transfer(uint8_t data)
Definition: EmulationIDF.h:203
void endTransaction()
Definition: EmulationIDF.h:199
Definition: EmulationIDF.h:250
size_t requestFrom(int address, int length)
Definition: EmulationIDF.h:309
bool begin(int sdaPin, int sclPin, uint32_t freqHz=100000)
Definition: EmulationIDF.h:257
TwoWire()=default
uint8_t endTransmission(bool sendStop=true)
Definition: EmulationIDF.h:295
int read()
Definition: EmulationIDF.h:329
void write(uint8_t value)
Definition: EmulationIDF.h:286
int available() const
Definition: EmulationIDF.h:325
TwoWire(i2c_port_t port)
Definition: EmulationIDF.h:253
void beginTransmission(uint8_t address)
Definition: EmulationIDF.h:276
Definition: DSIBusESP32.h:19
constexpr int SPI_MODE1
Definition: EmulationIDF.h:115
constexpr int LSBFIRST
Definition: EmulationIDF.h:113
constexpr int SPI_MODE2
Definition: EmulationIDF.h:116
TwoWire Wire
Definition: EmulationIDF.h:406
SPIClass SPI
Definition: EmulationIDF.h:236
constexpr int LOW
Definition: EmulationIDF.h:74
unsigned long millis()
Definition: EmulationDesktop.h:43
constexpr int HIGH
Definition: EmulationIDF.h:73
void delay(uint32_t ms)
Definition: EmulationIDF.h:57
constexpr int SPI_MODE3
Definition: EmulationIDF.h:117
constexpr int INPUT_PULLUP
Definition: EmulationIDF.h:77
constexpr int MSBFIRST
Definition: EmulationIDF.h:112
constexpr int SPI_MODE0
Definition: EmulationIDF.h:114
void pinMode(int pin, int mode)
Definition: EmulationIDF.h:84
constexpr int INPUT
Definition: EmulationIDF.h:75
int digitalRead(int pin)
Reads a GPIO input level, matching Arduino's digitalRead().
Definition: EmulationIDF.h:103
void digitalWrite(int pin, int value)
Sets a GPIO output level, matching Arduino's digitalWrite().
Definition: EmulationIDF.h:97
constexpr int OUTPUT
Definition: EmulationIDF.h:76
Definition: EmulationIDF.h:121
SPISettings(uint32_t clockHz, uint8_t order, uint8_t mode)
Definition: EmulationIDF.h:127
uint8_t bitOrder
Definition: EmulationIDF.h:123
uint8_t dataMode
Definition: EmulationIDF.h:124
uint32_t clock
Definition: EmulationIDF.h:122
SPISettings()
Definition: EmulationIDF.h:126