cancel
Showing results for 
Search instead for 
Did you mean: 

ADC running at half speed

Dave Jones
Associate III
Posted on September 17, 2017 at 00:41

I am working with an STM32L452 on a custom board. I use System Workbench as my development system. The initialization code is generated by CubeMX. All of those have been updated (today) to the latest versions.

I have the part configured using an internal oscillator and PLL to run at 80MHz. The system clock, bus clocks, and ADC clock are all at 80 MHZ. I am converting 5 channels through the ADC. They are 'slow' channels, which according to the reference manual simply means they require a longer sampling time. The data sheet shows them capable of sampling and converting at up to 4.21Msps. I am using a slightly longer sampling time (12.5 cycles instead of the 6.5 cycles minimum required for slow channels). The data sheet shows the conversion is 12.5 cycles, so with 12.5 cycles sample time that adds up to a total of 25 cycles for the sampling and conversion.

With an 80MHZ clock and 25 cycles total conversion time, that means it should take 312ns per channel to convert. With 5 channels converting, that should mean I get back 5 results every 1.562 uSec. I double checked these calculations based on the datasheet saying the slow channels can run at 4.21Msps on an 80 MHz clock. That uses a 6.5 cycle sample time and 12.5 cycle conversion. 80MHz/19 cycles = 4.21Msps. So my math is correct, and I should be converting at 3.2Msps. (and 5 channels at 640Ksps each)

I  have it configured to use DMA and a 10 sample buffer, so I get 5 channels on the half complete interrupt and an updated 5 channels on the fully complete interrupt. I am toggling a GPIO pin in those interrupts and watching it on a scope.

Discontinuous mode is disabled, as are injections.

The actual times between interrupts are measuring at about 3 uSec for the first one and about 3.5 uSec for the second one. That is half the calculated speed I described above. If I change the ADC clock pre-scaler from 1 to 8, those same times are 8 times slower as expected, and still half what the calculated speeds are.

I searched here and the only thing I found was from 2 years ago where somebody pointed out that CubeMX was setting the JAUTO bit even when no injection channels were being used, and that was slowing down the ADC.

https://community.st.com/0D50X00009XkhErSAJ

 

But, I checked that by reading the ADC1->CFGR register during my interrupts, saving it, and at a later time reading the saved value via an external device on a serial port where I could examine the value. The JAUTO bit was not set. Neither were any other bits that seemed suspicious.

So, what is going on here???    Anybody have any ideas??

null
8 REPLIES 8
Posted on September 17, 2017 at 02:33

I'm not keen to recreate the scenario, post at least initialization code, ideally a minimal template

Review clocks actually in use, I tend to crack the running state, so I can change/tune parameters yet still understand the gearing.

printf('Core=%d, %d MHz\n', SystemCoreClock, SystemCoreClock / 1000000);

