2020年7月18日土曜日

GPIO操作をcで

取りこぼし発覚からpython以外でGPIOを直接操作したくてライブラリやパッケージを検索してたのですが、思っていたよりは乱立していなかったものの、メジャーっぽいので4つぐらい存在していました。ほとんどpythonから利用できるもので、cから使えそうな感じでした。

すでに意識してなかったけど、pythonでも使ってるし、raspberry piの公式ページで見かけていたRPI.GPIOがいいかな?とcのソースを起こし始めたのですが…あれ?cのサンプルになりそうなソースが検索で引っかからない…
もう何でもいいかと投げやりで、最悪デバイスをファイルから出力すればいいか…とかあきらめかけていたところ、もう少しハードよりの低レベルライブラリとしてhttp://www.airspayce.com/mikem/bcm2835/bcm2835-1.64
というものがありました。
良くも悪くもハード寄りなので定数や変数、関数名が分かりづらいですが、これで直接操作できそうです。

扱い方の基本はpythonでGPIOを操作するときと変わらないので手元のスクリプトを基にデータを受信するところまで作って、受信内容を表示させてみたいと思います。

まずは…コメントの日本語でコンパイルがおかしいことにwクソッw
gccのコンパイルでソース記述のコードページと、生成したオブジェクトが出力するコードページを指定するようにしてコンパイルが通るようになりました。
pi@rasp3b:~ $ gcc dht22.c -o dht22 -l rt -l bcm2835  -finput-charset=UTF-8 -fexec-charset=UTF-8

さっそく実行させてみましす。

pi@rasp3b:~ $ ./dht22
sampling_start_lev : 0
length : 81
22 26 54 27 56 23 54 27 53 27 53 27 54 73 54 25 68 74 54 73 54 26 53 27 54 26 54 73 55 26 54 25 67 26 54 27 53 27 53 27 53 26 54 26 54 27 53 25 68 74 54 73 54 74 53 74 54 26 54 26 54 74 54 25 65 73 54 26 54 74 54 73 54 74 54 26 54 26 52 27 47

受信はμs単位ですが、何となくデータは取りこぼしも発生してなさそうで、連続で実行しても見た感じは大丈夫そうです。

ただ、気になるのはデータ送信開始の信号の時間間隔が短すぎるところと、pythonではこち側のHIGHレベルの信号を受信できることがあったのですが、今回作成したものは開始がLOWから始まることしかないのが気になり、そうしていると受信データが79個とかいきなり取りこぼしてそうな感じに。

実際に信号をサンプリングしているループがどの程度の速度で回っているのか様子を見ることから始めました。

sampling_start_lev : 0
length : 81
37 27 53 26 55 25 54 26 55 26 54 26 54 73 54 25 67 74 54 74 54 26 54 26 54 26 54 74 53 26 54 73 67 26 55 26 53 27 54 26 53 27 54 26 54 26 54 25 68 73 54 73 55 73 54 74 53 27 53 27 54 26 54 25 64 74 54 26 54 73 54 74 54 26 54 74 53 74 54 73 46
82 58 119 58 120 57 120 58 120 58 119 58 120 163 119 56 149 164 119 164 119 58 119 58 120 58 118 164 119 58 120 161 149 58 120 58 119 58 120 58 119 58 120 57 120 58 120 55 150 163 120 163 113 153 119 164 119 58 119 58 120 58 119 56 143 163 120 58 120 163 120 163 120 58 120 163 119 164 120 160 104

2個目の数字の塊がループ中カウントを行った回数です。
37μsで82回、回ってるので処理落ちしている様子はありません。
何度か実行していると、ループのカウンタが200を越える数字が見えるようになりました。
66 101 205 100 209 101 214 101 204 101 206 97 205 279 205 96 256 277 208 98 205 280 208 277 209 280 207 277 207 278 208 98 257 102 211 90 192 94 176 88 185 80 179 89 175 85 179 84 221 262 209 281 211 281 208 277 207 101 201 100 207 277 207 91 247 278 210 101 203 289 203 283 203 101 209 94 208 277 207 96 175

