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
607 stars 191 forks source link

HD44780 in 16X1 mode lcd.Display() panic: runtime error: index out of range #654

Closed 0pcom closed 7 months ago

0pcom commented 7 months ago

For some reason, the 16x2 mode of the LCD I'm testing displays with a lot of apparent noise. However, in 16x1, the text is much cleaner and free from noise. Hence my motivation for using 16x1 configuration on a 16x2 LCD. The 16x2 mode does not suffer from this panic.

Below is my example code, running on arduino uno.

package main

import (
    m "machine"
    "time"

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

var period uint64 = 1e9 / 500

func main() {
    cont := m.D9
    pwm := m.Timer1

    // Configure the PWM with the given period.
    pwm.Configure(m.PWMConfig{
        Period: period,
    })

    ch, err := pwm.Channel(cont)
    if err != nil {
        println(err.Error())
        return
    }
    pwm.Set(ch, pwm.Top()/uint32(2))
    m.UART0.Write([]byte("set contrast\n"))

    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"))

m.Serial.Configure(m.UARTConfig{BaudRate: 9600})
lcd, err := hd44780.NewGPIO8Bit([]m.Pin{m.D4, m.D5, m.D6, m.D7, m.D8, m.D12, m.D10, m.D11}, m.D3, m.D2, m.NoPin)
//lcd, err := hd44780.NewGPIO4Bit([]m.Pin{m.D8, m.D12, m.D10, m.D11}, m.D3, m.D2, m.NoPin)
    if err != nil {
        m.UART0.Write([]byte("error initializing lcd NewGPIO8Bit \n"))
        m.UART0.Write([]byte(err.Error()+"\n"))
    }
    m.UART0.Write([]byte("initialized lcd\n"))

    err = lcd.Configure(hd44780.Config{
        Width:       16,
        Height:      1,
        CursorOnOff: true,
        CursorBlink: true,
    })
    if err != nil {
        m.UART0.Write([]byte("error configuring lcd\n"))
        m.UART0.Write([]byte(err.Error()+"\n"))
    }
    m.UART0.Write([]byte("configured lcd\n"))
    m.UART0.Write([]byte("testing lcd.Write\n"))
    _, err = lcd.Write([]byte("Hello world"))
    if err != nil {
        m.UART0.Write([]byte("error on lcd.Write\n"))
        m.UART0.Write([]byte(err.Error()+"\n"))
    }

    m.UART0.Write([]byte("testing lcd.Display\n"))
    err = lcd.Display()
    if err != nil {
        m.UART0.Write([]byte("error displaying lcd buffer\n"))
        m.UART0.Write([]byte(err.Error()+"\n"))
    }
    time.Sleep(time.Millisecond * 2000)
lcd.ClearDisplay()
lcd.SetCursor(0,0)
    for {
        data := make([]byte, 64)
        n, _ := m.UART0.Read(data)
        led.High()
        m.UART0.Write(data[:n])
        time.Sleep(time.Millisecond * 5)
        lcd.Write(data[:n])
        lcd.SetCursor(0,0)
        lcd.Display()
        time.Sleep(time.Millisecond * 1000)
        led.Low()
    }
}

serial interface program

package main

import (
    "fmt"
    "log"
    "bufio"
    "os"
    "time"
    "sync"
    "github.com/tarm/serial"
    cc "github.com/ivanpirog/coloredcobra"
    "github.com/spf13/cobra"
    "strings"
)

func main() {
    cc.Init(&cc.Config{
        RootCmd:       RootCmd,
        Headings:      cc.HiBlue + cc.Bold,
        Commands:      cc.HiBlue + cc.Bold,
        CmdShortDescr: cc.HiBlue,
        Example:       cc.HiBlue + cc.Italic,
        ExecName:      cc.HiBlue + cc.Bold,
        Flags:         cc.HiBlue + cc.Bold,
        FlagsDescr:      cc.HiBlue,
        NoExtraNewlines: true,
        NoBottomNewline: true,
    })
    if err := RootCmd.Execute(); err != nil {
        log.Fatal("Failed to execute command: ", err)
    }
}

var(
    ttyUSB string
    baud int
)

func init() {
    RootCmd.AddCommand(monCmd, sendCmd)
    RootCmd.CompletionOptions.DisableDefaultCmd = true
    RootCmd.Flags().StringVarP(&ttyUSB, "mon", "m", "/dev/ttyUSB0", "block device to monitor")
    RootCmd.Flags().IntVarP(&baud, "baud", "b", 9600, "baud rate")
    monCmd.Flags().StringVarP(&ttyUSB, "mon", "m", "/dev/ttyUSB0", "block device to monitor")
    monCmd.Flags().IntVarP(&baud, "baud", "b", 9600, "baud rate")
    sendCmd.Flags().StringVarP(&ttyUSB, "mon", "m", "/dev/ttyUSB0", "block device to monitor")
    sendCmd.Flags().IntVarP(&baud, "baud", "b", 9600, "baud rate")
    var helpflag bool
    RootCmd.SetUsageTemplate(help)
    RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for "+RootCmd.Use)
    RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
    RootCmd.PersistentFlags().MarkHidden("help") //nolint
}

// RootCmd is the root command
var RootCmd = &cobra.Command{
    Use:                   "serial",
    Short:                 "serial interfacer",
    Long:                  `serial interfacer`,
    SilenceErrors:         true,
    SilenceUsage:          true,
    DisableSuggestions:    true,
    DisableFlagsInUseLine: true,
    Run: func(cmd *cobra.Command, args []string) {
        port, err := serial.OpenPort(&serial.Config{Name: ttyUSB, Baud: baud})
        if err != nil {
            log.Fatalf("serial.OpenPort: %v", err)
        }
        defer port.Close()
        var wg sync.WaitGroup
        wg.Add(2)
        go func() {
            defer wg.Done()
            reader := bufio.NewReader(port)
            for {
                data, err := reader.ReadString('\n')
                if err != nil {
                    log.Fatalf("Error reading from serial port: %v", err)
                }
                fmt.Print(data)
            }
        }()
        go func() {
            defer wg.Done()
            reader := bufio.NewReader(os.Stdin)
            for {
                input, err := reader.ReadString('\n')
                if err != nil {
                    log.Fatalf("Error reading from stdin: %v", err)
                }
                input = strings.TrimSuffix(input, "\n") // Trim newline character
                _, err = port.Write([]byte(input))
                if err != nil {
                    log.Fatalf("Error writing to serial port: %v", err)
                }
            }
            }()
        wg.Wait()
    },
}

var monCmd = &cobra.Command{
    Use:                   "mon",
    Short:                 "serial monitor",
    Long:                  `serial monitor`,
    SilenceErrors:         true,
    SilenceUsage:          true,
    DisableSuggestions:    true,
    DisableFlagsInUseLine: true,
    Run: func(cmd *cobra.Command, args []string) {
        port, err := serial.OpenPort(&serial.Config{Name: ttyUSB, Baud: baud})
        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]))
        }
    },
}

