田舎の組み込みプログラマーがわざわざ趣味でも色々開発してみようとあがく様を綴るブログです。
DMM.makeのクリエイターズマーケットに出品しています。

hx711の仕様を読み解く2018年11月30日

先日、hx711の動作にミステリーな点があると書きましたが、結論から言うと、思い込みよる勘違いでした。
今回はその辺りについて書いてみようと思います。

やっちゃだめなやつ

今回の失敗例は、4個のhx711から同時に重量値を読み出そうとして、1つのシリアルクロック(PD_SCK)で4個まとめて処理する方法です。
つまり、4個のhx711が全てreadyになったら読み出しをかけていたんですが、その結果がこちら。

複数のhx711を一つのクロックで読み出すと・・・

readyになるタイミングがバラバラなだけでなく、数周期の間readyにならないやつまで出てくる始末です。
4個全てがreadyにならないと読み出しをかけられないため、しばらく待っていたんですが、他の3個はその間一定周期で短時間だけhighになっています。
ここでようやくhx711のアウトプットレート(10SPS or 80SPS)の意味に思い至ったわけです。

そもそも

実はこれまでA/D変換は逐次比較型しか使ったことがなくて、変換は任意のタイミングで出来るものだと思っていました。

hx711の場合、24bitということは多分ΔΣ型で、あらためて調べてみると、常時変換をしているタイプでワンショットやマルチプレックスには向かないとのことでした。
常時変換しているということは、読み出しをかけなくてもずっと動いているということなので、その辺をロジアナで確認してみました。

4個のhx711の変換タイミング

ご覧のように、それぞれ一定周期で一瞬だけhighになっています。
highになっている間はbusyなので、この時にA/D変換を行っているわけですね。

なので、busyになる直前に読み出しをかけると、読み出し中にA/D変換が行われてしまうことになり、その時のDOUT出力はおかしなことになってしまうわけです。
今使っている4個だけでもA/D変換周期の個体差が結構あるため、徐々に互いのタイミングがずれてきて問題が発生したというオチでした。

というわけで

hx711を複数使うシンプルな方法は、それぞれにそれぞれのシリアルクロックで読み出しをかけてやることですね。

まぁ、横着せずに最初からそうしておけば余計な苦労をせずに済んだのですが、趣味ですのでこういうのもありかなと思います。
なんとなく理解が深まった気もしますしね。

本題に進む前に

今まで使っていたモジュールは推奨回路と違っていたり出力周期が変えられなかったりと気になる点が多かったので、他のモジュールを取り寄せてみました。

2種類のhx711使用基板の比較

右のモジュールはコンデンサの容量は分かりませんが部品構成は推奨通りで、出力周期も変えられます。
あと、コンパクトで裏面がベタなのもポイントが高いですね。

せっかくなので周期設定を80SPSにしてみました。

いよいよ本題

配線とソフトを変更して実行した結果がこちらです。

個別クロックで複数のhx711を処理

ソフトは大がかりな変更をしましたが、珍しく一発で動いてかなり感動しました。
そしてそのソフトがこちら。

複数のhx711をR8Cで読み出すソース

メインループで直に処理していますが、今回はスマートに構造体風に処理してみました。
一回のループで1パルスだけ処理するようにし、次のhx711に切り替えるようになっています。

一見何の言語か分からないかもしれませんが、R8Cの構造化アセンブラです。
マクロ機能が便利なので調子に乗って使いまくってしまっているのが、正体不明な感じに拍車をかけていますね。

ifとか++があったりしてC言語風でありながら、アセンブラですので普通にニーモニックが使えますし、マイコンのフラグも記述できます。
R8C自体もビット処理が使いやすかったり、アドレスレジスタ以外にもSBとかFBが相対アドレッシングに使えたりと、アセンブラでプログラミングするのが楽しいマイコンですね。

もう一つの方法

今回はやっていませんが、複数のhx711を使用するもう一つの方法として、複数のhx711に同一の外部クロックを供給してやる方法があります(たぶん)。

個々の内臓オシレータで動作している状態ではタイミングが徐々にズレていくのは当然と言えば当然なわけで、ならば、同一の外部クロックで動作させれば(たぶん)同期がとれるはずなわけです。

その際、外部クロックは最大20Mhzまで使え、RATE端子がhighの場合は20MHz÷138240で約145SPS、lowの場合は20MHz÷1105920で約18SPSとなります。
つまり、外部クロックを使う事で変換周期も変更できるということですね。

あともう一つ

データシートに「セトリング時間」という項目があり、A/D変換4回分の時間になっています。

