2
\$\begingroup\$

It is my first time to use a Bluepill with STM32F103 processor (so far used custom board with STM32L4+). I want to setup dual mode so I can read 4 channels, 2 at a time simultaneously. I do not need continuous mode. For the sake of simplicity, I use DMA in circular mode so I can use the loop in main for series of ADC measurements. Started the project with STM32CubeMX, added some code based on the reference manual and a few tutorials. The final code is:

#include "main.h"

ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
DMA_HandleTypeDef hdma_adc1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_ADC2_Init(void);

uint32_t ADCBuffer[20];
uint16_t lower_word1;
uint16_t upper_word1;
uint16_t lower_word2;
uint16_t upper_word2;

int main(void)
{
  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_ADC2_Init();

  //HAL_ADCEx_Calibration_Start(&hadc1);
  //HAL_ADCEx_Calibration_Start(&hadc2);



  ADC1->CR2 |= (1<<8); // Enable DMA
  ADC2->CR2 |= (1<<8); // Enable DMA
  ADC1->CR2 |= (1<<20);// Enable Ext trigger
  ADC2->CR2 |= (1<<20);// Enable Ext trigger

  DMA1_Channel1->CNDTR = 2;   // Set the size of the transfer
  DMA1_Channel1->CPAR = (uint32_t) 0x4001244C;  // Source address is ADC1->DR
  DMA1_Channel1->CMAR = (uint32_t) ADCBuffer;  // Destination Address is memory address
  DMA1_Channel1->CCR |= (1<<0);  // DMA EN =1

  ADC2->CR2 |= (1U); //ADC2 EN
  ADC1->CR2 |= (1U); //ADC1 EN

//  HAL_ADCEx_MultiModeStart_DMA

  while (1)
  {
      ADC1->CR2 |= (1U);
      //HAL_ADC_Start_DMA(&hadc1, ADCBuffer, 2);

      lower_word1 = (uint16_t) (ADCBuffer[0] & 0xFFFFUL);
      upper_word1 = (uint16_t) ((ADCBuffer[0] >> 16) & 0xFFFFUL);

      lower_word2 = (uint16_t) (ADCBuffer[1] & 0xFFFFUL);
      upper_word2 = (uint16_t) ((ADCBuffer[1] >> 16) & 0xFFFFUL);
      //HAL_ADC_Stop_DMA(&hadc1);
  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_ADC1_Init(void)
{
  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;

  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  multimode.Mode = ADC_DUALMODE_REGSIMULT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_ADC2_Init(void)
{
    //ADC_MultiModeTypeDef multimode = {0};
    ADC_ChannelConfTypeDef sConfig = {0};

  hadc2.Instance = ADC2;
  hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 2;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  //multimode.Mode = ADC_DUALMODE_REGSIMULT;
  //if (HAL_ADCEx_MultiModeConfigChannel(&hadc2, &multimode) != HAL_OK)
  //{
  //  Error_Handler();
 // }

  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_3;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_DMA_Init(void)
{
  __HAL_RCC_DMA1_CLK_ENABLE();
  __DMA1_CLK_ENABLE();

  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{

}
#endif

also, in stm32f1xx_hal_msp.c:

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hadc->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();

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

    /* 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;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);



  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }
  else if(hadc->Instance==ADC2)
  {
  /* USER CODE BEGIN ADC2_MspInit 0 */

  /* USER CODE END ADC2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_ADC2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC2 GPIO Configuration
    PA2     ------> ADC2_IN2
    PA3     ------> ADC2_IN3
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN ADC2_MspInit 1 */

  /* USER CODE END ADC2_MspInit 1 */
  }

}

I debug with ST link v2 and a breakpoint around the upper_words. What I observe:

I have readings only at the lower part of the DR, where ADC1 data is supposed to be but nothing in the upper side. Also, ADC2->DR is empty so I guess it is not triggering correctly. If I run just ADC2, I do have values in its DR register so it is working, so my main guess is I have issue with the setting of the multimode or triggers. Lost a few hours in testing and google with no success. I will appreciate any idea!

Thank you!

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

The F103 is a old part and this bit works a bit funny.

You can do "dual simultaneous regular mode", but you can only use the DMA request of ADC1.

This generates a problem: if ADC2 conversion is slower, there won't be any result for the DMA to copy. So it is important that ADC2 has a sampling time slower or equal to the channels of ADC1 so that they are finished at the same time.

Another funny is that you can only access the ADC registers in 32 bit mode.

I would really recommend for the F103 to use bare metal and not the HAL. The HAL expects everything to work correctly, which in the F103 it doesn't.

For example:

In dual ADC mode, to read the slave converted data on the master data register, the DMA bit must be enabled even if it is not used to transfer converted regular channel data.

Which probably gates the mirror of ADC2->DR to ADC1->DR. But I see that in the code below it is not set... Funny.

This is a section from a working setup:

/* TIM2 setup omitted */
ADC1->CR2 |=(3<<ADC_CR2_EXTSEL_Pos);
ADC1->CR1 |=(1<<ADC_CR1_DUALMOD_Pos) | ADC_CR1_SCAN;

ADC2->CR2 |=(7<<ADC_CR2_EXTSEL_Pos);
ADC2->CR1 |=(1<<ADC_CR1_DUALMOD_Pos) | ADC_CR1_SCAN;

ADC1->SQR1..
ADC1->SQR2..
ADC1->SQR3..
ADC2->SQR1..
ADC2->SQR2..
ADC2->SQR3.. 

ADC1->SMPR2..
ADC1->SMPR1..

ADC2->SMPR1 = ADC1->SMPR1;
ADC2->SMPR2 = ADC1->SMPR2;

volatile uint32_t target[] __attribute__((aligned (8)));
DMA1_Channel1->CNDTR = ...
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel1->CMAR = (uint32_t)&target;
DMA1_Channel1->CCR = (0x2<<DMA_CCR_MSIZE_Pos) | (0x2<<DMA_CCR_PSIZE_Pos) | DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_TEIE | DMA_CCR_HTIE | DMA_CCR_TCIE;

ADC1->SR = 0;
ADC2->SR = 0;

HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

ADC1->CR2 |= ADC_CR2_EXTTRIG | ADC_CR2_DMA;
ADC2->CR2 |= ADC_CR2_EXTTRIG;
DMA1_Channel1->CCR |= DMA_CCR_EN;

TIM2->CR1 |= (TIM_CR1_CEN);
\$\endgroup\$
2
  • \$\begingroup\$ So, I think all works nice apart from ADC2 not getting triggered by the ADC in dual mode. The register for ADC1 is in multimode (dual simultaneous regular), ext interrupt enabled, software interrupt. Same for ADC1 since the reference manual states it and it is also in your code. No further idea apart from wrong sequence to update the register values (e.g. in L+ you should update some registers while ADC is on, some when it is OFF). While it is not supposed to work this way, I wonder what would happen if I keep the dual mode, but trigger both adc using the same timer. \$\endgroup\$
    – D. K.
    Nov 29, 2021 at 14:09
  • \$\begingroup\$ Today I tried increasing sampling time for ADC1, no success. Added a line to enable ADC2 right after I enable ADC1. ADC2->DR had data, ADC1->DR upper part also had data, all transferred by DMA with no issues. So I think the main problem is not working trigger. BTW, I agree HAL often leaves problems, so I confirmed the ADC and DMA registers in debug mode. My code above has magic numbers fixing teh issues found. No difference with your proposal. \$\endgroup\$
    – D. K.
    Nov 29, 2021 at 14:12
1
\$\begingroup\$

Lost a few days with that but finally made the program work by exchanging in the while() loop

ADC1->CR2 |= (1U);

to

HAL_ADCEx_MultiModeStart_DMA(&hadc1, ADCBuffer, 2);

I guess is still some issue with registers or checks I am not doing, but HAL does correctly. Not going deep into it since it works already.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.