cancel
Showing results for 
Search instead for 
Did you mean: 

ADC1 CMSIS Configuration on STM32L476RG microcontroller

Manuel-Baez
Associate II

Hello everyone! 

Recently, I started to use the CMSIS HAL to program my Nucleo-stm32l476 board, and currently, I am working on a project using the ADC1, for the data acquisition from an LM35 temperature sensor, and I have found some problems in the configuration, I have already configured the RCC module to enable the clock for the ADC1 module and select the SYSCLK as input clock for it.

Next, I wrote an initial function to configure the ADC1 module and another function to start the conversion and read the value obtained after it, but the read function return always the same value.

 

 

/* @Brief:  Función de configuración del reloj del sistema MSI a una fracuencia de 40MHz usando el PLL con un multiplicador de 10
 * 		    y un divisor de 2.
 *
 * @Note:   No utilizar un multiplicador por encima de 10, esto evita que la función SystemCoreClockUpdate() pueda ser ejecutada,
 * 		    aún no estoy seguro de la causa, espero resolverlo en pruebas posteriores.
 *
 * @PAram:  None
 * @Return: None
 */
void SystemClock_Config(void){
	RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;	// Habilitación del modulo PWR.
	PWR->CR1 &= ~PWR_CR1_VOS_1;	// Configuración del rango de scala de voltage en Rango 1 (VOS[1:0] = 0b01).
	PWR->CR1 |= (PWR_CR1_VOS_0 | PWR_CR1_DBP);
	while(PWR->SR2 & PWR_SR2_VOSF);	// Tiempo de espera para finalizar la configuración anterior.

	RCC->CR &= 0x000000F1;	// Reset del registro ignorando los bits a configurar.
	while(!(RCC->CR & RCC_CR_MSIRDY));	// Tiempo de espera para la estabilización del MSI.
	RCC->CR |= RCC_CR_MSIRGSEL;	// Habilitación de la configuración del rango del MSI mediante el MSIRABGE[3:0].
	RCC->CR &= 0xFFFFFF7F;	// Configuración del MSI en el rango de 8MHz.
	RCC->CR |= 0x00000071;
	while(!(RCC->CR & RCC_CR_MSIRDY));	// Tiempo de espera para la estabilización del MSI.

	RCC->CR &= ~RCC_CR_PLLON;	// Deshabilitación del PLL para su configuración.
	while(RCC->CR & RCC_CR_PLLRDY);	// Tiempo de espera para la deshabilitación del PLL.
	RCC->PLLCFGR &= 0xF9FF8A8D;	// Configuración del PLL para un divisor de 2 (PLLR[1:0] = 0b00) y un multiplicador de (10 PLLN[6:0] = 0x0A).
	RCC->PLLCFGR |= 0x01000A01;
	RCC->CR |= (RCC_CR_PLLON | RCC_CR_MSIPLLEN);	// Habilitación del PLL.
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLREN;	// Habilitación de la salida principal del PLL
	RCC->CFGR = RCC_CFGR_SW_PLL;
	RCC->CFGR &= 0x00FFC00F; 	// Selección de PLLCLK como reloj de sistema (SW[1:0] = 0b11) y deshabilitación de los divisores.
	RCC->CFGR |= 0x00000003;

	SystemCoreClockUpdate();

	RCC->AHB2ENR |= 0x00002001;	// Habilitación del puerto A y del ADC1.
	RCC->APB1ENR1 |= 0x00020000;	// Habilitación del USART2.
	RCC->CCIPR |= 0x30000004;	// Selección de SYSCLK como entrada de reloj para el USART2(USAR2SEL[1:0]=0b01) y los ADC123(ADCSEL[1:0]=0b11).
	return;
}

void init_GPIO(void){
	GPIOA->MODER &= 0xFFFFF30C;	// Habilitación de los pines PA2 y PA3 para funciones adicionales, el pin PA0 como entrada analogica y PA5 como salida.
	GPIOA->MODER |= 0x000004A3;
	GPIOA->OSPEEDR |= 0x000000F0;	// Asignación de la maxima velocidad de conmutación a los pines PA2 Y PA3.
	GPIOA->AFR[0] &= 0xFFFF00FF;	// Configuración de PA2 Y PA3 como RX y TX para el USART2.
	GPIOA->AFR[0] |= 0x00007700;
	return;
}

