first version, first embedded lib, please help.

This commit is contained in:
puckoprutt 2026-07-12 03:12:42 +02:00
commit ce75a8f890
12 changed files with 1740 additions and 0 deletions

1013
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

30
Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "led-button"
version = "0.1.0"
edition = "2024"
[profile.dev]
opt-level = 's' # default is opt-level = '0', but that makes very verbose machine code
codegen-units = 1 # trade compile speed for slightly better optimisations
# cargo build/run --release
[profile.release]
opt-level = 's' # default is opt-level = '3', but that makes quite verbose machine code
codegen-units = 1 # trade compile speed for slightly better optimisations
lto = 'fat' # Use Link Time Optimisations to further inline things across crates
debug = 2 # Leave the debug symbols in (default is no debug info)
strip = true # strip stupid stuff.
[dependencies]
cortex-m = { version = "0.7.7" }
cortex-m-rt = "0.7.5"
critical-section = { version = "1.2.0", features = ["restore-state-u8"] }
embedded-hal = "1.0.0"
rp235x-hal = { version = "0.4.0", features = ["rt", "critical-section-impl"] }
semihosting = "0.1.25"
[[bin]]
name = "led-button-example"
path = "src/example/led_button.rs"
test = false
bench = false

0
README.md Normal file
View File

27
build.rs Normal file
View File

@ -0,0 +1,27 @@
//! Set up linker scripts for the rp235x-hal examples
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put the linker script somewhere the linker can find it
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display());
// The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which
// is what we specify in `.cargo/config.toml` for Arm builds
let memory_x = include_bytes!("memory.x");
let mut f = File::create(out.join("memory.x")).unwrap();
f.write_all(memory_x).unwrap();
println!("cargo:rerun-if-changed=memory.x");
// The file `rp235x_riscv.x` is what we specify in `.cargo/config.toml` for
// RISC-V builds
let rp235x_riscv_x = include_bytes!("rp235x_riscv.x");
let mut f = File::create(out.join("rp235x_riscv.x")).unwrap();
f.write_all(rp235x_riscv_x).unwrap();
println!("cargo:rerun-if-changed=rp235x_riscv.x");
println!("cargo:rerun-if-changed=build.rs");
}

25
src/button_config.rs Normal file
View File

@ -0,0 +1,25 @@
//! Button config
use super::PullConfig;
/// The configuration of a simple button.
pub struct ButtonConfig {
/// fn_refresh * pressing_time = how many loops to trigger ButtonState::Pressed
pub pressing_time : f32,
/// fn_refresh * longpressing_time = how many loops to trigger ButtonState::LongPress
pub longpressing_time : f32,
/// if the button is in pull-up/down configuration.
pub pull_configuration : PullConfig
}
impl Default for ButtonConfig {
/// some random default values.
fn default() -> Self {
Self {
pressing_time : 0.25,
longpressing_time : 1.77,
pull_configuration : PullConfig::PullUp,
}
}
}

13
src/button_state.rs Normal file
View File

@ -0,0 +1,13 @@
//! contains the enum ButtonState.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ButtonState {
NotPressed,
Released,
LongReleased,
Flank,
LongFlank,
Pressing,
Pressed,
LongPress,
}

141
src/button_stateful.rs Normal file
View File