A/D変換の場合、ステップ信号入力に対してデジタル出力が対応した値に収束するまでの時間ということで、例えばロードセルに100gの重りがスパンと乗ったとして、デジタル出力値がその100g相当の値になるまでには4回のA/D変換が必要というわけです。

ということで、今回はhx711の仕様を少しばかり読み解いてみました。

ラジコン用のコーナーウエイトゲージ3回目2018年11月11日

訂正やら変更やら

先日、今使っているHX711モジュールはRATE端子がどこにもつながっていないと書きましたが、ICの下でちゃんとGNDに接続されていました。
なので、レートは10SPSになっているはずで、readyになるまでの時間が4個のHX711でバラバラになっている理由が分かりません。

そこで、別の用途のために買ってあった赤いヤツを繋いでみました。

HX711 赤いモジュール

あと、先日はPD_SCKの立ち下がりクロックで割り込みをかけていましたが、PD_SCKの立ち上がりに対してDOUTは最長でも0.1μsecで準備ができるとのことで、割り込みのオーバーヘッドよりもはるかに短時間のため、立ち上がりクロックでの割り込みに変更しました。
となると、無理にHIGH時間を短くする必要もなく、PD_SCKの配線を4個のHX711に引き回していることもありますので、HIGH時間も長めにしました。

で、きっと赤いヤツはきっちり10SPSなんだろうと期待して波形を見てみると・・・。

HX711 4連波形・・・一人以外速度違反

違ってました orz
しかも、この接続に変えるまではずっと1番が10SPSだったのに、この接続にしてからはずっと3番が10SPSだったりします。
(赤いヤツは4番)

ミステリーは放置して

次のステップに進みます。

HX711から読み込んだ値を観察していると、下位8bitは常時変動している感じです。
赤いヤツについては繋いだばかりの時は変動は7bitで、流石SparkFunだと思ったんですが、ICが温まってくると8bitになっちゃいました。

しかしまぁ、生データをいくら眺めていても何にもなりませんので、重量値に換算してみましょう。
最近仕事で2点校正をよく使っていたので、それで行ってみます。

HX711の入力値を分銅で校正

この時のために、ちゃんと分銅も用意しましたよ。

で、100gと500gの分銅を乗せたときのデータを記録して、その値を元に換算します。
1番のロードセルだけ負の値を出しますが、そこはきっちり対処して負の値もちゃんと扱えるように作り込みます。

24bitは扱いづらいので符号拡張して32bitにし、平均を取る→2点校正で1mg単位で重量値に→ゼロ点補正→100で割って0.1g単位に・・・といった手順でやってみます。
が、アセンブラではかなり大変そうなので・・・

苦しいときの神頼み

C言語の力を借ります。

C言語の力で2点校正

C言語を使うのは久しぶりすぎて、こんな感じでいいのかどうか分かりませんが、一応ちゃんと動作しているようなので良しとします。
注意点は、整数演算なので有効桁確保のために掛け算を先にやること、その時32bitでは足りなくなるので64bitで演算することくらいでしょうか。

RAMモニタの数値は実際にラジコンを乗せて量ったもので、1行目の左から順に左フロント、左リア、右リア、右フロントとなっています。
HX711の計測値は今のところ加算平均5回で処理していますが、0.1g単位だとほとんどバラつきません。

精度については、多分ロードセルの温度特性なんでしょうけど、これまでのところゼロ点は最大5gくらいズレますし、ゲインは500gの重りで0.2gぐらいズレたりします。
値がバラつくことに関してはシールド線を使ったりコンデンサを入れたりで改善できそうな気もしますが、温度特性は頭の痛いところですね。

まぁ、自分用のコーナーウエイトゲージということを考えると、4個が同じ様にズレるのなら特に問題はないんですけどね。

といったところで、今回はここまで。

ラジコンのコーナーウエイト計測

ラジコン用コーナーウエイトゲージ2回目2018年11月08日

続きです。

コーナーウエイトゲージということで当然秤は4つあるわけで、HX711も4つ使っています。
なので、ロジアナでまとめて観察してみたところ、気になることが・・・

HX711の足並みが揃わない

1回目の読み出しは次のチャンネルとゲインを指定するためのものなので除外するとして、それ以降のREADYになるタイミングがバラバラです。

DOUT 1(1個目のHX711のDOUT)だけは95msec弱とほぼ10SPS相当なので、それ以外はダメということになりそうです。
もしかすると、使っているHX711をモジュール化した基板でRATE端子をどこにも接続していないのが問題なのかもしれません。

