chiark / gitweb /
Improved the PWM functions with help from Chris Hall.
authorGordon Henderson <gordon@drogon.net>
Sun, 16 Sep 2012 09:15:32 +0000 (10:15 +0100)
committerGordon Henderson <gordon@drogon.net>
Sun, 16 Sep 2012 09:15:32 +0000 (10:15 +0100)
gpio/gpio.c
wiringPi/wiringPi.c
wiringPi/wiringPi.h

index b696542e04c0566eeef4558ba8c7b8cffbf5df2d..8b15eea113a5bd27a22814dbc73bf431741df2d6 100644 (file)
@@ -52,9 +52,10 @@ char *usage = "Usage: gpio -v\n"
              "       gpio drive <group> <value>\n"
              "       gpio pwm-bal/pwm-ms \n"
              "       gpio pwmr <range> \n"
+             "       gpio pwmc <divider> \n"
              "       gpio load spi/i2c\n"
              "       gpio gbr <channel>\n"
-             "       gpio gbw <channel> <value>\n" ;
+             "       gpio gbw <channel> <value>" ;     // No trailing newline needed here.
 
 
 /*
@@ -687,8 +688,8 @@ void doPwm (int argc, char *argv [])
 
 
 /*
- * doPwmMode: doPwmRange:
- *     Change the PWM mode and Range values
+ * doPwmMode: doPwmRange: doPwmClock:
+ *     Change the PWM mode, range and clock divider values
  *********************************************************************************
  */
 
@@ -718,6 +719,27 @@ static void doPwmRange (int argc, char *argv [])
   pwmSetRange (range) ;
 }
 
