How to control ADC sampling frequency

This is the Seventh tutorial in the STM32 ADC series. In this series will see how to use the ADC peripheral of the STM32 to read the data from the Analog devices. We will cover how to use the ADC in different modes, that includes polling mode, interrupt mode and the DMA mode. We will also see how to use the multiple channels to read multiple analog devices using the same ADC. Later in the series we will cover more advanced features like differential ADC, combined ADCs, ADC watchdogs, injected channels etc.

I am going to use the STM32H750 based development board for this series because it has advanced ADC features. But I will also cover the configuration changes required for some of the popular series that includes STM32F103C8 and STM32F446RE.

This tutorial will cover how to use the external trigger source to control the ADC sampling frequency. By using one of the timer as the trigger source, we can set the desired sampling frequency for the ADC channels.

F103C8 Configuration

We will cover both, ADC and Timer configuration for all the microcontrollers.

ADC Configuration

Below is the image showing the ADC configuration for multiple channels.

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. Instead we will use the timer’s event to start the conversion. This way we can control the sampling rate of the ADC channels.

The External Trigger Conversion Source is set to Timer3 Trigger out Event. So whenever the Timer3 will generate an event, it will start the conversion in the ADC1.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously.

The F103C8 has 12 bit ADC resolution by default and we don’t have the option to configure it. Therefore the data width for the DMA must be set to Half-Word (16 bits).


Timer Configuration

We are using Timer3’s event to trigger the ADC conversion. Before we configure the timer, we must find more details about where this timer is connected to. Below is the image showing some details about it.

The second half of the image above is taken from the F103C8 Datasheet. It shows that the TIM3 is connected to the APB1 Bus. The first Half of the image is showing the clock configuration for this project. The APB1 Timer clock is running at 72MHz clock, so this will be the TIM3 clock as well.

We will now configure the TIM3’s Prescaler and ARR to reduce this clock to the desired ADC sampling rate we need.

We will first enable the internal clock as the clock source for the TIM3. The TIM3 is currently running at a clock of 72MHz.

We will use the prescaler of 7200 to reduce the TIM3 clock to 10KHz, 72000000/7200 = 10000. We will further use the ARR value of 1000 to reduce the clock down to 10Hz, 10000/1000 = 10.

Choose the Update Event in the Trigger Event Selection.

Basically the timer’s counter starts counting up from 0. When the counter overflows, an event will be triggered. This event will then be used by the ADC to start the conversion.



F446RE Configuration

We will start with the ADC configuration first.

ADC Configuration

Below is the image showing the ADC configuration for multiple channels.

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. Instead we will use the timer’s event to start the conversion. This way we can control the sampling rate of the ADC channels.

The External Trigger Conversion Source is set to Timer2 Trigger out Event. So whenever the Timer2 will generate an event, it will start the conversion in the ADC1.

The DMA Continuous Request must also be Enabled. This is to make sure that the DMA requests the data continuously from the ADC. This mode should be kept Enabled while using the DMA in Circular mode.

The End of Conversion Selection should be set at the end of all the conversions. Doing this will make sure that the Conversion complete interrupt in only triggered when all the 4 channels (one complete sequence) have been converted.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously. Set the data width as per the ADC Resolution. I have configured the ADC with 12bits resolution, therefore the data width is set to half word (16bits).


Timer Configuration

We are using Timer2’s event to trigger the ADC conversion. Before we configure the timer, we must find more details about where this timer is connected to. Below is the image showing some details about it.

The First half of the image above is taken from the F446RE Datasheet. It shows that the TIM2 is connected to the APB1 Bus. The Second Half of the image is showing the clock configuration for this project. The APB1 Timer clock is running at 90MHz clock, so this will be the TIM2 clock as well.

We will now configure the TIM2’s Prescaler and ARR to reduce this clock to the desired ADC sampling rate we need.

We will first enable the internal clock as the clock source for the TIM2. The TIM2 is currently running at a clock of 90MHz.

We will use the prescaler of 9000 to reduce the TIM2 clock to 10KHz, 90000000/9000 = 10000. We will further use the ARR value of 1000 to reduce the clock down to 10Hz, 10000/1000 = 10.

Choose the Update Event in the Trigger Event Selection.

Basically the timer’s counter starts counting up from 0. When the counter overflows, an event will be triggered. This event will then be used by the ADC to start the conversion.



H750 Configuration

We will again start with the ADC configuration first.

ADC Configuration

Below is the image showing the ADC configuration for multiple channels.

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. Instead we will use the timer’s event to start the conversion. This way we can control the sampling rate of the ADC channels.

The External Trigger Conversion Source is set to Timer1 Trigger out Event. So whenever the Timer1 will generate an event, it will start the conversion in the ADC1.

The Conversion Data Management Mode should be set to DMA Circular. This is to make sure that the DMA does requests the data continuously from the ADC. This mode should be used while using the DMA in Circular mode.

