O meu primeiro passo foi conferir que o meu programa anterior funcionava no Due. O único detalhe especial é que as conexões de SPI estão no antigo conector ISP:
Como o meu objetivo é melhorar o desempenho usando DMA, resolvi fazer um programa que informa quantas atualizações por segundo eu consigo em cada condição, para uma mesma "animação". Por simplificação, a minha animação consiste em acender uma cor de cada vez.
A primeira versão a testar é usando a biblioteca SPI padrão, sem DMA:
// Número de LEDs na fita const int NUM_LEDS = 60; // Comandos para a fita byte fita1[4*(NUM_LEDS+2)]; byte fita2[4*(NUM_LEDS+2)]; // Teste 1 - Envio usando SPI.transfer (s/ DMA) void teste1 (byte vel) { unsigned cont = 0; unsigned long termino; // iniciações SPI.begin(); SPI.setClockDivider(84/vel); int i = 0; fita1 [i++] = 0; fita1 [i++] = 0; fita1 [i++] = 0; fita1 [i++] = 0; while (i < 4*(NUM_LEDS+1)) { fita1 [i++] = 0xE8; fita1 [i++] = 0xFF; fita1 [i++] = 0; fita1 [i++] = 0; } fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; // Teste termino = millis() + 10000; while (termino > millis()) { // Envia imagem atual for (i = 0; i < 4*(NUM_LEDS+2); i++) { SPI.transfer(fita1[i]); } cont++; // Passa para a imagem seguinte for (i = 4; i < 4*(NUM_LEDS+1); i+=4) { if (fita1[i+1] == 0xFF) { fita1[i+1] = 0; fita1[i+2] = 0xFF; } else if (fita1[i+2] == 0xFF) { fita1[i+2] = 0; fita1[i+3] = 0xFF; } else { fita1[i+3] = 0; fita1[i+1] = 0xFF; } } } Serial.print ("Teste1 @"); Serial.print (vel); Serial.print ("MHz: "); Serial.print (cont/10); Serial.println (" atualizacoes por segundo."); }O parâmetro vel é a velocidade em MHz. O padrão do Due é 4MHz (igual ao Uno). O Due trabalha com um clock do processador de 84MHz e o clock do SPI é este valor dividido por um número de 1 a 255.
A versão com DMA é bem mais complicada, pois vamos acessar diretamente o hardware. O que eu fiz aqui foi simplificar o código que eu achei em https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino.
// Teste 2 - Acesso direto, c/ DMA void teste2(byte vel) { unsigned cont = 0; unsigned long termino; // iniciações spiBegin(); spiInit(84/vel); int i = 0; fita1 [i++] = 0; fita1 [i++] = 0; fita1 [i++] = 0; fita1 [i++] = 0; while (i < 4*(NUM_LEDS+1)) { fita1 [i++] = 0xE8; fita1 [i++] = 0xFF; fita1 [i++] = 0; fita1 [i++] = 0; } fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; fita1 [i++] = 0xFF; // Teste termino = millis() + 10000; while (termino > millis()) { // Envia imagem atual memcpy (fita2, fita1, sizeof(fita2)); spiSend(fita2, sizeof(fita2)); cont++; // Passa para a imagem seguinte for (i = 4; i < 4*(NUM_LEDS+1); i+=4) { if (fita1[i+1] == 0xFF) { fita1[i+1] = 0; fita1[i+2] = 0xFF; } else if (fita1[i+2] == 0xFF) { fita1[i+2] = 0; fita1[i+3] = 0xFF; } else { fita1[i+3] = 0; fita1[i+1] = 0xFF; } } } Serial.print ("Teste2 @"); Serial.print (vel); Serial.print ("MHz: "); Serial.print (cont/10); Serial.println (" atualizacoes por segundo."); } // Rotinas para uso do SPI com DMA // Adaptadas de https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino /** chip select register number */ #define SPI_CHIP_SEL 3 /** DMAC transmit channel */ #define SPI_DMAC_TX_CH 0 /** DMAC Channel HW Interface Number for SPI TX. */ #define SPI_TX_IDX 1 /** Disable DMA Controller. */ static void dmac_disable() { DMAC->DMAC_EN &= (~DMAC_EN_ENABLE); } /** Enable DMA Controller. */ static void dmac_enable() { DMAC->DMAC_EN = DMAC_EN_ENABLE; } /** Disable DMA Channel. */ static void dmac_channel_disable(uint32_t ul_num) { DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num; } /** Enable DMA Channel. */ static void dmac_channel_enable(uint32_t ul_num) { DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num; } /** Poll for transfer complete. */ static bool dmac_channel_transfer_done(uint32_t ul_num) { return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true; } // start TX DMA void spiDmaTX(const uint8_t* src, uint16_t count) { static uint8_t ff = 0XFF; uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING; if (!src) { src = &ff; src_incr = DMAC_CTRLB_SRC_INCR_FIXED; } dmac_channel_disable(SPI_DMAC_TX_CH); DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = (uint32_t)src; DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | src_incr | DMAC_CTRLB_DST_INCR_FIXED; DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG; dmac_channel_enable(SPI_DMAC_TX_CH); } // Prepara para o uso da interface SPI static void spiBegin() { PIO_Configure( g_APinDescription[PIN_SPI_MOSI].pPort, g_APinDescription[PIN_SPI_MOSI].ulPinType, g_APinDescription[PIN_SPI_MOSI].ulPin, g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration); PIO_Configure( g_APinDescription[PIN_SPI_MISO].pPort, g_APinDescription[PIN_SPI_MISO].ulPinType, g_APinDescription[PIN_SPI_MISO].ulPin, g_APinDescription[PIN_SPI_MISO].ulPinConfiguration); PIO_Configure( g_APinDescription[PIN_SPI_SCK].pPort, g_APinDescription[PIN_SPI_SCK].ulPinType, g_APinDescription[PIN_SPI_SCK].ulPin, g_APinDescription[PIN_SPI_SCK].ulPinConfiguration); pmc_enable_periph_clk(ID_SPI0); pmc_enable_periph_clk(ID_DMAC); dmac_disable(); DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED; dmac_enable(); } // configura a interface SPI // velocidade = 84/spiRate MHz static void spiInit(uint8_t spiRate) { Spi* pSpi = SPI0; uint8_t scbr = 255; if (spiRate > 0) { scbr = spiRate; } // disable SPI pSpi->SPI_CR = SPI_CR_SPIDIS; // reset SPI pSpi->SPI_CR = SPI_CR_SWRST; // no mode fault detection, set master mode pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR; // mode 0, 8-bit, pSpi->SPI_CSR[SPI_CHIP_SEL] = SPI_CSR_SCBR(scbr) | SPI_CSR_NCPHA; // enable SPI pSpi->SPI_CR |= SPI_CR_SPIEN; } // Aguarda fim do envio anterior e dispara novo envio static void spiSend(const uint8_t* buf, size_t len) { Spi* pSpi = SPI0; // wait end of previous dma transfer while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) { } // wait last byte sent while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) { } // leave RDR empty uint8_t b = pSpi->SPI_RDR; // start new transfer spiDmaTX(buf, len); }O programa principal exercita as duas versões a 4, 7, 14, 21 e 28MHz. O programa completo pode ser baixado dos arquivos do blog (FitaLED_Due.ino). Os resultados estão no gráfico abaixo:
Na versão sem DMA a velocidade pouco mais que dobra quando passamos de 4 para 28MHz. Isto ocorre porque não temos paralelismo entre o envio e a geração da nova imagem e temos um trabalho significativo para disponibilizar cada byte a ser enviado.
Na versão com DMA a velocidade aumenta 7 vezes passando de 4 para 28MHz.O hardware cuida de mover os bytes da memória para o transmissor, liberando o processador para gerar a imagem.
25/03/16: Corrigido erro na versão com DMA e atualizado o gráfico e o texto com os novos resultados.
Nenhum comentário:
Postar um comentário