DMA nos RL78

Olá pessoal,

Neste artigo eu descrevo o módulo de DMA disponível em todos os modelos RL78/G13. O DMA (Direct Memory Access ou acesso direto à memória) é um módulo que permite que um periférico acesse diretamente a memória RAM do microcontrolador, transferindo dados da RAM para o periférico ou vice-versa.

A utilidade do DMA é indiscutível, mas é conveniente exemplificar como este periférico pode auxiliar numa aplicação.

As aplicações envolvendo microcontroladores frequentemente possuem a necessidade de movimentar dados: sejam caracteres recebidos ou a serem enviados pela porta serial, sejam amostras obtidas pelo conversor A/D, etc. Tradicionalmente utilizam-se interrupções para sinalizar a CPU que existe a necessidade de se realizar uma movimentação de dados como as descritas. Assim, ao receber um novo caractere, por exemplo, a UART gera uma interrupção. A CPU reconhece a interrupção e desvia o fluxo do programa para a ISR que irá efetuar a leitura do dado recebido e o seu armazenamento, por exemplo, num buffer.

Esta abordagem, bastante comum, apresenta um grande inconveniente: ela consome um tempo precioso da CPU e o utiliza pra uma tarefa que, digamos, é pouco nobre. É aí que entra o módulo de acesso direto à memória (DMA), pois ele permite automatizar este tipo de operação, realizando a transferência de dados entre periférico e memória sem a necessidade de intervenção da CPU!

Nos RL78, o módulo de DMA apresenta-se com 2 ou 4 canais independentes. Cada canal pode ser configurado para realizar transferências de 8 ou 16 bits entre periféricos e RAM (a direção pode ser selecionada), com blocos de até 1024 transferências.

Um canal de DMA pode ser disparado por um dos seguintes periféricos: conversor A/D, canais da TAU, SAU (UART) ou SAU (CSI). Uma característica interessante é que uma transferência DMA pode ser disparada por um periférico, mas a operação de transferência do DMA pode envolver outro periférico e não necessariamente aquele que provocou o disparo. Um exemplo típico desta situação é a utilização de um canal da TAU para realizar leituras periódicas de uma porta de E/S e o seu armazenamento (através do DMA) na RAM.

O exemplo a seguir demonstra como utilizar um canal de DMA para automatizar a transmissão de dados com a UART.

Um buffer de 32 bytes é utilizado para o armazenamento dos dados a serem transmitidos. A função tx_UART2() é utilizada para escrever uma string no buffer e configurar o  DMA para realizar a transferência dos dados através da UART2.

Toda a transferência é realizada automaticamente pelo DMA, ou seja, após cada caractere ser transmitido o flag de transmissão da UART2 (STIF2) é setado, provocando o disparo do DMA que transfere um novo caractere do buffer para o registrador TXD2.

Este processo segue até que todos os caracteres tenham sido transmitidos (registrador DBC0 chegue a zero e bit DST seja apagado no registrador DRC0).

Adicionalmente a aplicação também responde aos caracteres recebidos pela UART2, invertendo o estado do led D2 da placa RPB a cada caractere recebido e enviando a string “Teste!” ao receber o caractere ‘t’ e “RL78!” ao receber o caractere ‘r’.

O código foi escrito para a placa de promoção do RL78/G13  (YRPBRL78G13) e utiliza a porta COM virtual disponibilizada pela mesma. Para utilizar esta porta virtual, basta configurar, após o download do código, os jumpers J6 a J9 na posição 2-3.

#include "ior5f100le.h"
#include "ior5f100le_ext.h"
#include "intrinsics.h"
#include "myRL78.h"
// Configura watchdog = desligado
#pragma location = "OPTBYTE"
__root __far const char opbyte0 = WDT_OFF;
// Configura detector de baixa tensão = desligado
#pragma location = "OPTBYTE"
__root __far const char opbyte1 = LVD_OFF;
// oscilador 32MHz flash high speed
#pragma location = "OPTBYTE"
__root __far const char opbyte2 = FLASH_HS | CLK_32MHZ;
// debug ativado, com apagamento em caso de falha de autenticação
#pragma location = "OPTBYTE"
__root __far const char opbyte3 = DEBUG_ON_ERASE;
/* Configura security ID */
#pragma location = "SECUID"
__root __far const char senha[10] = {0,0,0,0,0,0,0,0,0,0};

#define __9600BPS 51<<9
#define LED P7_bit.no7
#define TX_BUF_SIZE 32

unsigned char tx_buffer[TX_BUF_SIZE];

void tx_UART2(unsigned char *string);

#pragma vector = INTSR2_vect
__interrupt void trata_rx_UART2(void)
{
  unsigned char temp;
  temp = RXD2; // lê o caractere recebido
  // se recebeu 't' envia "Teste!"
  if (temp=='t') tx_UART2("Teste!\r\n");
  // se recebeu 'r' envia "RL78!"
  if (temp=='r') tx_UART2("RL78!\r\n");
  LED = !LED; // a cada caractere recebido, inverte o estado do led
}

#pragma vector = INTDMA0_vect
__interrupt void trata_DMA_tx_uart2(void)
{
  DEN0 = 0; // desabilita o DMA
}

void MCU_init(void)
{
  PM1_bit.no3 = 0; // P13/TXD2 como saída
  P1_bit.no3 = 1; // coloca TXD2 em 1 (importante!!!)
  PM1_bit.no4 = 1; // P14/RXD2 como entrada
  PM7_bit.no7 = 0; // P77 como saída (led)
  LED = 1; // desliga o led
  SAU1EN = 1; // ativa a SAU1
  // Clock CK0 da SAU1 = 32MHz / 32 = 1MHz
  SPS1 = SAU_CK0_DIV32;
  // Configura o canal 0 da SAU1 (transmissão da UART2)
  SMR10 = SAU_MD_UART;
  SCR10 = SAU_COMM_TX|SAU_NO_PARITY|SAU_LSB_FIRST|SAU_ONE_STOP|SAU_8BITS;
  SDR10 = __9600BPS; // seta o baud rate do transmissor
  // Configura o canal 1 da SAU1 (recepção da UART2)
  SMR11 = bSAU_STS | SAU_MD_UART;
  SCR11 = SAU_COMM_RX|SAU_NO_PARITY|SAU_LSB_FIRST|SAU_ONE_STOP|SAU_8BITS;
  SDR11 = __9600BPS; // seta o baud rate do receptor
  SOE1 = SAU_CH0; // habilita a saída da UART2
  SO1 = SAU_CH0; // seta a saída TXD2
  NFEN0 = SNFEN20; // ativa o filtro digital da entrada RXD2
  // Dispara os canais 0 e 1 da SAU1
  SS1 = SAU_CH1 | SAU_CH0;
  SRMK2 = 0; // habilita a interrupção de recepção da UART
  DMAMK0 = 0; // habilita a interrupção do DMA0
  __enable_interrupt(); // habilita as interrupções do RL78
}

// envia uma string pela UART2 utilizando o DMA
void tx_UART2(unsigned char *string)
{
  unsigned char index;
  index = 0;
  // copia a string para o buffer de transmissão
  while (string)
  {
    tx_buffer[index++] = *string++;
    // se atingiu o final da string ou o tamanho do buffer, encerra a cópia
    if (index==TX_BUF_SIZE || !*string) break;
  }
  // se o buffer contém caracteres, configura o DMA para a transferência
  if (index)
  {
    DRC0 = bDEN; // habilita o DMA0
    DBC0 = index; // configura o número de bytes a transferir
    DSA0 = (char)&TXD2; // configura o endereço do registrador alvo
    DRA0 = (int)tx_buffer; // configura o endereço do buffer com os dados
    DMC0 = bDRS | DMA_TRIG_TX2; // configura o canal de DMA
    DRC0 = bDEN | bDST; // seta DST para iniciar a transferência
    STG0 = 1; // dispara o DMA e inicia a transferência
  }
}

void main(void)
{
  MCU_init();
  // transmite uma string de saudação
  tx_UART2("Teste da UART com DMA!");
  while (1); // aguarda uma interrupção
}

3 thoughts on “DMA nos RL78

  1. Olá Fábio. Estou seguindo aqui o livro e já no primeiro exemplo travei.
    O IAR não esta recnhecendo o #include “myRL78.h” e nem a última palavra das linhas com “__root __far” que vem logo abaixo. Pode me dar essa força?

    1. Olá Rafael,

      Você baixou o código daqui do blog? O arquivo myRL78.h é de minha autoria e deve ser baixado juntamente com os demais arquivos do livro, conforme está mencionado na página 40. Qualquer problema estou as ordens.

      Fábio

Leave a Reply