This is an automated email from the ASF dual-hosted git repository.

lupyuen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 53ef7c06350 arch/arm/rp23xx: Fix PWM frequency and duty cycle 
calculation.
53ef7c06350 is described below

commit 53ef7c06350d0bca12423fc824155716b40edab1
Author: Brunocor26 <[email protected]>
AuthorDate: Sun May 24 01:02:21 2026 +0100

    arch/arm/rp23xx: Fix PWM frequency and duty cycle calculation.
    
    Fixed three bugs in the RP23XX PWM driver:
    
    * setup_period: The previous divisor calculation used integer arithmetic
      that caused overflow and loss of precision. The divider is now computed
      as a 16-bit fixed-point value (div16) using 64-bit arithmetic, and
      clamped to the valid hardware range (0x10 to 0xFFF).
    
    * setup_pulse: The compare value was incorrectly scaled by TOP instead
      of 65535, producing wrong duty cycles. The formula is now corrected
      to ((duty * (top + 1)) / 65535) with an overflow guard.
    
    * pwm_start: The driver was not updated as part of the breaking change
      introduced in commit 4df80e19 ("!drivers/pwm: remove PWM_MULTICHAN
      option"). Access to single channel API is now info->channels[0].duty
      instead of info[0].duty.
    
    Signed-off-by: Brunocor26 <[email protected]>
---
 arch/arm/src/rp23xx/rp23xx_pwm.c | 126 +++++++++++++++++++++++++++------------
 1 file changed, 88 insertions(+), 38 deletions(-)

diff --git a/arch/arm/src/rp23xx/rp23xx_pwm.c b/arch/arm/src/rp23xx/rp23xx_pwm.c
index 1b9e980142a..72df3c8f85f 100644
--- a/arch/arm/src/rp23xx/rp23xx_pwm.c
+++ b/arch/arm/src/rp23xx/rp23xx_pwm.c
@@ -359,9 +359,9 @@ int pwm_start(struct pwm_lowerhalf_s  * dev,
         }
     }
 #else
-  if (priv->duty != info[0].duty)
+  if (priv->duty != info->channels[0].duty)
     {
-      priv->duty = info[0].duty;
+      priv->duty = info->channels[0].duty;
     }
 #endif
 
