chiark / gitweb /
Improved the PWM functions with help from Chris Hall.
[wiringPi.git] / wiringPi / wiringPi.c
index fb5e7c68f6e37205c14fb323101ce440d0b7f10c..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
@@ -56,6 +57,8 @@
 
 #include <stdio.h>
 #include <stdint.h>
+#include <stdlib.h>
+#include <ctype.h>
 #include <poll.h>
 #include <unistd.h>
 #include <errno.h>
@@ -79,6 +82,9 @@ void (*setPadDrive)       (int group, int value) ;
 int  (*digitalRead)       (int pin) ;
 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
@@ -167,6 +173,14 @@ static volatile uint32_t *timer ;
 
 static volatile uint32_t *timerIrqRaw ;
 
+// Raspberry Pi board revision
+
+static int boardRevision = -1 ;
+
+// Debugging
+
+static int wiringPiDebug = FALSE ;
+
 // The BCM2835 has 54 GPIO pins.
 //     BCM2835 data sheet, Page 90 onwards.
 //     There are 6 control registers, each control the functions of a block
@@ -196,8 +210,11 @@ static int sysFds [64] ;
 
 // pinToGpio:
 //     Take a Wiring pin (0 through X) and re-map it to the BCM_GPIO pin
+//     Cope for 2 different board revieions here
+
+static int *pinToGpio ;
 
-static int pinToGpio [64] =
+static int pinToGpioR1 [64] =
 {
   17, 18, 21, 22, 23, 24, 25, 4,       // From the Original Wiki - GPIO 0 through 7
    0,  1,                              // I2C  - SDA0, SCL0
@@ -207,7 +224,23 @@ static int pinToGpio [64] =
 
 // Padding:
 
-          -1, -1, -1,-1,-1,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 31
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 31
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 47
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 63
+} ;
+
+static int pinToGpioR2 [64] =
+{
+  17, 18, 27, 22, 23, 24, 25, 4,       // From the Original Wiki - GPIO 0 through 7
+   2,  3,                              // I2C  - SDA0, SCL0
+   8,  7,                              // SPI  - CE1, CE0
+  10,  9, 11,                          // SPI  - MOSI, MISO, SCLK
+  14, 15,                              // UART - Tx, Rx
+  28, 29, 30, 31,                      // New GPIOs 8 though 11
+
+// Padding:
+
+                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 31
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 47
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,      // ... 63
 } ;
@@ -348,7 +381,6 @@ static unsigned long long epoch ;
 
 void pinModeGpio (int pin, int mode)
 {
-  static int pwmRunning  = FALSE ;
   int fSel, shift, alt ;
 
   pin &= 63 ;
@@ -369,41 +401,40 @@ 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
 
-//     Gert/Doms Values
-      *(clk + PWMCLK_DIV)  = BCM_PASSWORD | (32<<12) ; // set pwm div to 32 (19.2/3 = 600KHz)
-      *(clk + PWMCLK_CNTL) = BCM_PASSWORD | 0x11 ;     // Source=osc and enable
-      digitalWrite (pin, LOW) ;
-      *(pwm + PWM_CONTROL) = 0 ;                       // Disable PWM
-      delayMicroseconds (10) ;
-      *(pwm + PWM0_RANGE) = 0x400 ;
-      delayMicroseconds (10) ;
-      *(pwm + PWM1_RANGE) = 0x400 ;
-      delayMicroseconds (10) ;
+    (void)*(pwm + PWM_CONTROL) ;
+    while ((*(pwm + PWM_CONTROL) & 0x80) != 0) // Wait for clock to be !BUSY
+      delayMicroseconds (1) ;
 
-// Enable PWMs
+    *(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_DATA) = 512 ;
-      *(pwm + PWM1_DATA) = 512 ;
+// Default range regsiter of 1024
 
-      *(pwm + PWM_CONTROL) = PWM0_ENABLE | PWM1_ENABLE ;
+    *(pwm + PWM0_DATA) = 0 ; *(pwm + PWM0_RANGE) = 1024 ;
+    *(pwm + PWM1_DATA) = 0 ; *(pwm + PWM1_RANGE) = 1024 ;
 
-      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
-
-//  delayMicroseconds (300) ;
+//     Or we used to... Hm. Commented out now because for some wieird reason,
+//     it seems to block subsequent attempts to set the pull up/downs and I've
+//     not quite gotten to the bottom of why this happens
+//     The down-side is that the pull up/downs are rememberd in the SoC between
+//     power cycles, so it's going to be a good idea to explicitly set them in
+//     any new code.
+//
 //  pullUpDnControl (pin, PUD_OFF) ;
+
 }
 
 void pinModeWPi (int pin, int mode)
@@ -417,6 +448,89 @@ void pinModeSys (int pin, int mode)
 }
 
 
