2020-10-04 02:41 AM
Hello,
I'm learning DMA and using CubeMX to generate the configuration codes and SystemWorkbench(Eclipse) as editor to edit my application.
To better grasp the concept of DMA I'm doing a small project in which I use USART in combination with DMA to read some data from the PC's terminal and reply from the code according to 3 situations. Here is how the project is supposed to work:
1) I send some data from the PC via Terra Term and ask a question:
For this I have configured DMA1 Stream 5 as Peripheral to Memory and USART2_RX as DMA
request.
2) According to what text I receive I send some data back via the USART to the PC and print
some stuff on the terminal.
For this I have configured DMA1 Stream 6 as Memory to Peripheral and USART2_TX serves as DMA request.
The problem I encountered is at step 1:
In my application code I have defined a function: 'cmplt_DMA_Receive' which I intended to use as complete DMA receive callback in my application where I would write my logic.
So I did this:
hdma_usart2_rx.XferCpltCallback = &cmplt_DMA_Receive;
And was expecting to have this function called when I send a character on the terminal(I have configured the DMA to send byte by byte so the function should be called after each received byte - character).
However the function was not getting called after doing some debugging I have found out that, in the HAL_DMA_IRQHandler global interrupt, the weak unimplemented callback function for half transfer complete(XferHalfCpltCallback) gets called instead of the callback XferCpltCallback for full DMA transfer complete which I implemented in the main.c file.
Apparently the stream used in step 1 is configured in half mode and not in full mode as I was expecting it to be by default. Previously I did a memory to memory exercise and that callback was getting called as I was expecting.
Now apparently the CubeMx configured the DMA stream as half transfer and I don't know how to change it to full transfer so I can have that callback in the application be called.
Please help me correct this by helping me configure it from the CubeMX software - I do understand what the difference between a full and half DMA transfer is from the datasheet however I do not know where to make that configuration in CubeMX software.
Thank you very much for reading this! I will reply with the full code as the text would be too long if I did it in the same post.
2020-10-04 02:53 AM
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2020 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stdio.h"
#include <string.h>
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
DMA_HandleTypeDef hdma_usart2_tx;
uint8_t received_bytes = 0;
//The address of the receive buffer in SRAM1 and the transmit value
#define rcv_addr 0x20000000
#define trn_addr 0x20010000
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
void cmplt_DMA_Receive( struct __DMA_HandleTypeDef * hdma);
void cmplt_DMA_Transcieve( struct __DMA_HandleTypeDef * hdma);
//two pointers to the received buffer and the tr_val
char* rcv_buffer = (char*)rcv_addr;
uint64_t* tr_val = (uint16_t*)trn_addr;
int main(void)
{
/* 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 */
/* USER CODE END SysInit */
hdma_usart2_rx.XferCpltCallback = &cmplt_DMA_Receive;
hdma_usart2_tx.XferCpltCallback = &cmplt_DMA_Transcieve;
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_UART_Receive_DMA(&huart2, &rcv_buffer, 1); //receive byte by byte and store them in the buffer
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Stream5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
/* DMA1_Stream6_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_EVT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : LD2_Pin */
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
}
void cmplt_DMA_Receive( struct __DMA_HandleTypeDef * hdma) //this should get called when a byte is received from PC
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
if( *(huart2.pRxBuffPtr) != '\r' ) //if enter was not pressed keep updating the buffer
{
//*(rcv_buffer + received_bytes) = *(huart2->pRxBuffPtr + received_bytes); //this happens automatically
received_bytes++;
}
else // if entered was pressed check what we received and reply
{
received_bytes++;
if( *(rcv_buffer + (received_bytes-1)) == 'r' ) //sir => reply with response 1
{
*tr_val = '/n25/n';
HAL_UART_Transmit_DMA(&huart2, tr_val, 1);
}
else if( *(rcv_buffer + (received_bytes-1)) == 'm' ) //m'am => reply with response 2
{
*tr_val = '/n20/n';
HAL_UART_Transmit_DMA(&huart2, tr_val, 1);
}
else //none => reply with response 3
{
*tr_val = '/nInvalid/n';
HAL_UART_Transmit_DMA(&huart2, tr_val, 1);
}
//clear the receive buffer
memset(rcv_buffer, 0, received_bytes);
received_bytes = 0;
}
}
void cmplt_DMA_Transcieve( struct __DMA_HandleTypeDef * hdma) //this should get called when data is transmited to PC
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
2020-10-04 07:54 AM
Implement the HAL_UART_RxCpltCallback and HAL_UART_TxCpltCallback functions and use those. Don't mess with XferCpltCallback, HAL uses them to do its thing.
I don't know what "configured in half mode" means.
2020-10-07 02:10 AM
Hello thanks for your answer and sorry for my late reply I was in vacation.
Not only do I think it's ok to 'mess with those functions', I actually think it is the right way to do it for what I'm trying to do. That's what I understand at least from this documentation from STM's HAL DMA driver:
At the end of data transfer HAL_DMA_IRQHandler() function is executed and user can
add his own function by customization of function pointer XferCpltCallback and
XferErrorCallback (i.e a member of DMA handle structure).
Basically these function pointers are used in the HAL_DMA_IRQHandler and they are initialized to NULL pointer. If you do not overwrite them(assign them addresses of some callback functions that you define) they will not be used at all. See this snippet from HAL_DMA_IRQHandler as an example:
if(hdma->XferCpltCallback != NULL)
{
/* Transfer complete callback */
hdma->XferCpltCallback(hdma);
}
In the above example if the XferCpltCallback member from hdma handle that we pass to the function is not made to point to a function then no callback will be called when full DMA transfer complete occurs.
As for what configured in half mode means:
I think I made a mistake when I said it like that. You do not configure the DMA in half mode. It's just that when half the data is sent in DMA then this callback is called: XferHalfCpltCallback.
In my application when I send data from the PC to the MCU(via UART) the XferHalfCpltCallback gets called but XferCpltCallback never gets called.
And this puzzles me as it would mean that only half the data that I send via the terminal reaches the memory. Which would mean 4bits reach the memory as I have configured one data item as 1 byte.
Sorry if it does not make sense my english is not the best I hope I explained well enough what I think the issue is.
2020-10-07 06:32 AM
Regardless of what is in the documentation, this line is performed within HAL_UART_Receive_DMA:
/* Set the UART DMA transfer complete callback */
huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;
So whatever you put in there prior isn't getting called. You could change it afterwards, but like I said it will affect how HAL operates.
HAL_UART_RxCpltCallback is called from within UART_DMAReceiveCplt and is the correct function to implement if you want a callback when complete.
Next time include your chip number.
2020-10-07 11:35 PM
Ok I will try your solution and let you know of the result, thanks. I'm using STM32F446RE MCU from NUCLE-F446RE sorry for not mentioning that.
2020-10-08 02:40 AM
You can change the DMA data width in 'ioc' file or 'stm32f3xx_hal_msp.c' file.
In my case is use DMA for record ADC value. But now am i explain how change data width. So i think not many difference.
1.Change in .ioc file
I think maybe you aleady added DMA request .
After add DMA and click that request.
Then you got 'DMA Reques Setting' menu and 'scroll bar'.
Now you can change DMA working mode and data width.
2.Change 'stm32f3xx_hal_msp.c'
It maybe little diffrence because my DMAhandle name is 'hdma_adc1' and your DMAhandle name is maybe 'hdma_uartx?' anyway.
DMA init is occured in 'HAL_ADC_MspInit' function.
So if you change the data width of the code below, you can get the desired result.
---------------------------------------------------------------------------------------------------------------------------------
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; <--- here
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; <--- here
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
--------------------------------------------------------------------------------------------------------------------------------
I haven't actually tried the second method.
2020-10-08 02:44 AM
Oh! I have mistake.
Maybe you must find 'HAL_UART_MspInit' function In second method.