diff --git a/cmd/test-apple-iie-boot.go b/cmd/test-apple-iie-boot.go index bd1627f..4b4329c 100644 --- a/cmd/test-apple-iie-boot.go +++ b/cmd/test-apple-iie-boot.go @@ -8,6 +8,7 @@ import ( "github.com/hajimehoshi/ebiten/ebitenutil" "mos6502go/cpu" + "mos6502go/keyboard" "mos6502go/mmu" "mos6502go/vid" ) @@ -49,6 +50,7 @@ func checkResetKeys() { } func update(screen *ebiten.Image) error { + keyboard.Poll() checkResetKeys() cpu.Run(&cpuState, *showInstructions, nil, *disableBell, 1024000/60) @@ -67,6 +69,9 @@ func main() { cpuState.Memory = memory cpuState.PageTable = &memory.PageTable cpuState.Init() + + keyboard.Init() + reset() var err error diff --git a/cmd/test-cpu.go b/cmd/test-cpu.go index 0c4e61a..52b1939 100644 --- a/cmd/test-cpu.go +++ b/cmd/test-cpu.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "mos6502go/cpu" + "mos6502go/keyboard" "mos6502go/mmu" "mos6502go/utils" ) @@ -88,6 +89,8 @@ func main() { breakAddress = &foo } + keyboard.Init() + cpu.Run(&s, *showInstructions, breakAddress, false, 0) fmt.Printf("Finished running %s\n\n", rom) } diff --git a/cpu/cpu.go b/cpu/cpu.go index df4b881..54b1901 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -2,6 +2,7 @@ package cpu import ( "fmt" + "mos6502go/keyboard" "mos6502go/mmu" "os" ) @@ -131,6 +132,23 @@ func pop16(s *State) uint16 { func readMemory(s *State, address uint16) uint8 { if (address >= 0xc000) && (address < 0xc100) { + + if (address == mmu.KEYBOARD) || (address == mmu.STROBE) { + keyBoardData, strobe := keyboard.Read() + if address == mmu.KEYBOARD { + return keyBoardData + } else { + keyboard.ResetStrobe() + return strobe + } + } else if address == mmu.RDCXROM { + // using external slot ROM not implemented + return 0 + } else if address == mmu.RD80VID { + // using 80-column display mode not implemented + return 0 + } + fmt.Printf("TODO read %04x\n", address) return 0 } @@ -166,7 +184,9 @@ func writeMemory(s *State, address uint16, value uint8) { } if address >= 0xc000 { - if address == mmu.CLRCXROM { + if address == mmu.STROBE { + keyboard.ResetStrobe() + } else if address == mmu.CLRCXROM { mmu.MapFirstHalfOfIO(s.Memory) } else if address == mmu.SETCXROM { mmu.MapSecondHalfOfIO(s.Memory) diff --git a/keyboard/keyboard.go b/keyboard/keyboard.go new file mode 100644 index 0000000..6dcdb54 --- /dev/null +++ b/keyboard/keyboard.go @@ -0,0 +1,254 @@ +package keyboard + +import ( + "github.com/hajimehoshi/ebiten" +) + +var ebitenAsciiMap map[ebiten.Key]uint8 +var shiftMap map[uint8]uint8 +var controlMap map[uint8]uint8 + +var keyBoardData uint8 +var strobe uint8 +var previousKeysPressed map[uint8]bool + +func Init() { + keyBoardData = 0 + strobe = 0 + + ebitenAsciiMap = make(map[ebiten.Key]uint8) + shiftMap = make(map[uint8]uint8) + controlMap = make(map[uint8]uint8) + previousKeysPressed = make(map[uint8]bool) + + ebitenAsciiMap[ebiten.KeyLeft] = 8 + ebitenAsciiMap[ebiten.KeyTab] = 9 + ebitenAsciiMap[ebiten.KeyDown] = 10 + ebitenAsciiMap[ebiten.KeyUp] = 11 + ebitenAsciiMap[ebiten.KeyEnter] = 13 + ebitenAsciiMap[ebiten.KeyRight] = 21 + ebitenAsciiMap[ebiten.KeyEscape] = 27 + ebitenAsciiMap[ebiten.KeyDelete] = 127 + + ebitenAsciiMap[ebiten.Key0] = '0' + ebitenAsciiMap[ebiten.Key1] = '1' + ebitenAsciiMap[ebiten.Key2] = '2' + ebitenAsciiMap[ebiten.Key3] = '3' + ebitenAsciiMap[ebiten.Key4] = '4' + ebitenAsciiMap[ebiten.Key5] = '5' + ebitenAsciiMap[ebiten.Key6] = '6' + ebitenAsciiMap[ebiten.Key7] = '7' + ebitenAsciiMap[ebiten.Key8] = '8' + ebitenAsciiMap[ebiten.Key9] = '9' + ebitenAsciiMap[ebiten.KeyA] = 'a' + ebitenAsciiMap[ebiten.KeyB] = 'b' + ebitenAsciiMap[ebiten.KeyC] = 'c' + ebitenAsciiMap[ebiten.KeyD] = 'd' + ebitenAsciiMap[ebiten.KeyE] = 'e' + ebitenAsciiMap[ebiten.KeyF] = 'f' + ebitenAsciiMap[ebiten.KeyG] = 'g' + ebitenAsciiMap[ebiten.KeyH] = 'h' + ebitenAsciiMap[ebiten.KeyI] = 'i' + ebitenAsciiMap[ebiten.KeyJ] = 'j' + ebitenAsciiMap[ebiten.KeyK] = 'k' + ebitenAsciiMap[ebiten.KeyL] = 'l' + ebitenAsciiMap[ebiten.KeyM] = 'm' + ebitenAsciiMap[ebiten.KeyN] = 'n' + ebitenAsciiMap[ebiten.KeyO] = 'o' + ebitenAsciiMap[ebiten.KeyP] = 'p' + ebitenAsciiMap[ebiten.KeyQ] = 'q' + ebitenAsciiMap[ebiten.KeyR] = 'r' + ebitenAsciiMap[ebiten.KeyS] = 's' + ebitenAsciiMap[ebiten.KeyT] = 't' + ebitenAsciiMap[ebiten.KeyU] = 'u' + ebitenAsciiMap[ebiten.KeyV] = 'v' + ebitenAsciiMap[ebiten.KeyW] = 'w' + ebitenAsciiMap[ebiten.KeyX] = 'x' + ebitenAsciiMap[ebiten.KeyY] = 'y' + ebitenAsciiMap[ebiten.KeyZ] = 'z' + ebitenAsciiMap[ebiten.KeyApostrophe] = '\'' + ebitenAsciiMap[ebiten.KeyBackslash] = '\\' + ebitenAsciiMap[ebiten.KeyComma] = ',' + ebitenAsciiMap[ebiten.KeyEqual] = '=' + ebitenAsciiMap[ebiten.KeyGraveAccent] = '`' + ebitenAsciiMap[ebiten.KeyLeftBracket] = '[' + ebitenAsciiMap[ebiten.KeyMinus] = '-' + ebitenAsciiMap[ebiten.KeyPeriod] = '.' + ebitenAsciiMap[ebiten.KeyRightBracket] = ']' + ebitenAsciiMap[ebiten.KeySemicolon] = ';' + ebitenAsciiMap[ebiten.KeySlash] = '/' + ebitenAsciiMap[ebiten.KeySpace] = ' ' + + shiftMap['1'] = '!' + shiftMap['2'] = '@' + shiftMap['3'] = '#' + shiftMap['4'] = '$' + shiftMap['5'] = '%' + shiftMap['6'] = '^' + shiftMap['7'] = '&' + shiftMap['8'] = '*' + shiftMap['9'] = '(' + shiftMap['0'] = ')' + shiftMap['-'] = '_' + shiftMap['='] = '+' + shiftMap['a'] = 'A' + shiftMap['b'] = 'B' + shiftMap['c'] = 'C' + shiftMap['d'] = 'D' + shiftMap['e'] = 'E' + shiftMap['f'] = 'F' + shiftMap['g'] = 'G' + shiftMap['h'] = 'H' + shiftMap['i'] = 'I' + shiftMap['j'] = 'J' + shiftMap['k'] = 'K' + shiftMap['l'] = 'L' + shiftMap['m'] = 'M' + shiftMap['n'] = 'N' + shiftMap['o'] = 'O' + shiftMap['p'] = 'P' + shiftMap['q'] = 'Q' + shiftMap['r'] = 'R' + shiftMap['s'] = 'S' + shiftMap['t'] = 'T' + shiftMap['u'] = 'U' + shiftMap['v'] = 'V' + shiftMap['w'] = 'W' + shiftMap['x'] = 'X' + shiftMap['y'] = 'Y' + shiftMap['z'] = 'Z' + shiftMap[','] = '<' + shiftMap['.'] = '>' + shiftMap['/'] = '?' + shiftMap['`'] = '~' + shiftMap['['] = '{' + shiftMap[']'] = '}' + shiftMap[';'] = ':' + shiftMap['\''] = '"' + shiftMap['\\'] = '|' + shiftMap[' '] = ' ' + + controlMap['A'] = 'A' - 0x40 + controlMap['B'] = 'B' - 0x40 + controlMap['C'] = 'C' - 0x40 + controlMap['D'] = 'D' - 0x40 + controlMap['E'] = 'E' - 0x40 + controlMap['F'] = 'F' - 0x40 + controlMap['G'] = 'G' - 0x40 + controlMap['H'] = 'H' - 0x40 + controlMap['I'] = 'I' - 0x40 + controlMap['J'] = 'J' - 0x40 + controlMap['K'] = 'K' - 0x40 + controlMap['L'] = 'L' - 0x40 + controlMap['M'] = 'M' - 0x40 + controlMap['N'] = 'N' - 0x40 + controlMap['O'] = 'O' - 0x40 + controlMap['P'] = 'P' - 0x40 + controlMap['Q'] = 'Q' - 0x40 + controlMap['R'] = 'R' - 0x40 + controlMap['S'] = 'S' - 0x40 + controlMap['T'] = 'T' - 0x40 + controlMap['U'] = 'U' - 0x40 + controlMap['V'] = 'V' - 0x40 + controlMap['W'] = 'W' - 0x40 + controlMap['X'] = 'X' - 0x40 + controlMap['Y'] = 'Y' - 0x40 + controlMap['Z'] = 'Z' - 0x40 + controlMap[']'] = 0x5d + controlMap['`'] = 0x60 + + controlMap['a'] = 'a' - 0x60 + controlMap['b'] = 'b' - 0x60 + controlMap['c'] = 'c' - 0x60 + controlMap['d'] = 'd' - 0x60 + controlMap['e'] = 'e' - 0x60 + controlMap['f'] = 'f' - 0x60 + controlMap['g'] = 'g' - 0x60 + controlMap['h'] = 'h' - 0x60 + controlMap['i'] = 'i' - 0x60 + controlMap['j'] = 'j' - 0x60 + controlMap['k'] = 'k' - 0x60 + controlMap['l'] = 'l' - 0x60 + controlMap['m'] = 'm' - 0x60 + controlMap['n'] = 'n' - 0x60 + controlMap['o'] = 'o' - 0x60 + controlMap['p'] = 'p' - 0x60 + controlMap['q'] = 'q' - 0x60 + controlMap['r'] = 'r' - 0x60 + controlMap['s'] = 's' - 0x60 + controlMap['t'] = 't' - 0x60 + controlMap['u'] = 'u' - 0x60 + controlMap['v'] = 'v' - 0x60 + controlMap['w'] = 'w' - 0x60 + controlMap['x'] = 'x' - 0x60 + controlMap['y'] = 'y' - 0x60 + controlMap['z'] = 'z' - 0x60 + controlMap['}'] = 0x5d + controlMap['~'] = 0x60 +} + +// Poll queries ebiten's keyboard state and transforms that into apple //e +// values in $c000 and $c010 +func Poll() { + allKeysPressed := make(map[uint8]bool) + newKeysPressed := make(map[uint8]bool) + + for k, v := range ebitenAsciiMap { + if ebiten.IsKeyPressed(k) { + allKeysPressed[v] = true + + _, present := previousKeysPressed[v] + if !present { + newKeysPressed[v] = true + } + } + } + + previousKeysPressed = allKeysPressed + + if len(allKeysPressed) == 0 { + // No keys are pressed, clear the strobe and return + strobe = keyBoardData & 0x7f + return + } else if len(newKeysPressed) == 0 { + // No new keys pressed, do nothing + return + } else if len(newKeysPressed) > 1 { + // More than one new keys pressed, do nothing + return + } + + // Implicit else, one new key has been pressed + keys := []uint8{} + for k := range newKeysPressed { + keys = append(keys, k) + } + key := keys[0] + + if ebiten.IsKeyPressed(ebiten.KeyShift) { + shiftedKey, present := shiftMap[key] + if present { + key = shiftedKey + } + } + + if ebiten.IsKeyPressed(ebiten.KeyControl) { + controlKey, present := controlMap[key] + if present { + key = controlKey + } + } + + keyBoardData = key | 0x80 + strobe = keyBoardData + + return +} + +func Read() (uint8, uint8) { + return keyBoardData, strobe +} + +func ResetStrobe() { + keyBoardData &= 0x7f +} diff --git a/mmu/mmu.go b/mmu/mmu.go index 1bd317f..d88ebc4 100644 --- a/mmu/mmu.go +++ b/mmu/mmu.go @@ -30,6 +30,23 @@ const ( CLRALTCH = 0xC00E // use alternate character set ROM SETALTCH = 0xC00F STROBE = 0xC010 // strobe (unlatch) keyboard data + + RDLCBNK2 = 0xC011 // reading from LC bank $Dx 2 + RDLCRAM = 0xC012 // reading from LC RAM + RDRAMRD = 0xC013 // reading from auxilliary 48K + RDRAMWR = 0xC014 // writing to auxilliary 48K + RDCXROM = 0xC015 // using external slot ROM + RDAUXZP = 0xC016 // using auxilliary ZP, stack, & LC + RDC3ROM = 0xC017 // using external slot C3 ROM + RD80COL = 0xC018 // using 80-column memory mapping + RDVBLBAR = 0xC019 // not VBL (VBL signal low) + RDTEXT = 0xC01A // using text mode + RDMIXED = 0xC01B // using mixed mode + RDPAGE2 = 0xC01C // using text/graphics page2 + RDHIRES = 0xC01D // using Hi-res graphics mode + RDALTCH = 0xC01E // using alternate character set ROM + RD80VID = 0xC01F // using 80-column display mode + ) type PhysicalMemory struct {