mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-18 14:04:48 +02:00
e54c756423
Rust (all 15 projects):
- Refactored overlength functions: format_counter, format_u8, format_f32_1,
format_u32_minimal, gpio_drive, read_sensor, poll_sensor, format_round_trip,
format_u32, prepare_write_buf, write_min_digits, write_temp, UartDriver::init,
init_spi, angle_to_pulse_us, compute_servo_level
- Added 200+ docstrings to test functions, mock structs, impl blocks
- Fixed pub static comments (//) to doc comments (///) in all main.rs files
- Fixed helper function ordering (helpers above callers)
- Fixed Fn(u32) -> FnMut(u32) bound in button poll_button
- Moved OneShot trait import from main.rs to board.rs in adc project
- Added unsafe {} blocks in flash unsafe fn bodies (Rust 2024 edition)
- Removed unused hal::Clock imports from pwm/servo main.rs
- All 15 projects build with zero errors and zero warnings
C Pico SDK (all 15 projects):
- Added docstrings to all public functions, macros, and static variables
- All 15 projects rebuilt with zero errors
Cleanup:
- Removed build/ and target/ directories from git tracking
- Added target/ to .gitignore
- Deleted temporary fix_rust_docs.py script
234 lines
6.7 KiB
Rust
234 lines
6.7 KiB
Rust
//! @file servo.rs
|
|
//! @brief Implementation of a simple SG90 servo driver (pure-logic helpers)
|
|
//! @author Kevin Thomas
|
|
//! @date 2025
|
|
//!
|
|
//! MIT License
|
|
//!
|
|
//! Copyright (c) 2025 Kevin Thomas
|
|
//!
|
|
//! Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
//! of this software and associated documentation files (the "Software"), to deal
|
|
//! in the Software without restriction, including without limitation the rights
|
|
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
//! copies of the Software, and to permit persons to whom the Software is
|
|
//! furnished to do so, subject to the following conditions:
|
|
//!
|
|
//! The above copyright notice and this permission notice shall be included in
|
|
//! all copies or substantial portions of the Software.
|
|
//!
|
|
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
//! SOFTWARE.
|
|
|
|
/// Default minimum pulse width in microseconds (0 degrees).
|
|
pub const SERVO_DEFAULT_MIN_US: u16 = 1000;
|
|
|
|
/// Default maximum pulse width in microseconds (180 degrees).
|
|
pub const SERVO_DEFAULT_MAX_US: u16 = 2000;
|
|
|
|
/// Default PWM wrap value for 50 Hz servo (20 000 - 1).
|
|
pub const SERVO_WRAP: u32 = 20000 - 1;
|
|
|
|
/// Default servo frequency in Hz.
|
|
pub const SERVO_HZ: f32 = 50.0;
|
|
|
|
/// Convert a pulse width in microseconds to a PWM counter level.
|
|
///
|
|
/// Uses the configured PWM wrap and servo frequency to map pulse time
|
|
/// into the channel compare value expected by the PWM hardware.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `pulse_us` - Pulse width in microseconds.
|
|
/// * `wrap` - PWM counter wrap value.
|
|
/// * `hz` - PWM frequency in Hz.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// PWM level suitable for the channel compare register.
|
|
pub fn pulse_us_to_level(pulse_us: u32, wrap: u32, hz: f32) -> u32 {
|
|
let period_us = 1_000_000.0f32 / hz;
|
|
let counts_per_us = (wrap + 1) as f32 / period_us;
|
|
(pulse_us as f32 * counts_per_us + 0.5f32) as u32
|
|
}
|
|
|
|
/// Clamp a pulse width to the valid servo range.
|
|
///
|
|
/// Values below min_us are raised to min_us; values above max_us are
|
|
/// lowered to max_us.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `pulse_us` - Raw pulse width in microseconds.
|
|
/// * `min_us` - Minimum allowed pulse width.
|
|
/// * `max_us` - Maximum allowed pulse width.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Clamped pulse width.
|
|
pub fn clamp_pulse_us(pulse_us: u16, min_us: u16, max_us: u16) -> u16 {
|
|
if pulse_us < min_us {
|
|
min_us
|
|
} else if pulse_us > max_us {
|
|
max_us
|
|
} else {
|
|
pulse_us
|
|
}
|
|
}
|
|
|
|
/// Clamp a floating-point angle to the valid servo range [0.0, 180.0].
|
|
fn clamp_degrees(degrees: f32) -> f32 {
|
|
if degrees < 0.0f32 {
|
|
0.0f32
|
|
} else if degrees > 180.0f32 {
|
|
180.0f32
|
|
} else {
|
|
degrees
|
|
}
|
|
}
|
|
|
|
/// Map a servo angle in degrees to a pulse width in microseconds.
|
|
///
|
|
/// Clamps degrees to [0, 180], then linearly maps to the pulse range.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `degrees` - Angle in degrees (0.0 to 180.0).
|
|
/// * `min_us` - Pulse width at 0 degrees.
|
|
/// * `max_us` - Pulse width at 180 degrees.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Pulse width in microseconds corresponding to the given angle.
|
|
pub fn angle_to_pulse_us(degrees: f32, min_us: u16, max_us: u16) -> u16 {
|
|
let d = clamp_degrees(degrees);
|
|
let ratio = d / 180.0f32;
|
|
let span = (max_us - min_us) as f32;
|
|
(min_us as f32 + ratio * span + 0.5f32) as u16
|
|
}
|
|
|
|
/// Compute the PWM clock divider for the servo frequency.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `sys_hz` - System clock frequency in Hz.
|
|
/// * `servo_hz` - Desired servo PWM frequency in Hz.
|
|
/// * `wrap` - PWM counter wrap value.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Clock divider value.
|
|
pub fn calc_clk_div(sys_hz: u32, servo_hz: f32, wrap: u32) -> f32 {
|
|
sys_hz as f32 / (servo_hz * (wrap + 1) as f32)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// Import all parent module items
|
|
use super::*;
|
|
|
|
/// Pulse us to level 1000us.
|
|
#[test]
|
|
fn pulse_us_to_level_1000us() {
|
|
let level = pulse_us_to_level(1000, SERVO_WRAP, SERVO_HZ);
|
|
assert_eq!(level, 1000);
|
|
}
|
|
|
|
/// Pulse us to level 2000us.
|
|
#[test]
|
|
fn pulse_us_to_level_2000us() {
|
|
let level = pulse_us_to_level(2000, SERVO_WRAP, SERVO_HZ);
|
|
assert_eq!(level, 2000);
|
|
}
|
|
|
|
/// Pulse us to level 1500us.
|
|
#[test]
|
|
fn pulse_us_to_level_1500us() {
|
|
let level = pulse_us_to_level(1500, SERVO_WRAP, SERVO_HZ);
|
|
assert_eq!(level, 1500);
|
|
}
|
|
|
|
/// Pulse us to level zero.
|
|
#[test]
|
|
fn pulse_us_to_level_zero() {
|
|
let level = pulse_us_to_level(0, SERVO_WRAP, SERVO_HZ);
|
|
assert_eq!(level, 0);
|
|
}
|
|
|
|
/// Clamp pulse us below min.
|
|
#[test]
|
|
fn clamp_pulse_us_below_min() {
|
|
assert_eq!(
|
|
clamp_pulse_us(500, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US),
|
|
1000
|
|
);
|
|
}
|
|
|
|
/// Clamp pulse us above max.
|
|
#[test]
|
|
fn clamp_pulse_us_above_max() {
|
|
assert_eq!(
|
|
clamp_pulse_us(3000, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US),
|
|
2000
|
|
);
|
|
}
|
|
|
|
/// Clamp pulse us within range.
|
|
#[test]
|
|
fn clamp_pulse_us_within_range() {
|
|
assert_eq!(
|
|
clamp_pulse_us(1500, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US),
|
|
1500
|
|
);
|
|
}
|
|
|
|
/// Angle to pulse us zero.
|
|
#[test]
|
|
fn angle_to_pulse_us_zero() {
|
|
let pulse = angle_to_pulse_us(0.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
|
|
assert_eq!(pulse, 1000);
|
|
}
|
|
|
|
/// Angle to pulse us 180.
|
|
#[test]
|
|
fn angle_to_pulse_us_180() {
|
|
let pulse = angle_to_pulse_us(180.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
|
|
assert_eq!(pulse, 2000);
|
|
}
|
|
|
|
/// Angle to pulse us 90.
|
|
#[test]
|
|
fn angle_to_pulse_us_90() {
|
|
let pulse = angle_to_pulse_us(90.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
|
|
assert_eq!(pulse, 1500);
|
|
}
|
|
|
|
/// Angle to pulse us clamped negative.
|
|
#[test]
|
|
fn angle_to_pulse_us_clamped_negative() {
|
|
let pulse = angle_to_pulse_us(-10.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
|
|
assert_eq!(pulse, 1000);
|
|
}
|
|
|
|
/// Angle to pulse us clamped above.
|
|
#[test]
|
|
fn angle_to_pulse_us_clamped_above() {
|
|
let pulse = angle_to_pulse_us(200.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
|
|
assert_eq!(pulse, 2000);
|
|
}
|
|
|
|
/// Calc clk div 150mhz.
|
|
#[test]
|
|
fn calc_clk_div_150mhz() {
|
|
let div = calc_clk_div(150_000_000, SERVO_HZ, SERVO_WRAP);
|
|
assert!((div - 150.0).abs() < 0.01);
|
|
}
|
|
}
|
|
|
|
// End of file
|