Lumitronix_Iflex_Pro_Workshop
Library to interact with the iFlexPro
DotStarEsp32DmaSpiMethod.h
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2 LumitronixIFlex library helper functions for DotStars using Esp32, DMA and SPI (APA102).
3 
4 Written by Michael C. Miller.
5 DotStarEsp32DmaSpiMethod written by Louis Beaudoin (Pixelvation)
6 
7 I invest time and resources providing this open source code,
8 please support me by dontating (see https://github.com/Makuna)
9 
10 -------------------------------------------------------------------------
11 This file is part of the LUMITRONIX_iFlex_Workshop library.
12 
13 LumitronixIFlexBus is free software: you can redistribute it and/or modify
14 it under the terms of the GNU Lesser General Public License as
15 published by the Free Software Foundation, either version 3 of
16 the License, or (at your option) any later version.
17 
18 LumitronixIFlexBus is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU Lesser General Public License for more details.
22 
23 You should have received a copy of the GNU Lesser General Public
24 License along with LumitronixIFlex. If not, see
25 <http://www.gnu.org/licenses/>.
26 -------------------------------------------------------------------------*/
27 
28 #pragma once
29 
30 #include "driver/spi_master.h"
31 
32 // API and type use require newer IDF versions
33 #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 1)
34 
35 template<typename T_SPISPEED, typename T_SPIBUS> class DotStarEsp32DmaSpiMethodBase
36 {
37 public:
38  typedef typename T_SPISPEED::SettingsObject SettingsObject;
39 
40  DotStarEsp32DmaSpiMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
41  _sizeStartFrame(4 * T_SPIBUS::ParallelBits),
42  _sizePixelData(pixelCount * elementSize + settingsSize),
43  _sizeEndFrame((pixelCount + 15) / 16 * T_SPIBUS::ParallelBits) // 16 = div 2 (bit for every two pixels) div 8 (bits to bytes)
44  {
45  _spiBufferSize = _sizeStartFrame + _sizePixelData + _sizeEndFrame;
46 
47  // must have a 4 byte aligned buffer for i2s
48  uint32_t alignment = _spiBufferSize % 4;
49  if (alignment)
50  {
51  _spiBufferSize += 4 - alignment;
52  }
53 
54  _data = static_cast<uint8_t*>(malloc(_spiBufferSize));
55  _dmadata = static_cast<uint8_t*>(heap_caps_malloc(_spiBufferSize, MALLOC_CAP_DMA));
56 
57  // data cleared later in LumitronixIFlexBus::Begin()
58  }
59 
60  // Support constructor specifying pins by ignoring pins
61  DotStarEsp32DmaSpiMethodBase(uint8_t, uint8_t, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
62  DotStarEsp32DmaSpiMethodBase(pixelCount, elementSize, settingsSize)
63  {
64  }
65 
67  {
68  if (_spiHandle)
69  {
70  deinitSpiDevice();
71  esp_err_t ret = spi_bus_free(T_SPIBUS::SpiHostDevice);
72  ESP_ERROR_CHECK(ret);
73  }
74  free(_data);
75  heap_caps_free(_dmadata);
76  _spiHandle = NULL;
77  }
78 
79  bool IsReadyToUpdate() const
80  {
81  spi_transaction_t t;
82  spi_transaction_t* tptr = &t;
83 
84  esp_err_t ret = spi_device_get_trans_result(_spiHandle, &tptr, 0);
85 
86  // we are ready if prev result is back (ESP_OK) or if we got a timeout and
87  // transaction length of 0 (we didn't start a transaction)
88  return (ret == ESP_OK || (ret == ESP_ERR_TIMEOUT && 0 == _spiTransaction.length));
89  }
90 
91  void Initialize(int8_t sck, int8_t dat0, int8_t dat1, int8_t dat2, int8_t dat3, int8_t dat4, int8_t dat5, int8_t dat6, int8_t dat7, int8_t ss)
92  {
93  memset(_data, 0x00, _sizeStartFrame);
94  memset(_data + _sizeStartFrame + _sizePixelData, 0x00, _spiBufferSize - (_sizeStartFrame + _sizePixelData));
95 
96  _ssPin = ss;
97 
98  esp_err_t ret;
99  spi_bus_config_t buscfg = { 0 };
100 
101  buscfg.sclk_io_num = sck;
102  buscfg.data0_io_num = dat0;
103  buscfg.data1_io_num = dat1;
104  buscfg.data2_io_num = dat2;
105  buscfg.data3_io_num = dat3;
106  buscfg.data4_io_num = dat4;
107  buscfg.data5_io_num = dat5;
108  buscfg.data6_io_num = dat6;
109  buscfg.data7_io_num = dat7;
110  buscfg.max_transfer_sz = _spiBufferSize;
111  if (T_SPIBUS::ParallelBits == 8)
112  {
113  buscfg.flags = SPICOMMON_BUSFLAG_OCTAL;
114  }
115 
116  //Initialize the SPI bus
117  ret = spi_bus_initialize(T_SPIBUS::SpiHostDevice, &buscfg, SPI_DMA_CH_AUTO);
118  ESP_ERROR_CHECK(ret);
119 
120  _spiTransaction = { 0 };
121  initSpiDevice();
122  }
123 
124  void Initialize(int8_t sck, int8_t dat0, int8_t dat1, int8_t dat2, int8_t dat3, int8_t ss)
125  {
126  Initialize(sck, dat0, dat1, dat2, dat3, -1, -1, -1, -1, ss);
127  }
128 
129  void Initialize(int8_t sck, int8_t miso, int8_t mosi, int8_t ss)
130  {
131  Initialize(sck, mosi, miso, -1, -1, ss);
132  }
133 
134  // If pins aren't specified, initialize bus with just the default SCK and MOSI pins for the SPI peripheral (no SS, no >1-bit pins)
135  void Initialize()
136  {
137 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
138  if (T_SPIBUS::SpiHostDevice == VSPI_HOST)
139  {
140  Initialize(SCK, -1, MOSI, -1, -1, -1);
141  }
142  else
143  {
144  Initialize(14, -1, 13, -1, -1, -1);
145  }
146 #else
147  Initialize(SCK, -1, MOSI, -1, -1, -1);
148 #endif
149  }
150 
151  void Update(bool)
152  {
153  while(!IsReadyToUpdate())
154  {
155  portYIELD();
156  }
157 
158  memcpy(_dmadata, _data, _spiBufferSize);
159 
160  _spiTransaction = { 0 };
161  _spiTransaction.length = (_spiBufferSize) * 8; // in bits not bytes!
162 
163  if (T_SPIBUS::ParallelBits == 1)
164  {
165  _spiTransaction.flags = 0;
166  }
167  if (T_SPIBUS::ParallelBits == 2)
168  {
169  _spiTransaction.flags = SPI_TRANS_MODE_DIO;
170  }
171  if (T_SPIBUS::ParallelBits == 4)
172  {
173  _spiTransaction.flags = SPI_TRANS_MODE_QIO;
174  }
175  if (T_SPIBUS::ParallelBits == 8)
176  {
177  _spiTransaction.flags = SPI_TRANS_MODE_OCT;
178  }
179  _spiTransaction.tx_buffer = _dmadata;
180 
181  esp_err_t ret = spi_device_queue_trans(_spiHandle, &_spiTransaction, 0); //Transmit!
182  ESP_ERROR_CHECK(ret);
183  }
184 
186  {
187  // this method requires update to be called only if changes to buffer
188  return false;
189  }
190 
191  uint8_t* getData() const
192  {
193  return _data + _sizeStartFrame;
194  };
195 
196  size_t getDataSize() const
197  {
198  return _sizePixelData;
199  };
200 
201  void applySettings([[maybe_unused]] const SettingsObject& settings)
202  {
203  _speed.applySettings(settings);
204  if (_spiHandle)
205  {
206  deinitSpiDevice();
207  initSpiDevice();
208  }
209  }
210 
211 private:
212  void initSpiDevice()
213  {
214  spi_device_interface_config_t devcfg = {0};
215 
216  devcfg.clock_speed_hz = _speed.Clock;
217  devcfg.mode = 0; //SPI mode 0
218  devcfg.spics_io_num = _ssPin; //CS pin
219  devcfg.queue_size = 1;
220  if (T_SPIBUS::ParallelBits == 1)
221  {
222  devcfg.flags = 0;
223  }
224  if (T_SPIBUS::ParallelBits >= 2)
225  {
226  devcfg.flags = SPI_DEVICE_HALFDUPLEX;
227  }
228 
229  //Allocate the LEDs on the SPI bus
230  esp_err_t ret = spi_bus_add_device(T_SPIBUS::SpiHostDevice, &devcfg, &_spiHandle);
231  ESP_ERROR_CHECK(ret);
232  }
233 
234  void deinitSpiDevice()
235  {
236  while(!IsReadyToUpdate());
237  esp_err_t ret = spi_bus_remove_device(_spiHandle);
238  ESP_ERROR_CHECK(ret);
239  }
240 
241  const size_t _sizeStartFrame;
242  const size_t _sizePixelData; // Size of '_data' buffer below, minus (_sizeStartFrame + _sizeEndFrame)
243  const size_t _sizeEndFrame;
244 
245  size_t _spiBufferSize;
246  uint8_t* _data; // Holds start/end frames and LED color values
247  uint8_t* _dmadata; // Holds start/end frames and LED color values
248  spi_device_handle_t _spiHandle = NULL;
249  spi_transaction_t _spiTransaction;
250  T_SPISPEED _speed;
251  int8_t _ssPin;
252 };
253 
254 
255 // Unfortunately we have a bit of a mess with SPI bus names across different version of the ESP32
256 // e.g ESP32 has SPI, HSPI, and VSPI (1, 2, and 3), ESP32-S2 has SPI, FSPI, and HSPI (1, 2, and 3)
257 // and the S3 and C3 dropped the silly names entirely and just uses SPI1, SPI2, and SPI3.
258 //
259 // SPI1 can be only be used by ESP32 and supports up to 4 bit
260 // SPI2 supports up to 4 bit output across all of those devices (!) and supports 8 bit on S2 and S3
261 // SPI3 supports up to 4 bit output on ESP32 and S3, and 1 bit only on the S2
262 
264  WIDTH1 = 1,
265  WIDTH2 = 2,
266  WIDTH4 = 4,
267  WIDTH8 = 8,
268 };
269 
270 template <spi_host_device_t bus, spi_bus_width_t bits = WIDTH1>
272 {
273  const static spi_host_device_t SpiHostDevice = bus;
274  const static int ParallelBits = bits;
275 };
276 
277 // Define all valid ESP32 SPI Buses with a default speed
278 
279 // SPI1 -- ESP32 Only
280 #if defined(CONFIG_IDF_TARGET_ESP32)
281 typedef Esp32SpiBus<SPI1_HOST, WIDTH1> Esp32Spi1Bus;
282 typedef Esp32SpiBus<SPI1_HOST, WIDTH2> Esp32Spi12BitBus;
283 typedef Esp32SpiBus<SPI1_HOST, WIDTH4> Esp32Spi14BitBus;
284 
285 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi1Bus> DotStarEsp32DmaSpi1Method;
286 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi12BitBus> DotStarEsp32DmaSpi12BitMethod;
287 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi14BitBus> DotStarEsp32DmaSpi14BitMethod;
288 #endif
289 
290 // SPI2
294 #if SOC_SPI_SUPPORT_OCT
295 typedef Esp32SpiBus<SPI2_HOST, WIDTH8> Esp32Spi28BitBus;
296 #endif
297 
301 #if SOC_SPI_SUPPORT_OCT
302 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi28BitBus> DotStarEsp32DmaSpi28BitMethod;
303 #endif
304 
305 
306 // SPI3
307 #if (defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3))
308 typedef Esp32SpiBus<SPI3_HOST, WIDTH1> Esp32Spi3Bus;
309 typedef Esp32SpiBus<SPI3_HOST, WIDTH2> Esp32Spi32BitBus;
310 typedef Esp32SpiBus<SPI3_HOST, WIDTH4> Esp32Spi34BitBus;
311 
312 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi3Bus> DotStarEsp32DmaSpi3Method;
313 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi32BitBus> DotStarEsp32DmaSpi32BitMethod;
314 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi34BitBus> DotStarEsp32DmaSpi34BitMethod;
315 #endif
316 
317 #if defined(CONFIG_IDF_TARGET_ESP32S2)
318 typedef Esp32SpiBus<SPI3_HOST, WIDTH1> Esp32Spi3Bus;
319 typedef DotStarEsp32DmaSpiMethodBase<SpiSpeed10Mhz, Esp32Spi3Bus> DotStarEsp32DmaSpi3Method;
320 #endif
321 
322 // Default SpiDma methods if we don't care about bus. It's nice that every single ESP32 out there
323 // supports up to 4 bits on SPI2
324 
328 #if SOC_SPI_SUPPORT_OCT
329 typedef DotStarEsp32DmaSpi28BitMethod DotStarEsp32DmaSpi8BitMethod;
330 #endif
331 
332 #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 1)
DotStarEsp32DmaSpiMethodBase< SpiSpeed10Mhz, Esp32Spi2Bus > DotStarEsp32DmaSpi2Method
Definition: DotStarEsp32DmaSpiMethod.h:298
DotStarEsp32DmaSpiMethodBase< SpiSpeed10Mhz, Esp32Spi24BitBus > DotStarEsp32DmaSpi24BitMethod
Definition: DotStarEsp32DmaSpiMethod.h:300
DotStarEsp32DmaSpi2Method DotStarEsp32DmaSpiMethod
Definition: DotStarEsp32DmaSpiMethod.h:325
spi_bus_width_t
Definition: DotStarEsp32DmaSpiMethod.h:263
@ WIDTH1
Definition: DotStarEsp32DmaSpiMethod.h:264
@ WIDTH8
Definition: DotStarEsp32DmaSpiMethod.h:267
@ WIDTH4
Definition: DotStarEsp32DmaSpiMethod.h:266
@ WIDTH2
Definition: DotStarEsp32DmaSpiMethod.h:265
DotStarEsp32DmaSpi24BitMethod DotStarEsp32DmaSpi4BitMethod
Definition: DotStarEsp32DmaSpiMethod.h:327
DotStarEsp32DmaSpi22BitMethod DotStarEsp32DmaSpi2BitMethod
Definition: DotStarEsp32DmaSpiMethod.h:326
Esp32SpiBus< SPI2_HOST, WIDTH4 > Esp32Spi24BitBus
Definition: DotStarEsp32DmaSpiMethod.h:293
Esp32SpiBus< SPI2_HOST, WIDTH1 > Esp32Spi2Bus
Definition: DotStarEsp32DmaSpiMethod.h:291
DotStarEsp32DmaSpiMethodBase< SpiSpeed10Mhz, Esp32Spi22BitBus > DotStarEsp32DmaSpi22BitMethod
Definition: DotStarEsp32DmaSpiMethod.h:299
Esp32SpiBus< SPI2_HOST, WIDTH2 > Esp32Spi22BitBus
Definition: DotStarEsp32DmaSpiMethod.h:292
Definition: DotStarEsp32DmaSpiMethod.h:36
bool IsReadyToUpdate() const
Definition: DotStarEsp32DmaSpiMethod.h:79
void Update(bool)
Definition: DotStarEsp32DmaSpiMethod.h:151
void Initialize(int8_t sck, int8_t dat0, int8_t dat1, int8_t dat2, int8_t dat3, int8_t ss)
Definition: DotStarEsp32DmaSpiMethod.h:124
bool AlwaysUpdate()
Definition: DotStarEsp32DmaSpiMethod.h:185
DotStarEsp32DmaSpiMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize)
Definition: DotStarEsp32DmaSpiMethod.h:40
uint8_t * getData() const
Definition: DotStarEsp32DmaSpiMethod.h:191
void applySettings([[maybe_unused]] const SettingsObject &settings)
Definition: DotStarEsp32DmaSpiMethod.h:201
void Initialize(int8_t sck, int8_t dat0, int8_t dat1, int8_t dat2, int8_t dat3, int8_t dat4, int8_t dat5, int8_t dat6, int8_t dat7, int8_t ss)
Definition: DotStarEsp32DmaSpiMethod.h:91
void Initialize()
Definition: DotStarEsp32DmaSpiMethod.h:135
void Initialize(int8_t sck, int8_t miso, int8_t mosi, int8_t ss)
Definition: DotStarEsp32DmaSpiMethod.h:129
size_t getDataSize() const
Definition: DotStarEsp32DmaSpiMethod.h:196
DotStarEsp32DmaSpiMethodBase(uint8_t, uint8_t, uint16_t pixelCount, size_t elementSize, size_t settingsSize)
Definition: DotStarEsp32DmaSpiMethod.h:61
~DotStarEsp32DmaSpiMethodBase()
Definition: DotStarEsp32DmaSpiMethod.h:66
T_SPISPEED::SettingsObject SettingsObject
Definition: DotStarEsp32DmaSpiMethod.h:38
Definition: DotStarEsp32DmaSpiMethod.h:272
static const int ParallelBits
Definition: DotStarEsp32DmaSpiMethod.h:274
static const spi_host_device_t SpiHostDevice
Definition: DotStarEsp32DmaSpiMethod.h:273