From 4a31f2cfa669b4d06736c5ab92a063d529bb63d2 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Fri, 17 Nov 2017 02:57:42 +0100 Subject: [PATCH 1/6] Add Pwm mode for pins --- rpio.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/rpio.go b/rpio.go index 4ebea04..8e6c9dc 100644 --- a/rpio.go +++ b/rpio.go @@ -91,11 +91,12 @@ func init() { clkBase = base + clkOffset } -// 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 @@ -135,6 +136,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,6 +156,7 @@ func (pin Pin) Toggle() { TogglePin(pin) } +// Set frequency of Clock pin func (pin Pin) Freq(freq int) { SetFreq(pin, freq) } @@ -189,9 +196,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 +207,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 +218,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 } From cc678981b0d0d5a200a88323980922390341c177 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Fri, 17 Nov 2017 09:37:18 +0100 Subject: [PATCH 2/6] Mmap pwm memmory --- rpio.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rpio.go b/rpio.go index 8e6c9dc..f10989a 100644 --- a/rpio.go +++ b/rpio.go @@ -76,6 +76,7 @@ const ( bcm2835Base = 0x20000000 gpioOffset = 0x200000 clkOffset = 0x101000 + pwmOffset = 0x20C000 memLength = 4096 ) @@ -83,12 +84,14 @@ 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, Clock or Pwm mode @@ -117,8 +120,10 @@ var ( memlock sync.Mutex gpioMem []uint32 clkMem []uint32 + pwmMem []uint32 gpioMem8 []uint8 clkMem8 []uint8 + pwmMem8 []uint8 ) // Set pin as Input @@ -388,7 +393,7 @@ func Open() (err error) { // Open fd for rw mem access; try dev/mem first (need root) file, err = os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, 0) - if os.IsPermission(err) { // try gpiomem otherwise (some extra functions like clock setting wont work) + if os.IsPermission(err) { // try gpiomem otherwise (some extra functions like clock and pwm setting wont work) file, err = os.OpenFile("/dev/gpiomem", os.O_RDWR|os.O_SYNC, 0) } if err != nil { @@ -412,6 +417,11 @@ func Open() (err error) { return } + pwmMem, pwmMem8, err = memMap(file.Fd(), clkBase) + if err != nil { + return + } + return nil } From 7445bb69a5c243ec3daa577f79ee951f0ab47227 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Sun, 19 Nov 2017 17:23:48 +0100 Subject: [PATCH 3/6] Implement pwm methods --- rpio.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/rpio.go b/rpio.go index f10989a..04314d2 100644 --- a/rpio.go +++ b/rpio.go @@ -4,7 +4,7 @@ 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) +- Pin mode/direction (input/output/clock/pwm) - Pin write (high/low) - Pin read (high/low) - Pull up/down/off @@ -166,6 +166,11 @@ 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) @@ -295,9 +300,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() @@ -322,37 +327,44 @@ 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) +// (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 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 chanels + clkCtlReg += 12 + clkDivReg += 13 + StopPwm() + defer StartPwm() default: return } @@ -386,6 +398,62 @@ 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 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 + pwmDatReg = 4 + pwmRngReg = 5 + shift = 0 + case 13, 19, 41, 45: // chanel pwm1 + pwmDatReg = 8 + pwmDatReg = 9 + shift = 8 + default: + return + } + + const ctlMask = 255 // ctl setting has 8 bits for each chanel + const msen = 1 << 7 // use M/S transition instead of pwm algorithm + + // reset settings + pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg]&^(ctlMask< Date: Mon, 20 Nov 2017 09:29:43 +0100 Subject: [PATCH 4/6] Fix pwmMem and some pwm Regs --- rpio.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/rpio.go b/rpio.go index 04314d2..388ff95 100644 --- a/rpio.go +++ b/rpio.go @@ -363,7 +363,7 @@ func SetFreq(pin Pin, freq int) { case 12, 13, 40, 41, 45, 18, 19: // pwm_clk - shared clk for both pwm chanels clkCtlReg += 12 clkDivReg += 13 - StopPwm() + StopPwm() // pwm clk busy wont go down without stopping pwm first defer StartPwm() default: return @@ -418,11 +418,11 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { switch pin { case 12, 18, 40: // chanel pwm0 - pwmDatReg = 4 - pwmRngReg = 5 + pwmRngReg = 4 + pwmDatReg = 5 shift = 0 case 13, 19, 41, 45: // chanel pwm1 - pwmDatReg = 8 + pwmRngReg = 8 pwmDatReg = 9 shift = 8 default: @@ -430,14 +430,17 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { } 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< Date: Mon, 20 Nov 2017 10:22:14 +0100 Subject: [PATCH 5/6] Improve doc --- rpio.go | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/rpio.go b/rpio.go index 388ff95..98016d6 100644 --- a/rpio.go +++ b/rpio.go @@ -161,12 +161,12 @@ func (pin Pin) Toggle() { TogglePin(pin) } -// Set frequency of Clock 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 +// Set duty cycle for Pwm pin (see doc of SetDutyCycle) func (pin Pin) DutyCycle(dutyLen, cycleLen uint32) { SetDutyCycle(pin, dutyLen, cycleLen) } @@ -329,14 +329,14 @@ func PullMode(pin Pin, pull Pull) { // 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) +// Param freq should be in range 4688Hz - 19.2MHz to prevent unexpected behavior, +// hovewer output frequency of Pwm pins can be further adjusted with setDutyCycle. +// (Thus 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 +// 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. 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 @@ -360,7 +360,7 @@ func SetFreq(pin Pin, freq int) { case 6, 43: // clk2 clkCtlReg += 4 clkDivReg += 5 - case 12, 13, 40, 41, 45, 18, 19: // pwm_clk - shared clk for both pwm chanels + 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 @@ -400,14 +400,21 @@ func SetFreq(pin Pin, freq int) { // Set cycle length (range) and duty length (data) for pwm in M/S mode // -// |<- duty ->| -// __________ -// _/ \___________/ -// |<------ cycle ------->| +// |<- 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 +// 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 belongig to channel. +// It is channel 1 (pwm0) for pins 12, 18, 40, and channel 2 (pwm1) for pins 13, 19, 41, 45. func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { const pwmCtlReg = 0 var ( @@ -417,11 +424,11 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { ) switch pin { - case 12, 18, 40: // chanel pwm0 + case 12, 18, 40: // channel pwm0 pwmRngReg = 4 pwmDatReg = 5 shift = 0 - case 13, 19, 41, 45: // chanel pwm1 + case 13, 19, 41, 45: // channel pwm1 pwmRngReg = 8 pwmDatReg = 9 shift = 8 @@ -429,7 +436,7 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { return } - const ctlMask = 255 // ctl setting has 8 bits for each chanel + 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 @@ -443,14 +450,14 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { // NOTE without root permission this changes will simply do nothing successfully } -// Stop pwm for both chanels +// Stop pwm for both channels func StopPwm() { const pwmCtlReg = 0 const pwen = 1 pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg] &^ (pwen<<8 | pwen) } -// Start pwm for both chanels +// Start pwm for both channels func StartPwm() { const pwmCtlReg = 0 const pwen = 1 From ec49196c8ca3181812b5e630af0f2b11228b1668 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Mon, 20 Nov 2017 12:14:52 +0100 Subject: [PATCH 6/6] Update doc overview --- rpio.go | 99 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/rpio.go b/rpio.go index 98016d6..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/pwm) -- 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 ( @@ -208,8 +214,8 @@ func (pin 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 +// 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 @@ -330,13 +336,16 @@ func PullMode(pin Pin, pull Pull) { // Set clock speed for given pin in Clock or Pwm mode // // Param freq should be in range 4688Hz - 19.2MHz to prevent unexpected behavior, -// hovewer output frequency of Pwm pins can be further adjusted with setDutyCycle. -// (Thus for smaller frequencies use Pwm pin with large cycle range or implement custom software clock using output pin and sleep.) +// 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). -// Also all pwm pins (12, 13, 18, 19, 40, 41, 45) share same source clock. +// 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 @@ -398,23 +407,25 @@ 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 in M/S mode +// 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: +// 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.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 belongig to channel. -// It is channel 1 (pwm0) for pins 12, 18, 40, and channel 2 (pwm1) for pins 13, 19, 41, 45. +// 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 (