ビット処理はunionで案外イケそう・・・
2012-12-10
この記事、縦にやたらと長いです
プログラム開発のC言語化について、既存のMPASMのソースプログラムは、単純にXC8の持つ「Macro Assembler」に移植すれば済むわけですが、そんなに超高速なプログラムを作っても仕方がないことや、XC8の無償版でも案外綺麗にコンパイルしてくれそうなこと(=objがそんなに大きくならないこと)などから、ポート処理には欠かせないビット演算がそれなりの範疇に収まるようなら、殆どC言語ベースの方が後々楽だろう・・・という風に、「アセンブラ遣い」を返上した序でにもっと手を抜いてしまおうと思うに至りました。
そこで、C言語の共用体宣言を以下のように組み、どんなニーモニック(これを逆アセンブルすれば、アセンブラのソースになります)になるかを確かめてみました。
この共用体では、8ビットの符号無し変数を「1ビット単位」「4ビット単位」「8ビット丸ごと」でアクセスできるようになりますが、これに追随して、
・1ビット単位:bsf,bcf に置き換わる
・4ビット単位:swapfに置き換わる
という部分が綺麗に行くなら、多少の無駄を覚悟して「ほぼオールC言語」に持っていけるかな
というわけです。
コンパイラは勿論XC8。ソースファイル名は「lcd.c」としていますが、気にしないでください
コンパイル結果をまとめてみましょう。
なお、前提として「REG_HL」という変数を以下のように「7f 番地」に定義しています。
◆ 役割の決まったレジスタは特別扱い
PORTAやSTATUSといったPICの構成要素として役割が決まっているレジスタは、即値のセットにおいてもそのまま最適に設定されますから、ここはよくできている部分ですね。
ちなみに、アドレスを指定した変数にすれば上記PORTAの場合と同じかなぁ・・・と思ったら、「無駄な2ステップを暴いた記事」に示した通り(この記事の変数bbの展開形をご覧あれ)、余計な2ステップがもれなく付いてきます
う~ん、残念・・・。
◆ 1ビットのON/OFFはイイ感じ
これは、PORTAだろうが自分で指定した固定アドレスの変数であろうが、上記のunionできちんと最適な機械語・・・つまり1ステップのアセンブラ命令にコンパイルされます。例えば、以下のような感じです。
全く無駄がない
というわけで、1ビットのON/OFFは、C言語表現のままで良さそうです。
◆ 4ビット一括設定もまずまず・・・
4ビットの設定は、LCDを4ビットモードで動かすことが多いことから多用されますので、きちんと片付けておきたいところです。どのポートにアサインされるかは、ハード設計仕様に依存しますから固定的では不味いのですが、何れにせよ「どこかのポート」になりますよね。
そこで、一捻りして「PORTx にBIT_t を被せる」という形で、4ビット一括で扱えるようにしました。
これで、「LCD_DT_PORT.BH」と「LCD_DT_PORT.BL」で、各々PORTAの上位4ビット/下位4ビットが扱えます。
セオリー通り、PORTAから読み取ったデータの上位に「0x04」を設定して書き戻してますね。これも合格です。ちなみに、自分で指定した固定アドレスの変数も同じ展開形でした。
◆ 4ビットのハイ・ロー
これもLCDの処理では定番に当たるかも知れませんが、8ビットの上位4ビット⇒下位4ビットの順で出力する場合があります。PICのアセンブラ命令の「swapf」は、この処理に最適なんですが、果たしてC言語で表現した場合、この命令を使って効率的な機械語が吐き出されるか・・・。
ちょいと冗長・・・でも、論理演算を使って上手く処理していると思います。やはり変数「(??_lcd_cmd+0)+0」に纏わる部分がもたついてますね。
ただ、この上位⇒下位の出力処理があちこちにあるわけではないため、そんなに目くじらを立てなくても良いかも知れません。
◆ オマケ・・・
LCD制御にはストローブを与える必要がありますが、これもちょっとマクロで作ってみました。
これでC言語1ラインでストローブ処理「toggle」が使えるようになりました。この部分の展開形を参考に。どういうわけかバンク指定が入ってしまいますが、まぁ良しとしました。
◆ 結果的には
LCDにコマンドを送る処理をアセンブラで記述したサブルーチンが24ステップ。一方、上記を駆使して作ったC言語の関数が吐き出した機械語が36ステップ・・・約1.5倍になりました。これが多いのか少ないのかの議論とは別に、C言語のソースライン数は何と8行
これは、圧倒的に見通しが良い処理になりましたから、ひとまずこの調子でC言語ベースのLCDドライバを完成させ、既に動いているアセンブラ版と比較してみようと思います。
どんどん方針が変わるって
いいのいいの、仕事じゃないんだから

