diff --git a/Adafruit_RA8875_Due.cpp b/Adafruit_RA8875_Due.cpp index 7fd19d6..a7be79f 100644 --- a/Adafruit_RA8875_Due.cpp +++ b/Adafruit_RA8875_Due.cpp @@ -11,6 +11,10 @@ typedef volatile union _SADDR_Type { Word raw; } SADDR_Type; +/** + * Redef of the LLI as an entry in a row. + * Uses SADDR_Type union for easier debugging of what's in the SADDR field + */ typedef volatile struct _RowFrame { SADDR_Type SADDR; Word DADDR; @@ -30,6 +34,12 @@ typedef volatile struct _Row { } } Row; +/** + * Gets a chunk of LLIs and treats them like an entire row of pixels. Good for bulk operations + * @param manager The DMA Manager + * @param row Which row to get + * @return The collection of frames as a row pointer, if within range. Nullptr otherwise + */ static Row *get_row(DMAManager *manager, size_t row) { if (row * FRAMES_PER_LINE >= LLI_MAX_FRAMES) { return nullptr; diff --git a/DMAManager.h b/DMAManager.h index 386178b..e79b81b 100644 --- a/DMAManager.h +++ b/DMAManager.h @@ -11,18 +11,33 @@ * */ /********************************/ #ifndef FRAMES_PER_LINE +/** + * The number of frames required to draw one line to the screen. + */ #define FRAMES_PER_LINE 16 #endif #ifndef LINES_PER_DMA +/** + * The number of lines to be created/filled before the transfer starts + */ #define LINES_PER_DMA 8 #endif #ifndef WORKING_DATA_PER_LINE +/** + * The number of bytes required to draw a line on the screen + */ #define WORKING_DATA_PER_LINE 19 #endif #define LLI_MAX_FRAMES (FRAMES_PER_LINE * LINES_PER_DMA) #define WORKING_DATA_SIZE (WORKING_DATA_PER_LINE * LINES_PER_DMA) +/** + * The number of bytes needed to set the lower/upper bytes for one coordinate + */ #define COORD_BUF_SPACE 4 +/** + * How long to wait / buffer the CS pin low or high + */ #define DMA_CS_HIGH_TRANSFERS 8 // Forward Declaration for class @@ -66,8 +81,14 @@ typedef union DMAFunctionData { } DMAFunctionData; typedef struct DMACallbackData { + /** + * Data for use in the complete_cb function + */ void *dataPtr; + /** + * Function called when transfers are complete + */ void (*complete_cb)(void *); } DMACallbackData; @@ -98,12 +119,19 @@ typedef struct DMA_Data { void volatile (*on_complete)(SpiDriver *spiDriver); - + /** + * Resets the working data idx to 0. Doesn't actually clear data, just allows for overwrite + * @param full_clear Resets last_idx if true + */ inline void clear_working_data(bool full_clear = false) { last_storage_idx = full_clear ? 0 : storage_idx; storage_idx = 0; } + /** + * Completely resets the DMA_Data instance + * @param full_reset Clears the last_data fields + */ inline void reset(bool full_reset = false) { clear_working_data(full_reset); memset_volatile(&functionData, '\0', sizeof(DMAFunctionData)); @@ -116,10 +144,21 @@ typedef struct DMA_Data { on_complete = nullptr; } + /** + * @param size The amount of bytes needed to add + * @return true if there is space to add, false otherwise + */ inline bool can_add_working_data(size_t size) const { return ((storage_idx - 1) + size) < WORKING_DATA_SIZE; } + /** + * Store sensitive data needed for DMA in a buffer for use during the transfers. + * Provides a size check to make sure the data can be added. + * @param buf The data to store + * @param size The number of bytes being added. + * @return uint8_t ptr to the location of the added data in the permanent buffer for future access. + */ inline volatile uint8_t *add_working_data(const uint8_t *buf, size_t size) { if (!can_add_working_data(size)) { return nullptr; @@ -165,6 +204,12 @@ class DMAManager { volatile LLI *get_next_blank_entry(); + /** + * Gets an entry from the chain + * @param idx The idx to get. + * @param force If the index is out of 'size' bounds, will return nullptr unless force is true. Will return the index from the array + * @return The entry, if within range (or if force specified). Nullptr otherwise. + */ volatile LLI *get_entry(size_t idx, bool force = false); inline volatile LLI *get_last() { @@ -179,16 +224,28 @@ class DMAManager { * until we're done with the entire chain. This way if an element needs to be removed, all the `next` pointers won't need to be * rewritten. * This must be called before passing the beginning entry to DMA + * @return Ptr to the head of the list */ volatile LLI *finalize(); + /** + * Resets the size counter, does not zero memory + */ void clear_frames(); + /** + * Resets frames & working data + * @param full Clears the 'last_data' fields, if true + */ void reset(bool full = false); size_t get_size() const; - bool can_add_entries(uint32_t entries) const; + /** + * @param entries The number of entries to check if available + * @return True if space, false otherwise + */ + bool can_add_entries(size_t entries) const; DMA_Data *get_cur_data() { return &transaction_data; @@ -197,18 +254,55 @@ class DMAManager { /************************ * General purpose methods for adding SPI Frames, PIO Frames, etc. ************************/ + + /** + * @param state The state of the pin (Returns SODR for true, CODR for false) + * @param pin The pin to get the register for + * @return The address for the register. + */ WoReg *get_pio_reg(bool state, uint8_t pin); + /** + * Gets the register for the chipselect pin + * @param state The state for the pin + * @return The address for the register + */ WoReg *get_cs_pio_reg(bool state) { return get_pio_reg(state, _csPin); } + /** + * Adds an entry to toggle a pin high or low + * @param state The new state of the pin + * @param pin The pin number + * @param pin_mask_ptr A pointer to somewhere with the mask for that pin. The g_APinDescriptor will not work for this purpose. + * @param num_transfers How many transfers to send. + * @return Whether or not the entry was added to the buffer + */ bool add_entry_pin_toggle(bool state, uint8_t pin, const uint32_t *pin_mask_ptr, size_t num_transfers = 2); + /** + * Specifically adds an entry to toggle the CS pin + * @param state The new state for the pin + * @param num_transfers How many transfers to send. The RA chip requires that CS be high for ~5 CLK cycles before going low again. + * @return Whether or not the entry was added to the buffer. + */ bool add_entry_cs_pin_toggle(bool state, size_t num_transfers = 2); + /** + * Adds a generic SPI transfer to the list + * @param buf The start of the data to transfer. + * @param qty The number of bytes in the buffer. + * @return Whether or not the entry was added to the list + */ bool add_entry_spi_transfer(volatile uint8_t *buf, size_t qty); + /** + * Adds all the necessary entries to draw a batch of pixels in an area. + * @param buf The buffer of pixels to add + * @param qty The number of pixels drawn + * @return uint8_t ptr to the start of the working data containing the draw commands (RA8875_CMDWRITE, DATAWRITE, etc) + */ volatile uint8_t *add_entry_spi_draw_pixels(volatile uint8_t *buf, size_t qty); /** diff --git a/DMAManagerDue.cpp b/DMAManagerDue.cpp index 81782ee..5e98f3e 100644 --- a/DMAManagerDue.cpp +++ b/DMAManagerDue.cpp @@ -101,7 +101,7 @@ size_t DMAManager::get_size() const { return size; } -bool DMAManager::can_add_entries(uint32_t entries) const { +bool DMAManager::can_add_entries(size_t entries) const { bool result = size + entries <= LLI_MAX_FRAMES; return result; } diff --git a/DMA_LLI.h b/DMA_LLI.h index 380aa21..681e4aa 100644 --- a/DMA_LLI.h +++ b/DMA_LLI.h @@ -1,6 +1,13 @@ #ifndef ADAFRUIT_RA8875_DMA_LLI_H #define ADAFRUIT_RA8875_DMA_LLI_H +/******************************************* + * + * Add whatever includes necessary to implement your DMA controller logic. + * Currently only supports Arduino Due + * + ********************************************/ + #if defined(ARDUINO_SAM_DUE) diff --git a/DMA_LLI_Due.h b/DMA_LLI_Due.h index 95aaf2a..e86a88e 100644 --- a/DMA_LLI_Due.h +++ b/DMA_LLI_Due.h @@ -5,6 +5,9 @@ typedef uint32_t Word; +/** + * Bitfield for CTRLA register. + */ typedef volatile union _CTRLA_Field { _CTRLA_Field() : raw(0) {}; @@ -25,6 +28,9 @@ typedef volatile union _CTRLA_Field { Word raw; } CTRLA_Field; +/** + * Bitfield for CTRLB Register + */ typedef volatile union _CTRLB_Field { _CTRLB_Field() : _CTRLB_Field(0) {}; diff --git a/SpiDriver.h b/SpiDriver.h index 0249ec9..6854b83 100644 --- a/SpiDriver.h +++ b/SpiDriver.h @@ -36,26 +36,69 @@ class SpiDriver { public: explicit SpiDriver(uint8_t csPin, bool interrupts = false); + /** + * Activates the driver. Similar to SPI.beginTransaction or setting clock div, etc. + */ void activate(); + /** + * Deactivates the driver after activate(). Similar to SPI.endTransaction + */ void deactivate(); + /** + * Starts SPI. Usually wraps SPI.begin() + */ void begin(); + /** + * Ends SPI. Usually wraps SPI.end() + */ void end(); + /** + * Reads 1 byte from SPI + * @return Read data + */ uint8_t receive(); + /** + * Reads 2 bytes + * @return Bytes read + */ uint16_t receive16(); + /** + * Read n bytes + * @param buf buffer to read into + * @param count number of bytes to read + * @return A status code + */ uint8_t receive(uint8_t *buf, size_t count); + /** + * Sends 1 byte + * @param data Data to send + */ void send(uint8_t data); + /** + * Sends 2 bytes of data + * @param data The data to send + */ void send16(uint16_t data); + /** + * Send n bytes + * @param buf The start of the buffer to send + * @param count Number of bytes + */ void send(uint8_t *buf, size_t count); + /** + * Sets the clock speed for the SPI controller + * @param speed Speed in Hz + */ inline void setClockSpeed(uint32_t speed) { #if SPI_HAS_TRANSACTION _spiSettings = SPISettings(speed, MSBFIRST, SPI_MODE0); @@ -64,12 +107,23 @@ class SpiDriver { #if USE_DMA_INTERRUPT + /** + * Get the underlying DMA manager, if available. + * @return The Manager + */ DMAManager *getDMAManager() { return &dmaManager; } + /** + * Sends a chain of DMA frames, if available + * @param head The start of the chain. + */ void sendChain(volatile LLI *head); + /** + * Starts the next chain of DMA operations + */ void nextDMA(); #endif diff --git a/examples/dma_squares/dma_squares.ino b/examples/dma_squares/dma_squares.ino new file mode 100644 index 0000000..2602a3f --- /dev/null +++ b/examples/dma_squares/dma_squares.ino @@ -0,0 +1,153 @@ +#if defined(ARDUINO_SAM_DUE) +#include +#include "Adafruit_I2CDevice.h" +#include "Adafruit_RA8875.h" +#include "function_timings.h" + + +// Library only supports hardware SPI at this time +// Connect SCLK to UNO Digital #13 (Hardware SPI clock) +// Connect MISO to UNO Digital #12 (Hardware SPI MISO) +// Connect MOSI to UNO Digital #11 (Hardware SPI MOSI) +#define RA8875_INT 23 +#define RA8875_CS 10 +#define RA8875_RESET 26 + +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 + +#define draw_width (DISPLAY_WIDTH / 10) +#define draw_height (DISPLAY_HEIGHT / 6) + +#define TRACE_DEBUG 0 + +uint16_t pixels[draw_width * draw_height]; + +Adafruit_RA8875 tft = Adafruit_RA8875(RA8875_CS, RA8875_RESET, true); +volatile bool ready = true; +int16_t x_pos = 0; +int16_t y_pos = 0; + +static void test_spi_blocking() { +#if TRACE_DEBUG + Serial.println("\r\nStarting draw (Blocking SPI)"); + uint32_t full_timing = micros(); +#endif + while(y_pos < DISPLAY_HEIGHT) { + tft.drawPixelsArea(pixels, draw_height * draw_width, x_pos, y_pos, draw_width); + if(x_pos + draw_width >= DISPLAY_WIDTH) { + y_pos += draw_height; + x_pos = 0; + } else { + x_pos += draw_width; + } + delay(250); + } +#if TRACE_DEBUG + Serial.println("Released draw"); + Serial.print("Total time: "); Serial.print(micros() - full_timing); Serial.println(" us"); +#endif +} + +static void test_dma_regular() { +#if USE_DMA_INTERRUPT + if(y_pos >= DISPLAY_HEIGHT) { + return; + } +#if TRACE_DEBUG + Serial.print("[ "); Serial.print(x_pos); Serial.print(", "); Serial.print(y_pos); Serial.println(" ]"); +#endif + tft.drawPixelsAreaDMASlow(pixels, draw_height * draw_width, x_pos, y_pos, draw_width, nullptr, [](void* data) { + if(x_pos + draw_width >= DISPLAY_WIDTH) { + y_pos += draw_height; + x_pos = 0; + } else { + x_pos += draw_width; + } + ready = true; + }); +#endif +} + +static void test_dma_rows() { +#if USE_DMA_INTERRUPT + if(y_pos >= DISPLAY_HEIGHT) { + return; + } +#if TRACE_DEBUG + Serial.print("[ "); Serial.print(x_pos); Serial.print(", "); Serial.print(y_pos); Serial.println(" ]"); +#endif + tft.drawPixelsAreaDMA(pixels, draw_height * draw_width, x_pos, y_pos, draw_width, nullptr, [](void* data) { + if(x_pos + draw_width >= DISPLAY_WIDTH) { + y_pos += draw_height; + x_pos = 0; + } else { + x_pos += draw_width; + } + ready = true; + }); +#endif +} + + +void setup() +{ + // Enable interrupts for DMAC + NVIC_EnableIRQ(DMAC_IRQn); + Serial.begin(250000); + Serial.println("\nRA8875 start"); + delay(1000); + + // Fill buffer with green pixels + for(unsigned short & pixel : pixels) { + pixel = 0xA07F; + } + + /* Initialize the display using 'RA8875_480x80', 'RA8875_480x128', 'RA8875_480x272' or 'RA8875_800x480' */ + if (!tft.begin(RA8875_800x480)) { + Serial.println("RA8875 Not Found!"); + while (1); + } + + /** + * Text mode doesn't like the high bus speeds for some reason + * */ + tft.setClockSpeed(4000000); + + tft.displayOn(true); + tft.GPIOX(true); // Enable TFT - display enable tied to GPIOX + tft.PWM1config(true, RA8875_PWM_CLK_DIV1024); // PWM output for backlight + tft.PWM1out(255); + tft.fillScreen(RA8875_BLACK); + tft.graphicsMode(); + + // HARD LOW LIMIT: 28MHz (inclusive) + // Anything below that and DMA just moves too quickly for every byte to transfer in time + // HARD HIGH LIMIT: 42MHz (exclusive) + // Anything higher and it moves too fast for the RA chip. + // Basically MCK / 3 + tft.setClockSpeed(28000000L); + RA_SET_DEBUG(true); + delay(1000); +} + +void loop() +{ + delay(250); + if(ready) { + ready = false; + // test_spi_blocking(); + test_dma_regular(); +// test_dma_rows(); + } +} + +#if USE_DMA_INTERRUPT +ISR(DMAC_Handler) { + RA_DEBUG_STOP(SPI_TIMING); + RA_DEBUG_START(INTERRUPT); + tft.onDMAInterrupt(); + RA_DEBUG_STOP(INTERRUPT); +} +#endif +#endif \ No newline at end of file