@ -0,0 +1,141 @@
//! # A stateful button
//! -------------------
//! has counter and poll that can be used to avoid contact bounce.
//! very very nice, much wow.
use embedded_hal::digital::InputPin;
use super::button_state::ButtonState;
use super::button_config::ButtonConfig;
use super::PullConfig;
/// A stateful input pin represented as a button.
pub struct StatefulButton<IP> {
/// The pin from hal denoted as IP for Input Pin.
pub pin : IP,
/// ButtonConfig struct with a few options like pull-configuration.
pub config : ButtonConfig,
/// Current state of the button
state : ButtonState,
/// The previous state of the button
last_state : ButtonState,
/// counter for how many loops the button have been pressed.
pub counter : u32,
/// Threshold for triggering long pressed.
long_press_th : u32,
/// Threshold for triggering pressed.
pressing_th : u32
}
impl<IP: InputPin> StatefulButton<IP> {
/// Creates a stateful button.
///
/// ## Parameters:
/// - button_pin : IP
/// - An input pin from embedded-hal crate.
/// - config : ButtonConfig
/// - a config from this crate (see led_button::button_config::ButtonConfig)
/// - fn_refresh : f32
/// - function refresh, lets say your program runs the function 10.000.000 times a seconds then thats a good value to use here.
///
/// # Examples
/// ```
/// use led_button::button_stateful::StatefulButton;
/// use led_button::button_config::ButtonConfig;
///
/// // remember to always do a weird dance..
///
/// let button = StatefulButton::create(
/// pins.gpio21.into_pull_up_input(),
/// ButtonConfig::default(),
/// 274000.1
/// );
/// ```
pub fn create(button_pin: IP, config: ButtonConfig, fn_refresh: f32) -> Self {
let lpt = config.longpressing_time;
let pt = config.pressing_time;
Self {
pin : button_pin,
config : config,
long_press_th : (fn_refresh * lpt) as u32,
pressing_th : (fn_refresh * pt) as u32,
state : ButtonState::NotPressed,
last_state : ButtonState::NotPressed,
counter : 0,
}
}
/// checks if button is in active state.
pub fn is_pressed(&mut self) -> u8 {
match (&self.config.pull_configuration, self.pin.is_low()) {
(PullConfig::PullUp, Ok(true)) => 1,
(PullConfig::PullDown, Ok(false)) => 1,
_ => 0
}
}
/// trigger a state, in case you want to.
pub fn trigger_state(&mut self, state: ButtonState) {
self.last_state = self.state;
self.state = state;
}
/// polling logic will increase self.counter by one each time you call this function and the button is pressed.
/// if the button is released and poll is called the counter is reset.
/// will also set internal state of the button for a press, long press, not pressed and pressing.
pub fn poll(&mut self) {
self.last_state = self.state;
if self.is_pressed() > 0 {
self.counter = self.counter.wrapping_add(1);
if self.counter >= self.long_press_th {
self.state = ButtonState::LongPress;
}
else if self.counter >= self.pressing_th {
self.state = ButtonState::Pressed;
}
else {
self.state = ButtonState::Pressing;
}
}
else {
self.reset();
}
}
/// read the current state and previous state and return one of the following states.
/// - NotPressed
/// - Released
/// - LongReleased
/// - Pressing
/// - Flank
/// - Pressed
/// - LongFlank
/// - LongPress
pub fn read(&mut self) -> ButtonState {
match (self.state, self.last_state) {
(ButtonState::NotPressed, ButtonState::NotPressed) => ButtonState::NotPressed,
(ButtonState::NotPressed, ButtonState::Pressed) => ButtonState::Released,
(ButtonState::NotPressed, ButtonState::Pressing) => ButtonState::Released,
(ButtonState::NotPressed, ButtonState::LongPress) => ButtonState::LongReleased,
(ButtonState::Pressing, _) => ButtonState::Pressing,
(ButtonState::Pressed, ButtonState::Pressing) => ButtonState::Flank,
(ButtonState::Pressed, ButtonState::Pressed) => ButtonState::Pressed,
(ButtonState::LongPress, ButtonState::Pressed) => ButtonState::LongFlank,
(ButtonState::LongPress, ButtonState::LongPress) => ButtonState::LongPress,
_ => ButtonState::NotPressed
}
}
/// resets the counter and set state and previous state to NotPressed.
pub fn reset(&mut self) {
self.state = ButtonState::NotPressed;
self.last_state = ButtonState::NotPressed;
self.counter = 0;
}
}

73
src/errors.rs Normal file
View File

