-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy pathadc.rs
149 lines (130 loc) · 5.63 KB
/
adc.rs
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
#![no_std]
#![no_main]
//! Example usage for ADC on STM32F303
use defmt_rtt as _;
use panic_probe as _;
use core::cell::RefCell;
use cortex_m::asm;
use cortex_m_rt::entry;
use critical_section::Mutex;
use embedded_hal::adc::OneShot;
use stm32f3xx_hal::{
adc,
pac::{self, interrupt},
prelude::*,
timer,
};
/// That's a mouthful, so explain it:
///
/// 1. Wrap the Timer in a Mutex, so it can be safely shared between
/// main loop and interrupt or rather used in functions, which could theoretically
/// be preempted.
/// 2. Wrap the Timer in a RefCell to be able obtain a mutable reference to the Timer itself.
/// E.g. the interrupt can't take ownership of the timer, it is shared between main-loop and
/// interrupt context.
/// 3. Wrap the Timer in an Option, so that it can be "lazily initialized". Statics have to be
/// initialized with const values, which the timer itself is obviously not, but None is.
///
/// This could all be done just with a static mut && unsafe as
/// the usecase it pretty clear, but this is to show the definitely safe
/// alternative.
static TIMER: Mutex<RefCell<Option<timer::Timer<pac::TIM2>>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
// Get peripherals, clocks and freeze them
let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut dp.FLASH.constrain().acr);
// This is a workaround, so that the debugger will not disconnect imidiatly on asm::wfi();
// https://github.com/probe-rs/probe-rs/issues/350#issuecomment-740550519
dp.DBGMCU.cr.modify(|_, w| {
w.dbg_sleep().set_bit();
w.dbg_standby().set_bit();
w.dbg_stop().set_bit()
});
// Create a Common ADC instance, which is shared between ADC1 and ADC2 in this case,
// and for example is in control of the clock of both of these peripherals.
let mut adc_common = adc::CommonAdc::new(dp.ADC1_2, &clocks, &mut rcc.ahb);
// We have to pack these peripherals in a tuple, so that `TemperatureSensor` can
// get a mutable reference to both of these as a singular argument.
//
// This is needed, as both ADC1 and ADC2 have to be of to enable the `TemperatureSensor`.
let mut tuple = (dp.ADC1, dp.ADC2);
let mut ts = adc::TemperatureSensor::new(&mut adc_common, &mut tuple);
// Set up ADC1
let mut adc = adc::Adc::new(
tuple.0, // The ADC we are going to control
adc::config::Config::default(),
// The following is only needed to make sure the clock signal for the ADC is set up
// correctly.
&clocks,
&adc_common,
)
// Convert the ADC into `OneShot` mode.
//
// This is not necessary, as the normal ADC does also implement the `OneShot` trait, which can
// call `read`. But this conversion implies less overhead for the trait implementation. The
// consequence is, that in this mode nothing can be configured manually.
.into_oneshot();
// You can also create an ADC, which is initially disabled and uncalibrated and calibrate
// it later:
let mut adc2 = adc::Adc::new_disabled(tuple.1);
adc2.calibrate(&clocks, &adc_common);
adc2.set_config(adc::config::Config::default());
let _ = adc2.into_enabled();
// Set up pin PA0 as analog pin.
// This pin is connected to the user button on the stm32f3discovery board.
let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
let mut analog_pin = gpioa.pa0.into_analog(&mut gpioa.moder, &mut gpioa.pupdr);
let mut timer = timer::Timer::new(dp.TIM2, clocks, &mut rcc.apb1);
unsafe {
cortex_m::peripheral::NVIC::unmask(timer.interrupt());
}
timer.enable_interrupt(timer::Event::Update);
// Start a timer which fires regularly to wake up from `asm::wfi`
timer.start(500.milliseconds());
// Put the timer in the global context.
critical_section::with(|cs| {
TIMER.borrow(cs).replace(Some(timer));
});
// Be aware that the values in the table below depend on the input of VREF.
// To have a stable VREF input, put a condensator and a volt limiting diode in front of it.
//
// Also know that integer division and the ADC hardware unit always round down.
// To make up for those errors, see this forum entry:
// [https://forum.allaboutcircuits.com/threads/why-adc-1024-is-correct-and-adc-1023-is-just-plain-wrong.80018/]
defmt::info!("
The ADC has a 12 bit resolution, i.e. if your reference Value is 3V:
approx. ADC value | approx. volt value
==================+===================
0 | 0 mV
2048 | 1500 mV
4095 | 3000 mV
If you are using a STM32F3Discovery, PA0 is connected to the User Button.
Pressing it should connect the user Button to to HIGH and the value should change from 0 to 4095.
");
loop {
let adc_data: u16 = adc.read(&mut analog_pin).unwrap();
defmt::trace!("PA0 reads {}", adc_data);
let adc_data: u16 = adc.read(&mut ts).unwrap();
defmt::trace!("TemperatureSensor reads {}", adc_data);
asm::wfi();
}
}
#[interrupt]
fn TIM2() {
// Just handle the pending interrupt event.
critical_section::with(|cs| {
TIMER
// Unlock resource for use in critical section
.borrow(cs)
// Get a mutable reference from the RefCell
.borrow_mut()
// Make the inner Option<T> -> Option<&mut T>
.as_mut()
// Unwrap the option, we know, that it has Some()!
.unwrap()
// Finally operate on the timer itself.
.clear_event(timer::Event::Update);
})
}