Interesting results from various ADC sample times

@peekay123 I can fully and completely understand low ADC counts from high impedance sources (I.E. 50K resistor). But a wire is as low-impedance as you can get, which is why I’m a little confused.

I’m going to add this as a firmware issue to work through in the near future. I know we can make this better :smile:

@WebDust21, agreed! I just wanted some sort of reference to go by. I agree with @BDub that this needs to be looked at :smile:

I have not looked at this in detail but as a clue for @BDub the numbers that @WebDust21 gets (~3890) can be explained by the 10 sample average code getting 9 samples at 4095 and 1 sample at 2048 (half scale).

We saw some similar problems in the distant past on the Core and the @satishgn updated the averaging code to interact better with the DMA channel. So I would be looking for a timing problem of that type. As @BDub said, turning off the averaging would help debug the problem but will add noise of plus or minus several counts.

Because of the way the switched-capacitor input works (and the diagram in that app note is simplified from what I have seen in the past) the input impedance is not returned to ground at each measurement cycle but to some pre-charged level in the switched capacitor bank. Essentially each bit in the SAR computation has a different input impedance relative to ground. You should plan on driving the input with a source impedance of less than 20k ohms for good results. A lot of folks have had trouble with these inputs and certain sensors, but a good op-amp buffer always fixes the impedance problems in my experience.

1 Like

Hah, that sounds very very familiar :wink:

Your op-amp suggestion saved me from going crazy. After humble beginnings with a lunchtime walk to get a lm358 through hole as an experiment, its progressed to the point of using LM324 in SMD for 4 inputs.

I now think that its quietly the reason why multiple projects have experienced odd readings as different boards (with varying onboard op-amps) have been used.

1 Like

@BDub. I don't see any issues dating 27 days back on this subject. But if this counts as cross-posting (here and GitHub issues), by all means chew me out :smile:

Basically, after reading through some of the STM32F103 reference manual, it appears that the problem is entirely caused by the exclusive use of dual-ADC slow interleaved mode in analogRead. With that mode, the maximum permissible sample time is 14 ADCCLKs—explaining why selecting 239 ADCCLK sample time doesn't quite work. With both ADC1 and ADC2 sampling, you have a lower input impedance, not a higher one.

I'm experimenting with changing the ADC clock scaler from 6 to 8 (= from 12MHz to 9MHz), and completely removing all dual slow interleaved mode code. (That rhymes...) Basically, as I understand it, if you have two ADCs alternating on the same input line, you've halved your input impedance. AFAIK the only purpose for interleaved mode on the STM32 is for faster sample rates, not higher input impedance. Will update this thread based on my findings.

@BDub, @satishgn, both of your names are in the code file, so I have permission to pester you! Just for good measure, @bko, @ScruffR, @kennethlimcp
the solution to the ADC problem is here!

I totally disabled the slow-interleaved ADC mode in a month-old LATEST build, modifying \core\adc_hal.c:

  • HAL_ADC_Read: Commented out all references to ADC2 in (ADC_RegularChannelConfig, ADC_ExternalTrigConvCmd)
  • HAL_ADC_Read: some optimizations to the sum/divide averaging routine, also removing the ADC_DualConvertedValues[i] >> 16 and the *2 from the final division
  • HAL_ADC_DMA_Init: Changed ADCLK to RCC_ADCCLKConfig(RCC_PCLK2_Div8); (instead of Div6. Don’t think that this is important)
  • HAL_ADC_DMA_Init: Disabled ADC2 clock
  • HAL_ADC_DMA_Init: Set ADC1 Mode to independent (ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;)
  • HAL_ADC_DMA_Init: As it’s no longer interleaved, I had to also enable the continuous conversion mode on ADC1
  • HAL_ADC_DMA_Init: Commented out all ADC2 initialization code.

With the above changes and ADC_SampleTime_239Cycles5, I get the following readings:

  • A4 to 3V3* -> 4095
  • A4 to 3V3* with 330K (no capacitor) -> 4065 (pretty high impedance if you ask me)
  • A4 to 3V3* with a 330K resistor and 0.1”F capacitor -> 4093
  • A0 to 3V3* -> 4084

Experimenting with ADC_SampleTime_1Cycles5:

  • A4 to 3V3* -> 4094
  • A4 to 3V3* with 330K (no capacitor) -> 2700 (looks low-impedance, alright!)
  • A4 to 3V3* with a 330K resistor and 0.1”F capacitor -> 4092
  • A0 to 3V3* -> 4083

Just for proof-of-principle, I replaced “adc_hal.c” with the latest LATEST “adc_hal.c” (making a backup of my changes, of course!) With my system code completely untouched (@ADC_SampleTime_239Cycles5) I get the following:

  • A4 to 3V3* -> 3890 counts
  • A4 to 3V3* with 330K (no capacitor) -> 3714
  • A4 to 3V3* with a 330K resistor and 0.1”F capacitor -> 3888
  • A0 to 3V3* -> alternating 4084 and 3879

And with ADC_SampleTime_1Cycles5


  • A4 to 3V3* -> 4094
  • A4 to 3V3* with 330K (no capacitor) -> 2642 (ditto)
  • A4 to 3V3* with a 330K resistor and 0.1”F capacitor -> 3888
  • A0 to 3V3* -> 4083

I haven’t dug through the firmware to see if the Core’s A0 translates to PA0 on the STM32, but there is a documented nonfixable problem in the STM32F103 errata regarding a “glitch” on A0, as follows:

FWIW, I’m checking A0, A1, A3, A4, and when not connected to anything with the unmodified firmware they read as follows:

  • with ADC_SampleTime_239Cycles5: 4, 1823, 1986, 2034
  • with ADC_SampleTime_1Cycles5: 883, 2069, 2095, 2145

I don’t recall hitting A0 with a sledgehammer (and this is a different Core than I was using in the opening post, too!)

I hereby submit that the dual slow-interleaved mode should be completely removed from the official firmware. It appears to be the source of the problem documented in the opening post.

Oh, and by the way, removing all that code? It results in a slightly smaller firmware file. :wink:

5 Likes

I did all of my rigorous testing before dual slow interleaved mode and liked the results back then as well. You have my vote to revert the ADC mode :wink: I don’t believe it was DMA driven before, so your modifications that keep it DMA driven may be worthwhile. Do you want to wrap up your changes in a PR so that we can more easily test/compare the changes? Thanks for digging into this @WebDust21!

1 Like

What is a PR? I'm pretty sure it isn't public relations :stuck_out_tongue_winking_eye:.

Here's my modified copy of the last two subroutines in firmware-latest\hal\src\core\adc_hal.c

/*
 * @brief Read the analog value of a pin.
 * Should return a 16-bit value, 0-65536 (0 = LOW, 65536 = HIGH)
 * Note: ADC is 12-bit. Currently it returns 0-4096
 */
int32_t HAL_ADC_Read(uint16_t pin)
{

  if (adcChannelConfigured != PIN_MAP[pin].adc_channel)
  {
    HAL_GPIO_Save_Pin_Mode(PIN_MAP[pin].pin_mode);
    HAL_Pin_Mode(pin, AN_INPUT);
  }

  if (adcInitFirstTime == true)
  {
    HAL_ADC_DMA_Init();
    adcInitFirstTime = false;
  }

  if (adcChannelConfigured != PIN_MAP[pin].adc_channel)
  {
    ADC_RegularChannelConfig(ADC1, PIN_MAP[pin].adc_channel, 1, ADC_Sample_Time);    // ADC1 regular channel configuration
    adcChannelConfigured = PIN_MAP[pin].adc_channel;                    // Save the ADC configured channel
  }

  DMA_SetCurrDataCounter(DMA1_Channel1, ADC_DMA_BUFFERSIZE);    // Reset the number of data units in the DMA1 Channel1 transfer
  DMA_Cmd(DMA1_Channel1, ENABLE);                // Enable DMA1 Channel1
  ADC_DMACmd(ADC1, ENABLE);                    // Enable ADC1 DMA
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);            // Start ADC1 Software Conversion

  // Wait for the 10-sample conversion to finish.
  while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));    // Wait for DMA1 to finish the transfer

  // DMA done.  Clean up.
  DMA_ClearFlag(DMA1_FLAG_TC1);            // Clear the DMA1 "finished" flag
  ADC_DMACmd(ADC1, DISABLE);            // Disable ADC1 DMA trigger
  DMA_Cmd(DMA1_Channel1, DISABLE);          // Disable DMA1 Channel1

  uint32_t ADC_SummatedValue = 0;
  for(int i = 0; i < ADC_DMA_BUFFERSIZE; i++)
  {
    ADC_SummatedValue += ADC_DualConvertedValues[i] & 0xFFFF;    // Retrieve the ADC1 converted value and add to ADC_SummatedValue
  }

  // Return ADC averaged value
  return (uint16_t)(ADC_SummatedValue / ADC_DMA_BUFFERSIZE);
}

