Browse Source

Initial import

master
maze 10 months ago
parent
commit
824fdeb32e
49 changed files with 3508 additions and 0 deletions
  1. +7
    -0
      .gitignore
  2. +216
    -0
      clock.go
  3. +6
    -0
      cmd/matrix/driver_arm.go
  4. +82
    -0
      cmd/matrix/main.go
  5. +29
    -0
      config.go
  6. BIN
      data/font/16bit.ttf
  7. BIN
      data/font/32bit.ttf
  8. BIN
      data/font/8bit.ttf
  9. BIN
      data/font/ar_delaney_regular_24.png
  10. +18
    -0
      data/font/ar_delaney_regular_24.xml
  11. BIN
      data/font/ar_essence_regular_24.png
  12. +18
    -0
      data/font/ar_essence_regular_24.xml
  13. BIN
      data/font/ar_julian_regular_24.png
  14. +18
    -0
      data/font/ar_julian_regular_24.xml
  15. +18
    -0
      data/font/asimov_regular_24.xml
  16. +17
    -0
      data/font/butch_regular_24.xml
  17. BIN
      data/font/calligraphic_regular_24.png
  18. +17
    -0
      data/font/calligraphic_regular_24.xml
  19. BIN
      data/font/child's_play_normal_24.png
  20. +17
    -0
      data/font/child's_play_normal_24.xml
  21. +18
    -0
      data/font/comicate_regular_24.xml
  22. BIN
      data/font/narpassword00000.ttf
  23. BIN
      data/font/pixelmix.ttf
  24. BIN
      data/font/quadrit.ttf
  25. +348
    -0
      display.go
  26. +34
    -0
      driver/color.go
  27. +18
    -0
      driver/driver.go
  28. +364
    -0
      driver/internal/bcm283x/gpio.go
  29. +260
    -0
      driver/internal/bcm283x/spio.go
  30. +484
    -0
      driver/internal/rpi/gpio.go
  31. +88
    -0
      driver/matrix.go
  32. +17
    -0
      driver/oled/driver.go
  33. +284
    -0
      driver/oled/ssd1305.go
  34. +1
    -0
      driver/rgbled/driver.go
  35. +172
    -0
      driver/rgbled/driver_arm.go
  36. +152
    -0
      driver/text/driver.go
  37. +197
    -0
      font.go
  38. +34
    -0
      font_test.go
  39. +20
    -0
      go.mod
  40. +86
    -0
      go.sum
  41. +199
    -0
      loader.go
  42. +27
    -0
      matrix.conf
  43. +149
    -0
      server.go
  44. +15
    -0
      template/animation_set.html
  45. +17
    -0
      template/index.html
  46. BIN
      testdata/animations/sttng.ani
  47. BIN
      testdata/animations/sttng.pal
  48. BIN
      testdata/animations/sttng.vni
  49. +61
    -0
      util.go

+ 7
- 0
.gitignore View File

@ -15,3 +15,10 @@
# Dependency directories (remove the comment below to include it)
# vendor/
# IDE
/.idea
# Build artifacts
/matrix
/matrix-*
/test

+ 216
- 0
clock.go View File

@ -0,0 +1,216 @@
package matrix
import (
"image"
"image/color"
"image/draw"
"strings"
"time"
"golang.org/x/image/colornames"
"golang.org/x/image/font/gofont/gomono"
)
var defaultClockConfig = &ClockConfig{
Format: "15:04:05",
Font: "data/font/quadrit.ttf",
FontSize: 10,
Blink: true,
Alpha: 0xff,
}
type ClockConfig struct {
Format string `toml:"format"`
Font string `toml:"font"`
FontSize int `toml:"font_size"`
Blink bool `toml:"blink"`
Slide bool `toml:"slide"`
Color string `toml:"color"`
Brightness uint16 `toml:"brightness"`
Alpha uint8 `toml:"alpha"`
}
type Clock struct {
config *ClockConfig
buffer *image.RGBA
bounds image.Rectangle
font Font
fontSize image.Point
color color.RGBA
glyphs map[rune]*image.RGBA
colorStep uint8
}
func NewClock(config *ClockConfig) (*Clock, error) {
var c = colornames.Gray
if config.Color != "" {
var err error
if c, err = parseColor(config.Color); err != nil {
return nil, err
}
}
c.A = config.Alpha
if config.Brightness == 0 || config.Brightness >= 0x100 {
config.Brightness = 0x100
}
c = adjustBrightness(c, uint32(config.Brightness))
var (
f Font
err error
)
if config.Font == "" {
f, _ = UseTTFFont(gomono.TTF, 16)
} else if f, err = LoadTTFFont(config.Font, config.FontSize); err != nil {
return nil, err
}
return &Clock{
config: config,
buffer: newLayer(image.Pt(128, 32)),
bounds: image.Rect(0, 0, 128, 32),
font: f,
fontSize: f.Size(),
color: c,
glyphs: make(map[rune]*image.RGBA),
}, nil
}
func (c *Clock) Frames() <-chan *image.RGBA {
frames := make(chan *image.RGBA)
if c.config.Slide {
go c.slide(frames)
} else {
go c.tick(frames)
}
return frames
}
func (c *Clock) tick(frames chan<- *image.RGBA) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
var (
now = <-ticker.C
text = now.Format(c.config.Format)
offset = image.Pt(0, (32-c.fontSize.Y)/2)
)
if c.config.Blink && now.Second()&1 == 1 {
text = strings.Replace(text, ":", " ", -1)
}
for _, r := range text {
//if g := c.font.Glyph(r); g != nil {
if g := c.glyph(r); g != nil {
//draw.DrawMask(c.buffer, c.bounds.Add(offset), c.mask, image.Point{}, g, image.Point{}, draw.Src)
if r == ':' {
offset.X += 8
}
draw.Draw(c.buffer, c.bounds.Add(offset), g, image.Point{}, draw.Src)
if r == ':' {
offset.X -= 8
}
}
offset.X += c.fontSize.X
}
frames <- c.buffer
}
}
func (c *Clock) slide(frames chan<- *image.RGBA) {
// Render at 25 fps
ticker := time.NewTicker(time.Millisecond * 40)
defer ticker.Stop()
var (
last string
step = int(c.fontSize.Y / 10) // 20 fps, fall in the first half second
fall []int
empty = image.NewUniform(color.Transparent)
)
if step == 0 {
step = 1
}
var prev = time.Now()
for {
var (
now = <-ticker.C
text = now.Format(c.config.Format)
offset = image.Pt((128-len(text)*c.fontSize.X)/2, (32-c.fontSize.Y)/2)
)
if c.config.Blink && now.Nanosecond() > int(time.Second/2/time.Nanosecond) {
text = strings.Replace(text, ":", " ", -1)
}
if last == "" {
last = text
fall = make([]int, len(last))
} else {
prev := []rune(last)
for i, r := range text {
if r != prev[i] && r != ':' && r != ' ' {
//fall[i] = c.fontSize.Y
fall[i] = 32 - c.fontSize.Y
}
}
}
// Clear the line below if we're falling.
for _, i := range fall {
if i > 0 {
y := offset.Y + c.fontSize.Y - i
draw.Draw(c.buffer, image.Rect(0, y-1, 128, y+1), empty, image.Point{}, draw.Src)
break
}
}
drawing:
for i, r := range text {
//if g := c.font.Glyph(r); g != nil {
if g := c.glyph(r); g != nil {
// c.colorize(g)
if r == ':' && c.config.Blink && now.Sub(prev) > 500*time.Millisecond {
draw.Draw(c.buffer, c.bounds.Add(offset), empty, image.Point{}, draw.Src)
offset.X += c.fontSize.X
continue drawing
}
draw.Draw(c.buffer, c.bounds.Add(offset).Add(image.Pt(0, -fall[i])), g, image.Point{}, draw.Src)
}
offset.X += c.fontSize.X
if fall[i] > 0 {
fall[i] -= step
if fall[i] < 0 {
fall[i] = 0
}
}
}
frames <- c.buffer
last = text
prev = now
// c.advanceColor()
}
}
func (c *Clock) glyph(r rune) image.Image {
if g, ok := c.glyphs[r]; ok {
return g
}
g := c.font.Glyph(r)
if g != nil {
// Colorize image.
for o := 0; o < len(g.Pix); o += 4 {
if g.Pix[o+3] != 0 {
g.Pix[o+0] = c.color.R
g.Pix[o+1] = c.color.G
g.Pix[o+2] = c.color.B
g.Pix[o+3] = c.color.A
}
}
}
c.glyphs[r] = g
return g
}

