mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-08-05 17:25:23 +00:00
Videx Ultraterm support
This commit is contained in:
@@ -32,6 +32,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||
- Apple //e 80 columns card with 64Kb extra RAM and optional RGB modes
|
||||
- No Slot Clock based on the DS1216
|
||||
- Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only)
|
||||
- Videx Ultraterm 80 to 160 column card wuth integrated Video Switch
|
||||
- SwyftCard (Apple //e only)
|
||||
- Brain Board
|
||||
- Brain Board II
|
||||
@@ -52,7 +53,9 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||
|
||||
- Graphic modes:
|
||||
- Text 40 columns
|
||||
- Text 80 columns (Apple //e and Videx VideoTerm)
|
||||
- Text 80 columns Apple //e
|
||||
- Text 80 columns Videx VideoTerm
|
||||
- Text up to 160 columns and 48 lines Videx UltraTerm
|
||||
- Low-Resolution graphics
|
||||
- Double-Width Low-Resolution graphics (Apple //e only)
|
||||
- High-Resolution graphics
|
||||
@@ -237,6 +240,7 @@ The available pre-configured models are:
|
||||
desktop: Apple II DeskTop
|
||||
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
||||
swyft: swyft
|
||||
ultraterm: Apple ][+ with Videx Ultraterm demo
|
||||
|
||||
The available cards are:
|
||||
brainboard: Firmware card. It has two ROM banks
|
||||
@@ -259,7 +263,8 @@ The available cards are:
|
||||
softswitchlogger: Card to log softswitch accesses
|
||||
swyftcard: Card with the ROM needed to run the Swyftcard word processing system
|
||||
thunderclock: Clock card
|
||||
videx: Videx compatible 80 columns card
|
||||
videx: Videx Videoterm compatible 80 columns card
|
||||
videxultraterm: Videx Utraterm compatible 80 columns card
|
||||
vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode
|
||||
z80softcard: Microsoft Z80 SoftCard to run CP/M
|
||||
|
||||
|
@@ -18,7 +18,7 @@ type Apple2 struct {
|
||||
cards [8]Card
|
||||
tracers []executionTracer
|
||||
|
||||
softVideoSwitch *SoftVideoSwitch
|
||||
softVideoSwitch softVideoSwitch
|
||||
board string
|
||||
isApple2e bool
|
||||
hasLowerCase bool
|
||||
|
@@ -69,9 +69,12 @@ func (at *apple2Tester) getText(textMode testTextModeFunc) string {
|
||||
func (at *apple2Tester) getTextBest() string {
|
||||
videxMaybe := at.a.cards[3]
|
||||
if videxMaybe != nil {
|
||||
if videx, ok := videxMaybe.(*CardVidex); ok {
|
||||
if videx, ok := videxMaybe.(*CardVidexVideoterm); ok {
|
||||
return videx.getText()
|
||||
}
|
||||
if videxUltraterm, ok := videxMaybe.(*CardVidexUltraterm); ok {
|
||||
return videxUltraterm.getText()
|
||||
}
|
||||
}
|
||||
|
||||
videoMode := at.a.video.GetCurrentVideoMode()
|
||||
|
@@ -101,7 +101,7 @@ func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error {
|
||||
if len(data) == 0x400 {
|
||||
// The file has C800 to CBFF for ROM
|
||||
// The 256 bytes in Cx00 are copied from the last page in C800-CBFF
|
||||
// Used on the Videx 80 columns card
|
||||
// Used on the Videx Videoterm 80 columns card
|
||||
c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM")
|
||||
c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM")
|
||||
} else {
|
||||
@@ -141,7 +141,7 @@ func (c *cardBase) assign(a *Apple2, slot int) {
|
||||
}
|
||||
if c.romCxxx != nil {
|
||||
rom := traceMemory(c.romCxxx, c.name, c.traceMemory)
|
||||
a.mmu.setCardROM(slot, c.romCxxx)
|
||||
a.mmu.setCardROM(slot, rom)
|
||||
a.mmu.setCardROMExtra(slot, rom)
|
||||
}
|
||||
}
|
||||
|
@@ -60,7 +60,8 @@ func getCardFactory() map[string]*cardBuilder {
|
||||
cardFactory["smartport"] = newCardSmartPortStorageBuilder()
|
||||
cardFactory["swyftcard"] = newCardSwyftBuilder()
|
||||
cardFactory["thunderclock"] = newCardThunderClockPlusBuilder()
|
||||
cardFactory["videx"] = newCardVidexBuilder()
|
||||
cardFactory["videx"] = newCardVidexVideotermBuilder()
|
||||
cardFactory["videxultraterm"] = newCardVidexUltratermBuilder()
|
||||
cardFactory["vidhd"] = newCardVidHDBuilder()
|
||||
cardFactory["z80softcard"] = newCardZ80SoftCardBuilder()
|
||||
return cardFactory
|
||||
|
@@ -60,10 +60,14 @@ func TestCardsDetected(t *testing.T) {
|
||||
testCardDetectedInternal(t, "2plus", "saturn", "s0", 50_000_000, "SATURN 128K CARD IN SLOT 0")
|
||||
})
|
||||
|
||||
t.Run("test Videx card", func(t *testing.T) {
|
||||
t.Run("test Videx Videoterm card", func(t *testing.T) {
|
||||
testCardDetectedInternal(t, "2plus", "videx", "s3", 50_000_000, "3 38-18-01-82 Videx 80 Column Text Display Card")
|
||||
})
|
||||
|
||||
t.Run("test Videx Ultraterm card", func(t *testing.T) {
|
||||
testCardDetectedInternal(t, "2plus", "videxultraterm", "s3", 50_000_000, "3 38-18-01-87 ? Unknown 80-Column Display Card")
|
||||
})
|
||||
|
||||
t.Run("test Dan 2 SD card", func(t *testing.T) {
|
||||
testCardDetectedInternal(t, "2enh", "dan2sd", "s2", 50_000_000, "2 03-3C-01-9D DAN II Card")
|
||||
})
|
||||
|
383
cardVidexUltraterm.go
Normal file
383
cardVidexUltraterm.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ivanizag/izapple2/component"
|
||||
)
|
||||
|
||||
/*
|
||||
Videx Ultraterm 80 columns card for the Apple II+
|
||||
|
||||
See:
|
||||
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/80%20Column%20Cards/Videx%20UltraTerm/
|
||||
http://www.bitsavers.org/components/motorola/_dataSheets/6845.pdf
|
||||
|
||||
|
||||
Firmware:
|
||||
The firmware to operate your UltraTerm is contained in a 4K-byte 2732A
|
||||
EPROM, U6. The lower half of this IC contains seven versions of the code
|
||||
which appears at $CN00 to $CNFF, one for each slot.
|
||||
There are 2K bytes of address space available for use in the co-resident
|
||||
memory space at $C800. However, the upper 1 K bytes of this space is
|
||||
used by the video refresh memory. For this reason the firmware is split into
|
||||
two banks. These banks are selected with bit seven of the MCP When the
|
||||
second bank of firmware is selected it overlays the Video Refresh Memory
|
||||
(VRM) at addresses from $CC00 to $CFE0. The first bank of the firmware
|
||||
always occupies the region from $C800 to $CBFF.
|
||||
|
||||
Formats (from the firmware listing, appendix F of the manual):
|
||||
0: 80 x 24 non-interlaced (low density chars) (sram512, Apple Video source ??)
|
||||
1: 96 x 24 non-interlaced (low density chars) (sram256, 17 Mhz)
|
||||
2: 160 x 24 non-interlaced (low density chars) (sram256, 28 mhz)
|
||||
3: 80 x 24 interlaced (high density chars) (sram256, 17 Mhz)
|
||||
4: 80 x 32 interlaced (high density chars) (sram256, 17 Mhz)
|
||||
5: 80 x 48 interlaced (low density chars) (sram256, 17 Mhz)
|
||||
6: 160 x 24 interlaced (used for 132 x 24 screen) (high density chars) (sram256, 28 mhz)
|
||||
7: 128 X 32 interlaced (high density chars) (sram256, 28 mhz)
|
||||
*/
|
||||
|
||||
// CardVidex represents a Videx Ultraterm compatible 80 column card
|
||||
type CardVidexUltraterm struct {
|
||||
cardBase
|
||||
mc6845 component.MC6845
|
||||
modeControl uint8
|
||||
videoAttribute uint8
|
||||
cxrom memoryHandler
|
||||
alwaysShow bool
|
||||
sramPage512 uint8 // sram page on 512 kb mode (videoterm emultation)
|
||||
|
||||
sram [0x1000]uint8
|
||||
charGen []uint8
|
||||
}
|
||||
|
||||
func newCardVidexUltratermBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Videx Ultraterm 80 columns Card",
|
||||
description: "Videx Utraterm compatible 80 columns card",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"rom", "ROM file to load", "<internal>/videx_ultraterm_frm_b537.bin"},
|
||||
{"charmap", "Character map file to load", "<internal>/videx_ultraterm_chs_7859.bin"},
|
||||
{"always", "Always show the 80 columns output", "false"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardVidexUltraterm
|
||||
|
||||
// The C800 area has ROM and RAM
|
||||
err := c.loadRomFromResource(paramsGetPath(params, "rom"), cardRomFull)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.cxrom = c.romCxxx
|
||||
c.romCxxx = &c
|
||||
|
||||
err = c.loadCharacterMap(paramsGetPath(params, "charmap"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.alwaysShow = paramsGetBool(params, "always")
|
||||
c.videoAttribute = videxUltratermDefaultVideoAttribute
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) loadCharacterMap(filename string) error {
|
||||
bytes, _, err := LoadResource(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size := len(bytes)
|
||||
if size < 0x1000 {
|
||||
return errors.New("character ROM size not supported for Videx")
|
||||
}
|
||||
c.charGen = bytes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) assign(a *Apple2, slot int) {
|
||||
|
||||
for page := uint8(0); page < 4; page++ {
|
||||
bitsA3A2 := page << 2
|
||||
ssName := fmt.Sprintf("ULTRATERMPAGE%v", page)
|
||||
|
||||
pageCopy := page
|
||||
c.addCardSoftSwitchR(0+bitsA3A2, func() uint8 {
|
||||
c.sramPage512 = pageCopy
|
||||
return c.mc6845.Read(false)
|
||||
}, ssName+"REGR")
|
||||
c.addCardSoftSwitchW(0+bitsA3A2, func(value uint8) {
|
||||
c.mc6845.Write(false, value)
|
||||
}, ssName+"REGW")
|
||||
|
||||
c.addCardSoftSwitchR(1+bitsA3A2, func() uint8 {
|
||||
c.sramPage512 = pageCopy
|
||||
return c.mc6845.Read(true)
|
||||
}, ssName+"VALR")
|
||||
c.addCardSoftSwitchW(1+bitsA3A2, func(value uint8) {
|
||||
c.mc6845.Write(true, value)
|
||||
}, ssName+"VALW")
|
||||
|
||||
c.addCardSoftSwitchR(2+bitsA3A2, func() uint8 {
|
||||
c.sramPage512 = pageCopy
|
||||
return c.modeControl
|
||||
}, ssName+"MODER")
|
||||
c.addCardSoftSwitchW(2+bitsA3A2, func(value uint8) {
|
||||
c.modeControl = value
|
||||
}, ssName+"MODEW")
|
||||
|
||||
c.addCardSoftSwitchR(3+bitsA3A2, func() uint8 {
|
||||
c.sramPage512 = pageCopy
|
||||
return c.videoAttribute
|
||||
}, ssName+"ATTRE")
|
||||
c.addCardSoftSwitchW(3+bitsA3A2, func(value uint8) {
|
||||
c.videoAttribute = value
|
||||
}, ssName+"ATTRW")
|
||||
}
|
||||
|
||||
c.cardBase.assign(a, slot)
|
||||
a.setSoftVideoSwitch(c)
|
||||
}
|
||||
|
||||
/*
|
||||
Bit:
|
||||
|
||||
7 Firmware Page Select
|
||||
6 Video Signal Select
|
||||
1 = UltraTerm
|
||||
5 Clock Frequency
|
||||
1 = 28.7595,0 = 17.430 MHz
|
||||
4 Character Address Format
|
||||
1 = 256-Byte Pages, 0 = 512-Byte Blocks
|
||||
3 Character RAM Address bit 11 (256-byte mode)
|
||||
2 Character RAM Address bit 10 (256-byte mode)
|
||||
1 Character RAM Address bit 9 (256-byte mode)
|
||||
0 Character RAM Address bit 8 (256-by1e mode)
|
||||
*/
|
||||
const (
|
||||
videxUltratermMCPFirmwarePageSelect = uint8(0x80)
|
||||
videxUltratermMCPVideoSignalSelect = uint8(0x40)
|
||||
videxUltratermMCPClockFrequency = uint8(0x20)
|
||||
videxUltratermMCPSRamAdressFormat = uint8(0x10)
|
||||
videxUltratermMCPSramPageMask = uint8(0x0f)
|
||||
)
|
||||
|
||||
const (
|
||||
videxUltratermAttributesHighlight = uint8(1) // Lowlight or Highlight
|
||||
videxUltratermAttributesInverse = uint8(2) // Normal or Inverse
|
||||
videxUltratermAttributesAlternateChar = uint8(4) // Normal or Alternate (low quality) character sets
|
||||
)
|
||||
|
||||
const videxUltratermDefaultVideoAttribute = uint8(0x00 | videxUltratermAttributesInverse<<4) // LowlightNormal and LowlightInverse
|
||||
|
||||
const videxUltratermSramStart = uint16(0xcc00)
|
||||
const videxUltratermSramLegacyMask = uint16(0x01ff)
|
||||
const videxUltratermSramMask = uint16(0x0ff)
|
||||
const videxUltratermSram512Mask = uint16(0x7ff)
|
||||
const videxUltratermSram256Mask = uint16(0xfff)
|
||||
|
||||
func (c *CardVidexUltraterm) sramAddress(address uint16) uint16 {
|
||||
is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0
|
||||
if is512mode {
|
||||
// Legacy or 512 mode
|
||||
return address&videxUltratermSramLegacyMask + uint16(c.sramPage512)*512
|
||||
}
|
||||
|
||||
sramPage256 := c.modeControl & videxUltratermMCPSramPageMask
|
||||
return address&videxUltratermSramMask + uint16(sramPage256)*256
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) peek(address uint16) uint8 {
|
||||
isFirmwarePageSelected := c.modeControl&videxUltratermMCPFirmwarePageSelect != 0
|
||||
if address < videxUltratermSramStart || isFirmwarePageSelected {
|
||||
return c.cxrom.peek(address)
|
||||
}
|
||||
return c.sram[c.sramAddress(address)]
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) poke(address uint16, value uint8) {
|
||||
if address >= videxUltratermSramStart {
|
||||
c.sram[c.sramAddress(address)] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) isSoftSwitchActive() bool {
|
||||
if c.alwaysShow {
|
||||
return true
|
||||
}
|
||||
return c.modeControl&videxUltratermMCPVideoSignalSelect != 0
|
||||
}
|
||||
|
||||
const (
|
||||
videxUltratermCharWidth = uint8(9)
|
||||
)
|
||||
|
||||
func (c *CardVidexUltraterm) colorsPerAttributes(topBit bool, lightColor color.Color) (color.Color, color.Color) {
|
||||
attributes := c.videoAttribute
|
||||
if topBit {
|
||||
attributes >>= 4
|
||||
}
|
||||
|
||||
inverse := attributes&videxUltratermAttributesInverse != 0
|
||||
highlight := attributes&videxUltratermAttributesHighlight != 0
|
||||
|
||||
var clearColor color.Color = color.Black
|
||||
setColor := lightColor
|
||||
|
||||
if !highlight {
|
||||
r, g, b, a := setColor.RGBA()
|
||||
setColor = color.NRGBA64{
|
||||
uint16(r>>1 + r>>2),
|
||||
uint16(g>>1 + g>>2),
|
||||
uint16(b>>1 + b>>2),
|
||||
uint16(a)}
|
||||
}
|
||||
|
||||
if inverse {
|
||||
temp := setColor
|
||||
setColor = clearColor
|
||||
clearColor = temp
|
||||
}
|
||||
|
||||
return clearColor, setColor
|
||||
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) buildImage(light color.Color) *image.RGBA {
|
||||
params := c.mc6845.ImageData()
|
||||
width, height := params.DisplayedWidthHeight(videxUltratermCharWidth)
|
||||
if (width == 0) || (height == 0) {
|
||||
// No image available
|
||||
size := image.Rect(0, 0, 3, 3)
|
||||
img := image.NewRGBA(size)
|
||||
img.Set(1, 1, color.White)
|
||||
return img
|
||||
}
|
||||
ms := time.Now().Nanosecond() / (1000 * 1000) // Host time, used for the cursor blink
|
||||
|
||||
size := image.Rect(0, 0, width, height)
|
||||
img := image.NewRGBA(size)
|
||||
|
||||
upperClearColor, upperSetColor := c.colorsPerAttributes(true, light)
|
||||
lowerClearColor, lowerSetColor := c.colorsPerAttributes(false, light)
|
||||
altChar := c.videoAttribute&videxUltratermAttributesAlternateChar != 0
|
||||
|
||||
sramMask := videxUltratermSram256Mask
|
||||
is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0
|
||||
if is512mode {
|
||||
sramMask = videxUltratermSram512Mask
|
||||
}
|
||||
|
||||
params.IterateScreen(func(address uint16, charLine uint8,
|
||||
cursorMode uint8, displayEnable bool,
|
||||
column uint8, y int) {
|
||||
|
||||
if !displayEnable {
|
||||
return
|
||||
}
|
||||
|
||||
bits := uint8(0)
|
||||
colorOn := lowerSetColor
|
||||
colorOff := lowerClearColor
|
||||
char := c.sram[address&sramMask]
|
||||
if char&0x80 != 0 {
|
||||
colorOn = upperSetColor
|
||||
colorOff = upperClearColor
|
||||
}
|
||||
|
||||
romIndex := (uint16(char&0x7f) << 4) + uint16(charLine)
|
||||
if !altChar {
|
||||
romIndex += 2048
|
||||
}
|
||||
bits = c.charGen[romIndex]
|
||||
|
||||
// Cursor
|
||||
isCursor := false
|
||||
switch cursorMode {
|
||||
case component.MC6845CursorFixed:
|
||||
isCursor = true
|
||||
case component.MC6845CursorSlow:
|
||||
// It should be 533ms (32/60, 32 screen refreshes)
|
||||
// Let's make a 2 blinks per second
|
||||
isCursor = ms/2 > 1000/4
|
||||
case component.MC6845CursorFast:
|
||||
// It should be 266ms (32/60, 16 screen refreshes)
|
||||
// Let's make a 4 blinks per second
|
||||
isCursor = ms/4 > 1000/8
|
||||
}
|
||||
if isCursor {
|
||||
bits = ^bits
|
||||
}
|
||||
|
||||
x := int(column) * int(videxUltratermCharWidth)
|
||||
color := colorOff
|
||||
for i := 0; i < int(videxUltratermCharWidth-1); i++ {
|
||||
pixel := (bits & 0x80) != 0
|
||||
if pixel {
|
||||
color = colorOn
|
||||
} else {
|
||||
color = colorOff
|
||||
}
|
||||
img.Set(x, y, color)
|
||||
bits <<= 1
|
||||
x++
|
||||
}
|
||||
|
||||
// The ninth bit: blank or repetition of the last bit
|
||||
if char&0x7f < 0x20 {
|
||||
img.Set(x, y, color)
|
||||
} else {
|
||||
img.Set(x, y, colorOff)
|
||||
}
|
||||
})
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
func (c *CardVidexUltraterm) getText() string {
|
||||
text := ""
|
||||
params := c.mc6845.ImageData()
|
||||
|
||||
sramMask := videxUltratermSram256Mask
|
||||
is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0
|
||||
if is512mode {
|
||||
sramMask = videxUltratermSram512Mask
|
||||
}
|
||||
|
||||
address := params.FirstChar
|
||||
for line := uint8(0); line < params.Lines; line++ {
|
||||
for column := uint8(0); column < params.Columns; column++ {
|
||||
char := c.sram[address&sramMask]
|
||||
text += string(char)
|
||||
address++
|
||||
}
|
||||
text = strings.TrimRight(text, " ")
|
||||
text += "\n"
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
//lint:ignore U1000 Ignore function used for debugging
|
||||
func (c *CardVidexUltraterm) dumpState() {
|
||||
data := c.mc6845.ImageData()
|
||||
width, height := data.DisplayedWidthHeight(videxUltratermCharWidth)
|
||||
is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0
|
||||
sramPage256 := c.modeControl & videxUltratermMCPSramPageMask
|
||||
mhz := "17.430"
|
||||
if c.modeControl&videxUltratermMCPClockFrequency != 0 {
|
||||
mhz = "28.7595"
|
||||
}
|
||||
|
||||
flags := c.a.mmu.Peek(0x7f8 + uint16(c.slot))
|
||||
|
||||
fmt.Printf("%vx%v %vx%v %vx%v 512:%v,%v 256page:%v %v MHz +-%v Flags: %v\n",
|
||||
videxUltratermCharWidth, data.CharLines,
|
||||
data.Columns, data.Lines, width, height, is512mode, c.sramPage512, sramPage256, mhz,
|
||||
data.AdjustLines, flags)
|
||||
}
|
@@ -12,17 +12,17 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
Videx 80 columns card for the Apple II+
|
||||
Videx Videoterm 80 columns card for the Apple II+
|
||||
|
||||
See:
|
||||
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/80%20Column%20Cards/Videx%20Videoterm/Manuals/Videx%20Videoterm%20-%20Installation%20and%20Operation%20Manual.pdf
|
||||
http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
|
||||
http://www.bitsavers.org/components/motorola/_dataSheets/6845.pdf
|
||||
https://glasstty.com/?p=660
|
||||
|
||||
*/
|
||||
|
||||
// CardVidex represents a Videx compatible 80 column card
|
||||
type CardVidex struct {
|
||||
// CardVidexVideoterm represents a Videx compatible 80 column card
|
||||
type CardVidexVideoterm struct {
|
||||
cardBase
|
||||
mc6845 component.MC6845
|
||||
sramPage uint8
|
||||
@@ -32,20 +32,20 @@ type CardVidex struct {
|
||||
alwaysShow bool
|
||||
}
|
||||
|
||||
func newCardVidexBuilder() *cardBuilder {
|
||||
func newCardVidexVideotermBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Videx 80 columns Card",
|
||||
description: "Videx compatible 80 columns card",
|
||||
description: "Videx Videoterm compatible 80 columns card",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"rom", "ROM file to load", "<internal>/Videx Videoterm ROM 2.4.bin"},
|
||||
{"charmap", "Character map file to load", "<internal>/80ColumnP110.BIN"},
|
||||
{"always", "Always show the 80 columns output", "false"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardVidex
|
||||
var c CardVidexVideoterm
|
||||
|
||||
// The C800 area has ROM and RAM
|
||||
err := c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin", cardRomUpperHalfEnd)
|
||||
err := c.loadRomFromResource(paramsGetPath(params, "rom"), cardRomUpperHalfEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func newCardVidexBuilder() *cardBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidex) loadCharacterMap(filename string) error {
|
||||
func (c *CardVidexVideoterm) loadCharacterMap(filename string) error {
|
||||
bytes, _, err := LoadResource(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,7 +76,7 @@ func (c *CardVidex) loadCharacterMap(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CardVidex) assign(a *Apple2, slot int) {
|
||||
func (c *CardVidexVideoterm) assign(a *Apple2, slot int) {
|
||||
|
||||
// TODO: use addCardSoftSwitches()
|
||||
for i := uint8(0x0); i <= 0xf; i++ {
|
||||
@@ -106,14 +106,14 @@ func (c *CardVidex) assign(a *Apple2, slot int) {
|
||||
}
|
||||
|
||||
c.cardBase.assign(a, slot)
|
||||
a.softVideoSwitch = NewSoftVideoSwitch(c, c.alwaysShow)
|
||||
a.setSoftVideoSwitch(c)
|
||||
}
|
||||
|
||||
const videxRomLimit = uint16(0xcc00)
|
||||
const videxSramLimit = uint16(0xce00)
|
||||
const videxSramMask = uint16(0x01ff)
|
||||
|
||||
func (c *CardVidex) peek(address uint16) uint8 {
|
||||
func (c *CardVidexVideoterm) peek(address uint16) uint8 {
|
||||
if address < videxRomLimit {
|
||||
return c.upperROM.peek(address)
|
||||
} else if address < videxSramLimit {
|
||||
@@ -122,17 +122,27 @@ func (c *CardVidex) peek(address uint16) uint8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *CardVidex) poke(address uint16, value uint8) {
|
||||
func (c *CardVidexVideoterm) poke(address uint16, value uint8) {
|
||||
if address >= videxRomLimit && address < videxSramLimit {
|
||||
c.sram[address&videxSramMask+uint16(c.sramPage)*0x200] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidexVideoterm) isSoftSwitchActive() bool {
|
||||
if c.alwaysShow {
|
||||
return true
|
||||
}
|
||||
|
||||
isTextMode := c.a.io.isSoftSwitchActive(ioFlagText)
|
||||
ann0 := c.a.io.isSoftSwitchActive(ioFlagAnnunciator0)
|
||||
return isTextMode && ann0
|
||||
}
|
||||
|
||||
const (
|
||||
videxCharWidth = uint8(8)
|
||||
)
|
||||
|
||||
func (c *CardVidex) buildImage(light color.Color) *image.RGBA {
|
||||
func (c *CardVidexVideoterm) buildImage(light color.Color) *image.RGBA {
|
||||
params := c.mc6845.ImageData()
|
||||
width, height := params.DisplayedWidthHeight(videxCharWidth)
|
||||
if (width == 0) || (height == 0) {
|
||||
@@ -194,7 +204,7 @@ func (c *CardVidex) buildImage(light color.Color) *image.RGBA {
|
||||
return img
|
||||
}
|
||||
|
||||
func (c *CardVidex) getText() string {
|
||||
func (c *CardVidexVideoterm) getText() string {
|
||||
text := ""
|
||||
params := c.mc6845.ImageData()
|
||||
address := params.FirstChar
|
@@ -34,8 +34,13 @@ func (m *MC6845) Write(rs bool, value uint8) {
|
||||
m.sel = value & 0x1f
|
||||
} else if m.sel <= 15 {
|
||||
// R0 to R15 are writable
|
||||
m.reg[m.sel] = value
|
||||
// fmt.Printf("Set %v to %v\n", m.sel, value)
|
||||
|
||||
if m.sel == 1 && value == 144 {
|
||||
// Horrible hack for the mode 6.
|
||||
m.reg[m.sel] = 160
|
||||
} else {
|
||||
m.reg[m.sel] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,15 +48,17 @@ func (m *MC6845) ImageData() MC6845ImageData {
|
||||
var data MC6845ImageData
|
||||
|
||||
data.FirstChar = uint16(m.reg[12]&0x3f)<<8 + uint16(m.reg[13])
|
||||
data.charLines = (m.reg[9] + 1) & 0x1f
|
||||
data.CharLines = (m.reg[9] + 1) & 0x1f
|
||||
data.Columns = m.reg[1]
|
||||
data.Lines = m.reg[6] & 0x7f
|
||||
data.adjustLines = m.reg[5] & 0x1f
|
||||
data.AdjustLines = m.reg[5] & 0x1f
|
||||
|
||||
data.cursorPos = uint16(m.reg[14]&0x3f)<<8 + uint16(m.reg[15])
|
||||
data.cursorStart = m.reg[10] & 0x1f
|
||||
data.cursorEnd = m.reg[11] & 0x1f
|
||||
data.cursorMode = (m.reg[10] >> 5) & 0x03 // Bit 6 and 5
|
||||
data.cursorMode = (m.reg[10] >> 5) & 0x03 // Bits 6 and 5
|
||||
|
||||
data.InterlaceMode = m.reg[8] & 0x03 // Bits 1 and 0
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -64,21 +71,23 @@ const (
|
||||
|
||||
type MC6845ImageData struct {
|
||||
FirstChar uint16 // 14 bits, address of the firt char on the first line
|
||||
charLines uint8 // 5 bits, lines par character
|
||||
CharLines uint8 // 5 bits, lines par character
|
||||
Columns uint8 // 8 bits, chars per line
|
||||
Lines uint8 // 7 bits, char lines per screen
|
||||
adjustLines uint8 // 5 bits, extra blank lines
|
||||
AdjustLines uint8 // 5 bits, extra blank lines
|
||||
|
||||
cursorPos uint16 // 14 bits, address? of the cursor position
|
||||
cursorStart uint8 // 5 bits, cursor starting char row
|
||||
cursorEnd uint8 // 5 bits, cursor ending char row
|
||||
cursorMode uint8 // 2 bits, cursor mode
|
||||
|
||||
InterlaceMode uint8 // 2 bit, interlace mode
|
||||
|
||||
}
|
||||
|
||||
func (data *MC6845ImageData) DisplayedWidthHeight(charWidth uint8) (int, int) {
|
||||
return int(data.Columns) * int(charWidth),
|
||||
int(data.Lines)*int(data.charLines) + int(data.adjustLines)
|
||||
int(data.Lines)*int(data.CharLines) + int(data.AdjustLines)
|
||||
}
|
||||
|
||||
type MC6845RasterCallBack func(address uint16, charLine uint8, // Lookup in char ROM
|
||||
@@ -90,7 +99,7 @@ func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) {
|
||||
y := 0
|
||||
var address uint16
|
||||
for line := uint8(0); line < data.Lines; line++ {
|
||||
for charLine := uint8(0); charLine < data.charLines; charLine++ {
|
||||
for charLine := uint8(0); charLine < data.CharLines; charLine++ {
|
||||
address = lineAddress // Back to the first char of the line
|
||||
for column := uint8(0); column < data.Columns; column++ {
|
||||
cursorMode := MC6845CursorNone
|
||||
@@ -108,7 +117,7 @@ func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) {
|
||||
}
|
||||
lineAddress = address
|
||||
}
|
||||
for adjust := uint8(0); adjust <= data.adjustLines; adjust++ {
|
||||
for adjust := uint8(0); adjust <= data.AdjustLines; adjust++ {
|
||||
for column := uint8(0); column < data.Columns; column++ {
|
||||
callBack(0, 0, MC6845CursorNone, false, column, y) // lines with display not enabled
|
||||
}
|
||||
|
9
configs/ultraterm.cfg
Normal file
9
configs/ultraterm.cfg
Normal file
@@ -0,0 +1,9 @@
|
||||
name: Apple ][+ with Videx Ultraterm demo
|
||||
parent: _base
|
||||
board: 2plus
|
||||
rom: <internal>/Apple2_Plus.rom
|
||||
charrom: <internal>/Apple2rev7CharGen.rom
|
||||
forceCaps: true
|
||||
s0: language
|
||||
s3: videxultraterm
|
||||
s6: diskii,disk1=<internal>/Videx Ultraterm Utilities.dsk
|
@@ -57,6 +57,7 @@ The available pre-configured models are:
|
||||
desktop: Apple II DeskTop
|
||||
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
||||
swyft: swyft
|
||||
ultraterm: Apple ][+ with Videx Ultraterm demo
|
||||
|
||||
The available cards are:
|
||||
brainboard: Firmware card. It has two ROM banks
|
||||
@@ -79,7 +80,8 @@ The available cards are:
|
||||
softswitchlogger: Card to log softswitch accesses
|
||||
swyftcard: Card with the ROM needed to run the Swyftcard word processing system
|
||||
thunderclock: Clock card
|
||||
videx: Videx compatible 80 columns card
|
||||
videx: Videx Videoterm compatible 80 columns card
|
||||
videxultraterm: Videx Utraterm compatible 80 columns card
|
||||
vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode
|
||||
z80softcard: Microsoft Z80 SoftCard to run CP/M
|
||||
|
||||
|
BIN
resources/Videx Ultraterm Utilities.dsk
Normal file
BIN
resources/Videx Ultraterm Utilities.dsk
Normal file
Binary file not shown.
BIN
resources/videx_ultraterm_chs_7859.bin
Normal file
BIN
resources/videx_ultraterm_chs_7859.bin
Normal file
Binary file not shown.
BIN
resources/videx_ultraterm_frm_b537.bin
Normal file
BIN
resources/videx_ultraterm_frm_b537.bin
Normal file
Binary file not shown.
@@ -6,45 +6,25 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
Videx Soft Video Switch
|
||||
Videx Soft Video Switch external on the Videx Videoterm and integrated on the Videx Ultraterm
|
||||
|
||||
See:
|
||||
https://archive.org/details/videx-soft-video-switch
|
||||
|
||||
*/
|
||||
|
||||
// SoftVideoSwitch represents a Videx soft video switch
|
||||
type SoftVideoSwitch struct {
|
||||
card *CardVidex
|
||||
forced bool
|
||||
type softVideoSwitch interface {
|
||||
buildImage(light color.Color) *image.RGBA
|
||||
isSoftSwitchActive() bool
|
||||
}
|
||||
|
||||
// NewSoftVideoSwitch creates a new SoftVideoSwitch
|
||||
func NewSoftVideoSwitch(card *CardVidex, force bool) *SoftVideoSwitch {
|
||||
var vs SoftVideoSwitch
|
||||
vs.card = card
|
||||
vs.forced = force
|
||||
return &vs
|
||||
func (a *Apple2) setSoftVideoSwitch(card softVideoSwitch) {
|
||||
a.softVideoSwitch = card
|
||||
}
|
||||
|
||||
func (vs *SoftVideoSwitch) isActive() bool {
|
||||
if vs == nil {
|
||||
func (a *Apple2) isSoftVideoSwitchActive() bool {
|
||||
if a.softVideoSwitch == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if vs.forced {
|
||||
return true
|
||||
}
|
||||
|
||||
isTextMode := vs.card.a.io.isSoftSwitchActive(ioFlagText)
|
||||
ann0 := vs.card.a.io.isSoftSwitchActive(ioFlagAnnunciator0)
|
||||
return isTextMode && ann0
|
||||
}
|
||||
|
||||
func (vs *SoftVideoSwitch) BuildAlternateImage(light color.Color) *image.RGBA {
|
||||
return vs.card.buildImage(light)
|
||||
}
|
||||
|
||||
func (a *Apple2) SoftVideoSwitch() *SoftVideoSwitch {
|
||||
return a.softVideoSwitch
|
||||
return a.softVideoSwitch.isSoftSwitchActive()
|
||||
}
|
||||
|
4
video.go
4
video.go
@@ -36,7 +36,7 @@ func (v *video) GetCurrentVideoMode() uint32 {
|
||||
isStore80Active := v.a.mmu.store80Active
|
||||
isDoubleResMode := !isTextMode && is80Columns && !v.a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||
isSuperHighResMode := v.a.io.isSoftSwitchActive(ioDataNewVideo)
|
||||
isVidex := v.a.softVideoSwitch.isActive()
|
||||
isVidex := v.a.isSoftVideoSwitchActive()
|
||||
|
||||
isRGBCard := v.a.io.isSoftSwitchActive(ioFlagRGBCardActive)
|
||||
rgbFlag1 := v.a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
||||
@@ -162,7 +162,7 @@ func (v *video) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isAl
|
||||
|
||||
// GetCardImage returns an image provided by a card, like the videx card
|
||||
func (v *video) GetCardImage(light color.Color) *image.RGBA {
|
||||
return v.a.softVideoSwitch.BuildAlternateImage(light)
|
||||
return v.a.softVideoSwitch.buildImage(light)
|
||||
}
|
||||
|
||||
// SupportsLowercase returns true if the video source supports lowercase
|
||||
|
Reference in New Issue
Block a user