diff --git a/rpio.go b/rpio.go index 4ebea04..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/clock) -- 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 ( @@ -76,6 +82,7 @@ const ( bcm2835Base = 0x20000000 gpioOffset = 0x200000 clkOffset = 0x101000 + pwmOffset = 0x20C000 memLength = 4096 ) @@ -83,19 +90,22 @@ const ( 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 mode, or clock +// 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 @@ -116,8 +126,10 @@ var ( memlock sync.Mutex gpioMem []uint32 clkMem []uint32 + pwmMem []uint32 gpioMem8 []uint8 clkMem8 []uint8 + pwmMem8 []uint8 ) // Set pin as Input @@ -135,6 +147,11 @@ 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) @@ -150,10 +167,16 @@ func (pin Pin) Toggle() { TogglePin(pin) } +// 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) @@ -189,9 +212,10 @@ func (pin Pin) PullOff() { PullMode(pin, PullOff) } -// PinMode sets the mode (direction) of a given pin (Input, Output or Clock) +// PinMode sets the mode (direction) of a given pin (Input, Output, Clock or Pwm) // -// Clock is possible only for some pins (bcm 4, 5, 6, 20, 21) +// 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 @@ -199,6 +223,9 @@ func PinMode(pin Pin, mode Mode) { shift := (uint8(pin) % 10) * 3 f := uint32(0) + const alt0 = 4 // 100 + const alt5 = 2 // 010 + switch mode { case Input: f = 0 // 000 @@ -207,9 +234,18 @@ func PinMode(pin Pin, mode Mode) { case Clock: switch pin { case 4, 5, 6, 32, 34, 42, 43, 44: - f = 4 // 100 - alt0 + f = alt0 case 20, 21: - f = 2 // 010 - alt5 + f = alt5 + default: + return + } + case Pwm: + switch pin { + case 12, 13, 40, 41, 45: + f = alt0 + case 18, 19: + f = alt5 default: return } @@ -270,9 +306,9 @@ 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() @@ -297,37 +333,47 @@ func PullMode(pin Pin, pull Pull) { } -// Set clock speed for given pin +// 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 implement custom software clock using output pin and sleep) +// 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: clk0 (4, 20, 32, 34), clk1 (5, 21, 42, 43) and clk2 (6 and 43) +// 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 maxUint12 = 4095 + const divMask = 4095 // divi and divf have 12 bits each divi := uint32(sourceFreq / freq) divf := uint32(((sourceFreq % freq) << 12) / freq) - divi &= maxUint12 - divf &= maxUint12 + divi &= divMask + divf &= divMask clkCtlReg := 28 - clkDivReg := 29 + clkDivReg := 28 switch pin { case 4, 20, 32, 34: // clk0 clkCtlReg += 0 - clkDivReg += 0 + clkDivReg += 1 case 5, 21, 42, 44: // clk1 clkCtlReg += 2 - clkDivReg += 2 + clkDivReg += 3 case 6, 43: // clk2 clkCtlReg += 4 - clkDivReg += 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 } @@ -361,6 +407,74 @@ func SetFreq(pin Pin, freq int) { // 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<