Wednesday, May 31, 2017

MKHBC-8-Rx, RTC / Banked RAM PCB.

Finally!
I started to work on RTC / Banked RAM PCB.
I use the same technique as before - point to point soldering on a prototype PCB. I have been thinking of perhaps doing the first ever PCB project in software (e.g.: Express PCB) and having my PCB done in China, but I decided this is relatively easy with only 139 connections / 278 or so solder points :-)
Anyway, now I have a nice gadget that makes this work a bit easier and more pleasant - a PCB holder. Check it out!




I also made many updates to my Parallax Propeller based character terminal device. I even implemented small subset of ANSI terminal commands (really small subset - just clear screen and locate cursor commands) and mass storage on micro SD card. The mass storage is quite primitive, allowing to load / save plain text files (with monitor commands or BASIC listings), but it works. This method allows 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 this system, store on SD card and then send them to the MKHBC-8-R2 system - a primitive and slow way of of loading or saving data, but effective, until I have a real mass storage device implemented.

The project comes together very nicely.
Here is the SPIN code (only the main part, not the drivers) for character terminal device:

{{

  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 = 11

  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.
' The last 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

   
I didn't yet publish my code on GitHub, but will do soon, please stay tuned.
I develop / test the software on Parallax Propeller QuickStart board with Parallax Human Interface + serial port shield. However the final device will be assembled on a Parallax Propeller USB Project Board. The final version of the I/O device I actually want to implement as a card interfacing with the system via I/O expansion bus rather than serial port. This is not the last word in this regard, but I needed to start with something simpler to implement to get familiar with the Propeller chip.


Startup message of character device, high resolution VGA.

Development / test system.

Serial port shield for Parallax Human Interface board.

Loading Tiny BASIC from SD card. The file 'tinybas' is just a plain text file with monitor memory write commands generated by my utility on a PC from the TB binary image.

Tiny BASIC loaded.

Loaded BASIC listing of a game from another file on SD card.

I have a kingdom to run. See ya later! ;-)

Parallax Propeller Project USB board with some of my additions like serial port, keyboard connector port, uSD card socket (on the bottom, but connections header is on top) where terminal will be implemented.
This is it for today.
Thank you for visiting my blog and see you in the next episode (sooner rather than later, hopefully).

MK 2017-06-01