同期パスキーの目的

SBI証券といえば不正アクセス問題で相当な被害を出して、

それ以後、ログインの追加認証が複雑化してめんどくさいなぁと思っていた。

複雑化はしたが本質的にメールアドレス認証であることは変わらず、

僕はそのメールアドレスを携帯電話のMMSにしているから、PCのメールアドレスを乗っ取っても無理だが、

メールも乗っ取られて被害が拡大するのはよくある話。本質的な解決には遠い。

という中でWebサイトのログインにもパスキーが導入され、早速設定した。


で、これをやったときに気づいたのですが、スマートフォンで設定したパスキーがWindowsでも使えて、

あれ? パスキーってデバイスに紐付くものではなかったのかと。

あまり考えていなかったのだが、Googleパスワードマネージャーにパスキーが格納されているようだ。

現在はChromeでは特別な設定をしない限りはそれが基本になっているよう。

このため同じGoogleアカウントを使用するAndroid端末・Chromeでは全て使えてしまうと。


パスキーについて生体認証でログインできる仕組みと思っている人もいるかもしれないが、

正確に言えばそれは正しくなくて、デバイスに登録された秘密鍵でログインする仕組みである。

ただ、それを利用する際に生体認証やPINコードを要求するのが通例であり、

結果として生体認証に見えていると。でも重要なのはそこではない。

この秘密鍵を格納された端末なのか外付けのセキュリティデバイスなのか、

これを奪われなければログインされないということが重要であると思っていた。

逆に物だけ奪えばログインできるんじゃないかと思うかも知れないが、そこは生体認証なりPINコードで対策していると。


でも、その秘密鍵がクラウド上にあっては意味がないのでは?

秘密鍵が格納された物を奪われなければログインできないはずだったのに、

秘密鍵をクラウドで保持しているなら、そこにログインできるアカウントを乗っ取ればことが済んでしまう。

うーん、どうしてだろうと考えたがよくわからない。


このクラウド上に保存する仕組みを同期パスキーと呼ぶそうである。

比較的新しい仕組みだが、2022年からAndroid同士・iOS同士ではこの方式をサポート、

2024年からはGoogleはAndroidとPCのChromeブラウザの間でもサポートするようになった。

冒頭書いたようなAndroidで設定して、Windowsで使うようなことができるようになったのは本当に最近だったと。

見ての通り、この同期パスキーはスマートフォン同士での導入が先行したが、

パスキーをデバイス固定にすると、端末変更のたびにパスキーを失うことになる。

これがかえって悪いんじゃないかという話があったようである。

端末を変える度にパスキーを再登録するのはかえって脆弱ではないかと。

Googleアカウント、Appleアカウントが乗っ取られるとそれこそ大変なわけで、そこは強固になっているだろう。

そのアカウントに紐付く形でパスキーを保持するのなら十分安全ではないか。

パスキーの秘密鍵は通常は本人であっても目視することはない。

フィッシングサイトでパスワードが奪われるような被害は考えにくい。


そんなわけで同期パスキーを各プラットフォーマーは推奨していると。

ただ、当然ですがこれはGoogleアカウントやAppleアカウントが強固であるのが前提になっている。

これが脆弱だと結局は1つ乗っ取られれば芋づる式にやられてしまう。

パスキーが広く普及するにつれて、そういう問題も起きそうですがどうでしょうね。

SBI証券ということについて言えば、取引パスワードや、出金時のメール認証というのはあるけど、

ログインパスワードと取引パスワードが同じで被害を受けたり、

メール認証ということはGmail乗っ取られたらやられる人はいるだろうと、

そういうところまで考えて強固化できている人がいるのかという話ですね。


とはいえ、これでログイン時の追加認証がなくなるのは楽である。

元々手間がかかる割に効果的とも思えないものでしたからね。

パスキーを使っておけばフィッシングサイトへの対策には十分というのはおそらく正しいとみられる。

そこが達成出来ればよいということにしたのだろう。

VFPレジスタの退避節約術

昨日、ARM Cortex-Mシリーズで割り込みからの復帰に使われるEXC_RETURNの話を書いた。

EXC_RETURNを使わないといけない

さらに面倒な話があって、それが浮動小数点数レジスタのことである。


今回使っているマイコンはCortex-M33なのだが、浮動小数点演算用のVFPが付いている。

ARMv7で言うところのM3, M4に対応するARMv8のプロセッサがM33で、

