H8/3664のIICを高速化

Daisukeh 2009/05/13 13:07 作成
Daisukeh 2009/05/13 13:45 追記
Daisukeh 2009/08/04 13:12 移設

記事一覧

ハードの限界へ迫れ!

アセンブラコーディング  先日の「H8/3664でIICを使う」で検証した、ルネサステクノロジのH8/3664のI2C(以下、IIC)通信機能について、さらなるステージを目指すべく、アセンブラによる高速化を行ったので紹介しておく。一応復習しておくが、H8/3664の内蔵機能によるIICインターフェースは ダメダメなので使えない 。したがって、電子工作に興じる諸先輩方に習って、ソフトウェア・インプリメントした。ただし、C言語で視認性をよくするために少々冗長であるがゆえ、スピードが遅いという結果に終わっていた。アプリケーションによってはこれで問題ないのだが、 組み込みシステムにおけるプログラミングでは、その実行性能をハードウェアの限界近くまで引き上げることにこそ喜びがあるというものだ。
 現在ターゲットとして使用しているのはルネサステクノロジのH8/3664に20MHzのクリスタル、IIC仕様のEEPROMがアトメルのAT24C512である。評価基板は秋月電商のもので、これはCQ出版がトランジスタ技術誌で過去におまけでつけてくれた基板とほとんど同じものだ。今回は(昨日の今日だけど…)C言語をアセンブラ言語にポーティングして、ハードの限界に迫るような高速化をしてみようと思う。

C言語版について

オシロスコープでみる "ヒゲ"  プログラムを確認しながらオシロスコープで何度も眺めていたら、SDAのヒゲやらSCLのパルス幅がバラバラだったりと、あまり美しい波形(=制御)だとはいえなかった。だから、少しずつコードの順序を入れ替えたりして、ロジックを壊さないように最適化と冗長性のバランスをとったものが、以下のコードである。基本的なロジックは変わっていない。IICではSDAラインを入出力に使用するため、マスタ側もスレーブ側もバスを入力にすると、プルアップされている都合でHレベルに引っ張られてしまう。IICの送信では8ビット送信後に入力に切り替えてスレーブからのACKを受信するのだが、このタイミングにわずかなバスの開放があって、ヒゲ(0.5V)が出てしまう。もちろん実用上は問題ない。SCLがHレベルの期間はマスタ側出力であってもスレーブ出力であっても、SDAに出力されるデータは保障されているからだ。そうは言っても、オシロスコープで観察される ヒゲは剃るべきだと思う

プレイステーション2  このソースを見てもらえばわかるとおり、SCLのパルス前後で出力や入力のビット情報をハンドリングしているが、それがパルスの脱調を助長しているようだった。これはC言語レベルで解決できるものではないかもしれない。C言語などの高級言語ではよくも悪くも、スタックからレジスタへ転送する処理とメモリ間のデータ転送が乱用?されていて、レジスタを効率的に使っていない気がする。だから、同じ値や変数を使っているにもかかわらず、何度もスタックから引っ張ってきたりするし、条件判定になぜかわざわざMOV命令を使ったりと、不思議な最適化がかかることがある。これはルネサステクノロジが提供するHEWや、Best Technologyが提供しているGDL、そして秋月電商のキットにも付属したりするCygwinのGCCなどと比較すると、また違った結果が出るかもしれない。ただし、H8/Tinyのようなベーシックなインストラクションを持つCPUでは、コンパイラの最適化処理よりも人間が直接最適化した方が効果的な場合が多いのは仕方がないかもしれない。昔話になるが、自分がPlayStationでプログラムを作っていたとき、MIPSのR3000でアセンブル言語のライブラリを書いた。ブランチ命令直後の命令が実行されるなど、いままで体験したことがないCPUだったものだから少々困惑したものだ。その後、PlayStation 2GPU(Graphics Synthesizer)《他にベクトルユニット(=VU)とも呼んでいた》の描画エンジンをアセンブラで書き、CPU(Emotion Engine)との間で通信を行うDMA転送のライブラリを書いていたときは、ちょっと発狂するかと思ったよ…。なぜなら、アセンブラソースの中に、命令リストが2列並ぶのである。左がVU1、右がVU2、みたいな感じで。命令はどちらもほとんど同じだが、順序などによってストールする(=GPUが同期を取るために一一時停止する)ため、高速化するにはその順序を最適化しなければならないのだ。それに加えて、DMAから順次転送されてくるデータのハンドリングもしなければならない。話がよくわからないでしょ?

最適化したC言語版IIC関数群

