forked from adafruit/Adafruit-GFX-Library
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Adafruit_SPITFT.cpp
2511 lines (2382 loc) · 93.3 KB
/
Adafruit_SPITFT.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*!
* @file Adafruit_SPITFT.cpp
*
* @mainpage Adafruit SPI TFT Displays (and some others)
*
* @section intro_sec Introduction
*
* Part of Adafruit's GFX graphics library. Originally this class was
* written to handle a range of color TFT displays connected via SPI,
* but over time this library and some display-specific subclasses have
* mutated to include some color OLEDs as well as parallel-interfaced
* displays. The name's been kept for the sake of older code.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
* @section dependencies Dependencies
*
* This library depends on <a href="https://github.com/adafruit/Adafruit_GFX">
* Adafruit_GFX</a> being present on your system. Please make sure you have
* installed the latest version before using this library.
*
* @section author Author
*
* Written by Limor "ladyada" Fried for Adafruit Industries,
* with contributions from the open source community.
*
* @section license License
*
* BSD license, all text here must be included in any redistribution.
*/
#if !defined(__AVR_ATtiny85__) // Not for ATtiny, at all
#include "Adafruit_SPITFT.h"
#if defined(__AVR__)
#if defined(__AVR_XMEGA__) // only tested with __AVR_ATmega4809__
#define AVR_WRITESPI(x) \
for (SPI0_DATA = (x); (!(SPI0_INTFLAGS & _BV(SPI_IF_bp)));)
#else
#define AVR_WRITESPI(x) for (SPDR = (x); (!(SPSR & _BV(SPIF)));)
#endif
#endif
#if defined(PORT_IOBUS)
// On SAMD21, redefine digitalPinToPort() to use the slightly-faster
// PORT_IOBUS rather than PORT (not needed on SAMD51).
#undef digitalPinToPort
#define digitalPinToPort(P) (&(PORT_IOBUS->Group[g_APinDescription[P].ulPort]))
#endif // end PORT_IOBUS
#if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
// #pragma message ("GFX DMA IS ENABLED. HIGHLY EXPERIMENTAL.")
#include "wiring_private.h" // pinPeripheral() function
#include <Adafruit_ZeroDMA.h>
#include <malloc.h> // memalign() function
#define tcNum 2 // Timer/Counter for parallel write strobe PWM
#define wrPeripheral PIO_CCL // Use CCL to invert write strobe
// DMA transfer-in-progress indicator and callback
static volatile bool dma_busy = false;
static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
#if defined(__SAMD51__)
// Timer/counter info by index #
static const struct {
Tc *tc; // -> Timer/Counter base address
int gclk; // GCLK ID
int evu; // EVSYS user ID
} tcList[] = {{TC0, TC0_GCLK_ID, EVSYS_ID_USER_TC0_EVU},
{TC1, TC1_GCLK_ID, EVSYS_ID_USER_TC1_EVU},
{TC2, TC2_GCLK_ID, EVSYS_ID_USER_TC2_EVU},
{TC3, TC3_GCLK_ID, EVSYS_ID_USER_TC3_EVU},
#if defined(TC4)
{TC4, TC4_GCLK_ID, EVSYS_ID_USER_TC4_EVU},
#endif
#if defined(TC5)
{TC5, TC5_GCLK_ID, EVSYS_ID_USER_TC5_EVU},
#endif
#if defined(TC6)
{TC6, TC6_GCLK_ID, EVSYS_ID_USER_TC6_EVU},
#endif
#if defined(TC7)
{TC7, TC7_GCLK_ID, EVSYS_ID_USER_TC7_EVU}
#endif
};
#define NUM_TIMERS (sizeof tcList / sizeof tcList[0]) ///< # timer/counters
#endif // end __SAMD51__
#endif // end USE_SPI_DMA
// Possible values for Adafruit_SPITFT.connection:
#define TFT_HARD_SPI 0 ///< Display interface = hardware SPI
#define TFT_SOFT_SPI 1 ///< Display interface = software SPI
#define TFT_PARALLEL 2 ///< Display interface = 8- or 16-bit parallel
// CONSTRUCTORS ------------------------------------------------------------
/*!
@brief Adafruit_SPITFT constructor for software (bitbang) SPI.
@param w Display width in pixels at default rotation setting (0).
@param h Display height in pixels at default rotation setting (0).
@param cs Arduino pin # for chip-select (-1 if unused, tie CS low).
@param dc Arduino pin # for data/command select (required).
@param mosi Arduino pin # for bitbang SPI MOSI signal (required).
@param sck Arduino pin # for bitbang SPI SCK signal (required).
@param rst Arduino pin # for display reset (optional, display reset
can be tied to MCU reset, default of -1 means unused).
@param miso Arduino pin # for bitbang SPI MISO signal (optional,
-1 default, many displays don't support SPI read).
@note Output pins are not initialized; application typically will
need to call subclass' begin() function, which in turn calls
this library's initSPI() function to initialize pins.
*/
Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
int8_t mosi, int8_t sck, int8_t rst,
int8_t miso)
: Adafruit_GFX(w, h), connection(TFT_SOFT_SPI), _rst(rst), _cs(cs),
_dc(dc) {
swspi._sck = sck;
swspi._mosi = mosi;
swspi._miso = miso;
#if defined(USE_FAST_PINIO)
#if defined(HAS_PORT_SET_CLR)
#if defined(CORE_TEENSY)
#if !defined(KINETISK)
dcPinMask = digitalPinToBitMask(dc);
swspi.sckPinMask = digitalPinToBitMask(sck);
swspi.mosiPinMask = digitalPinToBitMask(mosi);
#endif
dcPortSet = portSetRegister(dc);
dcPortClr = portClearRegister(dc);
swspi.sckPortSet = portSetRegister(sck);
swspi.sckPortClr = portClearRegister(sck);
swspi.mosiPortSet = portSetRegister(mosi);
swspi.mosiPortClr = portClearRegister(mosi);
if (cs >= 0) {
#if !defined(KINETISK)
csPinMask = digitalPinToBitMask(cs);
#endif
csPortSet = portSetRegister(cs);
csPortClr = portClearRegister(cs);
} else {
#if !defined(KINETISK)
csPinMask = 0;
#endif
csPortSet = dcPortSet;
csPortClr = dcPortClr;
}
if (miso >= 0) {
swspi.misoPort = portInputRegister(miso);
#if !defined(KINETISK)
swspi.misoPinMask = digitalPinToBitMask(miso);
#endif
} else {
swspi.misoPort = portInputRegister(dc);
}
#else // !CORE_TEENSY
dcPinMask = digitalPinToBitMask(dc);
swspi.sckPinMask = digitalPinToBitMask(sck);
swspi.mosiPinMask = digitalPinToBitMask(mosi);
dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
swspi.sckPortSet = &(PORT->Group[g_APinDescription[sck].ulPort].OUTSET.reg);
swspi.sckPortClr = &(PORT->Group[g_APinDescription[sck].ulPort].OUTCLR.reg);
swspi.mosiPortSet = &(PORT->Group[g_APinDescription[mosi].ulPort].OUTSET.reg);
swspi.mosiPortClr = &(PORT->Group[g_APinDescription[mosi].ulPort].OUTCLR.reg);
if (cs >= 0) {
csPinMask = digitalPinToBitMask(cs);
csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPortSet = dcPortSet;
csPortClr = dcPortClr;
csPinMask = 0;
}
if (miso >= 0) {
swspi.misoPinMask = digitalPinToBitMask(miso);
swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(miso));
} else {
swspi.misoPinMask = 0;
swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(dc));
}
#endif // end !CORE_TEENSY
#else // !HAS_PORT_SET_CLR
dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
dcPinMaskSet = digitalPinToBitMask(dc);
swspi.sckPort = (PORTreg_t)portOutputRegister(digitalPinToPort(sck));
swspi.sckPinMaskSet = digitalPinToBitMask(sck);
swspi.mosiPort = (PORTreg_t)portOutputRegister(digitalPinToPort(mosi));
swspi.mosiPinMaskSet = digitalPinToBitMask(mosi);
if (cs >= 0) {
csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
csPinMaskSet = digitalPinToBitMask(cs);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPort = dcPort;
csPinMaskSet = 0;
}
if (miso >= 0) {
swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(miso));
swspi.misoPinMask = digitalPinToBitMask(miso);
} else {
swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(dc));
swspi.misoPinMask = 0;
}
csPinMaskClr = ~csPinMaskSet;
dcPinMaskClr = ~dcPinMaskSet;
swspi.sckPinMaskClr = ~swspi.sckPinMaskSet;
swspi.mosiPinMaskClr = ~swspi.mosiPinMaskSet;
#endif // !end HAS_PORT_SET_CLR
#endif // end USE_FAST_PINIO
}
/*!
@brief Adafruit_SPITFT constructor for hardware SPI using the board's
default SPI peripheral.
@param w Display width in pixels at default rotation setting (0).
@param h Display height in pixels at default rotation setting (0).
@param cs Arduino pin # for chip-select (-1 if unused, tie CS low).
@param dc Arduino pin # for data/command select (required).
@param rst Arduino pin # for display reset (optional, display reset
can be tied to MCU reset, default of -1 means unused).
@note Output pins are not initialized; application typically will
need to call subclass' begin() function, which in turn calls
this library's initSPI() function to initialize pins.
*/
#if defined(ESP8266) // See notes below
Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
int8_t rst)
: Adafruit_GFX(w, h), connection(TFT_HARD_SPI), _rst(rst), _cs(cs),
_dc(dc) {
hwspi._spi = &SPI;
}
#else // !ESP8266
Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
int8_t rst)
: Adafruit_SPITFT(w, h, &SPI, cs, dc, rst) {
// This just invokes the hardware SPI constructor below,
// passing the default SPI device (&SPI).
}
#endif // end !ESP8266
#if !defined(ESP8266)
// ESP8266 compiler freaks out at this constructor -- it can't disambiguate
// beteween the SPIClass pointer (argument #3) and a regular integer.
// Solution here it to just not offer this variant on the ESP8266. You can
// use the default hardware SPI peripheral, or you can use software SPI,
// but if there's any library out there that creates a 'virtual' SPIClass
// peripheral and drives it with software bitbanging, that's not supported.
/*!
@brief Adafruit_SPITFT constructor for hardware SPI using a specific
SPI peripheral.
@param w Display width in pixels at default rotation (0).
@param h Display height in pixels at default rotation (0).
@param spiClass Pointer to SPIClass type (e.g. &SPI or &SPI1).
@param cs Arduino pin # for chip-select (-1 if unused, tie CS low).
@param dc Arduino pin # for data/command select (required).
@param rst Arduino pin # for display reset (optional, display reset
can be tied to MCU reset, default of -1 means unused).
@note Output pins are not initialized in constructor; application
typically will need to call subclass' begin() function, which
in turn calls this library's initSPI() function to initialize
pins. EXCEPT...if you have built your own SERCOM SPI peripheral
(calling the SPIClass constructor) rather than one of the
built-in SPI devices (e.g. &SPI, &SPI1 and so forth), you will
need to call the begin() function for your object as well as
pinPeripheral() for the MOSI, MISO and SCK pins to configure
GPIO manually. Do this BEFORE calling the display-specific
begin or init function. Unfortunate but unavoidable.
*/
Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, SPIClass *spiClass,
int8_t cs, int8_t dc, int8_t rst)
: Adafruit_GFX(w, h), connection(TFT_HARD_SPI), _rst(rst), _cs(cs),
_dc(dc) {
hwspi._spi = spiClass;
#if defined(USE_FAST_PINIO)
#if defined(HAS_PORT_SET_CLR)
#if defined(CORE_TEENSY)
#if !defined(KINETISK)
dcPinMask = digitalPinToBitMask(dc);
#endif
dcPortSet = portSetRegister(dc);
dcPortClr = portClearRegister(dc);
if (cs >= 0) {
#if !defined(KINETISK)
csPinMask = digitalPinToBitMask(cs);
#endif
csPortSet = portSetRegister(cs);
csPortClr = portClearRegister(cs);
} else { // see comments below
#if !defined(KINETISK)
csPinMask = 0;
#endif
csPortSet = dcPortSet;
csPortClr = dcPortClr;
}
#else // !CORE_TEENSY
dcPinMask = digitalPinToBitMask(dc);
dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
if (cs >= 0) {
csPinMask = digitalPinToBitMask(cs);
csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPortSet = dcPortSet;
csPortClr = dcPortClr;
csPinMask = 0;
}
#endif // end !CORE_TEENSY
#else // !HAS_PORT_SET_CLR
dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
dcPinMaskSet = digitalPinToBitMask(dc);
if (cs >= 0) {
csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
csPinMaskSet = digitalPinToBitMask(cs);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPort = dcPort;
csPinMaskSet = 0;
}
csPinMaskClr = ~csPinMaskSet;
dcPinMaskClr = ~dcPinMaskSet;
#endif // end !HAS_PORT_SET_CLR
#endif // end USE_FAST_PINIO
}
#endif // end !ESP8266
/*!
@brief Adafruit_SPITFT constructor for parallel display connection.
@param w Display width in pixels at default rotation (0).
@param h Display height in pixels at default rotation (0).
@param busWidth If tft16 (enumeration in header file), is a 16-bit
parallel connection, else 8-bit.
16-bit isn't fully implemented or tested yet so
applications should pass "tft8bitbus" for now...needed to
stick a required enum argument in there to
disambiguate this constructor from the soft-SPI case.
Argument is ignored on 8-bit architectures (no 'wide'
support there since PORTs are 8 bits anyway).
@param d0 Arduino pin # for data bit 0 (1+ are extrapolated).
The 8 (or 16) data bits MUST be contiguous and byte-
aligned (or word-aligned for wide interface) within
the same PORT register (might not correspond to
Arduino pin sequence).
@param wr Arduino pin # for write strobe (required).
@param dc Arduino pin # for data/command select (required).
@param cs Arduino pin # for chip-select (optional, -1 if unused,
tie CS low).
@param rst Arduino pin # for display reset (optional, display reset
can be tied to MCU reset, default of -1 means unused).
@param rd Arduino pin # for read strobe (optional, -1 if unused).
@note Output pins are not initialized; application typically will need
to call subclass' begin() function, which in turn calls this
library's initSPI() function to initialize pins.
Yes, the name is a misnomer...this library originally handled
only SPI displays, parallel being a recent addition (but not
wanting to break existing code).
*/
Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, tftBusWidth busWidth,
int8_t d0, int8_t wr, int8_t dc, int8_t cs,
int8_t rst, int8_t rd)
: Adafruit_GFX(w, h), connection(TFT_PARALLEL), _rst(rst), _cs(cs),
_dc(dc) {
tft8._d0 = d0;
tft8._wr = wr;
tft8._rd = rd;
tft8.wide = (busWidth == tft16bitbus);
#if defined(USE_FAST_PINIO)
#if defined(HAS_PORT_SET_CLR)
#if defined(CORE_TEENSY)
tft8.wrPortSet = portSetRegister(wr);
tft8.wrPortClr = portClearRegister(wr);
#if !defined(KINETISK)
dcPinMask = digitalPinToBitMask(dc);
#endif
dcPortSet = portSetRegister(dc);
dcPortClr = portClearRegister(dc);
if (cs >= 0) {
#if !defined(KINETISK)
csPinMask = digitalPinToBitMask(cs);
#endif
csPortSet = portSetRegister(cs);
csPortClr = portClearRegister(cs);
} else { // see comments below
#if !defined(KINETISK)
csPinMask = 0;
#endif
csPortSet = dcPortSet;
csPortClr = dcPortClr;
}
if (rd >= 0) { // if read-strobe pin specified...
#if defined(KINETISK)
tft8.rdPinMask = 1;
#else // !KINETISK
tft8.rdPinMask = digitalPinToBitMask(rd);
#endif
tft8.rdPortSet = portSetRegister(rd);
tft8.rdPortClr = portClearRegister(rd);
} else {
tft8.rdPinMask = 0;
tft8.rdPortSet = dcPortSet;
tft8.rdPortClr = dcPortClr;
}
// These are all uint8_t* pointers -- elsewhere they're recast
// as necessary if a 'wide' 16-bit interface is in use.
tft8.writePort = portOutputRegister(d0);
tft8.readPort = portInputRegister(d0);
tft8.dirSet = portModeRegister(d0);
tft8.dirClr = portModeRegister(d0);
#else // !CORE_TEENSY
tft8.wrPinMask = digitalPinToBitMask(wr);
tft8.wrPortSet = &(PORT->Group[g_APinDescription[wr].ulPort].OUTSET.reg);
tft8.wrPortClr = &(PORT->Group[g_APinDescription[wr].ulPort].OUTCLR.reg);
dcPinMask = digitalPinToBitMask(dc);
dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
if (cs >= 0) {
csPinMask = digitalPinToBitMask(cs);
csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPortSet = dcPortSet;
csPortClr = dcPortClr;
csPinMask = 0;
}
if (rd >= 0) { // if read-strobe pin specified...
tft8.rdPinMask = digitalPinToBitMask(rd);
tft8.rdPortSet = &(PORT->Group[g_APinDescription[rd].ulPort].OUTSET.reg);
tft8.rdPortClr = &(PORT->Group[g_APinDescription[rd].ulPort].OUTCLR.reg);
} else {
tft8.rdPinMask = 0;
tft8.rdPortSet = dcPortSet;
tft8.rdPortClr = dcPortClr;
}
// Get pointers to PORT write/read/dir bytes within 32-bit PORT
uint8_t dBit = g_APinDescription[d0].ulPin; // d0 bit # in PORT
PortGroup *p = (&(PORT->Group[g_APinDescription[d0].ulPort]));
uint8_t offset = dBit / 8; // d[7:0] byte # within PORT
if (tft8.wide)
offset &= ~1; // d[15:8] byte # within PORT
// These are all uint8_t* pointers -- elsewhere they're recast
// as necessary if a 'wide' 16-bit interface is in use.
tft8.writePort = (volatile uint8_t *)&(p->OUT.reg) + offset;
tft8.readPort = (volatile uint8_t *)&(p->IN.reg) + offset;
tft8.dirSet = (volatile uint8_t *)&(p->DIRSET.reg) + offset;
tft8.dirClr = (volatile uint8_t *)&(p->DIRCLR.reg) + offset;
#endif // end !CORE_TEENSY
#else // !HAS_PORT_SET_CLR
tft8.wrPort = (PORTreg_t)portOutputRegister(digitalPinToPort(wr));
tft8.wrPinMaskSet = digitalPinToBitMask(wr);
dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
dcPinMaskSet = digitalPinToBitMask(dc);
if (cs >= 0) {
csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
csPinMaskSet = digitalPinToBitMask(cs);
} else {
// No chip-select line defined; might be permanently tied to GND.
// Assign a valid GPIO register (though not used for CS), and an
// empty pin bitmask...the nonsense bit-twiddling might be faster
// than checking _cs and possibly branching.
csPort = dcPort;
csPinMaskSet = 0;
}
if (rd >= 0) { // if read-strobe pin specified...
tft8.rdPort = (PORTreg_t)portOutputRegister(digitalPinToPort(rd));
tft8.rdPinMaskSet = digitalPinToBitMask(rd);
} else {
tft8.rdPort = dcPort;
tft8.rdPinMaskSet = 0;
}
csPinMaskClr = ~csPinMaskSet;
dcPinMaskClr = ~dcPinMaskSet;
tft8.wrPinMaskClr = ~tft8.wrPinMaskSet;
tft8.rdPinMaskClr = ~tft8.rdPinMaskSet;
tft8.writePort = (PORTreg_t)portOutputRegister(digitalPinToPort(d0));
tft8.readPort = (PORTreg_t)portInputRegister(digitalPinToPort(d0));
tft8.portDir = (PORTreg_t)portModeRegister(digitalPinToPort(d0));
#endif // end !HAS_PORT_SET_CLR
#endif // end USE_FAST_PINIO
}
// end constructors -------
// CLASS MEMBER FUNCTIONS --------------------------------------------------
// begin() and setAddrWindow() MUST be declared by any subclass.
/*!
@brief Configure microcontroller pins for TFT interfacing. Typically
called by a subclass' begin() function.
@param freq SPI frequency when using hardware SPI. If default (0)
is passed, will fall back on a device-specific value.
Value is ignored when using software SPI or parallel
connection.
@param spiMode SPI mode when using hardware SPI. MUST be one of the
values SPI_MODE0, SPI_MODE1, SPI_MODE2 or SPI_MODE3
defined in SPI.h. Do NOT attempt to pass '0' for
SPI_MODE0 and so forth...the values are NOT the same!
Use ONLY the defines! (Pity it's not an enum.)
@note Another anachronistically-named function; this is called even
when the display connection is parallel (not SPI). Also, this
could probably be made private...quite a few class functions
were generously put in the public section.
*/
void Adafruit_SPITFT::initSPI(uint32_t freq, uint8_t spiMode) {
if (!freq)
freq = DEFAULT_SPI_FREQ; // If no freq specified, use default
// Init basic control pins common to all connection types
if (_cs >= 0) {
pinMode(_cs, OUTPUT);
digitalWrite(_cs, HIGH); // Deselect
}
pinMode(_dc, OUTPUT);
digitalWrite(_dc, HIGH); // Data mode
if (connection == TFT_HARD_SPI) {
#if defined(SPI_HAS_TRANSACTION)
hwspi.settings = SPISettings(freq, MSBFIRST, spiMode);
#else
hwspi._freq = freq; // Save freq value for later
#endif
hwspi._mode = spiMode; // Save spiMode value for later
// Call hwspi._spi->begin() ONLY if this is among the 'established'
// SPI interfaces in variant.h. For DIY roll-your-own SERCOM SPIs,
// begin() and pinPeripheral() calls MUST be made in one's calling
// code, BEFORE the screen-specific begin/init function is called.
// Reason for this is that SPI::begin() makes its own calls to
// pinPeripheral() based on g_APinDescription[n].ulPinType, which
// on non-established SPI interface pins will always be PIO_DIGITAL
// or similar, while we need PIO_SERCOM or PIO_SERCOM_ALT...it's
// highly unique between devices and variants for each pin or
// SERCOM so we can't make those calls ourselves here. And the SPI
// device needs to be set up before calling this because it's
// immediately followed with initialization commands. Blargh.
if (
#if !defined(SPI_INTERFACES_COUNT)
1
#endif
#if SPI_INTERFACES_COUNT > 0
(hwspi._spi == &SPI)
#endif
#if SPI_INTERFACES_COUNT > 1
|| (hwspi._spi == &SPI1)
#endif
#if SPI_INTERFACES_COUNT > 2
|| (hwspi._spi == &SPI2)
#endif
#if SPI_INTERFACES_COUNT > 3
|| (hwspi._spi == &SPI3)
#endif
#if SPI_INTERFACES_COUNT > 4
|| (hwspi._spi == &SPI4)
#endif
#if SPI_INTERFACES_COUNT > 5
|| (hwspi._spi == &SPI5)
#endif
) {
hwspi._spi->begin();
}
} else if (connection == TFT_SOFT_SPI) {
pinMode(swspi._mosi, OUTPUT);
digitalWrite(swspi._mosi, LOW);
pinMode(swspi._sck, OUTPUT);
digitalWrite(swspi._sck, LOW);
if (swspi._miso >= 0) {
pinMode(swspi._miso, INPUT);
}
} else { // TFT_PARALLEL
// Initialize data pins. We were only passed d0, so scan
// the pin description list looking for the other pins.
// They'll be on the same PORT, and within the next 7 (or 15) bits
// (because we need to write to a contiguous PORT byte or word).
#if defined(__AVR__)
// PORT registers are 8 bits wide, so just need a register match...
for (uint8_t i = 0; i < NUM_DIGITAL_PINS; i++) {
if ((PORTreg_t)portOutputRegister(digitalPinToPort(i)) ==
tft8.writePort) {
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
}
#elif defined(USE_FAST_PINIO)
#if defined(CORE_TEENSY)
if (!tft8.wide) {
*tft8.dirSet = 0xFF; // Set port to output
*tft8.writePort = 0x00; // Write all 0s
} else {
*(volatile uint16_t *)tft8.dirSet = 0xFFFF;
*(volatile uint16_t *)tft8.writePort = 0x0000;
}
#else // !CORE_TEENSY
uint8_t portNum = g_APinDescription[tft8._d0].ulPort, // d0 PORT #
dBit = g_APinDescription[tft8._d0].ulPin, // d0 bit in PORT
lastBit = dBit + (tft8.wide ? 15 : 7);
for (uint8_t i = 0; i < PINS_COUNT; i++) {
if ((g_APinDescription[i].ulPort == portNum) &&
(g_APinDescription[i].ulPin >= dBit) &&
(g_APinDescription[i].ulPin <= (uint32_t)lastBit)) {
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
}
#endif // end !CORE_TEENSY
#endif
pinMode(tft8._wr, OUTPUT);
digitalWrite(tft8._wr, HIGH);
if (tft8._rd >= 0) {
pinMode(tft8._rd, OUTPUT);
digitalWrite(tft8._rd, HIGH);
}
}
if (_rst >= 0) {
// Toggle _rst low to reset
pinMode(_rst, OUTPUT);
digitalWrite(_rst, HIGH);
delay(100);
digitalWrite(_rst, LOW);
delay(100);
digitalWrite(_rst, HIGH);
delay(200);
}
#if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
if (((connection == TFT_HARD_SPI) || (connection == TFT_PARALLEL)) &&
(dma.allocate() == DMA_STATUS_OK)) { // Allocate channel
// The DMA library needs to alloc at least one valid descriptor,
// so we do that here. It's not used in the usual sense though,
// just before a transfer we copy descriptor[0] to this address.
if (dptr = dma.addDescriptor(NULL, NULL, 42, DMA_BEAT_SIZE_BYTE, false,
false)) {
// Alloc 2 scanlines worth of pixels on display's major axis,
// whichever that is, rounding each up to 2-pixel boundary.
int major = (WIDTH > HEIGHT) ? WIDTH : HEIGHT;
major += (major & 1); // -> next 2-pixel bound, if needed.
maxFillLen = major * 2; // 2 scanlines
// Note to future self: if you decide to make the pixel buffer
// much larger, remember that DMA transfer descriptors can't
// exceed 65,535 bytes (not 65,536), meaning 32,767 pixels max.
// Not that we have that kind of RAM to throw around right now.
if ((pixelBuf[0] = (uint16_t *)malloc(maxFillLen * sizeof(uint16_t)))) {
// Alloc OK. Get pointer to start of second scanline.
pixelBuf[1] = &pixelBuf[0][major];
// Determine number of DMA descriptors needed to cover
// entire screen when entire 2-line pixelBuf is used
// (round up for fractional last descriptor).
int numDescriptors = (WIDTH * HEIGHT + (maxFillLen - 1)) / maxFillLen;
// DMA descriptors MUST be 128-bit (16 byte) aligned.
// memalign() is considered obsolete but it's replacements
// (aligned_alloc() or posix_memalign()) are not currently
// available in the version of ARM GCC in use, but this
// is, so here we are.
if ((descriptor = (DmacDescriptor *)memalign(
16, numDescriptors * sizeof(DmacDescriptor)))) {
int dmac_id;
volatile uint32_t *data_reg;
if (connection == TFT_HARD_SPI) {
// THIS IS AN AFFRONT TO NATURE, but I don't know
// any "clean" way to get the sercom number from the
// the SPIClass pointer (e.g. &SPI or &SPI1), which
// is all we have to work with. SPIClass does contain
// a SERCOM pointer but it is a PRIVATE member!
// Doing an UNSPEAKABLY HORRIBLE THING here, directly
// accessing the first 32-bit value in the SPIClass
// structure, knowing that's (currently) where the
// SERCOM pointer lives, but this ENTIRELY DEPENDS on
// that structure not changing nor the compiler
// rearranging things. Oh the humanity!
if (*(SERCOM **)hwspi._spi == &sercom0) {
dmac_id = SERCOM0_DMAC_ID_TX;
data_reg = &SERCOM0->SPI.DATA.reg;
#if defined SERCOM1
} else if (*(SERCOM **)hwspi._spi == &sercom1) {
dmac_id = SERCOM1_DMAC_ID_TX;
data_reg = &SERCOM1->SPI.DATA.reg;
#endif
#if defined SERCOM2
} else if (*(SERCOM **)hwspi._spi == &sercom2) {
dmac_id = SERCOM2_DMAC_ID_TX;
data_reg = &SERCOM2->SPI.DATA.reg;
#endif
#if defined SERCOM3
} else if (*(SERCOM **)hwspi._spi == &sercom3) {
dmac_id = SERCOM3_DMAC_ID_TX;
data_reg = &SERCOM3->SPI.DATA.reg;
#endif
#if defined SERCOM4
} else if (*(SERCOM **)hwspi._spi == &sercom4) {
dmac_id = SERCOM4_DMAC_ID_TX;
data_reg = &SERCOM4->SPI.DATA.reg;
#endif
#if defined SERCOM5
} else if (*(SERCOM **)hwspi._spi == &sercom5) {
dmac_id = SERCOM5_DMAC_ID_TX;
data_reg = &SERCOM5->SPI.DATA.reg;
#endif
#if defined SERCOM6
} else if (*(SERCOM **)hwspi._spi == &sercom6) {
dmac_id = SERCOM6_DMAC_ID_TX;
data_reg = &SERCOM6->SPI.DATA.reg;
#endif
#if defined SERCOM7
} else if (*(SERCOM **)hwspi._spi == &sercom7) {
dmac_id = SERCOM7_DMAC_ID_TX;
data_reg = &SERCOM7->SPI.DATA.reg;
#endif
}
dma.setPriority(DMA_PRIORITY_3);
dma.setTrigger(dmac_id);
dma.setAction(DMA_TRIGGER_ACTON_BEAT);
// Initialize descriptor list.
for (int d = 0; d < numDescriptors; d++) {
// No need to set SRCADDR, DESCADDR or BTCNT --
// those are done in the pixel-writing functions.
descriptor[d].BTCTRL.bit.VALID = true;
descriptor[d].BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE;
descriptor[d].BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
descriptor[d].BTCTRL.bit.BEATSIZE = DMA_BEAT_SIZE_BYTE;
descriptor[d].BTCTRL.bit.DSTINC = 0;
descriptor[d].BTCTRL.bit.STEPSEL = DMA_STEPSEL_SRC;
descriptor[d].BTCTRL.bit.STEPSIZE =
DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
descriptor[d].DSTADDR.reg = (uint32_t)data_reg;
}
} else { // Parallel connection
#if defined(__SAMD51__)
int dmaChannel = dma.getChannel();
// Enable event output, use EVOSEL output
DMAC->Channel[dmaChannel].CHEVCTRL.bit.EVOE = 1;
DMAC->Channel[dmaChannel].CHEVCTRL.bit.EVOMODE = 0;
// CONFIGURE TIMER/COUNTER (for write strobe)
Tc *timer = tcList[tcNum].tc; // -> Timer struct
int id = tcList[tcNum].gclk; // Timer GCLK ID
GCLK_PCHCTRL_Type pchctrl;
// Set up timer clock source from GCLK
GCLK->PCHCTRL[id].bit.CHEN = 0; // Stop timer
while (GCLK->PCHCTRL[id].bit.CHEN)
; // Wait for it
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK0_Val;
pchctrl.bit.CHEN = 1; // Enable
GCLK->PCHCTRL[id].reg = pchctrl.reg;
while (!GCLK->PCHCTRL[id].bit.CHEN)
; // Wait for it
// Disable timer/counter before configuring it
timer->COUNT8.CTRLA.bit.ENABLE = 0;
while (timer->COUNT8.SYNCBUSY.bit.STATUS)
;
timer->COUNT8.WAVE.bit.WAVEGEN = 2; // NPWM
timer->COUNT8.CTRLA.bit.MODE = 1; // 8-bit
timer->COUNT8.CTRLA.bit.PRESCALER = 0; // 1:1
while (timer->COUNT8.SYNCBUSY.bit.STATUS)
;
timer->COUNT8.CTRLBCLR.bit.DIR = 1; // Count UP
while (timer->COUNT8.SYNCBUSY.bit.CTRLB)
;
timer->COUNT8.CTRLBSET.bit.ONESHOT = 1; // One-shot
while (timer->COUNT8.SYNCBUSY.bit.CTRLB)
;
timer->COUNT8.PER.reg = 6; // PWM top
while (timer->COUNT8.SYNCBUSY.bit.PER)
;
timer->COUNT8.CC[0].reg = 2; // Compare
while (timer->COUNT8.SYNCBUSY.bit.CC0)
;
// Enable async input events,
// event action = restart.
timer->COUNT8.EVCTRL.bit.TCEI = 1;
timer->COUNT8.EVCTRL.bit.EVACT = 1;
// Enable timer
timer->COUNT8.CTRLA.reg |= TC_CTRLA_ENABLE;
while (timer->COUNT8.SYNCBUSY.bit.STATUS)
;
#if (wrPeripheral == PIO_CCL)
// CONFIGURE CCL (inverts timer/counter output)
MCLK->APBCMASK.bit.CCL_ = 1; // Enable CCL clock
CCL->CTRL.bit.ENABLE = 0; // Disable to config
CCL->CTRL.bit.SWRST = 1; // Reset CCL registers
CCL->LUTCTRL[tcNum].bit.ENABLE = 0; // Disable LUT
CCL->LUTCTRL[tcNum].bit.FILTSEL = 0; // No filter
CCL->LUTCTRL[tcNum].bit.INSEL0 = 6; // TC input
CCL->LUTCTRL[tcNum].bit.INSEL1 = 0; // MASK
CCL->LUTCTRL[tcNum].bit.INSEL2 = 0; // MASK
CCL->LUTCTRL[tcNum].bit.TRUTH = 1; // Invert in 0
CCL->LUTCTRL[tcNum].bit.ENABLE = 1; // Enable LUT
CCL->CTRL.bit.ENABLE = 1; // Enable CCL
#endif
// CONFIGURE EVENT SYSTEM
// Set up event system clock source from GCLK...
// Disable EVSYS, wait for disable
GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN = 0;
while (GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN)
;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK0_Val;
pchctrl.bit.CHEN = 1; // Re-enable
GCLK->PCHCTRL[EVSYS_GCLK_ID_0].reg = pchctrl.reg;
// Wait for it, then enable EVSYS clock
while (!GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN)
;
MCLK->APBBMASK.bit.EVSYS_ = 1;
// Connect Timer EVU to ch 0
EVSYS->USER[tcList[tcNum].evu].reg = 1;
// Datasheet recommends single write operation;
// reg instead of bit. Also datasheet: PATH bits
// must be zero when using async!
EVSYS_CHANNEL_Type ev;
ev.reg = 0;
ev.bit.PATH = 2; // Asynchronous
ev.bit.EVGEN = 0x22 + dmaChannel; // DMA channel 0+
EVSYS->Channel[0].CHANNEL.reg = ev.reg;
// Initialize descriptor list.
for (int d = 0; d < numDescriptors; d++) {
// No need to set SRCADDR, DESCADDR or BTCNT --
// those are done in the pixel-writing functions.
descriptor[d].BTCTRL.bit.VALID = true;
// Event strobe on beat xfer:
descriptor[d].BTCTRL.bit.EVOSEL = 0x3;
descriptor[d].BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
descriptor[d].BTCTRL.bit.BEATSIZE =
tft8.wide ? DMA_BEAT_SIZE_HWORD : DMA_BEAT_SIZE_BYTE;
descriptor[d].BTCTRL.bit.SRCINC = 1;
descriptor[d].BTCTRL.bit.DSTINC = 0;
descriptor[d].BTCTRL.bit.STEPSEL = DMA_STEPSEL_SRC;
descriptor[d].BTCTRL.bit.STEPSIZE =
DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
descriptor[d].DSTADDR.reg = (uint32_t)tft8.writePort;
}
#endif // __SAMD51
} // end parallel-specific DMA setup
lastFillColor = 0x0000;
lastFillLen = 0;
dma.setCallback(dma_callback);
return; // Success!
// else clean up any partial allocation...
} // end descriptor memalign()
free(pixelBuf[0]);
pixelBuf[0] = pixelBuf[1] = NULL;
} // end pixelBuf malloc()
// Don't currently have a descriptor delete function in
// ZeroDMA lib, but if we did, it would be called here.
} // end addDescriptor()
dma.free(); // Deallocate DMA channel
}
#endif // end USE_SPI_DMA
}
/*!
@brief Allow changing the SPI clock speed after initialization
@param freq Desired frequency of SPI clock, may not be the
end frequency you get based on what the chip can do!
*/
void Adafruit_SPITFT::setSPISpeed(uint32_t freq) {
#if defined(SPI_HAS_TRANSACTION)
hwspi.settings = SPISettings(freq, MSBFIRST, hwspi._mode);
#else
hwspi._freq = freq; // Save freq value for later
#endif
}
/*!
@brief Call before issuing command(s) or data to display. Performs
chip-select (if required) and starts an SPI transaction (if
using hardware SPI and transactions are supported). Required
for all display types; not an SPI-specific function.
*/
void Adafruit_SPITFT::startWrite(void) {
SPI_BEGIN_TRANSACTION();
if (_cs >= 0)
SPI_CS_LOW();
}
/*!
@brief Call after issuing command(s) or data to display. Performs
chip-deselect (if required) and ends an SPI transaction (if
using hardware SPI and transactions are supported). Required
for all display types; not an SPI-specific function.
*/
void Adafruit_SPITFT::endWrite(void) {
if (_cs >= 0)
SPI_CS_HIGH();
SPI_END_TRANSACTION();
}
// -------------------------------------------------------------------------
// Lower-level graphics operations. These functions require a chip-select
// and/or SPI transaction around them (via startWrite(), endWrite() above).
// Higher-level graphics primitives might start a single transaction and
// then make multiple calls to these functions (e.g. circle or text
// rendering might make repeated lines or rects) before ending the
// transaction. It's more efficient than starting a transaction every time.
/*!
@brief Draw a single pixel to the display at requested coordinates.
Not self-contained; should follow a startWrite() call.
@param x Horizontal position (0 = left).
@param y Vertical position (0 = top).
@param color 16-bit pixel color in '565' RGB format.
*/
void Adafruit_SPITFT::writePixel(int16_t x, int16_t y, uint16_t color) {
if ((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) {
setAddrWindow(x, y, 1, 1);
SPI_WRITE16(color);
}
}
/*!
@brief Issue a series of pixels from memory to the display. Not self-
contained; should follow startWrite() and setAddrWindow() calls.
@param colors Pointer to array of 16-bit pixel values in '565' RGB
format.
@param len Number of elements in 'colors' array.
@param block If true (default case if unspecified), function blocks
until DMA transfer is complete. This is simply IGNORED
if DMA is not enabled. If false, the function returns
immediately after the last DMA transfer is started,
and one should use the dmaWait() function before
doing ANY other display-related activities (or even
any SPI-related activities, if using an SPI display
that shares the bus with other devices).
@param bigEndian If using DMA, and if set true, bitmap in memory is in
big-endian order (most significant byte first). By
default this is false, as most microcontrollers seem
to be little-endian and 16-bit pixel values must be
byte-swapped before issuing to the display (which tend
to be big-endian when using SPI or 8-bit parallel).
If an application can optimize around this -- for
example, a bitmap in a uint16_t array having the byte
values already reordered big-endian, this can save
some processing time here, ESPECIALLY if using this
function's non-blocking DMA mode. Not all cases are
covered...this is really here only for SAMD DMA and
much forethought on the application side.
*/
void Adafruit_SPITFT::writePixels(uint16_t *colors, uint32_t len, bool block,
bool bigEndian) {
if (!len)
return; // Avoid 0-byte transfers
// avoid paramater-not-used complaints
(void)block;
(void)bigEndian;
#if defined(ESP32) // ESP32 has a special SPI pixel-writing function...
if (connection == TFT_HARD_SPI) {
hwspi._spi->writePixels(colors, len * 2);
return;
}
#elif defined(ARDUINO_NRF52_ADAFRUIT) && \
defined(NRF52840_XXAA) // Adafruit nRF52 use SPIM3 DMA at 32Mhz
// TFT and SPI DMA endian is different we need to swap bytes
if (!bigEndian) {
for (uint32_t i = 0; i < len; i++) {
colors[i] = __builtin_bswap16(colors[i]);
}