@ -0,0 +1,73 @@
use core::{
fmt,
//convert::{Infallible},
};
//#[cfg(feature = "defmt-03")]
//use crate::defmt;
/*
pub trait Error: fmt::Debug {
/// Convert error to a generic error kind
///
/// By using this method, errors freely defined by HAL implementations
/// can be converted to a set of generic errors upon which generic
/// code can act.
fn kind(&self) -> ErrorKind;
}
impl Error for Infallible {
fn kind(&self) -> ErrorKind {
match *self {}
}
}
*/
/// Error kind.
///
/// This represents a common set of operation errors. HAL implementations are
/// free to define more specific or additional error types. However, by providing
/// a mapping to these common errors, generic code can still react to them.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
//#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[non_exhaustive]
pub enum ErrorKind {
LedCouldNotTurnPinLow,
LedCouldNotTurnPinHigh,
ButtonSomething
}
impl fmt::Display for ErrorKind {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LedCouldNotTurnPinLow => write!(
f,
"[puckoprutt error] <- could not turn on led."
),
Self::LedCouldNotTurnPinHigh => write!(
f,
"[puckoprutt error] <- could not turn off led."
),
Self::ButtonSomething => write!(
f,
"[placeholder]"
)
}
}
}
/*
/// Error type trait.
///
/// This just defines the error type, to be used by the other traits.
pub trait ErrorType {
type Error: Error;
}
impl<T: ErrorType + ?Sized> ErrorType for &T {
type Error = T::Error;
}
impl<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}
*/

126
src/example/led_button.rs Normal file
View File

@ -0,0 +1,126 @@
//!
#![no_std]
#![no_main]
use core::cell::RefCell;
use core::panic::PanicInfo;
use critical_section::Mutex;
use cortex_m::asm;
use cortex_m::peripheral::NVIC;
use cortex_m_rt::entry;
use led_button::{LedButton, PullConfig};
use led_button::led_state::LedState;
use led_button::button_config::ButtonConfig;
use rp235x_hal::{
self as hal,
block::ImageDef,
pac::{Peripherals, interrupt},
gpio::{
Interrupt,
Pin, FunctionSio,
PullDown, PullUp,
SioInput, SioOutput
}
};
type LedPin1 = Pin<hal::gpio::bank0::Gpio15, FunctionSio<SioOutput>, PullDown>;
type ButtonPin1 = Pin<hal::gpio::bank0::Gpio16, FunctionSio<SioInput>, PullUp>;
type LedPin2 = Pin<hal::gpio::bank0::Gpio13, FunctionSio<SioOutput>, PullDown>;
type ButtonPin2 = Pin<hal::gpio::bank0::Gpio21, FunctionSio<SioInput>, PullUp>;
static GLOBAL_LED_BTN_1 : Mutex<RefCell<Option<LedButton<LedPin1, ButtonPin1>>>> = Mutex::new(RefCell::new(None));
static GLOBAL_LED_BTN_2 : Mutex<RefCell<Option<LedButton<LedPin2, ButtonPin2>>>> = Mutex::new(RefCell::new(None));
pub const XOSC_CRYSTAL_FREQ : u32 = 12000000;
#[interrupt]
#[allow(non_snake_case)]
fn IO_IRQ_BANK0() {
critical_section::with(|cs| {
let mut button1_ref = GLOBAL_LED_BTN_1.borrow(cs).borrow_mut();
let mut button2_ref = GLOBAL_LED_BTN_2.borrow(cs).borrow_mut();
if let (Some(button1), Some(button2)) = (button1_ref.as_mut(), button2_ref.as_mut()) {
if button1.button.pin.interrupt_status(Interrupt::EdgeLow) {
let _ = button2.toggle();
button1.button.pin.clear_interrupt(Interrupt::EdgeLow);
}
if button2.button.pin.interrupt_status(Interrupt::EdgeLow) {
let _ = button1.toggle();
button2.button.pin.clear_interrupt(Interrupt::EdgeLow);
}
}
});
}
#[entry]
fn main() -> ! {
let mut pac = Peripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// clock and timers
let _clocks = hal::clocks::init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog
).ok().unwrap();
// init gpio
let sio = hal::Sio::new(pac.SIO);
let pins = hal::gpio::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS
);
let led1_pin = pins.gpio15.into_push_pull_output();
let button1_pin = pins.gpio16.into_pull_up_input();
let led2_pin = pins.gpio13.into_push_pull_output();
let button2_pin = pins.gpio21.into_pull_up_input();
button1_pin.set_interrupt_enabled(Interrupt::EdgeLow, true);
button2_pin.set_interrupt_enabled(Interrupt::EdgeLow, true);
let button1 = LedButton::create(
led1_pin, LedState::Off, PullConfig::PullDown, button1_pin, ButtonConfig::default(), 274000.0
);
let button2 = LedButton::create(
led2_pin, LedState::Off, PullConfig::PullDown, button2_pin, ButtonConfig::default(), 274000.0
);
critical_section::with(|cs| {
GLOBAL_LED_BTN_1.borrow(cs).replace(Some(button1));
GLOBAL_LED_BTN_2.borrow(cs).replace(Some(button2));
});
unsafe {
NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0);
}
loop {
asm::wfi();
}
}
#[unsafe(link_section = ".start_block")]
#[used]
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop { asm::udf() }
}
pub fn exit() -> ! {
semihosting::process::exit(0);
}
#[allow(non_snake_case)]
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
semihosting::process::exit(1);
}

