目次
前
次
表示器用ファームウエア開発
Z80を利用したワンボードマイコン基板に
載せるファームウエアの開発依頼が来た
とき、7セグメントLEDを利用しました。
打ち合わせで、今回は7セグメントLEDのみの
表示でとクライアントから要求仕様が出ました。
将来は2行16桁程度のLCDを接続できるようにと
オプション開発の依頼も受けました。
7セグメントLEDを4つとLCDを接続して、表示する
ためのファームウエアの開発手順を説明します。
守秘義務があるので、クライアントが示した
仕様とは、異なる内容での開発手順とします。
7セグメントLED表示
クライアントが作成していたブロック図は、以下でした。
4ビットのレジスタを4つ並べておき
16進数で4つのレジスタに数値を出力
すると、4つの7セグメントLEDに表示
ができるよう仕様でした。
どういう環境で使うのかを聞いてみると
電源として充電池を使い、約1週間は
測定値から、計算式で変換した数字を
表示するとか。
充電池を使うなら、基板に載せるIC数を
減らし、表示をダイナミック点灯にした
方がよいと提案しました。
こちらか提案したブロック図は、以下です。
Z80内部で、数値から数字のパターンに変更する
デコーダを定義し、その内容を時間ごとに4つ
の7セグメントLEDに転送します。
ダイナミック点灯は、1桁あたり10msの間LEDに
電流を流せば、フリッカなし、ファントムなし
で視認できます。
ICの個数が減り、動作時電力が減るなら
と、了解を貰えました。
計算式は、守秘義務があるので、表示に関係する
内容だけ説明していきます。
はじめに、Cでソースを記述し、アセンブリ言語に
変換していきます。
タイマー割込みで、10msごとにフラグが設定される
ようにし、7セグメントLEDへの表示と計算を担当
するシーケンサを作成します。
if ( tflag == ON ) {
/* clear flag */
tflag = OFF ;
/* call sequencer */
show_value_handler();
}
タイマー割込みに関しては、後で考えるとして
シーケンサshow_value_handlerの動作を、先に
決めます。
次のシーケンスで、動かすことにしました。
- 1000の位の数値表示
- 100の位の数値表示
- 10の位の数値表示
- 1の位の数値表示
- 測定値から4けたの10進に変換
- 1に戻る
シーケンサを使うので、変数stateを用意し
大まかな動作を記述します。
void show_value_handler()
{
/* impress */
switch (state) {
/* show 1000 */
case 0 : break ;
/* show 100 */
case 1 : break ;
/* show 10 */
case 2 : break ;
/* show 1 */
case 3 : break ;
/* calculate */
case 4 : break ;
/* others */
default : break ;
}
/* update */
state++ ;
/* judge */
if ( state == 5 ) { state = 0 ; }
}
数値を4桁の数字に変換する処理を考えます。
この変換には、定石があり、次のようにします。
for ( i = 0 ; i < 4 ; i++ ) {
*(result+3-i) = x % 10 ;
x /= 10 ;
}
配列resultには、数値が入っているので
7セグメントLEDの表示パターンに変換
するため、テーブル参照を使います。
led_pat[10] = {
0xfc , /* abcd_efgD = 1111_1100 */
0x60 , /* abcd_efgD = 0110_0000 */
0xda , /* abcd_efgD = 1101_1010 */
0xf2 , /* abcd_efgD = 1111_0010 */
0x66 , /* abcd_efgD = 0110_0110 */
0xb6 , /* abcd_efgD = 1011_0110 */
0xbe , /* abcd_efgD = 1011_1110 */
0xe4 , /* abcd_efgD = 1110_0100 */
0xfe , /* abcd_efgD = 1111_1110 */
0xf6 /* abcd_efgD = 1111_0110 */
} ;
2つの処理内容が確定したので、シーケンサを
書き換えます。
typedef unsigned char UBYTE ;
void show_value_handler()
{
UBYTE i ;
/* impress */
switch (state) {
/* show 1000 */
case 0 : tmp = led_pat[ *(result+0) ] ; break ;
/* show 100 */
case 1 : tmp = led_pat[ *(result+1) ] ; break ;
/* show 10 */
case 2 : tmp = led_pat[ *(result+2) ] ; break ;
/* show 1 */
case 3 : tmp = led_pat[ *(result+3) ] ; break ;
/* calculate */
case 4 : for ( i = 0 ; i < 4 ; i++ ) {
*(result+3-i) = x % 10 ;
x /= 10 ;
}
break ;
/* others */
default : break ;
}
/* update */
state++ ;
/* judge */
if ( state == 5 ) { state = 0 ; }
}
ブロック図をみると、7セグメントLEDの点灯パターンを
出力するのと、同時に選択用の信号も出ています。
これをシーケンサの中に入れます。
typedef unsigned char UBYTE ;
void show_value_handler()
{
UBYTE i ;
/* impress */
switch (state) {
/* show 1000 */
case 0 : tmp = led_pat[ *(result+0) ] ; break ;
/* enable 1000 bit*/
case 1 : sel = 0x08 ; break ;
/* show 100 */
case 2 : tmp = led_pat[ *(result+1) ] ; break ;
/* enable 100 bit*/
case 3 : sel = 0x04 ; break ;
/* show 10 */
case 4 : tmp = led_pat[ *(result+2) ] ; break ;
/* enable 10 bit*/
case 5 : sel = 0x02 ; break ;
/* show 1 */
case 6 : tmp = led_pat[ *(result+3) ] ; break ;
/* enable 1 bit*/
case 7 : sel = 0x01 ; break ;
/* calculate */
case 8 : for ( i = 0 ; i < 4 ; i++ ) {
*(result+3-i) = x % 10 ;
x /= 10 ;
}
break ;
/* others */
default : break ;
}
/* update */
state++ ;
/* judge */
if ( state == 9 ) { state = 0 ; }
}
Z80のI/Oに選択信号と7セグメントLEDの点灯パターン
を出力したいので、outportb命令を加えます。
typedef unsigned char UBYTE ;
void show_value_handler()
{
UBYTE tmp ;
UBYTE i ;
/* impress */
switch (state) {
/* show 1000 */
case 0 : tmp = led_pat[ *(result+0) ] ;
outportb( PIOAD , tmp );
break ;
/* enable 1000 bit*/
case 1 : tmp = 0x08 ;
outportb( PIOBD , tmp );
break ;
/* show 100 */
case 2 : tmp = led_pat[ *(result+1) ] ;
outportb( PIOAD , tmp );
break ;
/* enable 100 bit*/
case 3 : tmp = 0x04 ;
outportb( PIOBD , tmp );
break ;
/* show 10 */
case 4 : tmp = led_pat[ *(result+2) ] ;
outportb( PIOAD , tmp );
break ;
/* enable 10 bit*/
case 5 : tmp = 0x02 ;
outportb( PIOBD , tmp );
break ;
/* show 1 */
case 6 : tmp = led_pat[ *(result+3) ] ;
outportb( PIOAD , tmp );
break ;
/* enable 1 bit*/
case 7 : tmp = 0x01 ;
outportb( PIOBD , tmp );
break ;
/* calculate */
case 8 : for ( i = 0 ; i < 4 ; i++ ) {
*(result+3-i) = x % 10 ;
x /= 10 ;
}
tmp = 0x00 ;
outportb( PIOBD , tmp );
break ;
/* others */
default : break ;
}
/* update */
state++ ;
/* judge */
if ( state == 9 ) { state = 0 ; }
}
Cで記述したなら、MiniCでコンパイルして関数show_value_handler
のアセンブリ言語コードがどうなっているか、見てみます。
__show_value_handler:
DEC SP
PUSH BC
PUSH BC
LD A,(__state)
CALL mc_sxt
JP mc_75
mc_76:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,__led_pat
PUSH HL
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,00H
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
LD A,L
LD (DE),A
LD HL,1DH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_77:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,08H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_78:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,__led_pat
PUSH HL
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,01H
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
LD A,L
LD (DE),A
LD HL,1DH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_79:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,04H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_80:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,__led_pat
PUSH HL
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,02H
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
LD A,L
LD (DE),A
LD HL,1DH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_81:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,02H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_82:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,__led_pat
PUSH HL
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,03H
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
LD A,L
LD (DE),A
LD HL,1DH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_83:
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,01H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_84:
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,00H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,03E8H
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,(__x)
PUSH HL
LD HL,03E8H
POP DE
CALL mc_div
EX DE,HL
LD (__x),HL
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,01H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,64H
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,(__x)
PUSH HL
LD HL,64H
POP DE
CALL mc_div
EX DE,HL
LD (__x),HL
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,02H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,0AH
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,03H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,0AH
POP DE
CALL mc_div
EX DE,HL
POP DE
LD A,L
LD (DE),A
LD HL,04H
ADD HL,SP
PUSH HL
LD HL,00H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,06H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
JP mc_74
mc_85:
JP mc_74
JP mc_74
mc_75:
CALL MC_SWITCH
DEFW mc_76,00H
DEFW mc_77,01H
DEFW mc_78,02H
DEFW mc_79,03H
DEFW mc_80,04H
DEFW mc_81,05H
DEFW mc_82,06H
DEFW mc_83,07H
DEFW mc_84,08H
DEFW 0
JP mc_85
mc_74:
LD A,(__state)
CALL mc_sxt
INC HL
LD A,L
LD (__state),A
DEC HL
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,09H
CALL mc_eq
LD A,H
OR L
JP Z,mc_86
LD HL,00H
LD A,L
LD (__state),A
mc_86:
INC SP
POP BC
POP BC
RET
意外に長いコードを生成しています。
switch文を利用すると、ラベルを利用したテーブルを
生成し、テーブルの中に、ラベルとcaseで記述した値
を並べています。
元のCの関数を、if文だけで記述し直します。
処理を3に分けると、次のようになります。
変数stateが偶数で8より小さいときには
7セグメントLEDにビットパターンを出力。
変数stateが奇数のとき、どの7セグメント
LEDを利用するのかを指定するコードを出力。
変数stateが8のとき、計算で10進数の位に
ある重みを求める。
この処理を関数に入れます。
typedef unsigned char UBYTE ;
typedef char SBYTE ;
void show_value_handler()
{
UBYTE i ;
SBYTE j ;
SBYTE tmp ;
/* impress */
if ( (state & 1) == 0 && state < 8 ) {
j = (state >> 1) & 0x03 ;
tmp = led_pat[ *(result+j) ] ;
outportb( PIOAD , tmp );
}
if ( (state & 1) == 1 ) {
j = 3 - (state >> 1) ;
tmp = 1 << j ;
outportb( PIOBD , tmp );
}
if ( state == 8 ) {
for ( i = 0 ; i < 4 ; i++ ) {
*(result+3-i) = x % 10 ;
x /= 10 ;
}
tmp = 0x00 ;
outportb( PIOBD , tmp );
}
/* update */
state++ ;
/* judge */
if ( state == 9 ) { state = 0 ; }
}
関数を、アセンブリ言語に展開すると
次のようになります。
__show_value_handler:
DEC SP
DEC SP
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,01H
CALL mc_and
PUSH HL
LD HL,00H
CALL mc_eq
PUSH HL
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,08H
CALL mc_lt
CALL mc_andand
LD A,H
OR L
JP Z,mc_73
LD HL,01H
ADD HL,SP
PUSH HL
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,01H
POP DE
CALL mc_asr
PUSH HL
LD HL,03H
CALL mc_and
POP DE
LD A,L
LD (DE),A
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,__led_pat
PUSH HL
LD HL,__result
PUSH HL
LD HL,07H
ADD HL,SP
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
ADD HL,DE
CALL mc_gchar
POP DE
LD A,L
LD (DE),A
LD HL,1DH
PUSH HL
LD HL,02H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
mc_73:
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,01H
CALL mc_and
PUSH HL
LD HL,01H
CALL mc_eq
LD A,H
OR L
JP Z,mc_74
LD HL,01H
ADD HL,SP
PUSH HL
LD HL,03H
PUSH HL
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,01H
POP DE
CALL mc_asr
CALL mc_sub
POP DE
LD A,L
LD (DE),A
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,01H
PUSH HL
LD HL,05H
ADD HL,SP
CALL mc_gchar
CALL mc_asl
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,02H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
mc_74:
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,08H
CALL mc_eq
LD A,H
OR L
JP Z,mc_75
LD HL,__result
PUSH HL
LD HL,00H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,03E8H
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,(__x)
PUSH HL
LD HL,03E8H
POP DE
CALL mc_div
EX DE,HL
LD (__x),HL
LD HL,__result
PUSH HL
LD HL,01H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,64H
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,(__x)
PUSH HL
LD HL,64H
POP DE
CALL mc_div
EX DE,HL
LD (__x),HL
LD HL,__result
PUSH HL
LD HL,02H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,0AH
POP DE
CALL mc_div
POP DE
LD A,L
LD (DE),A
LD HL,__result
PUSH HL
LD HL,03H
POP DE
ADD HL,DE
PUSH HL
LD HL,(__x)
PUSH HL
LD HL,0AH
POP DE
CALL mc_div
EX DE,HL
POP DE
LD A,L
LD (DE),A
LD HL,00H
ADD HL,SP
PUSH HL
LD HL,00H
POP DE
LD A,L
LD (DE),A
LD HL,1FH
PUSH HL
LD HL,02H
ADD HL,SP
CALL mc_gchar
PUSH HL
CALL __outportb
POP BC
POP BC
mc_75:
LD A,(__state)
CALL mc_sxt
INC HL
LD A,L
LD (__state),A
DEC HL
LD A,(__state)
CALL mc_sxt
PUSH HL
LD HL,09H
CALL mc_eq
LD A,H
OR L
JP Z,mc_76
LD HL,00H
LD A,L
LD (__state),A
mc_76:
POP BC
RET
switch文を利用した場合と行数は変わりませんが
mc_??というラベルを手がかりに、動作を解析して
最適化するのは、楽になっています。
コンパイラが生成したアセンブリ言語コードを
最適化し、人間にとってわかりやすい記述へと
変更します。
最初に変数stateの値で、3つの処理に分割します。
__show_value_handler:
ld a,(__state)
bit 3,a
jp z,state_8
bit 0,a
jr nz,state_even
state_even:
; ?
jp state_update
state_odd:
; ?
jp state_update
state_8:
;?
state_update:
; ?
ret
変数stateの値をインクリメントし、9ならば
0に戻す処理を加えます。
__show_value_handler:
ld a,(__state)
bit 3,a
jp z,state_8
bit 0,a
jr nz,state_odd
state_even:
; ?
jp state_update
state_odd:
; ?
jp state_update
state_8:
;?
state_update:
inc a
cp 9
jr nz,f_exit
xor a
f_exit:
ld (__state),a
ret
変数stateの値が奇数のときに、7セグメントLEDを
選択する処理を加えます。
__show_value_handler:
ld a,(__state)
bit 3,a
jp z,state_8
bit 0,a
jr nz,state_odd
state_even:
; ?
jp state_update
state_odd:
cp 1
jr z,state_odd_1
cp 3
jr z,state_odd_3
cp 5
jr z,state_odd_5
cp 7
jr z,state_odd_7
jp state_update
state_odd_1:
ld l,8
jr state_odd_out
state_odd_3:
ld l,4
jr state_odd_out
state_odd_5:
ld l,2
jr state_odd_out
state_odd_7:
ld l,1
state_odd_out:
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
jp state_update
state_8:
;?
state_update:
inc a
cp 9
jr nz,f_exit
xor a
f_exit:
ld (__state),a
ret
変数stateの値が偶数のときに、7セグメントLEDに
ビットパターンを出力する処理を加えます。
__show_value_handler:
ld a,(__state)
bit 3,a
jp z,state_8
bit 0,a
jr nz,state_odd
state_even:
cp 0
jr z,state_even_0
cp 2
jr z,state_even_2
cp 4
jr z,state_even_4
cp 6
jr z,state_even_6
jp state_update
state_even_0:
ld e,0
jr z,state_even_out
state_even_2:
ld e,2
jr z,state_even_out
state_even_4:
ld e,4
jr z,state_even_out
state_even_6:
ld e,6
state_even_out:
ld d,0
ld hl,(__result)
add hl,de
ld e,(hl)
ld hl,(__led_pat)
add hl,de
ld a,(hl)
;
ld l,a
;
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
;
jp state_update
state_odd:
cp 1
jr z,state_odd_1
cp 3
jr z,state_odd_3
cp 5
jr z,state_odd_5
cp 7
jr z,state_odd_7
jp state_update
state_odd_1:
ld l,8
jr state_odd_out
state_odd_3:
ld l,4
jr state_odd_out
state_odd_5:
ld l,2
jr state_odd_out
state_odd_7:
ld l,1
state_odd_out:
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
jp state_update
state_8:
;?
state_update:
inc a
cp 9
jr nz,f_exit
xor a
f_exit:
ld (__state),a
ret
最後に7セグメントLEDに出力する数値を求めます。
__show_value_handler:
ld a,(__state)
bit 3,a
jp z,state_8
bit 0,a
jr nz,odd
state_even:
cp 0
jr z,state_even_0
cp 2
jr z,state_even_2
cp 4
jr z,state_even_4
cp 6
jr z,state_even_6
jp state_update
state_even_0:
ld e,0
jr z,state_even_out
state_even_2:
ld e,2
jr z,state_even_out
state_even_4:
ld e,4
jr z,state_even_out
state_even_6:
ld e,6
state_even_out:
ld d,0
ld hl,(__result)
add hl,de
ld e,(hl)
ld hl,(__led_pat)
add hl,de
ld a,(hl)
;
ld l,a
;
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
;
jp state_update
state_odd:
cp 1
jr z,state_odd_1
cp 3
jr z,state_odd_3
cp 5
jr z,state_odd_5
cp 7
jr z,state_odd_7
jp state_update
state_odd_1:
ld l,8
jr state_odd_out
state_odd_3:
ld l,4
jr state_odd_out
state_odd_5:
ld l,2
jr state_odd_out
state_odd_7:
ld l,1
state_odd_out:
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
jp state_update
state_8:
ld de,(__x)
ex de,hl
;
ld bc,__result
; /1000
xor a
ld de,1000
state_8_1000:
sbc hl,de
inc a
jr nc,state_8_1000
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
; /100
xor a
ld de,100
state_8_100:
sbc hl,de
inc a
jr nc,state_8_100
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
; /10
xor a
ld de,10
state_8_10:
sbc hl,de
inc a
jr nc,state_8_10
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
; /1
ld a,e
ld (bc),a ; store
; disable all 7 segment LEDs
ld de,1fh
ld hl,0
push de
push hl
call _outportb
pop hl
pop de
state_update:
inc a
cp 9
jr nz,f_exit
xor a
f_exit:
ld (__state),a
ret
スタック操作とラベル名変更をしておきます。
__show_value_handler:
;
push af
push bc
push de
push hl
;
ld a,(__state)
bit 3,a
jp z,svh8
bit 0,a
jr nz,svh1
; even value handling
svh0:
; state = 0
cp 0
jr z,svh00
; state = 2
cp 2
jr z,svh02
; state = 4
cp 4
jr z,svh04
; state = 6
cp 6
jr z,svh06
; update state
jp svhinc
svh00:
ld e,0
jr z,svh08
svh02:
ld e,2
jr z,svh08
svh04:
ld e,4
jr z,svh08
svh06:
ld e,6
svh08:
ld d,0
ld hl,(__result)
add hl,de ; result + i
ld e,(hl)
ld hl,(__led_pat)
add hl,de ; led_pat + j
ld a,(hl)
;
ld l,a
;
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
; update state
jp svhinc
; odd value handling
svh1:
; state = 1
cp 1
jr z,svh11
; state = 3
cp 3
jr z,svh13
; state = 5
cp 5
jr z,svh15
; state = 7
cp 7
jr z,svh17
; update state
jp svhinc
svh11:
ld l,8
jr svh19
svh13:
ld l,4
jr svh19
svh15:
ld l,2
jr svh19
svh17:
ld l,1
svh19:
ld de,1fh
ld h,0
push de
push hl
call _outportb
pop hl
pop de
; update state
jp svhinc
; separate
svh8:
ld de,(__x)
ex de,hl
; set pointer
ld bc,__result
; /1000
xor a
ld de,1000
svh81:
sbc hl,de
inc a
jr nc,svh81
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
; /100
xor a
ld de,100
svh82:
sbc hl,de
inc a
jr nc,svh82
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
; /10
xor a
ld de,10
svh83:
sbc hl,de
inc a
jr nc,svh83
add hl,de
or a ; clear carry flag
ld (bc),a ; store
inc bc ; result+1
svh84:
; /1
ld a,e
ld (bc),a ; store
; disable all 7 segment LEDs
ld de,1fh
ld hl,0
push de
push hl
call _outportb
pop hl
pop de
svhinc:
inc a
cp 9
jr nz,svhend
xor a
svhend:
ld (__state),a
pop hl
pop de
pop bc
pop af
;
ret
人間が最適化すると、50行近くコードが
短くなり、動作が目に見えるカタチに。
割込み処理は、作成済みなのでフラグ設定
部分だけを、こちらに合わせ変更します。
LCD表示
LCDを扱う場合、インタフェースはデータ、制御に
8ビット、3ビット必要で11ビットになります。
Z80は、8ビットの入出力を基本にするので
11ビットを8ビットまで圧縮しておきます。
4ビットレジスタ74HC175を利用して、I/Oが
8ビットに収まるようにしました。
LCDには、R/Wの信号があります。通常LCDに入っている
データを引出すことはないので、R/Wを'L'に固定して
LCDには、ライト操作だけが可能になるようにします。
LCDに対してのライト操作シーケンスは、以下とします。
- RSの論理値指定
- 下位4ビットデータ転送
- 上位4ビットデータ転送
- Eに'H'出力
- Eに'L'出力
Cの関数に、この操作シーケンスを記述します。
typedef unsigned char UBYTE ;
typedef unsigned short UWORD ;
UWORD lcd_dat ;
void put_lcd_primitive(void)
{
UBYTE portx ;
/* E : L , RS : L */
portx = 0x00 ;
outportb( LCD_PORT , portx );
/* transfer lower nibble */
portx = lcd_dat & 0x0f ;
outportb( LCD_PORT , portx );
portx |= 0x10 ;
outportb( LCD_PORT , portx );
portx &= ~0x10 ;
outportb( LCD_PORT , portx );
/* transfer upper nibble */
portx = (lcd_dat >> 4) & 0x0f ;
portx |= ((lcd_dat >> 4) & 0x0f) ;
outportb( LCD_PORT , portx );
portx |= 0x20 ;
outportb( LCD_PORT , portx );
portx &= ~0x20 ;
outportb( LCD_PORT , portx );
/* set RS */
if ( lcd_dat & MASK100 ) { portx |= 0x40 ; }
/* send E trigger */
portx |= 0x80 ;
outportb( LCD_PORT , portx );
portx &= ~0x80 ;
outportb( LCD_PORT , portx );
}
この関数を、アセンブリ言語コードに変換します。
LCD_PORT EQU 20h
__put_lcd_primitive:
;
push af
push bc
push de
push hl
; E : L , RS : L
xor a
ld c,LCD_PORT
out (c),a
; get data
ld de,(__lcd_dat)
; transfer lower nibble
plp0:
ld a,e
and 0fh ; clear upper nibble
out (c),a ; send lower nibble
; lower 74HC175 clk : H
set 4,a
out (c),a
; lower 74HC175 clk : L
res 4,a
out (c),a
; transfer upper nibble
plp1:
ld a,e
rra
rra
rra
rra
and 0fh ; clear upper nibble
out (c),a ; send lower nibble
; upper 74HC175 clk : H
set 5,a
out (c),a
; upper 74HC175 clk : L
res 5,a
out (c),a
; set RS
res 6,a
bit 0,d
jr z,plp2
set 6,a
; send trigger E
plp2:
; E : H
set 7,a
out (c),a
; E : L
res 7,a
out (c),a
;
pop hl
pop de
pop bc
pop af
;
ret
関数put_lcd_primitiveを使い、2つの
ラッパー関数を定義します。
関数put_lcd_cmdは、制御コマンドをLCDに転送。
関数put_lcd_datは、1文字の表示データをLCDに
転送します。
void put_lcd_cmd(UBYTE x)
{
put_lcd_primitive(x);
}
void put_lcd_dat(UBYTE x)
{
put_lcd_primitive(0x100 | x);
}
2つのラッパー関数を作成したなら、LCDを
初期化する関数を定義します。
void init_lcd(void)
{
/* initialize hardware */
delay_ms(15) ;
lcd_cmd(0x30);
delay_ms(5) ; /* 5ms */
lcd_cmd(0x30);
delay_ms(1); /* 1ms */
lcd_cmd(0x30);
delay_ms(1); /* 1ms */
/* set function */
lcd_cmd(0x38);
/*
Function
001 DL N F * *
DL(Data Length) = 1 (8bits)
N(Row) = 1 (2 row)
F(Font) = 0 (5x7)
001 1 1 0 * *
*/
lcd_cmd(0x08);
/*
Display off
0000 1 D C B
D(Display) = 0 (OFF)
C(Cursor) = 0 (OFF)
B(Blink) = 0 (OFF)
0000 1 0 0 0
*/
lcd_cmd(0x01);
delay_ms(2); /* 2ms */
lcd_cmd(0x0c);
/*
Display on
0000 1 D C B
D(Display) = 1 (ON)
C(Cursor) = 0 (OFF)
B(Blink) = 0 (OFF)
0000 1 1 0 0
*/
}
時間待ちするための関数delay_msは、後で定義します。
LCDには、文字を表示したいので、文字列
表示が単純になるようにします。
利用するLCDは、2行x16桁なので32文字分の
配列を用意し、その配列に文字を書込みます。
この配列を、LCDフレームバッファとします。
表示したい文字列は、関数send_lcd_stringを利用
します。この関数でLCDフレームバッファに文字列
を格納します。
LCDへの文字列転送は、関数update_lcdが担当します。
LCDへの文字列転送を、ひとつの関数にすると
タイマー割込みで、その関数を呼出すことで
LCD画面を自動で更新できます。
1秒ごとに、関数update_lcdを呼出すと
LCDの画面表示は、常に最新の状態になり
LCDフレームバッファの操作側は、表示に
関して考えなくてよくなります。
関数update_lcdを定義します。
char lcd_frame_buffer[32] ;
void update_lcd(void)
{
char line ;
char pixel ;
/* line handling */
for ( line = 0 ; line < 2 ; line++ ) {
/* set address */
lnumber = 0x00 ;
if ( line ) { lnumber = 0x40 ; }
lnumber |= 0x80 ;
lcd_cmd( lnumber );
/* calculate line */
lcd_index = 16 * line ;
/* pixel handling */
for ( pixel = 0 ; pixel < 16 ; pixel++ ) {
lcd_dat( *(lcd_frame_buffer+lcd_index+pixel) );
}
}
}
LCD内部の表示用メモリは、0行と1行の区別が
必要になります。ループの中で、0行と1行の
判定をし、アドレスを確定します。
タイマー割込みで、1msのインターバルを実現できると
1ms経過をトリガーフラグで通知します。
1msのトリガーフラグがあれば、1秒周期用カウンタを
+1して、1000になったなら関数update_lcdを呼びます。
文にすると面倒ですが、Cのソースコードで見ると単純です。
unsigned short lcounter ;
#define LCOUNTERMAX 1000
if ( tflag == ON ) {
/* clear trigger flag */
tflag = OFF ;
/* counter increment */
lcounter++;
/* judge */
if ( lcounter == LCOUNTERMAX ) {
lcounter = 0 ;
update_lcd();
}
}
タイマー割込みからのトリガーフラグをmainの中で
捕捉し、さらにカウンタ値を更新するので、CPUの
負担は小さいでしょう。
関数send_lcd_stringを定義します。
void send_lcd_string(char lx,char *x)
{
char line ;
char pixel ;
/* judge line */
if ( lx > 1 ) return ;
line = (lx << 4);
/* clear target line */
for ( pixel = 0 ; pixel < 16 ; pixel++ ) {
/* store space */
*(lcd_frame_buffer+line+pixel) = ' ' ;
}
/* store string */
for ( pixel = 0 ; pixel < 16 ; pixel++ ) {
/* judge */
if ( *x == '\0' ) break ;
/* store */
*(lcd_frame_buffer+line+pixel) = *x ;
/* update pointer */
x++ ;
}
}
1行あたり16文字なので、行の値からオフセットを求め
配列のどこに文字を格納するか決めています。
ゴミが残らないように、該当行をスペースでクリア後に
文字を転送します。
マイコン利用の電子機器用ファームウエアを開発する場合
システムタイマーを用意します。
システムタイマーは32ビットで構成し、1msごとに+1して
いきます。
32ビットのカウンタでは、(32ビットで表現できる最大値−1)ms
までカウントできます。時間にすると約1193時間です。
約49日の間、時を刻み続けるので、電子機器の電源を毎月
オンオフするなら、システムタイマーとしては充分でしょう。
システムタイマーの処理を、アセンブリ言語で記述すると
次のようになります。
STM: defs 4
STIME_HANDLER:
push af
push hl
; increment
STM0:
; set pointer
ld hl,STM
; get data
ld a,(hl)
; increment
inc a
; judge
jr z,STM1
; store
ld (hl),a
jr STM4
; increment
STM1:
; store
ld a,(hl)
; set pointer
ld hl,STM+1
; get data
ld a,(hl)
; increment
inc a
; judge
jr z,STM2
; store
ld (hl),a
jr STM4
; increment
STM2:
; store
ld a,(hl)
; set pointer
ld hl,STM+2
; get data
ld a,(hl)
; increment
inc a
; judge
jr z,STM3
; store
ld (hl),a
jr STM4
; increment
STM3:
; store
ld a,(hl)
; set pointer
ld hl,STM+3
; get data
ld a,(hl)
; increment
inc a
; store
ld (hl),a
;
STM4:
;
pop hl
pop af
;
ret
ラッパー関数put_lcd_cmd、put_lcd_datを
アセンブリ言語コードにします。
__put_lcd_cmd:
;
push af
push hl
; get data
ld hl,4
add hl,sp
ld a,(hl)
; store data
ld hl,__lcd_dat
ld (hl),a
inc hl
xor a
ld (hl),a
;
call __put_lcd_primitive
;
pop hl
pop af
;
ret
__put_lcd_dat:
;
push af
push hl
; get data
ld hl,4
add hl,sp
ld a,(hl)
; store data
ld hl,__lcd_dat
ld (hl),a
inc hl
ld (hl),1
;
call __put_lcd_primitive
;
pop hl
pop af
;
ret
文字列をLCDフレームバッファに格納する
処理をアセンブリ言語コードにします。
__send_lcd_string:
;
push af
push bc
push de
push hl
; get pointer
ld hl,10
add hl,sp
ld e,(hl)
inc hl
ld d,(hl)
; get data
inc hl
inc hl
ld a,(hl)
; judge line number
cp 2
jr c,slsend
; calcualte line entry pointer
sla a
sla a
;
ld hl,__lcd_frame_buffer
ld b,0
ld c,a
add hl,bc
ld b,16
ld a,' '
; store space
sls0:
ld (hl),a
inc hl
djnz sls0
;
ex de,hl
ld de,__lcd_frame_buffer
ld b,16
sls1:
; get data
ld a,(hl)
cp 0
jr slsend
; store
ld (de),a
; update pointer
inc hl
inc de
djnz sls1
;
slsend:
;
pop hl
pop de
pop bc
pop af
;
ret
LCDフレームバッファの内容を、LCDに
転送する内容も定義します。
__update_lcd:
;
push af
push bc
push de
push hl
;++++++++++++++++++
; line 0 handling
;++++++++++++++++++
; set address
xor a
set 7,a
ld de,__lcd_dat
ld (de),a
call __put_lcd_cmd
; set pointer
ld hl,__lcd_frame_buffer
; set counter
ld b,16
ul0:
; get data from LCD frame buffer
ld a,(hl)
ld (__lcd_dat),a
; put data to LCD
call __put_lcd_dat
; update pointer
inc hl
djnz ul0
;++++++++++++++++++
; line 1 handling
;++++++++++++++++++
; set address
ld a,40h
set 7,a
ld de,__lcd_dat
ld (de),a
call __put_lcd_cmd
; set pointer
ld hl,__lcd_frame_buffer
ld de,16
add hl,de
; set counter
ld b,16
ul1:
; get data from LCD frame buffer
ld a,(hl)
ld (__lcd_dat),a
; put data to LCD
call __put_lcd_dat
; update pointer
inc hl
djnz ul1
;
ulend:
;
pop hl
pop de
pop bc
pop af
;
ret
LCDに転送する内容ができたので、タイマー割込みで
呼出す処理を、アセンブリ言語コードに変換します。
TJ:
; get flag
ld hl,__TFLAG
bit 0,(hl)
jr z,TJ2
; clear flag
res 0,(hl)
TJ0:
; counter increment
ld hl,(__lcounter)
inc hl
ld (__lcounter),hl
TJ1:
; judge
inc hl
ld a,(hl)
cp 3 ; 3E8h => 3
jr nz,TJ2
dec hl
ld a,(hl) ; 3E8h => E8h
cp 0e8h
jr nz,TJ2
; clear lcounter
ld de,0
ld (__lcounter),de
; update
call __update_lcd
TJ2:
トリガーフラグは、タイマー割込み処理が設定
してくるので、深くは考えません。
関数delay_msは、次のように定義します。
typedef unsigned long ULONG ;
ULONG target ;
void delay_ms(void)
{
/* loop */
while ( target > stm ) ;
}
Z80のCコンパイラで、32ビット変数を扱えないときは
アセンブリ言語で、同等の内容を定義します。
__delay_ms:
;
push hl
pop de
;
ld hl,__target+3
ld de,STM+3
; compare 3 byte
dms0:
ld a,(hl)
ex de,hl
cp (hl)
jr z,dms1
jr dms0
; compare 2 byte
dms1:
ld hl,__target+2
ld de,STM+2
ld a,(hl)
ex de,hl
cp (hl)
jr z,dms2
jr dms0
; compare 1 byte
dms2:
ld hl,__target+1
ld de,STM+1
ld a,(hl)
ex de,hl
cp (hl)
jr z,dms3
jr dms0
; compare 0 byte
dms3:
ld hl,__target
ld de,STM
ld a,(hl)
ex de,hl
cp (hl)
jr z,dmsend
jr dms0
dmsend:
pop de
pop hl
;
ret
4バイトの数値を、順番に比較し、すべて一致
していれば、ループを抜ける仕様です。
使う側で、STMの値に1msの倍数値を加えて
変数targetに入れます。
LCDの処理だけに限定すると、すべてを入れても
1024バイトにもなりません。
アセンブリ言語を利用すると、小さく高速な処理
を実現できることを体感できます。
目次
前
次