forked from stianeikeland/go-rpio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rpio.go
874 lines (744 loc) · 22 KB
/
rpio.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
/*
Package rpio provides GPIO access on the Raspberry PI without any need
for external c libraries (eg. WiringPi or BCM2835).
Supports simple operations such as:
- Pin mode/direction (input/output/clock/pwm,alt0,alt1,alt2,alt3,alt4,alt5)
- Pin write (high/low)
- Pin read (high/low)
- Pin edge detection (no/rise/fall/any)
- Pull up/down/off
Also clock/pwm related oparations:
- Set Clock frequency
- Set Duty cycle
And SPI oparations:
- SPI transmit/recieve/exchange bytes
- Set speed
- Chip select
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, and not the wiringPi convention.
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:
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
and https://elinux.org/BCM2835_datasheet_errata - for errors in that spec
Changes to support the BCM2711, used on the Raspberry Pi 4, were cribbed from https://github.com/RPi-Distro/raspi-gpio/
*/
package rpio
import (
"bytes"
"encoding/binary"
"errors"
"os"
"reflect"
"sync"
"syscall"
"time"
"unsafe"
)
type Mode uint8
type Pin uint8
type State uint8
type Pull uint8
type Edge uint8
// Memory offsets for gpio, see the spec for more details
const (
bcm2835Base = 0x20000000
gpioOffset = 0x200000
clkOffset = 0x101000
pwmOffset = 0x20C000
spiOffset = 0x204000
intrOffset = 0x00B000
memLength = 4096
)
// BCM 2711 has a different mechanism for pull-up/pull-down/enable
const (
GPPUPPDN0 = 57 // Pin pull-up/down for pins 15:0
GPPUPPDN1 = 58 // Pin pull-up/down for pins 31:16
GPPUPPDN2 = 59 // Pin pull-up/down for pins 47:32
GPPUPPDN3 = 60 // Pin pull-up/down for pins 57:48
)
var (
gpioBase int64
clkBase int64
pwmBase int64
spiBase int64
intrBase int64
irqsBackup uint64
)
func init() {
base := getBase()
gpioBase = base + gpioOffset
clkBase = base + clkOffset
pwmBase = base + pwmOffset
spiBase = base + spiOffset
intrBase = base + intrOffset
}
// Pin mode, a pin can be set in Input or Output, Clock or Pwm mode
const (
Input Mode = iota
Output
Clock
Pwm
Spi
Alt0
Alt1
Alt2
Alt3
Alt4
Alt5
)
// State of pin, High / Low
const (
Low State = iota
High
)
// Which PWM algorithm to use, Balanced or Mark/Space
const (
Balanced = true
MarkSpace = false
)
// Pull Up / Down / Off
const (
PullOff Pull = iota
PullDown
PullUp
PullNone
)
// Edge events
const (
NoEdge Edge = iota
RiseEdge
FallEdge
AnyEdge = RiseEdge | FallEdge
)
// Arrays for 8 / 32 bit access to memory and a semaphore for write locking
var (
memlock sync.Mutex
gpioMem []uint32
clkMem []uint32
pwmMem []uint32
spiMem []uint32
intrMem []uint32
gpioMem8 []uint8
clkMem8 []uint8
pwmMem8 []uint8
spiMem8 []uint8
intrMem8 []uint8
)
// Input: Set pin as Input
func (pin Pin) Input() {
PinMode(pin, Input)
}
// Output: Set pin as Output
func (pin Pin) Output() {
PinMode(pin, Output)
}
// Clock: Set pin as Clock
func (pin Pin) Clock() {
PinMode(pin, Clock)
}
// Pwm: Set pin as Pwm
func (pin Pin) Pwm() {
PinMode(pin, Pwm)
}
// High: Set pin High
func (pin Pin) High() {
WritePin(pin, High)
}
// Low: Set pin Low
func (pin Pin) Low() {
WritePin(pin, Low)
}
// Toggle pin state
func (pin Pin) Toggle() {
TogglePin(pin)
}
// Freq: Set frequency of Clock or Pwm pin (see doc of SetFreq)
func (pin Pin) Freq(freq int) {
SetFreq(pin, freq)
}
// DutyCycle: Set duty cycle for Pwm pin (see doc of SetDutyCycle)
func (pin Pin) DutyCycle(dutyLen, cycleLen uint32) {
SetDutyCycle(pin, dutyLen, cycleLen)
}
// DutyCycleWithPwmMode: Set duty cycle for Pwm pin while also specifying which PWM
// mode to use, Balanced or MarkSpace (see doc of SetDutyCycleWithPwmMode)
func (pin Pin) DutyCycleWithPwmMode(dutyLen, cycleLen uint32, mode bool) {
SetDutyCycleWithPwmMode(pin, dutyLen, cycleLen, mode)
}
// Mode: Set pin Mode
func (pin Pin) Mode(mode Mode) {
PinMode(pin, mode)
}
// Write: 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)
}
// Pull: Set a given pull up/down mode
func (pin Pin) Pull(pull Pull) {
PullMode(pin, pull)
}
// PullUp: Pull up pin
func (pin Pin) PullUp() {
PullMode(pin, PullUp)
}
// PullDown: Pull down pin
func (pin Pin) PullDown() {
PullMode(pin, PullDown)
}
// PullOff: Disable pullup/down on pin
func (pin Pin) PullOff() {
PullMode(pin, PullOff)
}
func (pin Pin) ReadPull() Pull {
if !isBCM2711() {
return PullNone // Can't read pull-up/pull-down state on other Pi boards
}
reg := GPPUPPDN0 + (uint8(pin) >> 4)
bits := gpioMem[reg] >> ((uint8(pin) & 0xf) << 1) & 0x3
switch bits {
case 0:
return PullOff
case 1:
return PullUp
case 2:
return PullDown
default:
return PullNone // Invalid
}
}
// Detect: Enable edge event detection on pin
func (pin Pin) Detect(edge Edge) {
DetectEdge(pin, edge)
}
// EdgeDetected checks edge event on pin
func (pin Pin) EdgeDetected() bool {
return EdgeDetected(pin)
}
// PinMode sets the mode of a given pin (Input, Output, Clock, Pwm or Spi)
//
// Clock is possible only for pins 4, 5, 6, 20, 21.
// Pwm is possible only for pins 12, 13, 18, 19.
//
// Spi mode should not be set by this directly, use SpiBegin instead.
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 in = 0 // 000
const out = 1 // 001
const alt0 = 4 // 100
const alt1 = 5 // 101
const alt2 = 6 // 110
const alt3 = 7 // 111
const alt4 = 3 // 011
const alt5 = 2 // 010
switch mode {
case Input:
f = in
case Output:
f = out
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
}
case Spi:
switch pin {
case 7, 8, 9, 10, 11: // SPI0
f = alt0
case 35, 36, 37, 38, 39: // SPI0
f = alt0
case 16, 17, 18, 19, 20, 21: // SPI1
f = alt4
case 40, 41, 42, 43, 44, 45: // SPI2
f = alt4
default:
return
}
case Alt0:
f = alt0
case Alt1:
f = alt1
case Alt2:
f = alt2
case Alt3:
f = alt3
case Alt4:
f = alt4
case Alt5:
f = alt5
}
memlock.Lock()
defer memlock.Unlock()
const pinMask = 7 // 111 - 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)
// Set register, 7 / 8 depending on bank
// Clear register, 10 / 11 depending on bank
setReg := p/32 + 7
clearReg := p/32 + 10
memlock.Lock()
if state == Low {
gpioMem[clearReg] = 1 << (p & 31)
} else {
gpioMem[setReg] = 1 << (p & 31)
}
memlock.Unlock() // not deferring saves ~600ns
}
// ReadPin reads 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
}
// TogglePin: Toggle a pin state (high -> low -> high)
func TogglePin(pin Pin) {
p := uint8(pin)
setReg := p/32 + 7
clearReg := p/32 + 10
levelReg := p/32 + 13
bit := uint32(1 << (p & 31))
memlock.Lock()
if (gpioMem[levelReg] & bit) != 0 {
gpioMem[clearReg] = bit
} else {
gpioMem[setReg] = bit
}
memlock.Unlock()
}
// DetectEdge: Enable edge event detection on pin.
//
// Combine with pin.EdgeDetected() to check whether event occured.
//
// Note that using this function might conflict with the same functionality of other gpio library.
//
// It also clears previously detected event of this pin if there was any.
//
// Note that call with RiseEdge will disable previously set FallEdge detection and vice versa.
// You have to call with AnyEdge, to enable detection for both edges.
// To disable previously enabled detection call it with NoEdge.
//
// WARNING: this might make your Pi unresponsive, if this happens, you should either run the code as root,
// or add `dtoverlay=gpio-no-irq` to `/boot/config.txt` and restart your pi,
func DetectEdge(pin Pin, edge Edge) {
if edge != NoEdge {
// disable GPIO event interruption to prevent freezing in some cases
DisableIRQs(1<<49 | 1<<52) // gpio_int[0] and gpio_int[3]
}
p := uint8(pin)
// Rising edge detect enable register (19/20 depending on bank)
// Falling edge detect enable register (22/23 depending on bank)
// Event detect status register (16/17)
renReg := p/32 + 19
fenReg := p/32 + 22
edsReg := p/32 + 16
bit := uint32(1 << (p & 31))
if edge&RiseEdge > 0 { // set bit
gpioMem[renReg] |= bit
} else { // clear bit
gpioMem[renReg] &^= bit
}
if edge&FallEdge > 0 { // set bit
gpioMem[fenReg] |= bit
} else { // clear bit
gpioMem[fenReg] &^= bit
}
gpioMem[edsReg] = bit // to clear outdated detection
}
// EdgeDetected checks whether edge event occured since last call
// or since detection was enabled
//
// There is no way (yet) to handle interruption caused by edge event, you have to use polling.
//
// Event detection has to be enabled first, by pin.Detect(edge)
func EdgeDetected(pin Pin) bool {
p := uint8(pin)
// Event detect status register (16/17)
edsReg := p/32 + 16
test := gpioMem[edsReg] & (1 << (p & 31))
gpioMem[edsReg] = test // set bit to clear it
return test != 0
}
func PullMode(pin Pin, pull Pull) {
memlock.Lock()
defer memlock.Unlock()
if isBCM2711() {
pullreg := GPPUPPDN0 + (pin >> 4)
pullshift := (pin & 0xf) << 1
var p uint32
switch pull {
case PullOff:
p = 0
case PullUp:
p = 1
case PullDown:
p = 2
}
// This is verbatim C code from raspi-gpio.c
pullbits := gpioMem[pullreg]
pullbits &= ^(3 << pullshift)
pullbits |= (p << pullshift)
gpioMem[pullreg] = pullbits
} else {
// Pull up/down/off register has offset 38 / 39, pull is 37
pullClkReg := pin/32 + 38
pullReg := 37
shift := pin % 32
switch pull {
case PullDown, PullUp:
gpioMem[pullReg] |= uint32(pull)
case PullOff:
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] &^= 3
gpioMem[pullClkReg] = 0
}
}
// SetFreq: 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, 44
// 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
sourceFreq := 19200000 // oscilator frequency
if isBCM2711() {
sourceFreq = 52000000
}
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
}
// SetDutyCycle: 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.
//
// NOTE without root permission this function will simply do nothing successfully
func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) {
SetDutyCycleWithPwmMode(pin, dutyLen, cycleLen, MarkSpace)
}
// SetDutyCycleWithPwmMode extends SetDutyCycle to allow for the specification of the PWM
// algorithm to be used, Balanced or Mark/Space. The constants Balanced or MarkSpace
// as the value. See 'SetDutyCycle(pin, dutyLen, cycleLen)' above for more information
// regarding how to use 'SetDutyCycleWithPwmMode()'.
//
// NOTE without root permission this function will simply do nothing successfully
func SetDutyCycleWithPwmMode(pin Pin, dutyLen, cycleLen uint32, mode bool) {
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
var msen uint32 = 0
// The MSEN1 field in the CTL register is at offset 7. This block starts with the assumption
// that 'msen' will be associated with channel 'pwm0'. If this is not the case, 'msen' will
// be further shifted in the next code block below to the MSEN2 field at offset 15.
if mode == MarkSpace {
msen = 1 << 7
}
// Shifting 'pwen' and 'msen' puts the associated values at the correct offset within the CTL
// register ('pwmCtlReg'). In addition, 'msen' is associated with a PWM channel depending on the
// value of 'pin' (see above). 'msen' will either stay at offset 7, as set above for channel 'pwm0',
// or be shifted 8 bits if the the associated 'pin' is on channel 'pwm1'.
pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg]&^(ctlMask<<shift) | msen<<shift | pwen<<shift
// set duty cycle
pwmMem[pwmDatReg] = dutyLen
pwmMem[pwmRngReg] = cycleLen
time.Sleep(time.Microsecond * 10)
}
// StopPwm: Stop pwm for both channels
func StopPwm() {
const pwmCtlReg = 0
const pwen = 1
pwmMem[pwmCtlReg] &^= pwen<<8 | pwen
}
// StartPwm starts pwm for both channels
func StartPwm() {
const pwmCtlReg = 0
const pwen = 1
pwmMem[pwmCtlReg] |= pwen<<8 | pwen
}
// EnableIRQs: Enables given IRQs (by setting bit to 1 at intended position).
// See 'ARM peripherals interrupts table' in pheripherals datasheet.
// WARNING: you can corrupt your system, only use this if you know what you are doing.
func EnableIRQs(irqs uint64) {
const irqEnable1 = 0x210 / 4
const irqEnable2 = 0x214 / 4
intrMem[irqEnable1] = uint32(irqs) // IRQ 0..31
intrMem[irqEnable2] = uint32(irqs >> 32) // IRQ 32..63
}
// DisableIRQs: Disables given IRQs (by setting bit to 1 at intended position).
// See 'ARM peripherals interrupts table' in pheripherals datasheet.
// WARNING: you can corrupt your system, only use this if you know what you are doing.
func DisableIRQs(irqs uint64) {
const irqDisable1 = 0x21C / 4
const irqDisable2 = 0x220 / 4
intrMem[irqDisable1] = uint32(irqs) // IRQ 0..31
intrMem[irqDisable2] = uint32(irqs >> 32) // IRQ 32..63
}
func backupIRQs() {
const irqEnable1 = 0x210 / 4
const irqEnable2 = 0x214 / 4
irqsBackup = uint64(intrMem[irqEnable2])<<32 | uint64(intrMem[irqEnable1])
}
// Open and memory map GPIO memory range from /dev/mem .
// Some reflection magic is used to convert it to a unsafe []uint32 pointer
func Open() (err error) {
var file *os.File
// Open fd for rw mem access; try dev/mem first (need root)
file, err = os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm)
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, os.ModePerm)
}
if err != nil {
return
}
// FD can be closed after memory mapping
defer file.Close()
memlock.Lock()
defer memlock.Unlock()
// Memory map GPIO registers to slice
gpioMem, gpioMem8, err = memMap(file.Fd(), gpioBase)
if err != nil {
return
}
// Memory map clock registers to slice
clkMem, clkMem8, err = memMap(file.Fd(), clkBase)
if err != nil {
return
}
// Memory map pwm registers to slice
pwmMem, pwmMem8, err = memMap(file.Fd(), pwmBase)
if err != nil {
return
}
// Memory map spi registers to slice
spiMem, spiMem8, err = memMap(file.Fd(), spiBase)
if err != nil {
return
}
// Memory map interruption registers to slice
intrMem, intrMem8, err = memMap(file.Fd(), intrBase)
if err != nil {
return
}
backupIRQs() // back up enabled IRQs, to restore it later
return nil
}
func memMap(fd uintptr, base int64) (mem []uint32, mem8 []byte, err error) {
mem8, err = syscall.Mmap(
int(fd),
base,
memLength,
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_SHARED,
)
if err != nil {
return
}
// Convert mapped byte memory to unsafe []uint32 pointer, adjust length as needed
header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem8))
header.Len /= (32 / 8) // (32 bit = 4 bytes)
header.Cap /= (32 / 8)
mem = *(*[]uint32)(unsafe.Pointer(&header))
return
}
// Close unmaps GPIO memory
func Close() error {
EnableIRQs(irqsBackup) // Return IRQs to state where it was before - just to be nice
memlock.Lock()
defer memlock.Unlock()
for _, mem8 := range [][]uint8{gpioMem8, clkMem8, pwmMem8, spiMem8, intrMem8} {
if err := syscall.Munmap(mem8); err != nil {
return err
}
}
return nil
}
// Read /proc/device-tree/soc/ranges and determine the base address.
// Use the default Raspberry Pi 1 base address if this fails.
func readBase(offset int64) (int64, error) {
ranges, err := os.Open("/proc/device-tree/soc/ranges")
defer ranges.Close()
if err != nil {
return 0, err
}
b := make([]byte, 4)
n, err := ranges.ReadAt(b, offset)
if n != 4 || err != nil {
return 0, err
}
buf := bytes.NewReader(b)
var out uint32
err = binary.Read(buf, binary.BigEndian, &out)
if err != nil {
return 0, err
}
if out == 0 {
return 0, errors.New("rpio: GPIO base address not found")
}
return int64(out), nil
}
func getBase() int64 {
// Pi 2 & 3 GPIO base address is at offset 4
b, err := readBase(4)
if err == nil {
return b
}
// Pi 4 GPIO base address is as offset 8
b, err = readBase(8)
if err == nil {
return b
}
// Default to Pi 1
return int64(bcm2835Base)
}
// The Pi 4 uses a BCM 2711, which has different register offsets and base addresses than the rest of the Pi family (so far). This
// helper function checks if we're on a 2711 and hence a Pi 4
func isBCM2711() bool {
return gpioMem[GPPUPPDN3] != 0x6770696f
}