Cocoacrumbs

Cocoacrumbs

All of the buildings, all of those cars
were once just a dream
in somebody's head
Mercy Street - Peter Gabriel

Cocoacrumbs

7 minutes read

Pic 1

Setting up the 6845

Setting up the 6845 involves filling in 16 registers so that the 6845 can generate the correct timing signals for the desired resolution. The available data sheets provide more information on how to calculate the needed values. But since that’s a tedious process, I decided to write a small Python application that could calculate those values for me which you can find below and which was surprisingly accurate. Only R7 (Vertical Sync Position) was of a little bit of sometimes and needed a little bit of [manual] fine tuning.

# 80 by 25
characterWidth          = 8
characterHeight         = 16
nrOfTerminalRows        = 25
emptyScanLines          = 0
totalPixelsPerLine      = 800
activePixels            = 640
totalLines              = 525
activeLines             = 480
cursorStartAddress      = 13
cursorEndAddress        = 15
cursorType              = 0xC0  # Slow blinking cursor
dotClock                = 25175000

###

characterClock          = dotClock // 8
pixelTime               = 1 / dotClock
lineTime                = pixelTime * totalPixelsPerLine
totalCharactersPerLine  = totalPixelsPerLine // characterWidth
activeCharactersPerLine = activePixels // characterWidth
N                       = int(totalLines / (characterHeight + emptyScanLines))

R0  = int(totalCharactersPerLine - 1)
R1  = int(activeCharactersPerLine)
R3  = int(round((R0 - R1) / 3))
R2  = int(round(R1 + (R3 / 2)))
R4  = N - 1
R5  = totalLines % (characterHeight + emptyScanLines)
R6  = nrOfTerminalRows
R7  = int((R4 - 1) - ((16 - R5) / (characterHeight + emptyScanLines)))
R8  = 0
R9  = characterHeight + emptyScanLines - 1
R10 = cursorStartAddress + cursorType
R11 = cursorEndAddress   + cursorType
R12 = -1    # To start from video RAM address 0x0000
R13 = -1    # To start from video RAM address 0x0000
R14 = -1    # To start at position (0, 0)
R15 = -1    # To start at position (0, 0)

print("========================================")

print("dotClock                :", dotClock, "Hz")
print("characterClock          :", characterClock, "Hz")
print("pixelTime               : {:0.2f} ns".format(pixelTime * 1000000000))
print("lineTime                : {:0.2f} ms".format(lineTime * 1000000))
print("totalCharactersPerLine  : {:d} characters".format(totalCharactersPerLine))
print("activeCharactersPerLine : {:d} characters".format(activeCharactersPerLine))
print("characterWidth          : {:d}".format(characterWidth))
print("characterHeight         : {:d}".format(characterHeight))
print("nrOfTerminalRows        : {:d}".format(nrOfTerminalRows))
print("emptyScanLines          : {:d}".format(emptyScanLines))
print("N                       : {:d}".format(N))

print("========================================")

print("RO  : {:3d} (0x{:2x}) - Nr of Horizontal Characters Total.".format(R0, R0))
print("R1  : {:3d} (0x{:2x}) - Nr of Horizontal Characters Displayed.".format(R1, R1))
print("R2  : {:3d} (0x{:2x}) - Horizontal Sync Position.".format(R2, R2))
print("R3  : {:3d} (0x{:2x}) - Sync width.".format(R3, R3))
print("R4  : {:3d} (0x{:2x}) - Vertical Total.".format(R4, R4))
print("R5  : {:3d} (0x{:2x}) - Vertical Total Adjustment.".format(R5, R5))
print("R6  : {:3d} (0x{:2x}) - Nr of Vertical Characters Displayed.".format(R6, R6))
print("R7  : {:3d} (0x{:2x}) - Vertical Sync Position (might need manual fine tuning).".format(R7, R7))
print("R8  : {:3d} (0x{:2x}) - Interlace Mode.".format(R8, R8))
print("R9  : {:3d} (0x{:2x}) - Max Scanline Address.".format(R9, R9))
print("R10 : {:3d} (0x{:2x}) - Cursor Start Scan Line.".format(R10, R10))
print("R11 : {:3d} (0x{:2x}) - Cursor Stop Scan Line.".format(R11, R11))
print("R12 : {:3d} (0x{:2x}) - Start Address (High). Real start address is 0x0000.".format(R12, R12 & (2**8-1)))
print("R13 : {:3d} (0x{:2x}) - Start Address (Low). Real start address is 0x0000.".format(R13, R13 & (2**8-1)))
print("R14 : {:3d} (0x{:2x}) - Cursor Start Address (High). Cursor will be at position (0, 0).".format(R14, R14 & (2**8-1)))
print("R15 : {:3d} (0x{:2x}) - Cursor Start Address (Low). Cursor will be at position (0, 0).".format(R15, R15 & (2**8-1)))

print("========================================")

