Compare commits

..

11 Commits

24 changed files with 2146 additions and 318 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ out/
go.sum go.sum
.git/ .git/
*.swp *.swp
.vscode

View File

@ -9,7 +9,9 @@ build:
@go build -o out/out main.go @go build -o out/out main.go
export GO111MODULE=on export GO111MODULE=on
export GOPRIVATE=gitea.unprism.ru/KRBL/* export GOPRIVATE=gitea.unprism.ru/KRBL/*\
# In my case even setting up "url.git@gitea.unprism.ru:8022/.insteadof=https://gitea.unprism.ru/" did not help and git continued to use https instead of ssh
# So just go to config(location is listed in error message) and change the URL yourself
get: get:
go get . go get .

97
api/api.go Normal file
View File

@ -0,0 +1,97 @@
package api
import (
"fmt"
"io"
"log"
"time"
"gitea.unprism.ru/yotia/display-test/api/pages"
"gitea.unprism.ru/yotia/display-test/display"
"gitea.unprism.ru/yotia/display-test/drawer"
)
const (
loopTimeout = 10 * time.Millisecond
)
type displayApi struct {
d drawer.Drawer
curPage pages.Page
initPage pages.InitPage
statusPage pages.StatusPage
}
// Work process:
// Display has pages that you can switch between. Switch returns page controller.
// Every page has values that you can change:
// Init page - title, progress bar
// Status page - gps, time, rssi, service, ...
type Display interface {
// Put them to different functions because of different returns
SwitchToInitPage() pages.InitPageContent
SwitchToStatusPage() pages.StatusPageContent
io.Closer
}
func New() (Display, error) {
// Device
dev, err := display.New(log.New(log.Writer(), "display", log.LstdFlags), display.MT12232A)
if err != nil {
return nil, fmt.Errorf("create display: %w", err)
}
// Drawer
d := drawer.New(dev)
// Pages
// Init page
initPage, err := pages.NewInitPage(d)
if err != nil {
return nil, fmt.Errorf("new init page: %w", err)
}
// Status page
statusPage, err := pages.NewStatusPage(d)
if err != nil {
return nil, fmt.Errorf("new init page: %w", err)
}
return &displayApi{
d: d,
initPage: initPage,
statusPage: statusPage,
}, nil
}
func (d *displayApi) switchToPage(p pages.Page) {
if d.curPage != nil {
d.curPage.Diactivate()
}
if p != nil {
p.Activate()
}
d.curPage = p
}
func (d *displayApi) SwitchToInitPage() pages.InitPageContent {
d.switchToPage(d.initPage)
return d.initPage
}
func (d *displayApi) SwitchToStatusPage() pages.StatusPageContent {
d.switchToPage(d.statusPage)
return d.statusPage
}
func (d *displayApi) Close() error {
// !!! TMP DEBUG BAD CODE
d.d.Clear()
time.Sleep(100 * time.Millisecond)
// !!!
d.switchToPage(nil) // To deactivate cur page
return d.d.GetDisplay().Close()
}

77
api/pages/initpage.go Normal file
View File

@ -0,0 +1,77 @@
package pages
import (
"fmt"
"gitea.unprism.ru/yotia/display-test/components"
"gitea.unprism.ru/yotia/display-test/drawer"
)
type initPage struct {
drawer drawer.Drawer // Drawer with dysplay
title components.Text
bar components.ProgressBar
}
// Only functions that control content of page
type InitPageContent interface {
SetTitle(title string)
GetProgressBar() components.ProgressBar
}
type InitPage interface {
Page
InitPageContent
}
func NewInitPage(d drawer.Drawer) (InitPage, error) {
if err := d.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
title, err := components.NewText(d, 0, 0)
if err != nil {
return nil, fmt.Errorf("create title: %w", err)
}
pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) // Bottom
if err != nil {
return nil, fmt.Errorf("create progress bar: %w", err)
}
return &initPage{
drawer: d,
title: title,
bar: pb,
}, nil
}
func (p *initPage) Activate() {
// Draw
p.drawer.Clear()
p.title.Draw()
p.bar.Draw()
}
func (p *initPage) Diactivate() {
// Do not clear because next page will have to clear whole screen any way
}
func (p *initPage) SetTitle(title string) {
p.title.SetPos((p.drawer.W()-len(title)*(drawer.FontCharW+drawer.CharGap))/2, drawer.FontCharH) // Text in center
p.title.SetStr(title)
}
func (p *initPage) GetProgressBar() components.ProgressBar {
return p.bar
}
func (p *initPage) Close() (outErr error) {
// TODO Not the best way...
if err := p.title.Close(); err != nil {
outErr = fmt.Errorf("title close: %w:", err)
}
if err := p.bar.Close(); err != nil {
outErr = fmt.Errorf("progress bar close: %w:", err)
}
return
}

11
api/pages/pages.go Normal file
View File

@ -0,0 +1,11 @@
package pages
import "io"
type Page interface {
// Warning: now threre is no protection from double activation/diactivation
Activate()
Diactivate()
io.Closer
}

29
api/pages/status.go Normal file
View File

@ -0,0 +1,29 @@
package pages
import (
"sync"
"gitea.unprism.ru/KRBL/mpu/mpu"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
)
// Some supplement types and constants
// This struct contains status of the system that will be on the display
type systemStatus struct {
mutex sync.Mutex
// Status data
gpsData gps.Data
mpuData mpu.Data
rssi int // Received signal strength indicator (check gitea.unprism.ru/KRBL/sim-modem/api/modem/utils/signal.go)
service string // Internet service name, could be: "NO SERVICE", "GSM", "WCDMA", "LTE", "TDS"
}
type SystemStatusSetter interface {
SetGps(newData gps.Data)
SetMpu(newData mpu.Data)
SetRssi(newData int)
SetService(newData string)
}

370
api/pages/statuspage.go Normal file
View File

@ -0,0 +1,370 @@
package pages
import (
"context"
"fmt"
"math"
"sync"
"sync/atomic"
"time"
"gitea.unprism.ru/KRBL/mpu/mpu"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
"gitea.unprism.ru/yotia/display-test/components"
"gitea.unprism.ru/yotia/display-test/drawer"
)
/*
Status page has status line at the top and three lines with primary data below.
Status line shows time and signal and service icon at the right corner.
Main space can show several screens:
1) GPS
GPS coordinates
2) System state
temepature
speed
may be amount of cameras
Other pages can show for ex important logs.
*/
const (
timeLayout = "01.09.06 15:04:05"
screenN = 2
screenChangeTimeout = 6 * time.Second
)
type statusPage struct {
drawer drawer.Drawer // Drawer with dysplay
// Status data
st systemStatus
timeShift time.Duration
// Visual components
line0 components.Text
line1 components.Text
line2 components.Text
line3 components.Text
// Layout values
signalX int
serviceX int
// Current screen(index) (default - 0)
curScreen atomic.Int32
// Threads sync
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
// Only functions that control content of page
type StatusPageContent interface {
SystemStatusSetter
SetTimeShift(shift time.Duration)
}
type StatusPage interface {
Page
StatusPageContent
}
func NewStatusPage(d drawer.Drawer) (StatusPage, error) {
// Check display
if err := d.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
// Create visual components
line0, err := components.NewText(d, 0, drawer.LineH*0)
if err != nil {
return nil, fmt.Errorf("create line0: %w", err)
}
line1, err := components.NewText(d, 0, drawer.LineH*1)
if err != nil {
return nil, fmt.Errorf("create line1: %w", err)
}
line2, err := components.NewText(d, 0, drawer.LineH*2)
if err != nil {
return nil, fmt.Errorf("create line2: %w", err)
}
line3, err := components.NewText(d, 0, drawer.LineH*3)
if err != nil {
return nil, fmt.Errorf("create line3: %w", err)
}
// Calculate signal and service glyphs' pos
signalX := d.W() - drawer.CommonGlyphs[drawer.SignalStatusGlyphI].W - drawer.CommonGlyphs[drawer.ServiceStatusGlyphI].W - drawer.CharGap
serviceX := d.W() - drawer.CommonGlyphs[drawer.ServiceStatusGlyphI].W
return &statusPage{
drawer: d,
line0: line0,
line1: line1,
line2: line2,
line3: line3,
signalX: signalX,
serviceX: serviceX,
}, nil
}
func (p *statusPage) Activate() {
// Draw
p.drawer.Clear()
p.SetRssi(0)
p.SetService("NO SERVICE")
// Setup threads
p.ctx, p.cancel = context.WithCancel(context.Background())
go p.timeUpdateLoop()
go p.screenChangeLoop()
}
func (p *statusPage) Diactivate() {
// Stop all support threads
p.cancel()
p.wg.Wait()
}
func (p *statusPage) SetTimeShift(shift time.Duration) {
p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.timeShift = shift
}
func (p *statusPage) SetGps(newData gps.Data) {
p.st.mutex.Lock()
p.st.gpsData = newData
p.st.mutex.Unlock()
if p.curScreen.Load() == 0 { // Draw only on first screen
p.drawCoords(&newData)
}
}
func (p *statusPage) SetMpu(newData mpu.Data) {
p.st.mutex.Lock()
p.st.mpuData = newData
p.st.mutex.Unlock()
if p.curScreen.Load() == 1 { // Draw only on second screen
p.drawMpu(&newData)
}
}
func (p *statusPage) SetRssi(rssi int) {
// Save data
p.st.mutex.Lock()
p.st.rssi = rssi
p.st.mutex.Unlock()
// Draw
p.drawRssi(rssi)
}
func (p *statusPage) SetService(svc string) {
// Save data
p.st.mutex.Lock()
p.st.service = svc
p.st.mutex.Unlock()
// Draw
p.drawService(svc)
}
func (p *statusPage) Close() (outErr error) {
// TODO Not the best way...
if err := p.line0.Close(); err != nil {
outErr = fmt.Errorf("line 0 close: %w:", err)
}
if err := p.line1.Close(); err != nil {
outErr = fmt.Errorf("line 1 close: %w:", err)
}
if err := p.line2.Close(); err != nil {
outErr = fmt.Errorf("line 2 close: %w:", err)
}
if err := p.line3.Close(); err != nil {
outErr = fmt.Errorf("line 3 close: %w:", err)
}
return
}
func getSignalStrength(rssi int) int {
// Signal strength clasification (from google):
// 0 - no signal -110 to ...
// 1 - poor signal -90 to -110
// 2 - fair signal -70 to -90
// 3 - good signal -50 to -70
// 4 - excelent signal ... to -50
// Description from "sim-modem/api/modem/utils/signal.go"
// 0 -113 dBm or less
// 1 -111 dBm
// 2...30 -109... -53 dBm
// 31 -51 dBm or greater
// 99 not known or not detectable
// 100 -116 dBm or less
// 101 -115 dBm
// 102…191 -114... -26dBm
// 191 -25 dBm or greater
// 199 not known or not detectable
// 100…199 expand to TDSCDMA, indicate RSCPreceived
// Cut certain cases
switch rssi {
case 99, 199: // not known or not detectable
return 0
case 0, 1, 100, 101: // no signal
return 0
case 31, 191: // Excelent
return 4
}
// Ranged values
if rssi >= 2 && rssi <= 30 {
// it is in -109...-53 dBm
// Simplified interpolation from 2..30 to -109...-53 and then to 0...4
return int((float64(rssi)*2-3)/20) + 1
}
if rssi >= 102 && rssi <= 191 {
// it is in -114...-26 dBm
// Simplified interpolation from 2..30 to -114...-26 and then to 0...4
return min(int((float64(rssi-102)+15)/20), 4)
}
return 0 // Invalid value
}
func getServiceStrength(service string) int {
// Service clasification by speed:
// 0 - no service - "NO SERVICE"
// 1 - 1G - do not use
// 2 - 2G - "GSM"
// 3 - 3G - "WCDMA"
// 4 - 4G - "LTE", "TDS"
switch service {
case "NO SERVICE":
return 0
case "GSM":
return 2
case "WCDMA":
return 3
case "LTE", "TDS":
return 4
}
return 0 // Invalid value
}
// Update time in top line
func (p *statusPage) timeUpdateLoop() {
p.wg.Add(1)
// Because ticker do not send signal immediately
p.line0.SetStr(time.Now().Add(p.timeShift).Format(timeLayout))
ticker := time.NewTicker(time.Second)
for {
select {
case <-p.ctx.Done():
ticker.Stop()
p.wg.Done()
return
case now := <-ticker.C:
p.line0.SetStr(now.Add(p.timeShift).Format(timeLayout))
}
}
}
func (p *statusPage) screenChangeLoop() {
p.wg.Add(1)
// Because ticker do not send signal immediately
ticker := time.NewTicker(screenChangeTimeout)
for {
select {
case <-p.ctx.Done():
ticker.Stop()
p.wg.Done()
return
case <-ticker.C:
p.changeScreen()
}
}
}
func (p *statusPage) changeScreen() {
scr := p.curScreen.Load()
scr = (scr + 1) % screenN
p.curScreen.Store(scr)
p.drawScreen(int(scr))
}
///////////////////// Draw functions /////////////////////
// Draw functions get draw data as arguments because otherwise they have to get it using mutex
// Moreover they will lock it second time
func (p *statusPage) drawRssi(rssi int) {
ss := getSignalStrength(rssi) // Signal strength in [0; 4] range
p.drawer.CopyImg(p.signalX, 0, drawer.CommonGlyphs[drawer.SignalStatusGlyphI+ss])
p.drawer.GetDisplay().FlushByMask(p.drawer.GetDisplay().GetFlushMaskBit(1, 0))
}
func (p *statusPage) drawService(svc string) {
ss := getServiceStrength(svc) // Service strength in [0; 4] range
p.drawer.CopyImg(p.serviceX, 0, drawer.CommonGlyphs[drawer.ServiceStatusGlyphI+ss])
p.drawer.GetDisplay().FlushByMask(p.drawer.GetDisplay().GetFlushMaskBit(1, 0))
}
func (p *statusPage) drawCoords(data *gps.Data) {
// Langitude, longitude store format: ddmm.mmmmmm, dddmm.mmmmmm
// Latitude and longitude layout:
// DD° MM.MMM' N
// DDD° MM.MMM' W
latStr := fmt.Sprintf(" %02d°%06.3f' %s", int(p.st.gpsData.Latitude)/100, math.Mod(p.st.gpsData.Latitude, 100), p.st.gpsData.LatitudeIndicator)
p.line2.SetStr(latStr)
logStr := fmt.Sprintf(" %03d°%06.3f' %s", int(p.st.gpsData.Longitude)/100, math.Mod(p.st.gpsData.Longitude, 100), p.st.gpsData.LongitudeIndicator)
p.line3.SetStr(logStr)
}
func (p *statusPage) drawMpu(data *mpu.Data) {
// Layout:
// temp ttt.ttC
tempStr := fmt.Sprintf("temperature %06.3fC", data.TempData)
p.line2.SetStr(tempStr)
}
// Draw main part depends on what current screen is active
func (p *statusPage) drawScreen(screen int) {
// Clear
//p.drawer.FillBar(0, drawer.LineH, p.drawer.W(), p.drawer.H()-drawer.LineH, 0)
//p.drawer.GetDisplay().FlushByMask(0b01110111)
p.line1.SetStr("")
p.line2.SetStr("")
p.line3.SetStr("")
switch screen {
case 0:
p.st.mutex.Lock()
gpsData := &p.st.gpsData
p.st.mutex.Unlock()
p.drawCoords(gpsData)
case 1:
p.st.mutex.Lock()
mpuData := &p.st.mpuData
p.st.mutex.Unlock()
p.drawMpu(mpuData)
p.line1.SetStr("47 cameras connected")
p.line3.SetStr("some other info...")
}
}

62
api/progress/handler.go Normal file
View File

@ -0,0 +1,62 @@
package progress
import (
"io"
"gitea.unprism.ru/yotia/display-test/components"
)
type handler struct {
bar components.ProgressBar
checkpointN int // Number of check points
curP int
}
// Handles progress bar update
// You set the amount of
type Handler interface {
Checkpoint() // Increments current progress
// SetProgress(n int) // Do not want to make it public because there is no places it is needed yet
Finish() // Set progress to end
GetBar() components.ProgressBar
Reset()
io.Closer
}
func NewHandler(bar components.ProgressBar, checkpointN int) Handler {
return &handler{
bar: bar,
checkpointN: checkpointN,
}
}
func (h *handler) GetBar() components.ProgressBar {
return h.bar
}
func (h *handler) Checkpoint() {
h.curP = min(h.curP+1, h.checkpointN)
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
}
func (h *handler) SetProgress(n int) {
h.curP = min(max(n, 0), h.checkpointN)
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
}
func (h *handler) Finish() {
h.curP = h.checkpointN
h.bar.SetProgress(1)
}
func (h *handler) Reset() {
h.curP = 0
h.bar.SetProgress(0)
}
func (h *handler) Close() error {
h.curP = 0
h.checkpointN = 0
return h.bar.Close()
}

57
components/components.go Normal file
View File

@ -0,0 +1,57 @@
package components
import "io"
// Some general types
// Rectangle and its' suplements
type rect struct {
x int
y int
w int
h int
}
type RectGetter interface {
GetPos() (int, int)
GetSize() (int, int)
}
type RectSetter interface {
SetPos(x, y int)
SetSize(w, h int)
}
// Mask
// Implementation dependent !!!
// Now for mt12232
type mask struct {
bits uint32
}
func (m *mask) Update(r rect) {
m.bits = 0
y0 := min(3, max(0, int(r.y/8)))
y1 := min(3, max(0, int((r.y+r.h-1)/8)))
x0 := min(1, max(0, int(r.x/61)))
x1 := min(1, max(0, int((r.x+r.w-1)/61)))
//log.Println(x0, y0, x1, y1)
for y := y0; y <= y1; y++ {
for x := x0; x <= x1; x++ {
m.bits |= (1 << (x*4 + y))
}
}
}
// Component - all components should implement this interface
type Component interface {
RectGetter
Draw() // Draw and flush
io.Closer
}

93
components/progressbar.go Normal file
View File

@ -0,0 +1,93 @@
package components
import (
"fmt"
"gitea.unprism.ru/yotia/display-test/drawer"
)
type progressBar struct {
drawer drawer.Drawer // Drawer with dysplay
mask mask // Flush mask
progress float64
rect rect // Rect
}
type ProgressBar interface {
Component
SetProgress(coef float64) // Value from 0 to 1
Clear() // Clear space of bar
}
// Creation function
// Now the only way to chage shape is to recreate component
func NewProgressBar(drawer drawer.Drawer, x, y, w, h int) (ProgressBar, error) {
if err := drawer.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
// Check for correct sizes
if w < 4 {
return nil, fmt.Errorf("invalid width: below 4")
}
if h < 4 {
return nil, fmt.Errorf("invalid height: below 4")
}
pb := progressBar{
drawer: drawer,
rect: rect{x, y, w, h},
}
pb.mask.Update(pb.rect)
// pb.Draw()
return &pb, nil
}
func (pb *progressBar) flush() {
pb.drawer.GetDisplay().FlushByMask(pb.mask.bits)
}
func (pb *progressBar) Close() error {
return nil
}
func (pb *progressBar) Clear() {
pb.drawer.FillBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0x00)
}
// Draw whole bar
func (pb *progressBar) Draw() {
// Shape:
// Bounds - 1px
// Padding - 1px
// Progress - the rest space
pb.Clear()
pb.drawer.PutBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0xFF)
pb.drawBar()
pb.flush()
}
// Draw only bar(without frames)
func (pb *progressBar) drawBar() {
w := int(float64(pb.rect.w-4) * pb.progress)
pb.drawer.FillBar(pb.rect.x+2, pb.rect.y+2, pb.rect.x+2+w, pb.rect.y+pb.rect.h-2, 0xFF)
}
func (pb *progressBar) SetProgress(coef float64) {
pb.progress = max(0, min(1, coef))
pb.drawBar()
pb.flush()
}
func (pb *progressBar) GetPos() (int, int) {
return pb.rect.x, pb.rect.y
}
func (pb *progressBar) GetSize() (int, int) {
return pb.rect.w, pb.rect.h
}