その辺の調査は後回しにするとして、次にデータを見てみます。

HX711 4個のデータもバラバラ

データも見事にバラバラですね。
ゲインを64に抑えてもいますので、先頭から12ビットくらいは揃って欲しかったんですが・・・。

特にDOUT 1がいきなりFFhから始まっているのは、配線ミスやロードセルの不良も疑ったんですが、データシートを見ると測定値は2の補数形式ということなので、単にマイナスの値になっているということのようです。

この辺も使っているモジュールが推奨回路通りになっていなくて、入力抵抗が片側にしか入っていないことも影響しているかもしれません。

なので、推奨回路通りになっていると思われるモジュールを発注済みです。
中国発送のようですので、到着までまだしばらくかかりそうですが。

もうひとつ気になっていたこと

それは、割り込み中のデータシフト処理にどのくらいの時間がかかっているかということです。
これもロジアナを使って確認してみました。

割り込み処理タイミング

割り込み処理の入り口でポートビットをオンし、出口でオフしています。
クロックの立ち下がりで割り込みをかけるようにしていますが、割り込み処理に入るまでに1.96μsecかかっていますね。
そして、割り込み処理が5.556μsec。

次のクロックまでにはまだ若干の猶予がありますが、計測結果を別のマイコンに転送することになると思いますので、そうなると割り込み処理が増えるのでちょっと厳しいかもしれません。

そんなこんなで

一応、年内にはサーキットでコーナーウエイト調整の効果を確認してみたいとは思っているんですが、こんな感じでちまちまやっていますので、どうなることやら。

ラジコン用のコーナーウエイトゲージを作ろう2018年11月07日

またもや随分更新をサボってしまいました。

忙しかったと言えばそれまでですが、やはり具体的なビジョンがなければ頓挫しやすいということだと思います。

ということですので

ラジコン用のコーナーウエイトゲージを作ってみることにしました。

元々電子工作を始めようと思ったのはラジコン用の何かを作りたいというのが一番の理由で、コーナーウエイトゲージもずっと以前から作りたいと思っていた物です。

ぐずぐずしているうちに市販品が出てしまったわけですが、ロードセルとかロードセル用ADコンバータとかが簡単に手に入るようになったので、今更ながらやってみます。

ラジコン用コーナーウエイトゲージ試作品

で、これが試作品。
ロードセルは2kg、ADコンバータは定番のHX711、マイコンはいつものようにR8C/32Mです。

20MHzで不足とは・・・

HX711の使い方はあちこちで解説されていますが、マイコンからパルスを送ると同期してデータが出てくるという、クロック同期シリアルインターフェイス的なやつですね。

「的な」と言っているのはちょっと独自仕様が入っているからで、おかげでマイコンのシリアル通信機能が使えないわけですが、今回のように4つの物を使うとなるとクロック同期シリアルが4つも使えるマイコンなんて見当たらないので結局は自力でなんとかするしかないわけですね。

具体的には、転送クロックをタイマー機能で生成し、立ち下がりで割り込みをかけてシフトするわけですが、クロックパルスの幅は標準1μsecとかなり高速です。
この速度だと、1周期は2μsecですので20MHzだと40サイクルしかなく、この時間で4つ分のデータのシフトは無理そうというか実際無理でした。

ゆっくりしていってみる

標準はあくまでも標準ですので、ゆっくりやれば別に問題はないわけです。

が、一つ注意が必要なのはパルスがHIGHになっている時間が60μsecを超えるとHX711がパワーダウンモードに入ってしまうということです。
で、通常は50μsecまでにしろとのことですので、そこは守らないといけないわけですね。

ということで、実際の波形がこちら。

HX711クロック、データ波形

クロック周期を10μsec、HIGH時間を0.5μsecにしてみました。
具体的には、タイマRC機能でTRCIOAに出力、TRCGRAで10μsec周期を作りつつパルスの立ち下げと割り込み処理を、TRCGRCで9.5μsecの時点でパルスを立ち上げています。

どうやら10μsecもあればHX711の4個分のシフト処理は間に合うようです。

あとはメインループでHX711のデータ出力を監視し、4個全部の出力がLOWになったらタイマRCをスタート、タイマRCは1回分の入力処理が終わったら停止するように作り込んでいます。

といったところで、今回はこの辺で。

ゆっくりしていってみる2018年06月13日

このところ仕事が色々大変だったり、仕事以外でもアレコレあったりしたので、ここらでちょっとゆっくりしてみたいと思います。

というわけで

ゆっ くりチップで遊んでみます。

ゴチャゴチャゆっくり