var sendCmd = &cobra.Command{
    Use:                   "send",
    Short:                 "serial send",
    Long:                  `serial send`,
    SilenceErrors:         true,
    SilenceUsage:          true,
    DisableSuggestions:    true,
    DisableFlagsInUseLine: true,
    Run: func(cmd *cobra.Command, args []string) {
        port, err := serial.OpenPort(&serial.Config{Name: ttyUSB, Baud: baud})
        if err != nil {
            log.Fatalf("serial.OpenPort: %v", err)
        }
        defer port.Close()
        reader := bufio.NewReader(os.Stdin)
        for {
            input, err := reader.ReadString('\n')
            if err != nil {
                println("Error reading from stdin:", err)
                return
            }
            _, err = port.Write([]byte(input))
            if err != nil {
                    log.Fatal(err)
            }
            time.Sleep(time.Millisecond * 100)
        }
    },
}

const help = "Usage:\r\n" +
    "  {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
    "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
    "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n  " +
    "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
    "Flags:\r\n" +
    "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
    "Global Flags:\r\n" +
    "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"

Test

$ tinygo flash -target=arduino -port=/dev/ttyUSB0 main.go && go run mon.go  -m /dev/ttyUSB0
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/tinygo3215553026/main.hex:i
avrdude: reading input file /tmp/tinygo3215553026/main.hex for flash
         with 9101 bytes in 1 section within [0, 0x238c]
         using 72 pages and 115 pad bytes
avrdude: writing 9101 bytes flash ...
Writing | ################################################## | 100% 1.51 s 
avrdude: 9101 bytes of flash written
avrdude: verifying flash memory against /tmp/tinygo3215553026/main.hex
Reading | ################################################## | 100% 1.18 s 
avrdude: 9101 bytes of flash verified

avrdude done.  Thank you.

set contras��.W�ѥ���up...
configured LED
initialized lcd
set contras��.W�ѥ���up...
configured LED
initialized lcd
configured lcd
testing lcd.Write
testing lcd.Display
panic: runtime error: index out of range
0pcom commented 7 months ago

i've subsequently had no issues with the same display driven by rpi pico. Since this is basically an extreme edge case and I can't follow up on it, closing.