@ -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 | |||
} |
@ -0,0 +1,6 @@ | |||
package main | |||
import ( | |||
_ "maze.io/matrix/driver/oled" // OLED (SPI) | |||
_ "maze.io/matrix/driver/rgbled" // RGB LED matrix | |||
) |
@ -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) | |||
} |
@ -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 | |||
} |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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 | |||
} |
@ -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)) | |||
} |
@ -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 | |||
} |
@ -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 | |||
} |
@ -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) | |||
} |
@ -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 | |||
) | |||