@@ -465,51 +465,77 @@ int pwm_ioctl(struct pwm_lowerhalf_s  * dev,
  *
  ****************************************************************************/
 
-void setup_period(struct rp23xx_pwm_lowerhalf_s  * priv)
+void setup_period(struct rp23xx_pwm_lowerhalf_s *priv)
 {
   irqstate_t flags;
-  uint32_t max_freq = BOARD_SYS_FREQ / 0x10000; /* initially, with full range 
count */
   uint32_t frequency = priv->frequency;
+  uint32_t top;
+  uint32_t div16;
 
-  /* If we are running phase correct we double the frequency value
-   * since the PWM will generate a pulse chain at half what it
-   * would be in normal (non-phase correct) mode
-   */
+  /* Phase-correct mode halves output frequency */
 
   if (priv->flags & RP23XX_PWM_CSR_PH_CORRECT)
     {
       frequency *= 2;
     }
 
-  pwminfo("PWM%d freq %ld max %ld\n", priv->num, priv->frequency, max_freq);
+  /* Try to keep maximum resolution first */
+
+  top = 0xffff;
+
+  /* Divider is 8.4 fixed-point:
+   *
+   *   div16 = divider * 16
+   *
+   * PWM frequency:
+   *
+   *   f_pwm = clk_sys / (divider * (TOP + 1))
+   *
+   * therefore:
+   *
+   *   div16 = (16 * clk_sys) / (f_pwm * (TOP + 1))
+   */
+
+  div16 = (16ULL * BOARD_SYS_FREQ) /
+          ((uint64_t)frequency * (top + 1));
 
-  if (frequency <= max_freq)
-    {
-      /* We can keep full range count and slow clock down with divisor */
+  /* Divider must be in valid hardware range:
+   * 1.0 -> 255.9375
+   * represented as:
+   * 0x10 -> 0xFFF
+   */
 
-      priv->top = 0xffff;
+  if (div16 < 0x10)
+    {
+      /* Divider too small:
+       * reduce TOP to increase frequency
+       */
+
+      div16 = 0x10;
+      top = (BOARD_SYS_FREQ /
+            ((div16 / 16.0) * frequency)) - 1;
+      if (top > 0xffff)
+        {
+          top = 0xffff;
+        }
     }
-    else
+  else if (div16 > 0xfff)
     {
-      /* we need to speed things up by reducing top */
-
-      priv->top = 0xffff / (frequency / max_freq);
-
-      /* compute new maximum frequency */
-
-      max_freq  = BOARD_SYS_FREQ / (priv->top + 1);
+      div16 = 0xfff;
     }
 
-  priv->divisor = 16 * max_freq / frequency;
+  priv->top     = top;
+  priv->divisor = div16;
 
-  pwminfo("PWM%d top 0x%08X div 0x%08lX\n",
-          priv->num,
-          priv->top,
-          priv->divisor);
+  pwminfo("PWM%d freq=%lu top=%lu div=%lu\n",
+         priv->num,
+         priv->frequency,
+         priv->top,
+         priv->divisor);
 
   flags = enter_critical_section();
 
-  putreg32(priv->top,     RP23XX_PWM_TOP(priv->num));
+  putreg32(priv->top, RP23XX_PWM_TOP(priv->num));
   putreg32(priv->divisor, RP23XX_PWM_DIV(priv->num));
 
   leave_critical_section(flags);
@@ -526,19 +552,43 @@ void setup_period(struct rp23xx_pwm_lowerhalf_s  * priv)
  *
  ****************************************************************************/
 
-void setup_pulse(struct rp23xx_pwm_lowerhalf_s  * priv)
+void setup_pulse(struct rp23xx_pwm_lowerhalf_s *priv)
 {
   irqstate_t flags;
 
 #if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
-  uint32_t compare =
-             (0xffff * (uint32_t)priv->duty[0] / priv->top)
-          + ((0xffff * (uint32_t)priv->duty[1] / priv->top) << 16);
+
+  uint32_t level_a =
+      ((uint64_t)priv->duty[0] * (priv->top + 1)) / 65535;
+
+  uint32_t level_b =
+      ((uint64_t)priv->duty[1] * (priv->top + 1)) / 65535;
+
+  if (level_a > priv->top)
+    {
+      level_a = priv->top;
+    }
+
+  if (level_b > priv->top)
+    {
+      level_b = priv->top;
+    }
+
+  uint32_t compare = level_a | (level_b << 16);
+
 #else
-  uint32_t compare = 0xffff * (uint32_t)priv->duty / priv->top;
+
+  uint32_t compare =
+      ((uint64_t)priv->duty * (priv->top + 1)) / 65535;
+
+  if (compare > priv->top)
+    {
+      compare = priv->top;
+    }
+
 #endif
 
-  pwminfo("PWM%d compare 0x%08lX  flags 0x%08lX\n",
+  pwminfo("PWM%d compare=0x%08lx flags=0x%08lx\n",
           priv->num,
           compare,
           priv->flags);
@@ -548,11 +598,11 @@ void setup_pulse(struct rp23xx_pwm_lowerhalf_s  * priv)
   putreg32(compare, RP23XX_PWM_CC(priv->num));
 
   modreg32(priv->flags,
-            RP23XX_PWM_CSR_DIVMODE_MASK
-          | RP23XX_PWM_CSR_B_INV
-          | RP23XX_PWM_CSR_A_INV
-          | RP23XX_PWM_CSR_PH_CORRECT,
-          RP23XX_PWM_CSR(priv->num));
+           RP23XX_PWM_CSR_DIVMODE_MASK |
+           RP23XX_PWM_CSR_B_INV |
+           RP23XX_PWM_CSR_A_INV |
+           RP23XX_PWM_CSR_PH_CORRECT,
+           RP23XX_PWM_CSR(priv->num));
 
   leave_critical_section(flags);
 }

Reply via email to