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

Adds DMA support for ADC1 #73

Merged
merged 3 commits into from
Jun 20, 2019
Merged

Conversation

thalesfragoso
Copy link
Member

I also had to make some minor changes in the dma and serial files to better accommodate the adc, but they were non breaking, the examples remain the same and compile fine.

I would like @burrbull to take a look to make sure that I didn't make any changes that would have a bad effect on present or future implementations.

I tested the rx transfer on the stm32f103 and it seems that everything is working as expected. I will probably hard test the circular implementation too, but it should be working fine. I also fixed a typo in a comment on the RCC.

@burrbull
Copy link
Member

It looks good in first view. I'll test it tomorrow .

But can you modify it to support a sequence (array) of ADC channels, not a one?

@burrbull
Copy link
Member

burrbull commented Jun 13, 2019

It looks like you measure voltage each adcclk cycle.
What if I need to use external timer for start measuring?

See ADCx.CR2.EXTSEL + TIMx.CR2.MMS

@burrbull
Copy link
Member

@TheZoq2 , @therealprof
I have idea. What about to add such interface for peripheral wrappers?:

    pub unsafe fn configure(&mut self, f: fn(&mut PER)) {
        f(&mut self.rb)
    }

Unsafe user fns for advanced manual configure if required functionality is not present in HAL yet?
It can be a solution for difficult situations.

@thalesfragoso
Copy link
Member Author

It looks good in first view. I'll test it tomorrow .

But can you modify it to support a sequence (array) of ADC channels, not a one?

Right now, the only way I can think of is exposing a trait and trust the users to implement it, like we do with the pwm, which I think is totally fine, we just need to provide some documentation. However, there are probably others ways to achieve this, but either way I would like it to be like another with_dma method to keep the api simple for simple cases.

It looks like you measure voltage each adcclk cycle.
What if I need to use external timer for start measuring?

See ADCx.CR2.EXTSEL + TIMx.CR2.MMS

Not exactly at each adcclk cycle, it takes more for conversion, but I get what you are saying. Anyway, I would say it would be the same thing as I said above.

@TheZoq2 , @therealprof
I have idea. What about to add such interface for peripheral wrappers?:

    pub unsafe fn configure(&mut self, f: fn(&mut PER)) {
        f(&mut self.rb)
    }

Unsafe user fns for advanced manual configure if required functionality is not present in HAL yet?
It can be a solution for difficult situations.

I think you can already do that with the ::ptr() method, and I also think they will make steal really "steal" things, see rust-embedded/svd2rust#238 and rust-embedded/cortex-m#147.

@burrbull
Copy link
Member

I think you can already do that with the ::ptr() method, and I also think they will make steal really "steal" things, see rust-embedded/svd2rust#238 and rust-embedded/cortex-m#147.

I know, that I can use ptr (and I often do so). But it creates new instance, that leads to many bugs.

This PR I approve to merge as it is. Maybe later I will try to extend this API.

let (_buf, adc_dma) = adc_dma.read(buf).wait();
asm::bkpt();

let (_adc1, _adc_ch0, _dma_ch1) = adc_dma.split();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please clarify what are these lines for ? Even more, maybe it worth adding short comments for all logical blocks in this example ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, now I also changed some of your adc code, please take a look to make sure I did not do something wrong, thanks.

@thalesfragoso
Copy link
Member Author

To clarify my last commit:
Since there was recently a breaking change in the adc api, I took the opportunity to "rebreak it" to enforce some delay time required for startup as mentioned in the reference manual.

if two_adc_cycles > already_delayed {
delay(two_adc_cycles - already_delayed);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to datasheet, calibration is recommended after each power-up. If so, then why don't we move all the delays into power_up() function rather than keeping several pieces in different places ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that is a good point, but since it is just recommended I kinda did not want to enforce it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably didn't make myself clear. I mean, why don't we move that additional delay code into power_up function ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it would enforce that we delayed for calibration every time we power_up even when we don't want to calibrate it. Right now, it does not matter, since the methods are not public and we are powering up only once and we are calibrating. But maybe in some future implementation that could be different.

Anyway, I also think that we should calibrate after each power up, but I did not want to enforce it, otherwise I would just move the calibration code to power up and remove the calibration method.

self.power_down();
self.disable_clock(apb2);
self.rb
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why you don't want to return $ADC from this function ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, no, but it is returning ADC. isn't it ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, typo. Sorry for confusion. The question is "why do you want to return ADC from this function". I am asking not because I have any objections, I just want to understand the use-case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is a singleton, I am returning it so people can use it to start the adc again if they want to.

@@ -375,9 +403,7 @@ macro_rules! adc_hal {
type Error = ();

fn read(&mut self, _pin: &mut PIN) -> nb::Result<WORD, Self::Error> {
self.power_up();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, we opt to sacrifice power consumption for accuracy here. Right ? Alternatively we could calibrate before each read, but that might be tooooo slow...

Copy link
Member Author

@thalesfragoso thalesfragoso Jun 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct, and if you don't want to calibrate you still would need to wait 1us. Moreover, that was the reason I created the release method, so if you want to save power you could use it only in the time you needed it, in contrast to have to wait 1us+ every conversion.

And as a side note, I think that the first time you would call the old method it would cause some problem, because the adc was already up, so if you set the ADON bit you would start a conversion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirm the observation regarding the first measurement in the previous approach. However with new approach (w/o power_up/power_down around convert) all the values look strange, not only the first one. At least this is the case for temperature sensor. I need to do more testing using voltage inputs...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did more tests with various ADC clock settings. It turns out that sampling settings for temperature sensor (suggested in my previous PR #67 ) should be modified as follows to work well with introduced delays:

diff --git a/src/adc.rs b/src/adc.rs
index b1a73c2..d4752a9 100644
--- a/src/adc.rs
+++ b/src/adc.rs
@@ -468,7 +468,7 @@ impl Adc<ADC1> {
             2_400_001 ... 3_100_000 => AdcSampleTime::T_28,
             3_100_001 ... 4_000_000 => AdcSampleTime::T_41,
             4_000_001 ... 5_000_000 => AdcSampleTime::T_55,
-            5_000_001 ... 10_000_000 => AdcSampleTime::T_71,
+            5_000_001 ... 14_000_000 => AdcSampleTime::T_71,
             _ => AdcSampleTime::T_239,
         };

Could you please meld this fixup into you PR ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thank you for helping out :)

@geomatsi
Copy link
Contributor

LGTM. Thanks !

@@ -429,14 +461,14 @@ impl Adc<ADC1> {
// recommended ADC sampling for temperature sensor is 17.1 usec,
// so use the following approximate settings
// to support all ADC frequencies
let sample_time = match self.clk.0 {
let sample_time = match self.clocks.adcclk().0 {
0 ... 1_200_000 => AdcSampleTime::T_1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... syntax is deprecated in nightly. Use ..= instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching ! Maybe I can do it it in a separate 'cleanup' PR ? Otherwise we are going to have too much unrelated things in this DMA PR.

Copy link
Member

@therealprof therealprof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks.

@therealprof therealprof merged commit 4fdc432 into stm32-rs:master Jun 20, 2019
@thalesfragoso thalesfragoso deleted the adcDma branch July 16, 2019 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants