tinygo-org / drivers

TinyGo drivers for sensors, displays, wireless adaptors, and other devices that use I2C, SPI, GPIO, ADC, and UART interfaces.
https://tinygo.org
BSD 3-Clause "New" or "Revised" License
584 stars 180 forks source link

hd44780 not working in 4-bit mode with the LCD from Arduino Starter Kit #646

Open sorf opened 5 months ago

sorf commented 5 months ago

The code exhibiting the problem is pretty much a hello-world type (from here)

package main

import (
    "bytes"
    m "machine"
    "strconv"
    "time"

    "tinygo.org/x/drivers/hd44780"
)

func main() {
    m.Serial.Configure(m.UARTConfig{BaudRate: 9600})
    lcd, err := hd44780.NewGPIO4Bit([]m.Pin{m.D5, m.D4, m.D3, m.D2}, m.D11, m.D12, m.NoPin)
    if err != nil {
        println("error: create LCD", err.Error())
        return
    }
    if err := lcd.Configure(hd44780.Config{Width: 16, Height: 2}); err != nil {
        println("error: configure LCD", err.Error())
        return

    }

    println("Start")
    lcd.Write([]byte("Countdown:"))
    lcd.Display()
    time.Sleep(1 * time.Second)

    // snip

It happens with latest version (v0.26.0), on both:

The easiest way to reproduce the issue is with a virtual circuit on wokwi.com (such as this one), by pressing F1 there, then selecting Upload Firmware and Start Simulation and uploading the tinygo built hex file. The mis-behavior is of random "gibberish" showing up on the LCD.

Th expected behavior of the virtual circuit should be like exhibited by this one - programmed with the Arduino LiquidCrystal library.

Possibly related to #380?

sorf commented 5 months ago

In 8-bit mode (code here, wokwi-simulation), it works on the HW LCD from the Arduino starter kit, but not in the simulation.

In the simulation, it gets stuck at something like this:

screenshot-646-8
0pcom commented 4 months ago

It seems i'm encountering the same issue. Arduino code works in 4 bit mode but the tinygo example isn't working on it. I'm attempting to construct a setup for 8 bit mode, will update here with my findings.

0pcom commented 4 months ago

The display I'm currently testing with is an older VFD type display. It does work with arduino code, when i write one character at a time and introduce a small delay between writes.

I will come back and add to this issue a test on a more modern display when i get around to it.

Seems I'm getting panic on lcd.Display() with the following code, in 8 bit mode

//main.go
package main

import (
    "machine"
    "time"

    "tinygo.org/x/drivers/hd44780"
)

func main() {
    machine.UART0.Configure(machine.UARTConfig{TX: machine.UART_TX_PIN, RX: machine.UART_RX_PIN, BaudRate: 9600})
    machine.UART0.Write([]byte("starting up...\n"))
    led := machine.LED
    led.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.UART0.Write([]byte("configured LED\n"))

        rw := machine.NoPin
        rs := machine.D2
        en := machine.D3
        d4 := machine.D4
        d5 := machine.D5
        d6 := machine.D6
        d7 := machine.D7
        d8 := machine.D8
        d10 := machine.D10
        d11 := machine.D11
        d12 := machine.D12
contrast := machine.D9
contrast.Configure(machine.PinConfig{Mode: machine.PinOutput})
contrast.High()
machine.UART0.Write([]byte("set contrast\n"))

lcd, err := hd44780.NewGPIO8Bit(
    []machine.Pin{d12,d11,d10,d8,d7,d6,d5,d4},
    rs,
    en,
    rw,
)
    if err != nil {
        machine.UART0.Write([]byte("error initializing lcd NewGPIO8Bit \n"))
        machine.UART0.Write([]byte(err.Error()+"\n"))
    }
    machine.UART0.Write([]byte("initialized lcd\n"))

    err = lcd.Configure(hd44780.Config{
        Width:       16,
        Height:      1,
        CursorOnOff: true,
        CursorBlink: false,
    })
    if err != nil {
        machine.UART0.Write([]byte("error configuring lcd\n"))
        machine.UART0.Write([]byte(err.Error()+"\n"))
    }
    machine.UART0.Write([]byte("configured lcd\n"))
    machine.UART0.Write([]byte("testing lcd.Write\n"))
    lcd.Write([]byte("a"))
    machine.UART0.Write([]byte("testing lcd.Display\n"))
    time.Sleep(1000)
    err = lcd.Display()
    if err != nil {
        machine.UART0.Write([]byte("error initializing lcd\n"))
        machine.UART0.Write([]byte(err.Error()+"\n"))
    }

    for {
        led.Low()
        machine.UART0.Write([]byte("LED Low\n"))
        time.Sleep(time.Millisecond * 1000)
        led.High()
        machine.UART0.Write([]byte("LED High\n"))
        time.Sleep(time.Millisecond * 100)
    }
}

a serial monitor program which accompanies this

// mon.go
package main

import (
    "fmt"
    "log"
    "github.com/tarm/serial"
)

func main() {
    port, err := serial.OpenPort(&serial.Config{Name: "/dev/ttyUSB0", Baud: 9600})
    if err != nil {
        log.Fatalf("serial.OpenPort: %v", err)
    }
    defer port.Close()
    buf := make([]byte, 128)
    for {
        n, err := port.Read(buf)
        if err != nil {
            log.Fatalf("port.Read: %v", err)
        }
        fmt.Print(string(buf[:n]))
    }
}

Testing output

$ tinygo flash -target=arduino -port=/dev/ttyUSB0 main.go && go run mon.go
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e950f (probably m328p)
avrdude: Note: flash memory has been specified, an erase cycle will be performed.
         To disable this feature, specify the -D option.
avrdude: erasing chip

avrdude: processing -U flash:w:/tmp/tinygo1630339240/main.hex:i
avrdude: reading input file /tmp/tinygo1630339240/main.hex for flash
         with 6485 bytes in 1 section within [0, 0x1954]
         using 51 pages and 43 pad bytes
avrdude: writing 6485 bytes flash ...
Writing | ################################################## | 100% 1.08 s 
avrdude: 6485 bytes of flash written
avrdude: verifying flash memory against /tmp/tinygo1630339240/main.hex
Reading | ################################################## | 100% 0.83 s 
avrdude: 6485 bytes of flash verified

avrdude done.  Thank you.

starting up...
configured LED
set contra�starting up...
configured LED
set contrast
initialized lcd
configured lcd
testing lcd.Write
testing lcd.Display
panic: runtime error: index out of range

https://github.com/tinygo-org/drivers/assets/36607567/77d8884a-de30-42bd-babe-fac7b4885ade

sorf commented 4 months ago

Seems I'm getting panic on lcd.Display() with the following code, in 8 bit mode

If this panic is without it, could you please try with the proposed fix (#647) for this issue? It should be just a matter of adding:

replace tinygo.org/x/drivers => github.com/sorf/tinygo-drivers v0.0.0-20240126073429-67f5ced56e82

to the go.mod (such as here) and go mod tidy.

For me the the fix worked in both 4-bit and 8-bit mode with the LCD from the Arduino starter kit and the virtual one on wokwi.com.

0pcom commented 4 months ago

There were a few issues with the code in my previous comment, but after fixing those I could never get past panic: runtime error: index out of range

After many attempts, i ended up copying the whole driver into the modified example code. I was then able to just create a GPIO and use write8BitMode as follows

package main

import (
    "errors"
    "io"
    m "machine"
    "time"
//  "tinygo.org/x/drivers/hd44780"
)

func main() {
m.UART0.Configure(m.UARTConfig{TX: m.UART_TX_PIN, RX: m.UART_RX_PIN, BaudRate: 9600})
m.UART0.Write([]byte("starting up...\n"))
led := m.LED
led.Configure(m.PinConfig{Mode: m.PinOutput})
m.UART0.Write([]byte("configured LED\n"))

cont := m.D9
cont.Configure(m.PinConfig{Mode: m.PinOutput})
cont.High()
m.UART0.Write([]byte("set contrast\n"))

m.Serial.Configure(m.UARTConfig{BaudRate: 9600})
m.D12.Configure(m.PinConfig{Mode: m.PinOutput})
m.D11.Configure(m.PinConfig{Mode: m.PinOutput})
m.D10.Configure(m.PinConfig{Mode: m.PinOutput})
m.D8.Configure(m.PinConfig{Mode: m.PinOutput})
m.D7.Configure(m.PinConfig{Mode: m.PinOutput})
m.D6.Configure(m.PinConfig{Mode: m.PinOutput})
m.D5.Configure(m.PinConfig{Mode: m.PinOutput})
m.D4.Configure(m.PinConfig{Mode: m.PinOutput})
m.D3.Configure(m.PinConfig{Mode: m.PinOutput})
m.D2.Configure(m.PinConfig{Mode: m.PinOutput})
//rw.Configure(m.PinConfig{Mode: m.PinOutput})
//rw.Low()

gpio := GPIO{
    dataPins: []m.Pin{m.D12, m.D11, m.D10, m.D8,m.D7, m.D6, m.D5, m.D4},
    en:      m.D3,
    rs:       m.D2,
    rw:      m.NoPin,
}
gpio.write = gpio.write8BitMode
gpio.read = gpio.read8BitMode

m.UART0.Write([]byte("initialized gpio\n"))

m.UART0.Write([]byte("testing gpio.write8BitMode\n"))
for _, b := range []byte("ABCDEFGHIJKLMN0PQRSTUVWXYZ") {
    gpio.write8BitMode(b)
    time.Sleep(time.Millisecond * 100)
}
for {
        led.Low()
        m.UART0.Write([]byte("LED Low\n"))
        time.Sleep(time.Millisecond * 1000)
        led.High()
        m.UART0.Write([]byte("LED High\n"))
        time.Sleep(time.Millisecond * 100)
    }
}

and it works! (the characters need to be remapped)

image

perhaps there can be a more generic way of integrating this workaround into an example to support older displays. If the GPIO struct fields were exported, it would be possible to do this.

0pcom commented 4 months ago

I can confirm now that the example driver works for me in both 4 and 8 bit mode with a modern 16X2 LCD. I had misconfigured the contrast PWM, which was not actually part of that example. After adding that, the text became visible.