PID Closed-Loop Control Experiment Based on STM32F1 Series: Potentiometer Knob Drive

PID Closed-Loop Control Experiment Based on STM32F1 Series: Potentiometer Knob Drive

We use ADC2 to implement the potentiometer knob.

Potentiometer Knob Initialization Code.c

void PotentiometerKnob_Init(void){
	/* Enable clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);  // Enable ADC2 clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  // Enable GPIOA clock
	/* Set ADC clock */
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // Select clock division 6, ADCCLK = 72MHz / 6 = 12MHz
	/* GPIO initialization */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);  // Initialize PA2, PA3, PA4, PA5 as analog input
	/* ADC initialization */
	ADC_InitTypeDef ADC_InitStructure;  // Define structure variable
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // Mode, select independent mode, using ADC2 alone
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  // Data alignment, select right alignment
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // External trigger, use software trigger, no external trigger
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  // Continuous conversion, disabled, stop after one conversion
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;  // Scan mode, disabled, only convert sequence 1
	ADC_InitStructure.ADC_NbrOfChannel = 1;  // Number of channels, 1, only needs to specify more than 1 in scan mode
	ADC_Init(ADC2, &ADC_InitStructure);  // Pass structure variable to ADC_Init, configure ADC2
	/* Enable ADC */
	ADC_Cmd(ADC2, ENABLE);  // Enable ADC2, ADC starts running
	/* ADC calibration */
	ADC_ResetCalibration(ADC2);  // Fixed process, internal circuit will automatically execute calibration
	while (ADC_GetResetCalibrationStatus(ADC2) == SET);
	ADC_StartCalibration(ADC2);
	while (ADC_GetCalibrationStatus(ADC2) == SET);
}

Overall Function Purpose

  1. Open the clock for ADC2 and GPIOA;

  2. Configure ADC2 clock to 12 MHz;

  3. Configure GPIOA’s PA2, PA3, PA4, PA5 as analog input (ADC input pins);

  4. Configure ADC2 parameters (operating mode, alignment, trigger mode, etc.);

  5. Enable ADC2 and complete calibration.

Code Breakdown

1. Enable Clock

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);  // Enable ADC2 clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  // Enable GPIOA clock
  • Reason: In STM32, peripherals must have their clocks enabled before use, otherwise the registers will not work.

  • Here, the clocks for ADC2 and GPIOA are enabled.

2. Set ADC Clock

RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // ADC clock = 72 MHz / 6 = 12 MHz
  • The ADC clock in STM32 comes from the APB2 bus (PCLK2).

  • APB2 = 72 MHz, so dividing by 6 gives 12 MHz.

  • The maximum ADC clock for STM32F1 series cannot exceed 14 MHz.

3. GPIO Initialization (Analog Input)

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
  • Configure PA2, PA3, PA4, PA5 as analog input.

  • <span>GPIO_Mode_AIN</span> = analog input, indicating these pins are directly connected to the ADC, bypassing digital circuits.

  • <span>GPIO_Speed</span> has no effect in analog mode but must be specified.

4. ADC Parameter Initialization

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC2, &ADC_InitStructure);
  • Independent Mode (ADC_Mode_Independent) STM32 has ADC1, ADC2 (some models also have ADC3), which can sample in dual mode; here only ADC2 is used, selecting independent mode.

  • Data Alignment (ADC_DataAlign_Right) The ADC result is 12 bits, right alignment means the lower 12 bits are valid, and the upper 4 bits are filled with 0.

  • Trigger Mode (ADC_ExternalTrigConv_None) No external trigger is used; it is started by software.

  • Continuous Conversion Mode (ADC_ContinuousConvMode) Disabled, meaning only one sample is taken, then it stops.

  • Scan Mode (ADC_ScanConvMode) Disabled, meaning only one channel is sampled. If multiple channels need to be sampled sequentially, scan mode must be enabled, and <span>ADC_NbrOfChannel > 1</span> must be set.

  • Number of Channels (ADC_NbrOfChannel = 1) Only one channel is configured, corresponding to non-scan mode.

5. Enable ADC