M3とM4はVFPを積んでいるかどうかがだいたいの差で、

M33のVFPはオプションだったはずだが、大概積んでいる印象はある。

今回は浮動小数点演算を行うシステムなのでVFPを活用している。

整数の汎用レジスタとは別にVFPには単精度だと16個の浮動小数点数レジスタとFPSCRという状態レジスタがある。


これらのレジスタは割り込み処理で退避する対象となっている。

Cortex-Mシリーズではレジスタの退避はハードウェア的に行われるが、

浮動小数点機能を使わなければ32bitレジスタ8個(R0~R3, R12, LR, PC, xPSR)で済むのに、

浮動小数点を使うと+17個ということで大変である。

ただ、ここをサボるための仕組みがあるんですね。


Cortex-M4(F) Lazy Stacking and Context Switching – Application Note 298

M4の話だが、M33も仕組みは全く同じである。

まず、CONTROLレジスタにFPCAというビットがある。

初期値は0だが、浮動小数点命令を使うと自動的に1にセットされる。

FPCA=0で割り込みが発生すると8レジスタのみの退避を行う。

FPCA=1で割り込みが発生すると8+17レジスタの退避を行う……わけではない。

FPCCRレジスタの設定によるのだが、初期値(LSPEN=1, ASPEN=1)の場合は、

スタックポインタに8+17レジスタ分の領域を確保して、基本の8レジスタだけの退避を行う。

その上でCONTROL.FPCA=0, FPCCR.LSPACT=1 に設定する。


割り込みハンドラに浮動小数点演算がないパターンでは、FPCA=0のまま、EXC_RETURNに到達する。

この場合は割り込みハンドラ中にVFPレジスタは一切変化しなかったので、

CONTROL.FPCA=1, FPCCR.LSPACT=0 に戻すだけで完了となる。

あとは基本レジスタの復帰を行って、スタックポインタを戻して割り込み元に戻る。

割り込み処理の多くは浮動小数点演算を含まないだろうから、このパターンが比較的多いはず。

では、浮動小数点演算があった場合はどうなるか。

浮動小数点演算を行おうとすると、FPCA=0→1への変化が生じるが、

このときにFPCCR.LSPACT=1がセットされていたら、この時点でVFPレジスタの退避が行われる。

このようにレジスタ退避を後回しにする仕組みを”Lazy state preservation”と呼んでいる。

FPCA=1でEXC_RETURNに到達した場合は、VFPレジスタも復帰して元の処理に戻る。


で、この仕組みでもEXC_RETURNのアドレスが重要な意味を持っていて、

M33ではEXC_RETURNの4bit目が割り込み時点でのFPCAによって変わる。

割り込み時にFPCA=0の場合は4bit目は1、復帰時にスタックポインタを8レジスタ分だけ戻すことも表す。

割り込み時にFPCA=1の場合は4bit目は0、こちらはVFPレジスタ分もスタックポインタが進んでいて、

EXC_RETURN到達時のCONTROL.FPCAの値に応じて復帰要否が変わると。


で、昨日書いたCPU例外から再びmain関数に戻ってくる場合の話だが、

具体的にはEXC_RETURN=0xFFFFFFB8へジャンプすると、

Threadモード、Mainスタック、Non-Secure、特権モード、FPCA=0というリセット時と同様の状態になる。

ただ、このまま動かし続けるとそれはそれで問題があって、

それはFPCCR.LSPACT=1がセットされていると言うことである。

この状態で浮動小数点命令を実行すると誤ったレジスタ退避をしようとする。

ジャンプ後にFPCCRレジスタの値を明示的に設定する処理を入れれば問題は解消した。


割り込みハンドラでFPCA=0にセットする仕組みは多重割り込みにも効いて、

浮動小数点演算をしない割り込みハンドラ実行中に割り込みが発生した場合、

割り込みハンドラのレジスタ退避という観点では8レジスタの退避のみでよい。

レジスタ退避用のスタックサイズの削減にも寄与してるわけですね。

Mainスタックだけ使う前提で言えば、

  • main関数(VFP使用) スタック消費200byte+レジスタ退避104byte
  • 優先度2の割り込み(VFP不使用) スタック消費8byte+レジスタ退避32byte
  • 優先度1の割り込み(VFP使用) スタック消費40byte+レジスタ退避104byte
  • 優先度0の割り込み(VFP不使用) スタック消費8byte+レジスタ退避32byte
  • MPU例外ハンドラ スタック消費0byte

