Separate read and write soft switches. Keyboard works now in Apple2e. Extended char set

This commit is contained in:
Ivan Izaguirre 2019-02-24 23:54:13 +01:00
parent 69cc4d15d5
commit 590dfb5cec
7 changed files with 174 additions and 101 deletions

View File

@ -18,10 +18,17 @@ Those tricks do not work with the Apple2e ROM
*/
type ansiConsoleFrontend struct {
mmu *memoryManager
keyChannel chan uint8
extraLineFeeds chan int
}
func newAnsiConsoleFrontend(mmu *memoryManager) ansiConsoleFrontend {
var fe ansiConsoleFrontend
fe.mmu = mmu
return fe
}
const refreshDelayMs = 100
func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) {
@ -82,6 +89,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
// See "Understand the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
isAltText := fe.mmu.isApple2e && fe.mmu.ioPage.isSoftSwitchExtActive(ioFlagAltChar)
var i, j, h uint8
// Top, middle and botton screen
for i = 0; i < 120; i = i + 40 {
@ -91,7 +99,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
for _, h = range []uint8{0, 128} {
line := ""
for j = i + h; j < i+h+40; j++ {
line += textMemoryByteToString(p.Peek(j))
line += textMemoryByteToString(p.Peek(j), isAltText)
}
fmt.Printf("# %v #\n", line)
}
@ -106,19 +114,34 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
}
}
func textMemoryByteToString(value uint8) string {
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
// See https://en.wikipedia.org/wiki/Apple_II_character_set
// Supports the new lowercase characters in the Apple2e
// Only ascii from 0x20 to 0x5F is visible
// Does not support the new lowercase characters in the Apple2e
topBits := value >> 6
isInverse := topBits == 0
isFlash := topBits == 1
if isFlash && isAltCharSet {
// On the Apple2e with lowercase chars there is not flash mode.
isFlash = false
isInverse = true
}
if isAltCharSet {
value = value & 0x7F
} else {
value = value & 0x3F
}
value = (value & 0x3F)
if value < 0x20 {
value += 0x40
}
if value == 0x7f {
// DEL is full box
value = '_'
}
if isFlash {
if value == ' ' {
// Flashing space in Apple is the full box. It can't be done with ANSI codes
@ -132,6 +155,6 @@ func textMemoryByteToString(value uint8) string {
}
}
func textMemoryByteToStringHex(value uint8) string {
func textMemoryByteToStringHex(value uint8, _ bool) string {
return fmt.Sprintf("%02x ", value)
}

View File

@ -0,0 +1,33 @@
package apple2
import (
"testing"
)
func TestTextMemoryByteToString(t *testing.T) {
charExpectation(t, 0x01, false, "\033[7mA\033[0m")
charExpectation(t, 0x21, false, "\033[7m!\033[0m")
charExpectation(t, 0x41, false, "\033[5mA\033[0m")
charExpectation(t, 0x61, false, "\033[5m!\033[0m")
charExpectation(t, 0x81, false, "A")
charExpectation(t, 0xa1, false, "!")
charExpectation(t, 0xc1, false, "A")
charExpectation(t, 0xe1, false, "!")
charExpectation(t, 0x01, true, "\033[7mA\033[0m")
charExpectation(t, 0x21, true, "\033[7m!\033[0m")
charExpectation(t, 0x41, true, "\033[7mA\033[0m")
charExpectation(t, 0x61, true, "\033[7ma\033[0m")
charExpectation(t, 0x81, true, "A")
charExpectation(t, 0xa1, true, "!")
charExpectation(t, 0xc1, true, "A")
charExpectation(t, 0xe1, true, "a")
}
func charExpectation(t *testing.T, arg uint8, alt bool, expect string) {
s := textMemoryByteToString(arg, alt)
if s != expect {
t.Errorf("For 0x%02x:%v, got %v, expected %v", arg, alt, s, expect)
}
}

View File

@ -9,7 +9,7 @@ func Run(romFile string, log bool) {
var s core6502.State
s.Mem = mmu
var fe ansiConsoleFrontend
fe := newAnsiConsoleFrontend(mmu)
mmu.ioPage.setKeyboardProvider(&fe)
go fe.textModeGoRoutine(mmu.textPages1)

View File

@ -5,13 +5,15 @@ import (
)
type ioC0Page struct {
softSwitches [128]softSwitch
softSwitchesR [128]softSwitchR
softSwitchesW [128]softSwitchW
softSwitchesData [128]uint8
keyboard keyboardProvider
mmu *memoryManager
}
type softSwitch func(io *ioC0Page, isWrite bool, value uint8) uint8
type softSwitchR func(io *ioC0Page) uint8
type softSwitchW func(io *ioC0Page, value uint8)
type keyboardProvider interface {
getKey() (key uint8, ok bool)
@ -37,6 +39,27 @@ func newIoC0Page(mmu *memoryManager) *ioC0Page {
return &io
}
func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) {
p.addSoftSwitchR(address, ss)
p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) {
ss(p)
})
}
func (p *ioC0Page) addSoftSwitchR(address uint8, ss softSwitchR) {
if p.softSwitchesR[address] != nil {
fmt.Printf("Addresss 0x0c%02x is already assigned for read", address)
}
p.softSwitchesR[address] = ss
}
func (p *ioC0Page) addSoftSwitchW(address uint8, ss softSwitchW) {
if p.softSwitchesW[address] != nil {
fmt.Printf("Addresss 0x0c%02x is already assigned for write", address)
}
p.softSwitchesW[address] = ss
}
func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool {
return (p.softSwitchesData[ioFlag] & ssOn) == ssOn
}
@ -47,25 +70,19 @@ func (p *ioC0Page) setKeyboardProvider(kb keyboardProvider) {
func (p *ioC0Page) Peek(address uint8) uint8 {
//fmt.Printf("Peek on $C0%02x ", address)
return p.access(address, false, 0)
ss := p.softSwitchesR[address]
if ss == nil {
panic(fmt.Sprintf("Unknown softswitch on read to 0xC0%02x", address))
}
return ss(p)
}
func (p *ioC0Page) Poke(address uint8, value uint8) {
//fmt.Printf("Poke on $C0%02x with %02x ", address, value)
p.access(address, true, value)
}
func (p *ioC0Page) access(address uint8, isWrite bool, value uint8) uint8 {
// The second half of the pages is reserved for slots
if address >= 0x90 {
// TODO reserved slots data
return 0
}
ss := p.softSwitches[address]
ss := p.softSwitchesW[address]
if ss == nil {
panic(fmt.Sprintf("Unknown softswitch 0xC0%02x", address))
panic(fmt.Sprintf("Unknown softswitch on write to 0xC0%02x", address))
}
return ss(p, isWrite, value)
ss(p, value)
}

View File

@ -23,64 +23,66 @@ const (
)
func addApple2SoftSwitches(io *ioC0Page) {
ss := &io.softSwitches
ss[0x00] = getKeySoftSwitch // Keyboard
ss[0x10] = strobeKeyboardSoftSwitch // Keyboard Strobe
ss[0x20] = notImplementedSoftSwitch // Cassette Output
ss[0x30] = notImplementedSoftSwitch // Speaker
ss[0x40] = notImplementedSoftSwitch // Game connector Strobe
io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard
io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe
io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output
io.addSoftSwitchR(0x30, notImplementedSoftSwitchR) // Speaker
io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe
// Note: Some sources indicate that all these cover 16 positions
// for read and write. But the Apple2e take over some of them, with
// the prevention on acting only on writes.
ss[0x50] = getSoftSwitch(ioFlagGraphics, false)
ss[0x51] = getSoftSwitch(ioFlagGraphics, true)
ss[0x52] = getSoftSwitch(ioFlagMixed, false)
ss[0x53] = getSoftSwitch(ioFlagMixed, true)
ss[0x54] = getSoftSwitch(ioFlagSecondPage, false)
ss[0x55] = getSoftSwitch(ioFlagSecondPage, true)
ss[0x56] = getSoftSwitch(ioFlagHiRes, false)
ss[0x57] = getSoftSwitch(ioFlagHiRes, true)
ss[0x58] = getSoftSwitch(ioFlagAnnunciator0, false)
ss[0x59] = getSoftSwitch(ioFlagAnnunciator0, true)
ss[0x5a] = getSoftSwitch(ioFlagAnnunciator1, false)
ss[0x5b] = getSoftSwitch(ioFlagAnnunciator1, true)
ss[0x5c] = getSoftSwitch(ioFlagAnnunciator2, false)
ss[0x5d] = getSoftSwitch(ioFlagAnnunciator2, true)
ss[0x5e] = getSoftSwitch(ioFlagAnnunciator3, false)
ss[0x5f] = getSoftSwitch(ioFlagAnnunciator3, true)
io.addSoftSwitchRW(0x50, getSoftSwitch(ioFlagGraphics, false))
io.addSoftSwitchRW(0x51, getSoftSwitch(ioFlagGraphics, true))
io.addSoftSwitchRW(0x52, getSoftSwitch(ioFlagMixed, false))
io.addSoftSwitchRW(0x53, getSoftSwitch(ioFlagMixed, true))
io.addSoftSwitchRW(0x54, getSoftSwitch(ioFlagSecondPage, false))
io.addSoftSwitchRW(0x55, getSoftSwitch(ioFlagSecondPage, true))
io.addSoftSwitchRW(0x56, getSoftSwitch(ioFlagHiRes, false))
io.addSoftSwitchRW(0x57, getSoftSwitch(ioFlagHiRes, true))
io.addSoftSwitchRW(0x58, getSoftSwitch(ioFlagAnnunciator0, false))
io.addSoftSwitchRW(0x59, getSoftSwitch(ioFlagAnnunciator0, true))
io.addSoftSwitchRW(0x5a, getSoftSwitch(ioFlagAnnunciator1, false))
io.addSoftSwitchRW(0x5b, getSoftSwitch(ioFlagAnnunciator1, true))
io.addSoftSwitchRW(0x5c, getSoftSwitch(ioFlagAnnunciator2, false))
io.addSoftSwitchRW(0x5d, getSoftSwitch(ioFlagAnnunciator2, true))
io.addSoftSwitchRW(0x5e, getSoftSwitch(ioFlagAnnunciator3, false))
io.addSoftSwitchRW(0x5f, getSoftSwitch(ioFlagAnnunciator3, true))
ss[0x60] = notImplementedSoftSwitch // Cassetter Input
ss[0x61] = getStatusSoftSwitch(ioFlagButton0)
ss[0x62] = getStatusSoftSwitch(ioFlagButton1)
ss[0x63] = getStatusSoftSwitch(ioFlagButton2)
ss[0x64] = getStatusSoftSwitch(ioDataPaddle0)
ss[0x65] = getStatusSoftSwitch(ioDataPaddle1)
ss[0x66] = getStatusSoftSwitch(ioDataPaddle2)
ss[0x67] = getStatusSoftSwitch(ioDataPaddle3)
ss[0x68] = ss[0x60]
ss[0x69] = ss[0x61]
ss[0x6A] = ss[0x62]
ss[0x6B] = ss[0x63]
ss[0x6C] = ss[0x64]
ss[0x6D] = ss[0x65]
ss[0x6E] = ss[0x66]
ss[0x6F] = ss[0x67]
ss[0x70] = notImplementedSoftSwitch // Game controllers reset
io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input
io.addSoftSwitchR(0x61, getStatusSoftSwitch(ioFlagButton0))
io.addSoftSwitchR(0x62, getStatusSoftSwitch(ioFlagButton1))
io.addSoftSwitchR(0x63, getStatusSoftSwitch(ioFlagButton2))
io.addSoftSwitchR(0x64, getStatusSoftSwitch(ioDataPaddle0))
io.addSoftSwitchR(0x65, getStatusSoftSwitch(ioDataPaddle1))
io.addSoftSwitchR(0x66, getStatusSoftSwitch(ioDataPaddle2))
io.addSoftSwitchR(0x67, getStatusSoftSwitch(ioDataPaddle3))
// The previous 8 softswitches are repeated
io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input
io.addSoftSwitchR(0x69, getStatusSoftSwitch(ioFlagButton0))
io.addSoftSwitchR(0x6A, getStatusSoftSwitch(ioFlagButton1))
io.addSoftSwitchR(0x6B, getStatusSoftSwitch(ioFlagButton2))
io.addSoftSwitchR(0x6C, getStatusSoftSwitch(ioDataPaddle0))
io.addSoftSwitchR(0x6D, getStatusSoftSwitch(ioDataPaddle1))
io.addSoftSwitchR(0x6E, getStatusSoftSwitch(ioDataPaddle2))
io.addSoftSwitchR(0x6F, getStatusSoftSwitch(ioDataPaddle3))
io.addSoftSwitchR(0x70, notImplementedSoftSwitchR) // Game controllers reset
}
func notImplementedSoftSwitch(*ioC0Page, bool, uint8) uint8 {
func notImplementedSoftSwitchR(*ioC0Page) uint8 {
return 0
}
func getStatusSoftSwitch(ioFlag uint8) softSwitch {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
func getStatusSoftSwitch(ioFlag uint8) softSwitchR {
return func(io *ioC0Page) uint8 {
return io.softSwitchesData[ioFlag]
}
}
func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
func getSoftSwitch(ioFlag uint8, isSet bool) softSwitchR {
return func(io *ioC0Page) uint8 {
if isSet {
io.softSwitchesData[ioFlag] = ssOn
} else {
@ -90,18 +92,18 @@ func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch {
}
}
func getKeySoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 {
strobed := (p.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
if strobed && p.keyboard != nil {
if key, ok := p.keyboard.getKey(); ok {
p.softSwitchesData[ioDataKeyboard] = key + (1 << 7)
func getKeySoftSwitch(io *ioC0Page) uint8 {
strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
if strobed && io.keyboard != nil {
if key, ok := io.keyboard.getKey(); ok {
io.softSwitchesData[ioDataKeyboard] = key + (1 << 7)
}
}
return p.softSwitchesData[ioDataKeyboard]
return io.softSwitchesData[ioDataKeyboard]
}
func strobeKeyboardSoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 {
result := p.softSwitchesData[ioDataKeyboard]
p.softSwitchesData[ioDataKeyboard] &^= 1 << 7
func strobeKeyboardSoftSwitch(io *ioC0Page) uint8 {
result := io.softSwitchesData[ioDataKeyboard]
io.softSwitchesData[ioDataKeyboard] &^= 1 << 7
return result
}

View File

@ -4,45 +4,43 @@ const (
ioFlagIntCxRom uint8 = 0x15
ioFlagSlotC3Rom uint8 = 0x17
ioFlag80Store uint8 = 0x18
ioFlagAltChar uint8 = 0x19
ioFlag80Col uint8 = 0x1F
)
func addApple2ESoftSwitches(io *ioC0Page) {
ss := &io.softSwitches
ss[0x00] = getSoftSwitchExt(ioFlag80Store, ssOff, nil)
ss[0x01] = getSoftSwitchExt(ioFlag80Store, ssOn, nil)
ss[0x06] = getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff)
ss[0x07] = getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn)
ss[0x0A] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff)
ss[0x0B] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn)
ss[0x0C] = getSoftSwitchExt(ioFlag80Col, ssOff, nil)
ss[0x0D] = getSoftSwitchExt(ioFlag80Col, ssOn, nil)
io.addSoftSwitchW(0x00, getSoftSwitchExt(ioFlag80Store, ssOff, nil))
io.addSoftSwitchW(0x01, getSoftSwitchExt(ioFlag80Store, ssOn, nil))
io.addSoftSwitchW(0x06, getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff))
io.addSoftSwitchW(0x07, getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn))
io.addSoftSwitchW(0x0A, getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff))
io.addSoftSwitchW(0x0B, getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn))
io.addSoftSwitchW(0x0C, getSoftSwitchExt(ioFlag80Col, ssOff, nil))
io.addSoftSwitchW(0x0D, getSoftSwitchExt(ioFlag80Col, ssOn, nil))
io.addSoftSwitchW(0x0E, getSoftSwitchExt(ioFlagAltChar, ssOff, nil))
io.addSoftSwitchW(0x0F, getSoftSwitchExt(ioFlagAltChar, ssOn, nil))
io.softSwitchesData[ioFlagAltChar] = ssOn // Not sure about this.
ss[0x15] = getStatusSoftSwitch(ioFlagIntCxRom)
ss[0x17] = getStatusSoftSwitch(ioFlagSlotC3Rom)
ss[0x18] = getStatusSoftSwitch(ioFlag80Store)
ss[0x1C] = getStatusSoftSwitch(ioFlagSecondPage)
ss[0x1F] = getStatusSoftSwitch(ioFlag80Col)
io.addSoftSwitchR(0x15, getStatusSoftSwitch(ioFlagIntCxRom))
io.addSoftSwitchR(0x17, getStatusSoftSwitch(ioFlagSlotC3Rom))
io.addSoftSwitchR(0x18, getStatusSoftSwitch(ioFlag80Store))
io.addSoftSwitchR(0x1C, getStatusSoftSwitch(ioFlagSecondPage))
io.addSoftSwitchR(0x1F, getStatusSoftSwitch(ioFlag80Col))
}
type softSwitchExtAction func(io *ioC0Page)
func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitch {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
//fmt.Printf("Softswitch 0x%02x %v %v\n", ioFlag, isWrite, dstValue)
if !isWrite {
return 0 // New Apple2e softswitches ignore reads
}
func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitchW {
return func(io *ioC0Page, _ uint8) {
currentValue := io.softSwitchesData[ioFlag]
if currentValue == dstValue {
return 0 // Already switched, ignore
return // Already switched, ignore
}
if action != nil {
action(io)
}
io.softSwitchesData[ioFlag] = value
return 0
io.softSwitchesData[ioFlag] = dstValue
}
}

View File

@ -4,8 +4,8 @@ import "go6502/apple2"
func main() {
//romFile := "apple2/romdumps/Apple2.rom"
romFile := "apple2/romdumps/Apple2_Plus.rom"
//romFile := "apple2/romdumps/Apple2e.rom"
//romFile := "apple2/romdumps/Apple2_Plus.rom"
romFile := "apple2/romdumps/Apple2e.rom"
log := false
apple2.Run(romFile, log)