55
src/led_state.rs Normal file
View File

@ -0,0 +1,55 @@
//! LedState is pretty much a bool.
use core::ops::Not;
/// Digital output led state.
///
/// Conversion from `bool` and logical negation are also implemented
/// for this type.
/// ```rust
/// # use led_button::let_state::LedState;
/// let state = LedState::from(false);
/// assert_eq!(state, LedState::Low);
/// assert_eq!(!state, LedState::High);
///
/// let state = LedState::from(LedState::On);
/// assert_eq!(state, false);
/// assert_eq!(!state, true);
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LedState {
On,
Off
}
impl From<bool> for LedState {
#[inline]
fn from(on: bool) -> Self {
match on {
true => LedState::On,
false => LedState::Off
}
}
}
impl From<LedState> for bool {
#[inline]
fn from(value: LedState) -> bool {
match value {
LedState::Off => false,
LedState::On => true
}
}
}
impl Not for LedState {
type Output = LedState;
#[inline]
fn not(self) -> Self::Output {
match self {
LedState::Off => LedState::On,
LedState::On => LedState::Off
}
}
}

111
src/led_stateful.rs Normal file
View File

@ -0,0 +1,111 @@
//! # Stateful LED
//! --------------
//! cool stuff.
//! very much wow factor.
use embedded_hal::digital::OutputPin;
use super::PullConfig;
use super::led_state::LedState;
use super::errors::ErrorKind;
/// A stateful output pin representing a LED.
pub struct StatefulLed<OP> {
/// The pin from hal denoted as OP for Output Pin.
pub pin : OP,
/// The current state of the led, can be On or Off.
pub current_state : LedState,
/// Resistor pull-up/down configuration. is the led "on" by a low or high signal.
pull_configuration : PullConfig,
}
impl<OP: OutputPin> StatefulLed<OP> {
/// Creates a StatefulLed
///
/// # Examples
/// ```
/// use led_button::led_stateful::StatefulLed;
/// use led_button::led_state::LedState;
/// use led_button::button_config::Buttonconfig;
/// use led_button::PullConfig;
///
/// // ... init your hal and do a weird a dance..
///
/// let led1_pin = pins.gpio15.into_push_pull_output();
/// let button1_pin = pins.gpio16.into_pull_up_input();
///
/// let button1 = LedButton::create(
/// led1_pin,
/// LedState::Off,
/// PullConfig::PullDown,
/// button1_pin,
/// ButtonConfig::default(),
/// 274000.0
/// );
///
/// // ... continue doing a weird a dance.
/// ```
///
pub fn create(led_pin: OP, pull_config: PullConfig, start_state: LedState) -> Self {
Self {
pin : led_pin,
pull_configuration : pull_config,
current_state : start_state,
}
}
fn set_low(&mut self) -> Result<(), ErrorKind>{
match self.pin.set_low() {
Ok(()) => Ok(()),
Err(_e) => Err(ErrorKind::LedCouldNotTurnPinLow)
}
}
fn set_high(&mut self) -> Result<(), ErrorKind>{
match self.pin.set_high() {
Ok(()) => Ok(()),
Err(_e) => Err(ErrorKind::LedCouldNotTurnPinHigh)
}
}
/// will trigger 'on' state depending on your pull configuration.
pub fn on(&mut self) -> Result<(), ErrorKind> {
match self.set_state(LedState::On) {
Ok(()) => {
self.current_state = LedState::On;
Ok(())
},
Err(e) => Err(e)
}
}
/// will trigger 'off' state depending on your pull configuration.
pub fn off(&mut self) -> Result<(), ErrorKind>{
match self.set_state(LedState::Off) {
Ok(()) => {
self.current_state = LedState::Off;
Ok(())
},
Err(e) => Err(e)
}
}
/// will trigger the 'on/off' state thats not in use.
pub fn toggle(&mut self) -> Result<(), ErrorKind>{
match self.current_state {
LedState::On => self.off(),
LedState::Off => self.on(),
}
}
/// set the state that was given in the 'state' argument.
pub fn set_state(&mut self, state: LedState) -> Result<(), ErrorKind> {
match (state, self.pull_configuration) {
(LedState::Off, PullConfig::PullDown) => { self.set_low() }
(LedState::On, PullConfig::PullDown) => { self.set_high() }
(LedState::Off, PullConfig::PullUp) => { self.set_high() }
(LedState::On, PullConfig::PullUp) => { self.set_low() }
}
}
}

126
src/lib.rs Normal file
View File

@ -0,0 +1,126 @@
//! Hej och välkommen till denna skit.
#![no_std]
mod errors;
pub mod button_state;
pub mod button_config;
pub mod button_stateful;
pub mod led_state;
pub mod led_stateful;
use embedded_hal::digital::{InputPin, OutputPin};
use led_state::LedState;
use led_stateful::StatefulLed;
use button_stateful::StatefulButton;
use button_config::ButtonConfig;
use crate::button_state::ButtonState;
/// Pull configuration, used by both StatefulButton and StatefulLed.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum PullConfig {
PullUp,
PullDown
}
/// Represents a button with built in led.
pub struct LedButton<OP, IP> {
/// the pin from mcu to LED
pub led : StatefulLed<OP>,
/// the pin from mcu to button
pub button : StatefulButton<IP>,
/// the state of the button, always initialised to NotPressed.
pub state : ButtonState
}
impl<OP: OutputPin, IP: InputPin> LedButton<OP, IP> {
/// Creates a StatefulLed
///
/// # Examples
/// ```
/// use led_button::led_stateful::StatefulLed;
/// use led_button::led_state::LedState;
/// use led_button::button_config::Buttonconfig;
/// use led_button::PullConfig;
///
/// // ... init your hal and do a weird a dance..
///
/// let led1_pin = pins.gpio15.into_push_pull_output();
/// let button1_pin = pins.gpio16.into_pull_up_input();
///
/// let button1 = LedButton::create(
/// led1_pin,
/// LedState::Off,
/// PullConfig::PullDown,
/// button1_pin,
/// ButtonConfig::default(),
/// 274000.0
/// );
///
/// // ... continue doing a weird a dance.
/// ```
///
pub fn create(led_pin: OP, led_state: LedState, led_pullconf: PullConfig, button_pin: IP, button_config: ButtonConfig, fn_refresh: f32 ) -> Self {
Self {
led : StatefulLed::create(led_pin, led_pullconf, led_state),
button : StatefulButton::create(button_pin, button_config, fn_refresh),
state : ButtonState::NotPressed
}
}
/// get the current state of the LED.
pub fn led_state(self) -> LedState {
self.led.current_state
}
/// set the LED to "On"-state.
pub fn on(&mut self) {
let _ = self.led.on();
}
/// set the LED to "Off"-state.
pub fn off(&mut self) {
let _ = self.led.off();
}
/// toggle the LED.
pub fn toggle(&mut self) {
let _ = self.led.toggle();
}
/// the current counter value of the button.
pub fn counter(self) -> u32 {
self.button.counter
}
/// polls the button and read the current state
pub fn poll_and_read(&mut self, led_while_pressed: bool) -> ButtonState {
self.button.poll();
if led_while_pressed { let _ = self.led.on(); }
match self.button.read() {
ButtonState::Released => {
if led_while_pressed { let _ = self.led.off(); }
self.state = ButtonState::NotPressed;
ButtonState::Released
}
ButtonState::LongReleased => {
if led_while_pressed { let _ = self.led.off(); }
self.state = ButtonState::NotPressed;
ButtonState::LongReleased
}
ButtonState::Flank => {
self.state = ButtonState::Pressed;
ButtonState::Flank
}
ButtonState::LongFlank => {
self.state = ButtonState::LongPress;
ButtonState::LongFlank
}
n => {
n
}
}
}
}