で、合計528byte消費する可能性があるわけですね。


全体的にはあまり深く考えなくても効率よく動くように考えられているが、

通常と異なる操作をするといろいろ踏んでしまうんですね。

この失敗がなければ気づかなかったことは多かったわけですけど。

EXC_RETURNを使わないといけない

最近はまたARM Cortex-Mシリーズのマイコンを使って仕事をしていて、

CPU例外発生後にその情報を引数で渡してmain関数に戻ってくるコードを書いたら、どうもうまく動かない。

main関数を再度呼び出す


ARM Cortex-Mシリーズでは割り込み発生時にハードウェア的にレジスタの退避が行われる。

なので割り込みハンドラでレジスタ退避のことを考えなくてよい。

それは知っていたのだが退避させたレジスタは復帰させなければならない。

この復帰処理に入るための仮想的なアドレス(EXC_RETURN)がリンクレジスタに入る。

リンクレジスタというのは関数の呼び出し元を表すレジスタで、

関数からreturnするというのはここにジャンプすることを指す。

で、例外ハンドラから出るときには何らかのEXC_RETURNにジャンプするべきようですね。


とはいえ例外ハンドラを出るときに例外発生場所に戻っては仕方ないわけで、

そこを変えたければスタックに積まれている例外発生元アドレスを変える必要がある。

sp+0x18が例外発生元アドレス、すなわち復帰先アドレスになっている。

退避されているレジスタの値を変えたければここを変えなきゃいけないんですね。

EXC_RETURNのアドレスもまた例外発生時の状態が保存されている。

復帰先のモードやレジスタ復帰方法がここで決まるんですね。

このあたりの値を変える処理はアセンブラで書かないと無理なので、

例外ハンドラから出るアセンブラのコードを書いて、例外ハンドラの最後から呼び出すようにした。


ただ、これはこれで問題があって例外ハンドラを出ると再度例外が発生するという。

例外ハンドラの中で例外フラグを消しておく必要があったようで、

CFSRレジスタをリードして同じ値をライトして、消去するとうまくいった。

このことからわかるが、例外ハンドラを実行中は例外が起きないんですね。

例外ハンドラからEXC_RETURNにジャンプすると再度例外が起こるようになると。

さらに言えば例外ハンドラの優先度は他の割り込みより高いので、例外ハンドラ実行中は他の割り込みが起きない。


ただ、他にも問題はあったんですよね。

それは浮動小数点レジスタの退避ですね。

普通はあまり意識しなくても効率よくやってくれるのだが、けっこう複雑な仕組みで……

このあたりもまた改めて紹介しようかなと。

これもEXC_RETURNが関係していると言うね。

ハイパーリンクで駆動するVBA

来週不在になる前にそこそこ動くようにしないといけないなと、

いろいろやっていたが、それなりに動く環境が完成して一安心?


今回、作り込んだ機能の1つにメンテナンスポートがある。

メンテナンスポートのシリアル通信でコマンドを送るとあれこれできるのだが……

問題はこのコマンドがテキストベースではないということである。

テキストデータの送受信ならTeraTermなど使えばよいわけだが、

バイナリデータの送受信となるとそれでは面倒である。

将来的には操作用のツールを作る話はあるが、暫定的に送受信できるツールが必要と、

これをExcelでVBAを使って作ることにした。


通常はワークシート上からVBAのプログラムを呼び出すにはボタンを置くが、

コマンドが多数ある中でボタンを多数並べるのは大変である。

それで調べるとハイパーリンクを使った方法があるという。

適当なセルに向けてハイパーリングを貼る。

このリンクはHyperlink関数で生成したものではダメである。

そしてWorksheet_FollowHyperlinkルーチンを定義する。

ハイパーリンクのクリックでマクロを実行する (グローウィン)

するとクリックすると、ハイパーリンクの情報とともにこのルーチンが呼び出される。


この方法のよいところはハイパーリンクの情報をもとに処理を振り分けられることである。

例えばハイパーリンクのセルのテキストにコマンドの入力・出力に使うセル名などの情報を書いておいて、

Worksheet_FollowHyperlinkルーチンではこれを解析して実行すると。

リンクテキストに情報を書き込むのは見た目がよくないが、

リンクのあるセルの右隣に必要な情報を書いておくとか、

機能のバリエーションが多い場合には有効な方策ではないか。