93
components/text.go Normal file
View File

@ -0,0 +1,93 @@
package components
import (
"fmt"
"gitea.unprism.ru/yotia/display-test/drawer"
)
type text struct {
drawer drawer.Drawer // Drawer with dysplay
mask mask // Flush mask
str string
rect rect // Rect
}
type Text interface {
Component
RectSetter
SetStr(newStr string) // Update string and redraw
Clear() // Clear space of bar
}
// Creation function
// Now the only way to chage shape is to recreate component
func NewText(d drawer.Drawer, x, y int) (Text, error) {
if err := d.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
t := text{
drawer: d,
rect: rect{x, y, 0, drawer.FontCharH},
}
t.mask.Update(t.rect)
//t.Draw()
return &t, nil
}
func (t *text) Close() error {
return nil
}
func (t *text) Clear() {
t.drawer.FillBar(t.rect.x, t.rect.y, t.rect.x+t.rect.w, t.rect.y+t.rect.h, 0x00)
}
func (t *text) Draw() {
t.drawer.PutText(t.rect.x, t.rect.y, t.str)
t.drawer.GetDisplay().FlushByMask(t.mask.bits)
}
func (t *text) updateW() {
t.rect.w = len(t.str)*(drawer.FontCharW+drawer.CharGap) - drawer.CharGap
}
func (t *text) SetStr(str string) {
t.str = str
newW := len(t.str)*(drawer.FontCharW+drawer.CharGap) - drawer.CharGap
if t.rect.w-newW > 0 {
// Need to clear some space
t.Clear()
t.drawer.FillBar(t.rect.x+newW, t.rect.y, t.rect.x+t.rect.w, t.rect.y+t.rect.h, 0)
} else {
t.rect.w = newW
t.mask.Update(t.rect)
}
t.Draw()
t.rect.w = newW
t.mask.Update(t.rect)
}
func (t *text) GetPos() (int, int) {
return t.rect.x, t.rect.y
}
func (t *text) GetSize() (int, int) {
return t.rect.w, t.rect.h
}
func (t *text) SetPos(x, y int) {
t.rect.x = x
t.rect.y = y
t.mask.Update(t.rect)
}
func (t *text) SetSize(w, h int) {
// Empty
}

