diff --git a/Makefile b/Makefile index ce98226..0c84490 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +default: build + export GOOS=linux export GOARCH=arm export GOARM=6 @@ -5,3 +7,12 @@ export CGO_ENABLED=0 build: @go build -o out/out main.go + +export GO111MODULE=on +export GOPRIVATE=gitea.unprism.ru/KRBL/* + +get: + go get . + +update: + go get -u \ No newline at end of file diff --git a/display/display.go b/display/display.go index a4208c6..1bb306c 100644 --- a/display/display.go +++ b/display/display.go @@ -5,15 +5,11 @@ import ( "image" "io" "log" - - "golang.org/x/image/font" ) type Display interface { - GetImg() *image.Gray - GetFont() font.Face - Clear() error - PutText(x, y int, text string) error + Flush(img *image.Gray) error + GetBounds() image.Rectangle io.Closer } @@ -22,12 +18,15 @@ type DisplayModel int const ( SSD1306 DisplayModel = iota + MT12864A ) func New(logger *log.Logger, model DisplayModel) (Display, error) { switch model { case SSD1306: return newSsd1306(logger) + case MT12864A: + return newMt12864a(logger) } return nil, fmt.Errorf("invalid display model") } diff --git a/display/mt-12864a.go b/display/mt-12864a.go new file mode 100644 index 0000000..7b28c68 --- /dev/null +++ b/display/mt-12864a.go @@ -0,0 +1,253 @@ +package display + +import ( + "image" + "log" + "time" + + "github.com/stianeikeland/go-rpio/v4" +) + +const ( + adressWriteTimeout = 140 * time.Nanosecond + dataStrobeTimeout = 250 * time.Nanosecond // (Data transfer) +) + +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 + pinE1 rpio.Pin + pinE2 rpio.Pin + pinRES rpio.Pin +} + +func newMt12864a(logger *log.Logger) (Display, error) { + rpio.Open() + + d := mt12864a{ + logger: logger, + pinA0: rpio.Pin(7), + pinRW: rpio.Pin(11), + pinE: rpio.Pin(13), + pinDB0: rpio.Pin(15), + pinDB1: rpio.Pin(19), + pinDB2: rpio.Pin(21), + pinDB3: rpio.Pin(23), + pinDB4: rpio.Pin(27), + pinDB5: rpio.Pin(29), + pinDB6: rpio.Pin(31), + pinDB7: rpio.Pin(33), + pinE1: rpio.Pin(35), + pinE2: rpio.Pin(37), + pinRES: rpio.Pin(40), + } + + d.initLCD() + return &d, nil +} + +func (d *mt12864a) GetBounds() image.Rectangle { + return image.Rectangle{} +} + +func (d *mt12864a) Flush(img *image.Gray) error { + return nil +} + +func (d *mt12864a) Close() error { + return rpio.Close() +} + +func (d *mt12864a) initLCD() { + // Set All to output + d.pinA0.Output() + d.pinRW.Output() + d.pinE.Output() + d.pinE1.Output() + d.pinE2.Output() + d.pinRES.Output() + d.busOutput() + d.logger.Println("sdljfsldkf") + // d.pinA0.Low() + // d.logger.Println("pinA0 set low") + // d.pinRW.Low() + // d.logger.Println("pinRW set low") + // d.pinE.Low() + // d.logger.Println("pinE set low") + // d.pinDB0.Low() + // d.logger.Println("pinDB0 set low") + // d.pinDB1.Low() + // d.logger.Println("pinDB1 set low") + // d.pinDB2.Low() + // d.logger.Println("pinDB2 set low") + // d.pinDB3.Low() + // d.logger.Println("pinDB3 set low") + // d.pinDB4.Low() + // d.logger.Println("pinDB4 set low") + // d.pinDB5.Low() + // d.logger.Println("pinDB5 set low") + // d.pinDB6.Low() + // d.logger.Println("pinDB6 set low") + // d.pinDB7.Low() + // d.logger.Println("pinDB7 set low") + // d.pinE1.Low() + // d.logger.Println("pinE1 set low") + // d.pinE2.Low() + // d.logger.Println("pinE2 set low") + // d.pinRES.Low() + // d.logger.Println("pinRES set low") + + // d.pinE.Low() + // d.pinRES.Low() + // time.Sleep(time.Microsecond) + // d.pinRES.High() + // time.Sleep(10 * time.Microsecond) + // d.writeCodeL(0xC0) // Top line to 0 + // d.writeCodeR(0xC0) + // d.writeCodeL(0x3F) // Display on + // d.writeCodeR(0x3F) +} + +func (d *mt12864a) writeCodeL(c byte) { + d.writeByte(c, 0, 1, 0) +} + +func (d *mt12864a) writeCodeR(c byte) { + d.writeByte(c, 0, 0, 1) +} + +func (d *mt12864a) writeDataL(b byte) { + d.writeByte(b, 1, 1, 0) +} + +func (d *mt12864a) writeDataR(b byte) { + d.writeByte(b, 1, 0, 1) +} + +func (d *mt12864a) readDataL() byte { + return d.readByte(1, 1, 0) +} + +func (d *mt12864a) readDataR() byte { + return d.readByte(1, 0, 1) +} + +func (d *mt12864a) writeByte(b byte, cd, l, r rpio.State) { + if l == rpio.High && r == rpio.High { + d.logger.Println("L and R are high!!!") + return + } + d.waitReady(l, r) + 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() + time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout) +} + +func (d *mt12864a) readByte(cd, l, r rpio.State) byte { + if l == rpio.High && r == rpio.High { + d.logger.Println("L and R are high!!!") + return 0 + } + var b byte + + d.waitReady(l, r) + 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 +} + +// Wait, checking status byte +func (d *mt12864a) waitReady(l, r rpio.State) { + d.busInput() // Set bus to input + d.pinRW.High() // We read + d.pinA0.Low() // Data + 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 + counter := 0 + for d.pinDB7.Read() == rpio.High && counter < 100 { + counter++ + } + // Strobe end + d.pinE.Low() + time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout) +} + +// 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() +} diff --git a/display/ssd1336.go b/display/ssd1336.go index fa634b1..d1863fd 100644 --- a/display/ssd1336.go +++ b/display/ssd1336.go @@ -3,13 +3,8 @@ package display import ( "fmt" "image" - "image/color" "log" - "sync" - "golang.org/x/image/font" - "golang.org/x/image/font/inconsolata" - "golang.org/x/image/math/fixed" "periph.io/x/conn/v3/i2c" "periph.io/x/conn/v3/i2c/i2creg" "periph.io/x/devices/v3/ssd1306" @@ -17,13 +12,11 @@ import ( ) type displaySsd1306 struct { - mutex sync.Mutex logger *log.Logger - font font.Face - bus i2c.BusCloser - dev *ssd1306.Dev - img *image.Gray + bus i2c.BusCloser + dev *ssd1306.Dev + img *image.Gray } func newSsd1306(logger *log.Logger) (Display, error) { @@ -48,65 +41,24 @@ func newSsd1306(logger *log.Logger) (Display, error) { return nil, fmt.Errorf("create i2c: %w", err) } - // Create image - img := image.NewGray(image.Rect(0, 0, dev.Bounds().Dx(), dev.Bounds().Dy())) - return &displaySsd1306{ logger: logger, - font: inconsolata.Regular8x16, bus: bus, dev: dev, - img: img, }, nil } -func (d *displaySsd1306) GetImg() *image.Gray { - d.mutex.Lock() - defer d.mutex.Unlock() - - return d.img +func (d *displaySsd1306) GetBounds() image.Rectangle { + return d.dev.Bounds() } -func (d *displaySsd1306) GetFont() font.Face { - return d.font -} - -func (d *displaySsd1306) Clear() error { - d.mutex.Lock() - defer d.mutex.Unlock() - - for i := range d.img.Pix { - d.img.Pix[i] = 0 - } - return nil -} - -func (d *displaySsd1306) PutText(x, y int, text string) error { - d.mutex.Lock() - defer d.mutex.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) - - d.dev.Draw(d.img.Bounds(), d.img, image.Point{}) - return nil +func (d *displaySsd1306) Flush(img *image.Gray) error { + return d.dev.Draw(img.Bounds(), d.img, image.Point{}) } func (d *displaySsd1306) Close() error { - d.mutex.Lock() - defer d.mutex.Unlock() - if err := d.bus.Close(); err != nil { - d.logger.Println("Close i2c bus:", err.Error()) + d.logger.Println("ERROR: close i2c bus:", err.Error()) } return nil } diff --git a/drawer/bar.go b/drawer/bar.go new file mode 100644 index 0000000..961eae0 --- /dev/null +++ b/drawer/bar.go @@ -0,0 +1,69 @@ +package drawer + +import "math" + +func (d *drawer) FillBar(sx, sy, ex, ey, color int) { + d.Lock() + defer d.Unlock() + + bounds := d.img.Bounds() + // Crop + sx = int(math.Max(float64(sx), 0)) + sy = int(math.Max(float64(sy), 0)) + ex = int(math.Min(float64(ex), float64(bounds.Dx()))) + ey = int(math.Min(float64(ey), float64(bounds.Dy()))) + w := ex - sx + // Fill + for lineaddr := sy*bounds.Dx() + sx; lineaddr < (ey-1)*bounds.Dx()+ex; lineaddr += bounds.Dx() { + addr := lineaddr + le := addr + w + for ; addr < le; addr++ { + d.img.Pix[addr] = 255 + } + } +} + +func (d *drawer) PutBar(sx, sy, ex, ey, color int) { + d.Lock() + defer d.Unlock() + + bounds := d.img.Bounds() + // Crop + + // Top + if sy >= 0 && sy < bounds.Dy() { + x0 := int(math.Max(float64(sx), 0)) + 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 + } + } + // Bottom + if ey >= 0 && ey <= bounds.Dy() { + x0 := int(math.Max(float64(sx), 0)) + 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 + } + } + // Left + if sx >= 0 && sx < bounds.Dx() { + y0 := int(math.Max(float64(sy), 0)) + 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 + } + } + // Right + if ex >= 0 && ex <= bounds.Dx() { + y0 := int(math.Max(float64(sy), 0)) + 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 + } + } +} diff --git a/drawer/drawer.go b/drawer/drawer.go new file mode 100644 index 0000000..4ab035b --- /dev/null +++ b/drawer/drawer.go @@ -0,0 +1,76 @@ +package drawer + +import ( + "display-test/display" + "image" + "sync" + + "golang.org/x/image/font" + "golang.org/x/image/font/inconsolata" +) + +type drawer struct { + font font.Face + + dev display.Display + img *image.Gray + imgMutex sync.Mutex +} + +type Drawer interface { + GetFont() font.Face + + // Lowlevel image + GetImg() *image.Gray + Lock() + Unlock() + + Clear() + Flush() error + + PutText(x, y int, text string) + + PutBar(x0, y0, x1, y1, color int) + FillBar(x0, y0, x1, y1, color int) +} + +func New(dev display.Display) Drawer { + return &drawer{ + font: inconsolata.Regular8x16, + + dev: dev, + img: image.NewGray(dev.GetBounds()), + } +} + +func (d *drawer) GetImg() *image.Gray { + return d.img +} + +func (d *drawer) GetFont() font.Face { + return d.font +} + +func (d *drawer) Lock() { + d.imgMutex.Lock() +} + +func (d *drawer) Unlock() { + d.imgMutex.Unlock() +} + +func (d *drawer) Clear() { + d.Lock() + defer d.Unlock() + + for i := range d.img.Pix { + d.img.Pix[i] = 0 + } +} + +func (d *drawer) Flush() error { + d.Lock() + defer d.Unlock() + + return d.dev.Flush(d.img) +} diff --git a/drawer/text.go b/drawer/text.go new file mode 100644 index 0000000..81be22e --- /dev/null +++ b/drawer/text.go @@ -0,0 +1,26 @@ +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) +} diff --git a/go.mod b/go.mod index 72fbdbe..f9d4dcf 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,16 @@ module display-test go 1.22.5 require ( - golang.org/x/image v0.1.0 + gitea.unprism.ru/KRBL/sim-modem v0.1.7 + github.com/stianeikeland/go-rpio/v4 v4.6.0 + golang.org/x/image v0.19.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 ) + +require ( + github.com/creack/goselect v0.1.2 // indirect + go.bug.st/serial v1.6.2 // indirect + golang.org/x/sys v0.24.0 // indirect +) diff --git a/main.go b/main.go index 8e22679..643919f 100644 --- a/main.go +++ b/main.go @@ -1,30 +1,174 @@ package main import ( + "context" "display-test/display" + "display-test/drawer" "fmt" "log" + "os" + "os/signal" + "syscall" + "time" + + "gitea.unprism.ru/KRBL/sim-modem/api/modem" +) + +const ( + displayUpdateTimeout = 10 * time.Millisecond ) func main() { log.Println("CGSG forever!!!") - if err := mainE(); err != nil { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + // Just for logs + go func(ctx context.Context) { + <-ctx.Done() + log.Println("GOT INTERUPT SIGNAL") + }(ctx) + + if err := mainE(ctx); err != nil { log.Println("MAIN finished with error:", err.Error()) } log.Println("END") } -func mainE() error { - d, err := display.New(log.New(log.Writer(), "display", log.LstdFlags), display.SSD1306) +var logger *log.Logger +var dev display.Display +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) if err != nil { return fmt.Errorf("new display: %w", err) } - defer d.Close() + logger.Println("Display inited") + d := drawer.New(dev) - for { + d.Clear() + d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Initing modem...") + d.Flush() + + // 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.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Satelites: 0") - d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, "Coords: 0N 0E") + 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, _ := display.New(logger, display.MT12864A) + defer disp.Close() + // if err := Init(ctx); err != nil { + // return err + // } + // defer Close() + // if err := MainLoop(ctx); err != nil { + // return err + // } + return nil +}