Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fractional divider support for RMT hal #2127

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Previously unavailable memory is available via `.dram2_uninit` section (#2079)
- You can now use `Input`, `Output`, `OutputOpenDrain` and `Flex` pins as EXTI and RTCIO wakeup sources (#2095)
- Added `Rtc::set_current_time` to allow setting RTC time, and `Rtc::current_time` to getting RTC time while taking into account boot time (#1883)
- Added RMT fractional divider support. (#2127)
- Added APIs to allow connecting signals through the GPIO matrix. (#2128)

### Changed
Expand Down
157 changes: 141 additions & 16 deletions esp-hal/src/rmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,20 +247,20 @@ where

#[cfg(not(any(esp32, esp32s2)))]
fn configure_clock(&self, frequency: HertzU32) -> Result<(), Error> {
let src_clock = crate::soc::constants::RMT_CLOCK_SRC_FREQ;

if frequency > src_clock {
return Err(Error::UnreachableTargetFrequency);
}

let div = (src_clock / frequency) - 1;
use clock_divider_solver::ClockDivider;

if div > u8::MAX as u32 {
let src_clock = crate::soc::constants::RMT_CLOCK_SRC_FREQ;
let Some(clock_divider) =
ClockDivider::from_frequence(src_clock.to_Hz(), frequency.to_Hz(), 256, 0b11111)
else {
return Err(Error::UnreachableTargetFrequency);
}

self::chip_specific::configure_clock(div);
};

self::chip_specific::configure_clock(
clock_divider.div_num - 1,
clock_divider.div_a,
clock_divider.div_b,
);
Ok(())
}
}
Expand Down Expand Up @@ -1593,7 +1593,7 @@ mod private {

#[cfg(not(any(esp32, esp32s2)))]
mod chip_specific {
pub fn configure_clock(div: u32) {
pub fn configure_clock(div: u32, div_a: u32, div_b: u32) {
#[cfg(not(pcr))]
{
let rmt = unsafe { &*crate::peripherals::RMT::PTR };
Expand All @@ -1605,9 +1605,9 @@ mod chip_specific {
.sclk_div_num()
.bits(div as u8)
.sclk_div_a()
.bits(0)
.bits(div_a as u8)
.sclk_div_b()
.bits(0)
.bits(div_b as u8)
.apb_fifo_mask()
.set_bit()
});
Expand All @@ -1620,9 +1620,9 @@ mod chip_specific {
w.sclk_div_num()
.bits(div as u8)
.sclk_div_a()
.bits(0)
.bits(div_a as u8)
.sclk_div_b()
.bits(0)
.bits(div_b as u8)
});

#[cfg(esp32c6)]
Expand Down Expand Up @@ -2418,3 +2418,128 @@ mod chip_specific {
pub(crate) use impl_rx_channel;
pub(crate) use impl_tx_channel;
}

#[cfg(not(any(esp32, esp32s2)))]
mod clock_divider_solver {
// Calculate the greatest common divisor of a and b
const fn gcd(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
(a, b) = (b, (a % b))
}
a
}

pub struct ClockDivider {
// Integral clock divider value.
pub div_num: u32,

// Fractional clock divider numerator value.
pub div_b: u32,

// Fractional clock divider denominator value.
pub div_a: u32,
}

impl ClockDivider {
pub const fn from_frequence(
Tnze marked this conversation as resolved.
Show resolved Hide resolved
source: u32,
target: u32,
integral_max: u32,
fractional_max: u32,
) -> Option<Self> {
if target > source {
return None;
}

let quotient = source / target;
if quotient > integral_max {
return None;
}

let remainder = source % target;
if remainder == 0 {
return Some(Self {
div_num: quotient,
div_b: 0,
div_a: 0,
});
}

let gcd = gcd(target, remainder);
let numerator = remainder / gcd;
let denominator = target / gcd;
if numerator <= fractional_max && denominator <= fractional_max {
return Some(Self {
div_num: quotient,
div_b: numerator,
div_a: denominator,
});
}

// Search Stern-Brocot Tree
let target_frac = Fraction {
numerator,
denominator,
};
let mut l = Fraction {
numerator: 0,
denominator: 1,
};
let mut h = Fraction {
numerator: 1,
denominator: 1, // We only search the left harf part
Tnze marked this conversation as resolved.
Show resolved Hide resolved
};
loop {
let m = Fraction {
numerator: l.numerator + h.numerator,
denominator: l.denominator + h.denominator,
};
if m.numerator > fractional_max || m.denominator > fractional_max {
// L and H, which is closer?

// M - L
let a = Fraction {
Tnze marked this conversation as resolved.
Show resolved Hide resolved
numerator: m.numerator * l.denominator - m.denominator * l.numerator,
denominator: m.denominator * l.denominator,
};
// H - M
let b = Fraction {
numerator: h.numerator * m.denominator - h.denominator * m.numerator,
denominator: h.denominator * m.denominator,
};

// compare a and b
let c = a.numerator * b.denominator;
let d = a.denominator * b.numerator;
let m = if c < d { l } else { h };
return Some(Self {
div_num: quotient,
div_b: m.numerator,
div_a: m.denominator,
});
}

// compare M and target_frac
let a = m.numerator * target_frac.denominator;
let b = m.denominator * target_frac.numerator;
if a > b {
h = m;
} else if a < b {
l = m;
} else {
// unreachable!();
Tnze marked this conversation as resolved.
Show resolved Hide resolved
return Some(Self {
div_num: quotient,
div_b: m.numerator,
div_a: m.denominator,
});
}
}
}
}

struct Fraction {
pub numerator: u32,
pub denominator: u32,
}
}
Loading