Raspberry Pi GPIO - Go

Go examples for accessing Raspberry Pi GPIO, I2C, SPI, and PWM using periph.io.


Installation

 1# Install Go (if not already installed)
 2wget https://go.dev/dl/go1.21.5.linux-arm64.tar.gz
 3sudo tar -C /usr/local -xzf go1.21.5.linux-arm64.tar.gz
 4
 5# Add to PATH
 6echo 'export PATH=$PATH:/usr/local/bin/go/bin' >> ~/.bashrc
 7source ~/.bashrc
 8
 9# Verify
10go version

GPIO (Digital I/O)

Setup Project

1mkdir rpi-gpio && cd rpi-gpio
2go mod init rpi-gpio
3go get periph.io/x/conn/v3/gpio
4go get periph.io/x/conn/v3/gpio/gpioreg
5go get periph.io/x/host/v3

Basic GPIO

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "time"
 7
 8    "periph.io/x/conn/v3/gpio"
 9    "periph.io/x/conn/v3/gpio/gpioreg"
10    "periph.io/x/host/v3"
11)
12
13func main() {
14    // Initialize periph
15    if _, err := host.Init(); err != nil {
16        log.Fatal(err)
17    }
18
19    // Get GPIO pin (BCM numbering)
20    ledPin := gpioreg.ByName("GPIO18")
21    if ledPin == nil {
22        log.Fatal("Failed to find GPIO18")
23    }
24
25    // Set as output
26    if err := ledPin.Out(gpio.Low); err != nil {
27        log.Fatal(err)
28    }
29
30    // Blink LED
31    for i := 0; i < 10; i++ {
32        ledPin.Out(gpio.High)
33        fmt.Println("LED ON")
34        time.Sleep(500 * time.Millisecond)
35
36        ledPin.Out(gpio.Low)
37        fmt.Println("LED OFF")
38        time.Sleep(500 * time.Millisecond)
39    }
40}

GPIO Input with Button

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "time"
 7
 8    "periph.io/x/conn/v3/gpio"
 9    "periph.io/x/conn/v3/gpio/gpioreg"
10    "periph.io/x/host/v3"
11)
12
13func main() {
14    if _, err := host.Init(); err != nil {
15        log.Fatal(err)
16    }
17
18    // LED output
19    ledPin := gpioreg.ByName("GPIO18")
20    ledPin.Out(gpio.Low)
21
22    // Button input with pull-up
23    buttonPin := gpioreg.ByName("GPIO23")
24    if err := buttonPin.In(gpio.PullUp, gpio.BothEdges); err != nil {
25        log.Fatal(err)
26    }
27
28    fmt.Println("Press button to toggle LED")
29
30    ledState := false
31
32    for {
33        // Wait for button press
34        buttonPin.WaitForEdge(-1)
35
36        // Debounce
37        time.Sleep(50 * time.Millisecond)
38
39        // Check if button is pressed (active low with pull-up)
40        if buttonPin.Read() == gpio.Low {
41            ledState = !ledState
42            if ledState {
43                ledPin.Out(gpio.High)
44                fmt.Println("LED ON")
45            } else {
46                ledPin.Out(gpio.Low)
47                fmt.Println("LED OFF")
48            }
49        }
50    }
51}

PWM

 1package main
 2
 3import (
 4    "log"
 5    "time"
 6
 7    "periph.io/x/conn/v3/gpio"
 8    "periph.io/x/conn/v3/gpio/gpioreg"
 9    "periph.io/x/conn/v3/physic"
10    "periph.io/x/host/v3"
11)
12
13func main() {
14    if _, err := host.Init(); err != nil {
15        log.Fatal(err)
16    }
17
18    // Get PWM-capable pin
19    pwmPin := gpioreg.ByName("GPIO18")
20    if pwmPin == nil {
21        log.Fatal("Failed to find GPIO18")
22    }
23
24    // Check if pin supports PWM
25    if pwm, ok := pwmPin.(gpio.PinPWM); ok {
26        // Set PWM frequency (1kHz)
27        freq := 1000 * physic.Hertz
28
29        // Fade in
30        for duty := gpio.Duty(0); duty <= gpio.DutyMax; duty += gpio.DutyMax / 100 {
31            if err := pwm.PWM(duty, freq); err != nil {
32                log.Fatal(err)
33            }
34            time.Sleep(10 * time.Millisecond)
35        }
36
37        // Fade out
38        for duty := gpio.DutyMax; duty >= 0; duty -= gpio.DutyMax / 100 {
39            if err := pwm.PWM(duty, freq); err != nil {
40                log.Fatal(err)
41            }
42            time.Sleep(10 * time.Millisecond)
43        }
44
45        // Turn off
46        pwm.PWM(0, freq)
47    } else {
48        log.Fatal("Pin does not support PWM")
49    }
50}

I2C

Setup

1# Enable I2C
2sudo raspi-config
3# Interface Options > I2C > Enable
4
5# Add dependencies
6go get periph.io/x/conn/v3/i2c
7go get periph.io/x/conn/v3/i2c/i2creg

