aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32f411_encoder_polling/src
diff options
context:
space:
mode:
authorMilton Eduardo Sosa <31409391+snorkman88@users.noreply.github.com>2024-10-02 20:46:42 +0200
committerGitHub <noreply@github.com>2024-10-02 18:46:42 +0000
commit87a8b7490866cda9e5c6819eeb357647bdb8d36e (patch)
tree3b69862d31adfd0bc09d9098a9a998eaf4349295 /examples/stm32f411_encoder_polling/src
parent805ea267a8515b7ee70125f444763f4aaa95c454 (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.rs228
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
+ }
+}