+ 6
- 0
cmd/matrix/driver_arm.go View File

@ -0,0 +1,6 @@
package main
import (
_ "maze.io/matrix/driver/oled" // OLED (SPI)
_ "maze.io/matrix/driver/rgbled" // RGB LED matrix
)

+ 82
- 0
cmd/matrix/main.go View File

@ -0,0 +1,82 @@
package main
import (
"fmt"
"log"
"os"
"os/signal"
"maze.io/matrix"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "matrix",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "configuration file",
Value: "matrix.conf",
EnvVars: []string{"MATRIX_CONFIG"},
},
},
Action: run,
}
if err := app.Run(os.Args); err != nil {
fatal(err)
}
}
func run(ctx *cli.Context) error {
config := matrix.DefaultConfig()
if err := config.Load(ctx.String("config")); err != nil {
return err
}
fmt.Printf("config: %#+v\n", config)
clock, err := matrix.NewClock(config.Clock)
if err != nil {
return err
}
loader, err := matrix.NewLoader(config.Loader)
if err != nil {
return err
}
display, err := matrix.New(config.Display, config.Matrix)
if err != nil {
return err
}
go display.RenderClock(clock.Frames())
go loader.RenderRandomTo(display)
notify := make(chan os.Signal)
signal.Notify(notify, os.Interrupt)
signal.Notify(notify, os.Kill)
go func(notify <-chan os.Signal) {
sig := <-notify
fmt.Println()
log.Printf("matrix: received signal %s, closing", sig)
if err := display.Close(); err != nil {
log.Printf("matrix: display shutdown failed: %v", err)
}
os.Exit(0)
}(notify)
server, err := matrix.NewServer(config.Server,
matrix.WithLoader(loader),
matrix.WithOutput(display))
if err != nil {
return err
}
return server.Start()
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "fatal:", err)
os.Exit(1)
}

+ 29
- 0
config.go View File

@ -0,0 +1,29 @@
package matrix
import (
"github.com/BurntSushi/toml"
"maze.io/matrix/driver"
)
func DefaultConfig() *Config {
return &Config{
Matrix: driver.DefaultConfig(),
Display: defaultDisplayConfig,
Server: defaultServerConfig,
Clock: defaultClockConfig,
}
}
type Config struct {
Matrix *driver.Config `toml:"matrix"`
Display *DisplayConfig `toml:"display"`
Server *ServerConfig `toml:"server"`
Loader *LoaderConfig `toml:"loader"`
Clock *ClockConfig `toml:"clock"`
}
func (c *Config) Load(name string) error {
_, err := toml.DecodeFile(name, c)
return err
}

BIN
data/font/16bit.ttf View File


BIN
data/font/32bit.ttf View File


BIN
data/font/8bit.ttf View File


BIN
data/font/ar_delaney_regular_24.png View File

Before After
Width: 256  |  Height: 32  |  Size: 1.3 KiB

+ 18
- 0
data/font/ar_delaney_regular_24.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="AR DELANEY" height="41" style="Regular">
<Char width="11" offset="0 27" rect="1 22 0 4" code=" "/>
<Char width="9" offset="1 20" rect="2 15 7 11" code="."/>
<Char width="24" offset="1 6" rect="10 1 22 25" code="0"/>
<Char width="12" offset="1 6" rect="33 1 9 25" code="1"/>
<Char width="17" offset="1 6" rect="43 1 15 25" code="2"/>
<Char width="17" offset="1 6" rect="59 1 15 25" code="3"/>
<Char width="20" offset="1 6" rect="75 1 18 25" code="4"/>
<Char width="18" offset="1 6" rect="94 1 16 25" code="5"/>
<Char width="19" offset="1 6" rect="111 1 17 25" code="6"/>
<Char width="16" offset="0 6" rect="129 1 15 25" code="7"/>
<Char width="18" offset="1 6" rect="145 1 16 25" code="8"/>
<Char width="19" offset="1 6" rect="162 1 17 25" code="9"/>
<Char width="9" offset="1 10" rect="180 5 7 21" code=":"/>
<Char width="23" offset="1 6" rect="188 1 21 25" code="C"/>
<Char width="11" offset="1 6" rect="210 1 9 14" code="°"/>
</Font>

BIN
data/font/ar_essence_regular_24.png View File

Before After
Width: 256  |  Height: 32  |  Size: 865 B

+ 18
- 0
data/font/ar_essence_regular_24.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="AR ESSENCE" height="41" style="Regular">
<Char width="14" offset="0 29" rect="1 25 0 2" code=" "/>
<Char width="7" offset="1 24" rect="2 20 5 7" code="."/>
<Char width="16" offset="1 8" rect="8 4 14 23" code="0"/>
<Char width="9" offset="1 8" rect="23 4 8 23" code="1"/>
<Char width="14" offset="1 8" rect="32 4 12 23" code="2"/>
<Char width="14" offset="1 8" rect="45 4 12 23" code="3"/>
<Char width="15" offset="-1 7" rect="58 3 15 24" code="4"/>
<Char width="14" offset="1 8" rect="74 4 12 23" code="5"/>
<Char width="16" offset="1 6" rect="87 2 14 25" code="6"/>
<Char width="14" offset="-1 8" rect="102 4 14 24" code="7"/>
<Char width="16" offset="1 8" rect="117 4 14 23" code="8"/>
<Char width="16" offset="1 8" rect="132 4 14 24" code="9"/>
<Char width="8" offset="1 16" rect="147 12 5 15" code=":"/>
<Char width="14" offset="1 8" rect="153 4 12 24" code="C"/>
<Char width="12" offset="1 5" rect="166 1 10 12" code="°"/>
</Font>

