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.");
- }
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);
- }
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