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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
use panic_halt as _;
#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [SPI1, SPI2])]
mod app {
use defmt_rtt as _;
use rtic_monotonics::systick::prelude::*;
use rtic_sync::channel::{Receiver, Sender};
use rtic_sync::make_channel;
use stm32f4xx_hal::pac::ADC1;
use stm32f4xx_hal::{
adc::{
config::{AdcConfig, SampleTime},
Adc,
},
gpio::{self, Analog, Edge, Input, Output, PushPull},
pac::TIM1,
prelude::*,
timer,
};
systick_monotonic!(Mono, 100);
// A simple placeholder for the analog pin
struct Potentiometer {
analog_input: gpio::PA1<Analog>,
}
// An enum specifying the type of messages the printer actor running on
// a software expects
enum Message {
PotentiometerValue(u16),
Ping,
}
// Resources shared between tasks
#[shared]
struct Shared {
delayval: u32,
adc_module: Adc<ADC1>,
}
// Local resources to specific tasks (cannot be shared)
#[local]
struct Local {
sender_from_exti0: Sender<'static, Message, 8>,
button: gpio::PA0<Input>,
pot_instance: Potentiometer,
led: gpio::PC13<Output<PushPull>>,
delay: timer::DelayMs<TIM1>,
}
#[init]
fn init(ctx: init::Context) -> (Shared, Local) {
Mono::start(ctx.core.SYST, 12_000_000);
let mut dp = ctx.device;
// Configure and obtain handle for delay abstraction
// 1) Promote RCC structure to HAL to be able to configure clocks
let rcc = dp.RCC.constrain();
// 2) Configure the system clocks
// 25 MHz must be used for HSE on the Blackpill-STM32F411CE board according to manual
let clocks = rcc.cfgr.use_hse(25.MHz()).freeze();
// 3) Create delay handle
let delay = dp.TIM1.delay_ms(&clocks);
// Configure the LED pin as a push pull output and obtain handle
// On the Blackpill STM32F411CEU6 there is an on-board LED connected to pin PC13
// 4) Promote the GPIOC PAC struct
let gpioc = dp.GPIOC.split();
// 5) Configure PORTC OUTPUT Pins and Obtain Handle
let led = gpioc.pc13.into_push_pull_output();
// Configure the button pin as input and obtain handle
// On the Blackpill STM32F411CEU6 there is a button connected to pin PA0
// 6) Promote the GPIOA PAC struct
let gpioa: gpio::gpioa::Parts = dp.GPIOA.split();
// 7) Configure Pin and Obtain Handle
let mut button = gpioa.pa0.into_pull_up_input();
// 8) Configure pin A1 of the blackpill to be of type analog
// the input does not need to be mutable since we are only reading it.
let analog_input = gpioa.pa1.into_analog();
// 9) Configure the ADC modulke for single-shot conversion
let mut adc = Adc::adc1(dp.ADC1, true, AdcConfig::default());
// Calibrate by calculates the system VDDA by sampling the internal VREF
// channel and comparing the result with the value stored at the factory.
adc.calibrate();
let pot_instance = Potentiometer {
analog_input: analog_input,
};
// Configure Button Pin for Interrupts
// 10) Promote SYSCFG structure to HAL to be able to configure interrupts
let mut syscfg = dp.SYSCFG.constrain();
// 11) Make button an interrupt source
button.make_interrupt_source(&mut syscfg);
// 12) Configure the interruption to be triggered on a rising edge
button.trigger_on_edge(&mut dp.EXTI, Edge::Rising);
// 13) Enable gpio interrupt for button
button.enable_interrupt(&mut dp.EXTI);
// 14) Create a channel
let (tx_to_printer, rx) = make_channel!(Message, 8);
// 15) Spawn printer_actor and start listerning
printer_actor::spawn(rx).unwrap();
let sender_from_exti0 = tx_to_printer.clone();
let sender_from_pinger = tx_to_printer.clone();
pinger::spawn(sender_from_pinger).unwrap();
(
// Initialization of shared resources. In this case delay value and the ADC instance
Shared {
delayval: 2000_u32,
adc_module: adc,
},
// Initialization of task local resources
Local {
sender_from_exti0,
button,
pot_instance,
led,
delay,
},
)
}
// Background task, runs whenever no other tasks are running
#[idle(local = [led, delay], shared = [delayval])]
fn idle(mut ctx: idle::Context) -> ! {
let led = ctx.local.led;
let delay = ctx.local.delay;
loop {
// Turn On LED
led.set_high();
// Obtain shared delay variable and delay
delay.delay_ms(ctx.shared.delayval.lock(|del| *del));
// Turn off LED
led.set_low();
// Obtain shared delay variable and delay
delay.delay_ms(ctx.shared.delayval.lock(|del| *del));
}
}
// Handle the IRQ generated when the button is pressed and interact with local and shared resources.
#[task(binds = EXTI0, local = [sender_from_exti0, button, pot_instance], shared=[delayval, adc_module])]
fn gpio_interrupt_handler(mut ctx: gpio_interrupt_handler::Context) {
ctx.shared.delayval.lock(|del| {
*del = *del - 100_u32;
if *del < 200_u32 {
*del = 2000_u32;
}
*del
});
ctx.shared.delayval.lock(|del| {
defmt::info!("Current delay value {:?}", del);
});
// Obtain the Potentiometer instance that belongs to this task ONLY
let analog_input = &ctx.local.pot_instance.analog_input;
let send_to_printer = ctx.local.sender_from_exti0;
// Obtain the shared instance of Adc and do one conversion of the value seen
ctx.shared.adc_module.lock(|adc_module| {
let sample = adc_module.convert(analog_input, SampleTime::Cycles_480);
// Now that we have the sampled value, we want to pass it to the software
// task. Since we are in a non-async context, we must use 'try_send' method
// and process the result
let send_result = send_to_printer.try_send(Message::PotentiometerValue(sample));
match send_result {
Ok(_) => {}
Err(_error) => {
defmt::error!("EXTI0 handler could not send message to printer actor");
}
}
});
ctx.local.button.clear_interrupt_pending_bit();
}
// The printer actor is a software task that listens to an MPSC channel and prints
// the incoming Message from other tasks (Hardware or Software)
#[task(priority = 1)]
async fn printer_actor(_: printer_actor::Context, mut rx: Receiver<'_, Message, 8>) {
loop {
let maybe_new_message = rx.recv().await;
match maybe_new_message {
Ok(message) => match message {
Message::PotentiometerValue(value) => {
defmt::info!(
"Printer actor received a new PotentiometerValue: {:?} from hardware task.\n",
value
);
}
Message::Ping => {
defmt::info!("Printer actor received a PING from software task");
}
},
Err(error) => {
panic!("Receiver error {:?}", error);
}
}
}
}
// This software task sends a Message of type Ping to printer_actor every 25000 millis
#[task(priority = 1)]
async fn pinger(_: pinger::Context, mut sender_from_pinger: Sender<'_, Message, 8>) {
loop {
let _ = sender_from_pinger.send(Message::Ping).await;
Mono::delay(25000.millis()).await;
}
}
}
|