Compare commits

..

13 Commits

25 changed files with 2462 additions and 417 deletions

1
.gitignore vendored
View File

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

View File

@ -9,7 +9,9 @@ build:
@go build -o out/out main.go
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:
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
import (
"context"
"fmt"
"image"
"io"
@ -8,9 +9,20 @@ import (
)
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
Test(ctx context.Context) error // DEBUG ONLY
io.Closer
}
@ -19,6 +31,7 @@ type DisplayModel int
const (
SSD1306 DisplayModel = iota
MT12864A
MT12232A
)
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)
case MT12864A:
return newMt12864a(logger)
case MT12232A:
return newMt12232a(logger)
}
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
import (
"context"
"fmt"
"image"
"log"
@ -32,9 +33,16 @@ func newMt12864a(logger *log.Logger) (Display, error) {
}
// Temp debug draw
d.logger.Println("Draw...")
d.test()
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() {
@ -105,29 +113,52 @@ func (d *displayMt12864a) test() {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
},
}
_ = Logo128
for p := byte(0); p < 8; p++ {
d.dev.WriteCodeL(p | 0xB8)
d.dev.WriteCodeL(0x40)
for c := 0; c < 64; c++ {
d.dev.WriteDataL(Logo128[p][c])
}
d.dev.WriteCodeR(p | 0xB8)
d.dev.WriteCodeR(0x40)
for c := 64; c < 128; c++ {
d.dev.WriteDataR(Logo128[p][c])
}
}
d.logger.Println("Write")
d.dev.WriteCodeL(0xB8)
d.dev.WriteCodeL(0x40)
d.dev.WriteDataL(47)
d.logger.Println("Read")
// d.dev.WriteCodeL(0xB8)
d.dev.WriteCodeL(0x40)
d.logger.Println(d.dev.ReadDataL())
d.logger.Println(d.dev.ReadDataL())
// for p := byte(0); p < 8; p++ {
// d.dev.WriteCodeL(p | 0xB8)
// d.dev.WriteCodeL(0x40)
// for c := 0; c < 64; c++ {
// d.dev.WriteDataL(Logo128[p][c])
// }
// d.dev.WriteCodeR(p | 0xB8)
// d.dev.WriteCodeR(0x40)
// for c := 64; c < 128; c++ {
// d.dev.WriteDataR(Logo128[p][c])
// }
// }
}
func (d *displayMt12864a) GetBounds() image.Rectangle {
return image.Rectangle{}
}
func (d *displayMt12864a) Flush(img *image.Gray) error {
return nil
func (d *displayMt12864a) Flush(crystal, page byte) {
}
func (d *displayMt12864a) FlushByMask(mask uint32) {
}
func (d *displayMt12864a) Close() error {
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
import (
"context"
"fmt"
"image"
"log"
@ -41,19 +42,29 @@ func newSsd1306(logger *log.Logger) (Display, error) {
return nil, fmt.Errorf("create i2c: %w", err)
}
return &displaySsd1306{
_ = displaySsd1306{
logger: logger,
bus: bus,
dev: dev,
}, nil
}
return nil, fmt.Errorf("IMPLEMENTATION COMMENTED")
}
func (d *displaySsd1306) GetBounds() image.Rectangle {
return d.dev.Bounds()
}
func (d *displaySsd1306) Flush(img *image.Gray) error {
return d.dev.Draw(img.Bounds(), d.img, image.Point{})
func (d *displaySsd1306) Test(ctx context.Context) error {
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 {
@ -62,3 +73,13 @@ func (d *displaySsd1306) Close() error {
}
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"
func (d *drawer) FillBar(sx, sy, ex, ey, color int) {
d.Lock()
defer d.Unlock()
func (d *drawer) FillBar(sx, sy, ex, ey int, color byte) {
d.dev.LockImg()
defer d.dev.UnlockImg()
bounds := d.img.Bounds()
// Crop
@ -18,14 +18,14 @@ func (d *drawer) FillBar(sx, sy, ex, ey, color int) {
addr := lineaddr
le := addr + w
for ; addr < le; addr++ {
d.img.Pix[addr] = 255
d.img.Pix[addr] = color
}
}
}
func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
d.Lock()
defer d.Unlock()
func (d *drawer) PutBar(sx, sy, ex, ey int, color byte) {
d.dev.LockImg()
defer d.dev.UnlockImg()
bounds := d.img.Bounds()
// Crop
@ -36,7 +36,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ {
d.img.Pix[addr] = 255
d.img.Pix[addr] = color
}
}
// Bottom
@ -45,7 +45,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
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
@ -54,7 +54,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
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
@ -63,7 +63,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
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() {
d.img.Pix[addr] = 255
d.img.Pix[addr] = color
}
}
}

View File

@ -2,76 +2,72 @@ package drawer
import (
"image"
"sync"
"gitea.unprism.ru/yotia/display-test/display"
"golang.org/x/image/font"
"golang.org/x/image/font/inconsolata"
)
type drawer struct {
font font.Face
dev display.Display
dev display.Display
img *image.Gray
imgMutex sync.Mutex
img *image.Gray
}
type Drawer interface {
GetFont() font.Face
GetDisplay() display.Display
// Lowlevel image
GetImg() *image.Gray
Lock()
Unlock()
W() int
H() int
Clear()
Flush() error
Flush()
PutText(x, y int, text string)
PutBar(x0, y0, x1, y1, color int)
FillBar(x0, y0, x1, y1, color int)
PutBar(x0, y0, x1, y1 int, color byte)
FillBar(x0, y0, x1, y1 int, color byte)
CopyImg(x0, y0 int, img Image)
}
func New(dev display.Display) Drawer {
return &drawer{
font: inconsolata.Regular8x16,
d := &drawer{
dev: dev,
img: image.NewGray(dev.GetBounds()),
}
d.img = d.dev.GetImg()
return d
}
func (d *drawer) GetImg() *image.Gray {
return d.img
func (d *drawer) GetDisplay() display.Display {
return d.dev
}
func (d *drawer) GetFont() font.Face {
return d.font
func (d *drawer) W() int {
return d.img.Rect.Dx()
}
func (d *drawer) Lock() {
d.imgMutex.Lock()
}
func (d *drawer) Unlock() {
d.imgMutex.Unlock()
func (d *drawer) H() int {
return d.img.Rect.Dy()
}
func (d *drawer) Clear() {
d.Lock()
defer d.Unlock()
d.dev.LockImg()
defer d.dev.UnlockImg()
for i := range d.img.Pix {
d.img.Pix[i] = 0
}
d.dev.FlushByMask(0xFF)
}
func (d *drawer) Flush() error {
d.Lock()
defer d.Unlock()
return d.dev.Flush(d.img)
func (d *drawer) Flush() {
// Flush all pages
d.dev.Flush(0, 0)
d.dev.Flush(0, 1)
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
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
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/devices/v3 v3.7.1
periph.io/x/host/v3 v3.8.2
@ -14,5 +15,6 @@ require (
require (
github.com/creack/goselect v0.1.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
)

272
main.go
View File

@ -4,15 +4,20 @@ import (
"context"
"fmt"
"log"
"math/rand"
"os"
"os/signal"
"syscall"
"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/drawer"
"gitea.unprism.ru/KRBL/sim-modem/api/modem"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
)
const (
@ -42,138 +47,195 @@ var d drawer.Drawer
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 {
// Display init
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 {
return fmt.Errorf("new display: %w", err)
}
logger.Println("Display inited")
// Create drawer
d := drawer.New(dev)
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()
// Modem init
mInitCtx, mInitCtxCancel := context.WithCancel(ctx)
go DrawProgress(mInitCtx, 13*time.Second)
defer mInitCtxCancel()
time.Sleep(time.Second)
m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
if err := m.Init(); err != nil {
return fmt.Errorf("modem init: %w", err)
pbc, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH)
if err != nil {
return fmt.Errorf("new progress bar: %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()
}
pb := progress.NewHandler(pbc, 10)
func MainLoop(ctx context.Context) error {
mainLoopTimeout := time.Second
frameCounter := 0
deadline := time.Now().Add(100 * time.Millisecond)
for {
for range 10 {
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++
case <-time.After(time.Duration(rand.Int()%1000) * time.Millisecond):
}
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 {
logger = log.New(os.Stdout, "main : ", log.LstdFlags)
//
disp, err := display.New(logger, display.MT12864A)
if err != nil {
return err
}
time.Sleep(2 * time.Second)
defer disp.Close()
// if err := Init(ctx); err != nil {
// return err
// }
// defer Close()
//if err := Init(ctx); err != nil {
// return err
//}
////if err := dev.Test(ctx); err != nil {
//// return err
////}
//defer Close()
// if err := MainLoop(ctx); err != nil {
// 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)])
}
}
}

168
pkg/mt12232a/mt12232a.go Normal file
View File

@ -0,0 +1,168 @@
package mt12232a
import (
"fmt"
"io"
"log"
"time"
"gitea.unprism.ru/yotia/display-test/pkg/parallel8bit"
"github.com/stianeikeland/go-rpio/v4"
)
const (
waitReadyTimeout = time.Microsecond
)
type mt12232a struct {
logger *log.Logger
dev parallel8bit.Device
pinCS rpio.Pin
pinRES rpio.Pin
}
type Device interface {
Reset() error
WriteCode(cs rpio.State, c byte) error
WriteData(cs rpio.State, b byte) error
WriteDatas(cs rpio.State, b []byte) error
ReadData(cs rpio.State) (byte, error)
ReadStatus(cs rpio.State) byte
io.Closer
}
func New(logger *log.Logger) (Device, error) {
rpio.Open()
d := mt12232a{
logger: logger,
dev: parallel8bit.New(logger, parallel8bit.DevicePins{
PinA0: rpio.Pin(1), // 19
PinRW: rpio.Pin(13),
PinE: rpio.Pin(12),
PinDB0: rpio.Pin(22),
PinDB1: rpio.Pin(10),
PinDB2: rpio.Pin(9),
PinDB3: rpio.Pin(11),
PinDB4: rpio.Pin(21),
PinDB5: rpio.Pin(20),
PinDB6: rpio.Pin(26),
PinDB7: rpio.Pin(16),
}),
pinCS: rpio.Pin(17),
pinRES: rpio.Pin(27),
}
d.pinCS.Output()
d.pinRES.Output()
d.dev.Reset()
d.pinCS.Low()
d.pinRES.High()
return &d, nil
}
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()
}
func (d *mt12232a) status() {
d.logger.Printf("ST L: %08b R: %08b\n", d.ReadStatus(0)&0xFF, d.ReadStatus(1)&0xFF)
}
func (d *mt12232a) Reset() error {
d.pinRES.Low()
time.Sleep(10 * time.Microsecond)
d.pinRES.High()
time.Sleep(2 * time.Millisecond)
return nil
}
// Write codes
func (d *mt12232a) WriteCode(cs rpio.State, c byte) error {
return d.writeByte(c, 0, cs)
}
// Write data as byte
func (d *mt12232a) WriteData(cs rpio.State, b byte) error {
return d.writeByte(b, 1, cs)
}
func (d *mt12232a) WriteDatas(cs rpio.State, b []byte) error {
return d.writeBytes(b, 1, cs)
}
// Read data
func (d *mt12232a) ReadData(cs rpio.State) (byte, error) {
return d.readByte(1, cs)
}
// Low level functions
func (d *mt12232a) writeByte(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.WriteByte(b, cd)
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) {
// Setup
if err := d.waitReady(cs); err != nil {
return 0, fmt.Errorf("wait ready: %w", err)
}
d.pinCS.Write(cs) // Select cristals
return d.dev.ReadByte(cd), nil
}
// Wait, checking status byte
func (d *mt12232a) waitReady(cs rpio.State) error {
// d.pinCS.Write(cs) // Select cristals
// return d.dev.WaitReady()
time.Sleep(waitReadyTimeout)
return nil
}
func (d *mt12232a) ReadStatus(cs rpio.State) byte {
d.pinCS.Write(cs) // Select cristals
return d.dev.ReadByte(0)
}

View File

@ -6,31 +6,15 @@ import (
"log"
"time"
"gitea.unprism.ru/yotia/display-test/pkg/parallel8bit"
"github.com/stianeikeland/go-rpio/v4"
)
const (
enablePrintPins = false
adressWriteTimeout = 140 * time.Nanosecond
dataStrobeTimeout = 250 * time.Nanosecond // (Data transfer)
maxWaitCycles = 100
)
type mt12864a struct {
logger *log.Logger
// GPIO pins
pinA0 rpio.Pin
pinRW rpio.Pin
pinE rpio.Pin
pinDB0 rpio.Pin
pinDB1 rpio.Pin
pinDB2 rpio.Pin
pinDB3 rpio.Pin
pinDB4 rpio.Pin
pinDB5 rpio.Pin
pinDB6 rpio.Pin
pinDB7 rpio.Pin
dev parallel8bit.Device
pinE1 rpio.Pin
pinE2 rpio.Pin
pinRES rpio.Pin
@ -57,34 +41,31 @@ func New(logger *log.Logger) (Device, error) {
d := mt12864a{
logger: logger,
pinA0: rpio.Pin(18),
pinRW: rpio.Pin(17),
pinE: rpio.Pin(27),
pinDB0: rpio.Pin(22),
pinDB1: rpio.Pin(10),
pinDB2: rpio.Pin(9), // From 21
pinDB3: rpio.Pin(11),
pinDB4: rpio.Pin(12),
pinDB5: rpio.Pin(16), // From 32
pinDB6: rpio.Pin(20), // From 31
pinDB7: rpio.Pin(13), // From 33
dev: parallel8bit.New(logger, parallel8bit.DevicePins{
PinA0: rpio.Pin(18),
PinRW: rpio.Pin(17),
PinE: rpio.Pin(27),
// Reverted
// pinDB7: rpio.Pin(15),
// pinDB6: rpio.Pin(19),
// pinDB5: rpio.Pin(21),
// pinDB4: rpio.Pin(23),
// pinDB3: rpio.Pin(27),
// pinDB2: rpio.Pin(32),
// pinDB1: rpio.Pin(31),
// pinDB0: rpio.Pin(33),
PinDB0: rpio.Pin(22),
PinDB1: rpio.Pin(10),
PinDB2: rpio.Pin(9), // From 21
PinDB3: rpio.Pin(11),
PinDB4: rpio.Pin(12),
PinDB5: rpio.Pin(16), // From 32
PinDB6: rpio.Pin(20), // From 31
PinDB7: rpio.Pin(13), // From 33
}),
pinE1: rpio.Pin(19),
pinE2: rpio.Pin(26),
pinRES: rpio.Pin(21),
}
d.AllOutput()
d.pinE1.Output()
d.pinE2.Output()
d.pinRES.Output()
d.dev.Reset()
return &d, nil
}
@ -119,38 +100,35 @@ func (d *mt12864a) SetPixel(x, y byte, c bool) error {
}
func (d *mt12864a) PowerOn() error {
d.pinA0.Low()
d.pinRW.Low()
d.pinE.Low()
d.pinDB0.Low()
d.pinDB1.Low()
d.pinDB2.Low()
d.pinDB3.Low()
d.pinDB4.Low()
d.pinDB5.Low()
d.pinDB6.Low()
d.pinDB7.Low()
d.pinE1.Low()
d.pinE2.Low()
d.pinRES.Low()
d.logger.Println("All low ")
d.PrintPins()
d.logger.Println("Power on")
d.pinE.Low()
d.logger.Println(d.ReadStatus(1, 0)) // Should be 0
d.logger.Println("Reset")
d.pinRES.Low()
time.Sleep(time.Microsecond)
d.logger.Println(d.ReadStatus(1, 0)) // Should be 48 (power off and reset)
d.pinRES.High()
time.Sleep(10 * time.Microsecond)
d.logger.Println(d.ReadStatus(1, 0)) // Should be 32 (power off)
d.PrintPins()
// Module is reset and should be turned off
if d.ReadStatus(1, 0) == 0 || d.ReadStatus(0, 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.logger.Println("Top line to 0")
d.WriteCodeL(0xC0) // Top line to 0
d.WriteCodeR(0xC0) // Top line to 0
d.PrintPins()
d.logger.Println("Display on")
d.WriteCodeL(0x3F) // Display on
d.WriteCodeR(0x3F) // Display on
d.PrintPins()
// Check that crystals are turned on
if (d.ReadStatus(1, 0) & (1 << 5)) != 0 {
return fmt.Errorf("Left cristal is still off")
}
if (d.ReadStatus(0, 1) & (1 << 5)) != 0 {
return fmt.Errorf("Right cristal is still off")
}
return nil
}
@ -188,7 +166,7 @@ func (d *mt12864a) ReadDataR() (byte, error) {
// Low level functions
func (d *mt12864a) writeByte(b byte, cd, l, r rpio.State) error {
d.logger.Println("Write byte", b, cd, l, r)
// d.logger.Println("Write byte", b, cd, l, r)
if l == rpio.High && r == rpio.High {
d.logger.Println("L and R are high!!!")
return fmt.Errorf("cannot write left and right at the same times")
@ -196,37 +174,10 @@ func (d *mt12864a) writeByte(b byte, cd, l, r rpio.State) error {
if err := d.waitReady(l, r); err != nil {
return fmt.Errorf("wait ready: %w", err)
}
d.busOutput()
d.pinRW.Low() // We write
d.pinA0.Write(cd)
d.pinE1.Write(l) // Select cristals
d.pinE2.Write(r) // Select cristals
// Write bus
d.pinDB0.Write(rpio.State((b >> 0) & 1))
d.pinDB1.Write(rpio.State((b >> 1) & 1))
d.pinDB2.Write(rpio.State((b >> 2) & 1))
d.pinDB3.Write(rpio.State((b >> 3) & 1))
d.pinDB4.Write(rpio.State((b >> 4) & 1))
d.pinDB5.Write(rpio.State((b >> 5) & 1))
d.pinDB6.Write(rpio.State((b >> 6) & 1))
d.pinDB7.Write(rpio.State((b >> 7) & 1))
// Strobe start
d.pinE.High()
time.Sleep(dataStrobeTimeout)
// Strobe end
d.pinE.Low()
d.pinDB0.Low()
d.pinDB1.Low()
d.pinDB2.Low()
d.pinDB3.Low()
d.pinDB4.Low()
d.pinDB5.Low()
d.pinDB6.Low()
d.pinDB7.Low()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
d.dev.WriteByte(b, cd)
return nil
}
@ -237,146 +188,24 @@ func (d *mt12864a) readByte(cd, l, r rpio.State) (byte, error) {
}
// Setup
var b byte
if err := d.waitReady(l, r); err != nil {
return 0, fmt.Errorf("wait ready: %w", err)
}
d.busOutput()
d.pinRW.High() // We write
d.pinA0.Write(cd)
d.pinE1.Write(l) // Select cristals
d.pinE2.Write(r) // Select cristals
// Strobe start
d.pinE.High()
time.Sleep(dataStrobeTimeout)
// Read
b = uint8(d.pinDB0.Read()) |
(uint8(d.pinDB1.Read()) << 1) |
(uint8(d.pinDB2.Read()) << 2) |
(uint8(d.pinDB3.Read()) << 3) |
(uint8(d.pinDB4.Read()) << 4) |
(uint8(d.pinDB5.Read()) << 5) |
(uint8(d.pinDB6.Read()) << 6) |
(uint8(d.pinDB7.Read()) << 7)
// Strobe end
d.pinE.Low()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
return b, nil
return d.dev.ReadByte(cd), nil
}
// Wait, checking status byte
func (d *mt12864a) waitReady(l, r rpio.State) error {
d.logger.Println("Wait ready", l, r)
d.busInput() // Set bus to input
d.pinRW.High() // We read
d.pinA0.Low() // Status
d.pinE1.Write(l) // Select cristals
d.pinE2.Write(r) // Select cristals
time.Sleep(adressWriteTimeout)
// Strobe start
d.pinE.High()
time.Sleep(dataStrobeTimeout)
// Wait status flag drop
ok := false
d.busInput() // Set bus to input
for counter := 0; counter < maxWaitCycles; counter++ {
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())
ok = true
break
}
}
if !ok {
return fmt.Errorf("busy timeout")
}
// Strobe end
d.pinE.Low()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
d.logger.Println("Ready")
return nil
return d.dev.WaitReady()
}
func (d *mt12864a) ReadStatus(l, r rpio.State) byte {
d.busInput() // Set bus to input
d.pinRW.High() // We read
d.pinA0.Low() // Status
d.pinE1.Write(l) // Select cristals
d.pinE2.Write(r) // Select cristals
time.Sleep(adressWriteTimeout)
// Strobe start
d.pinE.High()
time.Sleep(dataStrobeTimeout)
d.busInput()
var b byte = uint8(d.pinDB0.Read()) |
(uint8(d.pinDB1.Read()) << 1) |
(uint8(d.pinDB2.Read()) << 2) |
(uint8(d.pinDB3.Read()) << 3) |
(uint8(d.pinDB4.Read()) << 4) |
(uint8(d.pinDB5.Read()) << 5) |
(uint8(d.pinDB6.Read()) << 6) |
(uint8(d.pinDB7.Read()) << 7)
// Strobe end
d.pinE.Low()
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
return b
}
func (d *mt12864a) PrintPins() {
if !enablePrintPins {
return
}
d.AllInput()
d.logger.Printf("A0:%d RW:%d E:%d BUS:%d%d%d%d%d%d%d%d E1:%d E2:%d RES:%d\n", d.pinA0.Read(), d.pinRW.Read(), d.pinE.Read(), 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.pinE1.Read(), d.pinE2.Read(), d.pinRES.Read())
d.AllOutput()
}
func (d *mt12864a) AllOutput() {
d.pinA0.Output()
d.pinRW.Output()
d.pinE.Output()
d.pinE1.Output()
d.pinE2.Output()
d.pinRES.Output()
d.busOutput()
}
func (d *mt12864a) AllInput() {
d.pinA0.Input()
d.pinRW.Input()
d.pinE.Input()
d.pinE1.Input()
d.pinE2.Input()
d.pinRES.Input()
d.busInput()
}
// Set bus pins to output
func (d *mt12864a) busOutput() {
d.pinDB0.Output()
d.pinDB1.Output()
d.pinDB2.Output()
d.pinDB3.Output()
d.pinDB4.Output()
d.pinDB5.Output()
d.pinDB6.Output()
d.pinDB7.Output()
}
func (d *mt12864a) busInput() {
d.pinDB0.Input()
d.pinDB1.Input()
d.pinDB2.Input()
d.pinDB3.Input()
d.pinDB4.Input()
d.pinDB5.Input()
d.pinDB6.Input()
d.pinDB7.Input()
return d.dev.ReadByte(1)
}

View File

@ -0,0 +1,218 @@
package parallel8bit
import (
"fmt"
"log"
"time"
"github.com/stianeikeland/go-rpio/v4"
)
const (
adressWriteTimeout = 50 * time.Nanosecond // 40
dataStrobeTimeout = 200 * time.Nanosecond // (Data transfer) 160
dataReadTimeout = 400 * time.Nanosecond // 300
gapTimeout = 2000 * time.Nanosecond // TIM // 2000
maxWaitCycles = 100
)
// Interface for MT displays (do not set cristals bits)
type Device interface {
Reset()
WriteByte(b byte, cd rpio.State)
WriteBytes(b []byte, cd rpio.State)
ReadByte(cd rpio.State) byte
Pins() DevicePins
WaitReady() error
}
type DevicePins struct {
// GPIO pins
PinA0 rpio.Pin
PinRW rpio.Pin
PinE rpio.Pin
PinDB0 rpio.Pin
PinDB1 rpio.Pin
PinDB2 rpio.Pin
PinDB3 rpio.Pin
PinDB4 rpio.Pin
PinDB5 rpio.Pin
PinDB6 rpio.Pin
PinDB7 rpio.Pin
}
type device struct {
logger *log.Logger
isBusOutput bool
DevicePins
}
func New(logger *log.Logger, pins DevicePins) Device {
return &device{
logger: logger,
DevicePins: pins,
}
}
func (d *device) Pins() DevicePins {
return d.DevicePins
}
func (d *device) Reset() {
d.PinA0.Output()
d.PinRW.Output()
d.PinE.Output()
d.busOutput()
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) {
d.busOutput()
d.PinRW.Low() // We write
d.PinA0.Write(cd)
// Write bus
d.PinDB0.Write(rpio.State((b >> 0) & 1))
d.PinDB1.Write(rpio.State((b >> 1) & 1))
d.PinDB2.Write(rpio.State((b >> 2) & 1))
d.PinDB3.Write(rpio.State((b >> 3) & 1))
d.PinDB4.Write(rpio.State((b >> 4) & 1))
d.PinDB5.Write(rpio.State((b >> 5) & 1))
d.PinDB6.Write(rpio.State((b >> 6) & 1))
d.PinDB7.Write(rpio.State((b >> 7) & 1))
time.Sleep(adressWriteTimeout)
d.PinE.Low() // Strobe start
time.Sleep(dataStrobeTimeout)
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 {
// Setup
var b byte
d.PinA0.Write(cd)
d.PinRW.High() // We read
d.busInput()
// Strobe start
time.Sleep(adressWriteTimeout)
d.PinE.Low()
time.Sleep(dataReadTimeout)
// Read
b = uint8(d.PinDB0.Read()) |
(uint8(d.PinDB1.Read()) << 1) |
(uint8(d.PinDB2.Read()) << 2) |
(uint8(d.PinDB3.Read()) << 3) |
(uint8(d.PinDB4.Read()) << 4) |
(uint8(d.PinDB5.Read()) << 5) |
(uint8(d.PinDB6.Read()) << 6) |
(uint8(d.PinDB7.Read()) << 7)
// Strobe end
d.PinE.High()
time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
return b
}
func (d *device) WaitReady() error {
d.busInput() // Set bus to input
d.PinA0.Low() // Status
d.PinRW.High() // We read
time.Sleep(adressWriteTimeout)
// Strobe start
d.PinE.Low()
time.Sleep(dataReadTimeout)
// Wait status flag drop
ok := false
for counter := 0; counter < maxWaitCycles; counter++ {
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())
ok = true
break
}
fmt.Print(".")
}
if !ok {
return fmt.Errorf("busy timeout")
}
// Strobe end
d.PinE.High()
time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
// d.logger.Println("Ready")
return nil
}
// Set bus Pins to output
func (d *device) busOutput() {
if d.isBusOutput {
return
}
d.PinDB0.Output()
d.PinDB1.Output()
d.PinDB2.Output()
d.PinDB3.Output()
d.PinDB4.Output()
d.PinDB5.Output()
d.PinDB6.Output()
d.PinDB7.Output()
d.isBusOutput = true
}
func (d *device) busInput() {
if !d.isBusOutput {
return
}
d.PinDB0.Input()
d.PinDB1.Input()
d.PinDB2.Input()
d.PinDB3.Input()
d.PinDB4.Input()
d.PinDB5.Input()
d.PinDB6.Input()
d.PinDB7.Input()
d.isBusOutput = false
}