View File

@ -1,6 +1,7 @@
package display package display
import ( import (
"context"
"fmt" "fmt"
"image" "image"
"io" "io"
@ -8,9 +9,20 @@ import (
) )
type Display interface { type Display interface {
Flush(img *image.Gray) error IsReady() error // Nil if it is ready, error with some info otherwise
Flush(crystal, page byte)
FlushByMask(mask uint32)
GetFlushMaskBit(crystal, page byte) uint32
// Image functions
GetImg() *image.Gray
LockImg()
UnlockImg()
GetBounds() image.Rectangle GetBounds() image.Rectangle
Test(ctx context.Context) error // DEBUG ONLY
io.Closer io.Closer
} }
@ -19,6 +31,7 @@ type DisplayModel int
const ( const (
SSD1306 DisplayModel = iota SSD1306 DisplayModel = iota
MT12864A MT12864A
MT12232A
) )
func New(logger *log.Logger, model DisplayModel) (Display, error) { func New(logger *log.Logger, model DisplayModel) (Display, error) {
@ -27,6 +40,8 @@ func New(logger *log.Logger, model DisplayModel) (Display, error) {
return newSsd1306(logger) return newSsd1306(logger)
case MT12864A: case MT12864A:
return newMt12864a(logger) return newMt12864a(logger)
case MT12232A:
return newMt12232a(logger)
} }
return nil, fmt.Errorf("invalid display model") return nil, fmt.Errorf("invalid display model")
} }

316
display/mt-12232a.go Normal file
View File

@ -0,0 +1,316 @@
package display
import (
"context"
"fmt"
"image"
"log"
"math/rand"
"sync"
"sync/atomic"
"time"
"gitea.unprism.ru/yotia/display-test/pkg/mt12232a"
)
var _ = rand.Int() // for import existance
const (
mt12232aW = 122
mt12232aH = 32
flushChanCap = 24 // Flush channel capacity
flushUpdateTimeout = 5 * time.Millisecond
)
type displayMt12232a struct {
logger *log.Logger
// Some state flags
isTurnedOn bool
// Image
img *image.Gray
imgMutex sync.Mutex
// GPIO pins
dev mt12232a.Device
// Flush goroutine
flushCancel context.CancelFunc
pagesFlushFlags atomic.Uint32 // (!use only 8 bits) Every bit corresponds to page/crystal with this number
flushDone chan struct{}
}
func newMt12232a(logger *log.Logger) (Display, error) {
dev, err := mt12232a.New(log.New(logger.Writer(), "display-mt12864 : ", log.LstdFlags))
if err != nil {
return nil, fmt.Errorf("mt12864 create: %w", err)
}
// Allocate bits
bits := make([][]byte, 32)
for i := 0; i < 32; i++ {
bits[i] = make([]byte, 122)
}
ctx, cancel := context.WithCancel(context.Background())
// Setup submit goroutine
d := displayMt12232a{
logger: logger,
dev: dev,
flushCancel: cancel,
img: image.NewGray(image.Rect(0, 0, mt12232aW, mt12232aH)),
flushDone: make(chan struct{}), // For waiting flush to finish before exiting
}
go d.flushLoop(ctx)
// Temp debug draw
//if st0, st1 := d.dev.ReadStatus(0), d.dev.ReadStatus(1); false { //st0&0x20 == 0 && st1&0x20 == 0 {
// d.logger.Println("Display is already on")
//} else {
d.status("Setup start")
if err := d.dev.Reset(); err != nil {
return nil, fmt.Errorf("reset: %w", err)
}
d.status("After reset")
time.Sleep(200 * time.Millisecond)
if st0, st1 := d.dev.ReadStatus(0), d.dev.ReadStatus(1); st0 == 0 && st1 == 0 {
return nil, fmt.Errorf("No status response from dysplay")
}
if err := d.powerOn(); err != nil {
return nil, fmt.Errorf("power on: %w", err)
}
d.status("Setup end")
return &d, nil
}
func (d *displayMt12232a) status(label string) {
//d.logger.Printf("STATUS %s -- L: %08b R: %08b\n", label, d.dev.ReadStatus(0)&0xFF, d.dev.ReadStatus(1)&0xFF)
}
func (d *displayMt12232a) powerOn() error {
// Reset
if err := d.dev.WriteCode(0, 0xE2); err != nil {
return fmt.Errorf("reset: %w", err)
}
if err := d.dev.WriteCode(1, 0xE2); err != nil {
return fmt.Errorf("reset: %w", err)
}
// ReadModifyWrite off
if err := d.dev.WriteCode(0, 0xEE); err != nil {
return fmt.Errorf("RMW off: %w", err)
}
if err := d.dev.WriteCode(1, 0xEE); err != nil {
return fmt.Errorf("RMW off: %w", err)
}
// Turn on common mode
if err := d.dev.WriteCode(0, 0xA4); err != nil {
return fmt.Errorf("turn on common mode: %w", err)
}
if err := d.dev.WriteCode(1, 0xA4); err != nil {
return fmt.Errorf("turn on common mode: %w", err)
}
// Multiplex 1/32
if err := d.dev.WriteCode(0, 0xA9); err != nil {
return fmt.Errorf("multiplex 1/32: %w", err)
}
if err := d.dev.WriteCode(1, 0xA9); err != nil {
return fmt.Errorf("multiplex 1/32: %w", err)
}
// Top line to 0
if err := d.dev.WriteCode(0, 0xC0); err != nil {
return fmt.Errorf("top line to 0: %w", err)
}
if err := d.dev.WriteCode(1, 0xC0); err != nil {
return fmt.Errorf("top line to 0: %w", err)
}
// Invert scan RAM
if err := d.dev.WriteCode(0, 0xA1); err != nil {
return fmt.Errorf("inver scan RAM: %w", err)
}
if err := d.dev.WriteCode(1, 0xA0); err != nil {
return fmt.Errorf("inver scan RAM: %w", err)
}
// Display on
if err := d.dev.WriteCode(0, 0xAF); err != nil {
return fmt.Errorf("display on: %w", err)
}
if err := d.dev.WriteCode(1, 0xAF); err != nil {
return fmt.Errorf("display on: %w", err)
}
//time.Sleep(time.Second)
// Check that crystals are turned on
// The same but with error
if d.dev.ReadStatus(0)&0x20 != 0 {
return fmt.Errorf("Left cristal is off")
}
if d.dev.ReadStatus(1)&0x20 != 0 {
return fmt.Errorf("Right cristal is off")
}
d.isTurnedOn = true
return nil
}
func (d *displayMt12232a) IsReady() error {
if !d.isTurnedOn {
return fmt.Errorf("display is turned off")
}
return nil
}
func (d *displayMt12232a) Test(ctx context.Context) error {
start := time.Now()
for p := 0; p < 4; p++ {
d.dev.WriteCode(0, byte(p)|0xB8)
d.dev.WriteCode(0, 0x13)
for c := 0; c < 61; c++ {
d.dev.WriteData(0, byte(rand.Int()))
}
d.dev.WriteCode(1, byte(p)|0xB8)
d.dev.WriteCode(1, 0x00)
for c := 0; c < 61; c++ {
d.dev.WriteData(1, byte(rand.Int()))
}
}
end := time.Now()
d.logger.Println(end.Sub(start))
return nil
}
func (d *displayMt12232a) GetBounds() image.Rectangle {
return image.Rect(0, 0, 122, 32)
}
func (d *displayMt12232a) FlushFull(img *image.Gray) error {
st := time.Now()
for p := byte(0); p < 4; p++ {
d.dev.WriteCode(0, (3-p)|0xB8)
d.dev.WriteCode(0, 0x13)
for c := 0; c < 61; c++ {
d.dev.WriteDatas(0, []byte{
img.Pix[int(p<<3+7)*122+c],
img.Pix[int(p<<3+6)*122+c],
img.Pix[int(p<<3+5)*122+c],
img.Pix[int(p<<3+4)*122+c],
img.Pix[int(p<<3+3)*122+c],
img.Pix[int(p<<3+2)*122+c],
img.Pix[int(p<<3+1)*122+c],
img.Pix[int(p<<3+0)*122+c],
})
}
d.dev.WriteCode(1, (3-p)|0xB8)
d.dev.WriteCode(1, 0x00)
for c := 61; c < 122; c++ {
d.dev.WriteDatas(1, []byte{
img.Pix[int(p<<3+7)*122+c],
img.Pix[int(p<<3+6)*122+c],
img.Pix[int(p<<3+5)*122+c],
img.Pix[int(p<<3+4)*122+c],
img.Pix[int(p<<3+3)*122+c],
img.Pix[int(p<<3+2)*122+c],
img.Pix[int(p<<3+1)*122+c],
img.Pix[int(p<<3+0)*122+c],
})
}
}
d.logger.Println("Flush time:", time.Since(st))
return nil
}
func (d *displayMt12232a) Flush(crystal, page byte) {
// !!! TODO Need to update GO to 1.23 to use .Or !!!
bit := uint32(1 << (crystal*4 + page))
d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | bit)
}
func (d *displayMt12232a) FlushByMask(mask uint32) {
// !!! TODO Need to update GO to 1.23 to use .Or !!!
d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | mask)
}
func (d *displayMt12232a) GetFlushMaskBit(crystal, page byte) uint32 {
return uint32(1 << (crystal*4 + page))
}
func (d *displayMt12232a) Close() error {
if d.flushCancel != nil {
d.flushCancel()
d.flushCancel = nil
<-d.flushDone
}
d.isTurnedOn = false
return d.dev.Close()
}
func (d *displayMt12232a) flushLoop(ctx context.Context) {
for {
select {
case <-ctx.Done():
close(d.flushDone)
return
case <-time.After(flushUpdateTimeout):
forUpdate := d.pagesFlushFlags.Swap(0)
checkBit := uint32(1)
//st := time.Now()
d.LockImg()
for p := byte(0); p < 4; p++ {
if forUpdate&(checkBit) != 0 {
d.dev.WriteCode(0, (3-p)|0xB8)
d.dev.WriteCode(0, 0x13)
for c := 0; c < 61; c++ {
d.dev.WriteDatas(0, []byte{
d.img.Pix[int(p<<3+7)*122+c],
d.img.Pix[int(p<<3+6)*122+c],
d.img.Pix[int(p<<3+5)*122+c],
d.img.Pix[int(p<<3+4)*122+c],
d.img.Pix[int(p<<3+3)*122+c],
d.img.Pix[int(p<<3+2)*122+c],
d.img.Pix[int(p<<3+1)*122+c],
d.img.Pix[int(p<<3+0)*122+c],
})
}
}
checkBit = checkBit << 1
}
//d.logger.Printf("%08b - %s\n", forUpdate, time.Since(st))
for p := byte(0); p < 4; p++ {
if forUpdate&(checkBit) != 0 {
d.dev.WriteCode(1, (3-p)|0xB8)
d.dev.WriteCode(1, 0x00)
for c := 61; c < 122; c++ {
d.dev.WriteDatas(1, []byte{
d.img.Pix[int(p<<3+7)*122+c],
d.img.Pix[int(p<<3+6)*122+c],
d.img.Pix[int(p<<3+5)*122+c],
d.img.Pix[int(p<<3+4)*122+c],
d.img.Pix[int(p<<3+3)*122+c],
d.img.Pix[int(p<<3+2)*122+c],
d.img.Pix[int(p<<3+1)*122+c],
d.img.Pix[int(p<<3+0)*122+c],
})
}
}
checkBit = checkBit << 1
}
d.UnlockImg()
}
}
}
func (d *displayMt12232a) GetImg() *image.Gray {
return d.img
}
func (d *displayMt12232a) LockImg() {
d.imgMutex.Lock()
}
func (d *displayMt12232a) UnlockImg() {
d.imgMutex.Unlock()
}

View File

@ -1,6 +1,7 @@
package display package display
import ( import (
"context"
"fmt" "fmt"
"image" "image"
"log" "log"
@ -36,7 +37,12 @@ func newMt12864a(logger *log.Logger) (Display, error) {
d.test() d.test()
d.logger.Println("Draw finished") d.logger.Println("Draw finished")
return &d, nil //return &d, nil
return nil, fmt.Errorf("IMPLEMENTATION COMMENTED")
}
func (d *displayMt12864a) Test(ctx context.Context) error {
return nil
} }
func (d *displayMt12864a) test() { func (d *displayMt12864a) test() {
@ -137,10 +143,22 @@ func (d *displayMt12864a) GetBounds() image.Rectangle {
return image.Rectangle{} return image.Rectangle{}
} }
func (d *displayMt12864a) Flush(img *image.Gray) error { func (d *displayMt12864a) Flush(crystal, page byte) {
return nil }
func (d *displayMt12864a) FlushByMask(mask uint32) {
} }
func (d *displayMt12864a) Close() error { func (d *displayMt12864a) Close() error {
return rpio.Close() return rpio.Close()
} }
func (d *displayMt12864a) GetImg() *image.Gray {
return nil
}
func (d *displayMt12864a) LockImg() {
}
func (d *displayMt12864a) UnlockImg() {
}

View File

@ -1,6 +1,7 @@
package display package display
import ( import (
"context"
"fmt" "fmt"
"image" "image"
"log" "log"
@ -41,19 +42,29 @@ func newSsd1306(logger *log.Logger) (Display, error) {
return nil, fmt.Errorf("create i2c: %w", err) return nil, fmt.Errorf("create i2c: %w", err)
} }
return &displaySsd1306{ _ = displaySsd1306{
logger: logger, logger: logger,
bus: bus, bus: bus,
dev: dev, dev: dev,
}, nil }
return nil, fmt.Errorf("IMPLEMENTATION COMMENTED")
} }
func (d *displaySsd1306) GetBounds() image.Rectangle { func (d *displaySsd1306) GetBounds() image.Rectangle {
return d.dev.Bounds() return d.dev.Bounds()
} }
func (d *displaySsd1306) Flush(img *image.Gray) error { func (d *displaySsd1306) Test(ctx context.Context) error {
return d.dev.Draw(img.Bounds(), d.img, image.Point{}) return nil
}
func (d *displaySsd1306) Flush(crystal, page byte) {
//return d.dev.Draw(img.Bounds(), d.img, image.Point{})
}
func (d *displaySsd1306) FlushByMask(mask uint32) {
//return d.dev.Draw(img.Bounds(), d.img, image.Point{})
} }
func (d *displaySsd1306) Close() error { func (d *displaySsd1306) Close() error {
@ -62,3 +73,13 @@ func (d *displaySsd1306) Close() error {
} }
return nil return nil
} }
func (d *displaySsd1306) GetImg() *image.Gray {
return nil
}
func (d *displaySsd1306) LockImg() {
}
func (d *displaySsd1306) UnlockImg() {
}

View File

@ -2,9 +2,9 @@ package drawer
import "math" import "math"
func (d *drawer) FillBar(sx, sy, ex, ey, color int) { func (d *drawer) FillBar(sx, sy, ex, ey int, color byte) {
d.Lock() d.dev.LockImg()
defer d.Unlock() defer d.dev.UnlockImg()
bounds := d.img.Bounds() bounds := d.img.Bounds()
// Crop // Crop
@ -18,14 +18,14 @@ func (d *drawer) FillBar(sx, sy, ex, ey, color int) {
addr := lineaddr addr := lineaddr
le := addr + w le := addr + w
for ; addr < le; addr++ { for ; addr < le; addr++ {
d.img.Pix[addr] = 255 d.img.Pix[addr] = color
} }
} }
} }
func (d *drawer) PutBar(sx, sy, ex, ey, color int) { func (d *drawer) PutBar(sx, sy, ex, ey int, color byte) {
d.Lock() d.dev.LockImg()
defer d.Unlock() defer d.dev.UnlockImg()
bounds := d.img.Bounds() bounds := d.img.Bounds()
// Crop // Crop
@ -36,7 +36,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
x1 := int(math.Min(float64(ex), float64(bounds.Dx()))) x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ { for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ {
d.img.Pix[addr] = 255 d.img.Pix[addr] = color
} }
} }
// Bottom // Bottom
@ -45,7 +45,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
x1 := int(math.Min(float64(ex), float64(bounds.Dx()))) x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
for addr := (ey-1)*bounds.Dx() + x0; addr < (ey-1)*bounds.Dx()+x1; addr++ { for addr := (ey-1)*bounds.Dx() + x0; addr < (ey-1)*bounds.Dx()+x1; addr++ {
d.img.Pix[addr] = 255 d.img.Pix[addr] = color
} }
} }
// Left // Left
@ -54,7 +54,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
y1 := int(math.Min(float64(ey), float64(bounds.Dy()))) y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
for addr := y0*bounds.Dx() + sx; addr < y1*bounds.Dx()+sx; addr += bounds.Dx() { for addr := y0*bounds.Dx() + sx; addr < y1*bounds.Dx()+sx; addr += bounds.Dx() {
d.img.Pix[addr] = 255 d.img.Pix[addr] = color
} }
} }
// Right // Right
@ -63,7 +63,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
y1 := int(math.Min(float64(ey), float64(bounds.Dy()))) y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
for addr := y0*bounds.Dx() + (ex - 1); addr < y1*bounds.Dx()+(ex-1); addr += bounds.Dx() { for addr := y0*bounds.Dx() + (ex - 1); addr < y1*bounds.Dx()+(ex-1); addr += bounds.Dx() {
d.img.Pix[addr] = 255 d.img.Pix[addr] = color
} }
} }
} }

View File

@ -2,76 +2,72 @@ package drawer
import ( import (
"image" "image"
"sync"
"gitea.unprism.ru/yotia/display-test/display" "gitea.unprism.ru/yotia/display-test/display"
"golang.org/x/image/font"
"golang.org/x/image/font/inconsolata"
) )
type drawer struct { type drawer struct {
font font.Face dev display.Display
dev display.Display img *image.Gray
img *image.Gray
imgMutex sync.Mutex
} }
type Drawer interface { type Drawer interface {
GetFont() font.Face GetDisplay() display.Display
// Lowlevel image W() int
GetImg() *image.Gray H() int
Lock()
Unlock()
Clear() Clear()
Flush() error Flush()
PutText(x, y int, text string) PutText(x, y int, text string)
PutBar(x0, y0, x1, y1, color int) PutBar(x0, y0, x1, y1 int, color byte)
FillBar(x0, y0, x1, y1, color int) FillBar(x0, y0, x1, y1 int, color byte)
CopyImg(x0, y0 int, img Image)
} }
func New(dev display.Display) Drawer { func New(dev display.Display) Drawer {
return &drawer{ d := &drawer{
font: inconsolata.Regular8x16,
dev: dev, dev: dev,
img: image.NewGray(dev.GetBounds()),
} }
d.img = d.dev.GetImg()
return d
} }
func (d *drawer) GetImg() *image.Gray { func (d *drawer) GetDisplay() display.Display {
return d.img return d.dev
} }
func (d *drawer) GetFont() font.Face { func (d *drawer) W() int {
return d.font return d.img.Rect.Dx()
} }
func (d *drawer) Lock() { func (d *drawer) H() int {
d.imgMutex.Lock() return d.img.Rect.Dy()
}
func (d *drawer) Unlock() {
d.imgMutex.Unlock()
} }
func (d *drawer) Clear() { func (d *drawer) Clear() {
d.Lock() d.dev.LockImg()
defer d.Unlock() defer d.dev.UnlockImg()
for i := range d.img.Pix { for i := range d.img.Pix {
d.img.Pix[i] = 0 d.img.Pix[i] = 0
} }
d.dev.FlushByMask(0xFF)
} }
func (d *drawer) Flush() error { func (d *drawer) Flush() {
d.Lock() // Flush all pages
defer d.Unlock() d.dev.Flush(0, 0)
d.dev.Flush(0, 1)
return d.dev.Flush(d.img) d.dev.Flush(0, 2)
d.dev.Flush(0, 3)
d.dev.Flush(1, 0)
d.dev.Flush(1, 1)
d.dev.Flush(1, 2)
d.dev.Flush(1, 3)
} }

354
drawer/font.go Normal file
View File