VOID readIIC(UB* adrs, UB* data, SIZE datasz);                                  // IICデータ列読込
VOID writeIIC(UB* adrs, UB* data, SIZE datasz);                                 // IICデータ列書込
UB controlIIC(ID iicid, UB data);                                               // IICソフトウェア制御
 
// readIIC : IICデータ列読込
VOID readIIC(UB* adrs, UB* data, SIZE datasz)
{
        // IICデータ列読込(1)
        controlIIC(IIC_START, 0);
        controlIIC(IIC_SETBYTE,                       0xa0);
        controlIIC(IIC_SETBYTE, ((VP_INT)adrs >> 8) & 0xff);
        controlIIC(IIC_SETBYTE,  (VP_INT)adrs       & 0xff);
 
        // IICデータ列読込(2)
        controlIIC(IIC_START, 0);
        controlIIC(IIC_SETBYTE, 0xa1);
        for(; datasz; datasz --)
                *(data ++) = controlIIC(IIC_GETBYTE, ((datasz == 1) ? TRUE : FALSE));
        controlIIC(IIC_STOP, 0);
}
 
// writeIIC : IICデータ列書込
VOID writeIIC(UB* adrs, UB* data, SIZE datasz)
{
        // IICデータ列書込
        controlIIC(IIC_START, 0);
        controlIIC(IIC_SETBYTE,                       0xa0);
        controlIIC(IIC_SETBYTE, ((VP_INT)adrs >> 8) & 0xff);
        controlIIC(IIC_SETBYTE,  (VP_INT)adrs       & 0xff);
        for(; datasz; datasz --)
                controlIIC(IIC_SETBYTE, *(data ++));
        controlIIC(IIC_STOP, 0);
}
 
#define iic_read_sda()          PCR5 &= ~0x40                                   // IIC:SDA受入モード
#define iic_write_sda()         PCR5 |=  0x40                                   // IIC:SDA送出モード
#define iic_check_sda()         PDR5 &   0x40                                   // IIC:SDAビット判定
#define iic_off_sda()           PDR5 &= ~0x40                                   // IIC:SDAビットオフ(=0)
#define iic_on_sda()            PDR5 |=  0x40                                   // IIC:SDAビットオン(=1)
#define iic_write_scl()         PCR5 &= ~0x80                                   // IIC:SCL送出モード
#define iic_off_scl()           PDR5 &= ~0x80                                   // IIC:SCLビットオフ(=0)
#define iic_on_scl()            PDR5 |=  0x80                                   // IIC:SCLビットオン(=1)
 
// controlIIC : IICソフトウェア制御
UB controlIIC(ID iicid, UB data)
{
        UB cnt;                                                                 // カウンタ
        BOOL flg;                                                               // フラグ
 
        switch(iicid)
        {
        case IIC_INIT :
                // IIC初期化
                iic_write_sda();
                iic_write_scl();
                iic_on_scl();
                iic_on_sda();
                break;
 
        case IIC_START :
                // スタート送出
                iic_on_sda();
                iic_write_sda();
                iic_on_scl();
                iic_off_sda();
                break;
 
        case IIC_STOP :
                // ストップ送出
                iic_off_sda();
                iic_write_sda();
                iic_on_scl();
                iic_on_sda();
                break;
 
        case IIC_SETBYTE :
                // バイト送出
                iic_write_sda();
                for(cnt = 0x80; cnt; cnt >>= 1)
                {
                        iic_off_scl();
                        if(data & cnt) iic_on_sda();
                        else           iic_off_sda();
                        iic_on_scl();
                }
                iic_off_scl();
                iic_off_sda();
                iic_on_scl();
                iic_read_sda();
                data = iic_check_sda() ? 1 : 0;
                iic_write_sda();
                iic_off_scl();
                return data;
 
        case IIC_GETBYTE :
                // バイト受入
                flg = (BOOL)data;
                iic_read_sda();
                for(data = 0, cnt = 0x80; cnt; cnt >>= 1)
                {
                        iic_on_scl();
                        if(iic_check_sda()) data |= cnt;
                        iic_off_scl();
                }
                if(flg) iic_on_sda();
                else    iic_off_sda();
                iic_write_sda();
                iic_on_scl();
                iic_off_scl();
                iic_read_sda();
                return data;
        }
 
        return 0;
}

 ちなみに、このC言語版では転送レートが 約179KHz であった。やっぱり遅いよねぇ。組み込みアプリケーションが使う設定データの退避と復旧ぐらいなら問題ないと思うけど、高速にデータ転送したい場合には、これだとそのアプリケーションがストールする可能性がある。インタラクション(表示器やボタン操作)が伴うとすれば、0.1秒でも惜しい。ただし、割込み対しては問題は発生していない。H8/3664のIICの不具合は割込み時のデータ取りこぼしに起因するというレポートがウェブでは挙がっていたようだ。