プログラム開発のC言語化について、既存のMPASMのソースプログラムは、単純にXC8の持つ「Macro Assembler」に移植すれば済むわけですが、そんなに超高速なプログラムを作っても仕方がないことや、XC8の無償版でも案外綺麗にコンパイルしてくれそうなこと(=objがそんなに大きくならないこと)などから、ポート処理には欠かせないビット演算がそれなりの範疇に収まるようなら、殆どC言語ベースの方が後々楽だろう・・・という風に、「アセンブラ遣い」を返上した序でにもっと手を抜いてしまおうと思うに至りました。
そこで、C言語の共用体宣言を以下のように組み、どんなニーモニック(これを逆アセンブルすれば、アセンブラのソースになります)になるかを確かめてみました。
typedef union { struct { unsigned B0 :1; // 1ビット毎 unsigned B1 :1; unsigned B2 :1; unsigned B3 :1; unsigned B4 :1; unsigned B5 :1; unsigned B6 :1; unsigned B7 :1; }; struct { unsigned BL :4; // 4ビット毎 unsigned BH :4; }; unsigned char BT; // 8ビット丸ごと } BIT_t; |
この共用体では、8ビットの符号無し変数を「1ビット単位」「4ビット単位」「8ビット丸ごと」でアクセスできるようになりますが、これに追随して、
・1ビット単位:bsf,bcf に置き換わる
・4ビット単位:swapfに置き換わる
という部分が綺麗に行くなら、多少の無駄を覚悟して「ほぼオールC言語」に持っていけるかな

コンパイラは勿論XC8。ソースファイル名は「lcd.c」としていますが、気にしないでください

なお、前提として「REG_HL」という変数を以下のように「7f 番地」に定義しています。
★extern volatile BIT_t REG_HL @ 0x7f; |
◆ 役割の決まったレジスタは特別扱い
PORTAやSTATUSといったPICの構成要素として役割が決まっているレジスタは、即値のセットにおいてもそのまま最適に設定されますから、ここはよくできている部分ですね。
;lcd.c: 65: PORTA = 0x0a; movlw (0Ah) movwf (12) ;volatile |
ちなみに、アドレスを指定した変数にすれば上記PORTAの場合と同じかなぁ・・・と思ったら、「無駄な2ステップを暴いた記事」に示した通り(この記事の変数bbの展開形をご覧あれ)、余計な2ステップがもれなく付いてきます

;lcd.c: 67: REG_HL.BT = 0x0a; movlw (0Ah) movwf (??_lcd_cmd+0)+0 ←これと movf (??_lcd_cmd+0)+0,w ←これ movwf (127) ;volatile |
う~ん、残念・・・。
◆ 1ビットのON/OFFはイイ感じ
これは、PORTAだろうが自分で指定した固定アドレスの変数であろうが、上記のunionできちんと最適な機械語・・・つまり1ステップのアセンブラ命令にコンパイルされます。例えば、以下のような感じです。
;lcd.c: 69: REG_HL.B1 = 1; bsf (127),1 ;volatile |
全く無駄がない

◆ 4ビット一括設定もまずまず・・・
4ビットの設定は、LCDを4ビットモードで動かすことが多いことから多用されますので、きちんと片付けておきたいところです。どのポートにアサインされるかは、ハード設計仕様に依存しますから固定的では不味いのですが、何れにせよ「どこかのポート」になりますよね。
そこで、一捻りして「PORTx にBIT_t を被せる」という形で、4ビット一括で扱えるようにしました。
★extern volatile BIT_t LCD_DT_PORT @ (&PORTA); |
これで、「LCD_DT_PORT.BH」と「LCD_DT_PORT.BL」で、各々PORTAの上位4ビット/下位4ビットが扱えます。
;lcd.c: 71: LCD_DT_PORT.BH = 0x04; movf (12),w ;volatile andlw not (((1<<4)-1)<<4) iorlw (04h & ((1<<4)-1))<<4 movwf (12) ;volatile |
セオリー通り、PORTAから読み取ったデータの上位に「0x04」を設定して書き戻してますね。これも合格です。ちなみに、自分で指定した固定アドレスの変数も同じ展開形でした。
◆ 4ビットのハイ・ロー
これもLCDの処理では定番に当たるかも知れませんが、8ビットの上位4ビット⇒下位4ビットの順で出力する場合があります。PICのアセンブラ命令の「swapf」は、この処理に最適なんですが、果たしてC言語で表現した場合、この命令を使って効率的な機械語が吐き出されるか・・・。
;lcd.c: 74: LCD_DT_PORT.BL = REG_HL.BH; swapf (127),w ;volatile andlw (1<<4)-1 movwf (??_lcd_cmd+0)+0 movf (12),w ;volatile xorwf (??_lcd_cmd+0)+0,w andlw not ((1<<4)-1) xorwf (??_lcd_cmd+0)+0,w movwf (12) ;volatile |
ちょいと冗長・・・でも、論理演算を使って上手く処理していると思います。やはり変数「(??_lcd_cmd+0)+0」に纏わる部分がもたついてますね。
ただ、この上位⇒下位の出力処理があちこちにあるわけではないため、そんなに目くじらを立てなくても良いかも知れません。
◆ オマケ・・・
LCD制御にはストローブを与える必要がありますが、これもちょっとマクロで作ってみました。
extern volatile BIT_t LCD_E_PORT @ (&PORTB); #define LCD_E LCD_E_PORT.B2 #define nop asm("nop") #define toggle { LCD_E = 1; nop; LCD_E = 0; } |
これでC言語1ラインでストローブ処理「toggle」が使えるようになりました。この部分の展開形を参考に。どういうわけかバンク指定が入ってしまいますが、まぁ良しとしました。
;lcd.c: 67: { LCD_E_PORT.B2 = 1; asm("nop"); LCD_E_PORT.B2 = 0; }; bsf (13),2 ;volatile nop ;# movlb 0 ; select bank0 bcf (13),2 ;volatile |
◆ 結果的には

LCDにコマンドを送る処理をアセンブラで記述したサブルーチンが24ステップ。一方、上記を駆使して作ったC言語の関数が吐き出した機械語が36ステップ・・・約1.5倍になりました。これが多いのか少ないのかの議論とは別に、C言語のソースライン数は何と8行

どんどん方針が変わるって


- 関連記事
-
- XC8のバージョンアップ(Ver 1.12)に落とし穴が・・・
- 関数呼び出しの様子
- ビット処理はunionで案外イケそう・・・
- XC8とMPASMの混在は無理そう・・・
- XC8とMPASMの混在が微妙・・・