aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32g4/src/bin/adc_injected_and_regular.rs
blob: 1e97fa925c8c379833670b27a8ba798b01c5fdfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! adc injected and regular conversions
//!
//! This example both regular and injected ADC conversions at the same time
//! p:pa0 n:pa2

#![no_std]
#![no_main]

use core::cell::RefCell;

use defmt::info;
use embassy_stm32::adc::{
    Adc, AdcChannel as _, ConversionTrigger, Exten, InjectedAdc, RegularConversionMode, SampleTime,
};
use embassy_stm32::interrupt::typelevel::{ADC1_2, Interrupt};
use embassy_stm32::peripherals::ADC1;
use embassy_stm32::time::Hertz;
use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, Mms2};
use embassy_stm32::timer::low_level::CountingMode;
use embassy_stm32::{Config, interrupt};
use embassy_sync::blocking_mutex::CriticalSectionMutex;
use {defmt_rtt as _, panic_probe as _};

static ADC1_HANDLE: CriticalSectionMutex<RefCell<Option<InjectedAdc<ADC1, 1>>>> =
    CriticalSectionMutex::new(RefCell::new(None));

/// This example showcases how to use both regular ADC conversions with DMA and injected ADC
/// conversions with ADC interrupt simultaneously. Both conversion types can be configured with
/// different triggers and thanks to DMA it is possible to use the measurements in different task
/// without needing to access the ADC peripheral.
///
/// If you don't need both regular and injected conversions the example code can easily be reworked
/// to only include one of the ADC conversion types.
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
    // See Table 166 and 167 in RM0440 Rev 9 for ADC1/2 External triggers
    // Note: Regular and Injected channels use different tables!!
    const ADC1_INJECTED_TRIGGER_TIM1_TRGO2: u8 = 8;
    const ADC1_REGULAR_TRIGGER_TIM1_TRGO2: u8 = 10;

    // --- RCC config ---
    let mut config = Config::default();
    {
        use embassy_stm32::rcc::*;
        config.rcc.pll = Some(Pll {
            source: PllSource::HSI,
            prediv: PllPreDiv::DIV4,
            mul: PllMul::MUL85,
            divp: None,
            divq: None,
            divr: Some(PllRDiv::DIV2),
        });
        config.rcc.mux.adc12sel = mux::Adcsel::SYS;
        config.rcc.sys = Sysclk::PLL1_R;
    }
    let p = embassy_stm32::init(config);

    // In this example we use tim1_trgo2 event to trigger the ADC conversions
    let tim1 = p.TIM1;
    let pwm_freq = 1;
    let mut pwm = ComplementaryPwm::new(
        tim1,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        Hertz::hz(pwm_freq),
        CountingMode::EdgeAlignedUp,
    );
    pwm.set_master_output_enable(false);
    // Mms2 is used to configure which timer event that is connected to tim1_trgo2.
    // In this case we use the update event of the timer.
    pwm.set_mms2(Mms2::UPDATE);

    // Configure regular conversions with DMA
    let adc1 = Adc::new(p.ADC1, Default::default());

    let vrefint_channel = adc1.enable_vrefint().degrade_adc();
    let pa0 = p.PC1.degrade_adc();
    let regular_sequence = [
        (vrefint_channel, SampleTime::CYCLES247_5),
        (pa0, SampleTime::CYCLES247_5),
    ]
    .into_iter();

    // Configurations of Injected ADC measurements
    let pa2 = p.PA2.degrade_adc();
    let injected_sequence = [(pa2, SampleTime::CYCLES247_5)];

    // Configure DMA for retrieving regular ADC measurements
    let dma1_ch1 = p.DMA1_CH1;
    // Using buffer of double size means the half-full interrupts will generate at the expected rate
    let mut readings = [0u16; 4];

    let injected_trigger = ConversionTrigger {
        channel: ADC1_INJECTED_TRIGGER_TIM1_TRGO2,
        edge: Exten::RISING_EDGE,
    };
    let regular_trigger = ConversionTrigger {
        channel: ADC1_REGULAR_TRIGGER_TIM1_TRGO2,
        edge: Exten::RISING_EDGE,
    };

    let (mut ring_buffered_adc, injected_adc) = adc1.into_ring_buffered_and_injected(
        dma1_ch1,
        &mut readings,
        regular_sequence,
        RegularConversionMode::Triggered(regular_trigger),
        injected_sequence,
        injected_trigger,
        true,
    );

    // Store ADC globally to allow access from ADC interrupt
    critical_section::with(|cs| {
        ADC1_HANDLE.borrow(cs).replace(Some(injected_adc));
    });
    // Enable interrupt for ADC1_2
    unsafe { ADC1_2::enable() };

    // Main loop for reading regular ADC measurements periodically
    let mut data = [0u16; 2];
    loop {
        {
            match ring_buffered_adc.read(&mut data).await {
                Ok(n) => {
                    defmt::info!("Regular ADC reading, VrefInt: {}, PA0: {}", data[0], data[1]);
                    defmt::info!("Remaining samples: {}", n,);
                }
                Err(e) => {
                    defmt::error!("DMA error: {:?}", e);
                    ring_buffered_adc.clear();
                }
            }
        }
    }
}

/// Use ADC1_2 interrupt to retrieve injected ADC measurements
/// Interrupt must be unsafe as hardware can invoke it any-time. Critical sections ensure safety
/// within the interrupt.
#[interrupt]
unsafe fn ADC1_2() {
    critical_section::with(|cs| {
        if let Some(injected_adc) = ADC1_HANDLE.borrow(cs).borrow_mut().as_mut() {
            let injected_data = injected_adc.read_injected_samples();
            info!("Injected reading of PA2: {}", injected_data[0]);
        }
    });
}