{

  RCC_OscInitTypeDef  RCC_OscInitStruct;

  HAL_RCC_GetOscConfig(&RCC_OscInitStruct);

  if (RCC_OscInitStruct.HSEState == RCC_HSE_BYPASS)    

      printf('HSE BYPASS\n');

  if (RCC_OscInitStruct.HSEState == RCC_HSE_ON)    

      printf('HSE ON\n');

  if (RCC_OscInitStruct.HSEState == RCC_HSE_OFF)    

      printf('HSE OFF\n');

  if (RCC_OscInitStruct.HSIState == RCC_HSI_ON)    

      printf('HSI ON\n');

  if (RCC_OscInitStruct.HSIState == RCC_HSI_OFF)    

      printf('HSI OFF\n');

  if (RCC_OscInitStruct.MSIState == RCC_MSI_ON)    

      printf('MSI ON\n');

  if (RCC_OscInitStruct.MSIState == RCC_MSI_OFF)    

      printf('MSI OFF\n');

  printf('RCC SR HSEBYP %s\n', (RCC->CR & (1 << 18)) ? 'High' : 'Low');

  printf('RCC SR HSERDY %s\n', (RCC->CR & (1 << 17)) ? 'High' : 'Low');

  printf('RCC SR HSEON  %s\n', (RCC->CR & (1 << 16)) ? 'High' : 'Low');

  printf('RCC SR PLLRDY %s\n', (RCC->CR & (1 << 25)) ? 'High' : 'Low');

  printf('RCC SR PLLON  %s\n', (RCC->CR & (1 << 24)) ? 'High' : 'Low');

  printf('RCC CFGR %08X\n', RCC->CFGR);

  switch((RCC->CFGR >> 2) & 3) // SWS

  {

        case 0 : printf('CLOCK SOURCE MSI\n'); break;

        case 1 : printf('CLOCK SOURCE HSI16\n'); break;

        case 2 : printf('CLOCK SOURCE HSE\n'); break;

        case 3 : printf('CLOCK SOURCE PLL\n'); break;    

  }

    

  switch((RCC->CCIPR >> 28) & 3) // ADCSEL

  {

        case 0 : printf('ADCSEL NONE\n'); break;

        case 1 : printf('ADCSEL PLLSAI1 R PLLADC1CLK\n'); break;

        case 2 : printf('ADCSEL PLLSAI2 R PLLADC2CLK\n'); break;

        case 3 : printf('ADCSEL SYSCLK\n'); break;    

  }

    

  printf('APB1=%d\n', HAL_RCC_GetPCLK1Freq());

  printf('APB2=%d\n', HAL_RCC_GetPCLK2Freq());

}
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on September 17, 2017 at 07:39

I don't have a terminal, or UART port, or display to send printf output to. I have one GPIO pin that I can watch on an oscilloscope, and I have an I2C port that a small external device can probe and read a pair of bytes from (displayed on a small LCD screen on that device, in decimal). So I will have to add code to read a value or bits from the ARM chip and place those bits in one of the two byte variables. Compile, run, then read the values on the external device. Then do it again for another location. Etc.... over and over until I get all the data you mention in your code.

I'll then post the init functions that CubeMX created, and the results of the probing of those locations.

Posted on September 17, 2017 at 14:26

If you connected SWO on your debug interface you could use the SWV rather than USART terminal. 

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on September 17, 2017 at 22:28

I use SWDIO/SWCLK to program the board via an ST-Link module. But I didn't have any spare pins, so didn't bring out SWO.

I've also been avoiding using debug mode in these tests, for fear that it would skew the timing. So I just compile and 'Run'.

Posted on September 18, 2017 at 01:57

OK, I modified my external device to read a pair of 16 bit numbers, allowing me to send over 32 bits and display them as hex. So it wasn't too bad. I had to modify the STM code a number of times to read each of the needed registers, recompile and run, then write down the results. Then I looked up the meaning of the bits to create the info below.

The raw data from the registers/variables/functions:

SystemCoreClock = 0x04C4B400

RCC_OscInitStruct.HSEState = 0x00000000

RCC_OscInitStruct.HSIState = 0x00000000

RCC_OscInitStruct.MSIState = 0x00000001

RCC->CR = 0x0300006B

RCC->CFGR = 0x0000000F

RCC->CCIPR = 0x38000000

HAL_RCC_GetPCLK1Freq() = 0x04C4B400

HAL_RCC_GetPCLK2Freq() = 0x04C4B400

The recreated printout that your posted code would have created using those values:

Core=80000000, 80 MHZ

HSE OFF

HSI OFF

MSI ON

RCC SR HSEBYP Low

RCC SR HSERDY Low

RCC SR HSEON Low

RCC SR PLLRDY High

RCC SR PLLON High

RCC CFGR 0x0000000F

CLOCK SOURCE PLL

ADCSEL SYSCLK

APB1=80000000

APB2=80000000

And a couple of others you didn't ask for:

ADC1->CFGR = 0x80003003

ADC_CCR = 0x00000000

Which mean:

ADC1->CFGR =

Injected Queue disabled Automatic injected group conversion disabled Analog watchdog 1 disabled on injected channels Analog watchdog 1 disabled on regular channels Discontinuous mode on injected channels disabled Discontinuous mode for regular channels disabled Auto-delayed conversion mode off Continuous conversion mode ADC_DR register is overwritten with the last conversion result when an overrun is detected Hardware trigger detection disabled (conversions can be launched by software) Right alignment 12-bit DFSDM mode disabled DMA Circular mode selected DMA enabled

