Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ had changed in them - which is the gap this file exists to close.

### Added

- **The RAK4631's display is drawn under Renode.** Its SSD1306 is on I2C,
and the TWIM model answered every address with a NACK. The TWIM now carries
devices registered at an address and moves each transfer's bytes through
EasyDMA, and a new SSD1306 model sends the controller's memory to the board
view. An address nobody claims is still a NACK. The published v1.17.1
repeater draws its own boot screen.
- **Script input at an instant of simulated time.** `console.type`,
`console.cli` and the `companion.*` verbs that write to a node take `at_ms`:
the call is held and runs when the run reaches that instant, so a script
Expand Down
11 changes: 9 additions & 2 deletions internal/firmware/emulated/renode.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ lora: Radio.VirtualSX1262 @ radiospi
%d -> lora@0
`, renode.EasyDMASPI(e.SPIBase), e.SPIBase, engineHost, enginePort, e.IrqPort,
e.IrqPin, e.NssPort, e.NssPin)
wiring += renode.Panel(e.SPIBase, e.PanelPort, e.PanelWidth, e.PanelHgt,
e.PanelCS, e.PanelDC, e.NssPort)
// A colour panel has a select line of its own; an I2C display has an
// address instead.
if e.PanelCS != 0 {
wiring += renode.Panel(e.SPIBase, e.PanelPort, e.PanelWidth, e.PanelHgt,
e.PanelCS, e.PanelDC, e.NssPort)
} else {
wiring += renode.Oled(e.PanelPort, e.PanelAddr, e.PanelWidth, e.PanelHgt, e.PanelOffset)
}
wiring += renode.Inputs(e.ButtonPort, e.HasMeter)
if err := os.WriteFile(repl, []byte(wiring), 0o644); err != nil {
return "", err
Expand All @@ -80,6 +86,7 @@ i @%[1]s/peripherals/VirtualSX1262Engine.cs
i @%[1]s/peripherals/VirtualSX1262.cs
i @%[1]s/peripherals/MeshBenchInputs.cs
i @%[1]s/peripherals/MeshBenchPanel.cs
i @%[1]s/peripherals/MeshBenchOled.cs
i @%[1]s/peripherals/NRF52840_Temp.cs
i @%[1]s/peripherals/NRF52840_Clock.cs
i @%[1]s/peripherals/NRF52840_SAADC.cs
Expand Down
20 changes: 20 additions & 0 deletions internal/firmware/emulated/renode/oled_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package renode

import (
"strings"
"testing"
)

// An I2C display hangs off the first TWIM at its own address, told its glass
// and where its columns start (#602).
func TestAnI2CDisplayIsRegisteredAtItsAddress(t *testing.T) {
got := Oled(4242, 0x3C, 128, 64, 0)
for _, want := range []string{"Video.MeshBenchOled @ twi0 0x3C", "port: 4242", "width: 128", "height: 64", "columnOffset: 0"} {
if !strings.Contains(got, want) {
t.Errorf("missing %q in:\n%s", want, got)
}
}
if Oled(0, 0x3C, 128, 64, 0) != "" || Oled(4242, 0, 128, 64, 0) != "" {
t.Error("a display with no channel or no address was wired anyway")
}
}
14 changes: 14 additions & 0 deletions internal/firmware/emulated/renode/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ func Panel(radioBase uint32, port, width, height, cs, dc int, dcPort string) str
`, stockSPIName, port, width, height, cs, dc, dcPort, cs, cs, dc, dc)
}

// Oled is an I2C display on the first TWIM, at its address. offset is where
// the glass starts in the controller's columns: 2 for an SH1106.
func Oled(port int, addr uint8, width, height, offset int) string {
if port == 0 || addr == 0 || width <= 0 || height <= 0 {
return ""
}
return fmt.Sprintf(`oled: Video.MeshBenchOled @ twi0 0x%X
port: %d
width: %d
height: %d
columnOffset: %d
`, addr, port, width, height, offset)
}

func UnregisterStockSPI() string {
return "sysbus Unregister sysbus." + stockSPIName + "\n"
}
Expand Down
12 changes: 12 additions & 0 deletions internal/sim/engine/boardsettings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,15 @@ func shortNodeFS(t *testing.T) string {
t.Cleanup(func() { _ = os.RemoveAll(dir) })
return dir
}

// The RAK4631's SSD1306 is drawn under Renode now, so the window must say so
// rather than say the gap is ours (#602).
func TestTheRAK4631DisplayIsModelled(t *testing.T) {
b, err := hw.BoardByName("RAK_4631")
if err != nil {
t.Fatal(err)
}
if !ScreenModelled(b) {
t.Error("the RAK4631's I2C display reads as not modelled")
}
}
19 changes: 14 additions & 5 deletions internal/sim/engine/nodeparts.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func withParts(node *emulated.EmulatedNode, board hw.Board, spec scenario.Node,
node.PanelCS, node.PanelDC = p.Screen.CS, p.Screen.DC
node.PanelWidth, node.PanelHgt = p.Screen.WidthPx, p.Screen.HeightPx
}
// Renode's I2C display model is told its glass too; QEMU's takes it
// from the controller it emulates.
if p.Screen.Bus == hw.BusI2C && board.Renode != nil {
node.PanelWidth, node.PanelHgt = p.Screen.WidthPx, p.Screen.HeightPx
}
}
return node, nil
}
Expand All @@ -162,11 +167,10 @@ func withParts(node *emulated.EmulatedNode, board hw.Board, spec scenario.Node,
// nothing draws must read as "no display modelled" and never as "silent", which
// says the firmware chose not to draw.
//
// QEMU has both buses. Renode has the SPI panels - an ST7789 and an ST7735,
// sharing the radio's controller and told apart by chip select, which is how
// they are wired in copper - and not the I2C one: its TWIM model answers an
// address with a NACK because until now no board under it had anything on that
// bus to answer.
// QEMU has both buses. So does Renode: the SPI panels - an ST7789 and an
// ST7735, sharing the radio's controller and told apart by chip select, which
// is how they are wired in copper - and an SSD1306 on I2C, registered on the
// TWIM at its address.
func ScreenModelled(board hw.Board) bool {
p := board.Hardware
if p == nil || p.Screen == nil {
Expand All @@ -178,6 +182,11 @@ func ScreenModelled(board hw.Board) bool {
// other port would be wired wrong rather than unsupported. Both boards
// that carry one have it on the radio's port; this is the check that
// says so rather than a comment claiming it.
// An I2C display hangs off the first TWIM at its address, where
// MeshBenchOled answers for an SSD1306 or an SH1106 (#602).
if p.Screen.Bus == hw.BusI2C {
return p.Screen.Controller == "SSD1306" || p.Screen.Controller == "SH1106"
}
return p.Screen.Bus == hw.BusSPI &&
p.Screen.CS != 0 && p.Screen.DC != 0 &&
onSamePort(p.Screen.CS, board.Renode.NssPort) &&
Expand Down
23 changes: 14 additions & 9 deletions internal/ui/workbench/boardview/rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ func TestABoardWithNoRecordedPanelStillHasARadio(t *testing.T) {
// reporting that as silence sends somebody looking for a fault in a firmware
// that is behaving perfectly.
//
// The RAK4631 is the one left. Its display is on I2C, and Renode's TWIM model
// answers an address with a NACK, so there is nowhere to put an SSD1306 yet -
// where the two SPI panels beside it are drawn.
// Every Renode board in the catalogue has its panel drawn now (the RAK4631's
// SSD1306 was the last, #602), so the case is made by hand: the same board
// with a controller nothing models, an e-paper one.
func TestAnUndrawablePanelBlamesUsRatherThanTheFirmware(t *testing.T) {
b, err := hw.BoardByName("RAK_4631")
if err != nil {
Expand All @@ -243,24 +243,29 @@ func TestAnUndrawablePanelBlamesUsRatherThanTheFirmware(t *testing.T) {
if b.Renode == nil || b.Hardware == nil || b.Hardware.Screen == nil {
t.Fatal("this test needs a Renode board that declares a screen")
}
panel := *b.Hardware
screen := *panel.Screen
screen.Controller = "UC8151"
panel.Screen = &screen
b.Hardware = &panel
running := &state.NodeStat{Name: "n", Board: b.Name, Running: true}
var screen *Row
var row *Row
rows := wiringRows(b, running)
for i := range rows {
if rows[i].Group == "Display" {
screen = &rows[i]
row = &rows[i]
break
}
}
if screen == nil {
if row == nil {
t.Fatal("a board that declares a screen produced no display row")
}
if screen.Verdict == Silent {
if row.Verdict == Silent {
t.Errorf("a panel with no model behind it read as %v, which says the "+
"firmware chose not to draw", Silent)
}
if screen.Verdict != NotModelled {
t.Errorf("read %v (%q), want %v", screen.Verdict, screen.Observed, NotModelled)
if row.Verdict != NotModelled {
t.Errorf("read %v (%q), want %v", row.Verdict, row.Observed, NotModelled)
}
}

Expand Down
Loading