アセンブラ版について

アセンブラで高速化!  C言語版は遅い!ということでポーティングしたアセンブラ版が下のコードである。基本的にC言語のロジックを踏襲していて、ラン・テストを繰り返して気になる部分を微調整してある。特にNOPが挿入されたりしている部分や、SDAラインの入出力切替の順序は、C言語版とは異なっているのだ。アセンブラ化でもっとも恩恵を受けるのは、スタックからレジスタへの転送など、メモリアクセスの煩雑さから開放される点にある。アドレッシングモードが比較的豊富なH8/300シリーズとはいえ、メモリアクセスに貴重なクロックが使われてしまうのは惜しい。このコードでは最低限のメモリアクセスで、あとはレジスタ上で事を済ませようという戦略で作成したつもりである。

高速化したアセンブラ版IIC関数群

; IIC Software Interface for H8/3664,3694   1.1.0 2009/05/13 Daisukeh
; IIC3664.asm - H8/3664用ソフトウェアIICインターフェース
 
PDR5            EQU             H'FFD8                                          ; ポート5データレジスタ
PCR5            EQU             H'FFE8                                          ; ポート5方向レジスタ
SDA             EQU             6                                               ; IIC:SDAビット
SCL             EQU             7                                               ; IIC:SCLビット
 
                SEGMENT         TEXT            ATR_CODE
 
;               IICデータ列読込
;               VOID readIIC(UB* adrs, UB* data, SIZE datasz)
;               adrs            4(ER6)
;               data            6(ER6)
;               datasz          8(ER6)
 
                PUBLIC          _readIIC
 
_readIIC:       PUSH.W          R6                                              ; ER6退避
                MOV.W           R7,R6                                           ; スタックポインタ退避
                SUBS            #2,ER7                                          ; スタックポインタ補正
                PUSH.L          ER4                                             ; ER4退避
                PUSH.L          ER5                                             ; ER5退避
                MOV.L           #PDR5,ER4                                       ; ER4=ポート5データレジスタ
                MOV.L           #PCR5,ER5                                       ; ER5=ポート5方向レジスタ
 
                BSR             cntrlIIC_INIT                                   ; IIC制御:初期化
                BSR             cntrlIIC_START                                  ; IIC制御:スタート送出
                MOV.B           #H'A0,R0L                                       ; 書込コマンド
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                MOV.W           @(+4,ER6),R2                                    ; adrs取得
                MOV.B           R2H,R0L                                         ; adrs上位バイト
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                MOV.B           R2L,R0L                                         ; adrs下位バイト
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
 
                BSR             cntrlIIC_START                                  ; IIC制御:スタート送出
                MOV.B           #H'A1,R0L                                       ; 読込コマンド
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                MOV.W           @(+6,ER6),R2                                    ; data取得
                EXTU.L          ER2                                             ; dataロングワード化
                MOV.L           @(+8,ER6),ER3                                   ; datasz取得
read_iic_loop:  XOR.B           R1L,R1L                                         ; 最終データフラグクリア
                DEC.L           #1,ER3                                          ; datasz更新
                BLT             read_iic_exit                                   ; データ列読込終了判定
                BNE             read_iic_data                                   ; 最終データ判定
                DEC.B           R1L                                             ; 最終データフラグセット
read_iic_data:  BSR             cntrlIIC_GET                                    ; IIC制御:バイト受入/ACK(NAK)送出
                MOV.B           R0L,@ER2                                        ; データストア
                INC.L           #1,ER2                                          ; data更新
                BRA             read_iic_loop                                   ; データ列読込繰返
read_iic_exit:  BSR             cntrlIIC_STOP                                   ; IIC制御:ストップ送出
 
                POP.L           ER5                                             ; ER5復旧
                POP.L           ER4                                             ; ER4復旧
                MOV.W           R6,R7                                           ; スタックポインタ復旧
                POP.W           R6                                              ; ER6復旧
                RTS                                                             ; 復帰
 
;               IICデータ列書込
;               VOID writeIIC(UB* adrs, UB* data, SIZE datasz)
;               adrs            4(ER6)
;               data            6(ER6)
;               datasz          8(ER6)
 
                PUBLIC          _writeIIC
 
