PSoCマイコンを使用しない人には役に立たない情報ではあるが、個人的な備忘録として PSoCでバイナリデータを受信する際の対応方法を書き残しておく。内容としてはこちらのブログに近い内容であるので、補足的な位置付けとなるだろうか。
PSoCの統合開発環境において、Device Editorで RX8 (またはUART) を使用した際、
- デフォルト設定
- "Enable interrupt generation control" が有効で、モジュールパラメータの "Interrupt API" を Enable した状態
のいずれかで Generate Application を実行すると、受信割り込みのためのコードが自動的に生成される。しかし、この受信割り込み処理は通信を介したコマンド&パラメータの受信処理、つまり文字列受信のアプリケーションに適合するように作られており、バイナリ受信を想定していない。先に挙げたブログでは「バイナリーの受信割り込みができない」と記載されているが、より正確には「バイナリ受信用としては都合の悪い処理が、自動生成されたアセンブラコードに含まれている」というところだろうか。いずれにせよ、これは”仕様”であるし、この”仕様”を変えたい場合には自ら手を入れるしかない。
先のブログでは C言語による受信割り込みの実装を試みているので、先ずこの線で考えてみる。自動生成されたアセンブラコード rx8int.asm (ブロック名が"RX8"の場合) を見ると、 "IF (RX8_RXBUF_ENABLE)" として割り込み処理の実体やそこで使う変数の宣言が括られている。つまり、Device Editor 上で "RxCmdBuffer" を Enable/Disable のいずれにするかで、標準の割り込み処理を使用するか否かを選択できることになる。一方で、rx8int.asm 内には、"Insert your custom code below this banner" という統合環境下の割り込み設定ではお馴染みの記述があり、指定された範囲に任意コードを記述すれば良いことが分かる。Cの関数にジャンプするなら "ljmp _isr_rx_interrupt" と書いて、Cソース側で"#pragma imterrupt isr_rx_interrupt" と後に続く "void isr_rx_interrupt()" 関数を用意すれば良い。
以上をまとめると、
- 受信割り込みルーチンの生成を有効にする
- "RxCmdBuffer" を Disable する
- Generate Application を実行する
- rx8...int.asm 側の "Insert your custom code..." に ljmp文を書く
- Cソース側に #pragma interrupt ... 文と割り込み処理関数を用意する
の 5ステップで、C言語による受信割り込みの実装を書く準備が整うことになる。先のブログでは 4 の部分を boot.asm の内容変更で行う方法が紹介されているが、boot.asm に書いた内容は Generate Application を実行するたびに初期化されてしまう (変更箇所が無効になる) ため、他の割り込み処理と同様に ...int.asm 内の指定部分に ljmp を書くアプローチの方がスマートだろう。
上記の方法で C言語による割り込みの実装を書く場合、APIとして RX8_bReadRxStatus() と RX8_bReadRxData() の 2つを利用することになる。先のブログでは受信バッファレジスタの値を直接ユーザバッファにコピーするだけの例が示されているが、用途によってはこれだけでは色々と足りない部分が出ると思う。標準生成されるアセンブラコードによる割り込み処理を見てみると、
- 受信完了 (8ビット揃っているか否か)の確認処理
- 受信エラー(パリティ/オーバーラン/フレーミング)の確認・フラグ記録処理
- フレーミングエラー発生の場合の受信復帰処理(コントロールレジスタの RX8_RX_ENABLE ビットを一度クリアしてから再セット)
- 受信バッファ (Device Editorでサイズ指定) のオーバーラン確認処理
といった処理が、"文字列受信"のための処理(行末コードの認識etc) 以外に組み込まれている。受信バッファは別として前の3項目は必須ではないかと思われるが、これらを全てC言語で処理していては時間的に間に合わないというケースも考えられる。
ここまでの情報を統合すると、C言語ではなくアセンブラによる実装も難しくないことが分かる。元々、自動生成のコードに「バイナリ受信用としては都合の悪い処理」が入っているだけで、それらを取り除けば上記のエラー処理と受信バッファへの格納処理をそのまま使うこともできる。名前が"RX8" のとき、rx8int.asm 内の「都合の悪い」処理は
tst [RX8_fStatus],RX8_RX_BUF_CMDTERM
jnz .RESTORE_IDX_PP
‥中略‥
jc .RESTORE_IDX_PP
ENDIF
の区間と、
RAM_SETPAGE_IDX >RX8_aRxBuffer
RAM_CHANGE_PAGE_MODE FLAG_PGMODE_10b
mov [X + RX8_aRxBuffer],00h
RAM_CHANGE_PAGE_MODE FLAG_PGMODE_00b
の区間だろうか。
前者の区間でコマンド終端の認識や特定コード以降の無視などを実行していて、後者の区間では受信バッファが埋まった際に末端データをナルにして、後の文字処理に支障が出ないようにしている。これらをゴッソリ取り除くだけでバイナリ受信の支障はなくなり、かつ受信データ配列 RX8_aRxBuffer[] や受信カウンタ変数 RX8_bRxCnt、ステータス変数 RX8_fStatus などの変更をアセンブラに任せることができ、残りの処理を C言語で行うとしてもエラー確認、ヘッダ確認、データ長確認、チェックコード確認などに抑えることができる。アセンブラの最後の受信バッファ更新部を変更して、リングバッファを組んでも良いだろう。
以上をまとめると、
- 受信割り込みルーチンの生成を有効にする
- "RxCmdBuffer" を Enable する
- Generate Application を実行する
- rx8int.asm から上記の”不要コード”を削除し、必要ならば小改造を加える
- 4の内容に応じて、C言語側の実装を書く
という 5ステップで、自動生成されるアセンブラコードを利用してソフトを組むことができる。ただし 4 での変更は、再度 Generate Application をすると消えてしまう点に注意。確認は行っていないが、2を Disable として、"Insert your custom code..."の箇所に"不要コード"を削除したアセンブラコードをコピーしてしまえば、より良い方法になるものと思われる。5の内容については色々と考えられるが、
- 受信割り込みとして C言語の関数へ ljmp せず、main関数で以下をポーリング処理
- 受信エラーあり or 受信バッファの先頭が規定のヘッダ値ではない → CmdReset
- 1を通過したら、受信バイト数 RX8_BrxCnt が受信データ長以上となるまで何もしない
- 2を満たしたら、受信データ全体についてチェックコード(チェックサム/CRC)で確認
- 3を満たしたら、受信データを利用
- CmdReset
という単純なパターンで済む場合は、"不要コード"を除いただけのアセンブラコードでも対応できる。いずれにせよアセンブラを用いる以上、無駄な処理を減らせるという利点を享受すべくアセンブラの割り込み関数から #pragmaなC言語の関数に ljmp する形にはしないことが重要だろう。
最後に余談だが、PSoC用のCコンパイラ (IMAGECRAFT製) はコンパイル前に "定数の畳み込み" の最適化をしてくれないことに今更ながら気付いた。
> #define HOGE (1+1)
> ...
> nData = nData + 1 + HOGE;
と書くと今時のコンパイラは畳み込みの最適化で nDataに 3 を足すマシンコードを生成するものだが、このコンパイラは愚直に 1ずつ 3回足すコードを生成してくれる。「定数は計算を敢えて残して分かりやすく‥(実行時には畳み込まれているから関係ないし)」と考えていると無駄処理に足を取られる格好になるので、このコンパイラを用いる場合は要注意です。
最近のコメント