/* @Brief:  Configuración del modulo ADC1 para una resolusión de 10 bits.
 * @PAram:  None
 * @Return: None
 */
void init_ADC1(void){
	ADC1->CR &= ADC_CR_ADDIS;	// Deshabilitación del modulo ADC1 para su configuración.
	while(ADC1->CR & ADC_CR_ADEN);	// Tiempo de espera para finalizar la deshabilitación.
	ADC1->CR &= 0x8FFFFFC3;	// Salida del modo de bajo consumo(DEEPPWD=0), modo de una estrada simple(ADCALDIF=0).
	ADC1->CR |= 0x10000000;	// Habilitación del regulador(ADVREGEN).
	ADC1->CFGR &= 0xFC008FC0;	// Deshabilitación del DMA(DMAEN=0), resolución de 8 bits(RES[1:0]=0b10), alineación a la derecha(ALIGN=0).
	ADC1->CFGR |= 0x80000010;	// Deshabilitación del la pila de conversiones diferenciales(JQDIS=1).
	ADC1->SMPR1 &= 0xFFFC7FFF;	// Borrado del vector SMP5[2:0] para su configuración.
	ADC1->SMPR1 |= 0x00010000;	// Configuración del tiempo de muestreo del canal 5en 12.5 ciclos de reloj(SMP5[2:0]=0b010).
	ADC1->SQR1 &= 0xFFFFF140;	// Longitud de secuencia de 1 elemento(L[3:0]=0b0000), canal 0 como primer canal de conversion(SQ1[4:0]=0x00).
	ADC123_COMMON->CCR &= 0xFE003FE0;	// RCC como entrada de reloj(CKMODE[1:0]=0b00), modo de funcionamiento independiente(DUAL[4:0]=0x00).
	ADC1->CR |= ADC_CR_ADCAL;	// Inicio del proceso de calibración del ADC1.
	while(ADC1->CR & ADC_CR_ADCAL);	// Tiempo de espera para finalizar la calibración.
	ADC1->CR |= ADC_CR_ADEN;	// Habilitación del modulo ADC1.
	while(!(ADC1->ISR & ADC_ISR_ADRDY));	// Tiempo de espera necesario para finalizar la Habilitación del ADC1.
	return;
}

/* @Brief:  Función de conversion y lectura de las señales analogicas por medio del ADC1.
 * @PAram:  None
 * @Return: (char) ADC1->DR: El resultado de la conversion, un dato de 8 bits guardado en el registro DR.
 */
char read_ADC1(void){
	ADC1->CR |= ADC_CR_ADSTART;	// Inicio de la conversion.
	while(!(ADC1->ISR & ADC_ISR_EOC));	// Tiempo necesario para finalizar la conversion.
	ADC1->ISR &= ADC_ISR_EOC;	// Borrado de la bandera de finalización de la conversion.
	return (char)(ADC1->DR);	// Retorno del resultado de la conversion en el registro DR. 
	
}

 

 

so I have created another project through the STM32CubeMX with the same configuration in the ADC1 and RCC module using the same read function and this time the value returned was correct, I think that the problem can be a configuration in the VREFBUF but I do not sure. 

 

 

static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */

  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_8B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure the ADC multi-mode
  */
  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_5;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */
}

char read_ADC1(void){
	ADC1->CR |= ADC_CR_ADSTART;	// Inicio de la conversion.
	while(!(ADC1->ISR & ADC_ISR_EOC));	// Tiempo necesario para finalizar la conversion.
	char dato_adc = (char)(ADC1->DR & ADC_DR_RDATA);	// Lectura del resultado de la conversion en el registro DR.
	ADC1->ISR &= ADC_ISR_EOC;	// Borrado de la bandera de finalización de la conversion.
	return dato_adc;
}

 

 

I am attaching both projects in case someone wants to review them further and if someone could tell me where the error is, I will be very grateful.

Manue Baez
1 ACCEPTED SOLUTION

Accepted Solutions

