Changes between Initial Version and Version 1 of epics/Aruduino_UNO_Q


Ignore:
Timestamp:
09/15/26 15:42:10 (43 hours ago)
Author:
Tetsuya Michikawa
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • epics/Aruduino_UNO_Q

    v1 v1  
     1= Arduino UNO Q =
     2
     3ArduinoがQualcommに買収されて始めに作ったQualcomm純正のArduinoボード。
     4
     5https://www.qualcomm.com/developer/hardware/arduino-uno-q
     6
     7Linux(Debian)が始めから動作している。
     82GB/16GB emmc版と4GB/32GB emmc版があるが、今回は4GB/32GB emmc版を使う。
     9
     10== 初期セットアップ ==
     11
     12各種ソフトウェアはここからダウンロードする。
     13https://www.arduino.cc/en/software/
     14
     15参考資料
     16
     17* https://www.denshi.club/cookbook/arduino/arduinounoq/arduino-uno-qwo-riyou.html
     18* https://qiita.com/Corgeek/items/3821b23ad4deaad372c0
     19
     20
     21== partition マージ ==
     22
     23Arduino UNO Qのディスクはemmcしかないが、{{{/}}}パーティションが小さく、{{{/home/arduino}}}にかなりの容量を割り当てているという変な構成になっている。
     24試しにEPICS baseをインストールすると容量の95%まで使ってしまったので、{{{/home/arduino}}}を{{{/}}}にマージする。
     25
     26[https://note.com/bsoft/n/n0ea0237d40c2 Arduino UNO Q:ルートパーティションを拡張する]
     27[attachment:merge_arduino_partitions.sh パーティションをマージするスクリプト]
     28
     29{{{
     30/dev/mmcblk0p68  28954256 7736576  19950792  28% /
     31}}}
     32
     33となる。
     34
     35が、マージに失敗してLinuxが起動しなくなったのでemmcを初期化をしてみると、{{{/home/arduino}}}は{{{8GB}}}になっていたので、マージしなくてもよかったかも。ユーザーパーティションがデカすぎて不評だったのかも。
     36
     37{{{
     38arduino@bella:~$ df
     39Filesystem      1K-blocks    Used Available Use% Mounted on
     40/dev/mmcblk0p68  20541220 6378032  13238176  33% /
     41/dev/mmcblk0p69   8402388  639224   7400120   8% /home/arduino
     42/dev/mmcblk0p67    499004  138136    360868  28% /boot/efi
     43}}}
     44
     45[https://www.switch-science.com/blogs/magazine/uno-q%E3%82%92%E8%B2%B7%E3%81%A3%E3%81%9F%E3%82%89%E6%9C%80%E5%88%9D%E3%81%AB%E8%AA%AD%E3%82%93%E3%81%A7-%E5%9B%B0%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96%E6%96%B9%E6%B3%95?srsltid=AfmBOopT0gzUa769-t3Lr85Sm6_dXYdkz1dhIm5jCWS-BQzHnSpZqtyS Uno Qを買ったら最初に覚えるべき、最後の手段《初期化方法》]
     46
     47= setup =
     48
     49初期設定は、[​https://www.arduino.cc/en/software/ Arduino App Lab]を使って行う。
     50初期ユーザーは{{{arduino}}}、パスワードはセットアップ時に設定する。
     51
     52== HW ==
     53
     54Arduino UNO Qは、通常使用する外部IFはUSB-Cしかないので、開発時には[https://www.switch-science.com/products/11134?_pos=15&_sid=8c22addc9&_ss=r USB給電ポート/Ethernet付USB-C HUB]がほぼ必須になる。
     55
     56今回は、手持ちのUSB給電ポート付USB-C HUBとUSB-Ether変換ケーブルを接続して使用。
     57ちなみにsshdは始めから動作しているようなので、DHCP環境下でUSB-Ether変換ケーブルを接続すると外部から接続が可能になる。(WiFi環境下ならUSB-Etherは要らない)
     58
     59== ユーザー作成 ==
     60
     61{{{
     62root@marya:~# useradd -m pfoper
     63root@marya:~# usermod -aG sudo pfoper
     64root@marya:~# usermod -aG dialout pfoper
     65}}}
     66
     67== パッケージ ==
     68
     69{{{
     70root@marya:~# apt -y install build-essential emacs procserv telnet libreadline-dev re2c libreadline-dev libtirpc-dev
     71}}}
     72
     73== epics ==
     74
     75epicsは最新版の 7.0.10。コンパイルは特に問題なくいつも通りで問題なし。arch は linux-aarch64。
     76インストール先は{{{/opt/epics/R7.0.10}}}。
     77インストールしたモジュールは、
     78
     79* snc_seq 2-2-9 [https://github.com/epics-modules/sequencer/archive/refs/tags/R2-2-9.tar.gz]
     80* asyn 4-46 [https://github.com/epics-modules/asyn/archive/refs/tags/R4-46.tar.gz]
     81* streamdevice 2.8.26 [https://github.com/paulscherrerinstitute/StreamDevice/archive/refs/tags/2.8.26.tar.gz]
     82
     83環境変数は、{{{/opt/epics/R7.0.10/etc/setenv.sh}}}
     84
     85= arduino =
     86
     87内蔵マイコン(STM32)用の開発環境は{{{arduino-cli}}}を使うのが良いそうなので、パッケージをrootでインストール。
     88
     89{{{
     90root@marya:~# curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
     91Installing in /usr/local/bin
     92ARCH=ARM64
     93OS=Linux
     94Using curl as download tool
     95Downloading https://downloads.arduino.cc/arduino-cli/arduino-cli_1.5.1_Linux_ARM64.tar.gz
     96arduino-cli  Version: 1.5.1 Commit: 01f3d4f2b Date: 2026-06-05T10:22:11Z installed successfully in /usr/local/bin
     97}}}
     98
     99各ユーザーで設定ファイルを初期化
     100
     101{{{
     102pfoper@marya:~$ arduino-cli config init
     103Config file written to: /home/pfoper/.arduino15/arduino-cli.yaml
     104}}}
     105
     106パッケージインデックスを更新
     107
     108{{{
     109pfoper@marya:~$ arduino-cli core update-index
     110Downloading index: library_index.tar.bz2 downloaded
     111Downloading index: package_index.tar.bz2 downloaded
     112Downloading missing tool builtin:mdns-discovery@1.1.0...
     113builtin:mdns-discovery@1.1.0 downloaded
     114Installing builtin:mdns-discovery@1.1.0...
     115Skipping tool configuration....
     116builtin:mdns-discovery@1.1.0 installed
     117Downloading missing tool builtin:serial-discovery@1.5.2...
     118builtin:serial-discovery@1.5.2 downloaded
     119Installing builtin:serial-discovery@1.5.2...
     120Skipping tool configuration....
     121builtin:serial-discovery@1.5.2 installed
     122Downloading missing tool builtin:serial-monitor@0.15.0...
     123builtin:serial-monitor@0.15.0 downloaded
     124Installing builtin:serial-monitor@0.15.0...
     125Skipping tool configuration....
     126builtin:serial-monitor@0.15.0 installed
     127Downloading missing tool builtin:ctags@5.8-arduino11...
     128builtin:ctags@5.8-arduino11 downloaded
     129Installing builtin:ctags@5.8-arduino11...
     130Skipping tool configuration....
     131builtin:ctags@5.8-arduino11 installed
     132Downloading missing tool builtin:dfu-discovery@0.1.2...
     133builtin:dfu-discovery@0.1.2 downloaded
     134Installing builtin:dfu-discovery@0.1.2...
     135Skipping tool configuration....
     136builtin:dfu-discovery@0.1.2 installed
     137Downloading index: package_index.tar.bz2 downloaded
     138}}}
     139
     140ボードコア(UNO Q用)をインストール
     141インストールには多少時間がかかる。
     142
     143{{{
     144pfoper@marya:~$ arduino-cli core install arduino:zephyr
     145    :
     146}}}
     147
     148== 動作確認 ==
     149
     150まずは、普通のLチカをやってみたが動作しない。
     151色々と調べてみたが、どうやらarduino-cliを使って直接STM32をいじってはいけないらしい。
     152
     153https://prahari.net/blog/arduino-uno-q-is-not-a-regular-arduino-what-i-learned-the-hard-way
     154
     155また、MPUとの通信はシリアル経由ではなく、RPC経由で実施する必要があることも判明。(AIに聞いたときはシリアル経由でやるように言われてたけど、他のArduinoでの使い方とごちゃ混ぜになっているようで、実際には使えない模様(;;))
     156
     157MPUのプログラムは色々と複雑(面倒)なので、Arduino App Lab経由で実装するのが一番正しそう。
     158App Labで開発後、EPICSとの通信部をRPCで実装する方針でやってみることとする。
     159
     160= RPC =
     161
     162[https://docs.arduino.cc/tutorials/uno-q/routerbridge-multilanguage/ Working with Arduino Router RPC]
     163[https://qiita.com/tbkMori/items/a94cd877c812dc11ff22 Arduino UNO QのMCU⇄Linux間で値をやり取りする]
     164
     165とりあえず、[https://docs.arduino.cc/tutorials/uno-q/routerbridge-multilanguage/ Working with Arduino Router RPC]のソースコードを使って、Lチカをしてみる。
     166
     167== MPU側 ==
     168
     169Arduino App Labを使って実装してみる。
     170App Labは起動時に接続可能なArduino UNO Qを探しに行くみたいで、起動画面に接続先リストが表示されるので、目的のUNO Qに接続する。
     171接続後はエージェントモードで動作するので、ソースコードもUNO Qに保存される。
     172
     173blinkLEDというAppを作成して、以下の様なsketch.inoを作成。
     174
     175{{{
     176#include "Arduino_RouterBridge.h"
     177
     178void setup() {
     179    pinMode(LED_BUILTIN, OUTPUT);
     180   
     181    Bridge.begin();
     182    Bridge.provide("set_led_state", set_led_state);
     183    Bridge.provide("get_led_state", get_led_state);
     184}
     185
     186void loop() {}
     187
     188void set_led_state(bool state) {
     189    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
     190}
     191
     192}}}
     193
     194書き終わったら、App Lab右上の{{{Run}}}ボタンを押すことで、コンパイルと実行が行われる。
     195
     196== Linux側(C++) ==
     197
     198まずは必要なパッケージをインストール
     199
     200{{{
     201apt install libmsgpack-cxx-dev
     202}}}
     203
     204Linux側のソースコード
     205{{{arduino_bridge.hpp}}}は[https://docs.arduino.cc/tutorials/uno-q/routerbridge-multilanguage/ Working with Arduino Router RPC]にあるものをコピペして使う。
     206最近のC++難しい、、、
     207
     208{{{
     209#include "arduino_bridge.hpp"
     210#include <iostream>
     211#include <thread>
     212#include <chrono>
     213
     214int main() {
     215    ArduinoBridge bridge;
     216
     217    std::cout << "Connecting to arduino-router..." << std::endl;
     218    if (!bridge.connect()) {
     219        std::cerr << "Failed to connect!" << std::endl;
     220        return 1;
     221    }
     222
     223    std::cout << "Connected! Blinking LED..." << std::endl;
     224
     225    bool state = false;
     226    for (int i = 0; i < 10; i++) {
     227        state = !state;
     228
     229        if (bridge.notify("set_led_state", state)) {
     230            std::cout << "SET LED: " << (state ? "ON" : "OFF") << std::endl;
     231        }
     232
     233        std::this_thread::sleep_for(std::chrono::milliseconds(500));
     234    }
     235
     236    bridge.disconnect();
     237    return 0;
     238}
     239}}}
     240
     241コンパイル&実行
     242
     243{{{
     244$ g++ -std=c++17 blinkLED.cpp -pthread -o blinkLED
     245$ ./blinkLED
     246Connecting to arduino-router...
     247Connected! Blinking LED...
     248SET LED: ON
     249SET LED: OFF
     250SET LED: ON
     251SET LED: OFF
     252SET LED: ON
     253SET LED: OFF
     254SET LED: ON
     255SET LED: OFF
     256SET LED: ON
     257SET LED: OFF
     258}}}
     259
     260動くことは確認できた。
     261
     262== Linux側(python) ==
     263
     264ついでにpython版も。
     265
     266こちらもパッケージをインストール
     267{{{
     268# pip3 install msgpack --break-system-packages
     269}}}
     270
     271ソースコード
     272
     273{{{
     274from arduino_bridge import ArduinoBridge
     275import time
     276
     277def main():
     278    bridge = ArduinoBridge()
     279
     280    if not bridge.connect():
     281        print("Failed to connect")
     282        return
     283
     284    print("Connected!")
     285
     286    for i in range(10):
     287        state = i % 2 == 0
     288        bridge.notify("set_led_state", state)
     289        print(f"LED: {'ON' if state else 'OFF'}")
     290        time.sleep(0.5)
     291
     292    bridge.disconnect()
     293
     294if __name__ == "__main__":
     295    main()
     296
     297}}}
     298
     299実行
     300
     301{{{
     302$ python3 ./blinkLED.py
     303Connected!
     304LED: ON
     305LED: OFF
     306LED: ON
     307LED: OFF
     308LED: ON
     309LED: OFF
     310LED: ON
     311LED: OFF
     312LED: ON
     313LED: OFF
     314}}}
     315
     316こちらも動作する。
     317
     318== 修正版C++用RPC ヘッダ ==
     319
     320RPCでArduinoからデータを取ろうとするとコンパイルエラーになったので、AIで修正したヘッダファイルを追加しておく。
     321
     322[attachment:arduino_bridge.hpp]
     323
     324= EPICS IOC =
     325
     326本番のEPICS IOCを作成する。
     327RPCを使う場合、asynPortDriverかsub(or aSub) record、又はpythonを使うかになるが、今回はasynPortDriverでやってみる。
     328
     329== ADC 予備実験 ==
     330IOCを作る前に、通常のプログラムでデータが読めるか実験。
     331
     332* MPU(arduino)側
     333
     334 何も考えずに、データ要求が来たら生のADC値の返す。
     335
     336{{{
     337#include "Arduino_RouterBridge.h"
     338
     339#define MAX_CH  6
     340
     341int adcPins[MAX_CH] = {A0, A1, A2, A3, A4, A5};
     342
     343void setup() {
     344    // ADCは14bit
     345    analogReadResolution(14);
     346    // リファレンスはデフォルト3.3Vなので設定しない
     347    // analogReference();
     348 
     349    Bridge.begin();
     350    Bridge.provide("getADC", getADC);
     351}
     352
     353void loop() {}
     354
     355int getADC(int ch) {
     356    int value = -1;
     357    if((ch >= 0) && (ch < MAX_CH)) {
     358       value = analogRead(adcPins[ch]);
     359    }
     360    return value;
     361}
     362}}}
     363
     364* CPU(linux)側
     365
     366各チャネルを10回読み込む
     367
     368{{{
     369#include "arduino_bridge.hpp"
     370#include <iostream>
     371#include <thread>
     372#include <chrono>
     373
     374int main() {
     375    ArduinoBridge bridge;
     376
     377    std::cout << "Connecting to arduino-router..." << std::endl;
     378    if (!bridge.connect()) {
     379        std::cerr << "Failed to connect!" << std::endl;
     380        return 1;
     381    }
     382
     383    std::cout << "Connected! ADC_test..." << std::endl;
     384
     385    bool state = false;
     386    for (int i = 0; i < 10; i++) {
     387
     388      for(int ch = 0 ; ch < 6 ; ch++) {
     389        auto rdStat = bridge.call("getADC", ch);
     390        if(rdStat.success) {
     391          int adcVal = rdStat.result.get().as<int>();
     392          std::cout << "GET ADC[" << ch << "]: " << adcVal << std::endl;
     393        }
     394      }
     395      std::this_thread::sleep_for(std::chrono::milliseconds(500));
     396    }
     397
     398    bridge.disconnect();
     399    return 0;
     400}
     401}}}
     402
     403きちんと動作はしたので、プログラム的には問題ない。
     404
     405== IOC ==
     406
     407EPICS本体はインストール済み。
     408いつも通り{{{makeBaseApp}}}で雛型を作成。今回は {{{micon_test}}}とした。
     409asynPortDriverの雛型はAIで作ってもらった。
     410
     411* {{{configure/RELEASE.local}}}
     412
     413 最近は、{{{configure/RELEASE}}}を変更せずに、{{{configure/RELEASE.local}}}に設定する方がはやり(?)らしい。
     414 {{{
     415ASYN=$(EPICS_BASE)/../modules/soft/asyn/R4-46
     416}}}
     417
     418* {{{micon_testApp/src/unoQRpcDriver.cpp}}}
     419
     420 今回必要なのは、ADCからの受信のみなので、{{{readInt32}}}のみを実装
     421 {{{
     422#include <iocsh.h>
     423#include <epicsExport.h>
     424#include <errlog.h>
     425#include "unoQRpcDriver.h"
     426
     427unoQRpcDriver::unoQRpcDriver(const char *portName, const char *rpcEndpoint)
     428    : asynPortDriver(portName,
     429                     NUM_CHANNELS,                    /* maxAddr = 6 (ch 0~5) */
     430                     asynInt32Mask | asynDrvUserMask, /* インターフェース */
     431                     asynInt32Mask,                   /* 割り込みマスク */
     432                     ASYN_MULTIDEVICE | ASYN_CANBLOCK,
     433                     1, 0, 0)
     434      , rpcEndpoint_(rpcEndpoint)
     435{
     436    createParam(P_ChannelValueString, asynParamInt32, &P_ChannelValue);
     437
     438    if(!bridge.connect()) {
     439      errlogPrintf("arduino-router connection fail.\n");
     440    }
     441}
     442
     443asynStatus unoQRpcDriver::readInt32(asynUser *pasynUser, epicsInt32 *value) {
     444    int function = pasynUser->reason;
     445    int addr = 0;
     446    getAddress(pasynUser, &addr);
     447
     448    if (addr < 0 || addr >= NUM_CHANNELS) {
     449        return asynError;
     450    }
     451
     452    *value = 0;
     453    if (function == P_ChannelValue) {
     454      auto rdStat = bridge.call("getADC", addr);
     455      if(rdStat.success) {
     456        *value = rdStat.result.get().as<int>();
     457        //std::cout << "GET ADC[" << ch << "]: " << adcVal << std::endl;
     458      }
     459      // --- RPCコール例 ---
     460      // int result = rpcClient.call("readChannel", addr).as<int>();
     461      // *value = result;
     462
     463      //*value = 0; // 取得した値を格納
     464      setIntegerParam(addr, P_ChannelValue, *value);
     465      return asynSuccess;
     466    }
     467
     468    return asynPortDriver::readInt32(pasynUser, value);
     469}
     470
     471// iocsh コマンド登録
     472extern "C" int unoQRpcDriverConfigure(const char *portName, const char *rpcEndpoint) {
     473    new unoQRpcDriver(portName, rpcEndpoint);
     474    return 0;
     475}
     476
     477static const iocshArg initArg0 = {"Port name", iocshArgString};
     478static const iocshArg initArg1 = {"RPC Endpoint", iocshArgString};
     479static const iocshArg * const initArgs[] = {&initArg0, &initArg1};
     480static const iocshFuncDef initFuncDef = {"unoQRpcDriverConfigure", 2, initArgs};
     481
     482static void initCallFunc(const iocshArgBuf *args) {
     483    unoQRpcDriverConfigure(args[0].sval, args[1].sval);
     484}
     485
     486void unoQRpcDriverRegister(void) {
     487    iocshRegister(&initFuncDef, initCallFunc);
     488}
     489extern "C" {
     490    epicsExportRegistrar(unoQRpcDriverRegister);
     491}
     492}}}
     493
     494* {{{micon_testApp/src/unoQRpcDriver.h}}}
     495 {{{
     496#ifndef UNOQ_RPC_DRIVER_H
     497#define UNOQ_RPC_DRIVER_H
     498
     499#include "asynPortDriver.h"
     500#include "arduino_bridge.hpp"
     501
     502#define NUM_CHANNELS 6
     503#define P_ChannelValueString "ADC_VAL" // asynInt32
     504
     505class unoQRpcDriver : public asynPortDriver {
     506public:
     507    unoQRpcDriver(const char *portName, const char *rpcEndpoint);
     508
     509    virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value) override;
     510
     511protected:
     512    int P_ChannelValue;
     513
     514private:
     515    std::string rpcEndpoint_;
     516    ArduinoBridge bridge;
     517};
     518
     519#endif
     520}}}
     521
     522* {{{micon_testApp/src/unoQRpc.dbd}}}
     523 {{{
     524registrar(unoQRpcDriverRegister)
     525}}}
     526
     527* {{{micon_testApp/src/Makefile}}}
     528 {{{
     529# SPDX-FileCopyrightText: 2003 Argonne National Laboratory
     530#
     531# SPDX-License-Identifier: EPICS
     532
     533TOP=../..
     534
     535include $(TOP)/configure/CONFIG
     536#----------------------------------------
     537#  ADD MACRO DEFINITIONS AFTER THIS LINE
     538#=============================
     539
     540#=============================
     541# Build the IOC application
     542
     543PROD_IOC = micon_test
     544# micon_test.dbd will be created and installed
     545DBD += micon_test.dbd
     546
     547# micon_test.dbd will be made up from these files:
     548micon_test_DBD += base.dbd
     549micon_test_DBD += unoQRpc.dbd
     550
     551# Include dbd files from all support applications:
     552#micon_test_DBD += xxx.dbd
     553
     554# Add all the support libraries needed by this IOC
     555#micon_test_LIBS += xxx
     556
     557# micon_test_registerRecordDeviceDriver.cpp derives from micon_test.dbd
     558micon_test_SRCS += micon_test_registerRecordDeviceDriver.cpp
     559micon_test_SRCS += unoQRpcDriver.cpp
     560
     561# Build the main IOC entry point on workstation OSs.
     562micon_test_SRCS_DEFAULT += micon_testMain.cpp
     563micon_test_SRCS_vxWorks += -nil-
     564
     565# Add support from base/src/vxWorks if needed
     566#micon_test_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary
     567
     568ifneq ($(ASYN),)
     569    micon_test_DBD += asyn.dbd
     570    micon_test_DBD += drvAsynIPPort.dbd            # IP Port Driver
     571#    micon_test_DBD += drvAsynSerialPort.dbd       # Serial Port Driver
     572#    micon_test_DBD += drvVxi11.dbd                # VXI-11 Driver
     573    micon_test_LIBS += asyn
     574endif
     575
     576# Finally link to the EPICS Base libraries
     577micon_test_LIBS += $(EPICS_BASE_IOC_LIBS)
     578
     579#===========================
     580
     581include $(TOP)/configure/RULES
     582#----------------------------------------
     583#  ADD RULES AFTER THIS LINE
     584}}}
     585
     586* {{{micon_testApp/Db/unoQ_ADC.db}}}
     587 {{{
     588record(longin, "$(USER):ADC:CH$(CH):RAW") {
     589    field(DTYP, "asynInt32")
     590    field(INP,  "@asyn($(PORT),$(CH))ADC_VAL")
     591    field(SCAN, "1 second")
     592}
     593}}}
     594
     595* {{{micon_testApp/Db/Makefile}}}
     596 {{{
     597# SPDX-FileCopyrightText: 1998 Argonne National Laboratory
     598#
     599# SPDX-License-Identifier: EPICS
     600
     601TOP=../..
     602include $(TOP)/configure/CONFIG
     603#----------------------------------------
     604#  ADD MACRO DEFINITIONS AFTER THIS LINE
     605
     606#----------------------------------------------------
     607# Create and install (or just install) into <top>/db
     608# databases, templates, substitutions like this
     609#DB += xxx.db
     610DB += unoQ_ADC.db
     611
     612#----------------------------------------------------
     613# If <anyname>.db template is not named <anyname>*.template add
     614# <anyname>_template = <templatename>
     615
     616include $(TOP)/configure/RULES
     617#----------------------------------------
     618#  ADD RULES AFTER THIS LINE
     619}}}
     620
     621* {{{iocBoot/iocmicon_test/st.cmd}}}
     622 {{{
     623#!../../bin/linux-aarch64/micon_test
     624
     625#- SPDX-FileCopyrightText: 2003 Argonne National Laboratory
     626#-
     627#- SPDX-License-Identifier: EPICS
     628
     629#- You may have to change micon_test to something else
     630#- everywhere it appears in this file
     631
     632< envPaths
     633
     634cd "${TOP}"
     635
     636epicsEnvSet("PORT", "UNOQ1")
     637
     638## Register all support components
     639dbLoadDatabase "dbd/micon_test.dbd"
     640micon_test_registerRecordDeviceDriver pdbbase
     641
     642unoQRpcDriverConfigure("$(PORT)", "")
     643
     644## Load record instances
     645dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=0,PORT=$(PORT)")
     646dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=1,PORT=$(PORT)")
     647dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=2,PORT=$(PORT)")
     648dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=3,PORT=$(PORT)")
     649dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=4,PORT=$(PORT)")
     650dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=5,PORT=$(PORT)")
     651
     652cd "${TOP}/iocBoot/${IOC}"
     653iocInit
     654
     655## Start any sequence programs
     656#seq sncxxx,"user=pfoper"
     657}}}
     658
     659== IOC実行 ==
     660
     661IOCを実行して、データが取れるかをテスト。
     662CH0をGNDに接続して開始して、途中でジャンパ―ピンを抜いて動作確認。
     663無事に動作した。
     664
     665{{{
     666$ camonitor PFTEST:UNOQ1:ADC:CH0:RAW
     667PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:40.115423 0
     668PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:41.116116 15
     669PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:42.115688 0
     670PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:44.115871 1
     671PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:45.115417 0
     672PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:46.116083 2
     673PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:47.115864 0
     674PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:51.115959 1023
     675PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:52.115446 1082
     676PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:53.116152 1370
     677PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:54.115828 1363
     678PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:55.116057 0
     679PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:56.115565 1
     680PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:57.116184 0
     681PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:58.115706 4
     682PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:59.115186 0
     683PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:43:05.115706 1
     684PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:43:06.115149 0
     685}}}
     686
     687= I2C =
     688
     689M5stack ENV IIIを使ってI2C接続でのデータ取得をやってみる。
     690接続は、UNO QのArduino PIN
     691
     692* SCL: D21
     693* SDA: D20
     694* 5V : +5V OUT
     695* GND: GND
     696
     697== Arduino(MPU)側 ==
     698ADC取得に付け加える形にした。
     699各デバイスの制御用プログラムは、[https://github.com/m5stack/M5Unit-ENV/ 公式サイト]から持ってきたものを使用する。
     700使用したファイルは、
     701 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/SHT3X.h
     702 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/SHT3X.cpp
     703 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/QMP6988.h
     704 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/QMP6988.cpp
     705 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/I2C_Class.h
     706 * https://github.com/m5stack/M5Unit-ENV/blob/master/src/I2C_Class.cpp
     707これらをsketch.inoと同じディレクトリにコピー。
     708但し、そのままではコンパイルが通らないので、{{{I2C_Class.cpp}}}を少し修正。
     709
     710{{{
     711void I2C_Class::begin(TwoWire *wire, uint8_t sda, uint8_t scl, long freq) {
     712    _wire = wire;
     713    _sda  = sda;
     714    _scl  = scl;
     715    _freq = freq;
     716    _wire->end();
     717//    _wire->begin(static_cast<int>(_sda), _scl, freq);
     718    _wire->begin();
     719}
     720}}}
     721
     722今回使用したsketch.ino。
     723
     724{{{
     725#include <Wire.h>
     726#include "Arduino_RouterBridge.h"
     727#include "QMP6988.h"
     728#include "SHT3X.h"
     729
     730#define MAX_CH  6
     731
     732int adcPins[MAX_CH] = {A0, A1, A2, A3, A4, A5};
     733
     734SHT3X sht31;
     735QMP6988 qmp6988;
     736
     737void setup() {
     738    // ADCは14bit
     739    analogReadResolution(14);
     740    // リファレンスはデフォルト3.3Vなので設定しない
     741    // analogReference();
     742 
     743    Bridge.begin();
     744    Bridge.provide("getADC", getADC);
     745    Bridge.provide("getTemp", getTemp);
     746    Bridge.provide("getHum", getHum);
     747    Bridge.provide("getPress", getPress);
     748
     749    //Wire.begin();
     750    sht31.begin(&Wire);
     751    qmp6988.begin(&Wire, QMP6988_SLAVE_ADDRESS_L);
     752 
     753}
     754
     755void loop() {
     756  sht31.update();
     757  qmp6988.update();
     758}
     759
     760int getADC(int ch) {
     761    int value = -1;
     762    if((ch >= 0) && (ch < MAX_CH)) {
     763       value = analogRead(adcPins[ch]);
     764    }
     765    return value;
     766}
     767
     768float getTemp() {
     769  return sht31.cTemp;
     770}
     771
     772float getHum() {
     773  return sht31.humidity;
     774}
     775
     776float getPress() {
     777  return qmp6988.pressure/100;
     778}
     779}}}
     780
     781== asynPortDriver ==
     782
     783修正したファイルは、{{{unoQRpcDriver.h}}} と {{{unoQRpcDriver.cpp}}}。
     784あと、レコードファイル{{{unoQ_I2C_ENV3.db}}} を別途追加。
     785
     786* {{{micon_testApp/src/unoQRpcDriver.h}}}
     787 {{{
     788#ifndef UNOQ_RPC_DRIVER_H
     789#define UNOQ_RPC_DRIVER_H
     790
     791#include "asynPortDriver.h"
     792#include "arduino_bridge.hpp"
     793
     794#define NUM_CHANNELS 6
     795
     796// Int32(Longin)
     797#define P_ChannelValueString "ADC_VAL" // asynInt32
     798
     799// Float64(ai)
     800#define P_TempValueString  "TEMP_VAL"  // asynFloat64
     801#define P_HumValueString   "HUM_VAL"   // asynFloat64
     802#define P_PressValueString "PRESS_VAL" // asynFloat64
     803
     804class unoQRpcDriver : public asynPortDriver {
     805public:
     806    unoQRpcDriver(const char *portName, const char *rpcEndpoint);
     807
     808    virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value) override;
     809    virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value) override;
     810
     811protected:
     812    // readInt32
     813    int P_ChannelValue;
     814
     815    // readFloat64
     816    int P_TempValue;
     817    int P_HumValue;
     818    int P_PressValue;
     819
     820private:
     821    std::string rpcEndpoint_;
     822    ArduinoBridge bridge;
     823};
     824
     825#endif
     826}}}
     827
     828* {{{micon_testApp/src/unoQRpcDriver.cpp}}}
     829 {{{
     830#include <iocsh.h>
     831#include <epicsExport.h>
     832#include <errlog.h>
     833#include "unoQRpcDriver.h"
     834
     835unoQRpcDriver::unoQRpcDriver(const char *portName, const char *rpcEndpoint)
     836    : asynPortDriver(portName,
     837                     NUM_CHANNELS,
     838                     asynInt32Mask | asynDrvUserMask |  /* インターフェース */
     839                     asynFloat64Mask | asynDrvUserMask,
     840                     asynInt32Mask|                     /* 割り込みマスク */
     841                     asynFloat64Mask,
     842                     ASYN_MULTIDEVICE | ASYN_CANBLOCK,
     843                     1, 0, 0)
     844      , rpcEndpoint_(rpcEndpoint)
     845{
     846    // asynInt32
     847    createParam(P_ChannelValueString, asynParamInt32, &P_ChannelValue);
     848
     849    // asynFloat64
     850    createParam(P_TempValueString,  asynParamFloat64, &P_TempValue);
     851    createParam(P_HumValueString,   asynParamFloat64, &P_HumValue);
     852    createParam(P_PressValueString, asynParamFloat64, &P_PressValue);
     853
     854    if(!bridge.connect()) {
     855      errlogPrintf("arduino-router connection fail.\n");
     856    }
     857}
     858
     859asynStatus unoQRpcDriver::readInt32(asynUser *pasynUser, epicsInt32 *value) {
     860    int function = pasynUser->reason;
     861    int addr = 0;
     862    getAddress(pasynUser, &addr);
     863
     864    if (addr < 0 || addr >= NUM_CHANNELS) {
     865        return asynError;
     866    }
     867
     868    *value = 0;
     869    if (function == P_ChannelValue) {
     870      auto rdStat = bridge.call("getADC", addr);
     871      if(rdStat.success) {
     872        *value = rdStat.result.get().as<int>();
     873      }
     874      setIntegerParam(addr, P_ChannelValue, *value);
     875      return asynSuccess;
     876    }
     877
     878    return asynPortDriver::readInt32(pasynUser, value);
     879}
     880asynStatus unoQRpcDriver::readFloat64(asynUser *pasynUser, epicsFloat64 *value) {
     881    int addr = 0;
     882    int function = pasynUser->reason;
     883    char rpcFunc[16];
     884
     885    memset(rpcFunc, 0x00, sizeof(rpcFunc));
     886
     887    getAddress(pasynUser, &addr);
     888
     889    *value = 0;
     890    if(function == P_TempValue) {
     891      sprintf(rpcFunc, "getTemp");
     892    }
     893    if(function == P_HumValue) {
     894      sprintf(rpcFunc, "getHum");
     895    }
     896    if(function == P_PressValue) {
     897      sprintf(rpcFunc, "getPress");
     898    }
     899    if(rpcFunc[0] != 0x00) {
     900      auto rdStat = bridge.call(rpcFunc);
     901      if(rdStat.success) {
     902        *value = rdStat.result.get().as<float>();
     903      }
     904      setDoubleParam(addr, function, *value);
     905      return asynSuccess;
     906    }
     907
     908    return asynPortDriver::readFloat64(pasynUser, value);
     909}
     910
     911// iocsh コマンド登録
     912extern "C" int unoQRpcDriverConfigure(const char *portName, const char *rpcEndpoint) {
     913    new unoQRpcDriver(portName, rpcEndpoint);
     914    return 0;
     915}
     916
     917static const iocshArg initArg0 = {"Port name", iocshArgString};
     918static const iocshArg initArg1 = {"RPC Endpoint", iocshArgString};
     919static const iocshArg * const initArgs[] = {&initArg0, &initArg1};
     920static const iocshFuncDef initFuncDef = {"unoQRpcDriverConfigure", 2, initArgs};
     921
     922static void initCallFunc(const iocshArgBuf *args) {
     923    unoQRpcDriverConfigure(args[0].sval, args[1].sval);
     924}
     925
     926void unoQRpcDriverRegister(void) {
     927    iocshRegister(&initFuncDef, initCallFunc);
     928}
     929extern "C" {
     930    epicsExportRegistrar(unoQRpcDriverRegister);
     931}
     932}}}
     933
     934* {{{micon_testApp/Db/unoQ_I2C_ENV3.db}}}
     935 {{{
     936record(ai, "$(USER):ENV3:TEMP") {
     937    field(DTYP, "asynFloat64")
     938    field(INP,  "@asyn($(PORT),0)TEMP_VAL")
     939    field(SCAN, "1 second")
     940}
     941
     942record(ai, "$(USER):ENV3:HUM") {
     943    field(DTYP, "asynFloat64")
     944    field(INP,  "@asyn($(PORT),0)HUM_VAL")
     945    field(SCAN, "1 second")
     946}
     947
     948record(ai, "$(USER):ENV3:PRESS") {
     949    field(DTYP, "asynFloat64")
     950    field(INP,  "@asyn($(PORT),0)PRESS_VAL")
     951    field(SCAN, "1 second")
     952}
     953}}}
     954
     955== st.cmd ==
     956{{{st.cmd}}}にレコードファイルを追加。
     957
     958{{{
     959dbLoadRecords("db/unoQ_I2C_ENV3.db","USER=PFTEST:UNOQ1,PORT=$(PORT)")
     960}}}
     961
     962これで動いた。
     963
     964{{{
     965[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ADC:CH0:RAW
     966PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:55.466526 1376
     967PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:56.467686 1448
     968PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:57.467789 1486
     969PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:58.466519 1454
     970^C
     971[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:PRESS
     972PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:00.462276 1008.46
     973PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:01.461867 1008.45
     974PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:02.461910 1008.44
     975^C
     976[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:TEMP
     977PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:05.452173 25.9026
     978PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:06.453314 25.9453
     979PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:07.452354 25.9319
     980^C
     981[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:HUM
     982PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:10.457186 55.613
     983PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:11.457150 55.6039
     984PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:12.457282 55.6252
     985PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:14.457327 55.6115
     986}}}