Videx Videoterm 80 columns card support. Videx Soft Video Switch support
parent
f34b3da510
commit
dfb8b1ffb2
@ -0,0 +1,163 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/ivanizag/izapple2/component"
|
||||
"github.com/ivanizag/izapple2/storage"
|
||||
)
|
||||
|
||||
/*
|
||||
Videx 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
|
||||
https://glasstty.com/?p=660
|
||||
|
||||
*/
|
||||
|
||||
// CardVidex represents a Videx compatible 80 column card
|
||||
type CardVidex struct {
|
||||
cardBase
|
||||
mc6845 component.MC6845
|
||||
sramPage uint8
|
||||
sram [0x800]uint8
|
||||
upperROM memoryHandler
|
||||
charGen []uint8
|
||||
}
|
||||
|
||||
// NewCardVidex creates a new CardVidex
|
||||
func NewCardVidex() *CardVidex {
|
||||
var c CardVidex
|
||||
c.name = "Videx 80 col Card"
|
||||
|
||||
// The C800 area has ROM and RAM
|
||||
c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin")
|
||||
c.upperROM = c.romC8xx
|
||||
c.romC8xx = &c
|
||||
|
||||
// The resource should be internal and never fail
|
||||
c.loadCharacterMap("<internal>/80ColumnP110.BIN")
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *CardVidex) loadCharacterMap(filename string) error {
|
||||
bytes, _, err := storage.LoadResource(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size := len(bytes)
|
||||
if size < 0x800 {
|
||||
return errors.New("Character ROM size not supported for Videx")
|
||||
}
|
||||
c.charGen = bytes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CardVidex) assign(a *Apple2, slot int) {
|
||||
for i := uint8(0x0); i <= 0xf; i++ {
|
||||
// Bit 0 goes to the RS pin of the MC6548. It controls
|
||||
// whether a register is being accesed or the contents
|
||||
// of the register is being accessed
|
||||
rsPin := (i & 1) == 1
|
||||
|
||||
// Bits 2 and 3 determine which page will be selected
|
||||
sramPage := i >> 2
|
||||
|
||||
ssName := fmt.Sprintf("VIDEXPAGE%v", sramPage)
|
||||
if rsPin {
|
||||
ssName += "REG"
|
||||
} else {
|
||||
ssName += "ADDRESS"
|
||||
}
|
||||
|
||||
c.addCardSoftSwitchR(i, func(*ioC0Page) uint8 {
|
||||
c.sramPage = sramPage
|
||||
return c.mc6845.Read(rsPin)
|
||||
}, ssName+"R")
|
||||
c.addCardSoftSwitchW(i, func(_ *ioC0Page, value uint8) {
|
||||
c.sramPage = sramPage
|
||||
c.mc6845.Write(rsPin, value)
|
||||
}, ssName+"W")
|
||||
}
|
||||
|
||||
c.cardBase.assign(a, slot)
|
||||
}
|
||||
|
||||
const videxRomLimit = uint16(0xcc00)
|
||||
const videxSramLimit = uint16(0xce00)
|
||||
const videxSramMask = uint16(0x01ff)
|
||||
|
||||
func (c *CardVidex) peek(address uint16) uint8 {
|
||||
if address < videxRomLimit {
|
||||
return c.upperROM.peek(address)
|
||||
} else if address < videxSramLimit {
|
||||
return c.sram[address&videxSramMask+uint16(c.sramPage)*0x200]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *CardVidex) poke(address uint16, value uint8) {
|
||||
if address >= videxRomLimit && address < videxSramLimit {
|
||||
c.sram[address&videxSramMask+uint16(c.sramPage)*0x200] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidex) setBase(base uint16) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
const (
|
||||
videxCharWidth = uint8(8)
|
||||
)
|
||||
|
||||
func (c *CardVidex) buildImage(light color.Color) *image.RGBA {
|
||||
params := c.mc6845.ImageData()
|
||||
width, height := params.DisplayedWidthHeight(videxCharWidth)
|
||||
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
|
||||
}
|
||||
|
||||
size := image.Rect(0, 0, width, height)
|
||||
img := image.NewRGBA(size)
|
||||
|
||||
params.IterateScreen(func(address uint16, charLine uint8,
|
||||
cursor bool, displayEnable bool,
|
||||
column uint8, y int) {
|
||||
|
||||
bits := uint8(0)
|
||||
if displayEnable {
|
||||
char := c.sram[address&0x7ff]
|
||||
bits = c.charGen[(uint16(char&0x7f)<<4)+uint16(charLine)]
|
||||
if cursor {
|
||||
bits = ^bits
|
||||
}
|
||||
if char >= 128 {
|
||||
bits = ^bits
|
||||
}
|
||||
}
|
||||
|
||||
x := int(column) * int(videxCharWidth)
|
||||
|
||||
for i := 0; i < int(videxCharWidth); i++ {
|
||||
pixel := (bits & 0x80) != 0
|
||||
if pixel {
|
||||
img.Set(x, y, light)
|
||||
} else {
|
||||
img.Set(x, y, color.Black)
|
||||
}
|
||||
bits <<= 1
|
||||
x++
|
||||
}
|
||||
})
|
||||
|
||||
return img
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package component
|
||||
|
||||
/*
|
||||
MC6845 CRT Controller
|
||||
See:
|
||||
Motorola MC6845 datasheet
|
||||
|
||||
Pins:
|
||||
RW, RS, D0-D7: Read() and Write()
|
||||
MA0-13, RA04, CURSOR, DE: MC6845RasterCallBack()
|
||||
*/
|
||||
|
||||
type MC6845 struct {
|
||||
reg [18]uint8 // Internal registers R0 to R17
|
||||
sel uint8 // Selected address register AR
|
||||
}
|
||||
|
||||
func (m *MC6845) Read(rs bool) uint8 {
|
||||
if !rs {
|
||||
// AR is not readable
|
||||
return 0x00
|
||||
} else if m.sel >= 14 && m.sel <= 17 {
|
||||
// Only R14 to R17 are readable
|
||||
// Should we mask R14 and R16?
|
||||
return m.reg[m.sel]
|
||||
}
|
||||
return 0x00
|
||||
}
|
||||
|
||||
func (m *MC6845) Write(rs bool, value uint8) {
|
||||
if !rs {
|
||||
// AR is 5 bits
|
||||
// What happens if AR > 17 ?
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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.columns = m.reg[1]
|
||||
data.lines = m.reg[6] & 0x7f
|
||||
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
|
||||
// cursor mode is on bits 6 and 5 of R10
|
||||
return data
|
||||
}
|
||||
|
||||
type MC6845ImageData struct {
|
||||
firstChar uint16 // 14 bits, address of the firt char on the first line
|
||||
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
|
||||
|
||||
cursorPos uint16 // 14 bits, address? of the cursor position
|
||||
cursorStart uint8 // 5 bits, cursor starting char row
|
||||
cursorEnd uint8 // 5 bits, cursos ending char row
|
||||
// cursor mode
|
||||
|
||||
}
|
||||
|
||||
func (data *MC6845ImageData) DisplayedWidthHeight(charWidth uint8) (int, int) {
|
||||
return int(data.columns) * int(charWidth),
|
||||
int(data.lines)*int(data.charLines) + int(data.adjustLines)
|
||||
}
|
||||
|
||||
type MC6845RasterCallBack func(address uint16, charLine uint8, // Lookup in char ROM
|
||||
cursor bool, displayEnable bool, // Modifiers
|
||||
column uint8, y int) // Position in screen
|
||||
|
||||
func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) {
|
||||
lineAddress := data.firstChar
|
||||
y := 0
|
||||
var address uint16
|
||||
for line := uint8(0); line < data.lines; line++ {
|
||||
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++ {
|
||||
isCursor := (address == data.cursorPos) &&
|
||||
(charLine >= data.cursorStart) &&
|
||||
(charLine <= data.cursorEnd)
|
||||
callBack(address, charLine, isCursor, true, column, y)
|
||||
address = (address + 1) & 0x3fff // 14 bits
|
||||
}
|
||||
y++
|
||||
}
|
||||
lineAddress = address
|
||||
}
|
||||
for adjust := uint8(0); adjust <= data.adjustLines; adjust++ {
|
||||
for column := uint8(0); column < data.columns; column++ {
|
||||
callBack(0, 0, false, false, column, y) // lines with display not enabled
|
||||
}
|
||||
y++
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
/*
|
||||
Videx Soft Video Switch
|
||||
|
||||
See:
|
||||
https://archive.org/details/videx-soft-video-switch
|
||||
|
||||
*/
|
||||
|
||||
// SoftVideoSwitch represents a Videx soft video switch
|
||||
type SoftVideoSwitch struct {
|
||||
card *CardVidex
|
||||
}
|
||||
|
||||
// NewSoftVideoSwitch creates a new SoftVideoSwitch
|
||||
func NewSoftVideoSwitch(card *CardVidex) *SoftVideoSwitch {
|
||||
var vs SoftVideoSwitch
|
||||
vs.card = card
|
||||
return &vs
|
||||
}
|
||||
|
||||
func (vs *SoftVideoSwitch) isActive() bool {
|
||||
if vs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue