Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broken ADC readings on esp32c3 with read_raw() and read() #468

Open
faulesocke opened this issue Aug 5, 2024 · 6 comments
Open

Broken ADC readings on esp32c3 with read_raw() and read() #468

faulesocke opened this issue Aug 5, 2024 · 6 comments
Labels
question Further information is requested

Comments

@faulesocke
Copy link

Test setup

  • esp32c3, connected to wifi (random dev board from aliexpress)
  • Using adc1 and gpio0
  • applying external voltage (~0.9V) to pin using lab PSU, checking voltage with good quality DMM. also have a small (couple nF) capacitor directly on the pin
  • issue reproduced on two out of two tested devices, one fresh from the packaging, so unlikely hardware issue
  • setup code:
let adc = AdcDriver::new(peripherals.adc1).unwrap();
let adc_pin = peripherals.pins.gpio0;
let adc_cfg = AdcChannelConfig::new();
let mut adc_driver = AdcChannelDriver::new(&adc, adc_pin, &adc_cfg).unwrap();

Problem

When I use read_raw() in a loop to collect 16 samples and then averaging over those

loop {
    let mut samples = [0; 16];
    for sample in &mut samples {
        *sample = adc.read(&mut adc_driver).unwrap();
    }

    // uncomment to fix:
    //info!("samples: {samples:?}");
    let sample = samples.into_iter().sum::<u16>() / samples.len() as u16;
    info!("sample: {sample}");

    thread::sleep(Duration::from_secs(1));
}

I get unreliable results. The applied voltage of 0.9V seems to max out the ADC since I see no fluctuation in the values anymore. However, the value I get is not 4095, but instead 3839, which converts to binary 0b111011111111. If I slightly change the code, and, for example log the individual sub-samples in the for-loop, or log the samples array, or add a small 10ms sleep to the for loop, then I suddenly see the correct reading of 4095.

The read() function, however, seems to behave correctly, returning reliable and accurate voltage readings. A user in IRC suggested, this might be some timing issue, that is magically right in read() all the time, but in my read_raw() setup sometimes is wrong.

If you need any more information, please ask.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 7, 2024

On the esp32c3, with 0 DB attenuation (that you are using) applying 900mV is already out of range. It is difficult for me to dig up a proof from the esp32c3 Technical Reference Manual, but it is documented clearly here (i.e., for the old/legacy 4.4.X ADC driver, but it uses the same hardware after all).

It seems to me that you are expecting out-of-range voltages to "max out" (in your wording) to a returned raw value of 4095, but isn't this just a speculation?

Also, without applying calibration (with read as opposed to read_raw) I think you might actually get lower values even for a voltage which is at the upper bound?

Why don't you first try with a voltage value which is in-range for your chosen attenuation (or lack thereof)? I.e. 500mV or so with esp32c3 and 0DB atten?

Otherwise, and with regards to bugs, while it is always possible that there is a bug in the Rust wrappers, with esp-idf-hal you are actually dealing with the ESP IDF C drivers, so while I'm not saying they are bug-free, it is more likely that you are using them in a mis-configured way, hence why I'm looking at your voltage levels and so on.

@faulesocke
Copy link
Author

faulesocke commented Aug 7, 2024

I investigated further. I'm not even sure this is a bug within the libs, could also be a miscompilation. I certainly can't see how such behaviour could be caused by my code or my setup, I'm using no unsafe code anywhere and doing nothing too special.

Currently I have the following setup:

  • I use 11 dB attenuation
  • I use read() instead of read_raw()
  • I apply "sane" voltages around 1.5 V

Depending on the exact structure of my code I still see completely wrong values, reproducible. For example, I can tweak the code to either read 1703(-0+2) mV (correct) or 1596(-1+0) (wrong). The applied voltage is 1.698V. These values are certainly within the range of 0..2500 mV from the docs.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 7, 2024

There is one bug in the driver I just noticed, which is that it does not even bother to release the internal ESP IDF calibration handle on driver drop. This I'll fix, but it is very unlikely that you are affected by that, as you are not dropping the driver in your test anyway. And it is more of a memory leak issue.

I am also very pessimistic that this could be a code generation issue, because the riscv32imc targets live upstream in Rust/LLVM, and they are pretty popular. Also you don't use FP emulation, which is one place where compiler output might create issues.

I don't have brilliant ideas, but how about you just flash the native C ESP IDF ADC oneshot example? You need to download ESP IDF and everything, but at least that way you will eliminate all of the Rust glue code and your potential worries about miscompilation (on the LLVM level, that is; GCC would still be in the game).

You can also play with the C example to see if it also returns wrong values when you push it (as in taking oneshot readings in a rapid sequence, as you are doing in the Rust code).

If you feel lazy w.r.t. going down the C route, you can also re-create the example (or a minimal part of it - without the cleanup logic) by using raw calls into esp-idf-sys, from Rust. Of course, this way you cannot completely eliminate the codegen issue (if you assume it is LLVM-induced) but then again, chances here are close to 0.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 7, 2024

Heh. Look here. They claim the issue is fixed, but the original poster never replied to confirm. Maybe it is just that the ESP IDF ADC oneshot driver is simply unreliable in the presence of WiFi?

If that's the case you can of course always do a rapid sequence of readings (say - 10 or 16), and then remove the outliers from the set, and assume the rest represent correct values. Lame workaround, I know, but that might be the only option with WiFi turned on, if the original bug is not really fixed yet.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 7, 2024

(The above bug is esp32 related, not esp32c3, but who knows, maybe they have something similar going on with the c3 as well.)

@ivmarkov ivmarkov changed the title Broken ADC readings on esp32c3 with read_raw() Broken ADC readings on esp32c3 with read_raw() and read() Aug 7, 2024
@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 7, 2024

One more suggestion: can you change from pin0 to pin2?
In the Reference manual it seems gpio0 (and gpio1) is also where the 32Khz quartz crystal is connected. Not sure if this can affect the operation in ADC mode, but who knows?

(UPDATE: in the C example they use ADC channels 2 and 3 which is gpio2 and gpio3...)

@Vollbrecht Vollbrecht added the question Further information is requested label Sep 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
Status: Todo
Development

No branches or pull requests

3 participants