The End of Conversion Selection should be set at the end of sequence of conversion. Doing this will make sure that the Conversion complete interrupt in only triggered when all the 4 channels (one complete sequence) have been converted.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously. I have configured the ADC resolution of 16bits, therefore the data width is set to Half-Word (16bits).


Timer Configuration

We are using Timer2’s event to trigger the ADC conversion. Before we configure the timer, we must find more details about where this timer is connected to. Below is the image showing some details about it.

The First half of the image above is taken from the H750 Datasheet. It shows that the TIM1 is connected to the APB2 Bus. The Second Half of the image is showing the clock configuration for this project. The APB2 Timer clock is running at 240MHz clock, so this will be the TIM1 clock as well.

We will now configure the TIM1’s Prescaler and ARR to reduce this clock to the desired ADC sampling rate we need.

We will first enable the internal clock as the clock source for the TIM1. The TIM1 is currently running at a clock of 240MHz.

We will use the prescaler of 24000 to reduce the TIM2 clock to 10KHz, 240000000/24000 = 10000. We will further use the ARR value of 1000 to reduce the clock down to 10Hz, 10000/1000 = 10.

Choose the Update Event in the Trigger Event Selection.

Basically the timer’s counter starts counting up from 0. When the counter overflows, an event will be triggered. This event will then be used by the ADC to start the conversion.


Cortex M7 Related

In this section we will cover the configuration and code exclusively needed for the cortex M7 devices.

We know that as per ST’s recommendation, we should use the Instruction and Data cache to improve the system performance. But if the cache are enabled, the DMA will always have the cache coherency issue. This is why we will use the Memory Processing Unit (MPU) of the cortex M7 to deal with the cache coherency.

Below is the image showing the MPU configuration in the cortex M7 Devices.

As you can see in the image above, I have enabled both Instruction and Data Cache (ICache & DCache).

We also need to enable the MPU in the MPU_PRIVILEGED_DEFAULT mode. Here I am choosing the Base Address region 0x30000000, this is the region for the RAM_D2 and we will relocate our buffer in this region.

  • Here I have selected the 32 Bytes region, since it’s the least size available. The ADC Buffer is going to be 20 bytes in size.
  • The rest of the configuration is to set the region as non-cacheable region. This would prevent the cache coherency issue between the CPU and the DMA.

In the main file, we need to relocate the ADC_VAL array to the RAM_D2 Region, 0x30000000. We can do this by using the section attribute as shown below.

__attribute__((section(".adcarray"))) uint16_t ADC_VAL[4];

Here I am relocating the ADC_VAL array to the section “.adcarray“. The section (.adcarray) will be defined in the flash script file as shown below.

  .mysection (NOLOAD):
  {
    . = ABSOLUTE(0x30000000);
    *(.adcarray)
  } >RAM_D2

As you can see above, the section (.adcarray) is linked to the region RAM_D2 (0x30000000).

The rest of the code remains the same as we used in the DMA section.



Connection

Below is the image showing the connection between the potentiometers and the H750.

As you can see in the image above, the 4 potentiometers are connected to the 4 pins of the MCU. These 4 pins represents 4 channels of the ADC. The potentiometers are connected to the 3.3V from the MCU.



The Code

The code will remain the same for all the STM32 MCUs. If there are any changes, I will specify in this section itself.

Definitions

Let’s define some variables, which we will use in the main function later.

//uint16_t ADC_VAL[4];
__attribute__((section(".adcarray"))) uint16_t ADC_VAL[4];  // cortex M7 only

ADC_VAL is an array of 4 elements of 16 bit in size. We will use this array to store the converted ADC Value. Even if you are using 10bits, 12bits or 14bits resolution the ADC_VAL will still be defined as a 16 bit variable.

The main function

Below is the main function.

int main()
{
  ....
  HAL_TIM_Base_Start(&htim1);
  HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 4);
  while (1)
  {
  }
}

Inside the main function we will first start the TIM1 in the base mode. This will start will Timer’s counter, which will start counting up. Once the counter overflows, an event will trigger, which will start the ADC conversions. The counter than resets back to 0 and starts counting UP again.

Next we will start the ADC in DMA Mode.The parameter ADC_VAL is the array where we want to store the converted data, and 4 is the number of conversions we want the DMA to transfer.

Once the ADC finishes all the conversions, an interrupt will trigger and the ADC Conversion Callback will be called.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
}

Inside this callback we will toggle the pin PA1. This pin toggles each time the callback is called and hence it can be used to calculate the time interval of the callback function. We will measure it on the scope.



Result

Below is the gif showing the ADC channel data on the debugger.

As you can see above, the ADC data is updating every 100ms. This is because we have set the Timer’s output to 10Hz. To confirm if this is indeed 100ms, we can view the toggling time of the pin PA1 on the logic analyzer. Below is the image showing the same.

As you can see above, the pin PA1 remains HIGH and LOW for 100ms. This means that the pin Toggles every 100ms. This is because the Conversion Complete Callback is called every 100ms. Which signifies that the ADC sampling frequency is set to 10Hz.

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
keyboard_arrow_up