ADC_CCR =

VBAT channel disabled, DAC2_int selected.

Temperature sensor channel disabled, DAC1_int selected VREFINT channel disabled input ADC clock not divided CK_ADCx (x=123) (Asynchronous clock mode), generated at product level

(I tried changing that last one to Synchronous clock dived by 1, but it made no difference)

Here are the Cube generated Clock and ADC init functions.

/** System Clock Configuration
*/
void SystemClock_Config(void)
{
 RCC_OscInitTypeDef RCC_OscInitStruct;
 RCC_ClkInitTypeDef RCC_ClkInitStruct;
 RCC_PeriphCLKInitTypeDef PeriphClkInit;
 /**Initializes the CPU, AHB and APB busses clocks 
 */
 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
 RCC_OscInitStruct.MSIState = RCC_MSI_ON;
 RCC_OscInitStruct.MSICalibrationValue = 0;
 RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
 RCC_OscInitStruct.PLL.PLLM = 1;
 RCC_OscInitStruct.PLL.PLLN = 40;
 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
 RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV4;
 RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Initializes the CPU, AHB and APB busses clocks 
 */
 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C2|RCC_PERIPHCLK_I2C3
 |RCC_PERIPHCLK_SDMMC1|RCC_PERIPHCLK_ADC;
 PeriphClkInit.I2c2ClockSelection = RCC_I2C2CLKSOURCE_PCLK1;
 PeriphClkInit.I2c3ClockSelection = RCC_I2C3CLKSOURCE_PCLK1;
 PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK;
 PeriphClkInit.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_PLL;
 if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure the main internal regulator output voltage 
 */
 if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure the Systick interrupt time 
 */
 HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
 /**Configure the Systick 
 */
 HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
 /* SysTick_IRQn interrupt configuration */
 HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
/* ADC1 init function */
static void MX_ADC1_Init(void)
{
 ADC_ChannelConfTypeDef sConfig;
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = ENABLE;
 hadc1.Init.NbrOfConversion = 5;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_12;
 sConfig.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(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_13;
 sConfig.Rank = 2;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_14;
 sConfig.Rank = 3;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_15;
 sConfig.Rank = 4;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_16;
 sConfig.Rank = 5;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Dave Jones
Associate III
Posted on September 18, 2017 at 22:36

Is there an official place to submit bug reports, such as this one I described above with the HAL_ADC_Start_DMA function in the FW package, and used by CubeMX?

 

Posted on September 18, 2017 at 22:21

I believe I have found the problem. It was caused by inaccurate documentation in the 'HAL_ADC_Start_DMA' function.

In the HAL_ADC_Start_DMA function, the 'Length' parameter is defined as :

  * @param Length Length of data to be transferred from ADC peripheral to memory (in bytes)

But that is wrong. It's not in bytes. It is in samples (or conversions). Since it said 'bytes' and my buffer is a series of half-words, (to hold the 12 bit results) I had defined the length of the buffer as 20 (bytes) to hold 10 samples.

I expected it to then transfer a total of 10 half-words at the time of the complete interrupt, (5 half-words at the time of the half complete interrupt). But instead it was transferring 10 samples (half-words) before the half complete interrupt, and another 10 before the complete interrupt.

So the 6.25 uSec timing that I'm seeing (I remeasured it and it's closer to 6.25 uSec and the 6.5 uSec I originally thought) is actually for 20 samples, instead of the 10 that I assumed it was. So conversion is twice as fast as I thought, and does in fact match the calculations.

1/80MHZ * 25 cycles * 20 conversions = 6.25 uSec

Thanks for taking a look. In the end, it comes down to bad wording in the documentation. That line in the code should be changed to:

  * @param Length Length of data to be transferred from ADC peripheral to memory (in samples)

Posted on September 19, 2017 at 10:11

Hi Dave,

It has already been reported...

https://community.st.com/0D50X00009XkYdJSAV

Hopefully the documentation will be fixed in a new version of the docs.

Andy