目次

表示器用ファームウエア開発

 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の動作を、先に  決めます。  次のシーケンスで、動かすことにしました。
  1. 1000の位の数値表示
  2. 100の位の数値表示
  3. 10の位の数値表示
  4. 1の位の数値表示
  5. 測定値から4けたの10進に変換
  6. 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に対してのライト操作シーケンスは、以下とします。
  1. RSの論理値指定
  2. 下位4ビットデータ転送
  3. 上位4ビットデータ転送
  4. Eに'H'出力
  5. 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バイトにもなりません。  アセンブリ言語を利用すると、小さく高速な処理  を実現できることを体感できます。
目次

inserted by FC2 system