Skip to content

PY32F003 ADC configuration

For this project (and most mixed-signal projects really) I need code to scan and convert a bunch of ADC channels, taking various measurements to get voltage, current and temperature readings in the system.

While it is possible to do this in software, I prefer to use the features of the hardware peripheral to streamline things whenever possible. By doing this, it is usually possible to get the quickest conversion with the lowest system load and power consumption (possibly even letting the CPU sleep while the ADC is doing conversions). The PY32F003 has a nice ADC peripheral that can scan a set of ADC inputs, and works in tandem with the DMA controller to store the conversion results to memory automatically.

Below is code that configures the ADC and DMA peripherals to do this:

/* ADC subsystem configuration */

#define ADC_READING_COUNT       8
static uint16_t adc_reading[ADC_READING_COUNT];

void ADCConfig(void)
{
  /* Turn on the ADC1 clock */
  LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);

  /* 3 MHz ADC clock to work down to 1.7 V and have minimum 9 us
   * sample time for internal temp sensor conversion */
  LL_ADC_SetClock(ADC1, LL_ADC_CLOCK_SYNC_PCLK_DIV8);
  /* Single mode, unlimited DMA transfer */
  LL_ADC_REG_SetContinuousMode(ADC1, LL_ADC_REG_CONV_SINGLE);
  LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
  /* 28.5 cycle or 9.5 us sample time to satisfy internal temp
   * sensor minimum sample time */
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_28CYCLES_5);

  /* Enable channels for scan sequence */
  LL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_1 |
                LL_ADC_CHANNEL_2 | LL_ADC_CHANNEL_4 | LL_ADC_CHANNEL_5 |
                LL_ADC_CHANNEL_6 | LL_ADC_CHANNEL_7 |
                LL_ADC_CHANNEL_TEMPSENSOR | LL_ADC_CHANNEL_VREFINT);
  /* Scan direction forward */
  LL_ADC_REG_SetSequencerScanDirection(ADC1, LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);

  /* Set ADC calibration mode */
  LL_ADC_SetCalibrationMode(ADC1, LL_ADC_CAL_MODE_OFFSETANDLINEARITY);
  /* Trigger by software */
  LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_SOFTWARE);

  /* Turn on VRef and internal temp sensor */
  LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(),
                LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_AWD_CH_TEMPSENSOR_REG);

  /* Turn on DMA1 clock */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

  /* Set up DMA channel 1 for ADC data transfer */
  LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_1,
                LL_DMA_DIRECTION_PERIPH_TO_MEMORY |
                LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT |
                LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD |
                LL_DMA_MDATAALIGN_HALFWORD | LL_DMA_PRIORITY_MEDIUM);
  LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC_READING_COUNT);
  LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1,
                LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),
                (uint32_t)adc_reading, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
  LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

  /* Enable End Of Sequence interrupt */
  LL_ADC_EnableIT_EOS(ADC1);
  NVIC_EnableIRQ(ADC_COMP_IRQn);
}

After this setup, it is possible to start a conversion scan of all channels by calling this function:

void ADCStartConversion(void)
{
  /* Enable ADC */
  LL_ADC_Enable(ADC1);
  /* Start conversion */
  LL_ADC_REG_StartConversion(ADC1);
}

Software triggering is good enough to get started. In the future I may look at using a timer for hardware triggering, in which case it may be possible to only wake the CPU after all ADC conversions are finished.

The ADC and comparator peripherals share an interrupt handler. At the end of the conversion scan, you can process the ADC conversion results in this interrupt handler:

/* ADC and comparator handler */

void ADC_COMP_IRQHandler(void)
{
  /* ADC interrupt? */
  if (LL_ADC_IsActiveFlag_EOS(ADC1))
  {
    /* Clear the interrupt flag */
    LL_ADC_ClearFlag_EOS(ADC1);

    /* Do processing of the data in the adc_reading array */
  }
}