/* Package rpio provides GPIO access on the Raspberry PI without any need for external c libraries (ex: WiringPI or BCM2835). Supports simple operations such as: - Pin mode/direction (input/output/clock/pwm) - Pin write (high/low) - Pin read (high/low) - Pull up/down/off Example of use: rpio.Open() defer rpio.Close() pin := rpio.Pin(4) pin.Output() for { pin.Toggle() time.Sleep(time.Second) } The library use the raw BCM2835 pinouts, not the ports as they are mapped on the output pins for the raspberry pi Rev 1 Raspberry Pi +------+------+--------+ | GPIO | Phys | Name | +------+------+--------+ | 0 | 3 | SDA | | 1 | 5 | SCL | | 4 | 7 | GPIO 7 | | 7 | 26 | CE1 | | 8 | 24 | CE0 | | 9 | 21 | MISO | | 10 | 19 | MOSI | | 11 | 23 | SCLK | | 14 | 8 | TxD | | 15 | 10 | RxD | | 17 | 11 | GPIO 0 | | 18 | 12 | GPIO 1 | | 21 | 13 | GPIO 2 | | 22 | 15 | GPIO 3 | | 23 | 16 | GPIO 4 | | 24 | 18 | GPIO 5 | | 25 | 22 | GPIO 6 | +------+------+--------+ See the spec for full details of the BCM2835 controller: http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf */ package rpio import ( "bytes" "encoding/binary" "os" "reflect" "sync" "syscall" "time" "unsafe" ) type Mode uint8 type Pin uint8 type State uint8 type Pull uint8 // Memory offsets for gpio, see the spec for more details const ( bcm2835Base = 0x20000000 gpioOffset = 0x200000 clkOffset = 0x101000 pwmOffset = 0x20C000 memLength = 4096 ) var ( gpioBase int64 clkBase int64 pwmBase int64 ) func init() { base := getBase() gpioBase = base + gpioOffset clkBase = base + clkOffset pwmBase = base + pwmOffset } // Pin mode, a pin can be set in Input or Output, Clock or Pwm mode const ( Input Mode = iota Output Clock Pwm ) // State of pin, High / Low const ( Low State = iota High ) // Pull Up / Down / Off const ( PullOff Pull = iota PullDown PullUp ) // Arrays for 8 / 32 bit access to memory and a semaphore for write locking var ( memlock sync.Mutex gpioMem []uint32 clkMem []uint32 pwmMem []uint32 gpioMem8 []uint8 clkMem8 []uint8 pwmMem8 []uint8 ) // Set pin as Input func (pin Pin) Input() { PinMode(pin, Input) } // Set pin as Output func (pin Pin) Output() { PinMode(pin, Output) } // Set pin as Clock func (pin Pin) Clock() { PinMode(pin, Clock) } // Set pin as Pwm func (pin Pin) Pwm() { PinMode(pin, Pwm) } // Set pin High func (pin Pin) High() { WritePin(pin, High) } // Set pin Low func (pin Pin) Low() { WritePin(pin, Low) } // Toggle pin state func (pin Pin) Toggle() { TogglePin(pin) } // Set frequency of Clock pin func (pin Pin) Freq(freq int) { SetFreq(pin, freq) } // Set duty cycle for pwm pin func (pin Pin) DutyCycle(dutyLen, cycleLen uint32) { SetDutyCycle(pin, dutyLen, cycleLen) } // Set pin Mode func (pin Pin) Mode(mode Mode) { PinMode(pin, mode) } // Set pin state (high/low) func (pin Pin) Write(state State) { WritePin(pin, state) } // Read pin state (high/low) func (pin Pin) Read() State { return ReadPin(pin) } // Set a given pull up/down mode func (pin Pin) Pull(pull Pull) { PullMode(pin, pull) } // Pull up pin func (pin Pin) PullUp() { PullMode(pin, PullUp) } // Pull down pin func (pin Pin) PullDown() { PullMode(pin, PullDown) } // Disable pullup/down on pin func (pin Pin) PullOff() { PullMode(pin, PullOff) } // PinMode sets the mode (direction) of a given pin (Input, Output, Clock or Pwm) // // Clock is possible only for pins 4, 5, 6, 20, 21 // Pwm is possible only for pins 12, 13, 18, 19 func PinMode(pin Pin, mode Mode) { // Pin fsel register, 0 or 1 depending on bank fselReg := uint8(pin) / 10 shift := (uint8(pin) % 10) * 3 f := uint32(0) const alt0 = 4 // 100 const alt5 = 2 // 010 switch mode { case Input: f = 0 // 000 case Output: f = 1 // 001 case Clock: switch pin { case 4, 5, 6, 32, 34, 42, 43, 44: f = alt0 case 20, 21: f = alt5 default: return } case Pwm: switch pin { case 12, 13, 40, 41, 45: f = alt0 case 18, 19: f = alt5 default: return } } memlock.Lock() defer memlock.Unlock() const pinMask = 7 // 0b111 - pinmode is 3 bits gpioMem[fselReg] = (gpioMem[fselReg] &^ (pinMask << shift)) | (f << shift) } // WritePin sets a given pin High or Low // by setting the clear or set registers respectively func WritePin(pin Pin, state State) { p := uint8(pin) // Clear register, 10 / 11 depending on bank // Set register, 7 / 8 depending on bank clearReg := p/32 + 10 setReg := p/32 + 7 memlock.Lock() defer memlock.Unlock() if state == Low { gpioMem[clearReg] = 1 << (p & 31) } else { gpioMem[setReg] = 1 << (p & 31) } } // Read the state of a pin func ReadPin(pin Pin) State { // Input level register offset (13 / 14 depending on bank) levelReg := uint8(pin)/32 + 13 if (gpioMem[levelReg] & (1 << uint8(pin&31))) != 0 { return High } return Low } // Toggle a pin state (high -> low -> high) // TODO: probably possible to do this much faster without read func TogglePin(pin Pin) { switch ReadPin(pin) { case Low: pin.High() case High: pin.Low() } } func PullMode(pin Pin, pull Pull) { // Pull up/down/off register has offset 38 / 39, pull is 37 pullClkReg := pin/32 + 38 pullReg := 37 shift := pin % 32 memlock.Lock() defer memlock.Unlock() switch pull { case PullDown, PullUp: gpioMem[pullReg] = gpioMem[pullReg]&^3 | uint32(pull) case PullOff: gpioMem[pullReg] = gpioMem[pullReg] &^ 3 } // Wait for value to clock in, this is ugly, sorry :( time.Sleep(time.Microsecond) gpioMem[pullClkReg] = 1 << shift // Wait for value to clock in time.Sleep(time.Microsecond) gpioMem[pullReg] = gpioMem[pullReg] &^ 3 gpioMem[pullClkReg] = 0 } // Set clock speed for given pin in Clock or Pwm mode // // freq should be in range 4688Hz - 19.2MHz to prevent unexpected behavior // (for smaller frequencies use Pwm pin with large cycle range or implement custom software clock using output pin and sleep) // // Note that some pins share the same clock source, it means that // changing frequency for one pin will change it also for all pins within a group // The groups are: clk0 (4, 20, 32, 34), clk1 (5, 21, 42, 43) and clk2 (6 and 43) // Also all pwm pins (12, 13, 18, 19, 40, 41, 45) share same source clock, // but final output frequency of pwm chanel can be adjusted individually with setDutyCycle func SetFreq(pin Pin, freq int) { // TODO: would be nice to choose best clock source depending on target frequency, oscilator is used for now const sourceFreq = 19200000 // oscilator frequency const divMask = 4095 // divi and divf have 12 bits each divi := uint32(sourceFreq / freq) divf := uint32(((sourceFreq % freq) << 12) / freq) divi &= divMask divf &= divMask clkCtlReg := 28 clkDivReg := 28 switch pin { case 4, 20, 32, 34: // clk0 clkCtlReg += 0 clkDivReg += 1 case 5, 21, 42, 44: // clk1 clkCtlReg += 2 clkDivReg += 3 case 6, 43: // clk2 clkCtlReg += 4 clkDivReg += 5 case 12, 13, 40, 41, 45, 18, 19: // pwm_clk - shared clk for both pwm chanels clkCtlReg += 12 clkDivReg += 13 StopPwm() // pwm clk busy wont go down without stopping pwm first defer StartPwm() default: return } mash := uint32(1 << 9) // 1-stage MASH if divi < 2 || divf == 0 { mash = 0 } memlock.Lock() defer memlock.Unlock() const PASSWORD = 0x5A000000 const busy = 1 << 7 const enab = 1 << 4 const src = 1 << 0 // oscilator clkMem[clkCtlReg] = PASSWORD | (clkMem[clkCtlReg] &^ enab) // stop gpio clock (without changing src or mash) for clkMem[clkCtlReg]&busy != 0 { time.Sleep(time.Microsecond * 10) } // ... and wait for not busy clkMem[clkCtlReg] = PASSWORD | mash | src // set mash and source (without enabling clock) clkMem[clkDivReg] = PASSWORD | (divi << 12) | divf // set dividers // mash and src can not be changed in same step as enab, to prevent lock-up and glitches time.Sleep(time.Microsecond * 10) // ... so wait for them to take effect clkMem[clkCtlReg] = PASSWORD | mash | src | enab // finally start clock // NOTE without root permission this changes will simply do nothing successfully } // Set cycle length (range) and duty length (data) for pwm in M/S mode // // |<- duty ->| // __________ // _/ \___________/ // |<------ cycle ------->| // // Note that some pins share common pwm chanel, // so calling this function will set same duty cycle for all pins belonig to chanel // Its chanel pwm0 for pins 12, 18, 40, and pwm1 for pins 13, 19, 41, 45 func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { const pwmCtlReg = 0 var ( pwmDatReg uint pwmRngReg uint shift uint // offset inside ctlReg ) switch pin { case 12, 18, 40: // chanel pwm0 pwmRngReg = 4 pwmDatReg = 5 shift = 0 case 13, 19, 41, 45: // chanel pwm1 pwmRngReg = 8 pwmDatReg = 9 shift = 8 default: return } const ctlMask = 255 // ctl setting has 8 bits for each chanel const pwen = 1 << 0 // enable pwm const msen = 1 << 7 // use M/S transition instead of pwm algorithm // reset settings pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg]&^(ctlMask<