なんかごっちゃりしていますが、PCのターミナルとR8Cマイコンをつなぎ、R8Cマイコンからゆっくりチップにしゃべらせる構成になっています。
具体的にはこんな感じです。

R8CでPCとゆっくりチップの仲介

ゆっくりチップに対してローマ字風のコマンドを送ることで発声できます。
R8Cのソフトでは、受信割り込みでキューに入ったデータをメインループで横流ししています。

構造化アセンブラと当方オリジナルのマクロライブラリで記述していますので見慣れない感じだとは思いますが、だいたい理解していただけるのではないかと。
R8Cで仲介しなくてもPCのターミナルとゆっくりチップで直にやりとりもできるのですが(USB<->UART変換は必要)、次段への布石とい うことで。

さて

ゆっくりチップとのUARTによる通信にはいくつかのお約束があり、主なところはだいたい以下の通りです。

  1. ボーレートを認識してもらうため、最初に"?"を送信
  2. コマンドの最後にはキャリッジリターンをつける
  3. ">"が返ってくるまで次のコマンド送信は控える

ちなみに、発声中に次のコマンドを送ると"*"が返ってきます。

なので、その辺りの処理を実装すると、後の処理が楽になります。

$LOOP
  ; PC→ゆっくり
  PROC$_CALL S0R$_GET    ; 受信データ取得(→R0L)
  for S == OFF
    PROC$_CALL$_B YUKKURI_UART$_PUT, R0L
    PROC$_CALL S0R$_GET  ; 受信データ取得(→R0L)
  next
  ; ゆっくり→PC
  PROC$_CALL S2R$_GET    ; 受信データ取得(→R0L)
  for S == OFF
    PROC$_CALL$_B YUKKURI_UART$_GET, R0L
    PROC$_CALL S2R$_GET  ; 受信データ取得(→R0L)
  next
$END

;========================================================== PART$ YUKKURI_UART ;---------------------------------------------------------- ; メモリ確保 QUEUE$_B$_ALLOC yukkuri_send_queue, 128 ; 送信キュー確保 send_char: .blkb 1 ;========================================================== ; コンディション定義 CONDITION$_DEFINE S_ENABLE ; [送信可能] ; ステータス定義 STATE$_DEFINE YUKKURI_BUSY ; ビジー<[] STATE$_DEFINE YUKKURI_READY ; 送信受け付け<[送信可能] ;----------------------------------- ; ビジー<[] ;----------------------------------- STATE$ YUKKURI_BUSY, 0 _GET if [ PARAMETER ].b == '>' TRANS YUKKURI_READY endif _END ;======================================= ;[送信可能] ;======================================= CONDITION$ S_ENABLE _PUT QUEUE$_GET yukkuri_send_queue, [send_char] for C ; キューデータあり PROC$_CALL$_B S2T$_PUT, [send_char] if [ send_char ] == 0dh TRANS YUKKURI_BUSY endif QUEUE$_GET yukkuri_send_queue, [send_char] next _END ;----------------------------------- ; 送信受け付け<[送信可能] ;----------------------------------- STATE$ YUKKURI_READY, S_ENABLE REDIRECT _PUT _END _PUT ; 代替えキャラを元のコードに R0L = [ PARAMETER ].b if R0L == '!' R0L = 27h elif R0L == '$' R0L = 0dh endif QUEUE$_PUT yukkuri_send_queue, R0L THROW ; 後は STATE に投げる _GET PROC$_CALL$_B S0T$_PUT, [PARAMETER].b THROW ; 後は STATE に投げる _INIT QUEUE$_INIT yukkuri_send_queue PROC$_CALL$_B S2T$_PUT, "'?'" ; ゆっくりチップに同期コマンドを送る ;---------------------------------------------------------- _END ;==========================================================

といった感じです。

PART$というのは状態遷移処理を内蔵したオブジェクト的な物で、指定したメソッドをアクティブなSTATE$で処理するのが基本です。
メソッドの探査順はPART$→STATE$(アクティブ)→CONDITION$(アクティブ)となっており、対応メソッド処理が見つかった時点で打ち 切りますが、THROWで次段に投げることができ、REDIRECTで次段の別のメソッド処理に投げることができるため、非常にシンプルな記述が可能に なっています。

という、投げっぱなしな感じで今回はここまでです。


シリアル カンタービレ2018年05月12日

とても昔からあって、今でも便利に使い続けられている技術。
それは俗にシリアルとかターミナルとか呼ばれているやつです。

以前少しだけarduinoをいじってみたんですが、標準でターミナルが実装されいて、コマンドを送ったりデータを表示させたりといったことが簡単にできるのが印象的でした。