I2C Communication

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6
 7    "periph.io/x/conn/v3/i2c"
 8    "periph.io/x/conn/v3/i2c/i2creg"
 9    "periph.io/x/host/v3"
10)
11
12func main() {
13    if _, err := host.Init(); err != nil {
14        log.Fatal(err)
15    }
16
17    // Open I2C bus
18    bus, err := i2creg.Open("")
19    if err != nil {
20        log.Fatal(err)
21    }
22    defer bus.Close()
23
24    // Device address
25    const deviceAddr = 0x48
26
27    // Create device
28    dev := &i2c.Dev{Bus: bus, Addr: uint16(deviceAddr)}
29
30    // Write byte
31    if err := dev.Tx([]byte{0x01, 0x83}, nil); err != nil {
32        log.Fatal(err)
33    }
34
35    // Read byte
36    read := make([]byte, 1)
37    if err := dev.Tx([]byte{0x00}, read); err != nil {
38        log.Fatal(err)
39    }
40    fmt.Printf("Read: 0x%02X\n", read[0])
41
42    // Read block
43    block := make([]byte, 2)
44    if err := dev.Tx([]byte{0x00}, block); err != nil {
45        log.Fatal(err)
46    }
47    fmt.Printf("Block: %v\n", block)
48}

I2C Example: BMP280 Sensor

 1package main
 2
 3import (
 4    "encoding/binary"
 5    "fmt"
 6    "log"
 7
 8    "periph.io/x/conn/v3/i2c"
 9    "periph.io/x/conn/v3/i2c/i2creg"
10    "periph.io/x/host/v3"
11)
12
13type BMP280 struct {
14    dev    *i2c.Dev
15    digT1  uint16
16    digT2  int16
17    digT3  int16
18}
19
20func NewBMP280(bus i2c.Bus, addr uint16) (*BMP280, error) {
21    dev := &i2c.Dev{Bus: bus, Addr: addr}
22    
23    bmp := &BMP280{dev: dev}
24    
25    // Read calibration data
26    calib := make([]byte, 6)
27    if err := dev.Tx([]byte{0x88}, calib); err != nil {
28        return nil, err
29    }
30    
31    bmp.digT1 = binary.LittleEndian.Uint16(calib[0:2])
32    bmp.digT2 = int16(binary.LittleEndian.Uint16(calib[2:4]))
33    bmp.digT3 = int16(binary.LittleEndian.Uint16(calib[4:6]))
34    
35    // Configure sensor
36    if err := dev.Tx([]byte{0xF4, 0x27}, nil); err != nil {
37        return nil, err
38    }
39    
40    return bmp, nil
41}
42
43func (b *BMP280) ReadTemperature() (float64, error) {
44    // Read raw temperature
45    data := make([]byte, 3)
46    if err := b.dev.Tx([]byte{0xFA}, data); err != nil {
47        return 0, err
48    }
49    
50    adcT := int32(data[0])<<12 | int32(data[1])<<4 | int32(data[2])>>4
51    
52    // Compensate
53    var1 := (float64(adcT)/16384.0 - float64(b.digT1)/1024.0) * float64(b.digT2)
54    var2 := ((float64(adcT)/131072.0 - float64(b.digT1)/8192.0) * 
55             (float64(adcT)/131072.0 - float64(b.digT1)/8192.0)) * float64(b.digT3)
56    
57    tFine := var1 + var2
58    temperature := tFine / 5120.0
59    
60    return temperature, nil
61}
62
63func main() {
64    if _, err := host.Init(); err != nil {
65        log.Fatal(err)
66    }
67
68    bus, err := i2creg.Open("")
69    if err != nil {
70        log.Fatal(err)
71    }
72    defer bus.Close()
73
74    sensor, err := NewBMP280(bus, 0x76)
75    if err != nil {
76        log.Fatal(err)
77    }
78
79    temp, err := sensor.ReadTemperature()
80    if err != nil {
81        log.Fatal(err)
82    }
83
84    fmt.Printf("Temperature: %.2f°C\n", temp)
85}

SPI

Setup

1# Enable SPI
2sudo raspi-config
3# Interface Options > SPI > Enable
4
5# Add dependencies
6go get periph.io/x/conn/v3/spi
7go get periph.io/x/conn/v3/spi/spireg

SPI Communication

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6
 7    "periph.io/x/conn/v3/physic"
 8    "periph.io/x/conn/v3/spi"
 9    "periph.io/x/conn/v3/spi/spireg"
10    "periph.io/x/host/v3"
11)
12
13func main() {
14    if _, err := host.Init(); err != nil {
15        log.Fatal(err)
16    }
17
18    // Open SPI port
19    port, err := spireg.Open("")
20    if err != nil {
21        log.Fatal(err)
22    }
23    defer port.Close()
24
25    // Configure SPI
26    conn, err := port.Connect(1*physic.MegaHertz, spi.Mode0, 8)
27    if err != nil {
28        log.Fatal(err)
29    }
30
31    // Transfer data
32    write := []byte{0x01, 0x02, 0x03}
33    read := make([]byte, len(write))
34
35    if err := conn.Tx(write, read); err != nil {
36        log.Fatal(err)
37    }
38
39    fmt.Printf("Sent: %v\n", write)
40    fmt.Printf("Received: %v\n", read)
41}

SPI Example: MCP3008 ADC

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "time"
 7
 8    "periph.io/x/conn/v3/physic"
 9    "periph.io/x/conn/v3/spi"
10    "periph.io/x/conn/v3/spi/spireg"
11    "periph.io/x/host/v3"
12)
13
14type MCP3008 struct {
15    conn spi.Conn
16}
17
18func NewMCP3008(port spi.Port) (*MCP3008, error) {
19    conn, err := port.Connect(1350*physic.KiloHertz, spi.Mode0, 8)
20    if err != nil {
21        return nil, err
22    }
23    return &MCP3008{conn: conn}, nil
24}
25
26func (m *MCP3008) ReadChannel(channel int) (int, error) {
27    if channel < 0 || channel > 7 {
28        return 0, fmt.Errorf("invalid channel: %d", channel)
29    }
30
31    // Build command
32    cmd := []byte{
33        0x01,
34        byte((8 + channel) << 4),
35        0x00,
36    }
37
38    // Send command and read response
39    reply := make([]byte, 3)
40    if err := m.conn.Tx(cmd, reply); err != nil {
41        return 0, err
42    }
43
44    // Extract 10-bit value
45    value := int(reply[1]&0x03)<<8 | int(reply[2])
46
47    return value, nil
48}
49
50func (m *MCP3008) ReadVoltage(channel int, vref float64) (float64, error) {
51    value, err := m.ReadChannel(channel)
52    if err != nil {
53        return 0, err
54    }
55
56    voltage := float64(value) * vref / 1023.0
57    return voltage, nil
58}
59
60func main() {
61    if _, err := host.Init(); err != nil {
62        log.Fatal(err)
63    }
64
65    port, err := spireg.Open("")
66    if err != nil {
67        log.Fatal(err)
68    }
69    defer port.Close()
70
71    adc, err := NewMCP3008(port)
72    if err != nil {
73        log.Fatal(err)
74    }
75
76    for {
77        value, err := adc.ReadChannel(0)
78        if err != nil {
79            log.Fatal(err)
80        }
81
82        voltage, err := adc.ReadVoltage(0, 3.3)
83        if err != nil {
84            log.Fatal(err)
85        }
86
87        fmt.Printf("Channel 0: %d (%.2fV)\n", value, voltage)
88        time.Sleep(1 * time.Second)
89    }
90}

Complete Example: Weather Station

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "time"
 7
 8    "periph.io/x/conn/v3/gpio"
 9    "periph.io/x/conn/v3/gpio/gpioreg"
10    "periph.io/x/conn/v3/i2c"
11    "periph.io/x/conn/v3/i2c/i2creg"
12    "periph.io/x/host/v3"
13)
14
15type WeatherStation struct {
16    led    gpio.PinOut
17    button gpio.PinIn
18    sensor *BMP280
19}
20
21func NewWeatherStation() (*WeatherStation, error) {
22    if _, err := host.Init(); err != nil {
23        return nil, err
24    }
25
26    // Setup LED
27    led := gpioreg.ByName("GPIO18")
28    led.Out(gpio.Low)
29
30    // Setup button
31    button := gpioreg.ByName("GPIO23")
32    button.In(gpio.PullUp, gpio.FallingEdge)
33
34    // Setup I2C sensor
35    bus, err := i2creg.Open("")
36    if err != nil {
37        return nil, err
38    }
39
40    sensor, err := NewBMP280(bus, 0x76)
41    if err != nil {
42        return nil, err
43    }
44
45    return &WeatherStation{
46        led:    led,
47        button: button,
48        sensor: sensor,
49    }, nil
50}
51
52func (ws *WeatherStation) Run() {
53    fmt.Println("Weather Station Running. Press button to read temperature.")
54
55    for {
56        // Wait for button press
57        ws.button.WaitForEdge(-1)
58
59        // Debounce
60        time.Sleep(50 * time.Millisecond)
61
62        if ws.button.Read() == gpio.Low {
63            ws.led.Out(gpio.High)
64
65            temp, err := ws.sensor.ReadTemperature()
66            if err != nil {
67                log.Println("Error reading temperature:", err)
68            } else {
69                fmt.Printf("Temperature: %.2f°C\n", temp)
70            }
71
72            time.Sleep(500 * time.Millisecond)
73            ws.led.Out(gpio.Low)
74        }
75    }
76}
77
78func main() {
79    station, err := NewWeatherStation()
80    if err != nil {
81        log.Fatal(err)
82    }
83
84    station.Run()
85}

Best Practices

  1. Always initialize host - Call host.Init() first
  2. Check pin availability - Verify pins exist before use
  3. Handle errors properly - Don't ignore errors
  4. Close resources - Use defer for cleanup
  5. Use appropriate voltage - RPi GPIO is 3.3V
  6. Debounce inputs - Add delays for button presses
  7. Use goroutines - For concurrent GPIO operations

Related Snippets