/*
 * @brief Initialize the ADC peripheral.
 */
void HAL_ADC_DMA_Init()
{
  //NOT using slow interleaved mode. . .to achieve higher input impedance.

  ADC_InitTypeDef ADC_InitStructure;
  DMA_InitTypeDef DMA_InitStructure;

  RCC_ADCCLKConfig(RCC_PCLK2_Div6);        //=12MHz ADCCLK; slowest is Div8 -> 9MHz ADCCLK

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    // Enable DMA1 clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  // Enable ADC1 clock. 

  // DMA1 channel1 configuration
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_ADDRESS;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_DualConvertedValues;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUFFERSIZE;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);

  // ADC1 configuration
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;    //NOT ADC_Mode_SlowInterl
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;    //Has to be enabled for INDEPENDENT mode to function
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfChannel = 1;
  ADC_Init(ADC1, &ADC_InitStructure);
  // If ContinuousConvMode is disabled in Independent, you'll only get one conversion.
  //Slow Interleaved mode does a continuous conversion regardless of the setting of this bit.

  ADC_Cmd(ADC1, ENABLE);      // Enable ADC1
  ADC_ResetCalibration(ADC1);   // Enable ADC1 reset calibration register

  // Check the end of ADC1 reset calibration register
  while(ADC_GetResetCalibrationStatus(ADC1));

  // Start ADC1 calibration
  ADC_StartCalibration(ADC1);
  while(ADC_GetCalibrationStatus(ADC1));  // Check the end of ADC1 calibration

}
```

@WebDust21, PR = pull request in https://github.com/spark/firmware

@wesner0019, @BDub
which branch should I make the pull request in? LATEST, DEVELOP, MASTER
???

EDIT: I’m utterly confused as to how GitHub works.

If you’re up for it please create a PR against the develop branch :sunglasses:

latest will probably merge just fine as well, if you don’t want to navigate the extra stuff required for develop.

Can you tell me how many feet there are, which foot goes in front of the other one, with what sequence, which angle, which weight, and at what time? I am absolutely, completely and totally confounded as to how to use GitHub.

To answer...no, I'm not up for it.

Ok no worries @WebDust21 your code posted above should be pretty easy to insert locally and test. If you’d like to learn more about Git and Github, there are literally a bazillion videos on youtube. You can also read the various official docs (harder).

This thread started recently and I have high hopes that we can get more folks up-to-speed on github.

1 Like

What is the status of this improvement? I am trying to use the photon for power measurement with a current sensor and I am getting +/- 20 counts of “noise”. I know all about decoupling caps and input impedance but I’d really like to try a version with this dual interleaved mode turned off and where a single call to A/D sample doesn’t execute 10 samples.

Since I’m using a photon instead of a core I’m not sure how to make this firmware change.

I would also like to be able to set ADC mode for Photon as described here: Why dual slow interleaved?

I am experiencing issues with ADCs when combined with SPI use on pins A5, A4, A3 and A2 for chip select - would the conclusion of this thread provide a reason as to why the analogReads I see for AC current sampling are all over the place and perhaps a solution? At the moment I am exploring an I2C external ADC.

Has this fix been implemented
There are probably 100 posts indicating that the ADC isn’t accurate due to the implementation. Some folks have fixed it by building localy but their changes are not available on the IDE. I just tested my code again (building with the IDE) and I’m still getting 20 counts of noise.

Honestly if you need an accurate ADC I think you’ll have to either build locally or buy an external ADC. Since I can’t get the code to build locally I’ve given up several times.

That is 0.4% (or ±0.2%) which I'd say is not bad considering possible influences as DC quality, temperature, signal to be measured, measuring setup (saying this not knowing your setup).

Measuring analog signals properly with higher precission is more involved than just picking a sample.
It needs a fair bit of statistics too.

BTW, have you checked the output signal of your sensor with an oscilloscope?

1 Like