そんなわけで

随分以前に写真だけ公開した基板をようやく動かす時が来ました。
実は通電するのも初めてだったりします。

いろいろシリアル基板

そして、まずはシリアル送信を試してみます。

$LOOP
  ; タイミング処理
  btstc SYSTEM_CYCLE  ; 1msec毎にONされる
  if C
    DIVIDING$_ regular_10, 10  ; 10分周
      PROC$_CALL$_B S0T$_PUT, 30h
      PROC$_CALL$_B S0T$_PUT, 31h
      PROC$_CALL$_B S0T$_PUT, 32h
    _$
  endif
$END

; シリアル送信処理
S0T$ 1
  QUEUE$_B$_ALLOC send_queue, 20    ; 送信キュー確保
_ITP  ; 送信終了割り込み(レジスタバンク切り替え)
  QUEUE$_GET send_queue, R0L
  if C
    WRITE$_B R0L
  endif
_PUT
  if [ ti_u0c1 ]  ; 送信バッファ空
    WRITE$_B [PARAMETER].b
  else      ; 送信中
    ; 送信データをキューに入れる(満杯の場合は空くまで待つ)
    QUEUE$_PUT$_WAIT send_queue, [PARAMETER].b
  endif
_INIT
  QUEUE$_INIT send_queue
_END

10msec周期で3バイトのデータを送り続けるだけの簡単なお仕事です。
簡単と言いつつ、送信バッファが空なら直に送信し、そうでなければキューに入れるという小技も使っています。
そして、キューにいれておけば、送信終了割り込みで勝手に持って行ってくれるという寸法です。

結果はこんな感じ。

データを送り続けるだけの簡単なお仕事

狙い通り、10msec周期できっちり送信されていますね。
ちなみに、通信速度設定は9600bpsです。

気分のほうが乗ってきたので

受信もやってみます。

パソコンのターミナルソフトから送ってみて・・・・・・・・・・・・・・・・・・・・・・・・・・受信できません。

一応、受信割り込みは1回かかるのですが、2回目以降は反応なし。
よく見るとフレームエラーが出ていましたが、速度・ストップビット・パリティの設定は間違っていないし、何故?

で、信号をよく見てみると・・・

電圧が低いし

信号は問題無さそうですが、電圧が3V少々ですね。

実は、使っているUSB-シリアル変換モジュールの出力(マイコン側では入力)が3.3V系なんですが、入力は5V対応なので5V電源のマイコンともそのまま接続できるということだったので気にしていませんでした。

しかし、R8C/xMの5V電源時のH入力閾値には届かないようですね。
受信割り込みはかかったので、オーバーシュート分が微妙に届いている感じなんだと思います。

そこでレベル変換です。
変換ICを乗せる程のことでもないので、トランジスタを使いました。

トランジスタでレベル変換

改めて信号を確認してみると、ちゃんと5V付近まで上がっています。

受信電圧が5Vにアップ

ソフトの動作はこちらです。

ソフトでも受信を確認

これでようやく準備が整い、あとはパソコンからコマンドを送って・・・と行きたいのですが、字句解析とかやったことがないので、どうなりますやら。

投げ技炸裂2018年05月07日

昨日の状態遷移図とソースコードには、よく見るとものすごい無駄があります。
それは、遷移先が違うだけで全く同じタイマー処理を3カ所でやっていることです。

そこで、今回はタイマー処理を一つにまとめてみます。
サブルーチン呼び出しなんかを使うとこれまでの苦労が台無しですので、オブジェクト指向風にいきます。

と言うわけで

まずは状態遷移図です。
外部からの指令はまず LED_CONTROL で受けますので、ここに _TIMER の処理を書いておけばアクティブなステートに関わりなくタイマー処理を行えます。

処理の投げ技

そして、タイムアップしたらおもむろに _TIMER を投げます。
で、アクティブなステートが受け取り可能なら受け取ってくれますし、そうでなければスルーされます。
最後にアクティブなコンディションに向かうわけですが、そこでも受け取ってもらえなければ投げっぱなしになってしまうわけですね。

今回の状態遷移図では _TIMER を受け取ることができるステートが3つあり、それ以外のステートとコンディションではスルーされます。

それではコーディングいきます

今回はパート処理の部分だけ掲載します。

