pub mod register {
pub const MODE1: u8 = 0x00;
pub const MODE2: u8 = 0x01;
pub const SUBADR1: u8 = 0x02;
pub const SUBADR2: u8 = 0x03;
pub const SUBADR3: u8 = 0x04;
pub const ALLCALL_ADDR: u8 = 0x05;
pub const LED0_ON_L: u8 = 0x06;
pub const ALL_LED_ON_L: u8 = 0xFA;
pub const ALL_LED_ON_H: u8 = 0xFB;
pub const ALL_LED_OFF_L: u8 = 0xFC;
pub const ALL_LED_OFF_H: u8 = 0xFD;
pub const PRE_SCALE: u8 = 0xFE;
}
pub mod mode1 {
pub const RESTART: u8 = 0x80;
pub const EXTCLK: u8 = 0x40;
pub const AUTO_INCREMENT: u8 = 0x20;
pub const SLEEP: u8 = 0x10;
pub const SUB1: u8 = 0x08;
pub const SUB2: u8 = 0x04;
pub const SUB3: u8 = 0x02;
pub const ALLCALL: u8 = 0x01;
}
pub const INTERNAL_OSC_HZ: u32 = 25_000_000;
pub const CHANNELS: u8 = 16;
pub const COUNTS: u16 = 4096;
pub const PRE_SCALE_RESET: u8 = 0x1E;
pub const MODE1_RESET: u8 = 0x11;
pub fn channel_register(channel: u8) -> u8 {
let channel = if channel >= CHANNELS {
CHANNELS - 1
} else {
channel
};
register::LED0_ON_L + 4 * channel
}
pub fn prescale_for_frequency(update_rate_hz: u32, osc_hz: u32) -> u8 {
let divisor = COUNTS as u32 * update_rate_hz.max(1);
let rounded = (osc_hz + divisor / 2) / divisor;
rounded.saturating_sub(1).clamp(3, 255) as u8
}
pub fn frequency_for_prescale(prescale: u8, osc_hz: u32) -> f32 {
osc_hz as f32 / (COUNTS as f32 * (prescale as f32 + 1.0))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Pwm {
on: u16,
off: u16,
}
impl Pwm {
pub fn from_counts(on: u16, off: u16) -> Pwm {
Pwm {
on: on & 0x0FFF,
off: off & 0x0FFF,
}
}
pub fn duty(off: u16) -> Pwm {
Pwm::from_counts(0, off)
}
pub fn servo(pulse_micros: u32, update_rate_hz: u32) -> Pwm {
let counts = (pulse_micros as u64 * COUNTS as u64 * update_rate_hz as u64) / 1_000_000;
Pwm::duty(counts.min(COUNTS as u64 - 1) as u16)
}
pub fn full_on() -> Pwm {
Pwm { on: 0x1000, off: 0 }
}
pub fn full_off() -> Pwm {
Pwm { on: 0, off: 0x1000 }
}
pub fn bytes(self) -> [u8; 4] {
[
self.on as u8,
(self.on >> 8) as u8,
self.off as u8,
(self.off >> 8) as u8,
]
}
pub fn from_bytes(bytes: &[u8; 4]) -> Pwm {
Pwm {
on: u16::from_le_bytes([bytes[0], bytes[1]]),
off: u16::from_le_bytes([bytes[2], bytes[3]]),
}
}
pub fn on(self) -> u16 {
self.on
}
pub fn off(self) -> u16 {
self.off
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prescale_matches_the_datasheet_example() {
assert_eq!(prescale_for_frequency(200, INTERNAL_OSC_HZ), 0x1E);
assert_eq!(
prescale_for_frequency(200, INTERNAL_OSC_HZ),
PRE_SCALE_RESET
);
assert_eq!(prescale_for_frequency(1526, INTERNAL_OSC_HZ), 3);
assert_eq!(prescale_for_frequency(100_000, INTERNAL_OSC_HZ), 3);
assert_eq!(prescale_for_frequency(50, INTERNAL_OSC_HZ), 0x79);
}
#[test]
fn frequency_and_prescale_round_trip() {
for prescale in [3u8, 30, 0x79, 255] {
let freq = frequency_for_prescale(prescale, INTERNAL_OSC_HZ);
assert_eq!(
prescale_for_frequency(freq as u32, INTERNAL_OSC_HZ),
prescale
);
}
}
#[test]
fn channel_registers_are_four_apart() {
assert_eq!(channel_register(0), 0x06);
assert_eq!(channel_register(1), 0x0A);
assert_eq!(channel_register(15), 0x42);
assert_eq!(channel_register(20), 0x42);
}
#[test]
fn pwm_bytes_pack_counts_little_endian() {
let pwm = Pwm::from_counts(0x199, 0xCCC);
assert_eq!(pwm.bytes(), [0x99, 0x01, 0xCC, 0x0C]);
}
#[test]
fn full_on_and_full_off_set_the_flag_bit() {
assert_eq!(Pwm::full_on().bytes(), [0x00, 0x10, 0x00, 0x00]);
assert_eq!(Pwm::full_off().bytes(), [0x00, 0x00, 0x00, 0x10]);
}
#[test]
fn servo_midpoint_is_a_centred_pulse() {
assert_eq!(Pwm::servo(1500, 50), Pwm::duty(307));
assert_eq!(Pwm::servo(1000, 50), Pwm::duty(204));
assert_eq!(Pwm::servo(2000, 50), Pwm::duty(409));
}
#[test]
fn a_setting_reads_back_from_the_registers_it_writes() {
for setting in [
Pwm::servo(1500, 50),
Pwm::duty(0),
Pwm::duty(2048),
Pwm::full_on(),
Pwm::full_off(),
Pwm::from_counts(410, 1229),
] {
assert_eq!(Pwm::from_bytes(&setting.bytes()), setting);
}
assert_eq!(Pwm::servo(1500, 50).off(), 307);
assert_eq!(Pwm::servo(1500, 50).on(), 0);
}
}