-
Notifications
You must be signed in to change notification settings - Fork 268
ADC free-running mode & FIFO #626
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
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.
Hi @nilclass, thanks for this pull request. Overall I like the API, but there are a few details left to do.
rp2040-hal/src/adc.rs
Outdated
/// | ||
/// In addition to being usable the same way as a `Fifo` returned from `start_fifo`, | ||
/// the Fifo returned by this function can also be used as a source for DMA transfers. | ||
pub fn start_fifo_with_dma(self, thresh: u8) -> Fifo<'a, true> { |
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.
The datasheet says: "The threshold for DREQ assertion (FCS.THRESH) should be set to 1, so that the DMA transfers as soon as a single sample is present in the FIFO. Note this is also the threshold used for IRQ assertion, so non-DMA use cases might prefer a higher value for less frequent interrupts."
Therefore I'd say for the DMA use case, the threshold should not be configurable, but fixed to 1.
Also, it probably (but see my other comment) doesn't harm to always enable DREQ. So start_fifo_with_dma
and start_fifo
may not need to be separate functions? And the DmaEnabled flag could be removed from the Fifo type signature?
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.
hm, yeah, those are some good points.
If DREQ is always enabled, we can't enforce FCS.THRESH=1 for the DMA usecase (or else not have the threshold configurable).
We could move this issue to the user (at least until it's clear how to handle it nicely API-wise), by having builder methods set_threshold(...)
, enable_dma(true | false)
, enable_interrupt(true | false)
, and have a single start_fifo
function as you suggest.
That would make these features usable without messing with registers manually, but it would leave some caveats:
- either there needs to be some note, that when enabling DMA, the threshold should be set to 1
- or
enable_dma
would force the threshold to 1, possibly overriding the user choice (or not, if the user callsset_threshold
afterenable_dma
)
Not the best options, imho.
So, also in light of your other comments: maybe the DMA part can be skipped for now, and handled in a future PR, when more is known about the usecases.
Then my goal would be to make the API nice for the interrupt usecase.
Which makes me wonder: do you think there is a usecase where neither the interrupt, nor DMA is used?
From what I can tell when capturing in free-running mode, CS.READY does not change, so even though the samples can be read from CS.RESULT there is no way to tell when a new sample was captured.
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.
You might have code that's only interested in the most recent measurement (ie. it doesn't matter if you skip some measurements). For example, with ADC in free-running mode, you could do such a thing:
loop {
do_something_timing_critical();
if adc.read_single() > THRESHOLD {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
}
That way, the CPU doesn't need to wait 96 cycles for a new ADC value, but still always gets a (relatively) recent 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.
I agree, that sounds reasonable.
Since AdcFifo
currently holds a &mut Adc
calling adc.read_single()
directly wouldn't work though (unless the AdcFifo
is dropped first, which makes it impossible to stop the conversion later on).
So I've added a read_single
method to AdcFifo
now, which just calls the underlying adc.read_single
to enable this use case.
@jannic thanks for taking the time to review this. I realize this PR is not in a good state to merge, it's more of a draft, to see if there is interest in such an API 🙂 I'll respond to your feedback inline. |
I've done a bit of cleaning up:
I've also added a working example: Some things that are still missing in my opinion (though they don't block usage of the features that are there):
|
- Rename `Fifo` struct to `AdcFifo` - Rename `FreeRunning` struct to `AdcFifoBuilder` (since that's what it is) - Remove DMA related features from `Fifo`: it's not clear to me how a good API for this will look
When this is called, the resulting AdcFifo returns a u8 from it's `read` method.
97239a4
to
bc53742
Compare
I'll try to do the review soon but I'm quite busy atm so I can't promise much. May take a few days, sorry! |
@jannic did you get a chance to take a look? 🫣 Is there anything I can do to make things easier to review? In case there is no capacity currently for this review, or there are general concerns about the API, I could look into putting this functionality in a separate crate for now. That way it can be used, without locking |
Sorry, the delay was entirely caused by my lack of time, and not by anything wrong with your work. The code is nicely readable and any difficulty reviewing it is caused by the inherent complexity of the hardware. I added a few minor remarks, but in general the pull request looks good. I won't worry too much about locking |
/// Manually set clock divider integral and fractional parts | ||
/// | ||
/// Default: 0, 0 | ||
pub fn clock_divider(self, int: u16, frac: u8) -> Self { |
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.
You could add an explanation to those parameters.
Also, in the examples (eg. https://github.com/rp-rs/rp-hal/pull/626/files#diff-54b5bbce718eaba891383cd18b03aa2b4533ef7b314c7584ec31bf880712a18bR114) it is difficult to understand why one needs such an odd divisor 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.
Fair enough :)
Added a bit of explanation, and a few example values
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.
"A bit"? That doc comment is great!
rp2040-hal/src/adc.rs
Outdated
/// | ||
/// In addition to being usable the same way as a `Fifo` returned from `start_fifo`, | ||
/// the Fifo returned by this function can also be used as a source for DMA transfers. | ||
pub fn start_fifo_with_dma(self, thresh: u8) -> Fifo<'a, true> { |
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.
You might have code that's only interested in the most recent measurement (ie. it doesn't matter if you skip some measurements). For example, with ADC in free-running mode, you could do such a thing:
loop {
do_something_timing_critical();
if adc.read_single() > THRESHOLD {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
}
That way, the CPU doesn't need to wait 96 cycles for a new ADC value, but still always gets a (relatively) recent 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.
Both code and comments are of high quality. Very readable, and correct as far as I can tell. While I didn't have time to try it out on real hardware, I think it's ready to be merged.
Thanks, @nilclass, for this great contribution, and sorry again for the delay reviewing it.
(And I should have waited for the pipeline results. It would be nice if you could fix those warnings / errors. Some of the code comments could be marked as ignore
or text
, eg. the examples in AdcFifoBuilder, as they are obviously not meant to be working code.)
/// Manually set clock divider integral and fractional parts | ||
/// | ||
/// Default: 0, 0 | ||
pub fn clock_divider(self, int: u16, frac: u8) -> Self { |
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.
"A bit"? That doc comment is great!
Thanks for the review @jannic, I'm happy to hear the changes are appreciated. I believe the pipeline will be green now. I've just run the two examples one more time on real hardware, still working fine :) |
An attempt to come up with an API for the ADC in free-running mode.
Inspired by #326.
Overview of changes:
adc
module:FreeRunning
andFifo
(EDIT: naming changed toAdcFifoBuilder
andAdcFifo
)FreeRunning
represents theAdc
configuration for free-running mode:adc.free_running()
start_fifo
,start_fifo_with_dma(thresh)
Fifo
struct represents the ADC Fifo while capture is running:start_*
methods fromFreeRunning
fifo.len()
andfifo.read()
give access to the FCS.LEVEL and FIFO register valuesfifo.is_over()
andfifo.is_under()
expose the FCS.OVER and FCS.UNDER flags (and reset them on read)fifo.stop()
stops the capture and disables the fifoBrief example: