// biblioteca para o nosso DMA
#include <SPI.h>
// esse é o tamanho dos endereços da nossa transferencia de dados
#define BUFFER_SIZE 50000
// de onde vem e pra onde vai
uint8_t sourceBuffer[BUFFER_SIZE];
uint8_t destBuffer[BUFFER_SIZE];
SPIClass spi(VSPI);
// o volatile faz a gente checar o valor na memoria e não no cache, assim vemos que ele realmente muda na memória.
volatile int dmaVariable = 0;
// vamos deixar isso tocando enquanto as operações ocorrem para termos ciencia de que:
// 1. Nosso sistema está rodando integralmente
// 2. A cpu *quase* não para seus processos (a gente rouba 2 ciclos pra fazer 2 coisas)
TaskHandle_t heartbeatTaskHandle;
void heartbeatTask(void *parameter) {
while (true) {
Serial.print(".");
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
// cronometramos o tempo da copia de dados para o nosso periférico
void cpuCopyTest() {
Serial.println("\n\n=== CPU COPY TEST ===");
unsigned long start = millis();
for (int i = 0; i < BUFFER_SIZE; i++) {
destBuffer[i] = sourceBuffer[i];
}
unsigned long elapsed = millis() - start;
Serial.print("\nCPU copy completed in ");
Serial.print(elapsed);
Serial.println(" ms");
}
// Aqui:
// 1. mandamos o pedido de transferencia fazendo uma cópia do dmaBuffer pelo source
// 2. Checamos se ela não é maior q o buffer_size
// 3. inicianmos a troca.
// 4. Depois de iniciada, o dma vai alterar os dados sozinho, ou seja, internamente.
// obs: no wowki isso é conceptual, ou seja, a cpu do esp q vai fazer, é de mentira.
// Em um esp32 real não teria pausa total nos heartbeats, somente um leve gargalo.
void dmaTransferTest() {
Serial.println("\n\n=== DMA TRANSFER TEST ===");
uint8_t *dmaBuffer = (uint8_t *)heap_caps_malloc(
BUFFER_SIZE,
MALLOC_CAP_DMA
);
if (!dmaBuffer) {
Serial.println("DMA buffer allocation failed!");
return;
}
memcpy(dmaBuffer, sourceBuffer, BUFFER_SIZE);
unsigned long start = millis();
spi.beginTransaction(SPISettings(
40000000,
MSBFIRST,
SPI_MODE0
));
// transferência DMA ocorre internamente
spi.transferBytes(
dmaBuffer,
NULL,
BUFFER_SIZE
);
spi.endTransaction();
unsigned long elapsed = millis() - start;
Serial.print("\nDMA transfer completed in ");
Serial.print(elapsed);
Serial.println(" ms");
heap_caps_free(dmaBuffer);
}
// No setup inicializamos os seriais pra visualizar tudo isso.
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("ESP32 DMA Demo");
// Preenchemos o buffer de source
for (int i = 0; i < BUFFER_SIZE; i++) {
sourceBuffer[i] = i % 256;
}
// Iniciamos a task da heartbeat
xTaskCreatePinnedToCore(
heartbeatTask,
"Heartbeat",
2048,
NULL,
1,
&heartbeatTaskHandle,
0
);
spi.begin(); // nosso dma controller
delay(1000);
}
void variableTransferTest() {
Serial.println("\n\n=== VARIABLE DMA TEST ===");
uint8_t *dmaBuffer = (uint8_t *)heap_caps_malloc(
BUFFER_SIZE,
MALLOC_CAP_DMA
);
if (!dmaBuffer) {
Serial.println("DMA buffer allocation failed!");
return;
}
memcpy(dmaBuffer, sourceBuffer, BUFFER_SIZE);
spi.beginTransaction(SPISettings(
40000000,
MSBFIRST,
SPI_MODE0
));
// iniciamos a transferência DMA
spi.transferBytes(
dmaBuffer,
NULL,
BUFFER_SIZE
);
// enquanto o DMA ocorre, a CPU continua printando a variável
for (dmaVariable = 0; dmaVariable <= 1000; dmaVariable++) {
Serial.print("DMA Variable Value: ");
Serial.println(dmaVariable);
delay(0.01);
}
spi.endTransaction();
Serial.println("\n\nVariable test complete.");
heap_caps_free(dmaBuffer);
}
void tests(){
cpuCopyTest(); // teste pra ver se a cópia funciona
Serial.println("\nWaiting 3 seconds for transfer test.");
delay(3000);
dmaTransferTest(); // teste de transferencia
Serial.println("\nWaiting 3 seconds for variable DMA test.");
delay(3000);
variableTransferTest();
Serial.println("\n\nDemo complete.");
Serial.println("\nWaiting 5 seconds to show again.");
delay(5000);
}
void loop() {
tests();
}