ES: PY32 ADC

The PY32F0xx ADC is a 12-bit analog-to-digital converter supporting multiple channels, configurable sampling times, and both single and continuous conversion modes. This guide covers ADC initialization, channel configuration, and reading analog values with the PY32 HAL.

The ADC (Analog-to-Digital Converter) is the bridge between the physical world and digital processing. Temperature sensors, light sensors, potentiometers, microphones, and pressure sensors all produce analog signals. The PY32F0xx's 12-bit ADC converts these analog voltages into digital values that firmware can process.


PY32F0xx ADC Features

FeatureDetail
Resolution12-bit (output range: 0 to 4095)
ChannelsMultiple ADC channels shared with GPIO pins
Reference voltageTypically VDDA (3.3V)
Conversion modesSingle conversion, continuous conversion
Trigger sourcesSoftware trigger, hardware trigger (timers)
Data alignmentLeft or right alignment
Sampling timeConfigurable per channel (1.5 to 239.5 cycles)

ADC Resolution and Voltage Conversion

The 12-bit ADC produces values from 0 to 4095. To convert a raw ADC reading to voltage:

md
Voltage = (ADC_Value / 4095) × VDDA

Example with VDDA = 3.3V:
ADC = 2048  →  Voltage = (2048 / 4095) × 3.3 ≈ 1.65V
ADC = 4095  →  Voltage = 3.3V
ADC = 0     →  Voltage = 0V


ADC Channel Mapping

ADC channels are multiplexed with GPIO pins. The GPIO pin must be configured in analog mode before it can be used as an ADC input:

ADC ChannelGPIO Pin
ADC_CHANNEL_0PA0
ADC_CHANNEL_1PA1
ADC_CHANNEL_2PA2
ADC_CHANNEL_3PA3
ADC_CHANNEL_4PA4
ADC_CHANNEL_5PA5
ADC_CHANNEL_6PA6
ADC_CHANNEL_7PA7

Step 1: Configure GPIO Pin as Analog Input

cpp
GPIO_InitTypeDef GPIO_InitStruct = {0};

// Configure PA5 as analog input (no pull-up/down)
GPIO_InitStruct.Pin  = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

In analog mode, the digital input/output buffers are disabled. This prevents the pin from drawing extra current and minimizes noise on the ADC reading.


Step 2: Initialize the ADC

cpp
ADC_HandleTypeDef AdcHandle;

// Enable ADC clock
__HAL_RCC_ADC_CLK_ENABLE();

AdcHandle.Instance = ADC1;

// Clock prescaler — ADC clock = PCLK / 1
AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV1;

// 12-bit resolution: values 0–4095
AdcHandle.Init.Resolution            = ADC_RESOLUTION_12B;

// Right-aligned: value appears in bits [11:0]
AdcHandle.Init.DataAlign             = ADC_DATAALIGN_RIGHT;

// Single channel scan (not multi-channel sequence)
AdcHandle.Init.ScanConvMode          = DISABLE;

// End-of-conversion flag on single conversion
AdcHandle.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;

// No auto-wait for low-power operation
AdcHandle.Init.LowPowerAutoWait      = DISABLE;

// Single conversion (not continuous)
AdcHandle.Init.ContinuousConvMode    = DISABLE;
AdcHandle.Init.DiscontinuousConvMode = DISABLE;

// Software-triggered conversion
AdcHandle.Init.ExternalTrigConv      = ADC_SOFTWARE_START;
AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;

// No DMA
AdcHandle.Init.DMAContinuousRequests = DISABLE;

// Overwrite old data if not read in time
AdcHandle.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;

if (HAL_ADC_Init(&AdcHandle) != HAL_OK) {
    // Handle initialization error
    while(1);
}


Step 3: Configure the ADC Channel

cpp
ADC_ChannelConfTypeDef sConfig = {0};

sConfig.Channel      = ADC_CHANNEL_5;             // PA5
sConfig.Rank         = 1;                          // First (and only) in sequence
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // Longest sampling for best accuracy

HAL_ADC_ConfigChannel(&AdcHandle, &sConfig);

Sampling Time Trade-off

Longer sampling times produce more accurate readings but reduce conversion speed:

SettingSampling TimeAccuracySpeed
ADC_SAMPLETIME_1CYCLES_51.5 cyclesLowerFastest
ADC_SAMPLETIME_7CYCLES_57.5 cyclesMediumFast
ADC_SAMPLETIME_239CYCLES_5239.5 cyclesHighestSlowest

For sensors with high source impedance (like the MQ303A alcohol sensor), use the longest sampling time.


Step 4: Read ADC Value

cpp
uint16_t Read_ADC_Value(void) {
    HAL_ADC_Start(&AdcHandle);                         // Start conversion
    HAL_ADC_PollForConversion(&AdcHandle, HAL_MAX_DELAY); // Wait for completion
    return HAL_ADC_GetValue(&AdcHandle);               // Return raw 12-bit value
}


Complete ADC Example

cpp
#include "py32f0xx_hal.h"

ADC_HandleTypeDef AdcHandle;

void ADC_Config(void) {

    // Configure PA5 as analog input
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin  = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // Configure ADC
    __HAL_RCC_ADC_CLK_ENABLE();
    AdcHandle.Instance                   = ADC1;
    AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV1;
    AdcHandle.Init.Resolution            = ADC_RESOLUTION_12B;
    AdcHandle.Init.DataAlign             = ADC_DATAALIGN_RIGHT;
    AdcHandle.Init.ScanConvMode          = DISABLE;
    AdcHandle.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;
    AdcHandle.Init.LowPowerAutoWait      = DISABLE;
    AdcHandle.Init.ContinuousConvMode    = DISABLE;
    AdcHandle.Init.DiscontinuousConvMode = DISABLE;
    AdcHandle.Init.ExternalTrigConv      = ADC_SOFTWARE_START;
    AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;
    AdcHandle.Init.DMAContinuousRequests = DISABLE;
    AdcHandle.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;
    HAL_ADC_Init(&AdcHandle);

    // Configure ADC channel
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel      = ADC_CHANNEL_5;
    sConfig.Rank         = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    HAL_ADC_ConfigChannel(&AdcHandle, &sConfig);
}

uint16_t Read_ADC_Value(void) {
    HAL_ADC_Start(&AdcHandle);
    HAL_ADC_PollForConversion(&AdcHandle, HAL_MAX_DELAY);
    return HAL_ADC_GetValue(&AdcHandle);
}

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

    while (1) {
        uint32_t adcValue = Read_ADC_Value();
        float voltage = (adcValue * 3.3f) / 4095.0f;  // Convert to voltage

        printf("ADC Value: %lu, Voltage: %.2f V\r\n", adcValue, voltage);

        HAL_Delay(1000);  // 1-second interval
    }
}


ADC Conversion Formula

c
// Convert raw ADC reading to voltage (3.3V reference)
float voltage = (adc_raw * 3.3f) / 4095.0f;

// Convert raw ADC reading to voltage (custom reference)
float voltage = (adc_raw * vref) / 4095.0f;


Data Alignment

The 12-bit ADC result can be placed in the 16-bit data register in two ways:

md
Right-aligned (ADC_DATAALIGN_RIGHT):
Bit: 15 14 13 12  11 10  9  8  7  6  5  4  3  2  1  0
      0  0  0  0   D  D  D  D  D  D  D  D  D  D  D  D
                   ^--- 12-bit result in bits [11:0]

Left-aligned (ADC_DATAALIGN_LEFT):
Bit: 15 14 13 12  11 10  9  8  7  6  5  4  3  2  1  0
      D  D  D  D   D  D  D  D  D  D  D  D  0  0  0  0
      ^--- 12-bit result in bits [15:4] (useful for 8-bit truncation)

Right alignment is standard for 12-bit reading. Left alignment is useful when you want to read only the top 8 bits for lower precision.


Final Thoughts

The PY32F0xx ADC is a capable 12-bit converter that enables the chip to interface with the full range of analog sensors. The HAL-based configuration flow is consistent with STM32 projects, making PY32 ADC knowledge directly transferable.

Key practices for accurate ADC readings:

  • Use the longest sampling time for high-impedance sources
  • Configure the GPIO in analog mode before using it as ADC input
  • Use a stable VDDA reference for voltage accuracy
  • Average multiple readings to reduce noise for precision measurements

The ADC is the window through which the PY32 sees the physical world in numbers.