From 8cb493937f1128fa0a1ed952214b138b557420f7 Mon Sep 17 00:00:00 2001 From: Gordon Henderson Date: Sun, 16 Sep 2012 10:15:32 +0100 Subject: [PATCH] Improved the PWM functions with help from Chris Hall. --- gpio/gpio.c | 29 ++++++++++++-- wiringPi/wiringPi.c | 97 +++++++++++++++++++++++++++++++++------------ wiringPi/wiringPi.h | 1 + 3 files changed, 98 insertions(+), 29 deletions(-) diff --git a/gpio/gpio.c b/gpio/gpio.c index b696542..8b15eea 100644 --- a/gpio/gpio.c +++ b/gpio/gpio.c @@ -52,9 +52,10 @@ char *usage = "Usage: gpio -v\n" " gpio drive \n" " gpio pwm-bal/pwm-ms \n" " gpio pwmr \n" + " gpio pwmc \n" " gpio load spi/i2c\n" " gpio gbr \n" - " gpio gbw \n" ; + " gpio gbw " ; // 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 \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 diff --git a/wiringPi/wiringPi.c b/wiringPi/wiringPi.c index b89309d..ac9f530 100644 --- a/wiringPi/wiringPi.c +++ b/wiringPi/wiringPi.c @@ -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 * * 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, + * 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 diff --git a/wiringPi/wiringPi.h b/wiringPi/wiringPi.h index 1d21fa0..503c28a 100644 --- a/wiringPi/wiringPi.h +++ b/wiringPi/wiringPi.h @@ -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 -- 2.30.2