Beaglebone Black. PWM minimum frequency of 1.52KHz
Luiz Otavio O Souza
lists.br at gmail.com
Thu May 22 14:19:20 UTC 2014
On 10 May 2014 08:56, fabiodive wrote:
> Hello,
>
> PWM on Beaglebone Black
>
> during my laboratory tests I was not able to setup a frequency suitable for servo motors.
> I tried several times with many values but the results was always the same.
> The minimal frequency I was able to achieve was 1.52 KHz.
> Also, I noticed odd results with the most of configuration values, in this case
> I lost the PWM output signal.
>
> I tried some combinations, measuring the results with an oscilloscope, the period
> configuration key appears to loose its nanoseconds meaning beyond a value:
Hi Fabio!
The PWM frequency is determined as SYSCLKOUT (100Mhz) / prescaler
settings / period. As the current code uses 1 for the prescaler and
the period is a 16bit value the lower frequency you can have is
actually 1.529Khz.
> # sysctl dev.am335x_pwm.1.period=1500
> # sysctl dev.am335x_pwm.1.dutyA=300
> result frequency: 66 KHz ->should be 666 KHz
> length of period: 15 us ->should be 1.5 us
> length of pulse: 2 us
As above, this is correct: 100Mhz / 1 / 1500 = 66Khz.
>
> # sysctl dev.am335x_pwm.1.period=1500000
> # sysctl dev.am335x_pwm.1.dutyA=10000
> result frequency: 1.71 KHz -> should be 666Hz
> length of period: 585 us
> length of pulse: 100 us
>
> # sysctl dev.am335x_pwm.1.period=1800000
> # sysctl dev.am335x_pwm.1.dutyA=10000
> result frequency: 3.24 KHz -> should be 555Hz
> length of period: 308 us
> length of pulse: 100 us
These two examples are overflowing the period variable.
I've the attached patch which enforces the maximum values where needed
(to avoid such overflows) and two new sysctls, one allows you to set
the prescaler for the PWM module and the other (which is probably more
interesting) allows you to check the actual PWM frequency.
The sysctl.am335x_pwm.X.freq can also be use to set an desired
frequency directly, it will try to get you the better setting it can
find for the desired frequency, which usually means get the bigger
period it can find to give the better PWM resolution that is possible
for a given frequency.
I've been able to drive R/C servos directly with this patch and the
following set:
sysctl.am335x_pwm.X.freq = 50 (digital servos may operate at higher frequencies)
And then, for the cheap chinese servo i have here, i can get it to
travel almost 180o using duty values from 2000 to 6000 (YMMV).
I've checked the frequencies with a scope and they look fine for me.
Regards,
Luiz
-------------- next part --------------
Index: sys/arm/ti/am335x/am335x_pwm.c
===================================================================
--- sys/arm/ti/am335x/am335x_pwm.c (revision 266516)
+++ sys/arm/ti/am335x/am335x_pwm.c (working copy)
@@ -29,10 +29,11 @@
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/bus.h>
#include <sys/kernel.h>
+#include <sys/limits.h>
+#include <sys/lock.h>
#include <sys/module.h>
-#include <sys/bus.h>
-#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/resource.h>
#include <sys/rman.h>
@@ -53,6 +54,7 @@
/* In ticks */
#define DEFAULT_PWM_PERIOD 1000
+#define PWM_CLOCK 100000000
#define PWM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
#define PWM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
@@ -170,6 +172,8 @@
static device_probe_t am335x_pwm_probe;
static device_attach_t am335x_pwm_attach;
static device_detach_t am335x_pwm_detach;
+
+static int am335x_pwm_clkdiv[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
struct am335x_pwm_softc {
device_t sc_dev;
@@ -177,6 +181,10 @@
struct resource *sc_mem_res[4];
int sc_id;
/* sysctl for configuration */
+ int sc_pwm_clkdiv;
+ int sc_pwm_freq;
+ struct sysctl_oid *sc_clkdiv_oid;
+ struct sysctl_oid *sc_freq_oid;
struct sysctl_oid *sc_period_oid;
struct sysctl_oid *sc_chanA_oid;
struct sysctl_oid *sc_chanB_oid;
@@ -241,7 +249,99 @@
return (0);
}
+static void
+am335x_pwm_freq(struct am335x_pwm_softc *sc)
+{
+ int clkdiv;
+
+ clkdiv = am335x_pwm_clkdiv[sc->sc_pwm_clkdiv];
+ sc->sc_pwm_freq = PWM_CLOCK / (1 * clkdiv) / sc->sc_pwm_period;
+}
+
static int
+am335x_pwm_sysctl_freq(SYSCTL_HANDLER_ARGS)
+{
+ int clkdiv, error, freq, i, period;
+ struct am335x_pwm_softc *sc;
+ uint32_t reg;
+
+ sc = (struct am335x_pwm_softc *)arg1;
+
+ PWM_LOCK(sc);
+ freq = sc->sc_pwm_freq;
+ PWM_UNLOCK(sc);
+
+ error = sysctl_handle_int(oidp, &freq, sizeof(freq), req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ if (freq > PWM_CLOCK)
+ freq = PWM_CLOCK;
+
+ PWM_LOCK(sc);
+ if (freq != sc->sc_pwm_freq) {
+ for (i = nitems(am335x_pwm_clkdiv) - 1; i >= 0; i--) {
+ clkdiv = am335x_pwm_clkdiv[i];
+ period = PWM_CLOCK / clkdiv / freq;
+ if (period > USHRT_MAX)
+ break;
+ sc->sc_pwm_clkdiv = i;
+ sc->sc_pwm_period = period;
+ }
+ /* Reset the duty cycle settings. */
+ sc->sc_pwm_dutyA = 0;
+ sc->sc_pwm_dutyB = 0;
+ EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA);
+ EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB);
+ /* Update the clkdiv settings. */
+ reg = EPWM_READ2(sc, EPWM_TBCTL);
+ reg &= ~TBCTL_CLKDIV_MASK;
+ reg |= TBCTL_CLKDIV(sc->sc_pwm_clkdiv);
+ EPWM_WRITE2(sc, EPWM_TBCTL, reg);
+ /* Update the period settings. */
+ EPWM_WRITE2(sc, EPWM_TBPRD, sc->sc_pwm_period - 1);
+ am335x_pwm_freq(sc);
+ }
+ PWM_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+am335x_pwm_sysctl_clkdiv(SYSCTL_HANDLER_ARGS)
+{
+ int error, i, clkdiv;
+ struct am335x_pwm_softc *sc;
+ uint32_t reg;
+
+ sc = (struct am335x_pwm_softc *)arg1;
+
+ PWM_LOCK(sc);
+ clkdiv = am335x_pwm_clkdiv[sc->sc_pwm_clkdiv];
+ PWM_UNLOCK(sc);
+
+ error = sysctl_handle_int(oidp, &clkdiv, sizeof(clkdiv), req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ PWM_LOCK(sc);
+ if (clkdiv != am335x_pwm_clkdiv[sc->sc_pwm_clkdiv]) {
+ for (i = 0; i < nitems(am335x_pwm_clkdiv); i++)
+ if (clkdiv >= am335x_pwm_clkdiv[i])
+ sc->sc_pwm_clkdiv = i;
+
+ reg = EPWM_READ2(sc, EPWM_TBCTL);
+ reg &= ~TBCTL_CLKDIV_MASK;
+ reg |= TBCTL_CLKDIV(sc->sc_pwm_clkdiv);
+ EPWM_WRITE2(sc, EPWM_TBCTL, reg);
+ am335x_pwm_freq(sc);
+ }
+ PWM_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
am335x_pwm_sysctl_duty(SYSCTL_HANDLER_ARGS)
{
struct am335x_pwm_softc *sc = (struct am335x_pwm_softc*)arg1;
@@ -292,15 +392,19 @@
if (period < 1)
return (EINVAL);
- if ((period < sc->sc_pwm_dutyA) || (period < sc->sc_pwm_dutyB)) {
- device_printf(sc->sc_dev, "Period can't be less then duty cycle\n");
- return (EINVAL);
- }
+ if (period > USHRT_MAX)
+ period = USHRT_MAX;
-
PWM_LOCK(sc);
+ /* Reset the duty cycle settings. */
+ sc->sc_pwm_dutyA = 0;
+ sc->sc_pwm_dutyB = 0;
+ EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA);
+ EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB);
+ /* Update the period settings. */
sc->sc_pwm_period = period;
EPWM_WRITE2(sc, EPWM_TBPRD, period - 1);
+ am335x_pwm_freq(sc);
PWM_UNLOCK(sc);
return (error);
@@ -360,6 +464,14 @@
ctx = device_get_sysctl_ctx(sc->sc_dev);
tree = device_get_sysctl_tree(sc->sc_dev);
+ sc->sc_clkdiv_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "clkdiv", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
+ am335x_pwm_sysctl_clkdiv, "I", "PWM clock prescaler");
+
+ sc->sc_freq_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
+ am335x_pwm_sysctl_freq, "I", "PWM frequency");
+
sc->sc_period_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"period", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
am335x_pwm_sysctl_period, "I", "PWM period");
@@ -372,7 +484,6 @@
"dutyB", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
am335x_pwm_sysctl_duty, "I", "Channel B duty cycles");
-
/* CONFIGURE EPWM1 */
reg = EPWM_READ2(sc, EPWM_TBCTL);
reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK);
@@ -381,6 +492,7 @@
sc->sc_pwm_period = DEFAULT_PWM_PERIOD;
sc->sc_pwm_dutyA = 0;
sc->sc_pwm_dutyB = 0;
+ am335x_pwm_freq(sc);
EPWM_WRITE2(sc, EPWM_TBPRD, sc->sc_pwm_period - 1);
EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA);
More information about the freebsd-arm
mailing list