-
Notifications
You must be signed in to change notification settings - Fork 212
New ADC V2 with Async support #814
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just finished a first pass at reviewing this. In general, I would say this is an excellent implementation. Given your willingness to learn best practices, my review is more detailed than usual, so feel free to ignore some of these comments if you feel they're too nitpicky.
That being said, I do have some other comments that I feel deserve some attention:
- SAMD11 support. I feel we're so close it makes sense to finish the implementation for SAMD11 chips as well. As far as I can tell, the ADC is exactly the same between D21 and D11 targets. The only thing that might change and that I'm unsure of, is the maximum supported clock speed.
- We've already discussed this a little, but I'm not so sure the
Channel
API is so necessary after all. When I initially suggested this, I was under the impression that the ADC could perform conversions simultaneously on multiple channels, similar to EIC or DAMC. That's obviously not the case. As far as I can tell, there is also a 1:1 relationship between ADC channels and physical pins. So while the ADC can mux between channels, there is no pin-channel muxing going on. I think the ADC could simply take&mut
refs to already-configured pins instead. This would cut down on complexity a whole bunch. - Before merging, the Tier 1 BSP examples will have to be updated, and clippy warnings resolved. Ideally we would show examples where the CPU clock is ran at full speed, and a second GCLK is started at a slower speed to clock the ADC.
- I like that the d51 implementation takes advantage of the v2 clock system. However, it seems like the new adc module is not taking fully advantage of the typesafe clocking system: one needs to (unsafely) steal the peripherals to create a
MCLK
instance in order to configure the ADC. We should instead use the clocks system in its intended way, which is to exchange anApbToken<Adcx>
usingApb::enable
to get a configured clock type, which can then be passed to theAdc
peripheral, thus proving the clock is enabled. This is somewhat inverse to what the module is currently doing, which is enabling the ADC clock inside the constructor using the MCLK peripheral.
self.sync(); | ||
self.adc.inputctrl().modify(|_, w| { | ||
w.muxneg().gnd(); | ||
w.gain().variant(Gainselect::Div2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this was in the previous ADC implementation, but do we know why a gain of 1/2 is necessary here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, but when testing, it appears that gain of 1x actually seems to provide a 2x amplification of the output voltage reported, and 1/2 outputs 1x. Ill need to read the data sheet more as to why this might be the case, maybe something else needs configuring?
Here's a proof of concept of what I mean by using the typesafe clocking system. It's still untested and needs some more feature gating to accomodate D11 and D21 targets, but I think it gets the idea across: https://github.com/jbeaurivage/atsamd/tree/adc-v2-clocking With an example usage: let pins = Pins::new(cx.device.port);
let (buses, clocks, tokens) = clock_system_at_reset(
cx.device.oscctrl,
cx.device.osc32kctrl,
cx.device.gclk,
cx.device.mclk,
&mut cx.device.nvmctrl,
);
let mut apb = buses.apb;
let adc0_clk = apb.enable(tokens.apbs.adc0);
let adc0_settings = Config::new()
.clock_cycles_per_sample(5)
.clock_divider(Prescalerselect::Div2)
.sample_resolution(AdcResolution::_12bit)
.accumulation_method(AdcAccumulation::Single);
let gclk0 = clocks.gclk0;
let (pclk_adc0, gclk0) = Pclk::enable(tokens.pclks.adc0, gclk0);
let (_pclk_adc1, _gclk0) = Pclk::enable(tokens.pclks.adc1, gclk0);
let (adc0, channels_adc0) =
Adc::new(cx.device.adc0, adc0_settings, adc0_clk, pclk_adc0.into()).unwrap(); |
@rnd-ash, your additional commits look good. You can check out This PR, which implements some of the larger changes mentioned here I also want to point out that when testing on a SAMD51 board (metro M4), I regularly get the ADC stalling on waiting for the RESRDY flag to get set. So far the only way I managed to get ADC readings was:
To me this sounds like there is still a clock synchronization issue or a race condition somewhere. So, among the things left to do:
|
@rnd-ash, if you're happy with this, I am too. I think you've been doing extensive testing recently. Only two things to get CI green and get this merged:
|
I'll be able to fix these next week or this weekend when I'm back in the UK and have access to all my test boards (SAMD21 I need to verify it doesn't read old results in the ADC during sequential reads) |
@ianrrees @jbeaurivage As far as I am concerned now, this is ready to Merge. If you guys want to do 1 final review feel free. As discussed with Ian, I fixed the pygamer example my deleting the neopixel examples |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With stable Rust, I note cargo doc
flags several dead links in the documentation, so please do have a look at those and ensure the rendered docs are what you intend.
Thanks for all this work! Looks pretty good.
// The double trigger here is in case the VREF value changed between | ||
// reads, this discards the conversion made just after the VREF changed, | ||
// which the data sheet tells us to do in order to not get a faulty reading | ||
// right after changing VREF value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this comment - why not have start_conversion()
just do one conversion and handle triggering+discarding that faulty reading when VREF is changed?
My concern is that this could be used in settings with tight timing requirements, so probably shouldn't spend time on things that aren't strictly necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ill do some testing tomorrow, since this was to prevent stagnant readings from the ADC when cycling between channels very quickly (Read 1 sample, cycle to another channel,...), but since then, I found the powering down the ADC and powering it up between samples helped, so Ill test without this double trigger and see if the problem is still gone
|
||
let adc_settings = Config::new() | ||
.clock_cycles_per_sample(5) | ||
// Overruns if clock divider < 128 in debug mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we shouldn't re-work the example, so that it's a little more robust - eg maybe just get one sample at a time so that it is impossible to get an overrun scenario. That limitation could motivate using DMA, which I think a lot of practical ADC applications will need to use anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, DMA is my next thing to work on with the ADC generally speaking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in rnd-ash#10
hal/src/peripherals/adc/config.rs
Outdated
|
||
/// This adjusts the number of ADC clock cycles taken to sample a single | ||
/// sample. The higher this number, the longer it will take the ADC to | ||
/// sample each sample. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the maximum value, and assuming it's not 8b what happens if a config is specified that's over it?
hal/src/peripherals/adc/config.rs
Outdated
/// * Single accumulation (No averaging or summing) | ||
/// | ||
/// ## Additional reading settings by default | ||
/// * Use VDDANA as reference voltage for a full 0.0-3.3V reading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the implementation actually uses the 1V reference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intvcc1 - VDDANA reference voltage (Refselselect register), but ill modify the comment to show this is the case
@rnd-ash, @ianrrees still had a few comments that are worth being addressed. I've fixed a bunch in rnd-ash#10. The only outstanding item that might require more discussion is the handling of the sampling parameters, ie, if we should force users to explicitly specify everything or not. To simplify things a bit, I suggest that if you want add convenience methods to setup the ADC to a specific number of cycles per sample, it could be done in a follow-up PR. |
I've been working on an ESP32 based project for a couple weeks now, and TBF it's reinforced my feelings that for things like UARTs (and I'd say ADC/DAC as well - just haven't used them in ESP32), it's really best to make the API explicit about settings where there is no clear default. However, I'm just one person, and I respect that the ESP32 HAL is quite successful, so maybe it's not worth worrying too much about... @rnd-ash - it would be great to get this merged, what's your feeling about the next steps with it? If changing the config builder is too much of a pain, I'm happy either leaving it as-is or having a crack at it myself if you'd like. |
@ianrrees i think we are ready to merge this, just 1 minor bug with the SAMD21's temperature sensor not reading accurately, but I can (For now at least), just disable the cpu temperature function for SAMD21 if it proves to be a blocker. I have not tested this on a D11 target either, as I don't have one, would it be possible for you to verify functionality? |
Awesome, thanks! Yep, my next few days are going to be a bit variable in terms of computer time, but will try to get to it soon. |
Alrighty, have put a little bit of time in to this and have a few thoughts in no particular order:
|
Done @ianrrees , should be up to date with your branch |
fn parts_to_f32(int: u32, dec: u32) -> f32 { | ||
let mut dec = dec as f32; | ||
|
||
if dec < 10.0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if/else seems suspect to me, it interprets both the scale and the value from the dec
parameter. Eg, say the scale is meant to be 1/1000; it wouldn't be possible to encode 0.042.
The datasheet is really poor in this area, but my interpretation is that dec is a BCD coded field, so the method should simply be (probably not a function):
(int * 10 + dec) as f32 / 10.0
Summary
This PR adds a new ADC API, based around a similar system to channels used by EIC and DMA peripherals.
At the moment I'll keep this as a draft PR for feedback whilst I am still finalizing bits and also adding SAMD11/21 support
Features
Progress checklist