STM32F103 USART Rx using DMA - Page 1
Forum section: electronics / general

author: noname4me
author_rank: Regular Contributor
timestamp: March 07, 2018, 10:52:05 pm
content: Hi,I am trying to receive a GPS NMEA string using USART1 on a STM32F103C8T6 board.When I use the HAL/CubeMX to do this, I can receive the string fine. I set myself the challenge to try and do it using the registers to see if I understand how to use the DMA properly.I am stuck - the Rx is not working at all.Could anyone have a look and let me know where I've messed it up?The code for the USART1 initialisation is:Code:RCC -> APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN;GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.Pin = GPIO_PIN_9;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);USART1 ->BRR = (72000000/9600);USART1 ->CR1 = USART_CR1_TE |USART_CR1_RXNEIE| USART_CR1_RE;USART1 ->CR1 |= USART_CR1_UE;HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);__enable_interrupt();The code for my DMA initialisation is:Code://1. Turn on clock for AHB Bus and DMA1RCC->AHBENR |= RCC_AHBENR_DMA1EN;//2. enable DMA on Usart1HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);// Clear interrupts//DMA1->IFCR = DMA_IFCR_CTEIF5| DMA_IFCR_CHTIF5| DMA_IFCR_CTCIF5| DMA_IFCR_CGIF5;DMA1->IFCR |= DMA_IFCR_CGIF5; //Global interrupt clear// 4. Data source for DMA - variable name// Peripheral and Memory locations to DMA registersDMA1_Channel5 -> CPAR = (uint32_t) (&(USART1->DR));DMA1_Channel5 -> CMAR = (uint32_t) (DMAArray);// DMA Memory Increment| Circular Buffer|DMA1_Channel5 -> CCR = DMA_CCR_MINC | DMA_CCR_CIRC ;//| DMA_CCR_TEIE | DMA_CCR_TCIE;DMA1_Channel5 -> CCR &= ~(DMA_CCR_MSIZE); // this is already unset on power on, but just to ensure register state:DMA1_Channel5 -> CCR &= ~(DMA_CCR_PSIZE);DMA1_Channel5 -> CCR &= ~(DMA_CCR_DIR);USART1->CR3 |= USART_CR3_DMAR; //Enable DMA in USART1 peripheralDMA1_Channel5 -> CNDTR = 600; //Just make this a big number to perform many transfers__enable_interrupt();DMA1_Channel5 ->CCR = DMA_CCR_EN;DMA1->IFCR |= DMA_IFCR_CGIF5;I have an array called DMAArray; that holds all the data transferred to it by DMA.Is there any specific command to link the DMA to the USART1 that I've missed?Thanks

author: ajb
author_rank: Super Contributor
timestamp: March 08, 2018, 06:11:51 am
content: Is DMA1/Channel5 the correct channel for USART 1 RX? You're enabling the USART rx interrupt, have you defined an interrupt handler for it? Generally you wouldn't have an RX interrupt and RX DMA both enabled at the same time--unless the rx interrupt is only used for error checking, in which case you'd still need to handle the interrupt and clear whatever flag triggered it--you'd either want to let DMA handle everything, or start with the RX interrupt and then disable the interrupt and enable DMA based on some condition.CNDTR should never be larger than the corresponding buffer, otherwise who knows what it might write over.

author: AndyC_772
author_rank: Super Contributor
timestamp: March 08, 2018, 07:15:23 am
content: You need to set the DMAR bit in USART_CR3.As a general rule, I don't tend to leave any configuration registers unset, even if I do want the default setting. Writing values to them all explicitly is good practice, IMHO, if only because it forces me to read and think about ALL the register definitions.