+/*
+ * pwmControl:
+ *     Allow the user to control some of the PWM functions
+ *********************************************************************************
+ */
+
+void pwmSetModeWPi (int mode)
+{
+  if (mode == PWM_MODE_MS)
+    *(pwm + PWM_CONTROL) = PWM0_ENABLE | PWM1_ENABLE | PWM0_MS_MODE | PWM1_MS_MODE ;
+  else
+    *(pwm + PWM_CONTROL) = PWM0_ENABLE | PWM1_ENABLE ;
+}
+
+void pwmSetModeSys (int mode)
+{
+  return ;
+}
+
+
+void pwmSetRangeWPi (unsigned int range)
+{
+  *(pwm + PWM0_RANGE) = range ; delayMicroseconds (10) ;
+  *(pwm + PWM1_RANGE) = range ; delayMicroseconds (10) ;
+}
+
+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
 /*
  * pinED01:
@@ -487,7 +601,7 @@ void pwmWriteGpio (int pin, int value)
   pin  = pin & 63 ;
   port = gpioToPwmPort [pin] ;
 
-  *(pwm + port) = value & 0x3FF ;
+  *(pwm + port) = value ;
 }
 
 void pwmWriteWPi (int pin, int value)
@@ -757,9 +871,19 @@ unsigned int millis (void)
 int wiringPiSetup (void)
 {
   int      fd ;
+  FILE    *cpuFd ;
+  char     line [80] ;
+  char    *c ;
+  int      revision = -1 ;
   uint8_t *gpioMem, *pwmMem, *clkMem, *padsMem, *timerMem ;
   struct timeval tv ;
 
+  if (getenv ("WIRINGPI_DEBUG") != NULL)
+    wiringPiDebug = TRUE ;
+
+  if (wiringPiDebug)
+    printf ("wiringPi: wiringPiSetup called\n") ;
+
             pinMode =           pinModeWPi ;
     pullUpDnControl =   pullUpDnControlWPi ;
        digitalWrite =      digitalWriteWPi ;
@@ -768,7 +892,65 @@ int wiringPiSetup (void)
         digitalRead =       digitalReadWPi ;
    waitForInterrupt =  waitForInterruptWPi ;
   delayMicroseconds = delayMicrosecondsWPi ;
+         pwmSetMode =        pwmSetModeWPi ;
+        pwmSetRange =       pwmSetRangeWPi ;
+        pwmSetClock =       pwmSetClockWPi ;
   
+// Find board revision
+
+  if ((cpuFd = fopen ("/proc/cpuinfo", "r")) == NULL)
+  {
+    fprintf (stderr, "wiringPiSetup: Unable to open /proc/cpuinfo: %s\n", strerror (errno)) ;
+    return -1 ;
+  }
+
+  while (fgets (line, 80, cpuFd) != NULL)
+    if (strncmp (line, "Revision", 8) == 0)
+      for (c = line ; *c ; ++c)
+      {
+       if (!isdigit (*c))
+         continue ;
+       revision = atoi (c) ;
+       break ;
+      }
+
+  fclose (cpuFd) ;
+  if (revision == -1)
+  {
+    fprintf (stderr, "wiringPiSetup: Unable to determine board revision\n") ;
+    errno = 0 ;
+    return -1 ;
+  }
+
+// If you have overvolted the Pi, then it appears that the revision
+//     has 100000 added to it!
+
+  if (wiringPiDebug)
+    if (revision > 1000)
+      printf ("wiringPi: This Pi has/is overvolted!\n") ;
+
+  revision %= 100 ;
+
+  /**/ if ((revision == 2) || (revision == 3))
+    boardRevision = 1 ;
+  else if ((revision == 4) || (revision == 5) || (revision == 6))
+    boardRevision = 2 ;
+  else
+  {
+    fprintf (stderr, "wiringPiSetup: Unable to determine board revision: %d\n", revision) ;
+    errno = 0 ;
+    return -1 ;
+  }
+
+
+  if (boardRevision == 1)
+    pinToGpio = pinToGpioR1 ;
+  else
+    pinToGpio = pinToGpioR2 ;
+
+  if (wiringPiDebug)
+    printf ("wiringPi: Revision: %d, board revision: %d\n", revision, boardRevision) ;
+
 // Open the master /dev/memory device
 
   if ((fd = open ("/dev/mem", O_RDWR | O_SYNC) ) < 0)
@@ -909,9 +1091,12 @@ int wiringPiSetup (void)
 
 int wiringPiSetupGpio (void)
 {
-  int x = wiringPiSetup () ;
+  int x  ;
+
+  if (wiringPiDebug)
+    printf ("wiringPi: wiringPiSetupGpio called\n") ;
 
-  if (x != 0)
+  if ((x = wiringPiSetup ()) < 0)
     return x ;
 
             pinMode =           pinModeGpio ;
@@ -922,6 +1107,9 @@ int wiringPiSetupGpio (void)
         digitalRead =       digitalReadGpio ;
    waitForInterrupt =  waitForInterruptGpio ;
   delayMicroseconds = delayMicrosecondsWPi ;   // Same
+         pwmSetMode =        pwmSetModeWPi ;
+        pwmSetRange =       pwmSetRangeWPi ;
+        pwmSetClock =       pwmSetClockWPi ;
 
   return 0 ;
 }
@@ -942,6 +1130,9 @@ int wiringPiSetupSys (void)
   struct timeval tv ;
   char fName [128] ;
 
+  if (wiringPiDebug)
+    printf ("wiringPi: wiringPiSetupSys called\n") ;
+
             pinMode =           pinModeSys ;
     pullUpDnControl =   pullUpDnControlSys ;
        digitalWrite =      digitalWriteSys ;
@@ -950,6 +1141,10 @@ int wiringPiSetupSys (void)
         digitalRead =       digitalReadSys ;
    waitForInterrupt =  waitForInterruptSys ;
   delayMicroseconds = delayMicrosecondsSys ;
+         pwmSetMode =        pwmSetModeSys ;
+        pwmSetRange =       pwmSetRangeSys ;
+        pwmSetClock =       pwmSetClockSys ;
+
 
 // Open and scan the directory, looking for exported GPIOs, and pre-open
 //     the 'value' interface to speed things up for later