Skip to content

Commit a5cd970

Browse files
committed
gd terminator
1 parent 977650b commit a5cd970

File tree

2 files changed

+75
-34
lines changed

2 files changed

+75
-34
lines changed

docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ services:
1111
- /dev/vcio:/dev/vcio
1212
- /dev/i2c-1:/dev/i2c-1
1313
- /dev/input/event0:/dev/input/event0
14+
- /dev/fb1:/dev/fb1
15+
volumes:
16+
- /sys/class/graphics:/sys/class/graphics:ro
1417
command: ["-addr=:9110", "-sensehat"]
1518
profiles:
1619
- sensehat

pkg/sensehat/sensehat.go

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ Then reboot or unload the modules manually:
2929
3030
sudo rmmod hts221_i2c hts221 lps25_i2c lps25 st_pressure st_sensors_i2c st_sensors
3131
32-
# LED Matrix and Joystick Conflicts
32+
# LED Matrix and Joystick
3333
3434
The LED matrix and joystick are controlled by an ATTINY88 at I2C address 0x46.
35-
The kernel's rpisense-fb framebuffer driver claims this device by default.
35+
The kernel's rpisense-fb framebuffer driver exposes this as /dev/fb1.
3636
37-
To use the LED functions, also blacklist the framebuffer driver:
37+
The LED functions in this package use the framebuffer device, so ensure the
38+
rpisense_fb module is loaded (it is by default on Raspberry Pi OS):
3839
39-
blacklist rpisense_fb
40-
blacklist rpisense_js
40+
lsmod | grep rpisense
4141
42-
Then reboot or unload:
42+
If running in Docker, expose the framebuffer device and sysfs:
4343
44-
sudo rmmod rpisense_fb rpisense_js
44+
devices:
45+
- /dev/fb1:/dev/fb1
46+
volumes:
47+
- /sys/class/graphics:/sys/class/graphics:ro
4548
4649
*/
4750