この方策によりメンテナンスポートで実行できる機能はほとんどExcelのワークシートから実行できるようになった。

これを使っていろいろ動作確認をしていたけど、やりたいことはだいたいできているように見える。

未実装機能はいくつか残っているし、ソースコードもいろいろ残骸が残って汚いが、

不在中の他のメンバーの作業には足りるぐらいの機能はあるんじゃないかね。

charとuint8_tは違う?

マイコンのプログラムを書いていたが、そろそろ終わりが見えてきた?

来週中にはある程度動くものに仕上げたいところだが……


比較的後回しになっていた機能として製造検査・メンテナンス用のコンソールがある。

コマンド名を識別するにはこんなコードを書くわけですよね。

if(strcmp(cmdname_buf, "FOO")==0){

そしたらcmdname_bufの型がconst char*と互換性がないというワーニングが出る。

形式的な問題であるのは明らかなのだが、なんでそんなこと言われたんだろ。


cmdname_bufは uint8_t型の配列である。これがchar型と互換性がないという指摘である。

ただ、実態としては同じもののはずなんですよね。

どちらもサイズは1バイトなので一致を比較する点では差はない。

char型は符号無し設定になっているわけだから、表せる値も同じである。


このあたりは処理系によるところもあるが、GCCで実験してみた。

_Generic文を使うことで型を識別することができる。

#define TYPESTR(x)  _Generic((x), uint8_t:"uint8_t", int8_t:"int8_t",default: "other")

これを使って unsigned char, signed char, char型を識別すると、

それぞれ uint8_t, int8_t, other に分類された。

なんとchar型は設定によりsigned charかunsigned charと切り替わるわけではなく、

どちらでもない型で扱われているのだという。これには驚いた。


もちろんこれはコンパイラ次第という話ではある。

ただ、文字を表すことを主眼に置いているchar型と、

数値を表すことを主眼に置いている int8_t, uint8_t型は分けて考えている場合があると。

そしてstrcmpは文字列の比較を想定したものなので、

数値の型を持ってくるとちょっと違うとなってしまうようである。


一方が文字列リテラルなので実質問題ないような気もするが、

strcmpは終端が正しくNULL文字になっていないとバッファオーバーランの危険もある。

そういう事情も考えれば専用の比較関数を作った方がいいかもなと思い出してきた。

そうすると今度は文字列リテラルとconst uint8_t*の変換で何か起こるかも知れないけど。

明示的にキャストすればいいんですけどね。


今日でドキュメント作業が片付いてやれやれと思ったのだが、

最初にドラフト版に書いた日付を見ると1ヶ月前で、えらいかかったなと。

他のドキュメントの改訂とかもやってたので時間がかかるのも仕方ないが、

当初の見積もりに比べるとちょうど1ヶ月遅れかなぁと。

これが終わったらその次にはやらないといけないこともあるけど、とりあえずは動くことが優先ですね。

main関数を再度呼び出す

散々遅れていたマイコンのコーディング作業を進めている。

つぎはぎだらけだが部分的に動くことを優先したらこの有様。

後で綺麗に整理しますけど。


この機器にはボタン操作で設定情報を変更できる機能がある。

設定情報を変更したら再起動という動作になっている。

再起動という観点ではウォッチドッグタイマを作動させるという方法も考えられるが、

設定情報による再起動であることを認識できるという観点では、

スタートアップルーチンにジャンプさせるのがよいだろうと。


というわけでまず最初はmain関数を再度呼び出すものを書いたが、

よく考えたら、これではスタックポインタがだんだんズレていく。

ジャンプ元の関数に戻ってくることはないのに、無駄にスタックを残している。

というわけでスタックポインタを再設定するコードをアセンブラで書いて、

これを経由してmain関数を再呼び出しするようにした。


ところがこれでも問題があって、それはRAMの初期化をしていなかったこと。

本来0で初期化されているはずの領域が0に初期化されていないことにより、不思議な動作をしてしまった。

これはこれでコーディングミスという側面もあったのだが、やはり問題である。

ということでゼロ埋めして、初期値付き変数セクションを初期化する処理を追加した。


最初はこの処理は設定情報の書換にだけ使うつもりで考えていた。

ただ、後にCPU例外発生後にも使うとよいことに気づいた。

というのもCPU例外が発生した場合、エラー情報を表示する必要がある。

このエラー情報の表示がそこそこ複雑な処理なので、例外ハンドラから直接駆動するのは難しいのではないかと。

そこで、同様の方法でmain関数に戻ってくればよいじゃないかと。

このとき引数を渡すこともできるので、そこに例外情報を埋め込めば、

その情報を表示したり記録したり出来るねと。


他にもやり方はあるとは思いますが。

リセットしても残るレジスタに情報を残しておくとか。

というわけでけっこういろいろアセンブラでも書いているという話だった。

Raspberry Pi PicoのPIO

職場で製品評価用の治具を作る話があって、けっこうお金かかるが作ろうと動いているよう。

従来、ファンクションジェネレータを接続していたところ、

治具にマイコンを乗せて代替するアイデアを出したところそれでいこうと。

で、USB接続できると便利だよなとか色々考えると Raspberry Pi Pico を載せるのが手軽ではないかと、

治具にはそれ用のソケットを用意するということで話が動いている。


Raspberry Piといえばシングルボードコンピュータの代表例で、

その上でLinuxなど動かしたりして使われることが多い。

ただ、Picoは高級なOSが動くようなものではないので全く違うものである。

今回想定しているのはUSBインターフェースだけのものだが、

無線LAN, Bluetoothを搭載したものもあって、通常インターネットにつながらない機器をインターネット接続化できる。

すなわちIoTの担い手を想定して作られたマイコンボードである。


ファンクションジェネレータの代わりというだけなら、もっと別の方法もあるが、

もう1つやりたいことがあって、それがシフトレジスタからのデータ取得である。

外部からクロックを入力すると、シフトレジスタのデータが読み出せて、

それで機器の状態を取得できる仕組みがある。

クロックを入力することはファンクションジェネレータでもできる。

ところが、このシフトレジスタから読み出したデータを取得するとなれば、

オシロスコープで読めなくもないが、やはりマイコンの出番であろうと。


クロックを出してデータを読み出すというのはSPI通信そのものだが、

困ったことに1つのクロックで2つのシフトレジスタから同時に信号が出てくるのである。

こういうのなんかあったなと思ったらQuad SPIか。

そういうのに対応したマイコンもありますけど。


というわけで、結局はGPIOでガリガリ操作しないといけないのか、

と思ったのだが、いろいろ調べているとRaspberry Pi PicoにはPIOという機能があるそう。

GPIOに接続できるステートマシンが4台あって、その4台共有で32個の命令を格納できるメモリがある。

このステートマシンはメインのプログラムとは独立して動作する。

いろいろ制約はあるのだが、特殊なインターフェースの制御に使用されるようだ。


PIOのステートマシンはIN命令用の入力、JMP命令用の入力、

OUT命令用の出力、SET命令用の出力、Side Set出力とそれぞれ連番で複数ピン設定できる。

さっき書いた2bit入力のSPIみたいなのは入力を2つ連番で取っておけばよいと。

IN命令・OUT命令はGPIOからFIFO、FIFOからGPIOに数ビット動かす命令で、

シリアル通信のデータはこの命令で出し入れするのが通常である。

SET命令はそれとは別にON/OFFを制御する命令なのだが、

実は命令に付帯してピンの出力値を規定できるSide Setという機能があって、

SPIのクロック出力みたいなのはこちらの方が容易に書ける。


ステートマシンの動作クロックを1MHzに設定して、

IN命令+Side Set 0とJMP命令+Side Set 1をぐるぐる回すと、

非常に大雑把には500kHzのSPIとして機能するということになる。

こういうのはわかりやすいが、実際にループ・条件分岐と複雑な動きもあるので、

思ったタイミングにするにはいろいろ工夫が必要そうだが。

命令数が32個と限られるのも制約ではある。

すくない命令を上手くやりくりする工夫として全命令にSide Setと遅延を付けられるが、

あわせて5bitという制約があり、Side Setを1bit使うと、

遅延時間は最大4bit、すなわち15cycleまでの遅延しかできないという。


制約は多いのだが、複数のステートマシンを組み合わせたり、工夫の余地はいろいろある。

命令が最大32個だと複数ステートマシンなんて厳しいと思ってしまうが、

同じ命令で動くステートマシンを複数作ることもできる。

そういうのも上手く活用している例もあるのかな。


GPIOでも作れそうではあるけど、せっかくならこういうのを使うのもいいのかも。

Raspberry Pi Picoなのは入手性・価格・利便性などを考えた結果だが、

特殊なインターフェースを作り込むことが想定されてたんですね。

SETレジスタとCLRレジスタ

新製品のマイコンのプログラムを本格的に書き始めた。

今までテスト用のプログラムをメーカーのライブラリを使って作っていたが、

うまく設定できてない部分があるのは把握していたので、レジスタを直接操作してあれこれやっていた。


このマイコンのレジスタ構成は面白いところがあり、

各種のフラグについて、値を読み書きできるレジスタとは別に、

SETレジスタとCLRレジスタというのが存在する。

INTENSET = 0x100; というのは INTEN |= 0x100; とほぼ同じで、

INTENCLR = 0x100; というのは INTEN &= ~0x100;とほぼ同じである。

割り込み有効フラグまでこんな方法で管理しているのは珍しい気がする。


この方法のメリットは割り込みなどで処理が競合しても影響を受けないことである。

INTEN |= 0x100; というのは INTENをリードして、0x100とのORをとって、ライトするという処理である。

このリードとライトの間に割り込みが入り INTEN &= ~0x20; が挟まったとする。

仮に最初のINTENが 0x20 だったとする。

0x120を書き込む直前で、割り込みでINTENが0x20→0x00と変化するも、

割り込み処理から戻ると 0x120となり 割り込み処理で消したフラグが復活してしまう。

こうならないように割り込み無効化して、書換を行いましょうという話である。

同じフラグをこうして競合して書き換えることがなければ問題ないのだが。


こういうことが比較的発生しやすいGPIOは対策されていることが多い。

1つはSETレジスタとCLRレジスタを用意するという方法。

もう1つはビット単位で操作出来るレジスタを用意することである。

32bitとか一括して操作出来るレジスタとは別に、1ビットずつリード・ライトできるレジスタもあると。

PORT ^= 0x10; のリード・ライトの間に PORT ^= 0x01; が挟まると、後者のビット反転が効かなくなるが、

PIN[4] ^= 1; と PIN[0] ^= 1; という形であれば、2つの書換は干渉しない。

PIN[4]のリード・ライトはその1ピンにしか影響しないためである。

両方用意しているのはそれはそれで珍しい気がするが、このマイコンは両方いけるみたいですね。


独特ではあるが、よくできた仕組みではあるのかな。

一般的なメモリとはだいぶ違うので戸惑いはありそうですけどね。

まぁマイコンってそんなもんだ。

9bit UARTをPCから使うのは難しい?

今後、製品評価でこういうことを予定してるんだけど、

どういうものが必要そう? と聞かれて、その1つがRS-485のインターフェースだったのだが……

そこで思い出したのが9bitのUARTのことだった。

9bitのUARTが必要なわけ


あまり一般的ではないと思うのですが、RS-485で1バイト9bitの通信が行われることがある。

UARTはLSBファーストなので、MSBは最後のビットにあたるが、

これは8bit+パリティの通信のパリティの位置に相当する。

そもそも9bitのUARTに対応している機器もあるとは思うが、

8bit+パリティ設定でパリティビットを任意に変更するという実現方法もある。


今まではマイコンとプロトコルアナライザを組み合わせて評価をしていたが、

製品評価に様々活用することを考えると、PCから制御できた方がよい。

RS-485のUSBインターフェースは買わないとどうにもならないが、

そもそもこの方法で9bit UARTに対応できるのだろうか?

RS-485でもRS-232CでもUSBシリアルインターフェースのIC自体は同種のもので、

手元にあったFTDIのUSBシリアルI/Fを使って実験してみた。


ところがこれがうまくいかなかった。

そもそも9bit設定がないのは当然として……

8bitでパリティ設定をMARKとSPACEを行き来しながら送受信すると、

まず送信側はMARKとSPACEを遷移するところでだいぶ間が空く。

COMポートの再設定を行うのにどうにも時間がかかるよう。

こんなに間が空いたら受信側でタイムアウトになってしまう。


受信側はMARKかSPACEに設定して、MSBが設定値と一致しない場合にパリティエラーとなることを利用すればよいとある。

SPACEに設定して、パリティエラーがあればMSB=1、なければMSB=0とわかると。

ところがパリティエラーが起きたことを判別できない。

なんで? と探してみたのだが、パリティエラーを検出したら、

文字を置き換え(というか挿入する)設定は存在するらしい。

ただ、その設定をしてもどうも思ったようにいかないのである。

エラーが起きたことを検出できればよい用途ではよいが、

このバイトはエラーがあったかなかったか判別する用途にはそぐわないようだ。


Virtual COM Portではなく、D2XXというFTDIのICを直接操作できるライブラリを使ったのだが、

やはりそれでも上記の問題は根本的に解決しなかった。

スペックにあるとおり、あくまでも7bitと8bit通信のためのICである。

MSB=0か1に固定すれば9bit通信できなくもないが、

本来9bit通信のためのICではないので目的は達成出来ない。


いろいろ探したのだが、9bit通信ができるUSBシリアルI/Fって普通にはないんですね。

世の中に全く無いかというとそうでもないようだけど。

ただ、9bit通信となればVirtual COMポートは適用できないわけですよね。

この時点で汎用性に乏しいので作りにくいのかなと。


マイコンのプログラムではこういうことはそう難しくないのですが。

USBインターフェースを持ってるマイコンボードを用意して、

UARTにRS-485インターフェースを取り付ければできそうだけど。

そんなに作るのが大変とは思わないけど、

そこにマンパワーを投じてやるのも考え物だなと。

果たしてどうしましょうかね。

I2C通信を割り込みでハンドリングする

開発中の製品でマイコンに接続するある機器がI2C接続で、

効率よく通信しようとすると割り込み処理でやらないとな、

ということでいろいろ調べていたのだが、ちょっと複雑だった。


そもそもI2Cってなんやねんという話ですが。

I2Cとは (ROHM)

SCLとSDAの2つの通信線で複数の機器をぶらさげて通信できると。

ただ、今回の場合、この構成にそこまでメリットがあるわけでもない。

接続先の機器が1つで、さらに言えば送信一辺倒だからである。


I2Cの典型的な使い方としては、温度センサなどを複数ぶら下げて、

設定を送って、値を読み出すというような使い方が多いのかな。

I2C接続のEEPROMを何かで使ってたような記憶もあるが、

双方向でやりとりするような用途には適しているのかも。

SPIだと、CS, CLK, MOSI, MISO で4本ぐらい使うところ、SCLとSDAの2本でいけるので。


で、I2Cってけっこう複雑なんですよね。

データ送信する場合、STARTコンディションにして、アドレスを送って、

するとスレーブからACKが返ってくるのでこれを確認する。

そしてデータ1byteを送るとACKが返ってくるので確認する。

これを繰り返し最後にはSTOPコンディションにして終わりと。

これを割り込みで処理しようとするとACKされるたびに割り込みが入る形になる。

このマイコンではI2CとFIFOの組み合わせはできないらしい。


受診の場合は、アドレスを送ってACKを受け取る部分は同じ。

次にマスタはSCLのクロックを駆動して1byte受信する。

続けて受信する場合はACKを送信、最後の場合はACKを送信せずにSTOPコンディションにする。

この最後にACKを送信するか、しないかという判別のこともあり、

この受信では最後にACKを送るか、送らず終わるかを受信開始時に表明する必要がある。

このため受信完了時に都度割り込みが入り、それを消化するまでは次の受信が始まらない仕組みである。


この操作をDMAと組み合わせてやる方法があったが、えらく複雑だった。

これ、まだI2Cのマスタ側はよいけど、I2Cのスレーブ側はより複雑である。

そこで知ったけど、I2Cってのはスレーブ側がマスタを待たせる手段があって、

1byte受信し終えた後、SCLの立ち下がりを検出して、SDA=Loを出力、

それを確認後、マスタはSCLを立ち上げるわけだが、ここでSDA=Hiになるのを待つんですね。

このSDA=HiにせずにLoのまま保つことでマスタを多少待たせられると。

受信側にとってみれば次の受信時にACKを返すか、返さないかという選択肢がある。

これ以上受信するデータがない場合はACKを返さないというやり方もあるため。

(このあたりのポリシーはI2Cデバイスにもよる)

ここの処理が終わるまで待たせることもできるんですね。


結局やることとしては他の通信とそう大差はないのかもしれないが、

I2Cのステータス管理を1byteごとに行うのが原則なので、

そこをペリフェラルで肩代わりしてくれる部分もあって、

DMA転送の場合はその機能との組み合わせが前提ですよと、

あれこれ複雑なことが書いてあって、めんどくさい! となったのだった。

1byte単位で割り込み入れてもらうならポーリングのコードがかなり参考になってよかったが。