CPU keyboard and display control with cpu3 instruction set

(This document can be downloaded at the lab3 homepage
http://www.cp.eng.chula.ac.th/faculty/pjw/teaching/lab3  )

This note explains how to write cpu3 assembly language to drive keyboard and display.  Your previous experiment dealt with "hard-wired" keyboard and display, unlike the system you are now building.  Your system have a CPU and the keyboard and display become "peripheral" which can be controlled by a program in your CPU assembly language.

Port names

Let give the name of the ports that dealt with keyboard and display.  The diaplay has 2 ports : ROW is the output to select the digit to be displayed, COL is the output port that drive the 7 segment display.  The segment pattern to be displayed for each digit is stored in  variables named D1, D2, D3, D4.

The keyboard is a matrix type 4x4.  The output port KEYOUT drives the scan line of key matrix.  The KEYIN input port reads the signal signifies key pressed.  When a key is pressed the circuit is closed between KEYOUT and KEYIN.  Normally, KEYIN is HI.  The output from KEYOUT selects a line to be LO.  If a key is pressed on that line KEYIN will detect a LO.

Assembly language

For assembly programming, we will use only the basic instructions without index addressing mode (which make programming a bit cumbersome) but will make it easier on your cpu implementation.  Without indexing, one cannot do a table look up.  The only way to implement a table look up is by using "if key then value".

Overview of the program

We will begin with top down design of the keyboard & display program.

pgm
     initialize
loop call display  ;;  send D1.. D4 to the display
     call getkey       ;;  scan if any key pressed
     if no-key-pressed goto loop
     call key2seg      ;; convert keyvalue to display in DIN
     D4 = D3           ;; shift diplay digit left  D4..D1
     D3 = D2
     D2 = D1
     D1 = DIN
     goto loop

Display routine


Fig the display wiring and ports.

The easiest is to display all 4 digits in one call.  However, the on-time of each display will be short and also each digit will not be turn on by equal time as the last digit will be on longer than the rest. (see fig )

Fig display all 4 digits in one call, the last digit is on longer than the rest

One solution is to display one digit per call and switch to other digits.  This way, every digit will be on much longer and all digits will be on with equal time.  We need to keep the state of the display routine, let DSTATE stores the state of display.  DSTATE = {0,1,2,3} signifies the digit 1, 2, 3, 4 is being displayed.  Assume the 7 segment data to be displayed are already in D1..D4.  Let the signal HI drive a digit.

Fig display one digit per call

display
  if DSTATE == 0 { out ROW 0001; out COL D1 }
  if DSTATE == 1 { out ROW 0010; out COL D2 }
  if DSTATE == 2 { out ROW 0100; out COL D3 }
  if DSTATE == 3 { out ROW 1000; out COL D4 }
  DSTATE = DSTATE + 1
  if DSTATE > 3 { DSTATE = 0 }

To display a digit, the input value (key pressed) must be converted to a 7 segment display data.  Let the input value stored in KIN = 0..15  and 16 for key not pressed, the 7 segment patterns are in c7segX, X = 0..15.  The result is in SEG.

key2seg
  if KIN == 0 { SEG = c7seg0 }
  if KIN == 1 { SEG = c7seg1 }
  . . .
  if KIN == 15 { SEG = c7seg15 }

Scan keyboard

To simplify, this routine will key all keys in one call.  If no-key-pressed return.  If key-pressed, debounce that key and return.  The return value is 0..15 and 16 if no-key-pressed.  The routine "mapkey"  maps the key to value.

fig keyboard wiring and ports

scankey
  out KEYOUT 1110
  KIN = in KEYIN
  if key-pressed goto debounce
  out KEYOUT 1101
  KIN = in KEYIN
  if key-pressed goto debounce
  out KEYOUT 1011
  KIN = in KEYIN
  if key-pressed goto debounce
  out KEYOUT 0111
  KIN = in KEYIN
  if key-pressed goto debounce
  return no-key-pressed

The debounce routine will allow the mechanical vibration of key to settle down before registers the key signal.  It performs delay and recheck the signal.  If the same key is encountered twice in succession, that key is registered.  The debounce loop is executed 6 times before return with no-key-pressed.

Fig key debounce

;; rep counts same key repeat, max set to 6
debounce
     rep = 0;  max = 0
loop previous = KIN
     call delay
     KIN = in KEYIN
     max = max + 1
     if max > 6 return no-key-pressed
     if previous == KIN {
       rep = rep + 1
       if rep == 2 return key-pressed
     }
     goto loop

How to do "delay"?  By repeat some instruction many times.  The instruction that takes time is PUSH and POP.  For example

:DELAY
       CLA
:LOOP  PSH
       POP
       ADD #1
       JNZ LOOP   ;; repeat until A overflow = 0

The mechanical keyboard needs about 10 ms delay time (repeat 6 times will be at most 60 ms)  The actual number of repetition in this loop depends on the implementation of the student's design of CPU3.

The last piece of code you need to write is to map the key pressed to the value 0..15 and 16 if no-key-pressed.  There are two variables in "scankey" :  KEYOUT, KIN.
KEYOUT = { 1110, 1101, 1011, 0111 }.  KIN = { 1111 no-key-pressed, 1110, 1101, 1011, 0111 when some key is pressed }.  Without indexing, we use arithmetic to convert these bit patterns into unique numbers.  The main idea is to map { 1110, 1101, 1011, 0111 } to { 0, 1, 2, 3 }  by using if .. then.  The unique number then can be calculated by 4 * KIN' + KEYOUT'.  The multiply by 4, we use shift left 2 bits.  The result will be 0..15 when key-pressed.  We need to check for the key no-key-pressed and return the value 16.

mapkey
    if KIN == 1111 then return 16
    k1 = map(KIN)
    k2 = map(KEYOUT)
    return k1*4 + k2

How to write assembly language of CPU3 ?

Just to brush up your memory on assembly language programming.  I will show some examples of mapping between pseudo code and CPU3 assembly language.  Assume no index addressing mode hence only 8-bit quantity in all instructions.
(notation for addressing mode :   # immediate, $ direct, + index )

 variable = constant

 LDA #CONST
 STA $VAR

 variable = variable + 1

 LDA $VAR
 ADD #1
 STA $VAR

 variable1 = variable2

 LDA $VAR2
 STA $VAR1

 if  variable == constant then . . .

 LDA $VAR
 XOR #CONST
 JZ . . .

Using these basic constructions you can write all the required code.  I will show an example of assembly code of CPU3 on the display routine.

;;  Example of assembly code for CPU3

ROW DEFINE   PORT_NUMBER
COL DEFINE   PORT_NUMBER

DATA SEGMENT

DSTATE DB 0
D1     DB 0
D2     DB 0
D3     DB 0
D4     DB 0

CODE SEGMENT

DISPLAY
     LDA $DSTATE ;;  if dstate == 0
     XOR #0
     JZ  DS0
 . . .
DS0  LDA #0001 ;;  then out row 0001
     OUT $ROW
     LDA $D1
     OUT $COL ;;  out col D1
     JMP DS5
 . . .
DS5  LDA $DSTATE ;;  dstate +1
     ADD #1
     XOR #4  ;;  if dstate == 4
     JZ DS6  ;;  then dstate = 0
     LDA $DSTATE
     ADD #1
DS6  STA $DSTATE
END