sexta-feira, fevereiro 26, 2016

Arduino Due, DMA e Fita de LED c/ APA102

Neste post vamos juntar o que vimos sobre a fita de LED com CI APA102 com o que estudamos sobre o Arduino DUE e experimentar o uso de DMA com o SPI.


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:
  1. // Número de LEDs na fita    
  2. const int NUM_LEDS = 60;  
  3.     
  4. // Comandos para a fita    
  5. byte fita1[4*(NUM_LEDS+2)];  
  6. byte fita2[4*(NUM_LEDS+2)];  
  7.     
  8. // Teste 1 - Envio usando SPI.transfer (s/ DMA)  
  9. void teste1 (byte vel) {  
  10.   unsigned cont = 0;  
  11.   unsigned long termino;  
  12.   
  13.   // iniciações  
  14.   SPI.begin();  
  15.   SPI.setClockDivider(84/vel);  
  16.   int i = 0;  
  17.   fita1 [i++] = 0;  
  18.   fita1 [i++] = 0;  
  19.   fita1 [i++] = 0;  
  20.   fita1 [i++] = 0;  
  21.   while (i < 4*(NUM_LEDS+1)) {  
  22.     fita1 [i++] = 0xE8;  
  23.     fita1 [i++] = 0xFF;  
  24.     fita1 [i++] = 0;  
  25.     fita1 [i++] = 0;  
  26.   }  
  27.   fita1 [i++] = 0xFF;  
  28.   fita1 [i++] = 0xFF;  
  29.   fita1 [i++] = 0xFF;  
  30.   fita1 [i++] = 0xFF;  
  31.   
  32.   // Teste  
  33.   termino = millis() + 10000;  
  34.   while (termino > millis()) {  
  35.     // Envia imagem atual  
  36.     for (i = 0; i < 4*(NUM_LEDS+2); i++) {    
  37.       SPI.transfer(fita1[i]);    
  38.     }  
  39.     cont++;  
  40.     // Passa para a imagem seguinte  
  41.     for (i = 4; i < 4*(NUM_LEDS+1); i+=4) {  
  42.       if (fita1[i+1] == 0xFF) {  
  43.         fita1[i+1] = 0;  
  44.         fita1[i+2] = 0xFF;  
  45.       } else if (fita1[i+2] == 0xFF) {  
  46.         fita1[i+2] = 0;  
  47.         fita1[i+3] = 0xFF;  
  48.       } else {  
  49.         fita1[i+3] = 0;  
  50.         fita1[i+1] = 0xFF;  
  51.       }  
  52.     }  
  53.   }  
  54.   
  55.   Serial.print ("Teste1 @");  
  56.   Serial.print (vel);  
  57.   Serial.print ("MHz: ");  
  58.   Serial.print (cont/10);  
  59.   Serial.println (" atualizacoes por segundo.");  
  60. }  
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.
  1. //  Teste 2 - Acesso direto, c/ DMA  
  2. void teste2(byte vel) {  
  3.   unsigned cont = 0;  
  4.   unsigned long termino;  
  5.   
  6.   // iniciações  
  7.   spiBegin();  
  8.   spiInit(84/vel);  
  9.   int i = 0;  
  10.   fita1 [i++] = 0;  
  11.   fita1 [i++] = 0;  
  12.   fita1 [i++] = 0;  
  13.   fita1 [i++] = 0;  
  14.   while (i < 4*(NUM_LEDS+1)) {  
  15.     fita1 [i++] = 0xE8;  
  16.     fita1 [i++] = 0xFF;  
  17.     fita1 [i++] = 0;  
  18.     fita1 [i++] = 0;  
  19.   }  
  20.   fita1 [i++] = 0xFF;  
  21.   fita1 [i++] = 0xFF;  
  22.   fita1 [i++] = 0xFF;  
  23.   fita1 [i++] = 0xFF;  
  24.   
  25.   // Teste  
  26.   termino = millis() + 10000;  
  27.   while (termino > millis()) {  
  28.     // Envia imagem atual  
  29.     memcpy (fita2, fita1, sizeof(fita2));  
  30.     spiSend(fita2, sizeof(fita2));  
  31.     cont++;  
  32.     // Passa para a imagem seguinte  
  33.     for (i = 4; i < 4*(NUM_LEDS+1); i+=4) {  
  34.       if (fita1[i+1] == 0xFF) {  
  35.         fita1[i+1] = 0;  
  36.         fita1[i+2] = 0xFF;  
  37.       } else if (fita1[i+2] == 0xFF) {  
  38.         fita1[i+2] = 0;  
  39.         fita1[i+3] = 0xFF;  
  40.       } else {  
  41.         fita1[i+3] = 0;  
  42.         fita1[i+1] = 0xFF;  
  43.       }  
  44.     }  
  45.   }  
  46.     
  47.   Serial.print ("Teste2 @");  
  48.   Serial.print (vel);  
  49.   Serial.print ("MHz: ");  
  50.   Serial.print (cont/10);  
  51.   Serial.println (" atualizacoes por segundo.");  
  52. }  
  53.   
  54. // Rotinas para uso do SPI com DMA  
  55. // Adaptadas de https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino  
  56.   
  57. /** chip select register number */  
  58. #define SPI_CHIP_SEL 3  
  59. /** DMAC transmit channel */  
  60. #define SPI_DMAC_TX_CH  0  
  61. /** DMAC Channel HW Interface Number for SPI TX. */  
  62. #define SPI_TX_IDX  1  
  63.   
  64. /** Disable DMA Controller. */  
  65. static void dmac_disable() {  
  66.   DMAC->DMAC_EN &= (~DMAC_EN_ENABLE);  
  67. }  
  68. /** Enable DMA Controller. */  
  69. static void dmac_enable() {  
  70.   DMAC->DMAC_EN = DMAC_EN_ENABLE;  
  71. }  
  72. /** Disable DMA Channel. */  
  73. static void dmac_channel_disable(uint32_t ul_num) {  
  74.   DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num;  
  75. }  
  76. /** Enable DMA Channel. */  
  77. static void dmac_channel_enable(uint32_t ul_num) {  
  78.   DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num;  
  79. }  
  80.   
  81. /** Poll for transfer complete. */  
  82. static bool dmac_channel_transfer_done(uint32_t ul_num) {  
  83.   return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true;  
  84. }  
  85.   
  86. // start TX DMA  
  87. void spiDmaTX(const uint8_t* src, uint16_t count) {  
  88.   static uint8_t ff = 0XFF;  
  89.   uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING;  
  90.   if (!src) {  
  91.     src = &ff;  
  92.     src_incr = DMAC_CTRLB_SRC_INCR_FIXED;  
  93.   }  
  94.   dmac_channel_disable(SPI_DMAC_TX_CH);  
  95.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = (uint32_t)src;  
  96.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR;  
  97.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR =  0;  
  98.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count |  
  99.     DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;  
  100.   
  101.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB =  DMAC_CTRLB_SRC_DSCR |  
  102.     DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC |  
  103.     src_incr | DMAC_CTRLB_DST_INCR_FIXED;  
  104.   
  105.   DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) |  
  106.       DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG;  
  107.   
  108.   dmac_channel_enable(SPI_DMAC_TX_CH);  
  109. }  
  110.   
  111. // Prepara para o uso da interface SPI  
  112. static void spiBegin() {  
  113.   PIO_Configure(  
  114.       g_APinDescription[PIN_SPI_MOSI].pPort,  
  115.       g_APinDescription[PIN_SPI_MOSI].ulPinType,  
  116.       g_APinDescription[PIN_SPI_MOSI].ulPin,  
  117.       g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration);  
  118.   PIO_Configure(  
  119.       g_APinDescription[PIN_SPI_MISO].pPort,  
  120.       g_APinDescription[PIN_SPI_MISO].ulPinType,  
  121.       g_APinDescription[PIN_SPI_MISO].ulPin,  
  122.       g_APinDescription[PIN_SPI_MISO].ulPinConfiguration);  
  123.   PIO_Configure(  
  124.       g_APinDescription[PIN_SPI_SCK].pPort,  
  125.       g_APinDescription[PIN_SPI_SCK].ulPinType,  
  126.       g_APinDescription[PIN_SPI_SCK].ulPin,  
  127.       g_APinDescription[PIN_SPI_SCK].ulPinConfiguration);  
  128.   pmc_enable_periph_clk(ID_SPI0);  
  129.   pmc_enable_periph_clk(ID_DMAC);  
  130.   dmac_disable();  
  131.   DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED;  
  132.   dmac_enable();  
  133. }  
  134.   
  135. //  configura a interface SPI  
  136. //  velocidade = 84/spiRate MHz  
  137. static void spiInit(uint8_t spiRate) {  
  138.   Spi* pSpi = SPI0;  
  139.   uint8_t scbr = 255;  
  140.   if (spiRate > 0) {  
  141.     scbr = spiRate;  
  142.   }  
  143.   //  disable SPI  
  144.   pSpi->SPI_CR = SPI_CR_SPIDIS;  
  145.   // reset SPI  
  146.   pSpi->SPI_CR = SPI_CR_SWRST;  
  147.   // no mode fault detection, set master mode  
  148.   pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR;  
  149.   // mode 0, 8-bit,  
  150.   pSpi->SPI_CSR[SPI_CHIP_SEL] = SPI_CSR_SCBR(scbr) | SPI_CSR_NCPHA;  
  151.   // enable SPI  
  152.   pSpi->SPI_CR |= SPI_CR_SPIEN;  
  153. }  
  154.   
  155. // Aguarda fim do envio anterior e dispara novo envio  
  156. static void spiSend(const uint8_t* buf, size_t len) {  
  157.   Spi* pSpi = SPI0;  
  158.   // wait end of previous dma transfer  
  159.   while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) {  
  160.   }  
  161.   // wait last byte sent  
  162.   while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {  
  163.   }  
  164.   // leave RDR empty  
  165.   uint8_t b = pSpi->SPI_RDR;  
  166.   // start new transfer  
  167.   spiDmaTX(buf, len);  
  168. }  
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: