DMA UART (Serial)?

Has anyone created a simple lib to use the DMA on UART (Serial) Output?

Thanks

Found this so far:

Ok, I'm having actual fun with this, as I read the PDF of the SAM IC :slight_smile:

http://www.atmel.com/Images/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf

Under the DMA section you can find all details, just replace PERIPH with UART

void ConfigureUart( void )
{
PIOA->PIO_IDR = PIO_PA8A_URXD | PIO_PA9A_UTXD;
PIOA->PIO_PDR = PIO_PA8A_URXD | PIO_PA9A_UTXD;
PIOA->PIO_ABSR &= ~(PIO_PA8A_URXD | PIO_PA9A_UTXD);
PIOA->PIO_PUER = PIO_PA8A_URXD | PIO_PA9A_UTXD;
PMC->PMC_PCER0 = 1 << ID_UART;
UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS |UART_CR_TXDIS;

// Set the baudrate to 115200
UART->UART_BRGR = 45 ; // 84000000 / 16 * x = BaudRate (write x into UART_BRGR)

// No Parity
UART->UART_MR = UART_MR_PAR_NO;
//UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
UART->UART_IDR = 0xFFFFFFFF ;

NVIC_EnableIRQ((IRQn_Type)ID_UART);
UART->UART_IER = UART_IER_ENDTX; //<--makierte Zeile

UART->UART_CR |= UART_CR_TXEN;

}

void UartSend(uint8_t* value,uint16_t size)
{
UART->UART_TPR = (uint32_t)value;
UART->UART_TCR = (uint32_t)size;

UART->UART_PTCR |= UART_PTCR_TXTEN;
}

Did you have any success using rblilja's DmaSerial library?

I couldn't make it work commenting the lines it said to comment and everything, but I don't have much experience using 3rd party libraries.

I'm trying to use it as I had a code writing to DAC and reading from ADC at a reasonable speed but if I try to send the data (both the written and read) through serial using Serial.println() it slows down a lot the code. Is DMA the solution to this? And if so, how do you send and read data? I don't get why your UartSend function needs two arguments.

DMA for both ADC and DAC should considerably improve your code, moreover if a DMA is used for UART the CPU utilization should be pretty minimal.

Here is an example sketch to echo data between Serial Monitor and the DUE with a PDC DMA. Note that reception side is less obvious than the transmission side.

Stepwise:

First off, you will have to make some modifications in variant.h. in order to write your own code for the UART Handler. Go to File>Preferences and click in the url at the bottom of the parameters window (C:\Users...\AppData\Local\Arduino 15\preferences.txt), then follow this path:
packages/arduino/sam/1.6.x/variants/arduino_due_x/variant.h

Copy variant.h into a blank sketch (without setup() and loop()), alter the code around void UART_Handler(void) as shown in the below sketch, copy and save the new variant.h.

/*********************************************************************************************/
/*             UART PDC DMA for Reception and Transmission                                   */
/* Hook a jumper between UART input pin (PA8) and TIOB0 (PB27) to trigger the timer counter  */
/*********************************************************************************************/

#define UART_RX_WAIT_MS (100)                 // For 100 milliseconds timestamp
#define TimeStamp  (CHIP_FREQ_SLCK_RC * UART_RX_WAIT_MS / 1000)

#define BUF_SIZE 8

uint8_t BUF1[BUF_SIZE];
uint8_t BUF2[BUF_SIZE];
uint8_t* RX_BUF;

extern void (*uart_isr)(void); // variant file previously modified
/*
 ***********   in variant.h  *****************
  // IT handlers
  void (*uart_isr)(void) = (0UL);
  void UART_Handler(void)
  {
  if (uart_isr) uart_isr();
  else Serial.IrqHandler();
  }
*/

void setup() {
  // Handy to initialize some UART registers
  Serial.begin(250000);
  
  tc_init();

  RX_BUF = BUF1;

  UART->UART_RPR = (uint32_t)RX_BUF;
  UART->UART_RCR = BUF_SIZE;
  UART->UART_PTCR = UART_PTCR_RXTEN | UART_PTCR_TXTEN;

  uart_isr = UART_ISR;
  UART->UART_IER = UART_IER_RXBUFF;
}

void loop() {
}

void UART_ISR(void) {

  if (UART->UART_SR & UART_SR_RXBUFF) {
    TransferRxBufAndRec(BUF_SIZE);
  }
}

void tc_init() {

  PMC->PMC_PCER0 = PMC_PCER0_PID27;                              // TC0 power ON

  TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKDIS ;
  TC0->TC_CHANNEL[0].TC_CMR = TC_CMR_WAVE |                      // Waveform
                              TC_CMR_TCCLKS_TIMER_CLOCK5 |       // Slow clock selected : 32 KHz
                              TC_CMR_WAVSEL_UP_RC |
                              TC_CMR_CPCSTOP  |                  // Stop on RC compare
                              TC_CMR_EEVT_TIOB |                 // TIOB input is the external event
                              TC_CMR_EEVTEDG_FALLING |           // Trigger on falling edge of external event
                              TC_CMR_ENETRG ;                    // Enable trigger on external event

  TC0->TC_CHANNEL[0].TC_RC = TimeStamp;

  TC0->TC_CHANNEL[0].TC_IER = TC_IER_CPCS;
  TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN;

  NVIC_EnableIRQ(TC0_IRQn);
}

void TC0_Handler(void) {

  TC0->TC_CHANNEL[0].TC_SR;  // Read and clear status register

  UART->UART_PTCR = UART_PTCR_RXTDIS;
  uint32_t rec_size = BUF_SIZE - (UART->UART_RCR);
  if (rec_size) {
    TransferRxBufAndRec(rec_size);
  }
  UART->UART_PTCR = UART_PTCR_RXTEN;
}

inline void TransferRxBufAndRec(int size){

  while (!(UART->UART_SR & UART_SR_TXBUFE));  // Wait for PDC DMA Transmission ready

  UART->UART_TPR = (uint32_t) RX_BUF;
  UART->UART_TCR = size;
  RX_BUF = (RX_BUF == BUF1) ? BUF2 : BUF1;
  UART->UART_RPR = (uint32_t) RX_BUF;
  UART->UART_RCR = BUF_SIZE;
}

Because the DMA feature may be very interesting in some cases, after UART with PDC DMA, I wanted to try USART with PDC DMA and USART with AHB DMA for reception and transmission. AHB DMA gives the opportunity to trigger a transfer from a peripheral to another peripheral (or memory and vice versa) unlike PDC DMA limited to transfers between memory and a peripheral and vice versa.

The 2 sketches below are only for demonstration purpose.

With a PDC DMA:

/****************************************************************************/
/*        Echo USART1 with PDC DMA for reception and transmission           */
/*  Hook a jumper between RX1 and TX2 and another one between RX2 and TX1   */
/****************************************************************************/

#define USART_RX_WAIT_MS (10)                 // For 10 milliseconds timestamp
#define TimeStamp  (CHIP_FREQ_SLCK_RC * UART_RX_WAIT_MS / 1000)

#define BUF_SIZE 8

uint8_t BUF1[BUF_SIZE];
uint8_t BUF2[BUF_SIZE];
uint8_t* RX_BUF;

extern void USART1_Handler(void);
/*
 ***********   in variant.h  *****************
  void USART1_Handler(void) __attribute__((weak));
  void USART1_Handler(void)
  {
  Serial2.IrqHandler();
  }
*/
char c = 0;
void setup() {
  Serial.begin(250000);
  Serial1.begin(250000);


  // Handy to initialize some USART1 registers
  Serial2.begin(250000); // USART1 init

  int wait_bit_time = USART_RX_WAIT_MS * 250000 / 1000;
  if (wait_bit_time > 0x1FFFF)
    wait_bit_time = 0x1FFFF;
  USART1->US_RTOR = US_RTOR_TO(wait_bit_time);

  RX_BUF = BUF1;

  USART1->US_RPR = (uint32_t)RX_BUF;
  USART1->US_RCR = BUF_SIZE;
  USART1->US_PTCR = US_PTCR_RXTEN | US_PTCR_TXTEN;

  USART1->US_IER = US_IER_RXBUFF | US_IER_TIMEOUT;

  Serial1.print("**** Hello World ****");
}

void loop() {

  String s;
  s = "";

  while (Serial1.available() > 0) {
    c = Serial1.read();
    s += c;

  }
  if (s.length() > 0) {
    Serial.println(s);
    Serial1.print(s);
  }
  delay(1000);
}

void USART1_Handler(void)
{
  USART1->US_CSR;  // Read and clear status register

  USART1->US_CR = US_CR_RTSDIS;

  int rec_size = BUF_SIZE - USART1->US_RCR;
  if (rec_size)
    TransferRxBufAndRec(rec_size);

  USART1->US_CR = US_CR_STTTO;
  USART1->US_CR = US_CR_RTSEN;
 
}

inline void TransferRxBufAndRec(int size) {

  while (!(USART1->US_CSR & US_CSR_TXBUFE));  // Wait for PDC DMA Transmission ready

  USART1->US_TPR = (uint32_t) RX_BUF;
  USART1->US_TCR = size;
  RX_BUF = (RX_BUF == BUF1) ? BUF2 : BUF1;
  USART1->US_RPR = (uint32_t) RX_BUF;
  USART1->US_RCR = BUF_SIZE;
}

With an AHB DMA:

/****************************************************************************/
/*        Echo USART1 with AHB DMA for reception and transmission           */
/*  Hook a jumper between RX1 and TX2 and another one between RX2 and TX1   */
/****************************************************************************/

#define DMAC_CH 0
extern void USART1_Handler(void); 
/*
 ***********   in variant.h  *****************
  void USART1_Handler(void) __attribute__((weak));
  void USART1_Handler(void)
  {
  Serial2.IrqHandler();
  }
*/
char c = 0;
void setup() {
  Serial.begin(250000);
  Serial1.begin(250000);

  // Handy to initialize some USART1 registers
  Serial2.begin(250000); // USART1 init

  PMC->PMC_PCER1 |= PMC_PCER1_PID39; // DMAC power ON
  DMAC->DMAC_EN = DMAC_EN_ENABLE;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DSCR = 0;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_SADDR = (uint32_t)&USART1->US_RHR;
  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DADDR = (uint32_t)&USART1->US_THR;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA = DMAC_CTRLA_BTSIZE(16) |
                                          DMAC_CTRLA_SRC_WIDTH_BYTE |
                                          DMAC_CTRLA_DST_WIDTH_BYTE;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLB = DMAC_CTRLB_FC_PER2PER_DMA_FC |
                                          DMAC_CTRLB_SRC_INCR_FIXED |
                                          DMAC_CTRLB_DST_INCR_FIXED;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CFG = DMAC_CFG_SRC_H2SEL_HW |
                                        DMAC_CFG_DST_H2SEL_HW |
                                        DMAC_CFG_SRC_PER(14) |
                                        DMAC_CFG_DST_PER(13) |
                                        DMAC_CFG_SOD_DISABLE |
                                        DMAC_CFG_FIFOCFG_ASAP_CFG;

  DMAC->DMAC_EBCIER = DMAC_EBCIER_CBTC0 << DMAC_CH;
  NVIC_EnableIRQ(DMAC_IRQn);

  NVIC_DisableIRQ(USART1_IRQn);
  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;

  Serial1.print("**** Hello World ****");
}

void loop() {

  String s;
  s = "";

  while (Serial1.available() > 0) {
    c = Serial1.read();
    s += c;

  }
  if (s.length() > 0) {
    Serial.println(s);
    Serial1.print(s);
  }
  delay(1000);
}

void DMAC_Handler(void)
{
 DMAC->DMAC_EBCISR;  // Read and clear status register

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA &= ~(uint32_t)DMAC_CTRLA_BTSIZE_Msk;
  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA |= DMAC_CTRLA_BTSIZE(16);

  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;
 
}