author: noname4me
author_rank: Regular Contributor
timestamp: March 08, 2018, 01:43:48 pm
content: Quote from: ajb on March 08, 2018, 06:11:51 amThanks for the info about the buffer... I'll change that to 128.I thought that the rxne interrupt was used by the dma process? I'll disable it and see how it goes. As an aside the interrupt handler only unsets the interrupt but when i was debugging the code it was not being hit? I don't know why.I would like the dma continuously running as it is supposed to be reading the nmea stream.I enabled the dma in the usart1... please see source above for the text... Ill write a better reply this evening... little hard to do on my phoneThanks for the help guys.Sent from my SM-G935F using Tapatalk

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 08, 2018, 01:50:55 pm
content: Quote from: AndyC_772 on March 08, 2018, 07:15:23 amAnother pitfall that's caught me in the past is assuming defaults, but a bootloader sets up a peripheral differently.

author: ajb
author_rank: Super Contributor
timestamp: March 08, 2018, 05:48:08 pm
content: Quote from: noname4me on March 08, 2018, 01:43:48 pmNo, the RXNE *flag* triggers the RX DMA request. RXNE ALSO triggers the USART IRQ (if RXNEIE is set), so you can use one, the other, or both. The interrupt shouldn't cause the DMA to not work, unless you don't clear flags properly or something and cause some sort of error that disables DMA. (At least on some of the STM32 parts, an overrun will cause the peripheral to simply stop issuing DMA requests until DMA is disabled and reenabled, so it's worth checking the peripheral flags. But I don't think that's your problem here.)Anyway if the ISR isn't getting called, then either the ISR isn't enabled or the USART isn't receiving anything. It doesn't look like you've configured the USART pins correctly. On STM32 you have to set the pin mode to alternate function (as opposed to input, output, or analog) and then you have to configure the AF mux to select which alternate function is active on that pin. The peripheral handles pin direction control.

author: noname4me
author_rank: Regular Contributor
timestamp: March 09, 2018, 12:55:55 am
content: Hi,ajb: Please could you confirm that the RX pin is set as an alternate function (push-pull or open drain?)?The CubeMX code sets it to a floating input...the code above copied the GPIO initialisation from the CubeMX generated code.I've tried with the following code:Code:void GPIOUSART(){GPIO_InitTypeDef GPIOInitStruct;//RX PinGPIOInitStruct.Mode = GPIO_MODE_AF_INPUT;GPIOInitStruct.Pin = GPIO_PIN_10;GPIOInitStruct.Pull = GPIO_NOPULL;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);GPIOInitStruct.Mode = GPIO_MODE_AF_PP;GPIOInitStruct.Pin = GPIO_PIN_9;GPIOInitStruct.Pull = GPIO_NOPULL;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);RCC->APB2ENR |= RCC_APB2ENR_USART1EN;RCC->AHBENR |= RCC_AHBENR_DMA1EN;USART1->BRR = 72000000/9600;USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;USART1->CR3 |= USART_CR3_DMAR;USART1->CR1 |= USART_CR1_UE;DMA1_Channel5 ->CCR &= ~(DMA_CCR_MSIZE);DMA1_Channel5 ->CCR &= ~(DMA_CCR_PSIZE);DMA1_Channel5 ->CCR &= ~(DMA_CCR_MEM2MEM);DMA1_Channel5 ->CCR |= DMA_CCR_CIRC|DMA_CCR_MINC|DMA_CCR_PL_0|DMA_CCR_EN;DMA1_Channel5 ->CMAR = (uint32_t) (DMAArray);DMA1_Channel5 ->CNDTR = 15;DMA1_Channel5 -> CPAR = (uint32_t) (&(USART1->DR));}This is my best effort going through the datasheet line by line and still this does not work???I checked some configuration registers in the debugger in the working version and they contain:USART1->CR1 = 0x210C;USART1->CR3 = 0x41;DMA1_Channel5->CCR = 0xAF;DMA1_Channel5->CNDTR = 0x50;I'm really stuck on this, and if anyone has a working example that I can study, that would be really appreciatedThanks

author: ajb
author_rank: Super Contributor
timestamp: March 09, 2018, 02:36:28 am
content: My mistake, I was thinking of the larger parts that have alternate function muxes and handle in/out/af mode selection differently. I would still recommend checking that the IO control registers are being configured properly, and that pin remapping is set correctly. Disregard DMA first and make sure that the USART itself is working--you said before that the USART ISR wasn't running, so that indicates a problem with USART or IO configuration.

author: newbrain
author_rank: Super Contributor
timestamp: March 09, 2018, 12:02:25 pm
content: I can see one problem in the DMA configuration procedure (see my comment):Code:DMA1_Channel5 ->CCR &= ~(DMA_CCR_MSIZE);DMA1_Channel5 ->CCR &= ~(DMA_CCR_PSIZE);DMA1_Channel5 ->CCR &= ~(DMA_CCR_MEM2MEM);DMA1_Channel5 ->CCR |= DMA_CCR_CIRC|DMA_CCR_MINC|DMA_CCR_PL_0|DMA_CCR_EN; // <--- Must not enable yet!DMA1_Channel5 ->CMAR = (uint32_t) (DMAArray);DMA1_Channel5 ->CNDTR = 15;DMA1_Channel5 -> CPAR = (uint32_t) (&(USART1->DR));The channel is enabled before the configuration is completed, specifically the code writes toCPAR, CMAR and CNTDR after setting DMA_CCR_EN.This is explicitly prohibited in thereference manual, in chapters 13.4.5, 13.4.6 for CPAR and CMAR:Quoteand 13.4.4 for CNTDR:QuoteAs the reset value of CNTDR is 0x00000000u, no DMA transfer can be started.Try to remove the DMA_CCR_EN from that line, and add at the end on a separate line.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 09, 2018, 12:08:47 pm
content: Considering how little data is involved in GPS NMEA messages, do you really need DMA at all ?

author: noname4me
author_rank: Regular Contributor
timestamp: March 10, 2018, 06:55:01 pm
content: Hi,Concerning whether I need DMA or not: my purpose is to learn how to use the all the different peripherals, the GPS NMEA string just an example of what can be received using the USART peripheral. I have an application in mind later this year which may require the use of this feature due to the critical timing aspects of the problem.Using the DMA command at the end of what I have put helped!Everything is working now - I'll clean up my mess in the code before posting the working codeThanks everyone!

author: noname4me
author_rank: Regular Contributor
timestamp: March 10, 2018, 11:10:00 pm
content: The working code to set up the USART1 on STM32F103C8T6:Apologies for the sparse comments - please see the reference manual for details.Code:void GPIOUSART(){RCC -> APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN;GPIO_InitTypeDef GPIOInitStruct;//RX PinGPIOInitStruct.Mode = GPIO_MODE_AF_INPUT;GPIOInitStruct.Pin = GPIO_PIN_10;GPIOInitStruct.Pull = GPIO_NOPULL;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);GPIOInitStruct.Mode = GPIO_MODE_AF_PP;GPIOInitStruct.Pin = GPIO_PIN_9;//GPIOInitStruct.Pull = GPIO_NOPULL;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);RCC->APB2ENR |= RCC_APB2ENR_USART1EN;RCC->AHBENR |= RCC_AHBENR_DMA1EN;USART1->BRR = 72000000/9600;USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;USART1->CR3 |= USART_CR3_DMAR; //Enable DMA on USART1 peripheralUSART1->CR1 |= USART_CR1_UE;}The code to enable the DMA:Code:void setupUsart1Dma(){//1. Turn on clock for AHB Bus and DMA1RCC->AHBENR |= RCC_AHBENR_SRAMEN;RCC->AHBENR |= RCC_AHBENR_DMA1EN;//2. Clear interrupts - may not be needed?DMA1->IFCR |= DMA_IFCR_CGIF5; //Global interrupt clear// Peripheral and Memory locations to DMA registersDMA1_Channel5 -> CPAR = (uint32_t) (&(USART1->DR));DMA1_Channel5 -> CMAR = (uint32_t) (DMAArray);// DMA Memory Increment| Circular Buffer|DMA1_Channel5 -> CCR |= DMA_CCR_MINC | DMA_CCR_CIRC ;//| DMA_CCR_TEIE | DMA_CCR_TCIE;DMA1_Channel5 -> CCR &= ~(DMA_CCR_MSIZE);DMA1_Channel5 -> CCR &= ~(DMA_CCR_PSIZE);DMA1_Channel5 -> CCR &= ~(DMA_CCR_DIR);USART1->CR3 |= USART_CR3_DMAR;DMA1_Channel5 -> CNDTR = 1024;HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);__enable_interrupt();DMA1_Channel5 ->CCR |= DMA_CCR_EN;DMA1->IFCR |= DMA_IFCR_CGIF5;}Hope this helps someone else in the same situation.Thanks everyone.

author: mubes
author_rank: Regular Contributor
timestamp: March 12, 2018, 08:20:03 am
content: A bit late to this party, but there's an example of serial DMA on a f103 available here;https://github.com/mubes/blackmagic/blob/bluepill/src/platforms/stm32/traceswoasync.c...it's using the STM32 libraries but the calls are mostly a 1:1 mapping to the register manipulations. That code runs at 2.25MBps with no issues and would run at 4.5MBps if only the usb connection behind it could reliably swallow the data fast enough.The one thing I would definitely change in your code is to make the Rx pin a pullup because otherwise if you have a device at the other end that doesn't permanently drive the line (I'm looking at you, JLink) you have the distinct potential to miss start bits.Dave

author: noname4me
author_rank: Regular Contributor
timestamp: April 06, 2018, 11:05:47 pm
content: HiThanks for your link - I have had a look around that project and have bookmarked it.I have developed my code a little since I last posted, but am again stuck, and am hoping that someone will be able to point me in the right direction.My idea is that I wish to receive the GPS string using USART with DMA, copy the receiving array to another buffer, and then transmit this over USB VCP.I have tried to implement this without using any sort of interrupt driven system, but found that I kept getting chopped up GPS strings (which seems obvious to me now that this is what I should have expected).I therefore tried to use the IDLE interrupt on the USART to disable the DMA, which I expect to stop the GPS string from being read into the microcontroller, thereby giving me time to copy the buffer into another buffer which I can pass to the USB transmit command.In the main loop I would then check for a situation where the DMA had been disabled so that I could re-enable it again and perform the next set of transfers.Unfortunately, as soon as I enable the IDLE interrupt, I get a hard fault, and am unable to figure out why this is happening.Code:#define DMA_RX_BUFFER_SIZE 64#define UART_BUFFER_SIZE 256uint8_t usb_tx_buf;uint8_t usb_rx_buf;uint8_t usart1_rx_buffer;uint8_t DMAArray; //this is the DMA usart1 rx bufferuint8_t usbss;uint8_t uart1GetChar(void);void setupUsart1Dma(){//1. Turn on clock for AHB Bus and DMA1RCC->AHBENR |= RCC_AHBENR_SRAMEN;RCC->AHBENR |= RCC_AHBENR_DMA1EN;//2. enable DMA on Usart1DMA1->IFCR |= DMA_IFCR_CGIF5; //Global interrupt clear// Peripheral and Memory locations to DMA registersDMA1_Channel5 -> CPAR = (uint32_t) (&(USART1->DR));DMA1_Channel5 -> CMAR = (uint32_t) (DMAArray);// DMA Memory Increment| Circular Buffer|DMA1_Channel5 -> CCR |= DMA_CCR_MINC | DMA_CCR_CIRC;DMA1_Channel5 -> CCR &= ~(DMA_CCR_MSIZE);DMA1_Channel5 -> CCR &= ~(DMA_CCR_PSIZE);DMA1_Channel5 -> CCR &= ~(DMA_CCR_DIR);USART1->CR3 |= USART_CR3_DMAR;USART1->CR1 |= USART_CR1_IDLEIE;DMA1_Channel5 -> CNDTR = GPSBUFFER;HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);HAL_NVIC_SetPriority(USART1_IRQn, 15, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);__enable_interrupt();DMA1_Channel5 ->CCR |= DMA_CCR_EN;DMA1->IFCR |= DMA_IFCR_CGIF5;}void usbprint(uint8_t data[], int length){//length = strlen(data);CDC_Transmit_FS(data, length);}void GPIOUSART(){RCC -> APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN;GPIO_InitTypeDef GPIOInitStruct;//RX PinGPIOInitStruct.Mode = GPIO_MODE_AF_INPUT;GPIOInitStruct.Pin = GPIO_PIN_10;GPIOInitStruct.Pull = GPIO_NOPULL;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);GPIOInitStruct.Mode = GPIO_MODE_AF_PP;GPIOInitStruct.Pin = GPIO_PIN_9;GPIOInitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIOInitStruct);RCC->APB2ENR |= RCC_APB2ENR_USART1EN;RCC->AHBENR |= RCC_AHBENR_DMA1EN;USART1->BRR = 72000000/9600;USART1->CR1 |= USART_CR1_RE;//|USART_CR1_TE |USART_CR1_IDLEIE;USART1->CR3 |= USART_CR3_DMAR;USART1->CR1 |= USART_CR1_UE;//__enable_interrupt();}int main(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration----------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit */GPIOUSART();setupUsart1Dma();/* USER CODE END SysInit *//* Initialize all configured peripherals *///MX_GPIO_Init();//MX_DMA_Init();MX_USB_DEVICE_Init();//MX_SPI1_Init();//MX_USART1_UART_Init();/* USER CODE BEGIN 2 */__enable_interrupt();while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_Delay(100);//usbprint("\r\nSTARTING POINT\r\n\n\n\n",21);strncpy(usbss, DMAArray, GPSBUFFER);HAL_Delay(100);//memset(DMAArray, 0, GPSBUFFER);HAL_Delay(100);usbprint(usbss ,(GPSBUFFER-1));HAL_Delay(100);HAL_Delay(100);//memset(usbss, 0, (GPSBUFFER));HAL_Delay(100);usbprint("\r\nSTRING ENDING HERE\r\n",22);HAL_Delay(100);//DMA1_Channel5 ->CCR &= ~DMA_CCR_EN;// if (!(USART1->CR3 & USART_CR3_DMAR))// {// USART1->CR3 |= USART_CR3_DMAR;// DMA1_Channel5 ->CCR |= DMA_CCR_EN;// }}}Has anyone any experience of trying to do something similar??Any tips for finding out what is causing the hard fault?Thanks

author: ajb
author_rank: Super Contributor
timestamp: April 08, 2018, 06:48:00 pm
content: Your code doesn't appear to do what you said it does.Do you have an interrupt handler for the USART? When an interrupt is requested for which no handler has been defined, this could result in a hardfault, depending on how your default handlers have been initialized.You can implement a hardfault handler that captures system state at the time of the fault, which will help you determine the exact nature of the fault. Here's a simple example:https://www.freertos.org/Debugging-Hard-Faults-On-Cortex-M-Microcontrollers.htmlThe same basic idea works across the whole Cortex M family, although there are some differences in some of the register details. You can use the captured status registers to figure out what sort of fault occurred, which will help narrow down your search for the cause.

author: noname4me
author_rank: Regular Contributor
timestamp: April 11, 2018, 10:25:02 pm
content: Thanks - I found the cause as being a buffer overrun on the USART when the IDLE interrupt was enabled (I think).I won't post the code because it is really kludgey and only works because it is called within a repeating loop in the main routine.Basically I periodically read the USART1 SR register, and then read the DR register to clear the ORE bit in the USART1->SRNot the best, but I am able to now read the full NMEA string, even with its variable length (thanks to strlen).The link is a nice look at the Hardfault issue; if you know of any youtube tutorial using the hardfault to track down a bug, that would be brilliant - I have found a few, but nothing that makes it click in my head yet.Thanks