diff options
| author | Milton Eduardo Sosa <31409391+snorkman88@users.noreply.github.com> | 2024-10-02 20:46:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-02 18:46:42 +0000 |
| commit | 87a8b7490866cda9e5c6819eeb357647bdb8d36e (patch) | |
| tree | 3b69862d31adfd0bc09d9098a9a998eaf4349295 /examples/stm32f411_encoder_polling/src | |
| parent | 805ea267a8515b7ee70125f444763f4aaa95c454 (diff) | |
Add example to poll encoder and display value on I2C display (#963)
Co-authored-by: Milton Eduardo Sosa <milton@Miltons-MacBook-Pro.local>
Diffstat (limited to 'examples/stm32f411_encoder_polling/src')
| -rw-r--r-- | examples/stm32f411_encoder_polling/src/main.rs | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/examples/stm32f411_encoder_polling/src/main.rs b/examples/stm32f411_encoder_polling/src/main.rs new file mode 100644 index 0000000..29a2947 --- /dev/null +++ b/examples/stm32f411_encoder_polling/src/main.rs @@ -0,0 +1,228 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true)] +mod app { + + use stm32f4xx_hal::i2c::I2c; + use stm32f4xx_hal::i2c::Mode; + use stm32f4xx_hal::{ + gpio::{self, Input, Output, PushPull}, + pac::{Peripherals, I2C1, TIM1, TIM2}, + prelude::*, + timer, + timer::{CounterHz, Event, Timer2}, + }; + + use rotary_encoder_embedded::standard::StandardMode; + use rotary_encoder_embedded::{Direction, RotaryEncoder}; + + use hd44780_driver::bus::I2CBus; + use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780}; + + use defmt_rtt as _; + + pub struct Knob { + rotary_encoder: RotaryEncoder<StandardMode, gpio::PB12<Input>, gpio::PB13<Input>>, + value: u8, + } + + impl Knob { + pub fn new( + rotary_encoder: RotaryEncoder<StandardMode, gpio::PB12<Input>, gpio::PB13<Input>>, + ) -> Knob { + Knob { + rotary_encoder: rotary_encoder, + value: 0_u8, + } + } + } + + // Set the I2C address of the PCF8574 located on the back of the HD44780. + // Check the the jumpers A0, A1 and A2 and datasheet + const LCD_I2C_ADDRESS: u8 = 0x27; + + // Resources shared between tasks + #[shared] + struct Shared { + delay: timer::DelayMs<TIM1>, + lcd: HD44780<I2CBus<I2c<I2C1>>>, + } + + // Local resources to specific tasks (cannot be shared) + #[local] + struct Local { + led: gpio::PC13<Output<PushPull>>, + tim2_timer: CounterHz<TIM2>, + knob_1: Knob, + } + + #[init] + fn init(ctx: init::Context) -> (Shared, Local) { + let dp: Peripherals = ctx.device; + + // Configure and obtain handle for delay abstraction using TIM1 + // Promote RCC structure to HAL to be able to configure clocks + let rcc = dp.RCC.constrain(); + + // 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(); + + let mut 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 + // Promote the GPIOC PAC struct + let gpioc = dp.GPIOC.split(); + 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 + // Promote the GPIOB PAC struct + let gpiob = dp.GPIOB.split(); + + // Configure Pins connected to encoder as floating input (only if your encoder + // board already has pull-up resistors, use 'into_pull_up_input' otherwise) + // and Obtain Handle. + let enc_1_a = gpiob.pb12.into_floating_input(); + let enc_1_b = gpiob.pb13.into_floating_input(); + + // Instantiate RotaryEncoder struct + let encoder_1 = RotaryEncoder::new(enc_1_a, enc_1_b).into_standard_mode(); + + // Instantiate Knob struct to hold 'encoder_1' and its current value + let knob_1 = Knob::new(encoder_1); + + // Instantiate TIM2 that will be used to poll the encoders + let tim2_timer = Timer2::new(dp.TIM2, &clocks); + let mut tim2_timer = tim2_timer.counter_hz(); + + // Get SDA and SCL pins for I2C1 + let sda = gpiob.pb7; + let scl = gpiob.pb6; + + // Instantiate I2C1 bus that will be used to communicate with the + // LCD display. + let i2c = I2c::new( + dp.I2C1, + (scl, sda), + Mode::Standard { + frequency: 400.kHz(), + }, + &clocks, + ); + + let mut lcd = HD44780::new_i2c(i2c, LCD_I2C_ADDRESS, &mut delay).expect("Init LCD failed"); + + let _ = lcd.reset(&mut delay); + let _ = lcd.clear(&mut delay); + let _ = lcd.set_display_mode( + DisplayMode { + display: Display::On, + cursor_visibility: Cursor::Invisible, + cursor_blink: CursorBlink::Off, + }, + &mut delay, + ); + let _ = lcd.set_cursor_pos(57, &mut delay); + let _ = lcd.write_str("RTIC + I2C + Encoder", &mut delay); + delay.delay_ms(1500); + + let _ = lcd.clear(&mut delay); + let _ = lcd.set_cursor_pos(68, &mut delay); + let _ = lcd.write_str("Encoder 1", &mut delay); + + // Start the timer at 2 kHz + tim2_timer.start(2000.Hz()).unwrap(); + + // Generate an interrupt when the timer expires + tim2_timer.listen(Event::Update); + + ( + // Initialization of shared resources. + Shared { delay, lcd }, + // Initialization of task local resources + Local { + led, + tim2_timer, + knob_1, + }, + ) + } + + // Background task, runs whenever no other tasks are running + #[idle(shared = [])] + fn idle(mut _ctx: idle::Context) -> ! { + loop {} + } + + // Handle the IRQ generated when the TIM2 times out + #[task(binds = TIM2, local=[led, tim2_timer, knob_1], shared=[lcd, delay])] + fn tim2_timeout_interrupt_handler(mut ctx: tim2_timeout_interrupt_handler::Context) { + ctx.local.tim2_timer.clear_all_flags(); + let led = &mut ctx.local.led; + led.toggle(); + + //Obtain 2 shared resources: lcd and delay. + let delay = &mut ctx.shared.delay; + let lcd = &mut ctx.shared.lcd; + + // Update the encoder, which will compute and return its direction + match ctx.local.knob_1.rotary_encoder.update() { + Direction::Clockwise => { + if ctx.local.knob_1.value < 255 { + ctx.local.knob_1.value += 1; + defmt::info!("Going UP! {:?}", ctx.local.knob_1.value); + let current_value = ctx.local.knob_1.value; + (delay, lcd).lock(|delay, lcd| { + //Move cursor to the middle of the third line and update value + let _ = lcd.set_cursor_pos(27, delay); + let _ = lcd.write_bytes(&u8_to_str(current_value), delay); + }) + } + } + Direction::Anticlockwise => { + if ctx.local.knob_1.value > 0 { + ctx.local.knob_1.value -= 1; + defmt::info!("Going DN! {:?}", ctx.local.knob_1.value); + let current_value = ctx.local.knob_1.value; + (delay, lcd).lock(|delay, lcd| { + //Move cursor to the middle of the third line and update value + let _ = lcd.set_cursor_pos(27, delay); + let _ = lcd.write_bytes(&u8_to_str(current_value), delay); + }) + } + } + Direction::None => { + // Do nothing + } + } + } + + // This auxiliary function is in charge of converting a u8 number into + // a 3-char string representation without doing memory allocation. + // 0 is represented as "000" + // 84 is represented as "084" + fn u8_to_str(n: u8) -> [u8; 3] { + let mut buffer = [b'0'; 3]; // Initialize the buffer with '0' + let mut num = n; + let mut i = 2; + + // Fill the buffer with digits from the end to the start + loop { + buffer[i] = (num % 10) + b'0'; + num /= 10; + if num == 0 { + break; + } + i -= 1; + } + + buffer + } +} |
