Couple of different ways:
For example: several of our display drivers have code built into them to allow them to do asynchronous updates.
Like my ili9341_t3n library as well as others for ST7735 and ST7789, and ILI9488_t3n library. In these libraries we have code that uses the DMAChannel stuff. The DMAChannel.h/.cpp files are in the Teensy core directories.
In addition:
You can try using the stuff we added to the SPI library, in particular calls:
C++:
// Asynch support (DMA )
#ifdef SPI_HAS_TRANSFER_ASYNC
bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder);
Sorry, there is not a lot of good documentation on the eventResponder and the like. When developing some of this, I had a few test programs, that show some of the different ways to use it:
C++:
#include <SPI.h>
#include <EventResponder.h>
#define CS_PIN 10
volatile bool event_happened = false;
EventResponder event;
static const uint8_t buffer[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
void asyncEventResponder(EventResponderRef event_responder)
{
digitalWriteFast(CS_PIN, HIGH);
event_happened = true;
//Serial.println("Event happened");
}
void setup() {
pinMode(CS_PIN, OUTPUT);
digitalWriteFast(CS_PIN, HIGH);
while (!Serial && millis() < 4000) ; // wait for Serial port
Serial.begin(115200);
SPI.begin();
Serial.println("SPI Test program");
Serial1.begin(2000000);
Serial2.begin(2000000);
Serial3.begin(2000000);
extern const uint8_t _serialEvent_default;
extern const uint8_t _serialEvent1_default;
extern const uint8_t _serialEvent2_default;
extern const uint8_t _serialEvent3_default;
Serial.printf("Default serialEvent? %d %d %d %d\n", _serialEvent_default,
_serialEvent1_default, _serialEvent2_default, _serialEvent3_default);
#if defined(__IMXRT1062__)
Serial4.begin(2000000);
Serial5.begin(2000000);
Serial6.begin(2000000);
Serial7.begin(2000000);
//Serial8.begin(2000000);
extern const uint8_t _serialEvent4_default;
extern const uint8_t _serialEvent5_default;
extern const uint8_t _serialEvent6_default;
extern const uint8_t _serialEvent7_default;
// extern const uint8_t _serialEvent8_default;
Serial.printf(" %d %d %d %d\n", _serialEvent4_default,
_serialEvent5_default, _serialEvent6_default, _serialEvent7_default);
#endif
}
void TimeYieldCalls(const char *sz) {
yield();
Serial.print(sz); Serial.flush();
elapsedMicros em = 0;
for (uint32_t i = 0; i < 1000; i++) yield();
uint32_t elapsed = em;
Serial.print(": ");
Serial.println(elapsed, DEC);
Serial.flush();
}
void loop() {
while (Serial.read() != -1) ; // Make sure queue is empty.
Serial.println("Press any key to run test");
while (!Serial.available()) ; // will loop until it receives something
while (Serial.read() != -1) ; // loop until queue is empty
Serial.printf("start test yield_active_check_flags %x\n", yield_active_check_flags);
Serial.printf(" systick ISR: %x\n", (uint32_t) _VectorsRam[15]);
TimeYieldCalls("Start");
// First try with immediate call.
event.attachImmediate(&asyncEventResponder);
Serial.printf("Test Immediate: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
event.clearEvent();
digitalWriteFast(CS_PIN, LOW);
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
SPI.transfer(buffer, NULL, sizeof(buffer), event);
while (!event_happened) ;
SPI.endTransaction();
TimeYieldCalls("After Immediate");
// Use yield .
event.detach();
event.attach(&asyncEventResponder);
Serial.printf("Test yield: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
event.clearEvent();
digitalWriteFast(CS_PIN, LOW);
SPI.transfer(buffer, NULL, sizeof(buffer), event);
while (!event_happened) ;
TimeYieldCalls("After yield");
// Use Interrupt .
event.detach();
event.attachInterrupt(&asyncEventResponder);
Serial.printf("Test Interrupt: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
event.clearEvent();
digitalWriteFast(CS_PIN, LOW);
SPI.transfer(buffer, NULL, sizeof(buffer), event);
while (!event_happened) ;
TimeYieldCalls("After Interrupt");
Serial2.write(buffer, sizeof(buffer));
delay(5000);
}
void XserialEvent1() {
int ch;
while ((ch = Serial1.read()) != -1) Serial.write(ch);
}
void XserialEvent() {
Serial.write(Serial.read());
}
void serialEventUSB1() {
while (SerialUSB1.available())
Serial.write(SerialUSB1.read());
}
void serialEventUSB2() {
while (SerialUSB2.available())
Serial.write(SerialUSB2.read());
}
Here is another one that shows doing two at the same time:
C++:
#include <SPI.h>
EventResponder event;
EventResponder event1;
uint8_t buffer0[100];
uint8_t buffer1[100];
volatile bool spi_active = false;
volatile bool spi1_active = false;
void Event_SPI0_Responder(EventResponderRef event_responder) {
digitalWriteFast(2, HIGH);
SPI.endTransaction();
Serial.println("SPI0 ended");
spi_active = false;
}
void Event_SPI1_Responder(EventResponderRef event_responder) {
digitalWriteFast(3, HIGH);
SPI1.endTransaction();
Serial.println("SPI1 ended");
spi1_active = false;
}
void setup() {
while (!Serial && (millis() < 2000)) ;
Serial.println("Test SPI DMA on SPI and SPI1");
pinMode(2, OUTPUT);
digitalWriteFast(2, HIGH);
SPI.begin();
event.attachImmediate(&Event_SPI0_Responder);
for (int i = 0; i < sizeof(buffer0); i++) buffer0[i] = i;
pinMode(3, OUTPUT);
digitalWriteFast(3, HIGH);
SPI1.begin();
event1.attachImmediate(&Event_SPI1_Responder);
for (int i = 0; i < sizeof(buffer1); i++) buffer1[i] = sizeof(buffer1) - i;
}
void loop() {
Serial.println("Start Two SPI transfers");
spi_active = true;
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWriteFast(2, LOW);
SPI.transfer(buffer0, nullptr, sizeof(buffer0), event);
spi1_active = true;
SPI1.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWriteFast(3, LOW);
SPI1.transfer(buffer1, nullptr, sizeof(buffer1), event1);
while (spi_active || spi1_active) ; // wait until both are done
Serial.println("Both done");
delay(1000); // wait a second
}
Note: I probably should update these test sketches to align buffers to 32 byte boundaries, especially if the memory you are desiring to use is up in DMAMEM (either directly or using malloc).
Also, when you are doing DMA into or out of memory areas that use the hardware memory cache, the data stored in physical memory may not be in sync with the logical memory values, so if you are doing your own DMA code you may need to do stuff to get them insync.
The SPI transfer has some stuff in it to do this:
Code:
// lets clear cache before we update sizes...
if ((uint32_t)buf >= 0x20200000u) arm_dcache_flush((uint8_t *)buf, count);
if ((uint32_t)retbuf >= 0x20200000u) arm_dcache_delete(retbuf, count);
There is a potential issue with the retbuf one and we probably should change it to: arm_dcache_flush_delete(...)
Why? these dcache functions work on blocks of 32 bytes. and the delete function will simply throw away all current values in the cache that are in that 32 byte range. So if you have other variables in that range, that were recently updated, those changes could easily be lost. Like for example if the memory came from malloc()...
Hope that helps
My look back at current SPI.cpp, there is a potential issue: