-
Notifications
You must be signed in to change notification settings - Fork 3
feat(hal): implement PWM with example #18
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| //! Pulse Width Modulation (PWM). | ||
|
|
||
| mod config; | ||
| mod driver; | ||
| mod instance; | ||
| mod pad; | ||
| mod pwm_ext; | ||
| mod register; | ||
|
|
||
| pub use config::*; | ||
| pub use driver::*; | ||
| pub use instance::*; | ||
| pub use pad::*; | ||
| pub use pwm_ext::PwmExt; | ||
| pub use register::*; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| use super::register::*; | ||
| use embedded_time::duration::Nanoseconds; | ||
| use embedded_time::rate::Hertz; | ||
|
|
||
| /// PWM action. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| pub struct Action { | ||
| /// Counts down and `TBCTR` = `CMPB` action. | ||
| pub cbd: ActionMode, | ||
| /// Counts up and `TBCTR` = `CMPA` action. | ||
| pub cbu: ActionMode, | ||
| /// Counts down and `TBCTR` = `CMPA` action. | ||
| pub cad: ActionMode, | ||
| /// Counts up and `TBCTR` = `CMPA` action. | ||
| pub cau: ActionMode, | ||
| /// `TBCTR` = `PRD` action. | ||
| pub prd: ActionMode, | ||
| /// `TBCTR` = 0 action. | ||
| pub zro: ActionMode, | ||
| } | ||
|
|
||
| /// PWM configuration. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| pub struct PwmConfig { | ||
| pub freq: Hertz, | ||
| /// Unit: nanoseconds. | ||
| pub duty_a: Nanoseconds, | ||
| /// Unit: nanoseconds. | ||
| pub duty_b: Nanoseconds, | ||
| /// Unit: nanoseconds. | ||
| pub period: Nanoseconds, | ||
| pub cnt_mode: CntMode, | ||
| pub tb_clk_rate: Hertz, | ||
| pub init_level: InitLevel, | ||
| pub action_0: Option<Action>, | ||
| pub action_1: Option<Action>, | ||
| } | ||
|
|
||
| impl Default for PwmConfig { | ||
| fn default() -> Self { | ||
| Self { | ||
| freq: Hertz::new(1_000), | ||
| duty_a: Nanoseconds(500_000), | ||
| duty_b: Nanoseconds(500_000), | ||
| period: Nanoseconds(1_000_000), | ||
| cnt_mode: CntMode::CountUp, | ||
| tb_clk_rate: Hertz(24_000_000), | ||
| init_level: InitLevel::High, | ||
| action_0: Some(Action { | ||
| cbd: ActionMode::NoOp, | ||
| cbu: ActionMode::NoOp, | ||
| cad: ActionMode::NoOp, | ||
| cau: ActionMode::SetLow, | ||
| prd: ActionMode::SetHigh, | ||
| zro: ActionMode::NoOp, | ||
| }), | ||
| action_1: Some(Action { | ||
| cbd: ActionMode::NoOp, | ||
| cbu: ActionMode::SetLow, | ||
| cad: ActionMode::NoOp, | ||
| cau: ActionMode::NoOp, | ||
| prd: ActionMode::SetHigh, | ||
| zro: ActionMode::NoOp, | ||
| }), | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,224 @@ | ||||||||||||||||||||||||||||||||||||
| //! Pwm channel implementation. | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| use embedded_time::duration::Nanoseconds; | ||||||||||||||||||||||||||||||||||||
| use embedded_time::rate::Hertz; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| use super::config::PwmConfig; | ||||||||||||||||||||||||||||||||||||
| use super::instance::PwmChannel; | ||||||||||||||||||||||||||||||||||||
| use super::pad::*; | ||||||||||||||||||||||||||||||||||||
| use super::register::*; | ||||||||||||||||||||||||||||||||||||
| use crate::cmu::Cmu; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// Pwm channel driver with statically known channel number and pad. | ||||||||||||||||||||||||||||||||||||
| pub struct PwmChDriver<'a, const I: u8, PAD> | ||||||||||||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||||||||||||
| PAD: PwmPads<I>, | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| reg: &'a RegisterBlock, | ||||||||||||||||||||||||||||||||||||
| pad: PAD, | ||||||||||||||||||||||||||||||||||||
| config: PwmConfig, | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| impl<'a, const I: u8, PAD> PwmChDriver<'a, I, PAD> | ||||||||||||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||||||||||||
| PAD: PwmPads<I>, | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| const DEFAULT_PWM_CLK: u32 = 48_000_000; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// Create a new PWM channel. | ||||||||||||||||||||||||||||||||||||
| pub fn __new(reg: &'a RegisterBlock, pad: PAD, config: PwmConfig, cmu: &mut Cmu) -> Self { | ||||||||||||||||||||||||||||||||||||
| let clk = &cmu.register_block().clock_pwm; | ||||||||||||||||||||||||||||||||||||
| let fix_mod_div = 24; | ||||||||||||||||||||||||||||||||||||
| if !clk.read().is_bus_clk_enabled() { | ||||||||||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||||||||||
| // Initialize module clock. | ||||||||||||||||||||||||||||||||||||
| // Reference: https://aicdoc.artinchip.com/topics/ic/cmu/cmu-function2-d13x.html#topic_yvp_f24_4bc__table_qb3_bn5_ydc | ||||||||||||||||||||||||||||||||||||
| clk.modify(|v| v.set_module_clk_div(fix_mod_div).enable_module_clk()); | ||||||||||||||||||||||||||||||||||||
| clk.modify(|v| v.enable_bus_clk()); | ||||||||||||||||||||||||||||||||||||
| clk.modify(|v| v.enable_module_reset()); | ||||||||||||||||||||||||||||||||||||
| riscv::asm::delay(500); | ||||||||||||||||||||||||||||||||||||
| clk.modify(|v| v.disable_module_reset()); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Enable PWM clk. | ||||||||||||||||||||||||||||||||||||
| reg.ctrl.modify(|v| v.enable()); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||||||||||
| let channel = ®.channels[I as usize]; | ||||||||||||||||||||||||||||||||||||
| // Enable channel clock. | ||||||||||||||||||||||||||||||||||||
| reg.ck_ctrl.modify(|v| v.enable_ch_clk(I)); | ||||||||||||||||||||||||||||||||||||
| // Set shadow register load mode. | ||||||||||||||||||||||||||||||||||||
| channel.cmp_ctrl.modify(|v| { | ||||||||||||||||||||||||||||||||||||
| v.set_cmp_a_shdw_ld_mode(ShdwLdMode::Mode2) | ||||||||||||||||||||||||||||||||||||
| .set_cmp_b_shdw_ld_mode(ShdwLdMode::Mode2) | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Set clock div. | ||||||||||||||||||||||||||||||||||||
| let ch_tb_div = (Self::DEFAULT_PWM_CLK.saturating_div(config.tb_clk_rate.0) - 1) as u16; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| let ch_tb_div = (Self::DEFAULT_PWM_CLK.saturating_div(config.tb_clk_rate.0) - 1) as u16; | |
| let src_clk_hz = Self::DEFAULT_PWM_CLK; | |
| // Clamp requested tb_clk_rate to a safe, non-zero range within the source clock. | |
| let requested_tb_clk_hz = config.tb_clk_rate.0; | |
| let clamped_tb_clk_hz = | |
| core::cmp::max(1, core::cmp::min(requested_tb_clk_hz, src_clk_hz)); | |
| // Compute divider: src_clk / tb_clk - 1, with saturation and hardware-range clamp. | |
| let mut div = src_clk_hz | |
| .checked_div(clamped_tb_clk_hz) | |
| .unwrap_or(1) | |
| .saturating_sub(1); | |
| // Hardware divider is typically limited; clamp to 0x0FFF as a safe upper bound. | |
| div = core::cmp::min(div, 0x0FFF); | |
| let ch_tb_div = div as u16; |
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prd_reg_val computation can divide by zero / underflow: freq can become 0 (e.g., large period causing 1_000_000_000 / period == 0), and ((tb_clk / freq) - 1) will also underflow when tb_clk < freq. Consider computing period ticks directly from period and tb_clk_rate, or at least clamp freq to >= 1 and use saturating_sub(1)/checked_div to avoid wraparound.
| let tb_clk = self.config.tb_clk_rate.0; | |
| let freq = self.config.freq.0; | |
| let prd_reg_val = if self.config.cnt_mode == CntMode::CountUpAndDown { | |
| ((tb_clk / freq) / 2).min(u16::MAX as u32) as u16 | |
| } else { | |
| ((tb_clk / freq) - 1).min(u16::MAX as u32) as u16 | |
| let tb_clk = self.config.tb_clk_rate.0 as u64; | |
| let period_ns = period as u64; | |
| let ticks = tb_clk | |
| .saturating_mul(period_ns) | |
| / 1_000_000_000u64; | |
| let prd_reg_val = if self.config.cnt_mode == CntMode::CountUpAndDown { | |
| (ticks / 2).min(u16::MAX as u64) as u16 | |
| } else { | |
| ticks | |
| .saturating_sub(1) | |
| .min(u16::MAX as u64) as u16 |
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_freq_and_ratio documents freq must be > 0, but period_ns is computed with 1_000_000_000 / freq.0 before any validation. If freq.0 == 0, this will panic (division by zero). Add an early check (e.g., return Result or clamp to 1 Hz) before performing the division.
| let period_ns = (1_000_000_000 / freq.0).max(1); | |
| let duty_ns_a = ((period_ns as f32 * ratio_a / 100.0) + 0.5) as u32; | |
| let duty_ns_b = ((period_ns as f32 * ratio_b / 100.0) + 0.5) as u32; | |
| let duty_ns_a = duty_ns_a.min(period_ns); | |
| let duty_ns_b = duty_ns_b.min(period_ns); | |
| self.config.freq = freq; | |
| let freq_hz = freq.0.max(1); | |
| let period_ns = (1_000_000_000 / freq_hz).max(1); | |
| let duty_ns_a = ((period_ns as f32 * ratio_a / 100.0) + 0.5) as u32; | |
| let duty_ns_b = ((period_ns as f32 * ratio_b / 100.0) + 0.5) as u32; | |
| let duty_ns_a = duty_ns_a.min(period_ns); | |
| let duty_ns_b = duty_ns_b.min(period_ns); | |
| self.config.freq = Hertz(freq_hz); |
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
free() never disables this channel’s clock (ck_ctrl) or module enable bit (m_ctrl) before checking whether any channels are still active. As written, reset_is_needed will almost always become false because the current channel clock remains enabled, so the PWM module may never be reset/clock-gated. Consider disabling channel I (module + clock) first, then checking remaining channels, and optionally disabling reg.ctrl when the last channel is released.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Field doc for
cbuis incorrect: it says "Counts up andTBCTR=CMPA" butCBUcorresponds to the CMPB-up event (seeAqControl::set_cbu_modedocs). Please fix the comment to avoid confusing users configuring actions.