@ -0,0 +1,354 @@
package drawer
import (
"log"
"golang.org/x/text/encoding/charmap"
)
const (
CharGap = 1
LineGap = 1
FontCharW = 5
FontCharH = 7
LineH = FontCharH + LineGap
)
// Standard ASCII 5x7 font
var stdFont = []byte{
0x0, 0x0, 0x0, 0x0, 0x0, // Empty Cell
0x3e, 0x5b, 0x4f, 0x5b, 0x3e, // Sad Smiley
0x3e, 0x6b, 0x4f, 0x6b, 0x3e, // Happy Smiley
0x1c, 0x3e, 0x7c, 0x3e, 0x1c, // Heart
0x18, 0x3c, 0x7e, 0x3c, 0x18, // Diamond
0x1c, 0x57, 0x7d, 0x57, 0x1c, // Clubs
0x1c, 0x5e, 0x7f, 0x5e, 0x1c, // Spades
0x18, 0x3c, 0x18, 0x0, 0x0, // Bullet Point
0xff, 0xe7, 0xc3, 0xe7, 0xff, // Rev Bullet Point
0x18, 0x24, 0x18, 0x0, 0x0, // Hollow Bullet Point
0xff, 0xe7, 0xdb, 0xe7, 0xff, // Rev Hollow BP
0x30, 0x48, 0x3a, 0x6, 0xe, // Male
0x26, 0x29, 0x79, 0x29, 0x26, // Female
0x40, 0x7f, 0x5, 0x5, 0x7, // Music Note 1
0x40, 0x7f, 0x5, 0x25, 0x3f, // Music Note 2
0x5a, 0x3c, 0xe7, 0x3c, 0x5a, // Snowflake
0x7f, 0x3e, 0x1c, 0x1c, 0x8, // Right Pointer
0x8, 0x1c, 0x1c, 0x3e, 0x7f, // Left Pointer
0x14, 0x22, 0x7f, 0x22, 0x14, // UpDown Arrows
0x5f, 0x5f, 0x0, 0x5f, 0x5f, // Double Exclamation
0x6, 0x9, 0x7f, 0x1, 0x7f, // Paragraph Mark
0x66, 0x89, 0x95, 0x6a, 0x0, // Section Mark
0x60, 0x60, 0x60, 0x60, 0x60, // Double Underline
0x94, 0xa2, 0xff, 0xa2, 0x94, // UpDown Underlined
0x8, 0x4, 0x7e, 0x4, 0x8, // Up Arrow
0x10, 0x20, 0x7e, 0x20, 0x10, // Down Arrow
0x8, 0x8, 0x2a, 0x1c, 0x8, // Right Arrow
0x8, 0x1c, 0x2a, 0x8, 0x8, // Left Arrow
0x1e, 0x10, 0x10, 0x10, 0x10, // Angled
0xc, 0x1e, 0xc, 0x1e, 0xc, // Squashed #
0x30, 0x38, 0x3e, 0x38, 0x30, // Up Pointer
0x6, 0xe, 0x3e, 0xe, 0x6, // Down Pointer
0x0, 0x0, 0x0, 0x0, 0x0, // Space
0x5f, 0x0, 0x0, 0x0, 0x0, // !
0x3, 0x0, 0x3, 0x0, 0x0, // "
0x14, 0x3e, 0x14, 0x3e, 0x14, // #
0x24, 0x6a, 0x2b, 0x12, 0x0, // $
0x63, 0x13, 0x8, 0x64, 0x63, // %
0x36, 0x49, 0x56, 0x20, 0x50, // &
0x3, 0x0, 0x0, 0x0, 0x0, // '
0x1c, 0x22, 0x41, 0x0, 0x0, // (
0x41, 0x22, 0x1c, 0x0, 0x0, // )
0x28, 0x18, 0xe, 0x18, 0x28, // *
0x8, 0x8, 0x3e, 0x8, 0x8, // +
0xb0, 0x70, 0x0, 0x0, 0x0, // ,
0x8, 0x8, 0x8, 0x8, 0x0, // -
0x60, 0x60, 0x0, 0x0, 0x0, // .
0x60, 0x18, 0x6, 0x1, 0x0, // /
0x3e, 0x41, 0x41, 0x3e, 0x0, // 0
0x42, 0x7f, 0x40, 0x0, 0x0, // 1
0x62, 0x51, 0x49, 0x46, 0x0, // 2
0x22, 0x41, 0x49, 0x36, 0x0, // 3
0x18, 0x14, 0x12, 0x7f, 0x0, // 4
0x27, 0x45, 0x45, 0x39, 0x0, // 5
0x3e, 0x49, 0x49, 0x30, 0x0, // 6
0x61, 0x11, 0x9, 0x7, 0x0, // 7
0x36, 0x49, 0x49, 0x36, 0x0, // 8
0x6, 0x49, 0x49, 0x3e, 0x0, // 9
0x00, 0x0, 0x22, 0x0, 0x0, // :
0x80, 0x50, 0x0, 0x0, 0x0, // ;
0x10, 0x28, 0x44, 0x0, 0x0, // <
0x14, 0x14, 0x14, 0x0, 0x0, // =
0x44, 0x28, 0x10, 0x0, 0x0, // >
0x2, 0x59, 0x9, 0x6, 0x0, // ?
0x3e, 0x49, 0x55, 0x5d, 0xe, // @
0x7e, 0x11, 0x11, 0x7e, 0x0, // A
0x7f, 0x49, 0x49, 0x36, 0x0, // B
0x3e, 0x41, 0x41, 0x22, 0x0, // C
0x7f, 0x41, 0x41, 0x3e, 0x0, // D
0x7f, 0x49, 0x49, 0x41, 0x0, // E
0x7f, 0x9, 0x9, 0x1, 0x0, // F
0x3e, 0x41, 0x49, 0x7a, 0x0, // G
0x7f, 0x8, 0x8, 0x7f, 0x0, // H
0x41, 0x7f, 0x41, 0x0, 0x0, // I
0x30, 0x40, 0x41, 0x3f, 0x0, // J
0x7f, 0x8, 0x14, 0x63, 0x0, // K
0x7f, 0x40, 0x40, 0x40, 0x0, // L
0x7f, 0x2, 0xc, 0x2, 0x7f, // M
0x7f, 0x4, 0x8, 0x10, 0x7f, // N
0x3e, 0x41, 0x41, 0x3e, 0x0, // O
0x7f, 0x9, 0x9, 0x6, 0x0, // P
0x3e, 0x41, 0x41, 0xbe, 0x0, // Q
0x7f, 0x9, 0x9, 0x76, 0x0, // R
0x46, 0x49, 0x49, 0x32, 0x0, // S
0x1, 0x1, 0x7f, 0x1, 0x1, // T
0x3f, 0x40, 0x40, 0x3f, 0x0, // U
0xf, 0x30, 0x40, 0x30, 0xf, // V
0x3f, 0x40, 0x38, 0x40, 0x3f, // W
0x63, 0x14, 0x8, 0x14, 0x63, // X
0x7, 0x8, 0x70, 0x8, 0x7, // Y
0x61, 0x51, 0x49, 0x47, 0x0, // Z
0x7f, 0x41, 0x0, 0x0, 0x0, // [
0x1, 0x6, 0x18, 0x60, 0x0, //
0x41, 0x7f, 0x0, 0x0, 0x0, // ]
0x2, 0x1, 0x2, 0x0, 0x0, // ^
0x40, 0x40, 0x40, 0x40, 0x0, // _
0x1, 0x2, 0x0, 0x0, 0x0, // `
0x20, 0x54, 0x54, 0x78, 0x0, // a
0x7f, 0x44, 0x44, 0x38, 0x0, // b
0x38, 0x44, 0x44, 0x28, 0x0, // c
0x38, 0x44, 0x44, 0x7f, 0x0, // d
0x38, 0x54, 0x54, 0x18, 0x0, // e
0x4, 0x7e, 0x5, 0x0, 0x0, // f
0x98, 0xa4, 0xa4, 0x78, 0x0, // g
0x7f, 0x4, 0x4, 0x78, 0x0, // h
0x44, 0x7d, 0x40, 0x0, 0x0, // i
0x40, 0x80, 0x84, 0x7d, 0x0, // j
0x7f, 0x10, 0x28, 0x44, 0x0, // k
0x41, 0x7f, 0x40, 0x0, 0x0, // l
0x7c, 0x4, 0x7c, 0x4, 0x78, // m
0x7c, 0x4, 0x4, 0x78, 0x0, // n
0x38, 0x44, 0x44, 0x38, 0x0, // o
0xfc, 0x24, 0x24, 0x18, 0x0, // p
0x18, 0x24, 0x24, 0xfc, 0x0, // q
0x7c, 0x8, 0x4, 0x4, 0x0, // r
0x48, 0x54, 0x54, 0x24, 0x0, // s
0x4, 0x3f, 0x44, 0x0, 0x0, // t
0x3c, 0x40, 0x40, 0x7c, 0x0, // u
0x1c, 0x20, 0x40, 0x20, 0x1c, // v
0x3c, 0x40, 0x3c, 0x40, 0x3c, // w
0x44, 0x28, 0x10, 0x28, 0x44, // x
0x9c, 0xa0, 0xa0, 0x7c, 0x0, // y
0x64, 0x54, 0x4c, 0x0, 0x0, // z
0x8, 0x36, 0x41, 0x0, 0x0, // {
0x7f, 0x0, 0x0, 0x0, 0x0, // |
0x41, 0x36, 0x8, 0x0, 0x0, // }
0x8, 0x4, 0x8, 0x4, 0x0, // ~
0x0, 0x0, 0x0, 0x0, 0x0, // Hollow Up Arrow
0x7f, 0x55, 0x49, 0x55, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x03, 0x03, 0x00, 0x00, 0x00, // °
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
0x7e, 0x11, 0x11, 0x7e, 0x0, // А
0x7f, 0x49, 0x49, 0x31, 0x0, // Б
0x7f, 0x49, 0x49, 0x36, 0x0, // В
0x7f, 0x1, 0x1, 0x1, 0x0, // Г
0xc0, 0x7e, 0x41, 0x7f, 0xc0, // Д
0x7f, 0x49, 0x49, 0x41, 0x0, // Е
0x77, 0x8, 0x7f, 0x8, 0x77, // Ж
0x22, 0x41, 0x49, 0x36, 0x0, // З
0x7f, 0x20, 0x10, 0x7f, 0x0, // И
0x7f, 0x20, 0x11, 0x7d, 0x0, // Й
0x7f, 0x8, 0x14, 0x63, 0x0, // К
0x40, 0x3c, 0x2, 0x7f, 0x0, // Л
0x7f, 0x2, 0xc, 0x2, 0x7f, // М
0x7f, 0x8, 0x8, 0x7f, 0x0, // Н
0x3e, 0x41, 0x41, 0x3e, 0x0, // О
0x7f, 0x1, 0x1, 0x7f, 0x0, // П
0x7f, 0x9, 0x9, 0x6, 0x0, // Р
0x3e, 0x41, 0x41, 0x22, 0x0, // С
0x1, 0x1, 0x7f, 0x1, 0x1, // Т
0x47, 0x48, 0x48, 0x3f, 0x0, // У
0xe, 0x11, 0x7f, 0x11, 0xe, // Ф
0x63, 0x14, 0x8, 0x14, 0x63, // Х
0x7f, 0x40, 0x40, 0x7f, 0xc0, // Ц
0xf, 0x8, 0x8, 0x7f, 0x0, // Ч
0x7f, 0x40, 0x7c, 0x40, 0x7f, // Ш
0x7f, 0x40, 0x7c, 0x40, 0xff, // Щ
0x1, 0x7f, 0x48, 0x48, 0x30, // Ъ
0x7f, 0x48, 0x30, 0x0, 0x7f, // Ы
0x7f, 0x48, 0x48, 0x30, 0x0, // Ь
0x41, 0x49, 0x49, 0x3e, 0x0, // Э
0x7f, 0x8, 0x3e, 0x41, 0x3e, // Ю
0x46, 0x29, 0x19, 0x7f, 0x0, // Я
0x20, 0x54, 0x54, 0x78, 0x0, // а
0x3c, 0x4a, 0x4a, 0x31, 0x0, // б
0x7e, 0x59, 0x56, 0x20, 0x0, // в
0x7c, 0x4, 0x4, 0x4, 0x0, // г
0xc0, 0x78, 0x44, 0x7c, 0xc0, // д
0x38, 0x54, 0x54, 0x18, 0x0, // е
0x6c, 0x10, 0x7c, 0x10, 0x6c, // ж
0x48, 0x84, 0x94, 0x68, 0x0, // з
0x7c, 0x20, 0x10, 0x7c, 0x0, // и
0x7c, 0x21, 0x11, 0x7c, 0x0, // й
0x7c, 0x10, 0x28, 0x44, 0x0, // к
0x40, 0x38, 0x4, 0x7c, 0x0, // л
0x7c, 0x8, 0x10, 0x8, 0x7c, // м
0x7c, 0x10, 0x10, 0x7c, 0x0, // н
0x38, 0x44, 0x44, 0x38, 0x0, // о
0x7c, 0x4, 0x4, 0x7c, 0x0, // п
0xfc, 0x24, 0x24, 0x18, 0x0, // р
0x38, 0x44, 0x44, 0x28, 0x0, // с
0x4, 0x7c, 0x4, 0x0, 0x0, // т
0x9c, 0xa0, 0xa0, 0x7c, 0x0, // у
0x18, 0x24, 0xfc, 0x24, 0x18, // ф
0x44, 0x28, 0x10, 0x28, 0x44, // х
0x7c, 0x40, 0x40, 0x7c, 0xc0, // ц
0x1c, 0x10, 0x10, 0x7c, 0x0, // ч
0x7c, 0x40, 0x70, 0x40, 0x7c, // ш
0x7c, 0x40, 0x70, 0x40, 0xfc, // щ
0x4, 0x7c, 0x48, 0x48, 0x30, // ъ
0x7c, 0x48, 0x30, 0x0, 0x7c, // ы
0x7c, 0x48, 0x48, 0x30, 0x0, // ь
0x44, 0x54, 0x54, 0x38, 0x0, // э
0x7c, 0x10, 0x38, 0x44, 0x38, // ю
0x48, 0x34, 0x14, 0x7c, 0x0, // я
}
func IntMin(a, b int) int {
if a < b {
return a
}
return b
}
func IntMax(a, b int) int {
if a > b {
return a
}
return b
}
func (d *drawer) putChar(x0, y0 int, c byte) {
if int(c) > len(stdFont)/5 {
return
}
//if x0+7 < 0 || y0+5 < 0 || y0 >= d.img.Rect.Dy() || x0 >= d.img.Rect.Dx() {
// return
//}
minX := IntMax(x0, 0) - x0
minY := IntMax(y0, 0) - y0
maxY := IntMin(y0+7, d.img.Rect.Dy()) - y0
maxX := IntMin(x0+5, d.img.Rect.Dx()) - x0
for yi := minY; yi < maxY; yi++ {
lineaddr := (y0+yi)*d.img.Stride + x0
for xi := minX; xi < maxX; xi++ {
d.img.Pix[lineaddr+xi] = ((stdFont[int(c)*5+xi] >> yi) & 1) * 255
}
}
}
func convertStr(str string) []byte {
out, err := charmap.Windows1251.NewEncoder().Bytes([]byte(str))
if err != nil {
log.Println("ENCODE ERROR: ", err.Error())
}
return out
}
func (d *drawer) PutText(x0, y0 int, text string) {
d.dev.LockImg()
defer d.dev.UnlockImg()
asciiStr := convertStr(text)
//asciiStr := []byte(text)
// Create font drawer
x := x0
for _, c := range asciiStr {
d.putChar(x, y0, c)
x += CharGap + FontCharW
}
// Calculate update mask
mask := uint32(0)
// Implementation dependent !!!
// Now for mt12232
my0 := min(3, max(0, int((y0)/8)))
my1 := min(3, max(0, int((y0+FontCharH-1)/8)))
mx0 := min(1, max(0, int(x0/61)))
mx1 := min(1, max(0, int((x0+FontCharW*len(text)-1)/61)))
//log.Println(x0, y0, x1, y1)
for y := my0; y <= my1; y++ {
for x := mx0; x <= mx1; x++ {
mask |= (1 << (x*4 + y))
}
}
d.dev.FlushByMask(mask)
}

