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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! This module includes an overview of ADC features available.
//! For project structure and debugging boilerplate, see the `synax_overview` example.
#![no_main]
#![no_std]
use cortex_m::delay::Delay;
use cortex_m_rt::entry;
use critical_section::with;
use hal::{
adc::{
Adc, AdcChannel, AdcInterrupt, Align, ClockMode, InputType, OperationMode,
SampleTime,
},
clocks::Clocks,
dma::{self, Dma, DmaChannel, DmaInput, DmaInterrupt, DmaPeriph, DmaWriteBuf},
gpio::{Pin, PinMode, Port},
low_power, pac,
prelude::*,
timer::{BasicTimer, MasterModeSelection},
};
static mut ADC_READ_BUF: [u16; 2] = [0; 2];
#[entry]
fn main() -> ! {
// Set up CPU peripherals
let mut cp = cortex_m::Peripherals::take().unwrap();
// Set up microcontroller peripherals
let mut dp = pac::Peripherals::take().unwrap();
let clock_cfg = Clocks::default();
clock_cfg.setup().unwrap();
let mut delay = Delay::new(cp.SYST, clock_cfg.systick());
let chan_num = 2;
// Configure the ADC pin in analog mode. (This is the default state for some STM32 families,
// but not all)
let _adc_pin = Pin::new(Port::B, 0, PinMode::Analog);
let mut adc = Adc::new_adc1(
dp.ADC1,
Default::default(),
clock_cfg.systick(),
);
// 1: Configuration options:
// Set a channel to a specific position in a sequence:
adc.set_sequence(1, 2); // Set channel 1 to be the second position in the sequence.
// Set the length of the sequence to read. (ie number of channels):
adc.set_sequence_len(2);
// Set up differential mode:
adc.set_input_type(chan_num, InputType::Differential);
// Change the sample rate:
adc.set_sample_time(chan_num, SampleTime::T2);
// Set left align mode:
adc.set_align(Align::Left);
adc.enable_interrupt(AdcInterrupt::EndOfSequence);
// If you wish to sample at a fixed rate, consider using a basic timer (TIM6 or TIM7)
let mut adc_timer = BasicTimer::new(
dp.TIM6, 100., // Frequency in Hz.
&clock_cfg,
);
// The update event is selected as a trigger output (TRGO). For instance a
// master timer can then be used as a prescaler for a slave timer.
adc_timer.set_mastermode(MasterModeSelection::Update);
adc_timer.enable();
// todo: Which should it be?
adc.set_trigger(adc::Trigger::Tim6Trgo, adc::TriggerEdge::HardwareRising);
adc.set_trigger(DacChannel::C1, Trigger::Tim6);
// 2: Set up DMA, for non-blocking transfers:
let mut dma = Dma::new(&mut dp.DMA1, &dp.RCC);
let mut dma_buf = [0];
dma::mux(DmaChannel::C1, DmaInput::Adc1);
// Begin a DMA transfer. Note that the `DmaChannel` we pass here is only used on
// MCUs that use `DMAMUX`, eg L5, G0, and G4. For those, you need to run `mux`, to
// set the channel: `dma::mux(DmaPeriph::Dma1, DmaChannel::C1, MuxInput::Adc1);
unsafe {
adc.read_dma(
&mut dma_buf,
&[chan_num],
DmaChannel::C1,
Default::default(),
DmaPeriph::Dma1,
)
};
// Wait for the transfer to complete. Ie by handling the channel's transfer-complete
// interrupt in an ISR, which is enabled by the `read_dma` command.
// For this example, we block until the flag is set.
while !dma.transfer_is_complete(DmaChannel::C1) {}
dma.stop(DmaChannel::C1);
defmt::println!("Reading: {:?}", &dma_buf[0]);
// Unmask the interrupt line, and set its priority. See the `DMA_CH1` interrupt handler below.
setup_nvic!([(DMA1_CH1, 3)]);
// 3: Example of starting a circular DMA transfer using 2 channels. This will continuously update
// the buffer with values from channels 17 and 12. (You can set longer sequence lengths as well).
// You can then read from the buffer at any point to get the latest reading.
let adc_cfg = AdcConfig {
operation_mode: adc::OperationMode::Continuous,
..Default::default()
};
let mut batt_curr_adc = Adc::new_adc2(dp.ADC2, adc_cfg, clock_cfg.systick());
unsafe {
batt_curr_adc.read_dma(
&mut ADC_READ_BUF,
&[17, 12],
DmaChannel::C2,
ChannelCfg {
circular: dma::Circular::Enabled,
..Default::default()
},
&mut dma,
);
}
// 4: Alternatively, we can take readings without DMA. This provides a simpler, blocking API.
// Take a blocking reading from channel 3.
let reading = adc.read(chan_num);
// Convert a reading to voltage, which includes compensation for the built-in VDDA
// reference voltage
let voltage = adc.reading_to_voltage(reading);
// Or, read convert multiple channels in a sequence. You can read the results once the end-
//-of-sequence interrupt fires.
adc.enable_interrupt(AdcInterrupt::EndOfSequence);
adc.start_conversion(&[1, 2, 3]);
loop {
low_power::sleep_now();
}
}
#[interrupt]
/// This interrupt fires when the ADC transfer is complete.
fn DMA1_CH1() {
dma::clear_interrupt(
DmaPeriph::Dma1,
DmaChannel::C1,
DmaInterrupt::TransferComplete,
);
// (Handle the readings as required here. Perhaps filter them, or use them.)
}
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
#[defmt::panic_handler]
fn panic() -> ! {
cortex_m::asm::udf()
}