もしかしてCPUクロックが変動するので、変なタイミングで処理落ちするのかな?と途方に暮れながらソースを眺めて寝落ちした後、少し落ち着いて考えてみると、sleepさせて開始させているけど、実際の時間はどのぐらいなのだろう?と実際に計測させてみました。

sampling_start_lev : 0
sampling_end_lev   : 1
length : 81
37 26 54 25 54 27 54 25 55 26 54 25 54 74 54 25 68 73 54 26 54 74 54 74 53 74 54 74 54 26 53 73 67 27 54 26 53 27 54 26 53 27 54 26 54 26 54 25 67 74 54 73 54 74 54 74 54 26 54 26 54 73 54 73 64 74 60 20 54 73 54 74 54 26 54 26 54 74 53 26 47
70 53 111 53 111 54 111 52 102 48 110 53 111 152 110 52 139 151 111 54 111 151 111 152 110 152 111 151 111 54 110 150 138 54 111 53 111 54 111 54 110 54 111 53 111 54 111 51 138 152 111 151 111 152 111 151 111 53 111 53 111 152 111 149 132 152 40 38 107 146 107 146 107 51 107 52 106 146 107 50 93
start : 1595040856 964706
p1 : 613
p2 : 50726
p3 : 70816
p4 : 71017
p5 : 71020
p6 : 1075103
p7 : 1075978

すべてμs単位で開始からの経過時間を表示させていますが、p1が最初のHIGH信号を送信する直前で、p2が信号を送信したあとusleep(50000);を行った後なのですが、50113μs経過しています。最初のHIGHの送信は実際は必要ないので経過時間はあまり関係ないのですが、それでもセットアップして信号をHIGHにするだけで100μs程度かかっているのは予想以上にもたついています。
次のp3はLOW信号を送信してusleep(20000);させた後なのですが、ここでも経過時間が20090μsとなっていて90μs程度もかかっています。
時間が不安定→sleepが原因
というわけで、ちょっとCPUに優しくなくなりますが、時間ループを作って様子を見ることに。

start : 1595041084 066267
p1 : 631
p2 : 50639
p3 : 70639
p4 : 70838
p5 : 70840
p6 : 1074781
p7 : 1075044

同じく8μs、1μs以下と、まったく高速化処理をしていない状態でかなりの精度で処理が行われるようになりました。
p3からp4はピンを入力に切り替えた後に信号をプルアップさせてHIGH状態に切り替えています。(この辺の手順が前々から気になっているんですけど、本来はHIGH状態にしてからピンの入出力を切り替えるべきだと思うんですがこの辺の考え方が良くわからない)199μsと、それなりに時間がかかっています。
p4からp5はループさせる前の初期設定で、2μs。p5からp6がループ中の時間で最後は出力系の部分で必要な処理ではありません。
時間的にタイトなところはp3からp4の部分で信号線がHIGHになった後、DHT22のデータシート(Table 6: Single bus signal characteristics)では
Tgo(ホスト側のHIGH信号)とTrel(センサー側のLOW信号)の最低時間が20μs、75μsとなっていて、最低でもHIGH信号をに変えた後、95μs以内にで受信開始できないと最初のデータを取りこぼすことが出てくるというところ。
なのでもう少し時間を見てみましょう。

start : 1595042362 253428
p1 : 607
p2 : 50615
p3 : 70616
p4 : 70618
p5 : 70892
p6 : 70894
p7 : 1074949
p8 : 1074949

増やした部分はp4のところでbcm2835_gpio_fselの処理が2μsで、信号をHIGHにしているbcm2835_gpio_set_pudが274μsも…
p3 : 70585
p4 : 70586
p5 : 70746
もう一回実行してみると今度は160μsとかなり処理時間にばらつきがあるようです。実際にどのぐらいのタイミングで切り替わっているのか不明ですが、結構こわい感じです。内部で後処理ではなく、前処理に時間がかかっていることになっていることを祈りましょう…

さて、この状態で受信データはどんな感じでしょうかねぇ…

sampling_start_lev : 1
sampling_end_lev   : 1
length : 82
21 54 26 54 26 54 26 54 26 54 26 54 26 54 74 53 26 67 74 54 26 54 73 54 74 54 74 53 74 54 26 54 73 67 26 54 26 54 26 54 26 54 26 54 26 54 26 54 25 68 73 54 74 54 74 53 74 54 26 54 74 54 26 54 72 65 73 54 26 54 74 54 74 53 27 53 74 54 26 54 25 47

開始がHIGHから拾えているのと、データの個数が82個になっていていい感じになってそうです。HIGH信号の部分を取って8bitで区切ってみると
21
26 26 26 26 26 26 74 26
74 26 73 74 74 74 26 73
26 26 26 26 26 26 26 25
73 74 74 74 26 74 26 72
73 26 74 74 27 74 26 25

結果からするとセンサー側の開始信号のLOWを見事に取りこぼしています。
何度か実行していると取得しているデータも79ことか76個に減っているときがあって、なんか嫌な予感です。
実際にプルアップしている処理時間は190μsと245μsと、データシート上のセンサー側からの信号を標準タイミングで計算してみると…80, 80, 50μsで データ信号のLOW状態までとなっていて、190でギリギリデータの開始ビットが見え、240μsだとデータの信号のHIGH信号からしか見えず、ビットの判定ができなくなります。
と、なると…pythonでいままでデータが上手く拾えていなかったパターンもこれじゃね?とか感じますが、cだとサンプリングが数μs単位で綺麗に取れているのでこのまま続けましょうw

さて、こうなると、pullup処理自体がダメなんじゃないかとなるので、データシートの手順に変更してみます。
sampling_start_lev : 1
sampling_end_lev   : 1
length : 82
20 78 82 54 26 54 26 54 27 53 26 54 26 54 26 54 74 54 25 67 74 54 26 54 74 54 73 54 74 54 26 54 26 54 72 68 26 54 26 54 26 54 26 54 26 54 26 54 26 160 60 54 74 54 73 54 74 54 26 54 74 54 73 54 25 64 74 54 26 54 74 54 73 54 26 54 26 54 26 54 73 47
40 162 170 112 55 112 54 112 55 112 54 112 54 112 54 112 153 112 53 140 153 112 54 112 154 112 150 91 149 113 54 112 54 112 151 140 55 112 54 112 54 113 54 112 54 112 55 111 55 83 119 106 146 107 146 108 147 107 53 107 147 108 146 106 50 127 146 107 52 108 147 107 147 108 48 173 94 197 101 208 275 182
start : 1595044413 146081
p1 : 614
p2 : 50624
p3 : 70625
p4 : 70626
p5 : 70626
p6 : 70628
p7 : 1074862
p8 : 1074862
最悪データが送られてこなくなるとも思われましたが、何の問題もなくデータの受信ができました。
p3,p4の間のbcm2835_gpio_set_pud部分はコメントアウトして、p3の手前で信号をHIGHにしてから受信状態にしています。
こうしてしまうと、信号をプルアップしなくなってしまうのですが、ハード的にセンサーの基盤にデータ信号とVCCの間に抵抗が入っていてプルアップされた状態なのでいいんじゃないのかな…と思います。
データもこちら側のHIGH信号から見えているようで、センサー側からの開始信号であるLOW(Trel 80μs)→HIGH(Treh 80μs)信号も取れているようなので時間的には十分問題なさそうです。
何度か実行してみると、データの個数は84個で開始もHIGHから取れているので問題なさそうです。

時間的な制約は問題なくなったので、あとは実際にデータビットを組み立てて数値を組み立てることになりますが、cなんてほんと何年ぶり?何十年ぶりだろうw

0 件のコメント:

コメントを投稿