And this is an example run for a 80 by 25 lines configuration. In the end I fine tuned the value for R7 from 0x1D to 0x1B.

$ python 6845.py 
========================================
dotClock                : 25175000 Hz
characterClock          : 3146875 Hz
pixelTime               : 39.72 ns
lineTime                : 31.78 ms
totalCharactersPerLine  : 100 characters
activeCharactersPerLine : 80 characters
characterWidth          : 8
characterHeight         : 16
nrOfTerminalRows        : 25
emptyScanLines          : 0
N                       : 32
========================================
RO  :  99 (0x63) - Nr of Horizontal Characters Total.
R1  :  80 (0x50) - Nr of Horizontal Characters Displayed.
R2  :  83 (0x53) - Horizontal Sync Position.
R3  :   6 (0x 6) - Sync width.
R4  :  31 (0x1f) - Vertical Total.
R5  :  13 (0x d) - Vertical Total Adjustment.
R6  :  25 (0x19) - Nr of Vertical Characters Displayed.
R7  :  29 (0x1d) - Vertical Sync Position (might need manual fine tuning).
R8  :   0 (0x 0) - Interlace Mode.
R9  :  15 (0x f) - Max Scanline Address.
R10 : 205 (0xcd) - Cursor Start Scan Line.
R11 : 207 (0xcf) - Cursor Stop Scan Line.
R12 :  -1 (0xff) - Start Address (High). Real start address is 0x0000.
R13 :  -1 (0xff) - Start Address (Low). Real start address is 0x0000.
R14 :  -1 (0xff) - Cursor Start Address (High). Cursor will be at position (0, 0).
R15 :  -1 (0xff) - Cursor Start Address (Low). Cursor will be at position (0, 0).
========================================

Hardware scrolling or how to achieve 115.200 baud reliably

Running at 115.200 baud means that around 11.500 characters are received every second (assume 10 bits for a character). That means we have around 1 / 11.500 = 86.95 usec on average to process and display a character. That’s not much, even with a Z180 running at 18.432 MHz. Assuming an instruction takes 1 microsecond, this means less than 100 instructions to handle the most complex escape sequence.

The obvious solution is of course using a buffer and a generous 256 byte, interrupt driven, buffer has been implemented.

But even this is not sufficient for the heaviest task which is scrolling. In this case we need to move 2 times 8K bytes (characters and their colours). Assume we can, optimistically, move 1 byte per microsecond, then we would need 2 * 8.192 * 0.000001 = 16.384 millisecond for a scroll operation. Even with a very generous character buffer characters will get lost with continuous scrolling.

The solution is to tell the 6845 to start the display from the next line. I.e. The 6845 has 2 8-bit registers (R12 and R13) that combined form the start address which the 6845 will use as the start address of the display. Initially you will set this to 0x0000, which is the start address of the first line to be display. The second line of the display will be 0x0000 + 0x0050 = 0x0050 (0x0050 is 80 in decimal, which is the character width of the terminal). The third line will be starting at 0x0050 + 0x0050 = 0x00A0 and so on. Assuming the display is set to 25 lines, the last line will be starting at address 0x0000 + 24 * 0x0050 = 0x0780.

If we now tell the 6845 to start the display from address 0x0050, then the first displayed line will now be the content starting from address 0x0050 while the last line will be 0x0780 + 0x0050 = 0x07D0. As a result, the display scrolled up. For a scroll down, we only need to subtract 0x0050 from the starting address. So far so good, but what happens when we reach the end of our video RAM?

Let’s use decimal values for now for convenience. Our 8K RAM is 8.192 bytes. This means we can have 102 lines of 80 characters and 32 bytes left over:

(102 * 80) + 32 = 8.192

The 102nd line (which is line 101 when you start counting from 0) then starts at address:

101 * 80 = 8.080 (start of line 101)

and ends at:

8.080 + 79 = 8.159 (end of line 101)

We now want to do a scroll up and we increment the start address of the 6845 with another 80 and now we start writing on line 102. From the above we know that line 102 will start at address 8.160.

So we start writing characters starting from 8.160 and we can do this without any problem till we reach address 8.191 (which is the highest address of the video RAM). The next character cannot be written to address 8.192 (although the Z180 with its 16 bit address bus could do this) but to address 0 again because we only use a 12 bit address bus on the 6845.

This means that the code writing a character into video RAM and increases its position by 1, always needs to check if we cross the 8.192 address boundary. And if so, start again from 0.

As a result, code that used to be easy when we would use a [slow] memory move to perform the scrolling now becomes a lot more complex. Scrolling is now very fast since it only requires updating 2 8-bit registers. But writing a character and updating the next position becomes quite a bit more complex. Nevertheless, everything balances out now and we can sustain 115.200 baud with this trick.

Vertically mounted terminal board.
Vertically mounted backplane (and using an amber/yellow colour for the font).

In part 4 I’ll discuss the software development.

Recent posts

See more

Categories

About

Cocoacrumbs