aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32f411_adc_and_mpsc_channel/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'examples/stm32f411_adc_and_mpsc_channel/src/main.rs')
-rw-r--r--examples/stm32f411_adc_and_mpsc_channel/src/main.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/examples/stm32f411_adc_and_mpsc_channel/src/main.rs b/examples/stm32f411_adc_and_mpsc_channel/src/main.rs
new file mode 100644
index 0000000..3169921
--- /dev/null
+++ b/examples/stm32f411_adc_and_mpsc_channel/src/main.rs
@@ -0,0 +1,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;
+ }
+ }
+}