前回の記事ではCOSMACで音をだしてみましたが、次はCOSMACで文字を表示してみます。
今回製作したCPU基板には4bitのバスがでていますので、これをHD44780インターフェース準拠のLCDに接続してみました。
回路図は以下のようになります。ついでにユーザスイッチとリセットスイッチもつけました。

オリジナルのCPU回路ではTPB信号がコネクタに出力されていないので、空きピンを使ってジャンパー線で接続することにします。
まずはブレッドボードでプロトタイプを製作しました。この写真ではすでに表示プログラムが動いています。

LCDの表示プログラムは次のようになりました。
0000- 1 *
0000- 2 * SC1602 LCD Display program 1 for COSMAC
0000- 3 * SB-Assembler
0000- 4 *
0000- 5 .CR 1802 ;To load the 1802 cross overlay
0000- 6 .OR $0000
0000- 7 *
0000- 8 * CDP1802 SC1602LCD
0000- 9 * TPB---AND----> E(6)
0000- 10 * N2-----+
0000- 11 * Q -----------> RS(4)
0000- 12 * BUS0 --------> DB4(11)
0000- 13 * BUS1 --------> DB5(12)
0000- 14 * BUS2 --------> DB6(13)
0000- 15 * BUS3 --------> DB7(14)
0000- 16 * GND--> R/W(5)
0000- 17 * GND--> GND(2)
0000- 18 * +5V--> VDD(1)
0000- 19 *
0000-30 16 20 ( 2) START BR MAIN
0002- 21 *
0002- 22 *---------------------------
0002- 23 * LCD Initialize Data(4bit mode, RS=0)
0002- 24 *---------------------------
0002-03 25 INTDAT .DB $03 ;Function set (Interface is 8bits long.)
0003- 26 * ;wait 4.1ms
0003-03 27 .DB $03 ;Function set (Interface is 8bits long.)
0004- 28 * ;wait 100us
0004-03 29 .DB $03 ;Function set (Interface is 8bits long.)
0005- 30 * ;wait 40us
0005-02 31 .DB $02 ;Function set (Set interface to be 4 bits long.)
0006- 32 * ;wait 40us
0006-02 33 .DB $02 ;Function set (2Line mode)
0007-08 34 .DB $08 ;
0008- 35 * ;wait 40us
0008-00 36 .DB $00 ;Display OFF, Cursor OFF, Blink OFF
0009-08 37 .DB $08 ;
000A- 38 * ;wait 40us
000A-00 39 .DB $00 ;Clear Display
000B-01 40 .DB $01 ;
000C- 41 * ;wait 1.64ms
000C-00 42 .DB $00 ;Entry Mode Set(Cursor right, shift off)
000D-06 43 .DB $06 ;
000E- 44 * ;wait 40us
000E-00 45 .DB $00 ;Display ON, Cursor OFF, Blink OFF
000F-0C 46 .DB $0C ;
0010- 47 * ;wait 40us
0010- 48 *---------------------------
0010- 49 * LCD Display data(4bit mode, RS=1)
0010- 50 *---------------------------
0010-03 51 .DB $03 ;"1"
0011-01 52 .DB $01 ;
0012- 53 * ;wait 40us
0012-03 54 .DB $03 ;"2"
0013-02 55 .DB $02 ;
0014- 56 * ;wait 40us
0014-03 57 .DB $03 ;"3"
0015-03 58 .DB $03 ;
0016- 59 * ;wait 40us
0016- 60 *----------------------
0016- 61 * Main routine
0016- 62 *----------------------
0016-7A 63 ( 2) MAIN REQ ;RS=0
0017-F8 02 64 ( 2) LDI #INTDAT ;INTDAT -> D
0019-A3 65 ( 2) PLO 3 ;D -> R(3).0
001A-E3 66 ( 2) SEX 3 ;3 -> X
001B- 67 *
001B- 68 * LCD Initrize
001B- 69 *
001B- 70 * Function set (Interface is 8bits long.)
001B- 71 *
001B-64 72 ( 2) OUT 4 ;M(R(X))->BUS, R(X)++
001C- 73 *
001C- 74 * Wait 4.1ms
001C- 75 *
001C-F8 40 76 ( 2) LDI #64 ;Load immediate value 170 into 0
001E-A5 77 ( 2) PLO 5 ;Put D in R5,0
001F-25 78 ( 2) LOOP1 DEC 5 ;Decrement R5 by 1 over 8 bits
0020-85 79 ( 2) GLO 5 ;Get R5.0 to test
0021-3A 1F 80 ( 2) BNZ LOOP1 ;If R5.0 != 0, branch to LOOP
0023- 81 *
0023- 82 * Function set (Interface is 8bits long.)
0023- 83 *
0023-64 84 ( 2) OUT 4 ;M(R(X))->BUS, R(X)++
0024- 85 *
0024- 86 * Wait 100us
0024- 87 *
0024-F8 02 88 ( 2) LDI #2 ;Load immediate value 2 into 0
0026-A5 89 ( 2) PLO 5 ;Put D in R5,0
0027-25 90 ( 2) LOOP2 DEC 5 ;Decrement R5 by 1 over 8 bits
0028-85 91 ( 2) GLO 5 ;Get R5.0 to test
0029-3A 27 92 ( 2) BNZ LOOP2 ;If R5.0 != 0, branch to LOOP
002B- 93 *
002B- 94 *
002B- 95 *
002B-F8 08 96 ( 2) LDI #8 ;Loop 8 count
002D-A5 97 ( 2) PLO 5 ;D -> R(5).0
002E-64 98 ( 2) LOOP3 OUT 4 ;M(R(3))->BUS, R(3)++
002F-25 99 ( 2) DEC 5 ;R(5).0--
0030-85 100 ( 2) GLO 5 ;R(5).0 -> D
0031-3A 2E 101 ( 2) BNZ LOOP3
0033- 102 *
0033- 103 * Wait 1.64ms
0033- 104 *
0033-F8 20 105 ( 2) LDI #32 ;Load immediate value 32 into 0
0035-A5 106 ( 2) PLO 5 ;Put D in R5,0
0036-25 107 ( 2) LOOP4 DEC 5 ;Decrement R5 by 1 over 8 bits
0037-85 108 ( 2) GLO 5 ;Get R5.0 to test
0038-3A 36 109 ( 2) BNZ LOOP4 ;If R5.0 != 0, branch to LOOP
003A- 110 *
003A- 111 * Send Enrty Mode Set/Set Address
003A- 112 *
003A-F8 04 113 ( 2) LDI #4 ;Loop 4 count
003C-A5 114 ( 2) PLO 5 ;D -> R(5).0
003D-64 115 ( 2) LOOP5 OUT 4 ;M(R(3))->BUS, R(3)++
003E-25 116 ( 2) DEC 5 ;R(5).0--
003F-85 117 ( 2) GLO 5 ;R(5).0 -> D
0040-3A 3D 118 ( 2) BNZ LOOP5
0042- 119 *
0042- 120 * SET Register Select
0042- 121 *
0042-7B 122 ( 2) SEQ ;RS=1
0043- 123 *
0043- 124 * Write data to RAM
0043- 125 *
0043-F8 06 126 ( 2) LDI #6 ;Loop 6 count
0045-A5 127 ( 2) PLO 5 ;D -> R(5).0
0046-64 128 ( 2) LOOP6 OUT 4 ;M(R(3)) -> BUS; R(3)++
0047-25 129 ( 2) DEC 5 ;R(5).0--
0048-85 130 ( 2) GLO 5 ;R(5).0 -> D
0049-3A 46 131 ( 2) BNZ LOOP6
004B- 132 *
004B- 133 * RESET Register Select
004B- 134 *
004B-7A 135 ( 2) REQ ;RS=0
004C- 136 *
004C-30 4C 137 ( 2) STOP BR STOP ;HALT
004E- 138 *
004E- 139 .EN
プログラムは0000から004Dですので、77バイトしかないのですが、これをトグルスイッチで入力するのは結構疲れます。プログラムが間違っているのか、入力ミスなのかという問題切り分けにも一苦労です。
そこで、このプログラムには同じような処理がいくつもあるので、それをサブルーチンにすれば少しはコードが短くなるかなと試してみました。SEP命令でプログラムカウンタとなるレジスタをR7またはR8に切り替えることでそれぞれのサブルーチンにジャンプします。サブルーチンから戻る場合はジャンプ前に使っていたR3レジスタをプログラムカウンタに設定することで元にもどります。サブルーチンのエントリアドレスの直前にSEP 3を置いている理由は、R7やR8レジスタをサブルーチンのエントリアドレスに再設定するためのテクニックです。
サブルーチンを使ったプログラムは次のようになりました。
0000- 1 *
0000- 2 * SC1602 LCD Display program 1 for COSMAC
0000- 3 * SB-Assembler
0000- 4 *
0000- 5 .CR 1802 ;To load the 1802 cross overlay
0000- 6 .OR $0000
0000- 7 *
0000- 8 * CDP1802 SC1602LCD
0000- 9 * TPB---AND----> E(6)
0000- 10 * N2-----+
0000- 11 * Q -----------> RS(4)
0000- 12 * BUS0 --------> DB4(11)
0000- 13 * BUS1 --------> DB5(12)
0000- 14 * BUS2 --------> DB6(13)
0000- 15 * BUS3 --------> DB7(14)
0000- 16 * GND--> R/W(5)
0000- 17 * GND--> GND(2)
0000- 18 * +5V--> VDD(1)
0000- 19 *
0000-F8 1E 20 ( 2) START LDI #MAIN
0002-A3 21 ( 2) PLO 3
0003-F8 3C 22 ( 2) LDI #WAIT1
0005-A7 23 ( 2) PLO 7
0006-F8 44 24 ( 2) LDI #SEND1
0008-A8 25 ( 2) PLO 8
0009- 26 * LDI #0
0009- 27 * PHI 3
0009- 28 * PHI 7
0009- 29 * PHI 8
0009- 30 *
0009-D3 31 ( 2) SEP 3 ; Jump MAIN
000A- 32 *
000A- 33 *---------------------------
000A- 34 * LCD Initialize Data(4bit mode, RS=0)
000A- 35 *---------------------------
000A-03 36 INTDAT .DB $03 ;Function set (Interface is 8bits long.)
000B- 37 * ;wait 4.1ms
000B-03 38 .DB $03 ;Function set (Interface is 8bits long.)
000C- 39 * ;wait 100us
000C-03 40 .DB $03 ;Function set (Interface is 8bits long.)
000D- 41 * ;wait 40us
000D-02 42 .DB $02 ;Function set (Set interface to be 4 bits long.)
000E- 43 * ;wait 40us
000E-02 44 .DB $02 ;Function set (2Line mode)
000F-08 45 .DB $08 ;
0010- 46 * ;wait 40us
0010-00 47 .DB $00 ;Display OFF, Cursor OFF, Blink OFF
0011-08 48 .DB $08 ;
0012- 49 * ;wait 40us
0012-00 50 .DB $00 ;Clear Display
0013-01 51 .DB $01 ;
0014- 52 * ;wait 1.64ms
0014-00 53 .DB $00 ;Entry Mode Set(Cursor right, shift off)
0015-06 54 .DB $06 ;
0016- 55 * ;wait 40us
0016-00 56 .DB $00 ;Display ON, Cursor OFF, Blink OFF
0017-0C 57 .DB $0C ;
0018- 58 * ;wait 40us
0018- 59 *---------------------------
0018- 60 * LCD Display data(4bit mode, RS=1)
0018- 61 *---------------------------
0018-03 62 .DB $03 ;"1"
0019-01 63 .DB $01 ;
001A- 64 * ;wait 40us
001A-03 65 .DB $03 ;"2"
001B-02 66 .DB $02 ;
001C- 67 * ;wait 40us
001C-03 68 .DB $03 ;"3"
001D-03 69 .DB $03 ;
001E- 70 * ;wait 40us
001E- 71 *----------------------
001E- 72 * Main routine
001E- 73 *----------------------
001E-7A 74 ( 2) MAIN REQ ;RS=0
001F-F8 0A 75 ( 2) LDI #INTDAT ;INTDAT -> D
0021-A4 76 ( 2) PLO 4 ;D -> R(4).0
0022-E4 77 ( 2) SEX 4 ;4 -> X
0023- 78 *
0023- 79 * LCD Initrize
0023- 80 *
0023- 81 * Function set (Interface is 8bits long.)
0023- 82 *
0023-64 83 ( 2) OUT 4 ;M(R(X))->BUS, R(X)++
0024- 84 *
0024- 85 * Wait 4.1ms
0024- 86 *
0024-F8 40 87 ( 2) LDI #64 ;Load immediate value 170 into 0
0026-D7 88 ( 2) SEP 7 ;Call WaitSub
0027- 89 *
0027- 90 * Function set (Interface is 8bits long.)
0027- 91 *
0027-64 92 ( 2) OUT 4 ;M(R(X))->BUS, R(X)++
0028- 93 *
0028- 94 * Wait 100us
0028- 95 *
0028-F8 02 96 ( 2) LDI #2 ;Load immediate value 2 into 0
002A-D7 97 ( 2) SEP 7 ;Call WaitSub
002B- 98 *
002B- 99 * Send Initrize command (8byte)
002B- 100 *
002B-F8 08 101 ( 2) LDI #8 ;Send 8 byte
002D-D8 102 ( 2) SEP 8 ;Call SEND
002E- 103 *
002E- 104 * Wait 1.64ms
002E- 105 *
002E-F8 20 106 ( 2) LDI #32 ;Load immediate value 32 into 0
0030-D7 107 ( 2) SEP 7
0031- 108 *
0031- 109 * Send Enrty Mode Set/Set Address
0031- 110 *
0031-F8 04 111 ( 2) LDI #4 ;Send 4 byte
0033-D8 112 ( 2) SEP 8 ;Call SEND
0034- 113 *
0034- 114 * SET Register Select
0034- 115 *
0034-7B 116 ( 2) SEQ ;RS=1
0035- 117 *
0035- 118 * Write data to RAM
0035- 119 *
0035-F8 06 120 ( 2) LDI #6 ;Send 6 byte
0037-D8 121 ( 2) SEP 8 ;Call SEND
0038- 122 *
0038- 123 * RESET Register Select
0038- 124 *
0038-7A 125 ( 2) REQ ;RS=0
0039- 126 *
0039-30 39 127 ( 2) STOP BR STOP ;HALT
003B- 128 *
003B- 129 * WAIT SUB ROUTINE
003B- 130 *
003B-D3 131 ( 2) EXIT1 SEP 3
003C-A5 132 ( 2) WAIT1 PLO 5 ;Put D in R5,0
003D-25 133 ( 2) LOOP2 DEC 5 ;Decrement R5 by 1 over 8 bits
003E-85 134 ( 2) GLO 5 ;Get R5.0 to test
003F-3A 3D 135 ( 2) BNZ LOOP2 ;If R5.0 != 0, branch to LOOP
0041-30 3B 136 ( 2) BR EXIT1
0043- 137 *
0043- 138 * SEND n byte SUB ROUTINE
0043- 139 *
0043-D3 140 ( 2) EXIT2 SEP 3
0044-A5 141 ( 2) SEND1 PLO 5 ;D -> R(5).0
0045-64 142 ( 2) LOOP6 OUT 4 ;M(R(X)) -> BUS; R(X)++
0046-25 143 ( 2) DEC 5 ;R(5).0--
0047-85 144 ( 2) GLO 5 ;R(5).0 -> D
0048-3A 45 145 ( 2) BNZ LOOP6
004A-30 43 146 ( 2) BR EXIT2
004C- 147
004C- 148 .EN
プログラムの大きさは0000から004Bとなったので2バイトは短くできましたが、結局サブルーチン側である程度のコードが必要なためこの大きさでは効果がでませんでした。現在のCPUには実装されているスタックは便利だなと実感できました。
今回の実験でCDP1802 COSMACにLCDを接続することができました。せっかくですので、これまで製作したミニ電子オルガンとLCDの専用基板を作って、Arduinoのように積み重ねて利用できるようにしてみます。