BIN
data/font/ar_julian_regular_24.png View File

Before After
Width: 256  |  Height: 32  |  Size: 946 B

+ 18
- 0
data/font/ar_julian_regular_24.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="AR JULIAN" height="36" style="Regular">
<Char width="15" offset="0 26" rect="1 22 0 3" code=" "/>
<Char width="6" offset="1 22" rect="2 18 4 7" code="."/>
<Char width="19" offset="1 5" rect="7 1 17 24" code="0"/>
<Char width="11" offset="0 6" rect="25 2 10 23" code="1"/>
<Char width="17" offset="0 5" rect="36 1 16 24" code="2"/>
<Char width="18" offset="0 5" rect="53 1 17 24" code="3"/>
<Char width="20" offset="0 5" rect="71 1 19 24" code="4"/>
<Char width="17" offset="-1 5" rect="91 1 17 24" code="5"/>
<Char width="19" offset="1 5" rect="109 1 17 24" code="6"/>
<Char width="17" offset="0 7" rect="127 3 17 22" code="7"/>
<Char width="19" offset="1 5" rect="145 1 17 24" code="8"/>
<Char width="19" offset="1 5" rect="163 1 17 24" code="9"/>
<Char width="6" offset="1 12" rect="181 8 4 17" code=":"/>
<Char width="19" offset="1 5" rect="186 1 18 24" code="C"/>
<Char width="11" offset="1 5" rect="205 1 9 11" code="°"/>
</Font>

+ 18
- 0
data/font/asimov_regular_24.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="Asimov" height="33" style="Regular">
<Char width="12" offset="0 25" rect="1 27 0 2" code=" "/>
<Char width="8" offset="0 20" rect="2 22 6 7" code="."/>
<Char width="17" offset="1 -1" rect="9 1 14 28" code="0"/>
<Char width="11" offset="0 -1" rect="24 1 9 28" code="1"/>
<Char width="15" offset="1 -1" rect="34 1 14 28" code="2"/>
<Char width="15" offset="0 -1" rect="49 1 15 28" code="3"/>
<Char width="15" offset="-1 -1" rect="65 1 16 28" code="4"/>
<Char width="15" offset="1 -1" rect="82 1 14 28" code="5"/>
<Char width="17" offset="1 -1" rect="97 1 16 28" code="6"/>
<Char width="15" offset="0 -1" rect="114 1 15 28" code="7"/>
<Char width="15" offset="0 -1" rect="130 1 14 28" code="8"/>
<Char width="15" offset="0 -1" rect="145 1 14 28" code="9"/>
<Char width="7" offset="0 5" rect="160 7 6 21" code=":"/>
<Char width="16" offset="0 -1" rect="167 1 13 28" code="C"/>
<Char width="13" offset="2 2" rect="181 4 9 10" code="°"/>
</Font>

+ 17
- 0
data/font/butch_regular_24.xml View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="Butch" height="42" style="Regular">
<Char width="9" offset="0 34" rect="1 27 0 0" code=" "/>
<Char width="7" offset="0 29" rect="2 22 5 5" code="."/>
<Char width="21" offset="0 10" rect="8 3 18 24" code="0"/>
<Char width="15" offset="0 10" rect="27 3 14 24" code="1"/>
<Char width="20" offset="0 10" rect="42 3 18 24" code="2"/>
<Char width="18" offset="0 10" rect="61 3 17 24" code="3"/>
<Char width="19" offset="0 10" rect="79 3 18 24" code="4"/>
<Char width="18" offset="0 10" rect="98 3 17 24" code="5"/>
<Char width="18" offset="0 9" rect="116 2 17 25" code="6"/>
<Char width="18" offset="0 10" rect="134 3 17 24" code="7"/>
<Char width="19" offset="0 10" rect="152 3 17 24" code="8"/>
<Char width="21" offset="0 10" rect="170 3 18 24" code="9"/>
<Char width="6" offset="0 18" rect="189 11 5 16" code=":"/>
<Char width="21" offset="0 8" rect="195 1 18 26" code="C"/>
</Font>

BIN
data/font/calligraphic_regular_24.png View File

Before After
Width: 256  |  Height: 32  |  Size: 894 B

+ 17
- 0
data/font/calligraphic_regular_24.xml View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="Calligraphic" height="42" style="Regular">
<Char width="9" offset="0 30" rect="1 24 0 2" code=" "/>
<Char width="8" offset="1 24" rect="2 18 7 8" code="."/>
<Char width="18" offset="1 10" rect="10 4 16 22" code="0"/>
<Char width="17" offset="4 11" rect="27 5 7 22" code="1"/>
<Char width="17" offset="1 11" rect="35 5 14 22" code="2"/>
<Char width="17" offset="2 11" rect="50 5 11 24" code="3"/>
<Char width="18" offset="1 11" rect="62 5 17 22" code="4"/>
<Char width="17" offset="2 11" rect="80 5 14 23" code="5"/>
<Char width="18" offset="2 10" rect="95 4 14 22" code="6"/>
<Char width="17" offset="2 10" rect="110 4 14 23" code="7"/>
<Char width="17" offset="1 10" rect="125 4 14 22" code="8"/>
<Char width="18" offset="2 10" rect="140 4 14 23" code="9"/>
<Char width="9" offset="2 13" rect="155 7 7 19" code=":"/>
<Char width="20" offset="1 7" rect="163 1 18 25" code="C"/>
</Font>

BIN
data/font/child's_play_normal_24.png View File

Before After
Width: 256  |  Height: 32  |  Size: 1022 B

+ 17
- 0
data/font/child's_play_normal_24.xml View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="Child's Play" height="34" style="Normal">
<Char width="16" offset="0 25" rect="1 26 0 2" code=" "/>
<Char width="9" offset="0 19" rect="2 20 7 8" code="."/>
<Char width="25" offset="1 3" rect="10 4 22 24" code="0"/>
<Char width="19" offset="1 1" rect="33 2 13 26" code="1"/>
<Char width="25" offset="1 0" rect="47 1 22 26" code="2"/>
<Char width="20" offset="1 0" rect="70 1 16 26" code="3"/>
<Char width="26" offset="1 0" rect="87 1 23 26" code="4"/>
<Char width="26" offset="1 3" rect="111 4 23 23" code="5"/>
<Char width="20" offset="1 1" rect="135 2 16 24" code="6"/>
<Char width="21" offset="1 3" rect="152 4 18 24" code="7"/>
<Char width="20" offset="1 2" rect="171 3 16 24" code="8"/>
<Char width="19" offset="0 1" rect="188 2 15 26" code="9"/>
<Char width="12" offset="1 4" rect="204 5 9 22" code=":"/>
<Char width="24" offset="1 0" rect="214 1 22 27" code="C"/>
</Font>

+ 18
- 0
data/font/comicate_regular_24.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Font size="24" family="Comicate" height="33" style="Regular">
<Char width="9" offset="0 27" rect="1 27 0 0" code=" "/>
<Char width="9" offset="-1 20" rect="2 20 9 9" code="."/>
<Char width="20" offset="1 2" rect="12 2 19 27" code="0"/>
<Char width="11" offset="1 1" rect="32 1 10 22" code="1"/>
<Char width="18" offset="0 3" rect="43 3 17 24" code="2"/>
<Char width="18" offset="-1 7" rect="61 7 20 23" code="3"/>
<Char width="21" offset="0 1" rect="82 1 21 25" code="4"/>
<Char width="19" offset="2 4" rect="104 4 18 25" code="5"/>
<Char width="19" offset="0 1" rect="123 1 19 28" code="6"/>
<Char width="17" offset="1 5" rect="143 5 15 23" code="7"/>
<Char width="21" offset="1 1" rect="159 1 21 24" code="8"/>
<Char width="20" offset="-1 4" rect="181 4 20 24" code="9"/>
<Char width="11" offset="1 10" rect="202 10 10 18" code=":"/>
<Char width="18" offset="0 5" rect="213 5 19 19" code="C"/>
<Char width="0" offset="0 27" rect="233 27 0 0" code="°"/>
</Font>

BIN
data/font/narpassword00000.ttf View File


BIN
data/font/pixelmix.ttf View File


BIN
data/font/quadrit.ttf View File


+ 348
- 0
display.go View File

@ -0,0 +1,348 @@
package matrix
import (
"image"
"image/color"
"image/draw"
"io"
"log"
"math/rand"
"time"
"maze.io/x/pixel/pixeleffect"
"maze.io/x/pixel"
"golang.org/x/image/colornames"
"maze.io/x/dmd"
"maze.io/matrix/driver"
_ "maze.io/matrix/driver/text" // Text driver
)
const (
layerBackground = iota
layerClockBackground
layerAnimation
layerClockForeground
animationFadeOut = 750 * time.Millisecond
animationFadeOutStep = 75 * time.Millisecond
animationShort = time.Second / 3
animationShortRepeat = 5
)
var (
emptyPix []byte
empty *image.RGBA
)
func init() {
emptyPix = make([]byte, 128*32*4)
for i := 0; i < 128*32*4; i += 4 {
emptyPix[i+3] = 0xff
}
empty = &image.RGBA{
Pix: emptyPix,
Stride: 128,
Rect: image.Rectangle{Max: image.Point{X: 128, Y: 32}},
}
}
type DisplayConfig struct {
Type string `toml:"type"`
Color string `toml:"color"`
ClockOnTop bool `toml:"clock_on_top"`
}
var defaultDisplayConfig = &DisplayConfig{
ClockOnTop: true,
}
type Display struct {
driver.Matrix
config *DisplayConfig
bounds image.Rectangle
buffer *image.RGBA
layers [4]*image.RGBA
animationLayer int
animationFrame *image.RGBA
clockLayer int
color color.RGBA
mask image.Image
update chan displayUpdateLayer
render chan struct{}
}
type displayUpdateLayer struct {
layer int
image image.Image
mask image.Image
}
func newDisplayUpdateLayer(layer int, image, mask image.Image) displayUpdateLayer {
return displayUpdateLayer{
layer: layer,
image: image,
mask: mask,
}
}
func New(config *DisplayConfig, driverConfig *driver.Config) (*Display, error) {
if config.Type == "" {
log.Printf("no display type configured, using text")
config.Type = "text"
}
matrix, err := driver.Load(config.Type, driverConfig)
if err != nil {
return nil, err
}
size := matrix.Bounds().Size()
log.Printf("display: using %dx%d driver %s", size.X, size.Y, matrix.Name())
d := &Display{
Matrix: matrix,
config: config,
bounds: matrix.Bounds(),
buffer: newLayer(size),
layers: [4]*image.RGBA{
newLayer(size), // Background
newLayer(size), // Clock background
newLayer(size), // Animation
newLayer(size), // Clock foreground
},
animationLayer: layerAnimation,
animationFrame: newLayer(size),
clockLayer: layerClockForeground,
color: colornames.Green,
mask: image.NewUniform(colornames.Green),
update: make(chan displayUpdateLayer, 64),
render: make(chan struct{}, 1),
}
fillLayer(d.layers[layerBackground], color.Black)
go d.loop()
return d, nil
}
func newLayer(size image.Point) *image.RGBA {
i := image.NewRGBA(image.Rectangle{Max: size})
clearLayer(i)
return i
}
func clearLayer(i *image.RGBA) {
//draw.Draw(i, i.Bounds(), image.NewUniform(color.Transparent), image.Point{}, draw.Src)
copy(i.Pix, emptyPix)
}
func fillLayer(i *image.RGBA, c color.Color) {
var (
rm, gm, bm, am = c.RGBA()
r = uint8(rm)
g = uint8(gm)
b = uint8(bm)
a = uint8(am)
)
for n := 0; n < len(i.Pix); n += 4 {
i.Pix[n+0] = r
i.Pix[n+1] = g
i.Pix[n+2] = b
i.Pix[n+3] = a
}
}
func convertPalette(p color.Palette) []driver.Color {
o := make([]driver.Color, len(p))
for i, c := range p {
o[i] = driver.ToColor(c)
}
return o
}
func (d *Display) loop() {
for update := range d.update {
d.renderLayer(update.layer, update.image, update.mask)
}
}
func (d *Display) renderLayer(layer int, frame, mask image.Image) {
if frame == nil {
return
}
// log.Printf("update: %T %s", frame, frame.Bounds())
if i, ok := frame.(*image.RGBA); ok {
copy(d.layers[layer].Pix, i.Pix)
d.doRender()
return
}
defer func() {
if err := recover(); err != nil {
log.Printf("update failed: %T %v", err, err)
}
}()
if mask != nil {
draw.DrawMask(d.layers[layer], d.bounds, mask, image.Point{}, frame, image.Point{}, draw.Src)
} else {
draw.Draw(d.layers[layer], d.bounds, frame, image.Point{}, draw.Src)
}
d.doRender()
}
func (d *Display) Render() error {
select {
case d.render <- struct{}{}:
default:
}
return nil
}
func (d *Display) doRender() {
// log.Println("display: rendering")
copy(d.buffer.Pix, d.layers[0].Pix)
if d.clockLayer == layerClockBackground {
draw.Draw(d.buffer, d.bounds, d.layers[d.clockLayer], image.Point{}, draw.Over)
}
draw.Draw(d.buffer, d.bounds, d.layers[d.animationLayer], image.Point{}, draw.Over)
if d.clockLayer == layerClockForeground {
draw.Draw(d.buffer, d.bounds, d.layers[d.clockLayer], image.Point{}, draw.Over)
}
draw.Draw(d.Matrix, d.bounds, d.buffer, image.Point{}, draw.Src)
d.Matrix.Render()
}
func (d *Display) RenderBackground(back *image.RGBA) {
copy(d.layers[layerBackground].Pix, back.Pix)
}
func (d *Display) RenderClock(frames <-chan *image.RGBA) {
for frame := range frames {
select {
case d.update <- newDisplayUpdateLayer(d.clockLayer, frame, nil):
// log.Println("display: update clock frame")
default:
log.Println("display: update buffer full, could not update clock!")
}
}
}
func (d *Display) RenderAnimation(a dmd.Animation) error {
log.Printf("display: render animation %q for %s", a.Name(), a.Duration())
repeat := 1
if a.Duration() <= animationShort {
repeat = animationShortRepeat
}
// Swap clock layers.
if !d.config.ClockOnTop {
clockLayer := layerClockBackground
if a.ClockInFront() {
clockLayer = layerClockForeground
}
if d.clockLayer != clockLayer {
copy(d.layers[clockLayer].Pix, d.layers[d.clockLayer].Pix)
clearLayer(d.layers[d.clockLayer])
d.clockLayer = clockLayer
}
}
var mask image.Image
if a.IsMask() {
mask = d.mask
}
var lastFrame image.Image
for i := 0; i < repeat; i++ {
if err := a.SeekFrame(0); err != nil {
return err
}
var (
frame image.Image
//total = a.Len()
//frames = 1
delay time.Duration
err error
)
// for frames <= total {
for {
if frame, delay, err = a.NextFrame(); err == io.EOF {
break
} else if err != nil {
log.Printf("display: animation error: %v", err)
return err
}
//log.Printf("display: animation frame %d/%d for %s", frames, total, delay)
//frames++
wait := time.After(delay)
select {
case d.update <- newDisplayUpdateLayer(layerAnimation, frame, mask):
default:
log.Println("display: frame drop on animation; update buffer full")
}
<-wait
lastFrame = frame
}
}
if lastFrame != nil {
draw.Draw(d.animationFrame, d.bounds, lastFrame, image.Point{}, draw.Src)
var effect func(time.Duration, pixel.Image) <-chan time.Time
switch d.Matrix.ColorDepth() {
case 2:
// Monochrome fade out.
switch rand.Intn(4) {
case 0:
log.Println("display: fade out horizontal")
effect = pixeleffect.FadeOutHorizontal
case 1:
log.Println("display: fade out vertical")
effect = pixeleffect.FadeOutVertical
case 2:
log.Println("display: fade out diagonal")
effect = pixeleffect.FadeOutDiagonal
case 3:
log.Println("display: fade out dither")
effect = pixeleffect.FadeOutDither
}
default:
switch rand.Intn(5) {
case 0:
log.Println("display: fade out horizontal")
effect = pixeleffect.FadeOutHorizontal
case 1:
log.Println("display: fade out vertical")
effect = pixeleffect.FadeOutVertical
case 2:
log.Println("display: fade out diagonal")
effect = pixeleffect.FadeOutDiagonal
case 3:
log.Println("display: fade out dither")
effect = pixeleffect.FadeOutDither
case 4:
log.Println("display: fade out shift")
effect = pixeleffect.FadeOutShift
}
}
effect = pixeleffect.FadeOutDiagonal
for range effect(animationFadeOut, d.animationFrame) {
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask)
}
draw.Draw(d.animationFrame, d.animationFrame.Rect, image.Transparent, image.Point{}, draw.Src)
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask)
}
return nil
}

+ 34
- 0
driver/color.go View File

@ -0,0 +1,34 @@
package driver
import "image/color"
var ColorModel = color.ModelFunc(colorModel)
type Color uint32
func (c Color) RGBA() (r, g, b, a uint32) {
r = uint32(c>>16) & 0xff
r |= r << 8
g = uint32(c>>8) & 0xff
g |= g << 8
b = uint32(c) & 0xff
b |= b << 8
a = 0xffff
return
}
func ToColor(c color.Color) Color {
if c, ok := c.(Color); ok {
return c
}
r, g, b, _ := c.RGBA()
return Color((r&0xff)<<16 | (g&0xff)<<8 | (b & 0xff))
}
func colorModel(c color.Color) color.Color {
if c, ok := c.(Color); ok {
return c
}
r, g, b, _ := c.RGBA()
return Color((r&0xff)<<16 | (g&0xff)<<8 | (b & 0xff))
}

+ 18
- 0
driver/driver.go View File

@ -0,0 +1,18 @@
package driver
import "fmt"
type Factory func(*Config) (Matrix, error)
var registry = map[string]Factory{}
func Load(name string, config *Config) (Matrix, error) {
if factory, ok := registry[name]; ok {
return factory(config)
}
return nil, fmt.Errorf("driver: unknown driver %q", name)
}
func Register(name string, factory Factory) {
registry[name] = factory
}

+ 364
- 0
driver/internal/bcm283x/gpio.go View File