;==========================================================
PART$ LED_CONTROL
;----------------------------------------------------------
blink_timer:    .blkw    1
blink_limit:    .blkw    1
;==========================================================
    ; コンディション定義
    CONDITION$_DEFINE C_LED_OFF      ;[消灯中]
    CONDITION$_DEFINE C_LED_ON       ;[点灯中]
    CONDITION$_DEFINE C_LED_BLINK    ;[点滅中]
    ; ステータス定義
    STATE$_DEFINE LED_STATIC_OFF     ; 消灯<[消灯中]
    STATE$_DEFINE LED_OFF_DELAY      ; 消灯遅延<[消灯中]
    STATE$_DEFINE LED_STATIC_ON      ; 点灯<[点灯中]
    STATE$_DEFINE LED_BLINK_OFF      ; 点滅(OFF)<[点滅中]
    STATE$_DEFINE LED_BLINK_ON       ; 点滅(ON)<[点滅中]

    ;=======================================
    ;[消灯中]
    ;=======================================
    CONDITION$ C_LED_OFF
    _ON
        TRANS LED_STATIC_ON      ; → 点灯<[点灯中]
    _UNIQUE
        TRANS LED_BLINK_ON       ; → 点滅(ON)<[点滅中]
    _END

    ;-----------------------------------
    ; 消灯<[消灯中]
    ;-----------------------------------
    STATE$ LED_STATIC_OFF, C_LED_OFF
        [ LED_PORT_BIT ] = OFF
    _END

    ;-----------------------------------
    ; 消灯遅延<[消灯中]
    ;-----------------------------------
    STATE$ LED_OFF_DELAY, C_LED_OFF
    _TIMER
        TRANS LED_STATIC_OFF    ; → 消灯<[消灯中]
    _END

    ;=======================================
    ;[点灯中]
    ;=======================================
    CONDITION$ C_LED_ON
    _OFF
        TRANS LED_STATIC_OFF    ; → 消灯<[消灯中]
    _UNIQUE
        TRANS LED_BLINK_OFF     ; → 点滅(OFF)<[点滅中]
    _END

    ;-----------------------------------
    ; 点灯<[点灯中]
    ;-----------------------------------
    STATE$ LED_STATIC_ON, C_LED_ON
        [ LED_PORT_BIT ] = ON
    _END

    ;=======================================
    ;[点滅中]
    ;=======================================
    CONDITION$ C_LED_BLINK
    _OFF
        NOP
        TRANS LED_OFF_DELAY        ; → 消灯遅延<[消灯中]
    _ON
        TRANS LED_STATIC_ON        ; → 点灯<[点灯中]
    _END

    ;-----------------------------------
    ; 点滅(OFF)<[点滅中]
    ;-----------------------------------
    STATE$ LED_BLINK_OFF, C_LED_BLINK
        [ LED_PORT_BIT ] = OFF
        [ blink_timer ].w = [ blink_limit ].w
    _TIMER
        TRANS LED_BLINK_ON    ; → 点滅(ON)<[点滅中]
    _END

    ;-----------------------------------
    ; 点滅(ON)<[点滅中]
    ;-----------------------------------
    STATE$ LED_BLINK_ON, C_LED_BLINK
        [ LED_PORT_BIT ] = ON
        [ blink_timer ].w = [ blink_limit ].w
    _TIMER
        TRANS LED_BLINK_OFF   ; → 点滅(OFF)<[点滅中]
    _END

;==========================================================
_TIMER
    if [ blink_timer ].w > [ PARAMETER ].w
        [ blink_timer ].w = [ blink_timer ].w - [ PARAMETER ].w
    else    ; タイムアップ
      [ blink_timer ].w = 0  ; blink_limit が割り切れないときの保険
        THROW    ; おもむろに _TIMER を投げる
    endif
_INIT
    [ blink_limit ].w = 1000
_SET
    [ blink_limit ].w = [ PARAMETER ].w
;----------------------------------------------------------
_END
;==========================================================

そう、投げるコマンドは "THROW" です。
このおかげでタイマー更新処理をまとめることができ、コードがスッキリします。

ただ、哀しいことにここまでやってもコードサイズはほとんど変わらなかったりします。
アセンブラなので元々効率がいいんですよね。

そんなこんなで

ようやくミドルウェア部分が出来上がってきましたが、まだ一つ重要な仕組みができていないのと、マイコン周辺機能処理の見直し、LCDなどの周辺機器ドライバやシリアル通信機能などなど、やることが山積みで、またしばらく更新が滞ってしまうかもしれません。

では、今回はこの辺で。

シェミュレータ炸裂2018年05月06日

またもや随分と放置してしまいました。
例によって思いっきりサボってみたり、水面下で色々あがいたりしていたんですが、その結果絞り出したのがこれです。

シェミュレータとは

