/** * \brief Interface pro ovladani pinu a pwm na raspberryPi. * \file rpi_hw.c * \date Jan 24, 2015 * \author Martin Prudek * * Poskytuje rozhrani pro cteni a zapisovani na GPIO. * Zajistuje funkcnoust PWM. * Napsano pro Raspberry Pi. * Inspired by wiringPi written by Gordon Henderson. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpin.h" #include "pwm.h" #define BCM_PASSWORD 0x5A000000 #ifndef NULL #define NULL (void*)0 #endif #define PI_GPIO_MASK (0xFFFFFFC0) #define WPI_MODE_UNINITIALISED 0 #define WPI_MODE_PINS 1 /* pouzite GPIO piny */ #define PWM_WIDTH 18 #define PWM_DIR 22 #define IRC_A 24 #define IRC_B 8 /* Bazove adresy periferii */ #define BCM2708_PERI_BASE 0x20000000 #define GPIO_PADS (BCM2708_PERI_BASE + 0x00100000) #define CLOCK_BASE (BCM2708_PERI_BASE + 0x00101000) #define GPIO_BASE (BCM2708_PERI_BASE + 0x00200000) #define GPIO_TIMER (BCM2708_PERI_BASE + 0x0000B000) #define GPIO_PWM (BCM2708_PERI_BASE + 0x0020C000) #define PAGE_SIZE (4*1024) #define BLOCK_SIZE (4*1024) // PWM // Word offsets into the PWM control region #define PWM_CONTROL 0 #define PWM_STATUS 1 #define PWM0_RANGE 4 #define PWM0_DATA 5 #define PWM1_RANGE 8 #define PWM1_DATA 9 // Clock regsiter offsets #define PWMCLK_CNTL 40 #define PWMCLK_DIV 41 #define PWM0_MS_MODE 0x0080 // Run in MS mode #define PWM0_USEFIFO 0x0020 // Data from FIFO #define PWM0_REVPOLAR 0x0010 // Reverse polarity #define PWM0_OFFSTATE 0x0008 // Ouput Off state #define PWM0_REPEATFF 0x0004 // Repeat last value if FIFO empty #define PWM0_SERIAL 0x0002 // Run in serial mode #define PWM0_ENABLE 0x0001 // Channel Enable #define PWM1_MS_MODE 0x8000 // Run in MS mode #define PWM1_USEFIFO 0x2000 // Data from FIFO #define PWM1_REVPOLAR 0x1000 // Reverse polarity #define PWM1_OFFSTATE 0x0800 // Ouput Off state #define PWM1_REPEATFF 0x0400 // Repeat last value if FIFO empty #define PWM1_SERIAL 0x0200 // Run in serial mode #define PWM1_ENABLE 0x0100 // Channel Enable // Locals to hold pointers to the hardware static volatile uint32_t *gpio ; static volatile uint32_t *pwm ; static volatile uint32_t *clk ; static volatile uint32_t *pads ; static int initialised = WPI_MODE_UNINITIALISED ; // gpioToGPFSEL: // Map a BCM_GPIO pin to it's Function Selection // control port. (GPFSEL 0-5) // Groups of 10 - 3 bits per Function - 30 bits per port static uint8_t gpioToGPFSEL[] = { 0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5, } ; // gpioToShift // Define the shift up for the 3 bits per pin in each GPFSEL port static uint8_t gpioToShift[] = { 0,3,6,9,12,15,18,21,24,27, 0,3,6,9,12,15,18,21,24,27, 0,3,6,9,12,15,18,21,24,27, 0,3,6,9,12,15,18,21,24,27, 0,3,6,9,12,15,18,21,24,27, } ; // gpioToGPSET: // (Word) offset to the GPIO Set registers for each GPIO pin static uint8_t gpioToGPSET[] = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, } ; // gpioToGPCLR: // (Word) offset to the GPIO Clear registers for each GPIO pin static uint8_t gpioToGPCLR[] = { 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, } ; // gpioToGPLEV: // (Word) offset to the GPIO Input level registers for each GPIO pin static uint8_t gpioToGPLEV[] = { 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, } ; // GPPUD: // GPIO Pin pull up/down register #define GPPUD 37 // gpioToPwmALT // the ALT value to put a GPIO pin into PWM mode static uint8_t gpioToPwmALT [] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0 -> 7 0, 0, 0, 0, FSEL_ALT0, FSEL_ALT0, 0, 0, // 8 -> 15 0, 0, FSEL_ALT5, FSEL_ALT5, 0, 0, 0, 0, // 16 -> 23 0, 0, 0, 0, 0, 0, 0, 0, // 24 -> 31 0, 0, 0, 0, 0, 0, 0, 0, // 32 -> 39 FSEL_ALT0, FSEL_ALT0, 0, 0, 0, FSEL_ALT0, 0, 0, // 40 -> 47 0, 0, 0, 0, 0, 0, 0, 0, // 48 -> 55 0, 0, 0, 0, 0, 0, 0, 0, // 56 -> 63 } ; // gpioToPwmPort // The port value to put a GPIO pin into PWM mode static uint8_t gpioToPwmPort [] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0 -> 7 0, 0, 0, 0, PWM0_DATA, PWM1_DATA, 0, 0, // 8 -> 15 0, 0, PWM0_DATA, PWM1_DATA, 0, 0, 0, 0, // 16 -> 23 0, 0, 0, 0, 0, 0, 0, 0, // 24 -> 31 0, 0, 0, 0, 0, 0, 0, 0, // 32 -> 39 PWM0_DATA, PWM1_DATA, 0, 0, 0, PWM1_DATA, 0, 0, // 40 -> 47 0, 0, 0, 0, 0, 0, 0, 0, // 48 -> 55 0, 0, 0, 0, 0, 0, 0, 0, // 56 -> 63 } ; // gpioToClk: // (word) Offsets to the clock Control and Divisor register /* static uint8_t gpioToClkCon [] = { -1, -1, -1, -1, 28, 30, 32, -1, // 0 -> 7 -1, -1, -1, -1, -1, -1, -1, -1, // 8 -> 15 -1, -1, -1, -1, 28, 30, -1, -1, // 16 -> 23 -1, -1, -1, -1, -1, -1, -1, -1, // 24 -> 31 28, -1, 28, -1, -1, -1, -1, -1, // 32 -> 39 -1, -1, 28, 30, 28, -1, -1, -1, // 40 -> 47 -1, -1, -1, -1, -1, -1, -1, -1, // 48 -> 55 -1, -1, -1, -1, -1, -1, -1, -1, // 56 -> 63 } ; static uint8_t gpioToClkDiv [] = { -1, -1, -1, -1, 29, 31, 33, -1, // 0 -> 7 -1, -1, -1, -1, -1, -1, -1, -1, // 8 -> 15 -1, -1, -1, -1, 29, 31, -1, -1, // 16 -> 23 -1, -1, -1, -1, -1, -1, -1, -1, // 24 -> 31 29, -1, 29, -1, -1, -1, -1, -1, // 32 -> 39 -1, -1, 29, 31, 29, -1, -1, -1, // 40 -> 47 -1, -1, -1, -1, -1, -1, -1, -1, // 48 -> 55 -1, -1, -1, -1, -1, -1, -1, -1, // 56 -> 63 } ; */ // PWM #define PWM_MODE_MS 0 #define PWM_MODE_BAL 1 /*****************************************************************************/ /****************** implementace rpin.h **************************************/ /* */ /* Funkce pro nativni ovladani pinu, pwm a hodin */ /* */ /*****************************************************************************/ /** * inicializuje pouziti GPIO pinu... * (namapuje registry DMA) */ int initialise (void) { int fd ; int model, rev, mem, maker, overVolted ; if (geteuid() != 0){ printf("Must be root. (Did you forget sudo?)\n") ; return 1; } // Open the master /dev/memory device if ((fd = open ("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0){ printf(" Unable to open /dev/mem: %s\n"); return 1; } // GPIO: gpio = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE) ; if ((int32_t)gpio == -1){ printf("mmap (GPIO) failed: %s\n") ; return 1; } // PWM pwm = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_PWM) ; if ((int32_t)pwm == -1){ printf("mmap (PWM) failed: %s\n"); return 1; } // Clock control (needed for PWM) clk = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, CLOCK_BASE) ; if ((int32_t)clk == -1){ printf("mmap (CLOCK) failed: %s\n"); return 1; } pads = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_PADS) ; if ((int32_t)pads == -1){ printf("mmap (PADS) failed: %s\n"); return 1; } close(fd); initialised = 1 ; return 0 ; } /** * \brief Initialize gpclk. */ int initClock(enum ClkSource source, int divI, int divF) { const int MASH = 0; #define CLK_GP0_CTL 28 #define CLK_GP0_DIV 29 #define CLK_GP1_CTL 30 #define CLK_GP1_DIV 31 #define CLK_GP2_CTL 32 #define CLK_GP2_DIV 33 #define CLK_CTL_SRC_OSC 1 /* 19.2 MHz */ #define CLK_CTL_SRC_PLLC 5 /* 1000 MHz */ #define CLK_CTL_SRC_PLLD 6 /* 500 MHz */ #define CLK_CTL_SRC_HDMI 7 /* 216 MHz */ int src[] ={ CLK_CTL_SRC_PLLD, CLK_CTL_SRC_OSC, CLK_CTL_SRC_HDMI, CLK_CTL_SRC_PLLC }; int clkSrc; uint32_t setting; if (!initialised){ return -1; } if ((source < 0) || (source > 3 )) return -2; if ((divI < 2) || (divI > 4095)) return -3; if ((divF < 0) || (divF > 4095)) return -4; if ((MASH < 0) || (MASH > 3)) return -5; clkSrc = src[source]; #define CLK_PASSWD (0x5A<<24) #define CLK_CTL_MASH(x)((x)<<9) #define CLK_CTL_BUSY (1 <<7) #define CLK_CTL_KILL (1 <<5) #define CLK_CTL_ENAB (1 <<4) #define CLK_CTL_SRC(x) ((x)<<0) clk[CLK_GP0_CTL] = CLK_PASSWD | CLK_CTL_KILL; /* wait for clock to stop */ while (clk[CLK_GP0_CTL] & CLK_CTL_BUSY){ usleep(10); } #define CLK_DIV_DIVI(x) ((x)<<12) #define CLK_DIV_DIVF(x) ((x)<< 0) clk[CLK_GP0_DIV] = (CLK_PASSWD | CLK_DIV_DIVI(divI) | CLK_DIV_DIVF(divF)); usleep(10); clk[CLK_GP0_CTL] = (CLK_PASSWD | CLK_CTL_MASH(MASH) | CLK_CTL_SRC(clkSrc)); usleep(10); clk[CLK_GP0_CTL] |= (CLK_PASSWD | CLK_CTL_ENAB); return 0; } int termClock(int clock) { int ctl[] = {CLK_GP0_CTL, CLK_GP2_CTL}; int clkCtl; if ((clock < 0) || (clock > 1)) return -1; clkCtl = ctl[clock]; clk[clkCtl] = CLK_PASSWD | CLK_CTL_KILL; /* wait for clock to stop */ while (clk[clkCtl] & CLK_CTL_BUSY){ usleep(10); } return 0; } /** * \brief A different approach to set gpio mode. */ void gpioSetMode(unsigned gpio_n, unsigned mode) { int reg, shift; reg = gpio_n/10; shift = (gpio_n%10) * 3; gpio[reg] = (gpio[reg] & ~(7< * after further study of the manual and testing with a 'scope */ void pwmSetClock (int divisor){ uint32_t pwm_control ; divisor &= 4095 ; if (initialised){ 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 usleep(110) ; // prevents clock going sloooow while ((*(clk + PWMCLK_CNTL) & 0x80) != 0) // Wait for clock to be !BUSY usleep(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 } } /** * \brief nastavi mod dnaeho pinu * \param pin [in] cislo GPIO pinu * \param mode [in] mod pinu -> in/out/altx * * Nastavi pin jako vstupni / vystupni, pripadne nastavi slternativni funkci. */ void pinMode (int pin, int mode){ int fSel, shift, alt ; if ((pin & PI_GPIO_MASK) == 0){ //exituje-li pin if (!initialised) return; fSel = gpioToGPFSEL[pin] ; shift = gpioToShift [pin] ; if (mode == INPUT) *(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) ; // Sets bits to zero = input else if (mode == OUTPUT) *(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (1 << shift) ; else if (mode == PWM_OUTPUT) { if ((alt = gpioToPwmALT [pin]) == 0) // Not a hardware capable PWM pin return ; // Set pin to PWM mode *(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (alt << shift) ; usleep(110) ; // See comments in pwmSetClockWPi pwmSetMode (PWM_MODE_BAL) ; // Pi default mode pwmSetRange (1024) ; // Default range of 1024 pwmSetClock (32) ; // 19.2 / 32 = 600KHz - Also starts the PWM } } } /** * \brief Precte logickou uroven daneho pinu 0/1 * v pripade neicnicializovaneho GPIO nebo neexistence pinu vrati 0 * \param pin [in] cislo GPIO pinu */ int digitalRead (int pin){ char c ; if ((pin & PI_GPIO_MASK) == 0) // On-Board Pin { if (!initialised) return LOW; if ((*(gpio + gpioToGPLEV [pin]) & (1 << (pin & 31))) != 0) return HIGH ; else return LOW ; }else{ return LOW; } } /** * \brief zapise logickou uroven na dany pin * \param pin [in] cislo GPIO pinu * \param value [in] logicka uroven 0/1 */ void digitalWrite (int pin, int value){ if ((pin & PI_GPIO_MASK) == 0) // On-Board Pin { if (!initialised) return; if (value == LOW) *(gpio + gpioToGPCLR [pin]) = 1 << (pin & 31) ; else *(gpio + gpioToGPSET [pin]) = 1 << (pin & 31) ; } } /** * nastavi hodnotu pwm * \param pin[in] cislo GPIO PWM pinu * \param value[int] sirka plneni pwm (max=1024) */ void pwmWrite (int pin, int value){ if ((pin & PI_GPIO_MASK) == 0) // On-Board Pin { if (!initialised) return; *(pwm + gpioToPwmPort[pin]) = value ; } } /*****************************************************************************/ /************ implementace pwm.h *********************************************/ /*****************************************************************************/ /** * nastavi a pripadne prevrati polaitu pwm pro pohon motoru * \param width[in] sirka plneni (max=1024) */ void pwm_width(int width){ if (width >= 0) { digitalWrite(PWM_DIR, 0); pwmWrite(PWM_WIDTH, width) ; } else { digitalWrite(PWM_DIR, 1); pwmWrite(PWM_WIDTH, -width) ; } } /** * \brief Inicializuje pwm. */ void pwm_init(){ if (initialise() < 0) { fprintf(stderr, "Unable to setup: %s\n", strerror(errno)); return; } pinMode(PWM_WIDTH, PWM_OUTPUT) ; pinMode(PWM_DIR, OUTPUT) ; pwm_width(0); } /** * \brief Odinicializuje pwm. */ void pwm_disable(){ pwm_width(0); pinMode (PWM_WIDTH, INPUT); pinMode (PWM_DIR, INPUT); }