49 changed files with 3508 additions and 0 deletions
@ -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 |
|||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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> |
After Width: | Height: | Size: 865 B |
@ -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> |
After Width: | Height: | Size: 946 B |
@ -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> |
After Width: | Height: | Size: 894 B |
@ -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> |
After Width: | Height: | Size: 1022 B |
@ -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> |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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) |
|||