_writeIIC:      PUSH.W          R6                                              ; ER6退避
                MOV.W           R7,R6                                           ; スタックポインタ退避
                SUBS            #2,ER7                                          ; スタックポインタ補正
                PUSH.L          ER4                                             ; ER4退避
                PUSH.L          ER5                                             ; ER5退避
                MOV.L           #PDR5,ER4                                       ; ER4=ポート5データレジスタ
                MOV.L           #PCR5,ER5                                       ; ER5=ポート5方向レジスタ
 
                BSR             cntrlIIC_INIT                                   ; IIC制御:初期化
                BSR             cntrlIIC_START                                  ; IIC制御:スタート送出
                MOV.B           #H'A0,R0L                                       ; 書込コマンド
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                MOV.W           @(+4,ER6),R2                                    ; adrs取得
                MOV.B           R2H,R0L                                         ; adrs上位バイト
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                MOV.B           R2L,R0L                                         ; adrs下位バイト
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
 
                MOV.W           @(+6,ER6),R2                                    ; data取得
                EXTU.L          ER2                                             ; dataロングワード化
                MOV.L           @(+8,ER6),ER3                                   ; datasz取得
write_iic_loop: DEC.L           #1,ER3                                          ; datasz更新
                BLT             write_iic_exit                                  ; データ列書込終了判定
                MOV.B           @ER2+,R0L                                       ; データロード/data更新
                BSR             cntrlIIC_SET                                    ; IIC制御:バイト送出/ACK(NAK)受入
                BRA             write_iic_loop                                  ; データ列読込繰返
write_iic_exit: BSR             cntrlIIC_STOP                                   ; IIC制御:ストップ送出
 
                POP.L           ER5                                             ; ER5復旧
                POP.L           ER4                                             ; ER4復旧
                MOV.W           R6,R7                                           ; スタックポインタ復旧
                POP.W           R6                                              ; ER6復旧
                RTS                                                             ; 復帰
 
;               IICソフトウェア制御処理群
 
;               IIC制御:初期化
cntrlIIC_INIT:  BSET            #SCL,@ER5                                       ; SCL=出力
                BSET            #SDA,@ER5                                       ; SDA=出力
                BSET            #SCL,@ER4                                       ; SCL=1
                BSET            #SDA,@ER4                                       ; SDA=1
                RTS                                                             ; 復帰
 
;               IIC制御:スタート送出
cntrlIIC_START: BSET            #SDA,@ER4                                       ; SDA=1
                BSET            #SDA,@ER5                                       ; SDA=出力
                BSET            #SCL,@ER4                                       ; SCL=1
                NOP                                                             ; ウェイト
                BCLR            #SDA,@ER4                                       ; SDA=0
                NOP                                                             ; ウェイト
                RTS                                                             ; 復帰
 
;               IIC制御:ストップ送出
cntrlIIC_STOP:  BCLR            #SDA,@ER4                                       ; SDA=0
                BSET            #SDA,@ER5                                       ; SDA=出力
                BSET            #SCL,@ER4                                       ; SCL=1
                NOP                                                             ; ウェイト
                BSET            #SDA,@ER4                                       ; SDA=1
                NOP                                                             ; ウェイト
                RTS                                                             ; 復帰
 
;               IIC制御:バイト送出/ACK(NAK)受入
cntrlIIC_SET:   BSET            #SDA,@ER5                                       ; SDA=出力
                MOV.B           #8,R0H                                          ; ループカウント設定
iic_loop_set:   BCLR            #SCL,@ER4                                       ; SCL=0
                SHLL.B          R0L                                             ; MSBビットテスト&シフト
                BST             #SDA,@ER4                                       ; SDA=0/1
                BSET            #SCL,@ER4                                       ; SCL=1
                DEC.B           R0H                                             ; ループカウント更新
                BNE             iic_loop_set                                    ; ループ判定
                BCLR            #SCL,@ER4                                       ; SCL=0
                XOR.L           ER0,ER0                                         ; 返値初期化
                BCLR            #SDA,@ER4                                       ; SDA=0
                BCLR            #SDA,@ER5                                       ; SDA=入力
                BSET            #SCL,@ER4                                       ; SCL=1
                MOV.B           @ER4,R0L                                        ; ポート5データ取得
                BCLR            #SCL,@ER4                                       ; SCL=0
                BSET            #SDA,@ER5                                       ; SDA=出力
                AND.B           #(1<<SDA),R0L                                   ; SDAビットマスク
                BEQ             iic_exit_set                                    ; ACK→スキップ
                MOV.B           #1,R0L                                          ; NAK→ER0=1
