Ben Brown bio photo

Ben Brown

Electronics and Embedded Systems Engineering

Email Twitter Facebook Github


Most of my current projects are using the very, very nice stm32f103 series of chips, as these provide excellent performance per dollar cost. The embedded DMA in these chips is a massive help in getting a project up and running without any struggle for cpu power.

I find the DMA documentation to be lacking at best with getting this system up and running using the new HAL drivers from STM. While this is actually quite an easy system to get up and running, I could not find any nice notes on how the system should be setup and things to be aware of.


For all of my projects, I begin in STM32 Cube MX software, which allows the planning of all of the pin connections for the chips main circuit board. While I am in the cube software, I take the time to setup as much of the DMA as possible, In the settings for the ADC I set it to use the DMA engine for transfers, and also setup automatic scanning of the required inputs. In this example I have setup two channels to be read via the ADC with the plan of having these automatically buffered into RAM for me.

On the other end this will just pop out into an array of uint32_t for us to then read whenever suits in our code.

After this setup, I proceeded to export the C code from the cube software, and store this in a separate working folder to my code. As I prefer to work in C++, I create a new C++ project in System Workbench and then manually transfer over the generated code. I find this also provides a great opportunity to sanity check the code as I transfer it over.

The Code

The Cube MX software has already created most of the initialization code required for the setup of the IO. All I have to do after this is add the required code to link the target array we want the DMA to fill to the DMA itself. I have set up the DMA to increment in half word size (half of a 32 bit value), this provides the minimum memory footprint as the ADC only outputs 16 bit numbers (it’s bit resolution is < 16 bits). Note that for other peripherals you will want 32 bit (word) increments on the address.

In the global array space I define the following array to store the results:

uint16_t ADCReadings[2];

Now that I have declared the array that I want the data to be pushed into, I use the HAL function HAL_ADC_Start_DMA that lets me link the DMA result array to the ADC and also start the ADC running.

HAL_ADC_Start_DMA(&hadc1, (uint32_t*) ADCReadings, 2);

This then starts the DMA engine and the ADC and links the two together for me. Note that I call this after i have setup all the other peripherals

How well does this work?


I have found that this method provides really nice quick updating of the memory address as you would expect.


The Cube MX software generated 99% of the code for me, however below is outlined all required code, omitting the auto generated stuff for things like clock setup, which will be device dependant. The only required lines, really, are in the main() function.


Some sections are omitted for brevity.

uint16_t ADCReadings[2]; //ADC Readings
int main(void)
	/* Configure the system clock */
	/* Initialize all configured peripherals */
	HAL_ADC_Start_DMA(&hadc1, (uint32_t*) ADCReadings, 2);//start the DMA collecting the data


/* ADC1 init function */
void MX_ADC1_Init(void) {

	ADC_ChannelConfTypeDef sConfig;

	/**Common config
	hadc1.Instance = ADC1;
	hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
	hadc1.Init.ContinuousConvMode = ENABLE;
	hadc1.Init.DiscontinuousConvMode = DISABLE;
	hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
	hadc1.Init.NbrOfDiscConversion = 0;
	hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
	hadc1.Init.NbrOfConversion = 2;

	if (HAL_ADC_Init(&hadc1) != HAL_OK) {

	/**Configure Regular Channel
	sConfig.Channel = ADC_CHANNEL_0;
	sConfig.Rank = 1;
	sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
	if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {

	/**Configure Regular Channel
	sConfig.Channel = ADC_CHANNEL_1;
	sConfig.Rank = 2;
	if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {


 * Enable DMA controller clock
void MX_DMA_Init(void) {
	/* DMA controller clock enable */

	/* DMA interrupt init */
	/* DMA1_Channel1_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);



DMA_HandleTypeDef hdma_adc1;

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)

  GPIO_InitTypeDef GPIO_InitStruct;
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* Peripheral clock enable */

    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    GPIO_InitStruct.Pin = X_Pin|Y_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* Peripheral DMA 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_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;

    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)



  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */


void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)

  /* USER CODE BEGIN ADC1_MspDeInit 0 */

  /* USER CODE END ADC1_MspDeInit 0 */
    /* Peripheral clock disable */

    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    HAL_GPIO_DeInit(GPIOA, X_Pin|Y_Pin);

    /* Peripheral DMA DeInit*/
  /* USER CODE BEGIN ADC1_MspDeInit 1 */

  /* USER CODE END ADC1_MspDeInit 1 */