From 4c32ace58173129c93a3f5b9ea1743822ab5fbd0 Mon Sep 17 00:00:00 2001 From: JP Stringham Date: Mon, 5 Jan 2026 13:10:08 -0500 Subject: [PATCH] Rotary dial added to enable target temp changing, GPIO 15 enabled as a gate pin for a mosfet power control --- src/main.rs | 100 ++++++++++++++++++++++++++++++++++++++++++-------- src/rotary.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/rotary.rs diff --git a/src/main.rs b/src/main.rs index 752af20..1f46f0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ #![no_std] #![no_main] +mod rotary; + use defmt::println; // Ensure we halt the program on panic (if we don't mention this crate it won't // be linked) @@ -27,6 +29,10 @@ use sh1106_pico_rs::{sh1106::SH1106Dev, graphics::GraphicsBuf}; // Some traits we need use embedded_hal::digital::OutputPin; +use embedded_hal::digital::InputPin; + +use crate::rotary::RotaryEnc; +use crate::rotary::RotaryResponse; /// The linker will place this boot block at the start of our program image. We /// need this to help the ROM bootloader get our code up and running. @@ -125,7 +131,52 @@ fn main() -> ! { let mut tick = 0u32; let mut last_gfx_push = 0u32; + let mut target_temp = 220; + + let mut gate_pin = pins.gpio15.into_push_pull_output_in_state(rp2040_hal::gpio::PinState::Low); + + + let mut rotary = RotaryEnc::default(); + // rotary encoder + let mut clk_pin = pins.gpio16.into_pull_up_input(); + let mut dt_pin = pins.gpio17.into_pull_up_input(); + + let mut last_rotary = 0u32; + + const CYCLES_PER_FRAME:u32 = 100_000; + const CYCLES_PER_ROTARY:u32 = 10_000; + + let mut rotary_input_allowed = true; + loop { + + if tick - last_rotary > CYCLES_PER_ROTARY { + rotary_input_allowed = true; + } + + if rotary_input_allowed { + + let clk_now = clk_pin.is_low().unwrap(); + let dt_now = dt_pin.is_low().unwrap(); + + match rotary.update(clk_now, dt_now) { + RotaryResponse::UP => { + target_temp += 1; + rotary_input_allowed = false; + last_rotary = tick; + // println!("Rotary up"); + } + RotaryResponse::DOWN => { + target_temp -= 1; + rotary_input_allowed = false; + last_rotary = tick; + // println!("Rotary dn"); + } + _ => () + } + + } + let samples = adc_fifo.len(); if samples > 4 { @@ -141,7 +192,7 @@ fn main() -> ! { tick = tick.wrapping_add(1); // tweak this magic number for lower/higher fps - if tick.abs_diff(last_gfx_push) < 100_000 { + if tick.abs_diff(last_gfx_push) < CYCLES_PER_FRAME { continue; } last_gfx_push = tick; @@ -180,7 +231,7 @@ fn main() -> ! { let adc_val = (adc_avg / accepted_samps) as u16; - println!("ADC {=u16}, samps {}", adc_val, accepted_samps); + // println!("ADC {=u16}, samps {}", adc_val, accepted_samps); let deg = match adc_val { ..ZERO_DEG_C => 0f32, @@ -194,26 +245,32 @@ fn main() -> ! { } }; - let deg = (deg * 10.) as u32; - let mut itoa_result = itoa(deg); + const START_Y:u8 = 8; + gfx_buf.draw_string(10, START_Y, "Probe temp"); + + let probe_temp = (deg * 10.) as u32; + + let mut itoa_result = itoa(probe_temp); + let deg_str = itoa_result.as_degrees_decicentigrade_str(); - let deg_buf = &mut itoa_result.buf; - for i in 1..10usize { - deg_buf[i-1] = deg_buf[i]; - } + // println!("tenths of a deg {}, deg {}", probe_temp, deg_str); + gfx_buf.draw_textfield(10, START_Y+12, 100, 60, deg_str); - deg_buf[9] = b'.'; - itoa_result.len += 1; + gfx_buf.draw_string(10, START_Y+30, "Target Temp"); - gfx_buf.draw_string(10, 10, "Probe temp"); - let deg_str = itoa_result.as_str(); - - println!("tenths of a deg {}, deg {}", deg, deg_str); - gfx_buf.draw_textfield(10, 22, 100, 60, deg_str); + gate_pin.set_state(match target_temp > probe_temp { + true => rp2040_hal::gpio::PinState::High, + false => rp2040_hal::gpio::PinState::Low + }).unwrap(); + // dancing sprite just for heartbeat visibility gfx_buf.sprites[0].y = 40 + ((test_val as i32) / 4) % 2; + itoa_result = itoa(target_temp); + let deg_str = itoa_result.as_degrees_decicentigrade_str(); + gfx_buf.draw_string(10, START_Y+42, deg_str); + gfx_buf.redraw(); sh1106_dev.blit_framebuffer(&mut i2c, &mut gfx_buf); @@ -221,7 +278,6 @@ fn main() -> ! { } } - // Can't return str without a ref to a lifetime somewhere else, // so instead return a buffer of the maximum i32 length pub struct ItoaResult { @@ -242,6 +298,18 @@ impl ItoaResult { pub fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[11usize.checked_sub(self.len).unwrap()..]).unwrap() } + + pub fn as_degrees_decicentigrade_str(&mut self) -> &str { + let itoa_buf = &mut self.buf; + for i in 1..10usize { + itoa_buf[i-1] = itoa_buf[i]; + } + + itoa_buf[9] = b'.'; + self.len += 1; + + self.as_str() + } } pub fn itoa(mut value: u32) -> ItoaResult { diff --git a/src/rotary.rs b/src/rotary.rs new file mode 100644 index 0000000..99cd305 --- /dev/null +++ b/src/rotary.rs @@ -0,0 +1,88 @@ +#[derive(Clone, Default)] +pub struct RotaryEnc { + state: RotaryEncState, + wait_for_reset: bool, + primed: bool +} + +impl RotaryEnc { + pub fn update(&mut self, clk: bool, dt: bool) -> RotaryResponse { + + let state_now = RotaryEncState::from_pin_states(clk, dt); + + if state_now == self.state { + return RotaryResponse::NOTHING; + } + + let response = match state_now { + RotaryEncState::BOTH_OFF => { + self.primed = false; + self.wait_for_reset = false; + RotaryResponse::NOTHING + } + RotaryEncState::CLK_ON | RotaryEncState::DT_ON => { + match (self.wait_for_reset, self.state) { + (false, RotaryEncState::BOTH_OFF) => { + self.primed = true; + } + _ => () + } + RotaryResponse::NOTHING + }, + RotaryEncState::BOTH_ON => { + match (self.primed, self.state) { + (true, RotaryEncState::CLK_ON) => { + self.primed = false; + RotaryResponse::UP + } + (true, RotaryEncState::DT_ON) => { + self.primed = false; + RotaryResponse::DOWN + } + _ => { + self.primed = false; + self.wait_for_reset = true; + RotaryResponse::NOTHING + } + } + } + }; + + self.state = state_now; + + response + } +} + +pub enum RotaryResponse { + NOTHING, + UP, + DOWN +} + +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum RotaryEncState { + #[default] + BOTH_OFF, + CLK_ON, + DT_ON, + BOTH_ON +} + +impl RotaryEncState { + pub fn from_pin_states(clk: bool, dt: bool) -> Self { + if clk && dt { + return Self::BOTH_ON; + } + + if clk { + return Self::CLK_ON; + } + + if dt { + return Self::DT_ON; + } + + Self::BOTH_OFF + } +} \ No newline at end of file