最近、ArduinoとOrangePi-ZeroをUSB接続したシステムを構築してみた。ArduinoはOrangePi側からのUSB給電により動作しOrangePiとの通信もそのUSB経由で行える。タイミング的にシビアな処理はArduino、WebやMail,DBなどはOrangePiにお任せという感じで簡単に連携処理することが可能だ。USBケーブル一本で構成できるシンプルさもとても良いし、この組み合わせはお互いの苦手をを補完しあい、ある意味、最強の組み合わせとも言えそうだ。まぁ、OrangePiじゃなくてRaspberryPiでも同じことが可能なわけで扱いやすさ(情報量の多さ)という点ではRaspberryPi、価格や性能を重視するならOrangePiという選択になったりするのだが、それはともかく、どっち側で実行しても問題ない処理も結構あったりする。例えば、I2C/SPI/Serialなどはどちらでも実行できる。が、ArduinoとOrangePiで別別に全く違うコードを書かなければいけない点が煩わしかったりも...
そこでググッてみたら、RaspberryPi上でArduinoのコード・スタイルで実行できるPIDUINOライブラリなるものを見つけてしまった。基本機能はほぼ網羅されているようだ。
んで、OrangePi用の同様な物がないか探してみた...が、なんもない...OrangePiのPWMってどうやって使うんだ?みたいな疑問点が多々あったりもするのでそれらのモヤモヤ感を一掃すべく自分で調べて作ってみた。GPIOやPWMはレジスタ直接操作で実現しているので実行にはroot権限が必要となることに注意しよう。
対応CPUはAllwinner-Hシリーズ(H2+,H3,H5,H6)とAシリーズ(A10, A20, A23, A64, A83T)。
コンパイル・オプション指定で切り替え可能。
-DALLWINNER_H=3 or 5 or 6 (H2+は3を指定)
or
-DALLWINNER_A=10 or 20 or 23 or 64 or 83
可能な限り互換性重視としたが、下記のようにいくつか拡張した点がある。
1.GPIOのポート・データ型をuint8_tからuint16_t に変更。(ポート番号が256を超えるため)
2.I2C(Wire)のアドレス・データ型をuint8_tからuint16_tへ変更。(10bitアドレス対応のため)
3.※Hardware-PWM(1Hz-24MHz)とSoftware-PWM(1Hz-1KHz)に対応。分解能は16bit。ポート番号によりどちらかが選択される。
4.1-Wire(W1GPIO)対応。温度センサー(DS18B20)等に対応。
5.USB(SerialUSB)対応。
6.EthernetClient/Server/Udp(W5100)互換仕様のネットワーク対応。IPv4/IPv6デュアルスタックに対応。
7.シグナルによるプロセス終了をサポート。(systemctl等での自動起動対応のため)
8.コマンドライン・オプション取得可能。(おまけ)
※OrangePi-Zeroの場合、ドキュメントに記載されているPWM1(PA06)は存在しない。Hardware-PWMは、RJ45コネクタの横の3Pinコネクタの真ん中のピン(PA05)に出力される。ttyS0のRXと同じピンを共有しているがttyS0をディセーブルにする必要はない。
非互換部分は次の通り。
1.I2C(Wire)のrepeated-startには対応しているがLinuxの仕様によりsendStop=false時の通信は不可能であるため、その後のsendStop=true時にまとめて処理される。そのためrequestFrom(sendStop = false)時の戻り値は常に0を返す。
2.analogReadは非対応。関数は存在するが何もせず常に0を返す。
3.interrupts/noInterruptsは非対応。関数は存在するが何もしない。
[新規作成]
Allwinner.cpp
Allwinner.h
BlockingIO.h
PWMBase.h
PWMCTRL.cpp
PWMCTRL.h
ThreadBase.cpp
ThreadBase.h
ThreadInterrupt.h
ThreadPWM.h
ThreadTimer.h
W1GPIO.cpp
W1GPIO.h
sysfs_gpio.h
xtoa.c (WString移殖用)
xtoa.h (WString移殖用)
[Arduinoコア・ソース全面書き換え(ファイル名は同じ)]
Arduino.h
EthernetClient.h
EthernetServer.h
EthernetUdp.h
HardwareSerial.cpp
HardwareSerial.h
SPI.cpp
SPI.h
Tone.cpp
WInterrupts.cpp
Wire.cpp
Wire.h
main.cpp
pins_arduino.h
wiring.c
wiring_analog.cpp
wiring_digital.cpp
[Arduinoコア・ソース流用]
binary.h
Client.h
IPAddress.cpp
IPAddress.h
Print.cpp
Print.h
Printable.h
Server.h
Stream.cpp
Stream.h
Udp.h
WCharacter.h
WMath.cpp
WString.cpp (includeのみ変更)
WString.h (includeのみ変更)
wiring_pulse.c (暫定的に書き換え)
wiring_shift.c (そのままでも良かったんだけど、なんとなく書き換えてしまった)
今回はコード量が多くて全てを掲載できないためZIP公開とします。
[Opduino Source Code Download]
※ソース・コード・ライセンスはArduinoと同じくGPL2.1です。
[2020-02-10]
Allwinner.hのレジスタ構造体の各メンバーにvolatile修飾子を追加するのを忘れてた。(-_-;
[2020-01-29]
yield()及びlinux用の独自拡張であるinterrupted()の呼び出しにおいてserialEventが実行されるよう修正。終了対応にはinterrupted()の呼び出しが必須となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void loop(void) { while (interrupted()) { ... } or while (1) { ... yield(); } } |
[2020-01-28]
ちょっとした最適化を行ったのとMakefileがショボかったので下記サイトで紹介されていた内容を参考に変更。ヘッダーやソースの抽出や依存関係が自動化されるため扱いが簡単。その代わりにコンパイルしたくないソースをプロジェクト構成に含ませてはいけない。
C/C++中規模プロジェクトのための超シンプルなMakefile
アプリケーション用の新規プロジェクトを作る場合は、コピーしたディレクトリ構成とMakefileのTARGETを書き換えればOK。別の場所のopduinoを参照する場合はOPDUINO_DIRの書き換えも必要。
[2020-01-24]
serialEventに対応してみた。が、Arduino的には、loop()から戻った時に総当たり戦でポーリング&コールバックする仕様なので効率よくないし互換性以外のメリットが感じられない。serialEventが使えなくても全く困ることもないので積極的に使うべき機能ではないというか実装すべきでない機能を実装してしまったという後悔のようなものを感じてしまった。
[2020-01-22]
HardwareSerial.end()を実行すると誤ってカレントコンソール設定を変更してしまうというバグを発見し修正。
[2020-01-20]
SerialUSB(“/dev/ttyACM*”)をSerialUSB(“/dev/ttyUSB*”)へ変更しSerialACM(“/dev/ttyACM*”)を新規追加。
[2020-01-17]
Allwinner-A83T対応とPWM(1)がバグってたので修正。
[2020-01-16]
Allwinner-A64対応のついでにA23, A20, A10も対応してみた。当然ながら動作は未確認...
[2020-01-15]
やろうと思っててすっかり忘れていたI2Cバス・クロックをWire.setClock()で変更できるように改良。最大2.4MHzまで。ついでにAllwinner-A64対応もしてみたが物がないので試せない...
[コンパイル]
Makefile先頭のTARGET=にArduinoのメイン・プログラム名(ファイル拡張子は.inoではなく.cppで)に書き換えてからmakeを実行。メイン・プログラムは、Arduino.hを必須でインクルードしその他のヘッダーは必要に応じてインクルードする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// // OranegPi-Zeroの電源LED(緑)を点滅させるプログラム // // ※CTRL-Cで終了。仕組みはmain.cppを見るべし。 // #include "Arduino.h" void cleanup() { // 終了要因となったシグナル番号を表示 printf("terminated(%d)\n", interrupted()); } void setup(void) { // 終了時の後始末関数を登録する atexit(cleanup); } void loop(void) { // 電源LED(緑)は、起動時にOUTPUT設定済み digitalWrite(ORANGEPI_ZERO_GREEN_PWR, HIGH); delay(1000); digitalWrite(ORANGEPI_ZERO_GREEN_PWR, LOW); delay(1000); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* * DS18B20 Sample Program * * System Setting: * [/boot/armbianEnv.txt] * overlay = ... w1-gpio ... * param_w1_pin=PA10 * param_w1_pin_int_pullup=1 */ #include "Arduino.h" #include "W1GPIO.h" void setup() { } void loop() { int count = W1.search(W1GPIO_THERM_DS18B20); for (int i = 0; i < count; ++i) { int32_t temp; const char *slave = W1.slave(i); if (W1.therm(slave, &temp) == 0) printf("[%s] = %d\n", slave, temp); else printf("[%s] = (error)\n", slave); } delay(1000); } |
ちなみに十分な確認作業など行ってないためバグは取り切れていないと思われるが、いつものように基本的に何かあっても対応できないので利用者自身の努力と責任において使ってほしい。
以上、皆様の参考にでもなれば幸いです。2020年元旦。って、少し過ぎてますけど(笑)