mirror of
https://github.com/adafruit/Adafruit-GFX-Library.git
synced 2024-09-20 09:36:32 -04:00
SPITFT: initial DMA support (disabled by default), very beta-y
This commit is contained in:
parent
75f81ac3e5
commit
860cd24319
@ -43,8 +43,50 @@
|
||||
#endif
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef 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
|
||||
|
||||
#include "Adafruit_SPITFT_Macros.h"
|
||||
|
||||
#ifdef USE_SPI_DMA
|
||||
#include <Adafruit_ZeroDMA.h>
|
||||
#include <malloc.h> // memalign() function
|
||||
|
||||
// DMA transfer-in-progress indicator and callback
|
||||
static volatile boolean dma_busy = false;
|
||||
static volatile Adafruit_SPITFT *spitft = NULL;
|
||||
static void dma_callback(Adafruit_ZeroDMA *dma) {
|
||||
// If spitft pointer is set, deselect TFT and end the SPI transaction
|
||||
// here in the callback rather than at end of drawing function. Avoids
|
||||
// a (possibly unnecessary) function call at the start of every graphics
|
||||
// operation. Can't do this in SPI_BEGIN_TRANSACTION because other code
|
||||
// outside the library (e.g. SD card reading) may be waiting on the
|
||||
// GFX SPI transaction to end first.
|
||||
if(spitft) {
|
||||
((Adafruit_SPITFT *)spitft)->endWrite();
|
||||
spitft = NULL;
|
||||
}
|
||||
dma_busy = false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Poll whether a previous DMA operation is still in-progress.
|
||||
@return true if SPITFT DMA operation in progress, false if available.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
boolean Adafruit_SPITFT::DMA_busy(void) {
|
||||
return dma_busy;
|
||||
}
|
||||
|
||||
#define DMA_WAIT while(dma_busy); ///< Wait for dma busy flag to clear
|
||||
#else
|
||||
#define DMA_WAIT ///< Do nothing; DMA not used
|
||||
#endif // USE_SPI_DMA
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@ -172,6 +214,7 @@ Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, SPIClass *spiClass,
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Initialiaze the SPI interface (hardware or software)
|
||||
@ -213,6 +256,121 @@ void Adafruit_SPITFT::initSPI(uint32_t freq) {
|
||||
digitalWrite(_rst, HIGH);
|
||||
delay(200);
|
||||
}
|
||||
|
||||
#ifdef USE_SPI_DMA
|
||||
|
||||
// INITIALIZE DMA
|
||||
|
||||
if(dma.allocate() == DMA_STATUS_OK) { // Allocate channel
|
||||
// The DMA library needs to allocate 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)) {
|
||||
// Allocate 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 tops.
|
||||
// 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;
|
||||
|
||||
// THIS IS AN AFFRONT TO NATURE, but I don't know
|
||||
// any "clean" way to get the sercom number from 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 **)_spi == &sercom0) {
|
||||
dmac_id = SERCOM0_DMAC_ID_TX;
|
||||
data_reg = &SERCOM0->SPI.DATA.reg;
|
||||
#if defined SERCOM1
|
||||
} else if(*(SERCOM **)_spi == &sercom1) {
|
||||
dmac_id = SERCOM1_DMAC_ID_TX;
|
||||
data_reg = &SERCOM1->SPI.DATA.reg;
|
||||
#endif
|
||||
#if defined SERCOM2
|
||||
} else if(*(SERCOM **)_spi == &sercom2) {
|
||||
dmac_id = SERCOM2_DMAC_ID_TX;
|
||||
data_reg = &SERCOM2->SPI.DATA.reg;
|
||||
#endif
|
||||
#if defined SERCOM3
|
||||
} else if(*(SERCOM **)_spi == &sercom3) {
|
||||
dmac_id = SERCOM3_DMAC_ID_TX;
|
||||
data_reg = &SERCOM3->SPI.DATA.reg;
|
||||
#endif
|
||||
#if defined SERCOM4
|
||||
} else if(*(SERCOM **)_spi == &sercom4) {
|
||||
dmac_id = SERCOM4_DMAC_ID_TX;
|
||||
data_reg = &SERCOM4->SPI.DATA.reg;
|
||||
#endif
|
||||
#if defined SERCOM5
|
||||
} else if(*(SERCOM **)_spi == &sercom5) {
|
||||
dmac_id = SERCOM5_DMAC_ID_TX;
|
||||
data_reg = &SERCOM5->SPI.DATA.reg;
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
lastFillColor = 0x0000;
|
||||
lastFillLen = 0;
|
||||
dma.setCallback(dma_callback);
|
||||
return; // Success!
|
||||
}
|
||||
// Else some alloc/init error along the way...clean up...
|
||||
free(pixelBuf[0]);
|
||||
pixelBuf[0] = pixelBuf[1] = NULL;
|
||||
}
|
||||
// Don't currently have a descriptor delete function in
|
||||
// ZeroDMA lib, but if we did, it would be called here.
|
||||
}
|
||||
dma.free(); // Deallocate DMA channel
|
||||
}
|
||||
#endif // end DMA init
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@ -273,6 +431,7 @@ void Adafruit_SPITFT::spiWrite(uint8_t b) {
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void inline Adafruit_SPITFT::startWrite(void){
|
||||
DMA_WAIT; // Wait for any prior SPI DMA to complete
|
||||
SPI_BEGIN_TRANSACTION();
|
||||
SPI_CS_LOW();
|
||||
}
|
||||
@ -283,9 +442,20 @@ void inline Adafruit_SPITFT::startWrite(void){
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void inline Adafruit_SPITFT::endWrite(void){
|
||||
#ifdef USE_SPI_DMA
|
||||
// SPI DMA enabled: wait for DMA completion and end transaction ONLY if
|
||||
// spitft is NULL. Otherwise, calling function can proceed and
|
||||
// equivalent code in DMA callback is used.
|
||||
if(!spitft) {
|
||||
DMA_WAIT; // Wait for DMA operation to complete
|
||||
SPI_CS_HIGH();
|
||||
SPI_END_TRANSACTION();
|
||||
}
|
||||
#else
|
||||
SPI_CS_HIGH();
|
||||
SPI_END_TRANSACTION();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@ -311,8 +481,6 @@ void Adafruit_SPITFT::pushColor(uint16_t color) {
|
||||
endWrite();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Blit multiple 2-byte colors (must have a transaction in progress)
|
||||
@ -321,7 +489,48 @@ void Adafruit_SPITFT::pushColor(uint16_t color) {
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_SPITFT::writePixels(uint16_t *colors, uint32_t len) {
|
||||
#ifdef USE_SPI_DMA
|
||||
int maxSpan = maxFillLen / 2; // One scanline max
|
||||
uint8_t pixelBufIdx = 0; // Active pixel buffer number
|
||||
while(len) {
|
||||
int count = (len < maxSpan) ? len : maxSpan;
|
||||
|
||||
// Because TFT and SAMD endianisms are different, must swap bytes
|
||||
// from the 'colors' array passed into a DMA working buffer. This
|
||||
// can take place while the prior DMA transfer is in progress,
|
||||
// hence the need for two pixelBufs.
|
||||
for(int i=0; i<count; i++) {
|
||||
pixelBuf[pixelBufIdx][i] = __builtin_bswap16(*colors++);
|
||||
}
|
||||
// The transfers themselves are relatively small, so we don't
|
||||
// need a long descriptor list. We just alternate between the
|
||||
// first two, sharing pixelBufIdx for that purpose.
|
||||
descriptor[pixelBufIdx].SRCADDR.reg =
|
||||
(uint32_t)pixelBuf[pixelBufIdx] + count * 2;
|
||||
descriptor[pixelBufIdx].BTCTRL.bit.SRCINC = 1;
|
||||
descriptor[pixelBufIdx].BTCNT.reg = count * 2;
|
||||
descriptor[pixelBufIdx].DESCADDR.reg = 0;
|
||||
|
||||
DMA_WAIT; // NOW wait for prior DMA to complete
|
||||
|
||||
// Move new descriptor into place...
|
||||
memcpy(dptr, &descriptor[pixelBufIdx], sizeof(DmacDescriptor));
|
||||
dma_busy = true;
|
||||
dma.startJob(); // Trigger SPI DMA transfer
|
||||
pixelBufIdx = 1 - pixelBufIdx; // Swap DMA pixel buffers
|
||||
|
||||
len -= count;
|
||||
}
|
||||
lastFillColor = 0x0000; // pixelBuf has been sullied
|
||||
lastFillLen = 0;
|
||||
// Return immediately -- other code runs with last DMA transfer in
|
||||
// background. startWrite() will wait for the transfer to complete
|
||||
// before issuing any more commands. User code MUST poll
|
||||
// display.DMA_busy() before attempting anything else on the same
|
||||
// SPI bus (e.g. SD card access).
|
||||
#else
|
||||
SPI_WRITE_PIXELS((uint8_t*)colors , len * 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@ -332,6 +541,75 @@ void Adafruit_SPITFT::writePixels(uint16_t * colors, uint32_t len){
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_SPITFT::writeColor(uint16_t color, uint32_t len) {
|
||||
|
||||
#ifdef USE_SPI_DMA
|
||||
|
||||
if((color >> 8) == (color & 0xFF)) { // If high & low bytes are same...
|
||||
onePixelBuf = color;
|
||||
// Can do this with a relatively short descriptor list,
|
||||
// each transferring a max of 32,767 (not 32,768) pixels.
|
||||
// This won't run off the end of the allocated descriptor list,
|
||||
// since we're using much larger chunks per descriptor here.
|
||||
int numDescriptors = (len + 32766) / 32767;
|
||||
for(int d=0; d<numDescriptors; d++) {
|
||||
int count = (len > 32767) ? 32767 : len;
|
||||
descriptor[d].SRCADDR.reg = (uint32_t)&onePixelBuf;
|
||||
descriptor[d].BTCTRL.bit.SRCINC = 0;
|
||||
descriptor[d].BTCNT.reg = count * 2;
|
||||
descriptor[d].DESCADDR.reg =
|
||||
(d < (numDescriptors - 1)) ? (uint32_t)&descriptor[d + 1] : 0;
|
||||
len -= count;
|
||||
}
|
||||
} else {
|
||||
// If high and low bytes are distinct, it's necessary to fill
|
||||
// a buffer with pixel data (swapping high and low bytes because
|
||||
// TFT and SAMD are different endianisms) and create a longer
|
||||
// descriptor list pointing repeatedly to this data. We can do
|
||||
// this slightly faster working 2 pixels (32 bits) at a time.
|
||||
uint32_t *pixelPtr = (uint32_t *)pixelBuf[0],
|
||||
twoPixels = __builtin_bswap16(color) * 0x00010001;
|
||||
// We can avoid some or all of the buffer-filling if the color
|
||||
// is the same as last time...
|
||||
if(color == lastFillColor) {
|
||||
// If length is longer than prior instance, fill only the
|
||||
// additional pixels in the buffer and update lastFillLen.
|
||||
if(len > lastFillLen) {
|
||||
int fillStart = lastFillLen / 2 + 1,
|
||||
fillEnd = (((maxFillLen < len) ?
|
||||
maxFillLen : len) + 1) / 2;
|
||||
for(int i=fillStart; i<fillEnd; i++) pixelPtr[i] = twoPixels;
|
||||
lastFillLen = fillEnd * 2;
|
||||
} // else do nothing, don't set pixels, don't change lastFillLen
|
||||
} else {
|
||||
int fillEnd = (((maxFillLen < len) ?
|
||||
maxFillLen : len) + 1) / 2;
|
||||
for(int i=0; i<fillEnd; i++) pixelPtr[i] = twoPixels;
|
||||
lastFillLen = fillEnd * 2;
|
||||
}
|
||||
|
||||
int numDescriptors = (len + maxFillLen - 1) / maxFillLen;
|
||||
for(int d=0; d<numDescriptors; d++) {
|
||||
int count = lastFillLen * 2; // Transfer size in bytes
|
||||
descriptor[d].SRCADDR.reg = (uint32_t)pixelPtr + count;
|
||||
descriptor[d].BTCTRL.bit.SRCINC = 1;
|
||||
descriptor[d].BTCNT.reg = count;
|
||||
descriptor[d].DESCADDR.reg =
|
||||
(d < (numDescriptors - 1)) ? (uint32_t)&descriptor[d + 1] : 0;
|
||||
len -= count;
|
||||
}
|
||||
}
|
||||
memcpy(dptr, &descriptor[0], sizeof(DmacDescriptor));
|
||||
dma_busy = true; // ANY function using SPI must poll busy flag!
|
||||
spitft = this; // Save pointer to Adafruit_SPITFT type for callback
|
||||
dma.startJob(); // Trigger SPI DMA transfer
|
||||
// Return immediately -- other code runs with DMA transfer in
|
||||
// background. startWrite() will wait for the transfer to complete
|
||||
// before issuing any more commands. User code MUST poll
|
||||
// display.DMA_busy() before attempting anything else on the same
|
||||
// SPI bus (e.g. SD card access).
|
||||
|
||||
#else // Non-DMA
|
||||
|
||||
#ifdef SPI_HAS_WRITE_PIXELS
|
||||
if(_sclk >= 0){
|
||||
for (uint32_t t=0; t<len; t++){
|
||||
@ -366,6 +644,8 @@ void Adafruit_SPITFT::writeColor(uint16_t color, uint32_t len){
|
||||
spiWrite(lo);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // end non-DMA
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@ -378,6 +658,7 @@ void Adafruit_SPITFT::writeColor(uint16_t color, uint32_t len){
|
||||
/**************************************************************************/
|
||||
void Adafruit_SPITFT::writePixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if((x < 0) ||(x >= _width) || (y < 0) || (y >= _height)) return;
|
||||
DMA_WAIT;
|
||||
setAddrWindow(x,y,1,1);
|
||||
writePixel(color);
|
||||
}
|
||||
@ -412,6 +693,7 @@ void Adafruit_SPITFT::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h,
|
||||
if(y2 >= _height) h = _height - y;
|
||||
|
||||
int32_t len = (int32_t)w * h;
|
||||
DMA_WAIT;
|
||||
setAddrWindow(x, y, w, h);
|
||||
writeColor(color, len);
|
||||
}
|
||||
@ -451,10 +733,15 @@ void inline Adafruit_SPITFT::writeFastHLine(int16_t x, int16_t y, int16_t w, uin
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_SPITFT::drawPixel(int16_t x, int16_t y, uint16_t color){
|
||||
// Clip first...
|
||||
if((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) {
|
||||
// THEN set up transaction (if needed) and draw...
|
||||
startWrite();
|
||||
writePixel(x, y, color);
|
||||
setAddrWindow(x, y, 1, 1);
|
||||
writePixel(color);
|
||||
endWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@ -561,7 +848,7 @@ void Adafruit_SPITFT::drawRGBBitmap(int16_t x, int16_t y,
|
||||
if(y2 >= _height) h = _height - y; // Clip bottom
|
||||
|
||||
pcolors += by1 * saveW + bx1; // Offset bitmap ptr to clipped top-left
|
||||
startWrite();
|
||||
startWrite(); // Includes a DMA_WAIT
|
||||
setAddrWindow(x, y, w, h); // Clipped area
|
||||
while(h--) { // For each (clipped) scanline...
|
||||
writePixels(pcolors, w); // Push one (clipped) row
|
||||
@ -571,3 +858,4 @@ void Adafruit_SPITFT::drawRGBBitmap(int16_t x, int16_t y,
|
||||
}
|
||||
|
||||
#endif // !__AVR_ATtiny85__
|
||||
|
||||
|
@ -12,7 +12,22 @@
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
|
||||
#define USE_FAST_PINIO
|
||||
#define USE_FAST_PINIO ///< If set, use PORT access instead of digitalWrite()
|
||||
//#define USE_SPI_DMA ///< If set, use SPI DMA if available
|
||||
// If DMA is enabled, Arduino sketch MUST #include <Adafruit_ZeroDMA.h>
|
||||
// Sketches MUST poll tft.DMA_busy() before doing other things that work
|
||||
// on the SPI bus (e.g. accessing SD card).
|
||||
// Estimated RAM usage:
|
||||
// 4 bytes/pixel on display major axis + 8 bytes/pixel on minor axis,
|
||||
// e.g. 320x240 pixels = 320 * 4 + 240 * 8 = 3,200 bytes.
|
||||
|
||||
#if !defined(ARDUINO_ARCH_SAMD)
|
||||
#undef USE_SPI_DMA ///< Only for SAMD chips
|
||||
#endif
|
||||
|
||||
#ifdef USE_SPI_DMA
|
||||
#include <Adafruit_ZeroDMA.h>
|
||||
#endif
|
||||
|
||||
#if defined(__AVR__)
|
||||
typedef volatile uint8_t RwReg;
|
||||
@ -90,6 +105,9 @@ class Adafruit_SPITFT : public Adafruit_GFX {
|
||||
void writeCommand(uint8_t cmd);
|
||||
void spiWrite(uint8_t v);
|
||||
uint8_t spiRead(void);
|
||||
#ifdef USE_SPI_DMA
|
||||
boolean DMA_busy(void);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
SPIClass *_spi; ///< The SPI device we want to use (set in constructor)
|
||||
@ -122,6 +140,17 @@ class Adafruit_SPITFT : public Adafruit_GFX {
|
||||
invertOffCommand = 0; ///< SPI command byte to turn off invert
|
||||
int16_t _xstart = 0; ///< Many displays don't have pixels starting at (0,0) of the internal framebuffer, this is the x offset from 0 to align
|
||||
int16_t _ystart = 0; ///< Many displays don't have pixels starting at (0,0) of the internal framebuffer, this is the y offset from 0 to align
|
||||
|
||||
#ifdef USE_SPI_DMA
|
||||
Adafruit_ZeroDMA dma; ///< DMA instance
|
||||
DmacDescriptor *dptr = NULL; ///< 1st descriptor
|
||||
DmacDescriptor *descriptor = NULL; ///< Allocated descriptor list
|
||||
uint16_t *pixelBuf[2]; ///< Working buffers
|
||||
uint16_t maxFillLen; ///< Max pixels per DMA xfer
|
||||
uint16_t lastFillColor = 0; ///< Last color used w/fill
|
||||
uint32_t lastFillLen = 0; ///< # of pixels w/last fill
|
||||
uint8_t onePixelBuf; ///< For hi==lo fill
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // !__AVR_ATtiny85__
|
||||
|
Loading…
Reference in New Issue
Block a user