@ -0,0 +1,364 @@
package bcm283x
import (
"encoding/binary"
"errors"
"log"
"os"
"reflect"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
var (
ErrClosed = errors.New("bcm283x: Open should be called first")
ErrSPIClosed = errors.New("bcm283x: OpenSPI should be called first")
)
const (
bcm2708PeriBase = 0x20000000
bcm2709PeriBase = 0x3f000000
gpioBaseOffset = 0x200000
uartBaseOffset = 0x201000
spi0BaseOffset = 0x204000
spi1BaseOffset
spi2BaseOffset
pwmBaseOffset = 0x20c000
fselOffset = 0x0000
gpioSetOffset8 = 0x001c
gpioSetOffset = gpioSetOffset8 >> 2
gpioClrOffset8 = 0x0028
gpioClrOffset = gpioClrOffset8 >> 2
gpioPinLevelOffset8 = 0x0034
gpioPinLevelOffset = gpioPinLevelOffset8 >> 2
gpioEventDetectOffset8 = 0x0040
gpioEventDetectOffset = gpioEventDetectOffset8 >> 2
gpioRisingEdgeOffset8 = 0x004c
gpioRisingEdgeOffset = gpioRisingEdgeOffset8 >> 2
gpioFallingEdgeOffset8 = 0x0058
gpioFallingEdgeOffset = gpioFallingEdgeOffset8 >> 2
gpioHighDetectOffset8 = 0x0064
gpioHighDetectOffset = gpioHighDetectOffset8 >> 2
gpioLowDetectOffset8 = 0x0070
gpioLowDetectOffset = gpioLowDetectOffset8 >> 2
gpioPullUpDownOffset8 = 0x0094
gpioPullUpDownOffset = gpioPullUpDownOffset8 >> 2
gpioPullUpDownClockOffset8 = 0x0098
gpioPullUpDownClockOffset = gpioPullUpDownClockOffset8 >> 2
pullUpDownOffset2711_0 = 57
pullUpDownOffset2711_1 = 58
pullUpDownOffset2711_2 = 59
pullUpDownOffset2711_3 = 60
)
var (
periMem []uint32
periMem8 []uint8
gpioBase uint32
gpioMem []uint32
gpioMem8 []uint8
uartBase uint32
spi0Base uint32
spi0Mem []uint32
spi0Mem8 []uint8
pwmBase uint32
)
func Open() error {
/*
memFD, err := unix.Open("/dev/gpiomem", 0, unix.O_RDWR|unix.O_SYNC)
if err == nil {
defer unix.Close(memFD)
if gpioMem, gpioMem8, err = memoryMap(memFD, 0, blockSize); err != nil {
if !os.IsPermission(err) {
return err
}
}
}
*/
// Determine peripheral base address
var (
buf [16]byte
periBase uint32
periSize uint32
)
ranges, err := os.Open("/proc/device-tree/soc/ranges")
if err != nil {
_ = Close()
return err
}
defer ranges.Close()
if _, err = ranges.Read(buf[:]); err != nil {
_ = Close()
return err
}
if periBase = binary.BigEndian.Uint32(buf[4:]); periBase == 0 {
return errors.New("bcm283x: could not determine peripheral base address")
}
if periSize = binary.BigEndian.Uint32(buf[8:]); periSize == 0 {
return errors.New("bcm283x: could not determine peripheral base size")
}
gpioBase = periBase + gpioBaseOffset/4
spi0Base = periBase + spi0BaseOffset/4
log.Printf("bcm283x: gpio base %#04x", gpioBase)
log.Printf("bcm283x: spi0 base %#04x", spi0Base)
var fd int
if fd, err = unix.Open("/dev/mem", unix.O_RDWR|unix.O_SYNC, 0); err != nil {
return err
}
defer func() { _ = unix.Close(fd) }()
/*
//if gpioMem == nil {
if gpioMem, gpioMem8, err = memoryMap(fd, gpioBase, blockSize); err != nil {
return err
}
//}
if spi0Mem, spi0Mem8, err = memoryMap(fd, spi0Base, blockSize); err != nil {
_ = Close()
return err
}
*/
if periMem, periMem8, err = memoryMap(fd, periBase, int(periSize)); err != nil {
_ = Close()
return err
}
log.Printf("bcm283x: peri base at %p", &periMem8[0])
gpioMem = periMem[gpioBaseOffset/4:]
gpioMem8 = periMem8[gpioBaseOffset:]
log.Printf("bcm283x: gpio base at %p", &gpioMem8[0])
spi0Mem = periMem[spi0BaseOffset/4:]
spi0Mem8 = periMem8[spi0BaseOffset:]
log.Printf("bcm283x: spi0 base at %p", &spi0Mem8[0])
return nil
}
func Close() error {
/*
var (
areas = []struct {
mem []uint32
mem8 []uint8
}{
{gpioMem, gpioMem8},
{spi0Mem, spi0Mem8},
}
err error
)
for _, area := range areas {
if area.mem8 != nil {
if area.mem, area.mem8, err = memoryUnmap(area.mem8); err != nil {
return err
}
}
}
*/
if periMem8 == nil {
return nil
}
err := syscall.Munmap(periMem8)
periMem = nil
periMem8 = nil
return err
}
func memoryMap(fd int, offset uint32, size int) (mem []uint32, mem8 []uint8, err error) {
log.Printf("bcm283x: mapping %#08x+%#04x from fd %d", offset, size, fd)
if mem8, err = syscall.Mmap(fd, int64(offset), size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED); err != nil {
return
}
slice := *(*reflect.SliceHeader)(unsafe.Pointer(&mem8))
slice.Len /= 4 // (32 bit = 4 bytes)
slice.Cap /= 4
mem = *(*[]uint32)(unsafe.Pointer(&slice))
return
}
func memoryUnmap(mem []uint8) (unmapped []uint32, unmapped8 []uint8, err error) {
err = syscall.Munmap(mem)
return
}
type PinMode uint8
const (
Input PinMode = iota // 0b000
Output // 0b001
Alt0 // 0b010
)
type PinState uint8
const (
Low PinState = iota
High
)
type PullMode uint8
const (
PullNone PullMode = iota
PullUp
PullDown
)
// Pin is a GPIO pin.
type Pin uint8
// Pin names.
const (
D0 Pin = iota
D1
D2
D3
D4
D5
D6
D7
D8
D9 // MISO
D10 // MOSI
D11 // SCLK
D12
D13
D14 // TXD
D15 // RXD
D16
D17
D18
D19 // MISO 1
D20 // MOSI 1
D21 // SCLK 1
D22
D23
D24
D25
D26
D27
D28
D29
D30
D31
D32
D33
D34
D35
D36
D37
D38
D39
D40 // MISO 2
D41 // MOSI 2
D42 // SCLK 2
D43
D44
D45
)
// Named pin aliases.
const (
// Pin numbers.
Pin3 = D2
Pin5 = D3
Pin7 = D4
Pin8 = D14
Pin10 = D15
Pin11 = D17
Pin12 = D18
Pin13 = D27
Pin15 = D22
Pin16 = D23
Pin18 = D24
Pin19 = D10
Pin21 = D9
Pin22 = D25
Pin23 = D11
Pin24 = D8
Pin26 = D7
Pin29 = D5
Pin31 = D6
Pin32 = D12
Pin33 = D13
Pin35 = D19
Pin36 = D16
Pin37 = D26
Pin38 = D20
Pin40 = D21
// I²C
SDA = D2
SCL = D3
// SPI
CE1 = D7
CE0 = D8
MISO = D9
MOSI = D10
SCLK = D11
TXD = D14
RXD = D15
)
func (pin Pin) Input() {
pin.setup(Input, PullNone)
}
func (pin Pin) Output() {
pin.setup(Output, PullUp)
}
func (pin Pin) Low() {
var (
addr = gpioClrOffset + uint32(pin)/32
shift = uint32(pin) % 32
)
gpioMem[addr] = 1 << shift
}
func (pin Pin) High() {
var (
addr = gpioSetOffset + uint32(pin)/32
shift = uint32(pin) % 32
)
gpioMem[addr] = 1 << shift
}
func (pin Pin) setup(mode PinMode, pud PullMode) {
var (
offset = fselOffset + int(pin)/10
shift = int(pin%10) * 3
)
if mode == Input {
gpioMem[offset] &= ^(7 << shift)
} else {
pin.setPullUpDown(pud)
gpioMem[offset] = (gpioMem[offset] & ^(7 << shift)) | (1 << shift)
}
}
func (pin Pin) setPullUpDown(pud PullMode) {
var (
offset = pullUpDownOffset2711_0 + int(pin)>>4
shift = int(pin&0x0f) << 1
bits uint32
pull uint32
)
switch pud {
case PullNone:
pull = 0
case PullUp:
pull = 1
case PullDown:
pull = 2
default:
pull = 0
}
bits |= gpioMem[offset]
bits &= ^(3 << shift)
bits |= pull << shift
gpioMem[offset] = bits
}

+ 260
- 0
driver/internal/bcm283x/spio.go View File

@ -0,0 +1,260 @@
package bcm283x
import "errors"
// SPI port.
type SPI struct {
Dev int
CE0, CE1 Pin
MISO Pin
MOSI Pin
SCLK Pin
}
// Default SPI ports.
var (
SPI0 = &SPI{Dev: 0, CE0: CE0, CE1: CE1, MOSI: MOSI, MISO: MISO, SCLK: SCLK}
SPI1 = &SPI{Dev: 1, CE0: 0, CE1: 1, MISO: D19, MOSI: D20, SCLK: D21}
SPI2 = &SPI{Dev: 2, CE0: 0, CE1: 1, MISO: D40, MOSI: D41, SCLK: D42}
)
const (
// Relative to spi0Mem8
spi0ControlOffset = 0x0000
spi0FIFOOffset = 0x0004
spi0ClockOffset = 0x0008
spi0DataLengthOffset = 0x000c
spi0LossiEnableLong = 0x02000000 /*!< Enable Long data word in Lossi mode if DMA_LEN is set */
spi0LossiEnableDMA = 0x01000000 /*!< Enable DMA mode in Lossi mode */
spi0Polarity2 = 0x00800000 /*!< Chip Select 2 Polarity */
spi0Polarity1 = 0x00400000 /*!< Chip Select 1 Polarity */
spi0Polarity0 = 0x00200000 /*!< Chip Select 0 Polarity */
spi0RXFull = 0x00100000 /*!< RXF - RX FIFO Full */
spi0RXReadable = 0x00080000 /*!< RXR RX FIFO needs Reading (full) */
spi0TXReady = 0x00040000 /*!< TXD TX FIFO can accept Data */
spi0RXReady = 0x00020000 /*!< RXD RX FIFO contains Data */
spi0TransferDone = 0x00010000 /*!< Done transfer Done */
spi0CSLEN = 0x00002000 /*!< LEN LoSSI enable */
spi0CSREN = 0x00001000 /*!< REN Read Enable */
spi0CSADCS = 0x00000800 /*!< ADCS Automatically Deassert Chip Select */
spi0CSINTR = 0x00000400 /*!< INTR Interrupt on RXR */
spi0CSINTD = 0x00000200 /*!< INTD Interrupt on Done */
spi0CSDMAEN = 0x00000100 /*!< DMAEN DMA Enable */
spi0TransferActive = 0x00000080 /*!< Transfer Active */
spi0CSCSPOL = 0x00000040 /*!< Chip Select Polarity */
spi0ClearRXTX = 0x00000030 /*!< Clear FIFO Clear RX and TX */
spi0ClearRX = 0x00000020 /*!< Clear FIFO Clear RX */
spi0ClearTX = 0x00000010 /*!< Clear FIFO Clear TX */
spi0ClockPolarity = 0x00000008 /*!< Clock Polarity */
spi0ClockPhase = 0x00000004 /*!< Clock Phase */
spi0ChipSelect = 0x00000003 /*!< Chip Select */
)
func (spi *SPI) setup() (err error) {
if spi0Mem == nil {
return ErrSPIClosed
}
// Set the pins to the Alt0 function to enable SPI0 access on them.
spi.CE1.setup(Alt0, PullNone)
spi.CE0.setup(Alt0, PullNone)
spi.MISO.setup(Alt0, PullNone)
spi.MOSI.setup(Alt0, PullNone)
spi.SCLK.setup(Alt0, PullNone)
// Set the SPI CS register to the some sensible defaults.
spi0Mem[0] = 0 // All zero
spi0Mem[0] = 0
// Clear TX and RX FIFOs.
spi0Mem[0] = spi0ClearRXTX
return nil
}
func (spi *SPI) Close() error {
if spi0Mem == nil {
return ErrSPIClosed
}
// Set all the pins back to input
spi.CE1.setup(Input, PullNone)
spi.CE0.setup(Input, PullNone)
spi.MISO.setup(Input, PullNone)
spi.MOSI.setup(Input, PullNone)
spi.SCLK.setup(Input, PullNone)
return nil
}
type ChipSelect uint8
const (
CS0 ChipSelect = iota
CS1
CS2
CSNone
)
// SetChip selects what SPI chip will be used.
func (spi *SPI) SetChip(chip ChipSelect) error {
if spi0Mem == nil {
return ErrSPIClosed
} else if chip > CSNone {
return errors.New("bcm283x: invalid chip select")
}
setBits(spi0Mem, spi0ControlOffset/4, uint32(chip), spi0ChipSelect)
return nil
}
// SetClockDivider sets the SPI clock pulse divider. Odd numbers are rounded down to a power of 2.
// Setting the divider to 0 sets the clock divider to 65536.
func (spi *SPI) SetClockDivider(divider uint16) error {
if spi0Mem == nil {
return ErrSPIClosed
}
spi0Mem[spi0ClockOffset/4] = uint32(divider)
return nil
}
const (
DataMode0 uint8 = iota // polarity = 0, phase = 0
DataMode1 // polarity = 0, phase = 1
DataMode2 // polarity = 1, phase = 0
DataMode3 // polarity = 1, phase = 1
)
func (spi *SPI) SetDataMode(mode uint8) error {
if spi0Mem == nil {
return ErrSPIClosed
} else if mode > DataMode3 {
return errors.New("bcm283x: invalid SPI data mode")
}
setBits(spi0Mem, spi0ControlOffset/4, uint32(mode)<<2, spi0ClockPhase|spi0ClockPolarity)
return nil
}
func (spi *SPI) Write(p []byte) (n int, err error) {
var (
addr uint32 = spi0ControlOffset >> 2 // 8->32 bit
fifo uint32 = spi0FIFOOffset >> 2 // 8->32 bit
)
// Clear TX and RX FIFOs.
setBits(spi0Mem, addr, spi0ClearRXTX, spi0ClearRXTX)
// Set transfer active.
setBits(spi0Mem, addr, spi0TransferActive, spi0TransferActive)
var c byte
for n, c = range p {
// Maybe wait for TXD.
for spi0Mem[addr]&spi0TXReady != 0 {
}
// Write to FIFO.
spi0Mem[fifo] = uint32(c)
// Read from FIFO to prevent stalling.
for spi0Mem[addr]&spi0RXReady != 0 {
_ = spi0Mem[fifo]
}
n++
}
// Clear transfer active.
setBits(spi0Mem, addr, 0, spi0TransferActive)
return
}
func (spi *SPI) WriteByte(b byte) {
var (
addr uint32 = spi0ControlOffset >> 2 // 8->32 bit
fifo uint32 = spi0FIFOOffset >> 2 // 8->32 bit
)
// Clear TX and RX FIFOs.
setBits(spi0Mem, addr, spi0ClearRXTX, spi0ClearRXTX)
// Set transfer active.
setBits(spi0Mem, addr, spi0TransferActive, spi0TransferActive)
// Maybe wait for TXD.
for spi0Mem[addr]&spi0TXReady != 0 {
}
// Write to FIFO.
spi0Mem[fifo] = uint32(b)
// Read from FIFO to prevent stalling.
for spi0Mem[addr]&spi0RXReady != 0 {
_ = spi0Mem[fifo]
}
// Clear transfer active.
setBits(spi0Mem, addr, 0, spi0TransferActive)
return
}
func (spi *SPI) Transfer(dst, src []byte) (n int, err error) {
var (
addr uint32 = spi0ControlOffset >> 2 // 8->32 bit
fifo uint32 = spi0FIFOOffset >> 2 // 8->32 bit
)
// Clear TX and RX FIFOs.
setBits(spi0Mem, addr, spi0ClearRXTX, spi0ClearRXTX)
// Set transfer active.
setBits(spi0Mem, addr, spi0TransferActive, spi0TransferActive)
var (
l = len(src)
rx, tx int
)
for rx < l || tx < l {
for spi0Mem[addr]&spi0TXReady != 0 && tx < l {
spi0Mem[fifo] = uint32(src[tx])
tx++
}
for spi0Mem[addr]&spi0RXReady != 0 && rx < l {
dst[rx] = byte(spi0Mem[fifo])
rx++
}
}
// Wait for transfer to be done.
for spi0Mem[addr]&spi0TransferDone == 0 {
}
// Clear transfer active.
setBits(spi0Mem, addr, 0, spi0TransferActive)
return rx, nil
}
func OpenSPI(n int) (*SPI, error) {
switch n {
case 0:
return SPI0, SPI0.setup()
case 1:
return SPI1, SPI1.setup()
case 2:
return SPI2, SPI2.setup()
default:
return nil, errors.New("bcm283x: invalid SPI number")
}
}
func setBits(mem []uint32, offset, value, mask uint32) {
v := mem[offset] & ^mask
mem[offset] = v | (value & mask)
mem[offset] = v | (value & mask)
}
func setBitsOnce(mem []uint32, offset, value, mask uint32) {
v := mem[offset] & ^mask
mem[offset] = v | (value & mask)
}

+ 484
- 0
driver/internal/rpi/gpio.go View File

@ -0,0 +1,484 @@
package rpi
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"reflect"
"sync"
"syscall"
"unsafe"
)
const (
coreClock = 250000000
bcm2835RPi2PeripheralBase = 0x3F000000 // Alternate base address for RPi 2 / 3
bcm2835RPi4PeripheralBase = 0xFE000000 // Alternate base address for RPi 4
bcm2835RPi4PeripheralSize = 0x01800000 // Alternate size for RPi 4
bcm2835SystemTimerBase = 0x3000 // Base address of the System Timer registers
bcm2835GPIOPads = 0x100000 // Base address of the Pads registers
bcm2835ClockBase = 0x101000 // Base address of the Clock/timer registers
bcm2835GPIOBase = 0x200000 // Base address of the GPIO registers
bcm2835SPI0Base = 0x204000 // Base address of the SPI0 registers
bcm2835BSC0Base = 0x205000 // Base address of the BSC0 registers
bcm2835PWMBase = 0x20C000 // Base address of the PWM registers
bcm2835AUXBase = 0x215000 // Base address of the AUX registers
bcm2835SPI1Base = 0x215080 // Base address of the AUX_SPI1 registers
bcm2835SPI2Base = 0x2150C0 // Base address of the AUX_SPI2 registers
bcm2835BSC1Base = 0x804000 // Base address of the BSC1 registers
bcm2835IntrBase = 0x00B000 // Base address of the interrupt registers
/*! GPIO register offsets from bcm2835GPIOBase.
Offsets into the GPIO Peripheral block in bytes per 6.1 Register View
*/
bcm2835GPIOFunctionSelect0 = 0x0000 /*!< GPIO Function Select 0 */
bcm2835GPIOFunctionSelect1 = 0x0004 /*!< GPIO Function Select 1 */
bcm2835GPIOFunctionSelect2 = 0x0008 /*!< GPIO Function Select 2 */
bcm2835GPIOFunctionSelect3 = 0x000c /*!< GPIO Function Select 3 */
bcm2835GPIOFunctionSelect4 = 0x0010 /*!< GPIO Function Select 4 */
bcm2835GPIOFunctionSelect5 = 0x0014 /*!< GPIO Function Select 5 */
bcm2835GPSET0 = 0x001c /*!< GPIO Pin Output Set 0 */
bcm2835GPSET1 = 0x0020 /*!< GPIO Pin Output Set 1 */
bcm2835GPCLR0 = 0x0028 /*!< GPIO Pin Output Clear 0 */
bcm2835GPCLR1 = 0x002c /*!< GPIO Pin Output Clear 1 */
bcm2835GPLEV0 = 0x0034 /*!< GPIO Pin Level 0 */
bcm2835GPLEV1 = 0x0038 /*!< GPIO Pin Level 1 */
bcm2835GPEDS0 = 0x0040 /*!< GPIO Pin Event Detect Status 0 */
bcm2835GPEDS1 = 0x0044 /*!< GPIO Pin Event Detect Status 1 */
bcm2835GPREN0 = 0x004c /*!< GPIO Pin Rising Edge Detect Enable 0 */
bcm2835GPREN1 = 0x0050 /*!< GPIO Pin Rising Edge Detect Enable 1 */
bcm2835GPFEN0 = 0x0058 /*!< GPIO Pin Falling Edge Detect Enable 0 */
bcm2835GPFEN1 = 0x005c /*!< GPIO Pin Falling Edge Detect Enable 1 */
bcm2835GPHEN0 = 0x0064 /*!< GPIO Pin High Detect Enable 0 */
bcm2835GPHEN1 = 0x0068 /*!< GPIO Pin High Detect Enable 1 */
bcm2835GPLEN0 = 0x0070 /*!< GPIO Pin Low Detect Enable 0 */
bcm2835GPLEN1 = 0x0074 /*!< GPIO Pin Low Detect Enable 1 */
bcm2835GPAREN0 = 0x007c /*!< GPIO Pin Async. Rising Edge Detect 0 */
bcm2835GPAREN1 = 0x0080 /*!< GPIO Pin Async. Rising Edge Detect 1 */
bcm2835GPAFEN0 = 0x0088 /*!< GPIO Pin Async. Falling Edge Detect 0 */
bcm2835GPAFEN1 = 0x008c /*!< GPIO Pin Async. Falling Edge Detect 1 */
bcm2835GPPUD = 0x0094 /*!< GPIO Pin Pull-up/down Enable */
bcm2835GPPUDCLK0 = 0x0098 /*!< GPIO Pin Pull-up/down Enable Clock 0 */
bcm2835GPPUDCLK1 = 0x009c /*!< GPIO Pin Pull-up/down Enable Clock 1 */
)
var (
bcm2835PeripheralBase uint32 = 0x20000000 // Peripherals block base address on RPi 1
bcm2835PeripheralSize uint32 = 0x01000000 // Size of the peripherals block on RPi 1
)
<