SPITFT: initial DMA support (disabled by default), very beta-y

This commit is contained in:
Phillip Burgess 2018-12-06 17:40:49 -08:00
parent 75f81ac3e5
commit 860cd24319
2 changed files with 339 additions and 22 deletions

View File

@ -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__

View File

@ -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__