ph00524: add emulator
This commit is contained in:
parent
7c663f42ca
commit
be255f2e6f
29
modules/ph00524/emu/README.md
Normal file
29
modules/ph00524/emu/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
PH00524, but emulated
|
||||||
|
===
|
||||||
|
|
||||||
|
When troubleshooting the PH00524 evacuation controller board it's useful to be
|
||||||
|
able to tell what the microcontroller _should_ be doing, rather than just
|
||||||
|
staring at disassembly/decompilation all day long.
|
||||||
|
|
||||||
|
This little program executes our scope evacuation controller's code (from the
|
||||||
|
U12 EEPROM) in an environment consisting of a Z80 CPU and all peripherals
|
||||||
|
required to reach a pumpdown state.
|
||||||
|
|
||||||
|
It comes with a little text UI interface:
|
||||||
|
|
||||||
|
Controls: [v] vent, [p] pumpdown, [q] quit
|
||||||
|
PC 02bc
|
||||||
|
---- -------- VENT
|
||||||
|
M1 M2
|
||||||
|
|... ..
|
||||||
|
|
||||||
|
This shows the controls (keyboard buttons v, p and q), the last program counter
|
||||||
|
at which an I/O operation was performed, indicator values, motor movement and
|
||||||
|
lightbarrier status.
|
||||||
|
|
||||||
|
Running
|
||||||
|
---
|
||||||
|
|
||||||
|
Change to this directory and run:
|
||||||
|
|
||||||
|
$ go run ./
|
98
modules/ph00524/emu/arbiters.go
Normal file
98
modules/ph00524/emu/arbiters.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/koron-go/z80"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ioPeripheral is a z80.IO device at a given address.
|
||||||
|
type ioPeripheral struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
peripheral z80.IO
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioArbiter is a collection of I/O devices at addresses. It implements z80.IO
|
||||||
|
// itself, dispatching to the appropriate device as needed.
|
||||||
|
type ioArbiter struct {
|
||||||
|
peripherals []*ioPeripheral
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ioArbiter) get(addr uint8) *ioPeripheral {
|
||||||
|
for _, periph := range i.peripherals {
|
||||||
|
if addr < periph.start {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if addr > periph.start+periph.length {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return periph
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ioArbiter) In(addr uint8) uint8 {
|
||||||
|
p := i.get(addr)
|
||||||
|
if p == nil {
|
||||||
|
log.Fatalf("Unhandled I/O In at %02x", addr)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return p.peripheral.In(addr - p.start)
|
||||||
|
}
|
||||||
|
func (i *ioArbiter) Out(addr uint8, value uint8) {
|
||||||
|
p := i.get(addr)
|
||||||
|
if p == nil {
|
||||||
|
log.Fatalf("Unhandled I/O Out at %02x", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.peripheral.Out(addr-p.start, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory is a continuous memory region at a given address, optionally marked
|
||||||
|
// read only.
|
||||||
|
type memory struct {
|
||||||
|
start uint16
|
||||||
|
data []uint8
|
||||||
|
readonly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// memoryArbiter implements Z80.Memory and dispatches accesses to subordinate
|
||||||
|
// memory instances.
|
||||||
|
type memoryArbiter struct {
|
||||||
|
memories []memory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryArbiter) get(addr uint16) *memory {
|
||||||
|
for _, mem := range m.memories {
|
||||||
|
if addr < mem.start {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if addr >= mem.start+uint16(len(mem.data)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &mem
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryArbiter) Get(addr uint16) uint8 {
|
||||||
|
mem := m.get(addr)
|
||||||
|
if mem == nil {
|
||||||
|
log.Fatalf("Unhandled memory Get at %04x", addr)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return mem.data[addr-mem.start]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryArbiter) Set(addr uint16, value uint8) {
|
||||||
|
mem := m.get(addr)
|
||||||
|
if mem == nil {
|
||||||
|
log.Fatalf("Unhandled memory Set at %04x", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mem.readonly {
|
||||||
|
log.Fatalf("Read-only memory Set at %04x", addr)
|
||||||
|
}
|
||||||
|
mem.data[addr-mem.start] = value
|
||||||
|
}
|
157
modules/ph00524/emu/controller.go
Normal file
157
modules/ph00524/emu/controller.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/koron-go/z80"
|
||||||
|
)
|
||||||
|
|
||||||
|
// evacuationController is the PH00524 board emulated in a synchronous step
|
||||||
|
// manner.
|
||||||
|
type evacuationController struct {
|
||||||
|
cpu *z80.CPU
|
||||||
|
|
||||||
|
m1 *motor
|
||||||
|
m2 *motor
|
||||||
|
|
||||||
|
u8 *i8255
|
||||||
|
u9 *i8255
|
||||||
|
|
||||||
|
// lastIOPC is the last PC on which an I/O operation (through U8 or U9) was
|
||||||
|
// performed. This is used for status/debugging.
|
||||||
|
lastIOPC uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEvacuationController(romPath string) *evacuationController {
|
||||||
|
rom, err := os.ReadFile(romPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read ROM: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mem := memoryArbiter{
|
||||||
|
memories: []memory{
|
||||||
|
{start: 0x0000, data: rom, readonly: true},
|
||||||
|
{start: 0x8000, data: make([]byte, 512)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 := i8255{}
|
||||||
|
u9 := i8255{}
|
||||||
|
io := ioArbiter{
|
||||||
|
peripherals: []*ioPeripheral{
|
||||||
|
{start: 0x40, length: 4, peripheral: &u8},
|
||||||
|
{start: 0x80, length: 4, peripheral: &u9},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m1 := motor{barrier: &barrier{npos: 4}}
|
||||||
|
m2 := motor{barrier: &barrier{npos: 2}}
|
||||||
|
|
||||||
|
cpu := z80.CPU{
|
||||||
|
Memory: &mem,
|
||||||
|
IO: &io,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &evacuationController{
|
||||||
|
cpu: &cpu,
|
||||||
|
m1: &m1,
|
||||||
|
m2: &m2,
|
||||||
|
u8: &u8,
|
||||||
|
u9: &u9,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make lastIOPC get updated on u8/u9 I/O.
|
||||||
|
u8.onIn = res.onIO
|
||||||
|
u8.onOut = res.onIO
|
||||||
|
u9.onIn = res.onIO
|
||||||
|
u9.onOut = res.onIO
|
||||||
|
|
||||||
|
// Expansion bus connector, seems to be always pulled up.
|
||||||
|
u9.pa = 0xff
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *evacuationController) onIO(_ i8255Port) {
|
||||||
|
e.lastIOPC = e.cpu.PC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *evacuationController) getIndcators() indicators {
|
||||||
|
return indicators{
|
||||||
|
indEvac: (e.u8.pc & 1) != 0,
|
||||||
|
indHTReadyN: (e.u8.pc & 2) != 0,
|
||||||
|
indPumpDown: (e.u8.pc & 4) != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *evacuationController) pressPumpdown(value bool) {
|
||||||
|
if value {
|
||||||
|
e.u8.pb |= 1
|
||||||
|
} else {
|
||||||
|
e.u8.pb &= (0xff ^ 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *evacuationController) pressVent(value bool) {
|
||||||
|
if value {
|
||||||
|
e.u8.pb |= 2
|
||||||
|
} else {
|
||||||
|
e.u8.pb &= (0xff ^ 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step through the simulation/emulation given a delta time.
|
||||||
|
func (e *evacuationController) step(dt time.Duration) bool {
|
||||||
|
e.cpu.Step()
|
||||||
|
if e.cpu.HALT {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate motors and barriers.
|
||||||
|
e.m1.dir = (e.u9.pc & 16) != 0
|
||||||
|
e.m1.en = (e.u9.pc & 4) != 0
|
||||||
|
e.m2.dir = (e.u9.pc & 8) != 0
|
||||||
|
e.m2.en = (e.u9.pc & 2) != 0
|
||||||
|
e.m1.sim(dt)
|
||||||
|
e.m2.sim(dt)
|
||||||
|
|
||||||
|
// Feed barrier status back into CPU.
|
||||||
|
b1p := e.m1.barrier.pins()
|
||||||
|
b2p := e.m2.barrier.pins()
|
||||||
|
e.u8.pa = 0
|
||||||
|
if b1p[0] {
|
||||||
|
e.u8.pa |= (1 << 5)
|
||||||
|
}
|
||||||
|
if b1p[1] {
|
||||||
|
e.u8.pa |= (1 << 4)
|
||||||
|
}
|
||||||
|
if b1p[2] {
|
||||||
|
e.u8.pa |= (1 << 3)
|
||||||
|
}
|
||||||
|
if b1p[3] {
|
||||||
|
e.u8.pa |= (1 << 2)
|
||||||
|
}
|
||||||
|
if b2p[0] {
|
||||||
|
e.u8.pa |= (1 << 1)
|
||||||
|
}
|
||||||
|
if b2p[1] {
|
||||||
|
e.u8.pa |= (1 << 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate vacuum gauge / comparators given light barrier status.
|
||||||
|
if b1p[3] {
|
||||||
|
// half succ (PRE_EVAC)
|
||||||
|
e.u8.pb |= 1 << 5
|
||||||
|
// full succ (???)
|
||||||
|
e.u8.pb |= 1 << 6
|
||||||
|
}
|
||||||
|
if !b1p[0] {
|
||||||
|
// no more succ
|
||||||
|
e.u8.pb &= 0xff ^ (1 << 5)
|
||||||
|
e.u8.pb &= 0xff ^ (1 << 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
10
modules/ph00524/emu/go.mod
Normal file
10
modules/ph00524/emu/go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module git.fa-fo.de/fafo/jeol-t330a/modules/ph00524/emu
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/koron-go/z80 v0.10.1
|
||||||
|
golang.org/x/term v0.23.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.23.0 // indirect
|
8
modules/ph00524/emu/go.sum
Normal file
8
modules/ph00524/emu/go.sum
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/koron-go/z80 v0.10.1 h1:Jfb0esP/QFL4cvcr+eFECVG0Y/mA9JBLC4EKbMU5zAY=
|
||||||
|
github.com/koron-go/z80 v0.10.1/go.mod h1:ry+Zl9kRKelzaDG9UzEtUpUnXy0Yv/kk1YEaX958xdk=
|
||||||
|
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||||
|
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
102
modules/ph00524/emu/i8255.go
Normal file
102
modules/ph00524/emu/i8255.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
// i8255 is a generic Intel 8255-compatible I/O controller chip.
|
||||||
|
//
|
||||||
|
// Only mode 0 is supported.
|
||||||
|
type i8255 struct {
|
||||||
|
pa, pb, pc uint8
|
||||||
|
|
||||||
|
onOut func(port i8255Port)
|
||||||
|
onIn func(port i8255Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
type i8255Port string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PA i8255Port = "PA"
|
||||||
|
PB i8255Port = "PB"
|
||||||
|
PC i8255Port = "PC"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *i8255) getPort(p i8255Port) uint8 {
|
||||||
|
switch p {
|
||||||
|
case PA:
|
||||||
|
return i.pa
|
||||||
|
case PB:
|
||||||
|
return i.pb
|
||||||
|
case PC:
|
||||||
|
return i.pc
|
||||||
|
default:
|
||||||
|
log.Fatalf("invalid port")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *i8255) Out(addr, value uint8) {
|
||||||
|
switch addr {
|
||||||
|
case 0:
|
||||||
|
i.pa = value
|
||||||
|
if i.onOut != nil {
|
||||||
|
i.onOut(PA)
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
i.pb = value
|
||||||
|
if i.onOut != nil {
|
||||||
|
i.onOut(PB)
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
i.pc = value
|
||||||
|
if i.onOut != nil {
|
||||||
|
i.onOut(PC)
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
if (value >> 7) == 0 {
|
||||||
|
// BSR mode
|
||||||
|
bitno := (value >> 1) & 0b111
|
||||||
|
bitval := value & 1
|
||||||
|
if bitval == 1 {
|
||||||
|
i.pc |= (1 << bitno)
|
||||||
|
} else {
|
||||||
|
i.pc &= 0xff ^ (1 << bitno)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Input/Output mode
|
||||||
|
gaMode := (value >> 5) & 0b11
|
||||||
|
gbMode := (value >> 5) & 0b11
|
||||||
|
if gaMode != 0 {
|
||||||
|
log.Fatalf("I8255 implementation only supports mode 0 on Port A")
|
||||||
|
}
|
||||||
|
if gbMode != 0 {
|
||||||
|
log.Fatalf("I8255 implementation only supports mode 0 on Port B")
|
||||||
|
}
|
||||||
|
// Don't care about the rest (input/output settings per group/port).
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("I8255: invalid out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *i8255) In(addr uint8) uint8 {
|
||||||
|
switch addr {
|
||||||
|
case 0:
|
||||||
|
if i.onIn != nil {
|
||||||
|
i.onIn(PA)
|
||||||
|
}
|
||||||
|
return i.pa
|
||||||
|
case 1:
|
||||||
|
if i.onIn != nil {
|
||||||
|
i.onIn(PB)
|
||||||
|
}
|
||||||
|
return i.pb
|
||||||
|
case 2:
|
||||||
|
if i.onIn != nil {
|
||||||
|
i.onIn(PC)
|
||||||
|
}
|
||||||
|
return i.pc
|
||||||
|
default:
|
||||||
|
log.Printf("I8255: invalid in")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
83
modules/ph00524/emu/main.go
Normal file
83
modules/ph00524/emu/main.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctrl := newEvacuationController("../eeprom/Vac-ours FK-U12.bin")
|
||||||
|
|
||||||
|
// Switch stdin into 'raw' mode.
|
||||||
|
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||||
|
|
||||||
|
// Keyboard input channel, byte at a time.
|
||||||
|
kbdC := make(chan byte)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err = os.Stdin.Read(b)
|
||||||
|
if err == nil {
|
||||||
|
kbdC <- b[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
t := time.Now()
|
||||||
|
|
||||||
|
// Countdown timers for simulating a button press.
|
||||||
|
pumpdownButton := time.Duration(0)
|
||||||
|
ventButton := time.Duration(0)
|
||||||
|
|
||||||
|
lastPrint := time.Now()
|
||||||
|
|
||||||
|
fmt.Print("\033[H\033[2J")
|
||||||
|
for {
|
||||||
|
dt := time.Since(t)
|
||||||
|
t = time.Now()
|
||||||
|
if !ctrl.step(dt) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case b := <-kbdC:
|
||||||
|
switch b {
|
||||||
|
case 'p':
|
||||||
|
pumpdownButton = time.Millisecond * 1000
|
||||||
|
case 'v':
|
||||||
|
ventButton = time.Millisecond * 1000
|
||||||
|
case 'q':
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.pressPumpdown(pumpdownButton > 0)
|
||||||
|
if pumpdownButton > 0 {
|
||||||
|
pumpdownButton -= dt
|
||||||
|
}
|
||||||
|
ctrl.pressVent(ventButton > 0)
|
||||||
|
if ventButton > 0 {
|
||||||
|
ventButton -= dt
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(lastPrint) > time.Millisecond*10 {
|
||||||
|
lastPrint = time.Now()
|
||||||
|
fmt.Print("\033[1;1H")
|
||||||
|
fmt.Printf("Controls: [v] vent, [p] pumpdown, [q] quit\r\n")
|
||||||
|
fmt.Printf("PC %04x\r\n", ctrl.lastIOPC)
|
||||||
|
fmt.Printf("%s\r\n", ctrl.getIndcators().String())
|
||||||
|
fmt.Printf("%s %s\r\n", ctrl.m1.status("M1"), ctrl.m2.status("M2"))
|
||||||
|
fmt.Printf("%s %s\r\n", ctrl.m1.barrier.status(), ctrl.m2.barrier.status())
|
||||||
|
fmt.Print("\r\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
modules/ph00524/emu/scope.go
Normal file
118
modules/ph00524/emu/scope.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// barrier represents a multi-position light barrier attached to a motor.
|
||||||
|
type barrier struct {
|
||||||
|
// npos is the number of the positions of the light barrier.
|
||||||
|
npos uint
|
||||||
|
// cur is the current position of the motor.
|
||||||
|
cur float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// pins returns an npos-length array representing the interruption state of the
|
||||||
|
// barrier given cur. An interrupted light barrier is represented by a true
|
||||||
|
// value, an uninterrupted one with a false value.
|
||||||
|
//
|
||||||
|
// Every light barrier position is half a unit away from eachother, with the
|
||||||
|
// first light barrier at unit 1.
|
||||||
|
func (b *barrier) pins() []bool {
|
||||||
|
res := make([]bool, b.npos)
|
||||||
|
for i := uint(0); i < b.npos; i++ {
|
||||||
|
pos := float64(i) * float64(1.0)
|
||||||
|
pos += 1.0
|
||||||
|
if b.cur*2.0 >= pos {
|
||||||
|
res[i] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// status returns a human-readable status of the barrier like '||..' for a
|
||||||
|
// 4-position light barrier with the first two barrier interrupted.
|
||||||
|
func (b *barrier) status() string {
|
||||||
|
res := ""
|
||||||
|
for _, v := range b.pins() {
|
||||||
|
if v {
|
||||||
|
res += "|"
|
||||||
|
} else {
|
||||||
|
res += "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// motor represents an electrical motor which drives a linear actuator at 1 unit per second.
|
||||||
|
type motor struct {
|
||||||
|
// en is true if the motor is enabled.
|
||||||
|
en bool
|
||||||
|
// dir is used to select the direction of the motor. If true, the motor
|
||||||
|
// actuator moves towards positive values.
|
||||||
|
dir bool
|
||||||
|
// pos is the current position of the motor actuator.
|
||||||
|
pos float64
|
||||||
|
|
||||||
|
// barrier attached to this motor, will be automatically updated when the
|
||||||
|
// motor simulation runs.
|
||||||
|
barrier *barrier
|
||||||
|
}
|
||||||
|
|
||||||
|
// status returns a human-readable staus of the motor like '<-ID ' indicating
|
||||||
|
// whether the motor is moving, and if so, in which direction.
|
||||||
|
func (s *motor) status(id string) string {
|
||||||
|
res := ""
|
||||||
|
if s.en {
|
||||||
|
if s.dir {
|
||||||
|
res += fmt.Sprintf(" %s->", id)
|
||||||
|
} else {
|
||||||
|
res += fmt.Sprintf("<-%s ", id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res += fmt.Sprintf(" %s ", id)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// sim runs a simulation step of the motor given a delta time.
|
||||||
|
func (s *motor) sim(dt time.Duration) {
|
||||||
|
if !s.en {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d := dt.Seconds()
|
||||||
|
if s.dir {
|
||||||
|
s.pos += d
|
||||||
|
} else {
|
||||||
|
s.pos -= d
|
||||||
|
}
|
||||||
|
s.barrier.cur = s.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
type indicators struct {
|
||||||
|
indEvac bool
|
||||||
|
indHTReadyN bool
|
||||||
|
indPumpDown bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i indicators) String() string {
|
||||||
|
res := ""
|
||||||
|
if i.indEvac {
|
||||||
|
res += "EVAC "
|
||||||
|
} else {
|
||||||
|
res += "---- "
|
||||||
|
}
|
||||||
|
if !i.indHTReadyN {
|
||||||
|
res += "HT READY "
|
||||||
|
} else {
|
||||||
|
res += "-------- "
|
||||||
|
}
|
||||||
|
if i.indPumpDown {
|
||||||
|
res += "PUMP DOWN "
|
||||||
|
} else {
|
||||||
|
res += "VENT "
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
Loading…
Reference in a new issue