SmartLeds.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. #pragma once
  2. #ifndef SMARTLEDS_H
  3. #define SMARTLEDS_H
  4. /*
  5. * A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32.
  6. *
  7. * Jan "yaqwsx" Mrázek <email@honzamrazek.cz>
  8. *
  9. * Based on the work by Martin F. Falatic - https://github.com/FozzTexx/ws2812-demo
  10. */
  11. /*
  12. * Permission is hereby granted, free of charge, to any person obtaining a copy
  13. * of this software and associated documentation files (the "Software"), to deal
  14. * in the Software without restriction, including without limitation the rights
  15. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  16. * copies of the Software, and to permit persons to whom the Software is
  17. * furnished to do so, subject to the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be included in
  20. * all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  23. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  24. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  25. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  26. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  27. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  28. * THE SOFTWARE.
  29. */
  30. #include <memory>
  31. #include <cassert>
  32. #include <cstring>
  33. #include "esp_idf_version.h"
  34. #if (ESP_IDF_VERSION_MAJOR >= 5)
  35. #include "soc/periph_defs.h"
  36. #include "esp_private/periph_ctrl.h"
  37. #include "soc/gpio_sig_map.h"
  38. #include "soc/gpio_periph.h"
  39. #include "soc/io_mux_reg.h"
  40. #include "esp_rom_gpio.h"
  41. #define gpio_pad_select_gpio esp_rom_gpio_pad_select_gpio
  42. #define gpio_matrix_in(a,b,c) esp_rom_gpio_connect_in_signal(a,b,c)
  43. #define gpio_matrix_out(a,b,c,d) esp_rom_gpio_connect_out_signal(a,b,c,d)
  44. #define ets_delay_us(a) esp_rom_delay_us(a)
  45. #endif
  46. #if defined ( ARDUINO )
  47. extern "C" { // ...someone forgot to put in the includes...
  48. #include "esp32-hal.h"
  49. #include "esp_intr_alloc.h"
  50. #include "esp_ipc.h"
  51. #include "driver/gpio.h"
  52. #include "driver/periph_ctrl.h"
  53. #include "freertos/semphr.h"
  54. #include "soc/rmt_struct.h"
  55. #include <driver/spi_master.h>
  56. #include "esp_idf_version.h"
  57. #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
  58. #include "soc/dport_reg.h"
  59. #endif
  60. }
  61. #elif defined ( ESP_PLATFORM )
  62. extern "C" { // ...someone forgot to put in the includes...
  63. #include <esp_intr_alloc.h>
  64. #include <esp_ipc.h>
  65. #include <driver/gpio.h>
  66. #include <freertos/FreeRTOS.h>
  67. #include <freertos/semphr.h>
  68. #include <soc/dport_reg.h>
  69. #include <soc/gpio_sig_map.h>
  70. #include <soc/rmt_struct.h>
  71. #include <driver/spi_master.h>
  72. }
  73. #include <stdio.h>
  74. #endif
  75. #if (ESP_IDF_VERSION_MAJOR >= 4) && (ESP_IDF_VERSION_MINOR > 1)
  76. #include "hal/gpio_ll.h"
  77. #else
  78. #include "soc/gpio_periph.h"
  79. #define esp_rom_delay_us ets_delay_us
  80. static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num)
  81. {
  82. if (gpio_num < 32) {
  83. return (hw->in >> gpio_num) & 0x1;
  84. } else {
  85. return (hw->in1.data >> (gpio_num - 32)) & 0x1;
  86. }
  87. }
  88. #endif
  89. #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
  90. #if !(configENABLE_BACKWARD_COMPATIBILITY == 1)
  91. #define xSemaphoreHandle SemaphoreHandle_t
  92. #endif
  93. #endif
  94. #include "Color.h"
  95. namespace detail {
  96. struct TimingParams {
  97. uint32_t T0H;
  98. uint32_t T1H;
  99. uint32_t T0L;
  100. uint32_t T1L;
  101. uint32_t TRS;
  102. };
  103. union RmtPulsePair {
  104. struct {
  105. int duration0:15;
  106. int level0:1;
  107. int duration1:15;
  108. int level1:1;
  109. };
  110. uint32_t value;
  111. };
  112. static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal
  113. static const int MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
  114. static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns
  115. } // namespace detail
  116. using LedType = detail::TimingParams;
  117. static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
  118. static const LedType LED_WS2812B = { 400, 850, 850, 400, 50100 };
  119. static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
  120. static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
  121. enum BufferType { SingleBuffer = 0, DoubleBuffer };
  122. enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2};
  123. class SmartLed {
  124. public:
  125. // The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds
  126. // can't fill the RMT buffer fast enough, resulting in rendering artifacts.
  127. // Usually, that means you have to set isrCore == CoreSecond.
  128. //
  129. // If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running,
  130. // so you can't use it if you define SmartLed as global variable.
  131. SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer, IsrCore isrCore = CoreCurrent)
  132. : _timing( type ),
  133. _channel( channel ),
  134. _count( count ),
  135. _firstBuffer( new Rgb[ count ] ),
  136. _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ),
  137. _finishedFlag( xSemaphoreCreateBinary() )
  138. {
  139. assert( channel >= 0 && channel < 8 );
  140. assert( ledForChannel( channel ) == nullptr );
  141. xSemaphoreGive( _finishedFlag );
  142. DPORT_SET_PERI_REG_MASK( DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN );
  143. DPORT_CLEAR_PERI_REG_MASK( DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST );
  144. PIN_FUNC_SELECT( GPIO_PIN_MUX_REG[ pin ], 2 );
  145. gpio_set_direction( static_cast< gpio_num_t >( pin ), GPIO_MODE_OUTPUT );
  146. gpio_matrix_out( static_cast< gpio_num_t >( pin ), RMT_SIG_OUT0_IDX + _channel, 0, 0 );
  147. initChannel( _channel );
  148. RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES;
  149. RMT.int_ena.val |= 1 << ( 24 + _channel );
  150. RMT.int_ena.val |= 1 << ( 3 * _channel );
  151. _bitToRmt[ 0 ].level0 = 1;
  152. _bitToRmt[ 0 ].level1 = 0;
  153. _bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
  154. _bitToRmt[ 0 ].duration1 = _timing.T0L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
  155. _bitToRmt[ 1 ].level0 = 1;
  156. _bitToRmt[ 1 ].level1 = 0;
  157. _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
  158. _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
  159. if ( !anyAlive() ) {
  160. _interruptCore = isrCore;
  161. if(isrCore != CoreCurrent) {
  162. ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL));
  163. } else {
  164. registerInterrupt(NULL);
  165. }
  166. }
  167. ledForChannel( channel ) = this;
  168. }
  169. ~SmartLed() {
  170. ledForChannel( _channel ) = nullptr;
  171. if ( !anyAlive() ) {
  172. if(_interruptCore != CoreCurrent) {
  173. ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL));
  174. } else {
  175. unregisterInterrupt(NULL);
  176. }
  177. }
  178. vSemaphoreDelete( _finishedFlag );
  179. }
  180. Rgb& operator[]( int idx ) {
  181. return _firstBuffer[ idx ];
  182. }
  183. const Rgb& operator[]( int idx ) const {
  184. return _firstBuffer[ idx ];
  185. }
  186. void show() {
  187. _buffer = _firstBuffer.get();
  188. startTransmission();
  189. swapBuffers();
  190. }
  191. bool wait( TickType_t timeout = portMAX_DELAY ) {
  192. if( xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE ) {
  193. xSemaphoreGive( _finishedFlag );
  194. return true;
  195. }
  196. return false;
  197. }
  198. int size() const {
  199. return _count;
  200. }
  201. Rgb *begin() { return _firstBuffer.get(); }
  202. const Rgb *begin() const { return _firstBuffer.get(); }
  203. const Rgb *cbegin() const { return _firstBuffer.get(); }
  204. Rgb *end() { return _firstBuffer.get() + _count; }
  205. const Rgb *end() const { return _firstBuffer.get() + _count; }
  206. const Rgb *cend() const { return _firstBuffer.get() + _count; }
  207. private:
  208. static intr_handle_t _interruptHandle;
  209. static IsrCore _interruptCore;
  210. static void initChannel( int channel ) {
  211. RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode.
  212. RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer
  213. RMT.conf_ch[ channel ].conf0.div_cnt = detail::DIVIDER;
  214. RMT.conf_ch[ channel ].conf0.mem_size = 1;
  215. RMT.conf_ch[ channel ].conf0.carrier_en = 0;
  216. RMT.conf_ch[ channel ].conf0.carrier_out_lv = 1;
  217. RMT.conf_ch[ channel ].conf0.mem_pd = 0;
  218. RMT.conf_ch[ channel ].conf1.rx_en = 0;
  219. RMT.conf_ch[ channel ].conf1.mem_owner = 0;
  220. RMT.conf_ch[ channel ].conf1.tx_conti_mode = 0; //loop back mode.
  221. RMT.conf_ch[ channel ].conf1.ref_always_on = 1; // use apb clock: 80M
  222. RMT.conf_ch[ channel ].conf1.idle_out_en = 1;
  223. RMT.conf_ch[ channel ].conf1.idle_out_lv = 0;
  224. }
  225. static void registerInterrupt(void *) {
  226. ESP_ERROR_CHECK(esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle));
  227. }
  228. static void unregisterInterrupt(void*) {
  229. esp_intr_free( _interruptHandle );
  230. }
  231. static SmartLed*& IRAM_ATTR ledForChannel( int channel );
  232. static void IRAM_ATTR interruptHandler( void* );
  233. void IRAM_ATTR copyRmtHalfBlock();
  234. void swapBuffers() {
  235. if ( _secondBuffer )
  236. _firstBuffer.swap( _secondBuffer );
  237. }
  238. void startTransmission() {
  239. // Invalid use of the library
  240. if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE )
  241. abort();
  242. _pixelPosition = _componentPosition = _halfIdx = 0;
  243. copyRmtHalfBlock();
  244. if ( _pixelPosition < _count )
  245. copyRmtHalfBlock();
  246. RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1;
  247. RMT.conf_ch[ _channel ].conf1.tx_start = 1;
  248. }
  249. static bool anyAlive() {
  250. for ( int i = 0; i != 8; i++ )
  251. if ( ledForChannel( i ) != nullptr ) return true;
  252. return false;
  253. }
  254. const LedType& _timing;
  255. int _channel;
  256. detail::RmtPulsePair _bitToRmt[ 2 ];
  257. int _count;
  258. std::unique_ptr< Rgb[] > _firstBuffer;
  259. std::unique_ptr< Rgb[] > _secondBuffer;
  260. Rgb *_buffer;
  261. xSemaphoreHandle _finishedFlag;
  262. int _pixelPosition;
  263. int _componentPosition;
  264. int _halfIdx;
  265. };
  266. class Apa102 {
  267. public:
  268. struct ApaRgb {
  269. ApaRgb( uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF )
  270. : v( 0xE0 | v ), b( b ), g( g ), r( r )
  271. {}
  272. ApaRgb& operator=( const Rgb& o ) {
  273. r = o.r;
  274. g = o.g;
  275. b = o.b;
  276. return *this;
  277. }
  278. ApaRgb& operator=( const Hsv& o ) {
  279. *this = Rgb{ o };
  280. return *this;
  281. }
  282. uint8_t v, b, g, r;
  283. };
  284. static const int FINAL_FRAME_SIZE = 4;
  285. static const int TRANS_COUNT = 2 + 8;
  286. Apa102( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer )
  287. : _count( count ),
  288. _firstBuffer( new ApaRgb[ count ] ),
  289. _secondBuffer( doubleBuffer ? new ApaRgb[ count ] : nullptr ),
  290. _initFrame( 0 )
  291. {
  292. spi_bus_config_t buscfg;
  293. memset( &buscfg, 0, sizeof( buscfg ) );
  294. buscfg.mosi_io_num = datapin;
  295. buscfg.miso_io_num = -1;
  296. buscfg.sclk_io_num = clkpin;
  297. buscfg.quadwp_io_num = -1;
  298. buscfg.quadhd_io_num = -1;
  299. buscfg.max_transfer_sz = 65535;
  300. spi_device_interface_config_t devcfg;
  301. memset( &devcfg, 0, sizeof( devcfg ) );
  302. devcfg.clock_speed_hz = 1000000;
  303. devcfg.mode = 0;
  304. devcfg.spics_io_num = -1;
  305. devcfg.queue_size = TRANS_COUNT;
  306. devcfg.pre_cb = nullptr;
  307. auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
  308. assert( ret == ESP_OK );
  309. ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
  310. assert( ret == ESP_OK );
  311. std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF );
  312. }
  313. ~Apa102() {
  314. // ToDo
  315. }
  316. ApaRgb& operator[]( int idx ) {
  317. return _firstBuffer[ idx ];
  318. }
  319. const ApaRgb& operator[]( int idx ) const {
  320. return _firstBuffer[ idx ];
  321. }
  322. void show() {
  323. _buffer = _firstBuffer.get();
  324. startTransmission();
  325. swapBuffers();
  326. }
  327. void wait() {
  328. for ( int i = 0; i != _transCount; i++ ) {
  329. spi_transaction_t *t;
  330. spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
  331. }
  332. }
  333. private:
  334. void swapBuffers() {
  335. if ( _secondBuffer )
  336. _firstBuffer.swap( _secondBuffer );
  337. }
  338. void startTransmission() {
  339. for ( int i = 0; i != TRANS_COUNT; i++ ) {
  340. _transactions[ i ].cmd = 0;
  341. _transactions[ i ].addr = 0;
  342. _transactions[ i ].flags = 0;
  343. _transactions[ i ].rxlength = 0;
  344. _transactions[ i ].rx_buffer = nullptr;
  345. }
  346. // Init frame
  347. _transactions[ 0 ].length = 32;
  348. _transactions[ 0 ].tx_buffer = &_initFrame;
  349. spi_device_queue_trans( _spi, _transactions + 0, portMAX_DELAY );
  350. // Data
  351. _transactions[ 1 ].length = 32 * _count;
  352. _transactions[ 1 ].tx_buffer = _buffer;
  353. spi_device_queue_trans( _spi, _transactions + 1, portMAX_DELAY );
  354. _transCount = 2;
  355. // End frame
  356. for ( int i = 0; i != 1 + _count / 32 / FINAL_FRAME_SIZE; i++ ) {
  357. _transactions[ 2 + i ].length = 32 * FINAL_FRAME_SIZE;
  358. _transactions[ 2 + i ].tx_buffer = _finalFrame;
  359. spi_device_queue_trans( _spi, _transactions + 2 + i, portMAX_DELAY );
  360. _transCount++;
  361. }
  362. }
  363. spi_device_handle_t _spi;
  364. int _count;
  365. std::unique_ptr< ApaRgb[] > _firstBuffer, _secondBuffer;
  366. ApaRgb *_buffer;
  367. spi_transaction_t _transactions[ TRANS_COUNT ];
  368. int _transCount;
  369. uint32_t _initFrame;
  370. uint32_t _finalFrame[ FINAL_FRAME_SIZE ];
  371. };
  372. class LDP8806 {
  373. public:
  374. struct LDP8806_GRB {
  375. LDP8806_GRB( uint8_t g_7bit = 0, uint8_t r_7bit = 0, uint32_t b_7bit = 0 )
  376. : g( g_7bit ), r( r_7bit ), b( b_7bit )
  377. {
  378. }
  379. LDP8806_GRB& operator=( const Rgb& o ) {
  380. //Convert 8->7bit colour
  381. r = ( o.r * 127 / 256 ) | 0x80;
  382. g = ( o.g * 127 / 256 ) | 0x80;
  383. b = ( o.b * 127 / 256 ) | 0x80;
  384. return *this;
  385. }
  386. LDP8806_GRB& operator=( const Hsv& o ) {
  387. *this = Rgb{ o };
  388. return *this;
  389. }
  390. uint8_t g, r, b;
  391. };
  392. static const int LED_FRAME_SIZE_BYTES = sizeof( LDP8806_GRB );
  393. static const int LATCH_FRAME_SIZE_BYTES = 3;
  394. static const int TRANS_COUNT_MAX = 20;//Arbitrary, supports up to 600 LED
  395. LDP8806( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000 )
  396. : _count( count ),
  397. _firstBuffer( new LDP8806_GRB[ count ] ),
  398. _secondBuffer( doubleBuffer ? new LDP8806_GRB[ count ] : nullptr ),
  399. // one 'latch'/start-of-data mark frame for every 32 leds
  400. _latchFrames( ( count + 31 ) / 32 )
  401. {
  402. spi_bus_config_t buscfg;
  403. memset( &buscfg, 0, sizeof( buscfg ) );
  404. buscfg.mosi_io_num = datapin;
  405. buscfg.miso_io_num = -1;
  406. buscfg.sclk_io_num = clkpin;
  407. buscfg.quadwp_io_num = -1;
  408. buscfg.quadhd_io_num = -1;
  409. buscfg.max_transfer_sz = 65535;
  410. spi_device_interface_config_t devcfg;
  411. memset( &devcfg, 0, sizeof( devcfg ) );
  412. devcfg.clock_speed_hz = clock_speed_hz;
  413. devcfg.mode = 0;
  414. devcfg.spics_io_num = -1;
  415. devcfg.queue_size = TRANS_COUNT_MAX;
  416. devcfg.pre_cb = nullptr;
  417. auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
  418. assert( ret == ESP_OK );
  419. ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
  420. assert( ret == ESP_OK );
  421. std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 );
  422. }
  423. ~LDP8806() {
  424. // noop
  425. }
  426. LDP8806_GRB& operator[]( int idx ) {
  427. return _firstBuffer[ idx ];
  428. }
  429. const LDP8806_GRB& operator[]( int idx ) const {
  430. return _firstBuffer[ idx ];
  431. }
  432. void show() {
  433. _buffer = _firstBuffer.get();
  434. startTransmission();
  435. swapBuffers();
  436. }
  437. void wait() {
  438. while ( _transCount-- ) {
  439. spi_transaction_t *t;
  440. spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
  441. }
  442. }
  443. private:
  444. void swapBuffers() {
  445. if ( _secondBuffer )
  446. _firstBuffer.swap( _secondBuffer );
  447. }
  448. void startTransmission() {
  449. _transCount = 0;
  450. for ( int i = 0; i != TRANS_COUNT_MAX; i++ ) {
  451. _transactions[ i ].cmd = 0;
  452. _transactions[ i ].addr = 0;
  453. _transactions[ i ].flags = 0;
  454. _transactions[ i ].rxlength = 0;
  455. _transactions[ i ].rx_buffer = nullptr;
  456. }
  457. // LED Data
  458. _transactions[ 0 ].length = ( LED_FRAME_SIZE_BYTES * 8 ) * _count;
  459. _transactions[ 0 ].tx_buffer = _buffer;
  460. spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
  461. _transCount++;
  462. // 'latch'/start-of-data marker frames
  463. for ( int i = 0; i < _latchFrames; i++ ) {
  464. _transactions[ _transCount ].length = ( LATCH_FRAME_SIZE_BYTES * 8 );
  465. _transactions[ _transCount ].tx_buffer = _latchBuffer;
  466. spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
  467. _transCount++;
  468. }
  469. }
  470. spi_device_handle_t _spi;
  471. int _count;
  472. std::unique_ptr< LDP8806_GRB[] > _firstBuffer, _secondBuffer;
  473. LDP8806_GRB *_buffer;
  474. spi_transaction_t _transactions[ TRANS_COUNT_MAX ];
  475. int _transCount;
  476. int _latchFrames;
  477. uint8_t _latchBuffer[ LATCH_FRAME_SIZE_BYTES ];
  478. };
  479. #endif //SMARTLEDS_H