ADC_Cmd(ADC2, ENABLE);
  • Enable ADC2 to allow it to operate.

6. ADC Calibration

ADC_ResetCalibration(ADC2);  // Reset calibration register
while (ADC_GetResetCalibrationStatus(ADC2) == SET);
ADC_StartCalibration(ADC2);  // Start calibration
while (ADC_GetCalibrationStatus(ADC2) == SET);
  • Why calibrate? The ADC has analog circuits that may deviate due to temperature and power supply. STM32 provides an internal calibration function that automatically adjusts the offset to improve accuracy.

  • Fixed process:

  1. Reset the calibration register first;

  2. Wait for the reset to complete;

  3. Start calibration;

  4. Wait for calibration to complete.

Please refer to the ADC Basics Study

HS, WeChat public account: Ordinary Inspiration Dock STM32F4xx ADC Conversion

Get the AD value function of the potentiometer knob.c

* Parameter: n specifies the potentiometer knob, range: 1~4, representing RP1, RP2, RP3, RP4 respectively

* Return value: AD conversion value, range: 0~4095

uint16_t RP_GetValue(uint8_t n){
	if (n == 1)  // Specify to read RP1
	{
		/* Configure the regular group for channel 2 (PA2) */
		ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 1, ADC_SampleTime_55Cycles5);
	}
	else if (n == 2)  // Specify to read RP2
	{
		/* Configure the regular group for channel 3 (PA3) */
		ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_55Cycles5);
	}
	else if (n == 3)  // Specify to read RP3
	{
		/* Configure the regular group for channel 4 (PA4) */
		ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
	}
	else if (n == 4)  // Specify to read RP4
	{
		/* Configure the regular group for channel 5 (PA5) */
		ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_55Cycles5);
	}
	ADC_SoftwareStartConvCmd(ADC2, ENABLE);  // Software trigger AD conversion once
	while (ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) == RESET);  // Wait for EOC flag, i.e., wait for AD conversion to complete
	return ADC_GetConversionValue(ADC2);  // Read data register to get AD conversion result
}

Function Purpose

uint16_t RP_GetValue(uint8_t n)
  • Input parameter <span>n</span>: indicates which channel to read (1~4).

  • Return value: returns the ADC conversion result (range 0~4095, 12-bit ADC).

2. Select Channel

if (n == 1) // Read RP1
{
	ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 1, ADC_SampleTime_55Cycles5);
}
else if (n == 2) // Read RP2
{
	ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_55Cycles5);
}
else if (n == 3) // Read RP3
{
	ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
}
else if (n == 4) // Read RP4
{
	ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_55Cycles5);
}

Here, the function is used:

ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t SampleTime);

Parameter description:

  • <span>ADC2</span>: Select ADC2.

  • <span>ADC_Channel_x</span>: Specify ADC channel (PA2=Channel_2, PA3=Channel_3, PA4=Channel_4, PA5=Channel_5).

  • <span>Rank=1</span>: Specify as the first in the regular sequence (since scan mode is not enabled, there is only one).

  • <span>SampleTime=55Cycles5</span>: Sample time, indicating that the ADC holds the sample for 55.5 cycles.

3. Software Trigger One Conversion

ADC_SoftwareStartConvCmd(ADC2, ENABLE);
  • Start one regular group conversion (software trigger mode).

  • Since it was configured during initialization as <span>ADC_ExternalTrigConv = None</span>, it must be started by software.

4. Wait for Conversion to Complete

while (ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) == RESET);
  • <span>EOC</span> = End Of Conversion (conversion complete flag).

  • The code will block here until the conversion is complete.

5. Read Result

return ADC_GetConversionValue(ADC2);
  • Return the converted digital value (0~4095).

  • Relationship between digital value and voltage:

PID Closed-Loop Control Experiment Based on STM32F1 Series: Potentiometer Knob Drive

  • Where <span>Vref</span> is typically 3.3V.

For example: If the sampling result = 2048, it indicates the input voltage is approximately:

PID Closed-Loop Control Experiment Based on STM32F1 Series: Potentiometer Knob Drive

Leave a Comment