iic_exit_set:   RTS                                                             ; 復帰
 
;               IIC制御:バイト受入/ACK(NAK)送出
cntrlIIC_GET:   XOR.L           ER0,ER0                                         ; 返値初期化
                BCLR            #SDA,@ER4                                       ; SDA=0
                BCLR            #SDA,@ER5                                       ; SDA=入力
                MOV.B           #8,R0H                                          ; ループカウント設定
iic_loop_get:   SHLL.B          R0L                                             ; ビット列シフト
                BSET            #SCL,@ER4                                       ; SCL=1
                MOV.B           @ER4,R1H                                        ; ポート5データ取得
                BCLR            #SCL,@ER4                                       ; SCL=0
                AND.B           #(1<<SDA),R1H                                   ; SDAビットマスク
                BEQ             iic_next_get                                    ; SDA=0→スキップ
                BSET            #0,R0L                                          ; LSBビットセット
iic_next_get:   DEC.B           R0H                                             ; ループカウント更新
                BNE             iic_loop_get                                    ; ループ判定
                BSET            #SDA,@ER5                                       ; SDA=出力
                SHLR.B          R1L                                             ; 最終データ確認
                BST             #SDA,@ER4                                       ; ACK(NAK)設定
                BSET            #SCL,@ER4                                       ; SCL=1
                NOP                                                             ; ウェイト
                BCLR            #SCL,@ER4                                       ; SCL=0
                BCLR            #SDA,@ER4                                       ; SDA=0
                RTS

 このコード、なんと 約630KHz の転送レートでEEPROMの読み書きができた。使っているEEPROM(AT24C512)の公証最高転送レートが +5V時に1MHz だから、まずまずといったところだ。しかも、H8/3664のIICインターフェースはクロック 20MHz でも 最大571KHz である。非同期通信ができないという点では不利だが、スピード勝負では負けていないのだ。ただし、アセンブラ化したとはいってもやはりハードロジックではないため、オシロスコープの波形をみるとクロックのデューティーがギリギリだったりしている。おそらく事実上、これが最速だと(僕は)思う。コーディングに際してBST命令を多用してみたのだが、おわかりだろうか?これはキャリーフラグを指定ビット位置に転送する命令だが、バイトデータをシフトしてキャリーにMSBを送り出し、それをSDAのビットに反映する仕組みは、とても美しいシリアライズ法だと思う。

 アセンブラの世界を久しぶりに体験した。もうすっかり「デジタル洞窟の囚人」なのである。

参考文献

failed to fetch data: unkown error

failed to fetch data: unkown error

failed to fetch data: unkown error

failed to fetch data: unkown error

掲示板

, 2009/06/11 17:21
自己レス。アトメルのAT24CxxシリーズのEEPROMには、ページング機構があるようだ。これはEEPROMの中に8ビットのインクリメンタル・カウンタを持っているということで、1バイトのリード/ライト後に自動的にアドレスがインクリメントされるという機構である。便利なのだが、1バイト=256アドレスしか指示できないため、これがページ単位となりボトルネックが生じてくる。スペックを見るとページングがライトにはあって、リードには「あるとは書かれていない」のだが、実際には両方に存在している。(内部構造が同じだと考えれば当然か?)なので、ページ境界にてスタートビットとダミーライトの再送出、あるいはストップ→スタートビットの再送出(ただし10msec待機 =Twr)が必要である。2009/06時点でここに掲載しているソースはページング機構を考慮していないため、EEPROMにアクセスするアプリケーション側でアドレスの管理をしなければならない。
, 2012/01/24 07:17
Whoa, whoa, get out the way with that good infroamtion.
, 2018/11/02 10:30
Sharp, you'll see the most common occurrences about new, theory women to make everything from impotence to information. Tenant screening viagra generic baking
soda. Quanto browse originale does medicare pay for 2014
5 mg prezzo farmacia 2012.
, 2018/11/02 10:31
Sharp, you'll see the most common occurrences about new, theory women to make everything from impotence to information. Tenant screening
viagra generic baking soda. Quanto browse originale does medicare pay for 2014 5 mg prezzo farmacia 2012.
Enter your comment
 
 
programming/embed/h8_3664のiicを高速化.txt · 最終更新: 2009/08/09 10:56 by daisukeh
 

Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS
Driven by DokuWiki Powered by Google do yourself a favour and use a real browser - get firefox ! GIMP is the GNU Image Manipulation Program. Adobe Flex smarty : Template Engine