If I understand correctly, you don't like to use STM32 HAL, instead, you would like to develop your code closer to registers. You may try from an ADC LL example using LL_ADC_xxx functions:

STM32CubeL4/Projects/NUCLEO-L496ZG/Examples_LL/ADC at master · STMicroelectronics/STM32CubeL4 · GitHub

View solution in original post

6 REPLIES 6
jiangfan
ST Employee

It will be good to start from an ADC example of similar MCU, such as:

STM32CubeL4/Projects/NUCLEO-L496ZG/Examples/ADC at master · STMicroelectronics/STM32CubeL4 · GitHub

Thanks @jiangfan  for your help but I have fine the error already and in addition, I would like to try to configure the microcontroller without the STM32 HAL, using the CMSIS to modify the registers and learn to write my configuration functions.

Manue Baez

If I understand correctly, you don't like to use STM32 HAL, instead, you would like to develop your code closer to registers. You may try from an ADC LL example using LL_ADC_xxx functions:

STM32CubeL4/Projects/NUCLEO-L496ZG/Examples_LL/ADC at master · STMicroelectronics/STM32CubeL4 · GitHub

Yes @jiangfan that's correct, but LL Drive is the Macros files to have access to the registers only for the STM32 microcontrollers based on ARM microprocessors, on the other hand, CMSIS is the Macros files to have access to the registers for all microcontrollers based on ARM microprocessors, so the definitions names for the registers and the access methods for they are different, usually a STM32CubeMX projects include the CMSIS files because the STM32 HAL is based on these macros definitions.

 

Manue Baez

> the read function return always the same value.

What value?

One way to find the problem is to read out and check/compare with working case the ADC,RCC and relevant GPIO registers.

JW

Manuel-Baez
Associate II

Dear community, I have found the error already, thanks, everyone for your help and recommendations, principally to @jiangfan because the LL_Drive examples were utils,

In the function void init_ADC1(void) the bit mask for the SQR1 register was an "And" but it should be an "Or" I will leave the correct function below.

/* @Brief:  Configuración del modulo ADC1 para una resolusión de 10 bits.
 * @PAram:  None
 * @Return: None
 */
void init_ADC1(void){
	ADC1->CR &= ADC_CR_ADDIS;	// Deshabilitación del modulo ADC1 para su configuración.
	while(ADC1->CR & ADC_CR_ADEN);	// Tiempo de espera para finalizar la deshabilitación.
	ADC1->CR &= 0x8FFFFFC3;	// Salida del modo de bajo consumo(DEEPPWD=0), modo de una estrada simple(ADCALDIF=0).
	ADC1->CR |= 0x10000000;	// Habilitación del regulador(ADVREGEN).
	ADC1->CFGR &= 0xFC008FC0;	// Deshabilitación del DMA(DMAEN=0), resolución de 8 bits(RES[1:0]=0b10), alineación a la derecha(ALIGN=0).
	ADC1->CFGR |= 0x80000010;	// Deshabilitación del la pila de conversiones diferenciales(JQDIS=1).
	ADC1->SMPR1 &= 0xFFFC7FFF;	// Borrado del vector SMP5[2:0] para su configuración.
	ADC1->SMPR1 |= 0x00010000;	// Configuración del tiempo de muestreo del canal 5en 12.5 ciclos de reloj(SMP5[2:0]=0b010).
	ADC1->SQR1 |= 0xFFFFF140;	// Longitud de secuencia de 1 elemento(L[3:0]=0b0000), canal 0 como primer canal de conversion(SQ1[4:0]=0x00).
	ADC123_COMMON->CCR &= 0xFE003FE0;	// RCC como entrada de reloj(CKMODE[1:0]=0b00), modo de funcionamiento independiente(DUAL[4:0]=0x00).
	ADC1->CR |= ADC_CR_ADCAL;	// Inicio del proceso de calibración del ADC1.
	while(ADC1->CR & ADC_CR_ADCAL);	// Tiempo de espera para finalizar la calibración.
	ADC1->CR |= ADC_CR_ADEN;	// Habilitación del modulo ADC1.
	while(!(ADC1->ISR & ADC_ISR_ADRDY));	// Tiempo de espera necesario para finalizar la Habilitación del ADC1.
	return;
}

 

Manue Baez