This little computer ultimately is going to be a standalone system. I am working towards this goal to hopefully one day cut the cord and get rid of the need for a PC as a host computer / cross-development platform. In the recent months I have been working on sort of an off-shot project that one day will help me complete this plan. I employed Parallax Propeller chip to build a serial terminal device for this computer. My requirements were as follows:
* Dumb terminal with some subset of ANSI functionality with a keyboard (PC or self-made) and at least 80 columns x 25 rows display.
* Mass storage (SD card) that would allow to transfer files to host computer (not PC, the MKHBC-8-R2) and save output from computer coming via serial port to a file on SD card.
This serial terminal device is coming together nicely. It is not the last word though. In the final version I would like to have a Propeller based I/O card which serves keyboard input and display output, maybe some sound and joystick / paddle ports as well which interfaces with MKHBC-8-R2 via CPU or I/O bus instead of slow serial port. This serial terminal is sort of an intermediate step allowing me to get more familiar with Propeller chip.
So far I was able to put together hardware and code to:
* Connect to MKHBC-8-R2 via serial (RS-232) with 9600 baud speed.
* Display hi-res text on VGA display.
* Interface my home-brew retro computer keyboard (TI99/4A + 8052 micro as controller, serial interface similar to PS2) to Propeller and write my driver code. I also have a variant for PC keyboard, both presenting identical API interface, so all I need to do is load different driver to cog if I want to use a different keyboard, other code does not change.
* Interface micro SD card to the Propeller and write Terminal Menu code which aids in sending contents of the text file from SD card to the serial port or dumping memory of MKHBC-8-R2 system to a file on SD card in the form of monitor memory write commands. Thus I can prepare file with monitor memory write commands on a PC or MKHBC system, store on SD card and then send them to the MKHBC-8-R2 system - a primitive way of of loading or saving data, but effective.
* Implemented few ANSI terminal functions, like clearing screen or positioning cursor at specified column and row.
I will soon post the code to github and update project with a link to it.
On this screenshot I just uploaded Tiny Basic from SD card, ran it, then uploaded BASIC program listing from SD card effectively entering BASIC program into the memory just as I would do it from keyboard, then listed it and ran it.
This is how the startup screen looks like. The 'mkhbc>' string is a prompt from the MKHBC-8-R2 computer received via serial port.
My development system. I am using Quick Start board from Parallax with Human Interface shield on top for VGA and keyboard interfacing. To the right you see TI99/4A keyboard with 8052 controller (not visible, tucked in under the keyboard) connected to the Human Interface board. PC is the host for SPIN program development. The serial port interface is put together on the breadboard on the left of the Parallax board (just 3 resistors). The VGA monitor connected to the Human Interface shield is the one on the left that you can only see a corner of with the green glow.
This is the Parallax Propeller USB Project board. This board will ultimately host the serial terminal device. I don't use it for code development for the serial terminal project because of the problem with powering keyboard from the USB port. To test code I needed to disconnect the USB programming cable and connect external power supply. For this reason I had to write the program to EPROM each time I made new build iteration if I needed to test it with the keyboard. Therefore I was afraid I was going to wear down the EPROM prematurely by frequent writing and brick the project board.
On the project area I added serial interface, 5V power supply, keyboard mini-din socket, various headers and connections to VGA and micro SD which has a socket on the bottom side of the board. I may also add some sound generating hardware there (probably some simple stuff, few resistors and socket as the actual sound will be generated by software in the Propeller).
The actual MKHBC-8-R2 computer as it looks now. CPU/ROM/RAM card is on the left, UART to the right and RTC and banked RAM is still on the breadboard.
Keyboard interface is the same as on Parallax Human Interface Board. On my project board I just assembled the keyboard part, leaving the mouse connector off.
This is the serial interface circuit. Pretty simple and it works:
Code (not all, just main cog):{{
Homebrew Serial Keyboard + VGA Terminal (80x40) and SD card.
Marek Karcz (C) 2016, 2017. All rights reserved.
Free for personal and educational use.
Serial keyboard consists of matrix retro keyboard (TI 99/4A)
and AT89S52 controller + open collector clock and data output,
pretty much like a PS/2 keyboard, but the protocol is different.
See documentation in serkb_recv object.
This configuration uses P8X32A QuickStart Board + Human Interface Board
from Parallax INC.
Terminal will also work with PC keyboard and Keyboard object.
}}
CON
_clkmode = xtal1 + pll16x 'Use low crystal gain, wind up 16x
_xinfreq = 5_000_000 'External 5 MHz crystal on XI & XO
''_CLKFREQ = 80_000_000
SDA_pin = 26 'keyboard port on human interface board
SCL_pin = 27
'SDA_pin = 24 'keyboard port on human interface board
'SCL_pin = 25
SerialTx_pin = 5
SerialRx_pin = 7
SerialBaud = 9600
SerialMode = %0011
MAX_col = scr#cols - 1
MAX_row = scr#rows - 1
chrs = scr#cols * scr#rows
'MAX_col = 31
'MAX_row = 14
CRSBLDEL = 3000
BKSPC = 8
BKSPC_PC = $C8
CTRL_H_PC = $268
NUMLOCK_PC = $DF
NL = $0D
CR = $0A
CTRL_C = 3 ' CTRL-C from TI99/4A keyboard driver
CTRL_C_PC = $263 ' CTRL-C from PS/2 keyboard driver
CTRL_Z = 26
CTRL_Z_PC = $27A
SPC = $20
ESC_PC = $CB
ESC = $1B
F1_PC = $D0
CTRL_Q_PC = $271
CTRL_Q = $11
' Micro SD connections
CS = 3 ' Propeller Pin 3 - Set up these pins to match the Parallax Micro SD Card adapter connections.
DI = 2 ' Propeller Pin 2 - For additional information, download and refer to the Parallax PDF file for the Micro SD Adapter.
CLK = 1 ' Propeller Pin 1 - The pins shown here are the correct pin numbers for my Micro SD Card adapter from Parallax
D0 = 0 ' Propeller Pin 0 - In addition to these pins, make the power connections as shown in the following comment block.
OBJ
scr : "vga_hires_text_mk"
serkb : "serkb_recv" ' serkb_recv object is interchangeable
'serkb : "keyboard" ' with keyboard object - no more code
' changes are required, just swap them here
rs232 : "FullDuplexSerial_mk"
'serial : "Parallax Serial Terminal"
'Num : "numbers"
sdfat : "fsrw" ' r/w file system
streng : "ASCII0_STREngine_1" ' string library
DAT
str01 BYTE "Serial Keyboard + VGA Terminal (80x40).",NL,0
strFmVer BYTE "Firmware version 2.0.",NL,0
strCpr01 BYTE "Copyright (C) by Marek Karcz 2016,2017.",NL,0
strCpr02 BYTE "All rights reserved.",NL,0
str01_1 BYTE NL,"Press (at any time):",NL,NL,0
str02 BYTE " CTRL-C to Clear Screen,",NL,0
str03 BYTE " CTRL-H to Backspace/Delete,",NL,0
str04 BYTE " CTRL-Z to open Terminal Menu,",NL,0
str05 BYTE " CTRL-Q (F1) to see this help.",NL,0
strSdFnd BYTE NL,"SD card found. Open Terminal Menu to mount.",NL,0
strNoSd BYTE NL,"ERROR: There is no SD card.",NL,0
strSpaces BYTE " ",0
rdcmd BYTE "r ",0
VAR
long col, row
long rcv, key, prevkey
'long crsct
'sync long - written to -1 by VGA driver after each screen refresh
long sync
'screen buffer - could be bytes, but longs allow more efficient scrolling
long screen[chrs/4]
'row colors
word colors[MAX_row+1]
'cursor control bytes
byte cx0, cy0, cm0, cx1, cy1, cm1
byte fname[13]
byte staddr[5], endaddr[5]
byte memrdcmd[16], buf[80]
long sdcard_found
PUB Main | i
sdcard_found := -1
prevkey := 0
'crsct := CRSBLDEL
col := 0
row := 0
'Num.init
'serial.Start(115200)
rs232.Start(SerialRx_pin, SerialTx_pin, SerialMode, SerialBaud)
serkb.Start(SDA_pin, SCL_pin) ' Start the serial keyboard object
scr.start(16, @screen, @colors, @cx0, @sync)
'set up colors, clear screen
repeat i from 0 to MAX_row
colors[i] := %%0100_1310
repeat i from 0 to chrs - 1
screen.byte[i] := $20
ScrStr(@str01)
ScrStr(@strFmVer)
ScrStr(@strCpr01)
ScrStr(@strCpr02)
HelpInfo
waitcnt(cnt + clkfreq)
MountSD(FALSE)
'sdcard_found := \sdfat.mount_explicit(D0, CLK, DI, CS) ' Here we call the 'mount' method using the 4 pins described in the 'CON' section.
if sdcard_found => 0
ScrStr(@strSdFnd)
UnmountSD(FALSE)
rs232.RxFlush
repeat
key := serkb.Key
' conversions related to used driver (ti99/4a a.k.a. serkb_recv or ps/2 a.k.a. keyboard)
if key > 0
if key == CR
key := NL
if key == CTRL_C_PC
key := CTRL_C
if key == CTRL_Z_PC
key := CTRL_Z
if key == BKSPC_PC or key == CTRL_H_PC
key := BKSPC
if key == NUMLOCK_PC
key := 0
if key == ESC_PC
key := ESC
if key == CTRL_Q_PC or key == F1_PC
key := CTRL_Q
if key > 0
rs232.Tx(key & $ff)
'rcv := rs232.RxCheck
rcv := rs232.RxTime(20)
if rcv => 0
'serial.Str(STRING("Received character from RS232:"))
'serial.Dec(rcv)
'serial.NewLine
PrnChar(rcv & $ff)
prevkey := rcv & $ff
Cursor
PUB UnmountSD(verbose)
if verbose == TRUE
PrnChar(NL)
if sdcard_found => 0
sdfat.unmount
sdcard_found := -1
if verbose == TRUE
ScrStr(String("SD card has been unmounted successfully.",NL))
else
if verbose == TRUE
ScrStr(String("ERROR: Nothing to unmount. (already unmounted?)",NL))
PUB MountSD(verbose)
if verbose == TRUE
PrnChar(NL)
if sdcard_found < 0
sdcard_found := \sdfat.mount_explicit(D0, CLK, DI, CS) ' Here we call the 'mount' method using the 4 pins described in the 'CON' section.
if verbose == TRUE
if sdcard_found => 0
ScrStr(String("SD card has been mounted successfully.",NL))
else
ScrStr(String("ERROR: Unable to mount SD card, error code="))
ScrStr(streng.integerToHexadecimal(sdcard_found, 8))
PrnChar(NL)
else
if verbose == TRUE
ScrStr(String("ERROR: Nothing to mount. (already mounted?)",NL))
PUB HelpInfo
StrOut(@str01_1)
StrOut(@str02)
StrOut(@str03)
StrOut(@str04)
StrOut(@str05)
Cursor
PUB ScrStr(strptr)
repeat StrSize(strptr)
PrnChar(byte[strptr++])
PUB ScrOut(c)
screen.byte[row*(MAX_col+1) + col] := c
PUB StrOut(strptr)
repeat StrSize(strptr)
if byte[strptr] == NL
col := 0
IncRow
strptr++
else
ScrOut(byte[strptr++])
IncCol
PUB Cursor
cx0 := col
cy0 := row
cm0 := %010
' uncomment code below to enable own cursor implementation
' and comment code above
{{
crsct--
if crsct > CRSBLDEL / 2
ScrOut("|")
else
ScrOut("_")
if crsct == 0
crsct := CRSBLDEL
}}
PUB IncRow
row := row + 1
if row > MAX_row
row := MAX_row
ByteMove(@screen, @screen+MAX_col+1, chrs-MAX_col-1)
ByteFill(@screen+chrs-MAX_col-1, 32, MAX_col+1)
PUB IncCol
col := col + 1
if col > MAX_col
col := 0
IncRow
PUB DecCol
if col > 0
col := col - 1
PUB ReadSerialAndPrint(rdw)
repeat
'rcv := rs232.RxCheck
rcv := rs232.RxTime(rdw)
if rcv < 0
Quit
PrnChar(rcv & $ff)
PUB SDDir | n
if sdcard_found < 0
ScrStr(@strNoSd)
return
PrnChar(NL)
PrnChar(NL)
ScrStr(String("Directory:",NL))
PrnChar(NL)
sdfat.opendir
repeat
n := sdfat.nextfile(@fname)
if n < 0
Quit
ScrStr(@fname)
PrnChar(NL)
PUB GetStr(pstr, size) | n, q
n := 0
q := FALSE
repeat until q == TRUE
key := serkb.Key
if key > 0
if key == CR
key := NL
if key == CTRL_C_PC
key := CTRL_C
if key == CTRL_Z_PC
key := CTRL_Z
if key == BKSPC_PC
key := BKSPC
if key == NUMLOCK_PC
key := 0
case key
NL: byte[pstr+n] := 0
q := TRUE
BKSPC: if n > 0
n := n - 1
byte[pstr+n] := 0
OTHER: if key > 0 and n < size-1
byte[pstr+n] := key
n := n + 1
byte[pstr+n] := 0
col := 0
ScrStr(@strSpaces)
ScrOut(SPC)
col := 0
ScrStr(pstr)
Cursor
PUB EnterFileName '| n, q
PrnChar(NL)
ScrStr(String("Enter file name:",NL))
PrnChar(NL)
Cursor
GetStr(@fname, 13)
PUB SendFileFromSD2Serial | n, q
if sdcard_found < 0
ScrStr(@strNoSd)
return
SDDir
EnterFileName
PrnChar(NL)
ReadSerialAndPrint(0)
ScrStr(String("*** Loading file ***", NL))
waitcnt(cnt + clkfreq)
sdfat.popen(@fname, "r")
key := NL
rs232.Tx(key & $ff)
ReadSerialAndPrint(0)
repeat
key := sdfat.pgetc
if key < 0
Quit
if key == NL
key := 0
if key == CR
key := NL
if key > 0
rs232.Tx(key & $ff)
waitcnt(cnt + clkfreq/250)
if key == NL
waitcnt(cnt + clkfreq/6)
ReadSerialAndPrint(0)
ReadSerialAndPrint(0)
PrnChar(NL)
sdfat.pclose
PUB SaveMemory2FileSD | m, adrbeg, adrend, curaddr
if sdcard_found < 0
ScrStr(@strNoSd)
return
EnterFileName
PrnChar(NL)
ScrStr(String("Enter start address (hex DDDD, e.g: 0400):",NL))
PrnChar(NL)
Cursor
GetStr(@staddr, 5)
adrbeg := streng.hexadecimalToInteger(@staddr)
PrnChar(NL)
ScrStr(String("Enter end address (hex DDDD, e.g: 1000):",NL))
PrnChar(NL)
Cursor
GetStr(@endaddr, 5)
adrend := streng.hexadecimalToInteger(@endaddr)
PrnChar(NL)
Cursor
if adrend - adrbeg < 15
ScrStr(String("Address range must be greater than 14 bytes.",NL))
return
curaddr := adrbeg
sdfat.popen(@fname, "w")
repeat
memrdcmd[0] := 0
streng.stringCopy(@memrdcmd, @rdcmd)
streng.stringConcatenate(@memrdcmd, @staddr)
streng.stringConcatenate(@memrdcmd, String("-"))
curaddr := curaddr + 16
streng.stringConcatenate(@memrdcmd, streng.integerToHexadecimal(curaddr-1, 4))
streng.stringConcatenate(@memrdcmd, String(" ",NL))
' Send the memory read command
streng.stringToLowerCase(@memrdcmd)
ScrStr(String("Command: "))
ScrStr(@memrdcmd)
rs232.str(@memrdcmd)
' Read response from memory read command and save it to file
' line by line
m := 0
repeat
rcv := rs232.RxTime(20)
if rcv < 0
Quit
if rcv <> NL
buf[m++] := rcv
if m > 79
m := 0
buf[m] := 0
else
buf[m++] := NL
if m > 79
m := 0
buf[m++] := CR
if m > 79
m := 0
buf[m] := 0
if buf[0] == "w"
sdfat.pputs(@buf)
ScrStr(String("buf="))
ScrStr(@buf)
m := 0
' End of address range
if curaddr > adrend
Quit
streng.stringCopy(@staddr, streng.integerToHexadecimal(curaddr, 4))
sdfat.pclose
PrnChar(NL)
ScrStr(String("File saved.",NL))
PUB ListFileSD
if sdcard_found < 0
ScrStr(@strNoSd)
return
EnterFileName
PrnChar(NL)
sdfat.popen(@fname, "r")
repeat
rcv := sdfat.pgetc
if rcv < 0
Quit
PrnChar(rcv & $ff)
sdfat.pclose
PUB TermMenu | q
q := FALSE
MountSD(TRUE) ' SD card us mounted only while in Terminal Menu
repeat until q == TRUE
ScrStr(String("Terminal Menu:",NL))
PrnChar(NL)
ScrStr(String(" 1 - Send file from SD card to serial port.",NL))
ScrStr(String(" 2 - Save memory write commands to file on SD card.",NL))
ScrStr(String(" 3 - SD card directory.",NL))
ScrStr(String(" 4 - List file contents.",NL))
ScrStr(String(" 5 - Unmount SD card.",NL))
ScrStr(String(" 6 - Mount SD card.",NL))
ScrStr(String(" Q - Exit Menu",NL))
PrnChar(NL)
ScrStr(String("Your selection ? "))
repeat
key := serkb.Key
if key > 0
case key
"1" : SendFileFromSD2Serial
q := TRUE
"2" : SaveMemory2FileSD
q := TRUE
"3" : SDDir
"4" : ListFileSD
"5" : UnmountSD(TRUE)
"6" : MountSD(TRUE)
"q" : ScrStr(String("Quit.",NL))
q := TRUE
OTHER: ScrStr(String("Unknown menu option.",NL))
PrnChar(NL)
Quit
Cursor
ScrOut(SPC)
UnmountSD(TRUE) ' SD card is only mounted while in Terminal Menu
PUB ClrScr
ByteFill(@screen, SPC, chrs)
col := 0
row := 0
PUB IsDigit(c)
if c => "0" and c =< "9"
return TRUE
return FALSE
' Read digits from rs232, convert to number and return value.
' Th las read non-digit character code is remembered in key variable.
'
PUB RdNumSer(chars) : numval | c, ba
numval := -1
ba := chars
byte[chars] := 0
c := rs232.RxTime(20)
if IsDigit(c) == TRUE
repeat while IsDigit(c) == TRUE
byte[chars++] := c
byte[chars] := 0
c := rs232.RxTime(20)
key := c
numval := streng.decimalToInteger(ba)
PUB PrnChar(c) | n, lcol, lrow
if c == "[" and prevkey == ESC
lrow := RdNumSer(@buf)
if lrow => 0
case key
"J" : case lrow
0 : ' write code here to clear from cursor to end of screen
return
1 : ' write code here to clear from cursor to begin of screen
return
2 :
'StrOut(String("[CLS]"))
ClrScr
return
OTHER : return
";" : 'StrOut(String("[;]"))
lcol := RdNumSer(@buf)
if lcol => 0
if key == "H" or key == "f"
col := lcol - 1
row := lrow - 1
if col < 0
col := 0
if row < 0
row := 0
return
OTHER : return
if c == NL
'ScrOut(SPC) 'uncomment only if own cursor impl. is used
col := 0
IncRow
if c == CTRL_C 'CTRL-C, Clear Screen
rs232.Tx(BKSPC) 'Send backspace to delete control character
ClrScr
rs232.Tx(NL) 'Send NL to serial
if c == CTRL_Q
HelpInfo
rs232.Tx(NL) 'Send NL to serial
if c == BKSPC
ScrOut(SPC)
DecCol
if c == CTRL_Z 'CTRL-Z, Menu
rs232.Tx(BKSPC) 'Send backspace to delete control character
ScrOut(SPC)
col := 0
IncRow
IncRow
TermMenu
IncRow
rs232.Tx(NL) 'Send NL to serial
if c > 31 and c < 127
ScrOut(c)
IncCol
{{
if c == ESC
StrOut(String("[ESC]"))
}}
Cursor
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.