167
drawer/image.go Normal file
View File

@ -0,0 +1,167 @@
package drawer
// Simple glyphs like signal or service status that are too simple to be whole image
const (
SignalStatusGlyphI = 0 // first of 5
ServiceStatusGlyphI = 5 // first of 5
)
type Image struct {
W int
H int
Bits []byte
}
var CommonGlyphs = []Image{
{ // Signal 0/0
W: 7,
H: 7,
Bits: []byte{
1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1,
1, 0, 0, 0, 1, 0, 1,
0, 0, 1, 0, 0, 0, 1,
1, 0, 1, 0, 1, 0, 1,
},
},
{ // Signal 1/4
W: 7,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0,
},
},
{ // Signal 2/4
W: 7,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0,
1, 0, 1, 0, 0, 0, 0,
},
},
{ // Signal 3/4
W: 7,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 0, 1, 0, 0,
},
},
{ // Signal 4/4
W: 7,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1,
0, 0, 1, 0, 1, 0, 1,
0, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1,
},
},
{ // Service 0/4
W: 9,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
},
},
{ // Service 1/4
W: 9,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
},
},
{ // Service 2/4
W: 9,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
},
},
{ // Service 3/4
W: 9,
H: 7,
Bits: []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
},
},
{ // Service 4/4
W: 9,
H: 7,
Bits: []byte{
0, 1, 1, 1, 1, 1, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 1, 1, 1, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
},
},
}
func (d *drawer) CopyImg(x0, y0 int, img Image) {
d.dev.LockImg()
defer d.dev.UnlockImg()
// Coords in img coord space
minX := IntMax(x0, 0) - x0
minY := IntMax(y0, 0) - y0
maxX := IntMin(x0+img.W, d.img.Rect.Dx()) - x0
maxY := IntMin(y0+img.H, d.img.Rect.Dy()) - y0
w := maxX - minX
if w <= 0 {
return
}
for yi := minY; yi < maxY; yi++ {
copy(d.img.Pix[(y0+yi)*d.img.Stride+x0:], img.Bits[yi*img.W:yi*img.W+w])
}
}

View File

@ -1,26 +0,0 @@
package drawer
import (
"image"
"image/color"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
func (d *drawer) PutText(x, y int, text string) {
d.Lock()
defer d.Unlock()
// Create font drawer
col := color.Gray{255}
point := fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)}
drawer := &font.Drawer{
Dst: d.img,
Src: image.NewUniform(col),
Face: d.font,
Dot: point,
}
drawer.DrawString(text)
}

6
go.mod
View File

@ -3,9 +3,10 @@ module gitea.unprism.ru/yotia/display-test
go 1.22.5 go 1.22.5
require ( require (
gitea.unprism.ru/KRBL/sim-modem v0.1.7 gitea.unprism.ru/KRBL/mpu v0.1.0
gitea.unprism.ru/KRBL/sim-modem v0.1.8
github.com/stianeikeland/go-rpio/v4 v4.6.0 github.com/stianeikeland/go-rpio/v4 v4.6.0
golang.org/x/image v0.19.0 golang.org/x/text v0.17.0
periph.io/x/conn/v3 v3.7.1 periph.io/x/conn/v3 v3.7.1
periph.io/x/devices/v3 v3.7.1 periph.io/x/devices/v3 v3.7.1
periph.io/x/host/v3 v3.8.2 periph.io/x/host/v3 v3.8.2
@ -14,5 +15,6 @@ require (
require ( require (
github.com/creack/goselect v0.1.2 // indirect github.com/creack/goselect v0.1.2 // indirect
go.bug.st/serial v1.6.2 // indirect go.bug.st/serial v1.6.2 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sys v0.24.0 // indirect golang.org/x/sys v0.24.0 // indirect
) )

272
main.go
View File

@ -4,15 +4,20 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"math/rand"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"gitea.unprism.ru/yotia/display-test/api"
"gitea.unprism.ru/yotia/display-test/api/progress"
"gitea.unprism.ru/yotia/display-test/components"
"gitea.unprism.ru/yotia/display-test/display" "gitea.unprism.ru/yotia/display-test/display"
"gitea.unprism.ru/yotia/display-test/drawer" "gitea.unprism.ru/yotia/display-test/drawer"
"gitea.unprism.ru/KRBL/sim-modem/api/modem" "gitea.unprism.ru/KRBL/sim-modem/api/modem"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
) )
const ( const (
@ -42,138 +47,195 @@ var d drawer.Drawer
var m modem.Modem var m modem.Modem
func DrawProgress(ctx context.Context, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
// Draw outline
x := 0
y := d.GetFont().Metrics().Height.Ceil() + 4
w := d.GetImg().Rect.Dx()
h := d.GetFont().Metrics().Height.Ceil() - 4
d.PutBar(x, y, x+w, y+h, 255)
gap := 3
// Draw body
x += gap
y += gap
w -= 2 * gap
h -= 2 * gap
lastX := x
for {
select {
case <-ctx.Done():
return nil
case <-time.After(displayUpdateTimeout):
value := 1 - float64(time.Until(deadline).Milliseconds())/float64(timeout.Milliseconds())
newX := x + int(value*float64(w))
// Draw
d.FillBar(lastX, y, newX, y+h, 255)
lastX = newX
if value >= 1 {
return nil
}
if err := d.Flush(); err != nil {
return fmt.Errorf("display flush: %w", err)
}
}
}
}
func Init(ctx context.Context) error { func Init(ctx context.Context) error {
// Display init // Display init
var err error var err error
dev, err = display.New(log.New(log.Writer(), "display", log.LstdFlags), display.SSD1306) dev, err = display.New(log.New(log.Writer(), "display", log.LstdFlags), display.MT12232A)
if err != nil { if err != nil {
return fmt.Errorf("new display: %w", err) return fmt.Errorf("new display: %w", err)
} }
logger.Println("Display inited") logger.Println("Display inited")
// Create drawer
d := drawer.New(dev) d := drawer.New(dev)
d.Clear() d.Clear()
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Initing modem...") //d.FillBar(0, 0, 10, 10, 1)
d.PutText(2, 0, "12345678901234567890")
d.PutText(2, drawer.LineH, "WWWWWWWWWWWWWWWWWWWWWWW")
d.PutText(1, drawer.LineH*2, "Progress bar")
d.Flush() d.Flush()
// Modem init time.Sleep(time.Second)
mInitCtx, mInitCtxCancel := context.WithCancel(ctx)
go DrawProgress(mInitCtx, 13*time.Second)
defer mInitCtxCancel()
m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags)) pbc, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH)
if err := m.Init(); err != nil { if err != nil {
return fmt.Errorf("modem init: %w", err) return fmt.Errorf("new progress bar: %w", err)
} }
logger.Println("Display inited")
mInitCtxCancel()
d.Clear() pb := progress.NewHandler(pbc, 10)
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Modem inited.")
d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, "Starting test...")
d.Flush()
return nil
}
func Close() {
m.Close()
if d != nil {
d.Clear()
d.Flush()
}
dev.Close()
}
func MainLoop(ctx context.Context) error { for range 10 {
mainLoopTimeout := time.Second
frameCounter := 0
deadline := time.Now().Add(100 * time.Millisecond)
for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil return nil
case <-time.After(time.Until(deadline)): case <-time.After(time.Duration(rand.Int()%1000) * time.Millisecond):
deadline = time.Now().Add(mainLoopTimeout)
st, _ := m.Gps().GetStatus()
data := m.GetData()
coordsStr := fmt.Sprintf("%03d°%02.0f'%s %03d°%02.0f'%s",
int(data.Latitude), (data.Latitude-float64(int(data.Latitude)))*100, data.LatitudeIndicator,
int(data.Longitude), (data.Longitude-float64(int(data.Longitude)))*100, data.LongitudeIndicator)
var str1 string
var str2 string
if frameCounter%4 < 2 {
str2 = fmt.Sprintf("Sat. count: %d", st.FoundSatelitesCount)
} else {
str2 = coordsStr
}
str1 = fmt.Sprintf("%s", time.Now().Format("15:04:05"))
d.Clear()
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), str1)
d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, str2)
d.Flush()
frameCounter++
} }
pb.Checkpoint()
} }
time.Sleep(400 * time.Millisecond)
pb.GetBar().Clear()
pb.Close()
d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0)
d.PutText(1, drawer.LineH*2, "Done")
d.Flush()
time.Sleep(100 * time.Millisecond)
//d.PutText(0, 0, "можно 4 строки впихнуть")
//d.PutText(0, drawer.FontCharH+drawer.LineGap, "каждая буква 5x7")
//d.PutText(0, (drawer.FontCharH+drawer.LineGap)*2, "+ русский через жопу")
//d.PutText(0, (drawer.FontCharH+drawer.LineGap)*3, "KRBL forever!!")
// Modem init
//mInitCtx, mInitCtxCancel := context.WithCancel(ctx)
//go DrawProgress(mInitCtx, 13*time.Second)
//defer mInitCtxCancel()
//
//m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
//if err := m.Init(); err != nil {
// return fmt.Errorf("modem init: %w", err)
//}
//logger.Println("Display inited")
//mInitCtxCancel()
//
//d.Clear()
//d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Modem inited.")
//d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, "Starting test...")
//d.Flush()
return nil
} }
func Close() {
//m.Close()
//if d != nil {
// d.Clear()
// d.Flush()
//}
dev.Close()
}
//func MainLoop(ctx context.Context) error {
// mainLoopTimeout := time.Second
//
// frameCounter := 0
// deadline := time.Now().Add(100 * time.Millisecond)
// for {
// select {
// case <-ctx.Done():
// return nil
// case <-time.After(time.Until(deadline)):
// deadline = time.Now().Add(mainLoopTimeout)
//
// st, _ := m.Gps().GetStatus()
// data := m.GetData()
//
// coordsStr := fmt.Sprintf("%03d°%02.0f'%s %03d°%02.0f'%s",
// int(data.Latitude), (data.Latitude-float64(int(data.Latitude)))*100, data.LatitudeIndicator,
// int(data.Longitude), (data.Longitude-float64(int(data.Longitude)))*100, data.LongitudeIndicator)
// var str1 string
// var str2 string
//
// if frameCounter%4 < 2 {
// str2 = fmt.Sprintf("Sat. count: %d", st.FoundSatelitesCount)
// } else {
// str2 = coordsStr
// }
// str1 = fmt.Sprintf("%s", time.Now().Format("15:04:05"))
//
// d.Clear()
// d.PutText(0, d.GetFont().Metrics().Height.Ceil(), str1)
// d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, str2)
// d.Flush()
// frameCounter++
// }
// }
//}
func mainE(ctx context.Context) error { func mainE(ctx context.Context) error {
logger = log.New(os.Stdout, "main : ", log.LstdFlags) logger = log.New(os.Stdout, "main : ", log.LstdFlags)
// //
disp, err := display.New(logger, display.MT12864A) //if err := Init(ctx); err != nil {
if err != nil { // return err
return err //}
} ////if err := dev.Test(ctx); err != nil {
time.Sleep(2 * time.Second) //// return err
defer disp.Close() ////}
// if err := Init(ctx); err != nil { //defer Close()
// return err
// }
// defer Close()
// if err := MainLoop(ctx); err != nil { // if err := MainLoop(ctx); err != nil {
// return err // return err
// } // }
return nil
logger.Println("")
a, err := api.New()
if err != nil {
return fmt.Errorf("new api: %w", err)
}
defer a.Close()
logger.Println("Inited")
// !!! Init example
// Setup init page
ip := a.SwitchToInitPage()
ip.SetTitle("KRBL")
pb := progress.NewHandler(ip.GetProgressBar(), 10)
// Simulate init process
for range 10 {
select {
case <-ctx.Done():
return nil
case <-time.After(60 * time.Millisecond):
}
pb.Checkpoint()
}
time.Sleep(200 * time.Millisecond)
// !!! Status page simulating
// Switch
services := []string{"NO SIGNAL", "GSM", "WCDMA", "LTE", "TDS"}
sp := a.SwitchToStatusPage()
for {
select {
case <-ctx.Done():
return nil
case <-time.After(1700 * time.Millisecond):
// Random coords
lat := float64(rand.Int()%10000) + float64(rand.Int()%1000000)/1000000
log := float64(rand.Int()%100000) + float64(rand.Int()%1000000)/1000000
latI := "N"
if rand.Int()%2 == 0 {
latI = "S"
}
logI := "E"
if rand.Int()%2 == 0 {
logI = "W"
}
sp.SetGps(gps.Data{
Latitude: lat,
LatitudeIndicator: latI,
Longitude: log,
LongitudeIndicator: logI,
})
// Random rssi
sp.SetRssi(rand.Int() % 31)
sp.SetService(services[rand.Int()%len(services)])
}
}
} }

View File

@ -10,6 +10,10 @@ import (
"github.com/stianeikeland/go-rpio/v4" "github.com/stianeikeland/go-rpio/v4"
) )
const (
waitReadyTimeout = time.Microsecond
)
type mt12232a struct { type mt12232a struct {
logger *log.Logger logger *log.Logger
@ -20,16 +24,13 @@ type mt12232a struct {
} }
type Device interface { type Device interface {
PowerOn() error Reset() error
SetPixel(x, y byte, c bool) error
WriteCodeL(c byte) error WriteCode(cs rpio.State, c byte) error
WriteCodeR(c byte) error WriteData(cs rpio.State, b byte) error
WriteDataL(b byte) error WriteDatas(cs rpio.State, b []byte) error
WriteDataR(b byte) error
ReadDataL() (byte, error) ReadData(cs rpio.State) (byte, error)
ReadDataR() (byte, error)
ReadStatus(cs rpio.State) byte ReadStatus(cs rpio.State) byte
io.Closer io.Closer
@ -42,138 +43,88 @@ func New(logger *log.Logger) (Device, error) {
logger: logger, logger: logger,
dev: parallel8bit.New(logger, parallel8bit.DevicePins{ dev: parallel8bit.New(logger, parallel8bit.DevicePins{
PinA0: rpio.Pin(18), PinA0: rpio.Pin(1), // 19
PinRW: rpio.Pin(17), PinRW: rpio.Pin(13),
PinE: rpio.Pin(27), PinE: rpio.Pin(12),
PinDB0: rpio.Pin(22), PinDB0: rpio.Pin(22),
PinDB1: rpio.Pin(10), PinDB1: rpio.Pin(10),
PinDB2: rpio.Pin(9), // From 21 PinDB2: rpio.Pin(9),
PinDB3: rpio.Pin(11), PinDB3: rpio.Pin(11),
PinDB4: rpio.Pin(12), PinDB4: rpio.Pin(21),
PinDB5: rpio.Pin(16), // From 32 PinDB5: rpio.Pin(20),
PinDB6: rpio.Pin(20), // From 31 PinDB6: rpio.Pin(26),
PinDB7: rpio.Pin(13), // From 33 PinDB7: rpio.Pin(16),
}), }),
pinCS: rpio.Pin(19), pinCS: rpio.Pin(17),
pinRES: rpio.Pin(21), pinRES: rpio.Pin(27),
} }
d.pinCS.Output() d.pinCS.Output()
d.pinRES.Output() d.pinRES.Output()
d.dev.Reset() d.dev.Reset()
d.pinCS.Low()
d.pinRES.High()
return &d, nil return &d, nil
} }
func (d *mt12232a) Close() error { func (d *mt12232a) Close() error {
d.pinCS.Low()
//d.pinRES.Low() // TMP
d.dev.Pins().PinA0.Low()
d.dev.Pins().PinE.Low()
d.dev.Pins().PinRW.Low()
d.dev.Pins().PinDB0.Low()
d.dev.Pins().PinDB1.Low()
d.dev.Pins().PinDB2.Low()
d.dev.Pins().PinDB3.Low()
d.dev.Pins().PinDB4.Low()
d.dev.Pins().PinDB5.Low()
d.dev.Pins().PinDB6.Low()
d.dev.Pins().PinDB7.Low()
return rpio.Close() return rpio.Close()
} }
func (d *mt12232a) SetPixel(x, y byte, c bool) error { func (d *mt12232a) status() {
// var c8 byte d.logger.Printf("ST L: %08b R: %08b\n", d.ReadStatus(0)&0xFF, d.ReadStatus(1)&0xFF)
// var mask byte
// Check bounds
if x > 127 || y > 63 {
return fmt.Errorf("positions out of bounds")
}
if x < 64 { // Left crystal
d.WriteCodeL(0xB8 | (y >> 3)) // Set page
d.WriteCodeL(0x40 | x) // Set addr
// c8=ReadDataL(); // ?? ok
// c8=ReadDataL(); // Read byte
// m8=1<<(y&0x07);//Вычислить маску нужного бита в байте
// if (c==1) //Зажигать точку?
// c8|=m8//Установить нужный бит в байте
// else //Или гасить точку?
// c8&=~m8;//Сбросить нужный бит в байте
// WriteCodeL(0x40|x);//Снова установить адрес нужного байта
d.WriteDataL(0x34) // Write byte
}
return nil
} }
func (d *mt12232a) PowerOn() error { func (d *mt12232a) Reset() error {
d.logger.Println(d.ReadStatus(0)) // Should be 0
d.logger.Println("Reset")
d.pinRES.Low() d.pinRES.Low()
time.Sleep(time.Microsecond)
d.logger.Println(d.ReadStatus(0)) // Should be 48 (power off and reset)
d.pinRES.High()
time.Sleep(10 * time.Microsecond) time.Sleep(10 * time.Microsecond)
d.logger.Println(d.ReadStatus(0)) // Should be 32 (power off) d.pinRES.High()
time.Sleep(2 * time.Millisecond)
// Module is reset and should be turned off
if d.ReadStatus(0) == 0 || d.ReadStatus(1) == 0 {
return fmt.Errorf("no response from display(or it is possible that it is turned on but...)")
}
d.logger.Println("Power on")
d.WriteCodeL(0xE2) // Reset
d.WriteCodeR(0xE2) // Reset
d.WriteCodeL(0xEE) // ReadModifyWrite off
d.WriteCodeR(0xEE) // ReadModifyWrite off
d.WriteCodeL(0xA4) // Turn on common mode
d.WriteCodeR(0xA4) // Turn on common mode
d.WriteCodeL(0xA9) // Multiplex 1/32
d.WriteCodeR(0xA9) // Multiplex 1/32
d.WriteCodeL(0xC0) // Top line to 0
d.WriteCodeR(0xC0) // Top line to 0
d.WriteCodeL(0xA1) // Invert scan RAM
d.WriteCodeR(0xA0) // NonInvert scan RAM
d.logger.Println("Display on")
d.WriteCodeL(0xAF) // Display on
d.WriteCodeR(0xAF) // Display on
// Check that crystals are turned on
if (d.ReadStatus(0) & (1 << 5)) != 0 {
return fmt.Errorf("Left cristal is still off")
}
if (d.ReadStatus(1) & (1 << 5)) != 0 {
return fmt.Errorf("Right cristal is still off")
}
return nil return nil
} }
// Write codes // Write codes
func (d *mt12232a) WriteCodeL(c byte) error { func (d *mt12232a) WriteCode(cs rpio.State, c byte) error {
return d.writeByte(c, 0, 0) return d.writeByte(c, 0, cs)
}
func (d *mt12232a) WriteCodeR(c byte) error {
return d.writeByte(c, 0, 1)
} }
// Write data as byte // Write data as byte
func (d *mt12232a) WriteDataL(b byte) error { func (d *mt12232a) WriteData(cs rpio.State, b byte) error {
return d.writeByte(b, 1, 0) return d.writeByte(b, 1, cs)
} }
func (d *mt12232a) WriteDataR(b byte) error { func (d *mt12232a) WriteDatas(cs rpio.State, b []byte) error {
return d.writeByte(b, 1, 1) return d.writeBytes(b, 1, cs)
} }
// Read data // Read data
func (d *mt12232a) ReadDataL() (byte, error) { func (d *mt12232a) ReadData(cs rpio.State) (byte, error) {
return d.readByte(1, 0) return d.readByte(1, cs)
}
func (d *mt12232a) ReadDataR() (byte, error) {
return d.readByte(1, 1)
} }
// Low level functions // Low level functions
func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error { func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error {
// d.logger.Println("Write byte", b, cd, l, r)
if err := d.waitReady(cs); err != nil { if err := d.waitReady(cs); err != nil {
return fmt.Errorf("wait ready: %w", err) return fmt.Errorf("wait ready: %w", err)
} }
@ -183,6 +134,16 @@ func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error {
return nil return nil
} }
func (d *mt12232a) writeBytes(b []byte, cd, cs rpio.State) error {
if err := d.waitReady(cs); err != nil {
return fmt.Errorf("wait ready: %w", err)
}
d.pinCS.Write(cs) // Select cristals
d.dev.WriteBytes(b, cd)
return nil
}
func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) { func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) {
// Setup // Setup
if err := d.waitReady(cs); err != nil { if err := d.waitReady(cs); err != nil {
@ -195,11 +156,13 @@ func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) {
// Wait, checking status byte // Wait, checking status byte
func (d *mt12232a) waitReady(cs rpio.State) error { func (d *mt12232a) waitReady(cs rpio.State) error {
d.pinCS.Write(cs) // Select cristals // d.pinCS.Write(cs) // Select cristals
return d.dev.WaitReady() // return d.dev.WaitReady()
time.Sleep(waitReadyTimeout)
return nil
} }
func (d *mt12232a) ReadStatus(cs rpio.State) byte { func (d *mt12232a) ReadStatus(cs rpio.State) byte {
d.pinCS.Write(cs) // Select cristals d.pinCS.Write(cs) // Select cristals
return d.dev.ReadByte(1) return d.dev.ReadByte(0)
} }

View File

@ -9,8 +9,10 @@ import (
) )
const ( const (
adressWriteTimeout = 140 * time.Nanosecond adressWriteTimeout = 50 * time.Nanosecond // 40
dataStrobeTimeout = 250 * time.Nanosecond // (Data transfer) dataStrobeTimeout = 200 * time.Nanosecond // (Data transfer) 160
dataReadTimeout = 400 * time.Nanosecond // 300
gapTimeout = 2000 * time.Nanosecond // TIM // 2000
maxWaitCycles = 100 maxWaitCycles = 100
) )
@ -18,8 +20,11 @@ const (
type Device interface { type Device interface {
Reset() Reset()
WriteByte(b byte, cd rpio.State) WriteByte(b byte, cd rpio.State)
WriteBytes(b []byte, cd rpio.State)
ReadByte(cd rpio.State) byte ReadByte(cd rpio.State) byte
Pins() DevicePins
WaitReady() error WaitReady() error
} }
@ -36,14 +41,12 @@ type DevicePins struct {
PinDB5 rpio.Pin PinDB5 rpio.Pin
PinDB6 rpio.Pin PinDB6 rpio.Pin
PinDB7 rpio.Pin PinDB7 rpio.Pin
PinE1 rpio.Pin
PinE2 rpio.Pin
PinRES rpio.Pin
} }
type device struct { type device struct {
logger *log.Logger logger *log.Logger
isBusOutput bool
DevicePins DevicePins
} }
@ -54,17 +57,29 @@ func New(logger *log.Logger, pins DevicePins) Device {
} }
} }
func (d *device) Pins() DevicePins {
return d.DevicePins
}
func (d *device) Reset() { func (d *device) Reset() {
d.PinA0.Output() d.PinA0.Output()
d.PinRW.Output() d.PinRW.Output()
d.PinE.Output() d.PinE.Output()
d.PinRES.Output()
d.busOutput() d.busOutput()
d.PinE.Low() d.PinA0.Low()
d.PinRW.Low()
d.PinE.High()
d.PinDB0.Low()
d.PinDB1.Low()
d.PinDB2.Low()
d.PinDB3.Low()
d.PinDB4.Low()
d.PinDB5.Low()
d.PinDB6.Low()
d.PinDB7.Low()
} }
func (d *device) WriteByte(b byte, cd rpio.State) { func (d *device) WriteByte(b byte, cd rpio.State) {
// d.logger.Println("Write byte", b, cd, l, r)
d.busOutput() d.busOutput()
d.PinRW.Low() // We write d.PinRW.Low() // We write
d.PinA0.Write(cd) d.PinA0.Write(cd)
@ -79,25 +94,50 @@ func (d *device) WriteByte(b byte, cd rpio.State) {
d.PinDB6.Write(rpio.State((b >> 6) & 1)) d.PinDB6.Write(rpio.State((b >> 6) & 1))
d.PinDB7.Write(rpio.State((b >> 7) & 1)) d.PinDB7.Write(rpio.State((b >> 7) & 1))
// Strobe start time.Sleep(adressWriteTimeout)
d.PinE.High()
time.Sleep(dataStrobeTimeout)
// Strobe end d.PinE.Low() // Strobe start
d.PinE.Low() time.Sleep(dataStrobeTimeout)
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout) d.PinE.High() // Strobe end
time.Sleep(gapTimeout - dataStrobeTimeout - adressWriteTimeout)
}
func (d *device) WriteBytes(b []byte, cd rpio.State) {
d.busOutput()
d.PinRW.Low() // We write
d.PinA0.Write(cd)
// Write bus
d.PinDB0.Write(rpio.State(b[0]))
d.PinDB1.Write(rpio.State(b[1]))
d.PinDB2.Write(rpio.State(b[2]))
d.PinDB3.Write(rpio.State(b[3]))
d.PinDB4.Write(rpio.State(b[4]))
d.PinDB5.Write(rpio.State(b[5]))
d.PinDB6.Write(rpio.State(b[6]))
d.PinDB7.Write(rpio.State(b[7]))
time.Sleep(adressWriteTimeout)
d.PinE.Low() // Strobe start
time.Sleep(dataStrobeTimeout)
d.PinE.High() // Strobe end
time.Sleep(gapTimeout - dataStrobeTimeout - adressWriteTimeout)
} }
func (d *device) ReadByte(cd rpio.State) byte { func (d *device) ReadByte(cd rpio.State) byte {
// Setup // Setup
var b byte var b byte
d.busOutput()
d.PinRW.High() // We write
d.PinA0.Write(cd) d.PinA0.Write(cd)
d.PinRW.High() // We read
d.busInput()
// Strobe start // Strobe start
d.PinE.High() time.Sleep(adressWriteTimeout)
time.Sleep(dataStrobeTimeout) d.PinE.Low()
time.Sleep(dataReadTimeout)
// Read // Read
b = uint8(d.PinDB0.Read()) | b = uint8(d.PinDB0.Read()) |
@ -110,44 +150,47 @@ func (d *device) ReadByte(cd rpio.State) byte {
(uint8(d.PinDB7.Read()) << 7) (uint8(d.PinDB7.Read()) << 7)
// Strobe end // Strobe end
d.PinE.Low() d.PinE.High()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout) time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
return b return b
} }
func (d *device) WaitReady() error { func (d *device) WaitReady() error {
d.busInput() // Set bus to input d.busInput() // Set bus to input
d.PinRW.High() // We read
d.PinA0.Low() // Status d.PinA0.Low() // Status
d.PinRW.High() // We read
time.Sleep(adressWriteTimeout) time.Sleep(adressWriteTimeout)
// Strobe start // Strobe start
d.PinE.High() d.PinE.Low()
time.Sleep(dataStrobeTimeout) time.Sleep(dataReadTimeout)
// Wait status flag drop // Wait status flag drop
ok := false ok := false
d.busInput() // Set bus to input
for counter := 0; counter < maxWaitCycles; counter++ { for counter := 0; counter < maxWaitCycles; counter++ {
if d.PinDB7.Read() != rpio.High { if d.PinDB7.Read() != rpio.High {
//d.logger.Printf("BUS:%d%d%d%d%d%d%d%d\n", d.pinDB0.Read(), d.pinDB1.Read(), d.pinDB2.Read(), d.pinDB3.Read(), d.pinDB4.Read(), d.pinDB5.Read(), d.pinDB6.Read(), d.pinDB7.Read()) //d.logger.Printf("BUS:%d%d%d%d%d%d%d%d\n", d.PinDB0.Read(), d.PinDB1.Read(), d.PinDB2.Read(), d.PinDB3.Read(), d.PinDB4.Read(), d.PinDB5.Read(), d.PinDB6.Read(), d.PinDB7.Read())
ok = true ok = true
break break
} }
fmt.Print(".")
} }
if !ok { if !ok {
return fmt.Errorf("busy timeout") return fmt.Errorf("busy timeout")
} }
// Strobe end // Strobe end
d.PinE.Low() d.PinE.High()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout) time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
// d.logger.Println("Ready") // d.logger.Println("Ready")
return nil return nil
} }
// Set bus pins to output // Set bus Pins to output
func (d *device) busOutput() { func (d *device) busOutput() {
if d.isBusOutput {
return
}
d.PinDB0.Output() d.PinDB0.Output()
d.PinDB1.Output() d.PinDB1.Output()
d.PinDB2.Output() d.PinDB2.Output()
@ -156,9 +199,13 @@ func (d *device) busOutput() {
d.PinDB5.Output() d.PinDB5.Output()
d.PinDB6.Output() d.PinDB6.Output()
d.PinDB7.Output() d.PinDB7.Output()
d.isBusOutput = true
} }
func (d *device) busInput() { func (d *device) busInput() {
if !d.isBusOutput {
return
}
d.PinDB0.Input() d.PinDB0.Input()
d.PinDB1.Input() d.PinDB1.Input()
d.PinDB2.Input() d.PinDB2.Input()
@ -167,4 +214,5 @@ func (d *device) busInput() {
d.PinDB5.Input() d.PinDB5.Input()
d.PinDB6.Input() d.PinDB6.Input()
d.PinDB7.Input() d.PinDB7.Input()
d.isBusOutput = false
} }