ご覧の通り、マイコンを載せた変換基板をエミュレータに繋げた物ですが、この状態でデバッガーを走らせることができます。
つまり、シミュレータ的に使えるエミュレータということで、「シェミュレータ」というわけですね。

エミュレータからの給電だけで動きますし、シミュレータと違ってタイマーなどの周辺機能も普通に使えますので、ちょっとしたコードを確認したいときに便利です。

そんなわけで

ちょっとしたコードを書いてみました。

    .list OFF
    .include    CORE_BASE.inc
    .include    sfr_r832m.inc
    .include    R8C_3M.inc
    .list ON

PART$_DEFINE LED_CONTROL

$CPU
PORT_BIT$_PULL P42          ; Vref不使用、プルアップ
XIN$_USE                    ; XIN使用
CLOCK$_EXT CLOCK_F1         ; CPU クロック 外部クロック入力、分周なし
; システム周期(タイマRA)
TRA$_TIMER_MODE C_f8        ; タイマモード f8 - 0.4usec
TRA$_PRE        250         ; プリスケーラ      0.1msec
TRA$_REG         10         ; TRAレジスタ       1.0msec
TRA$_START                  ; 初期化時にスタートする
$END

RAM$
FLAG$_ system_flags
    FLAG$_ADD SYSTEM_CYCLE
    FLAG$_ADD LED_PORT_BIT
_$
DIVIDING$_ALLOC regular_10           ; 分周処理定義
stop_timer:        .blkw    1
_END

$SETUP  ; arduino風
    PROC$_CALL LED_CONTROL$_UNIQUE   ; いきなり点滅
    [ stop_timer ] = 230
$END

$LOOP   ; arduino風
    btstc SYSTEM_CYCLE    ; 1msec毎にONされる(フラグ→C、クリア)
    if C    ; 1msec周期
        DIVIDING$_ regular_10, 10    ; 10分周
            PROC$_CALL$_W LED_CONTROL$_TIMER, 10    ; 10msec を通知
        _   ; 処理分割
            if [ stop_timer ]
                [ stop_timer ] = -- [ stop_timer ]
                if Z
                    PROC$_CALL LED_CONTROL$_OFF     ; 点滅をやめる
                endif
            endif
        _$
    endif
$END

TRA$ 1
_ITP    ; 割り込み処理
    btsts SYSTEM_CYCLE   ; フラグをCにコピーした後セット
    if C    ; フラグがクリアされていない
        ; 処理遅れ
    endif
_END

;==========================================================
PART$ LED_CONTROL
;----------------------------------------------------------
blink_timer:    .blkw    1
blink_limit:    .blkw    1
;==========================================================
    ; コンディション定義
    CONDITION$_DEFINE C_LED_OFF      ;[消灯中]
    CONDITION$_DEFINE C_LED_ON       ;[点灯中]
    CONDITION$_DEFINE C_LED_BLINK    ;[点滅中]
    ; ステータス定義
    STATE$_DEFINE LED_STATIC_OFF     ; 消灯<[消灯中]
    STATE$_DEFINE LED_OFF_DELAY      ; 消灯遅延<[消灯中]
    STATE$_DEFINE LED_STATIC_ON      ; 点灯<[点灯中]
    STATE$_DEFINE LED_BLINK_OFF      ; 点滅(OFF)<[点滅中]
    STATE$_DEFINE LED_BLINK_ON       ; 点滅(ON)<[点滅中]

    ;=======================================
    ;[消灯中]
    ;=======================================
    CONDITION$ C_LED_OFF
    _ON
        TRANS LED_STATIC_ON          ; → 点灯<[点灯中]
    _UNIQUE
        TRANS LED_BLINK_ON           ; → 点滅(ON)<[点滅中]
    _END

    ;-----------------------------------
    ; 消灯<[消灯中]
    ;-----------------------------------
    STATE$ LED_STATIC_OFF, C_LED_OFF
        [ LED_PORT_BIT ] = OFF
    _END

    ;-----------------------------------
    ; 消灯遅延<[消灯中]
    ;-----------------------------------
    STATE$ LED_OFF_DELAY, C_LED_OFF
    _TIMER
        ; ジャンクション●的な動作
        if [ blink_timer ].w > [ PARAMETER ].w
            ; タイマー更新
            [ blink_timer ].w = [ blink_timer ].w - [ PARAMETER ].w
        else
        ; タイムアップ
            TRANS LED_STATIC_OFF    ; → 消灯<[消灯中]
        endif
    _END

    ;=======================================
    ;[点灯中]
    ;=======================================
    CONDITION$ C_LED_ON
    _OFF
        TRANS LED_STATIC_OFF        ; → 消灯<[消灯中]
    _UNIQUE
        TRANS LED_BLINK_OFF         ; → 点滅(OFF)<[点滅中]
    _END

    ;-----------------------------------
    ; 点灯<[点灯中]
    ;-----------------------------------
    STATE$ LED_STATIC_ON, C_LED_ON
        [ LED_PORT_BIT ] = ON
    _END

    ;=======================================
    ;[点滅中]
    ;=======================================
    CONDITION$ C_LED_BLINK
    _OFF
        NOP
        TRANS LED_OFF_DELAY         ; → 消灯遅延<[消灯中]
    _ON
        TRANS LED_STATIC_ON         ; → 点灯<[点灯中]
    _END

    ;-----------------------------------
    ; 点滅(OFF)<[点滅中]
    ;-----------------------------------
    STATE$ LED_BLINK_OFF, C_LED_BLINK
        [ LED_PORT_BIT ] = OFF
        [ blink_timer ].w = [ blink_limit ].w
    _TIMER
        if [ blink_timer ].w > [ PARAMETER ].w
            [ blink_timer ].w = [ blink_timer ].w - [ PARAMETER ].w
        else
            TRANS LED_BLINK_ON    ; → 点滅(ON)<[点滅中]
        endif
    _END

    ;-----------------------------------
    ; 点滅(ON)<[点滅中]
    ;-----------------------------------
    STATE$ LED_BLINK_ON, C_LED_BLINK
        [ LED_PORT_BIT ] = ON
        [ blink_timer ].w = [ blink_limit ].w
    _TIMER
        if [ blink_timer ].w > [ PARAMETER ].w
            [ blink_timer ].w = [ blink_timer ].w - [ PARAMETER ].w
        else
            TRANS LED_BLINK_OFF    ; → 点滅(OFF)<[点滅中]
        endif
    _END

;==========================================================
_INIT
    [ blink_limit ].w = 1000
_SET
    [ blink_limit ].w = [ PARAMETER ].w
;----------------------------------------------------------
_END
;==========================================================

$END$
    .end

前回の最終形態の状態遷移図からコーディングしたもので、なんと、これがソースファイルの全体像なのです。
つまり、このまま動いてしまうわけで、スタートアップ処理とかその他もろもろの面倒なことはマクロの中でやってしまっているわけです。

エミュレータでの実行風景

ちなみに、このコードのプログラムセクションは988バイトです。
ほぼ定型的な処理をあちこちで展開しているせいで大きくなってしまっていますが、アセンブラの癖を悪用している都合上、色々難しいところではあります。

一応、コードを far 領域にも置けるように設計していますが、ピン数の少ない品種ではそこまでの領域がないのがつらいですね。

ぼやいていてもしかたないので

状態遷移図を再掲します。

Lチカ第四形態・・・

外部からの指令によってLEDを点灯/消灯/点滅させるわけですが、[点滅]から[消灯]に移行するときにLEDが唐突に消えないよう、遅延タイマーを入れているのが特徴です。

今回のコードだと、点滅は2秒周期、つまり1秒点いて1秒消えるの繰り返しになっていて、点滅を終えるときも1秒点いてから消えるようにしています。

テストコードでは、スタート直後に点滅指令を出し、2.3秒後に消灯指令を出しています。
遅延処理がなければ2回目は0.3秒で消灯してしまいますが、遅延処理のおかげでちゃんと1秒後に消灯します。

実を言えば

前回の更新から一年近く経ってしまったのは、状態遷移処理の実装方法を煮詰めるだけでなく、分周処理の仕様で悩んでいたことが大きかったりします。
以前は別オブジェクト的な実装でしたが、最終的に今回のインライン的な扱いに決めました。

以下のような感じで"_"で分割して処理を書くことで、1回通る毎に1つだけ処理を実行し、次回には次の処理を実行します。

DIVIDING$_ regular_10, 10    ; 10分周('_'で分割された処理を切替実行)
    処理1
_
    処理2
_
    処理3
  ・
  ・
  ・  
_$

ここでは分割数を10にしているので10個までの処理を書けますが、10個に満たない分は何もせずに通過します。
つまり、1msec毎に通過する部分にこの処理を書いておけば10msecのタイマーを10個得たことになるわけですね。

メインループの負荷分散のための優れた方法だと自画自賛しておきます。

そんなわけで

今度こそちゃんとした物を完成させようと何度目になるか分からない誓いを立てたりしていますが、どうなることやら。