diff --git a/rpio.go b/rpio.go index 5fdc5dd..3d3ce71 100644 --- a/rpio.go +++ b/rpio.go @@ -1,13 +1,15 @@ /* - Package rpio provides GPIO access on the Raspberry PI without any need -for external c libraries (ex: WiringPI or BCM2835). +for external c libraries (eg. WiringPi or BCM2835). Supports simple operations such as: -- Pin mode/direction (input/output) -- Pin write (high/low) -- Pin read (high/low) -- Pull up/down/off + - Pin mode/direction (input/output/clock/pwm) + - Pin write (high/low) + - Pin read (high/low) + - Pull up/down/off +And clock/pwm related oparations: + - Set Clock frequency + - Set Duty cycle Example of use: @@ -23,36 +25,40 @@ Example of use: } The library use the raw BCM2835 pinouts, not the ports as they are mapped -on the output pins for the raspberry pi +on the output pins for the raspberry pi, and not the wiringPi convention. - 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 | -+------+------+--------+ + Rev 2 and 3 Raspberry Pi Rev 1 Raspberry Pi (legacy) + +-----+---------+----------+---------+-----+ +-----+--------+----------+--------+-----+ + | BCM | Name | Physical | Name | BCM | | BCM | Name | Physical | Name | BCM | + +-----+---------+----++----+---------+-----+ +-----+--------+----++----+--------+-----+ + | | 3.3v | 1 || 2 | 5v | | | | 3.3v | 1 || 2 | 5v | | + | 2 | SDA 1 | 3 || 4 | 5v | | | 0 | SDA | 3 || 4 | 5v | | + | 3 | SCL 1 | 5 || 6 | 0v | | | 1 | SCL | 5 || 6 | 0v | | + | 4 | GPIO 7 | 7 || 8 | TxD | 14 | | 4 | GPIO 7 | 7 || 8 | TxD | 14 | + | | 0v | 9 || 10 | RxD | 15 | | | 0v | 9 || 10 | RxD | 15 | + | 17 | GPIO 0 | 11 || 12 | GPIO 1 | 18 | | 17 | GPIO 0 | 11 || 12 | GPIO 1 | 18 | + | 27 | GPIO 2 | 13 || 14 | 0v | | | 21 | GPIO 2 | 13 || 14 | 0v | | + | 22 | GPIO 3 | 15 || 16 | GPIO 4 | 23 | | 22 | GPIO 3 | 15 || 16 | GPIO 4 | 23 | + | | 3.3v | 17 || 18 | GPIO 5 | 24 | | | 3.3v | 17 || 18 | GPIO 5 | 24 | + | 10 | MOSI | 19 || 20 | 0v | | | 10 | MOSI | 19 || 20 | 0v | | + | 9 | MISO | 21 || 22 | GPIO 6 | 25 | | 9 | MISO | 21 || 22 | GPIO 6 | 25 | + | 11 | SCLK | 23 || 24 | CE0 | 8 | | 11 | SCLK | 23 || 24 | CE0 | 8 | + | | 0v | 25 || 26 | CE1 | 7 | | | 0v | 25 || 26 | CE1 | 7 | + | 0 | SDA 0 | 27 || 28 | SCL 0 | 1 | +-----+--------+----++----+--------+-----+ + | 5 | GPIO 21 | 29 || 30 | 0v | | + | 6 | GPIO 22 | 31 || 32 | GPIO 26 | 12 | + | 13 | GPIO 23 | 33 || 34 | 0v | | + | 19 | GPIO 24 | 35 || 36 | GPIO 27 | 16 | + | 26 | GPIO 25 | 37 || 38 | GPIO 28 | 20 | + | | 0v | 39 || 40 | GPIO 29 | 21 | + +-----+---------+----++----+---------+-----+ See the spec for full details of the BCM2835 controller: -http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf + +https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf +and https://elinux.org/BCM2835_datasheet_errata - for errors in that spec */ - package rpio import ( @@ -66,7 +72,7 @@ import ( "unsafe" ) -type Direction uint8 +type Mode uint8 type Pin uint8 type State uint8 type Pull uint8 @@ -74,16 +80,32 @@ type Pull uint8 // Memory offsets for gpio, see the spec for more details const ( bcm2835Base = 0x20000000 - pi1GPIOBase = bcm2835Base + 0x200000 - memLength = 4096 + gpioOffset = 0x200000 + clkOffset = 0x101000 + pwmOffset = 0x20C000 - pinMask uint32 = 7 // 0b111 - pinmode is 3 bits + memLength = 4096 ) -// Pin direction, a pin can be set in Input or Output mode +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 Direction = iota + Input Mode = iota Output + Clock + Pwm ) // State of pin, High / Low @@ -101,9 +123,13 @@ const ( // Arrays for 8 / 32 bit access to memory and a semaphore for write locking var ( - memlock sync.Mutex - mem []uint32 - mem8 []uint8 + memlock sync.Mutex + gpioMem []uint32 + clkMem []uint32 + pwmMem []uint32 + gpioMem8 []uint8 + clkMem8 []uint8 + pwmMem8 []uint8 ) // Set pin as Input @@ -116,6 +142,16 @@ 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) @@ -131,9 +167,19 @@ func (pin Pin) Toggle() { TogglePin(pin) } -// Set pin Direction -func (pin Pin) Mode(dir Direction) { - PinMode(pin, dir) +// Set frequency of Clock or Pwm pin (see doc of SetFreq) +func (pin Pin) Freq(freq int) { + SetFreq(pin, freq) +} + +// Set duty cycle for Pwm pin (see doc of SetDutyCycle) +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) @@ -166,22 +212,51 @@ func (pin Pin) PullOff() { PullMode(pin, PullOff) } -// PinMode sets the direction of a given pin (Input or Output) -func PinMode(pin Pin, direction Direction) { +// 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 - fsel := uint8(pin) / 10 + 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() - if direction == Input { - mem[fsel] = mem[fsel] &^ (pinMask << shift) - } else { - mem[fsel] = (mem[fsel] &^ (pinMask << shift)) | (1 << shift) - } + const pinMask = 7 // 0b111 - pinmode is 3 bits + gpioMem[fselReg] = (gpioMem[fselReg] &^ (pinMask << shift)) | (f << shift) } // WritePin sets a given pin High or Low @@ -199,9 +274,9 @@ func WritePin(pin Pin, state State) { defer memlock.Unlock() if state == Low { - mem[clearReg] = 1 << (p & 31) + gpioMem[clearReg] = 1 << (p & 31) } else { - mem[setReg] = 1 << (p & 31) + gpioMem[setReg] = 1 << (p & 31) } } @@ -211,7 +286,7 @@ func ReadPin(pin Pin) State { // Input level register offset (13 / 14 depending on bank) levelReg := uint8(pin)/32 + 13 - if (mem[levelReg] & (1 << uint8(pin))) != 0 { + if (gpioMem[levelReg] & (1 << uint8(pin&31))) != 0 { return High } @@ -231,94 +306,250 @@ func TogglePin(pin Pin) { func PullMode(pin Pin, pull Pull) { // Pull up/down/off register has offset 38 / 39, pull is 37 - pullClkReg := uint8(pin)/32 + 38 + pullClkReg := pin/32 + 38 pullReg := 37 - shift := (uint8(pin) % 32) + shift := pin % 32 memlock.Lock() defer memlock.Unlock() switch pull { case PullDown, PullUp: - mem[pullReg] = mem[pullReg]&^3 | uint32(pull) + gpioMem[pullReg] = gpioMem[pullReg]&^3 | uint32(pull) case PullOff: - mem[pullReg] = mem[pullReg] &^ 3 + gpioMem[pullReg] = gpioMem[pullReg] &^ 3 } // Wait for value to clock in, this is ugly, sorry :( time.Sleep(time.Microsecond) - mem[pullClkReg] = 1 << shift + gpioMem[pullClkReg] = 1 << shift // Wait for value to clock in time.Sleep(time.Microsecond) - mem[pullReg] = mem[pullReg] &^ 3 - mem[pullClkReg] = 0 + gpioMem[pullReg] = gpioMem[pullReg] &^ 3 + gpioMem[pullClkReg] = 0 } +// Set clock speed for given pin in Clock or Pwm mode +// +// Param freq should be in range 4688Hz - 19.2MHz to prevent unexpected behavior, +// however output frequency of Pwm pins can be further adjusted with SetDutyCycle. +// So 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: +// gp_clk0: pins 4, 20, 32, 34 +// gp_clk1: pins 5, 21, 42, 43 +// gp_clk2: pins 6 and 43 +// pwm_clk: pins 12, 13, 18, 19, 40, 41, 45 +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 channels + 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 pin in M/S mode +// +// |<- duty ->| +// __________ +// _/ \_____________/ +// |<------- cycle -------->| +// +// Output frequency is computed as pwm clock frequency divided by cycle length. +// So, to set Pwm pin to freqency 38kHz with duty cycle 1/4, use this combination: +// +// pin.Pwm() +// pin.DutyCycle(1, 4) +// pin.Freq(38000*4) +// +// Note that some pins share common pwm channel, +// so calling this function will set same duty cycle for all pins belonging to channel. +// The channels are: +// channel 1 (pwm0) for pins 12, 18, 40 +// channel 2 (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: // channel pwm0 + pwmRngReg = 4 + pwmDatReg = 5 + shift = 0 + case 13, 19, 41, 45: // channel pwm1 + pwmRngReg = 8 + pwmDatReg = 9 + shift = 8 + default: + return + } + + const ctlMask = 255 // ctl setting has 8 bits for each channel + 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<