cancel
Showing results for 
Search instead for 
Did you mean: 

USART TX DMA on STM32G031

Stastny.Petr
Associate III

Hello I need to send a string via USART every 500ms. I tried to use DMA that seems to be easy to configure. USART transfer works but only without DMA (test in while main cycle). I do not see any problem in DMA configuration. Can you tell me it there is something that I do not set?

I start DMA transfer in Systick cycle. 

Thank you 

 

 

#include "main.h"
#include "stm32g0xx.h"
#include <stdint.h>


#define BAUD_RATE 9600
#define SYS_CLK 64000000

const char message[] = "Hello, world!\r\n";

void DMA_Init(void) {
    // Enable DMA clock
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    // Configure DMA for USART1 TX (DMA1 Channel 1)
    DMA1_Channel1->CPAR = (uint32_t)&USART1->TDR;
    DMA1_Channel1->CMAR = (uint32_t)message;
    DMA1_Channel1->CNDTR = sizeof(message) - 1;
    DMA1_Channel1->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;

    // Enable DMA1 interrupt
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

void USART1_Init(void) {

    // Enable clocks
    RCC->IOPENR |= RCC_IOPENR_GPIOBEN;   // Enable GPIOB clock
    RCC->APBENR2 |= RCC_APBENR2_USART1EN; // Enable USART1 clock

    // Configure PB6 as USART1_TX and PB7 as USART1_RX
    GPIOB->MODER &= ~(GPIO_MODER_MODE6_Msk | GPIO_MODER_MODE7_Msk);
    GPIOB->MODER |= (0x2 << GPIO_MODER_MODE6_Pos) | (0x2 << GPIO_MODER_MODE7_Pos); // Set PB6 and PB7 to AF mode
    GPIOB->AFR[0] |= (0x0 << GPIO_AFRL_AFSEL6_Pos) | (0x0 << GPIO_AFRL_AFSEL7_Pos); // Set AF0 (USART1)

    // Configure USART1
    USART1->BRR = SYS_CLK / BAUD_RATE; // Set baud rate
    USART1->CR3 |= USART_CR3_DMAT; // Enable DMA for transmitter - TOTO JE NAVIC
    USART1->CR1 |= USART_CR1_TE;       // Enable transmitter
    USART1->CR1 |= USART_CR1_UE;       // Enable USART

}

void DMA1_Channel1_IRQHandler(void) {

    if (DMA1->ISR & DMA_ISR_TCIF1) {
        // Clear the transfer complete interrupt flag
        DMA1->IFCR |= DMA_IFCR_CTCIF1;

        // Optional: Disable DMA channel if needed, and re-enable if you need to prepare for the next transfer
        DMA1_Channel1->CCR &= ~DMA_CCR_EN; // Disable DMA channel
        DMA1_Channel1->CNDTR = sizeof(message) - 1;//1; // Reset number of data to transfer
        DMA1_Channel1->CCR |= DMA_CCR_EN; // Re-enable DMA channel
    }

}



void SysTick_Handler(void)
{
    SysTickCounter++;
    if (SysTickCounter>500) {
    	DMA1_Channel1->CCR |= DMA_CCR_EN;	
    	SysTickCounter = 0;
    }
}


void USART1_SendString(const char *str) {
    while (*str) {
        while (!(USART1->ISR & USART_ISR_TXE_TXFNF_Msk)); // Wait until TX buffer is empty
        USART1->TDR = *str++;                             // Transmit character
    }
}


int main(void)
{
  CLK_Config();
  GPIO_Init();
  SysTick_Init();

  DMA_Init();
  USART1_Init();


  while (1)
  {
 
      //USART1_SendString(message); - THIS WORKS, BUT NOT WITH DMA
      //for (volatile int i = 0; i < 1000000; i++);

  }

}

 

1 REPLY 1
KnarfB
Principal III

If your DMA target is a peripheral register, the peripheral (UART) determines the speed of the DMA transfer not to overrrun TDR. DMAMUX is used for that  like so

DMAMUX1_Channel0->CCR = ?? << DMAMUX_CxCR_DMAREQ_ID_Pos;

 (code was for another chip, check ?? for the request ID in the ref.man.)  The line

USART1->CR3 |= USART_CR3_DMAT;

will trigger the very first byte transmitted, so it should be done last, when everthing else is setup.

Some DMA registers cannto be set when DMA is enabled.  

 

Here is my code for the other chip for comparison:

#include <stm32c011xx.h>
void mem2uart_dma() {
    static char src[64] = "The quick brown fox jumped over the lazy dog.\n";

    USART1->TDR = '!'; // send one char for connection testing (without DMA))
    while (!(USART1->ISR & USART_ISR_TXE_TXFNF));   // busy wait for TDR empty

    RCC->AHBENR |= RCC_AHBENR_DMA1EN;   // this is good for DMA and DMAMUX
    (void)RCC->AHBENR;

    if( DMA1_Channel1->CCR & DMA_CCR_EN) {      // channel was in use before
        while(!(DMA1->ISR & DMA_ISR_TCIF1));    // wait for transfer complete (TC) channel flag
        DMA1_Channel1->CCR &= ~DMA_CCR_EN;      // disable DMA channel for setup
    }

    // route peripheral DMA request to DMA channel
    // Table 34: DMAMUX usart1_tx_dma == 51
    // caution: DMAMUX1_Channel0 is for DMA1_Channel1 and so on!
    DMAMUX1_Channel0->CCR = 51 << DMAMUX_CxCR_DMAREQ_ID_Pos;
    
    DMA1->IFCR = DMA_IFCR_CGIF1;    // clear all (HT, TC, TE) flags for DMA channel 1

    DMA1_Channel1->CPAR = (uint32_t)&(USART1->TDR);
    DMA1_Channel1->CMAR = (uint32_t)src;
    DMA1_Channel1->CNDTR = sizeof(src);
    DMA1_Channel1->CCR = 
        0 << DMA_CCR_MEM2MEM_Pos    // MEM2MEM 0: no memory-to-memory mode
    |   0 << DMA_CCR_PL_Pos         // PL priority level 0: low.. 3: very high
    |   0 << DMA_CCR_MSIZE_Pos      // MSIZE 0: 8-bit 1: 16-bit 2: 32-bit
    |   0 << DMA_CCR_PSIZE_Pos      // PSIZE 0: 8-bit 1: 16-bit 2: 32-bit
    |   1 << DMA_CCR_MINC_Pos       // MINC memory increment mode on (1)
    |   0 << DMA_CCR_PINC_Pos       // PINC peripheral increment mode off (0)
    |   0 << DMA_CCR_CIRC_Pos       // CIRC 1: circular mode
    |   1 << DMA_CCR_DIR_Pos        // DIR 0: read from peripheral, 1: memory
    |   0 << DMA_CCR_TEIE_Pos       // TEIE transfer error interrupt 1: enable
    |   0 << DMA_CCR_HTIE_Pos       // HTIE half transfer interrupt 1: enable
    |   0 << DMA_CCR_TCIE_Pos       // TCIE transfer complete interrupt 1: enable
    ;
    DMA1_Channel1->CCR |= DMA_CCR_EN; // enable DMA channel

    // A channel, as soon as enabled, may serve any DMA request from the peripheral 
    // connected to this channel, or may start a memory-to-memory block transfer.

    USART1->CR3 |= USART_CR3_DMAT; // trigger usart1_tx_dma request
}

 hth

KnarfB