@@ -90,13 +93,14 @@ const (
9093

9194
// SenseHat represents a Raspberry Pi Sense HAT device.
9295
type SenseHat struct {
93-
i2cFile *os.File
94-
hasColorSensor bool
96+
i2cFile *os.File
97+
fbFile *os.File // LED matrix framebuffer
98+
hasColorSensor bool
9599
colorSensorAddr uint8 // I2C address of detected color sensor (0x29 or 0x39)
96-
joystickFile *os.File
97-
joystickEvents []JoystickEvent
98-
joystickMu sync.Mutex
99-
joystickDone chan struct{} // signals joystick goroutine to stop
100+
joystickFile *os.File
101+
joystickEvents []JoystickEvent
102+
joystickMu sync.Mutex
103+
joystickDone chan struct{} // signals joystick goroutine to stop
100104

101105
// HTS221 calibration coefficients
102106
h0RH, h1RH float64
@@ -159,6 +163,12 @@ func New() (*SenseHat, error) {
159163
// Try to initialize color sensor (Sense HAT v2 only)
160164
hat.hasColorSensor = hat.initColorSensor() == nil
161165

166+
// Initialize LED framebuffer
167+
if err := hat.initFramebuffer(); err != nil {
168+
f.Close()
169+
return nil, fmt.Errorf("failed to initialize LED framebuffer: %w", err)
170+
}
171+
162172
// Initialize joystick
163173
hat.initJoystick()
164174

@@ -174,6 +184,9 @@ func (s *SenseHat) Close() error {
174184
if s.joystickFile != nil {
175185
s.joystickFile.Close()
176186
}
187+
if s.fbFile != nil {
188+
s.fbFile.Close()
189+
}
177190
return s.i2cFile.Close()
178191
}
179192

@@ -669,43 +682,68 @@ func GetCPUTemperature() (float64, error) {
669682
return 0, scanner.Err()
670683
}
671684

685+
// initFramebuffer finds and opens the Sense HAT LED framebuffer device.
686+
func (s *SenseHat) initFramebuffer() error {
687+
// Find the framebuffer device with name "RPi-Sense FB"
688+
for i := 0; i < 10; i++ {
689+
namePath := fmt.Sprintf("/sys/class/graphics/fb%d/name", i)
690+
name, err := os.ReadFile(namePath)
691+
if err != nil {
692+
continue
693+
}
694+
if strings.TrimSpace(string(name)) == "RPi-Sense FB" {
695+
fbPath := fmt.Sprintf("/dev/fb%d", i)
696+
fb, err := os.OpenFile(fbPath, os.O_RDWR, 0)
697+
if err != nil {
698+
return fmt.Errorf("failed to open framebuffer %s: %w", fbPath, err)
699+
}
700+
s.fbFile = fb
701+
return nil
702+
}
703+
}
704+
return fmt.Errorf("Sense HAT framebuffer not found")
705+
}
706+
672707
// ClearLEDs turns off all LEDs on the 8x8 matrix.
673708
func (s *SenseHat) ClearLEDs() error {
674-
if err := setI2CAddr(s.i2cFile, addrLEDMatrix); err != nil {
709+
if s.fbFile == nil {
710+
return fmt.Errorf("framebuffer not initialized")
711+
}
712+
// Write 128 bytes of zeros (64 pixels × 2 bytes RGB565)
713+
buf := make([]byte, 128)
714+
if _, err := s.fbFile.Seek(0, 0); err != nil {
675715
return err
676716
}
677-
// Write 129 bytes: 1 register address + 128 bytes of zeros (64 pixels × 2 bytes RGB565)
678-
buf := make([]byte, 129)
679-
buf[0] = 0x00 // Register address
680-
_, err := s.i2cFile.Write(buf)
717+
_, err := s.fbFile.Write(buf)
681718
return err
682719
}
683720

684721
// SetPixel sets a single pixel on the 8x8 LED matrix.
685-
// x and y are 0-7, r/g/b are 0-255.
686-
// Uses RGB565 format: 5 bits red, 6 bits green, 5 bits blue packed into 2 bytes.
722+
// x (column) and y (row) are 0-7, r/g/b are 0-255.
723+
// Uses RGB565 format via framebuffer.
687724
func (s *SenseHat) SetPixel(x, y int, r, g, b uint8) error {
688725
if x < 0 || x > 7 || y < 0 || y > 7 {
689726
return fmt.Errorf("pixel coordinates out of range: (%d, %d)", x, y)
690727
}
691-
if err := setI2CAddr(s.i2cFile, addrLEDMatrix); err != nil {
692-
return err
728+
if s.fbFile == nil {
729+
return fmt.Errorf("framebuffer not initialized")
693730
}
694-
// Pixel offset in framebuffer: row-major order, 2 bytes per pixel (RGB565)
695-
// y = row, x = column
696-
offset := (y*8 + x) * 2
697-
// Pack RGB into RGB565: (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3)
698-
r5 := uint16(r >> 3)
699-
g6 := uint16(g >> 2)
700-
b5 := uint16(b >> 3)
731+
// Pixel offset: row-major order, 2 bytes per pixel
732+
offset := int64((y*8 + x) * 2)
733+
// Pack RGB into RGB565: 5 bits red, 6 bits green, 5 bits blue
734+
r5 := uint16(r>>3) & 0x1F
735+
g6 := uint16(g>>2) & 0x3F
736+
b5 := uint16(b>>3) & 0x1F
701737
rgb565 := (r5 << 11) | (g6 << 5) | b5
702-
// Write register address followed by 2-byte RGB565 value (little-endian)
738+
// Write as little-endian 16-bit value (struct.pack('H', ...) in Python)
703739
buf := []byte{
704-
byte(offset),
705-
byte(rgb565 & 0xFF), // low byte
706-
byte((rgb565 >> 8) & 0xFF), // high byte
740+
byte(rgb565 & 0xFF),
741+
byte((rgb565 >> 8) & 0xFF),
742+
}
743+
if _, err := s.fbFile.Seek(offset, 0); err != nil {
744+
return err
707745
}
708-
_, err := s.i2cFile.Write(buf)
746+
_, err := s.fbFile.Write(buf)
709747
return err
710748
}
711749

0 commit comments

Comments
 (0)