+static void doPwmClock (int argc, char *argv [])
+{
+  unsigned int clock ;
+
+  if (argc != 3)
+  {
+    fprintf (stderr, "Usage: %s pwmc <clock>\n", argv [0]) ;
+    exit (1) ;
+  }
+
+  clock = (unsigned int)strtoul (argv [2], NULL, 10) ;
+
+  if ((clock < 1) || (clock > 4095))
+  {
+    fprintf (stderr, "%s: clock must be between 0 and 4096\n", argv [0]) ;
+    exit (1) ;
+  }
+
+  pwmSetClock (clock) ;
+}
+
 
 /*
  * main:
@@ -846,6 +868,7 @@ int main (int argc, char *argv [])
     if (strcasecmp (argv [1], "pwm-bal") == 0) { doPwmMode  (PWM_MODE_BAL) ; return 0 ; }
     if (strcasecmp (argv [1], "pwm-ms")  == 0) { doPwmMode  (PWM_MODE_MS) ;  return 0 ; }
     if (strcasecmp (argv [1], "pwmr")    == 0) { doPwmRange (argc, argv) ;   return 0 ; }
+    if (strcasecmp (argv [1], "pwmc")    == 0) { doPwmClock (argc, argv) ;   return 0 ; }
   }
 
 // Check for wiring commands
index b89309db392227efd7d1193c0e4f4cfd2c4dcddc..ac9f5300babc6a4550c3a2af80ba7199fae45f0a 100644 (file)
@@ -2,6 +2,7 @@
  * wiringPi:
  *     Arduino compatable (ish) Wiring library for the Raspberry Pi
  *     Copyright (c) 2012 Gordon Henderson
+ *     Additional code for pwmSetClock by Chris Hall <chris@kchall.plus.com>
  *
  *     Thanks to code samples from Gert Jan van Loo and the
  *     BCM2835 ARM Peripherals manual, however it's missing
@@ -83,6 +84,7 @@ int  (*waitForInterrupt)  (int pin, int mS) ;
 void (*delayMicroseconds) (unsigned int howLong) ;
 void (*pwmSetMode)        (int mode) ;
 void (*pwmSetRange)       (unsigned int range) ;
+void (*pwmSetClock)       (int divisor) ;
 
 
 #ifndef        TRUE
@@ -379,7 +381,6 @@ static unsigned long long epoch ;
 
 void pinModeGpio (int pin, int mode)
 {
-  static int pwmRunning  = FALSE ;
   int fSel, shift, alt ;
 
   pin &= 63 ;
@@ -400,38 +401,28 @@ void pinModeGpio (int pin, int mode)
 
     *(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (alt << shift) ;
 
-// We didn't initialise the PWM hardware at setup time - because it's possible that
-//     something else is using the PWM - e.g. the Audio systems! So if we use PWM
-//     here, then we're assuming that nothing else is, otherwise things are going
-//     to sound a bit funny...
+//  Page 107 of the BCM Peripherals manual talks about the GPIO clocks,
+//     but I'm assuming (hoping!) that this applies to other clocks too.
 
-    if (!pwmRunning)
-    {
+    *(pwm + PWM_CONTROL) = 0 ;                         // Stop PWM
+    *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x01 ;       // Stop PWM Clock
+      delayMicroseconds (110) ; // See comments in pwmSetClockWPi
 
-      *(pwm + PWM_CONTROL) = 0 ;                       // Stop PWM
-      delayMicroseconds (10) ;
-       
-//     Gert/Doms Values
-      *(clk + PWMCLK_DIV)  = BCM_PASSWORD | (32<<12) ; // set pwm div to 32 (19.2/32 = 600KHz)
-      *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x11 ;     // Source=osc and enable
+    (void)*(pwm + PWM_CONTROL) ;
+    while ((*(pwm + PWM_CONTROL) & 0x80) != 0) // Wait for clock to be !BUSY
+      delayMicroseconds (1) ;
 
-      delayMicroseconds (10) ;
+    *(clk + PWMCLK_DIV)  = BCM_PASSWORD | (32 << 12) ; // set pwm div to 32 (19.2/32 = 600KHz)
+    *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x11 ;       // enable clk
 
-      *(pwm + PWM0_RANGE) = 0x400 ; delayMicroseconds (10) ;
-      *(pwm + PWM1_RANGE) = 0x400 ; delayMicroseconds (10) ;
+// Default range regsiter of 1024
 
-// Enable PWMs
+    *(pwm + PWM0_DATA) = 0 ; *(pwm + PWM0_RANGE) = 1024 ;
+    *(pwm + PWM1_DATA) = 0 ; *(pwm + PWM1_RANGE) = 1024 ;
 
-      *(pwm + PWM0_DATA) = 512 ;
-      *(pwm + PWM1_DATA) = 512 ;
-
-// Balanced mode (default)
-
-      *(pwm + PWM_CONTROL) = PWM0_ENABLE | PWM1_ENABLE ;
-
-      pwmRunning = TRUE ;
-    }
+// Enable PWMs in balanced mode (default)
 
+    *(pwm + PWM_CONTROL) = PWM0_ENABLE | PWM1_ENABLE ;
   }
 
 // When we change mode of any pin, we remove the pull up/downs
@@ -488,6 +479,57 @@ void pwmSetRangeSys (unsigned int range)
   return ;
 }
 
+/*
+ * pwmSetClockWPi:
+ *     Set/Change the PWM clock. Originally my code, but changed
+ *     (for the better!) by Chris Hall, <chris@kchall.plus.com>
+ *     after further study of the manual and testing with a 'scope
+ *********************************************************************************
+ */
+
+void pwmSetClockWPi (int divisor)
+{
+  unsigned int pwm_control ;
+  divisor &= 4095 ;
+
+  if (wiringPiDebug)
+    printf ("Setting to: %d. Current: 0x%08X\n", divisor, *(clk + PWMCLK_DIV)) ;
+
+  pwm_control = *(pwm + PWM_CONTROL) ;         // preserve PWM_CONTROL
+
+// We need to stop PWM prior to stopping PWM clock in MS mode otherwise BUSY
+// stays high.
+
+  *(pwm + PWM_CONTROL) = 0 ;                   // Stop PWM
+
+// Stop PWM clock before changing divisor. The delay after this does need to
+// this big (95uS occasionally fails, 100uS OK), it's almost as though the BUSY
+// flag is not working properly in balanced mode. Without the delay when DIV is
+// adjusted the clock sometimes switches to very slow, once slow further DIV
+// adjustments do nothing and it's difficult to get out of this mode.
+
+  *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x01 ; // Stop PWM Clock
+    delayMicroseconds (110) ;                  // prevents clock going sloooow
+
+  while ((*(pwm + PWM_CONTROL) & 0x80) != 0)   // Wait for clock to be !BUSY
+    delayMicroseconds (1) ;
+
+  *(clk + PWMCLK_DIV)  = BCM_PASSWORD | (divisor << 12) ;
+
+  *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x11 ; // Start PWM clock
+  *(pwm + PWM_CONTROL) = pwm_control ;         // restore PWM_CONTROL
+
+  if (wiringPiDebug)
+    printf ("Set     to: %d. Now    : 0x%08X\n", divisor, *(clk + PWMCLK_DIV)) ;
+}
+
+void pwmSetClockSys (int divisor)
+{
+  return ;
+}
+
+
+
 
 #ifdef notYetReady
 /*
@@ -852,6 +894,7 @@ int wiringPiSetup (void)
   delayMicroseconds = delayMicrosecondsWPi ;
          pwmSetMode =        pwmSetModeWPi ;
         pwmSetRange =       pwmSetRangeWPi ;
+        pwmSetClock =       pwmSetClockWPi ;
   
 // Find board revision
 
@@ -1066,6 +1109,7 @@ int wiringPiSetupGpio (void)
   delayMicroseconds = delayMicrosecondsWPi ;   // Same
          pwmSetMode =        pwmSetModeWPi ;
         pwmSetRange =       pwmSetRangeWPi ;
+        pwmSetClock =       pwmSetClockWPi ;
 
   return 0 ;
 }
@@ -1099,6 +1143,7 @@ int wiringPiSetupSys (void)
   delayMicroseconds = delayMicrosecondsSys ;
          pwmSetMode =        pwmSetModeSys ;
         pwmSetRange =       pwmSetRangeSys ;
+        pwmSetClock =       pwmSetClockSys ;
 
 
 // Open and scan the directory, looking for exported GPIOs, and pre-open
index 1d21fa0f0c231e10cc25c615a3fa96e3f4888bd7..503c28a3bbe8bf116780cccc680be61bcdfd9fe3 100644 (file)
@@ -73,6 +73,7 @@ extern int  (*digitalRead)       (int pin) ;
 extern void (*delayMicroseconds) (unsigned int howLong) ;
 extern void (*pwmSetMode)        (int mode) ;
 extern void (*pwmSetRange)       (unsigned int range) ;
+extern void (*pwmSetClock)       (int divisor) ;
 
 // Interrupts