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

Open
wants to merge 14 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
168 changes: 150 additions & 18 deletions esp-hal/src/rmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,15 @@ 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;

if div > u8::MAX as u32 {
return Err(Error::UnreachableTargetFrequency);
}

self::chip_specific::configure_clock(div);

let clock_divider =
clock_divider_solver::solve(src_clock.to_Hz(), frequency.to_Hz(), 256, 0b11111)
.ok_or(Error::UnreachableTargetFrequency)?;

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 +1589,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 +1601,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 +1616,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 +2414,139 @@ mod chip_specific {
pub(crate) use impl_rx_channel;
pub(crate) use impl_tx_channel;
}

#[cfg(not(any(esp32, esp32s2)))]
mod clock_divider_solver {
use core::{cmp::Ordering, hint::unreachable_unchecked};

// 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,
}

pub fn solve(
source: u32,
target: u32,
integral_max: u32,
fractional_max: u32,
) -> Option<ClockDivider> {
if target > source {
return None;
}

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

let remainder = source % target;
if remainder == 0 {
return Some(ClockDivider {
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(ClockDivider {
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 half part
};
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?
let err_l = m.sub(&l);
let err_h = h.sub(&m);
let m = if err_l.lt(&err_h) { l } else { h };
return Some(ClockDivider {
div_num: quotient,
div_b: m.numerator,
div_a: m.denominator,
});
}

match m.cmp(&target_frac) {
Ordering::Less => {
l = m;
}
Ordering::Greater => {
h = m;
}
Ordering::Equal => {
// SAFETY: Before search Stern-Brocot Tree,
// we ensures that the simplest fractional
// form of target_frac has a greater denominator
// than the fractial_max. Therefore, in searches
// within a smaller range, there will never be a
// situation where M == targete_frac.
unsafe { unreachable_unchecked() }
}
}
}
}

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

impl Fraction {
fn cmp(&self, other: &Self) -> Ordering {
let a = self.numerator * other.denominator;
let b = self.denominator * other.numerator;
a.cmp(&b)
}

const fn lt(&self, other: &Self) -> bool {
let a = self.numerator * other.denominator;
let b = self.denominator * other.numerator;
a < b
}

const fn sub(&self, rhs: &Self) -> Self {
Self {
numerator: self.numerator * rhs.denominator - self.denominator * rhs.numerator,
denominator: self.denominator * rhs.denominator,
}
}
}
}