wiki:epics/Aruduino_UNO_Q

Version 1 (modified by Tetsuya Michikawa, 43 hours ago) ( diff )

--

Arduino UNO Q

ArduinoがQualcommに買収されて始めに作ったQualcomm純正のArduinoボード。

https://www.qualcomm.com/developer/hardware/arduino-uno-q

Linux(Debian)が始めから動作している。 2GB/16GB emmc版と4GB/32GB emmc版があるが、今回は4GB/32GB emmc版を使う。

初期セットアップ

各種ソフトウェアはここからダウンロードする。 https://www.arduino.cc/en/software/

参考資料

partition マージ

Arduino UNO Qのディスクはemmcしかないが、/パーティションが小さく、/home/arduinoにかなりの容量を割り当てているという変な構成になっている。 試しにEPICS baseをインストールすると容量の95%まで使ってしまったので、/home/arduino/にマージする。

Arduino UNO Q:ルートパーティションを拡張する パーティションをマージするスクリプト

/dev/mmcblk0p68  28954256 7736576  19950792  28% /

となる。

が、マージに失敗してLinuxが起動しなくなったのでemmcを初期化をしてみると、/home/arduino8GBになっていたので、マージしなくてもよかったかも。ユーザーパーティションがデカすぎて不評だったのかも。

arduino@bella:~$ df
Filesystem      1K-blocks    Used Available Use% Mounted on
/dev/mmcblk0p68  20541220 6378032  13238176  33% /
/dev/mmcblk0p69   8402388  639224   7400120   8% /home/arduino
/dev/mmcblk0p67    499004  138136    360868  28% /boot/efi

Uno Qを買ったら最初に覚えるべき、最後の手段《初期化方法》

setup

初期設定は、[​https://www.arduino.cc/en/software/ Arduino App Lab]を使って行う。 初期ユーザーはarduino、パスワードはセットアップ時に設定する。

HW

Arduino UNO Qは、通常使用する外部IFはUSB-Cしかないので、開発時にはUSB給電ポート/Ethernet付USB-C HUBがほぼ必須になる。

今回は、手持ちのUSB給電ポート付USB-C HUBとUSB-Ether変換ケーブルを接続して使用。 ちなみにsshdは始めから動作しているようなので、DHCP環境下でUSB-Ether変換ケーブルを接続すると外部から接続が可能になる。(WiFi環境下ならUSB-Etherは要らない)

ユーザー作成

root@marya:~# useradd -m pfoper
root@marya:~# usermod -aG sudo pfoper
root@marya:~# usermod -aG dialout pfoper

パッケージ

root@marya:~# apt -y install build-essential emacs procserv telnet libreadline-dev re2c libreadline-dev libtirpc-dev

epics

epicsは最新版の 7.0.10。コンパイルは特に問題なくいつも通りで問題なし。arch は linux-aarch64。 インストール先は/opt/epics/R7.0.10。 インストールしたモジュールは、

環境変数は、/opt/epics/R7.0.10/etc/setenv.sh

arduino

内蔵マイコン(STM32)用の開発環境はarduino-cliを使うのが良いそうなので、パッケージをrootでインストール。

root@marya:~# curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
Installing in /usr/local/bin
ARCH=ARM64
OS=Linux
Using curl as download tool
Downloading https://downloads.arduino.cc/arduino-cli/arduino-cli_1.5.1_Linux_ARM64.tar.gz
arduino-cli  Version: 1.5.1 Commit: 01f3d4f2b Date: 2026-06-05T10:22:11Z installed successfully in /usr/local/bin

各ユーザーで設定ファイルを初期化

pfoper@marya:~$ arduino-cli config init
Config file written to: /home/pfoper/.arduino15/arduino-cli.yaml

パッケージインデックスを更新

pfoper@marya:~$ arduino-cli core update-index
Downloading index: library_index.tar.bz2 downloaded
Downloading index: package_index.tar.bz2 downloaded
Downloading missing tool builtin:mdns-discovery@1.1.0...
builtin:mdns-discovery@1.1.0 downloaded
Installing builtin:mdns-discovery@1.1.0...
Skipping tool configuration....
builtin:mdns-discovery@1.1.0 installed
Downloading missing tool builtin:serial-discovery@1.5.2...
builtin:serial-discovery@1.5.2 downloaded
Installing builtin:serial-discovery@1.5.2...
Skipping tool configuration....
builtin:serial-discovery@1.5.2 installed
Downloading missing tool builtin:serial-monitor@0.15.0...
builtin:serial-monitor@0.15.0 downloaded
Installing builtin:serial-monitor@0.15.0...
Skipping tool configuration....
builtin:serial-monitor@0.15.0 installed
Downloading missing tool builtin:ctags@5.8-arduino11...
builtin:ctags@5.8-arduino11 downloaded
Installing builtin:ctags@5.8-arduino11...
Skipping tool configuration....
builtin:ctags@5.8-arduino11 installed
Downloading missing tool builtin:dfu-discovery@0.1.2...
builtin:dfu-discovery@0.1.2 downloaded
Installing builtin:dfu-discovery@0.1.2...
Skipping tool configuration....
builtin:dfu-discovery@0.1.2 installed
Downloading index: package_index.tar.bz2 downloaded

ボードコア(UNO Q用)をインストール インストールには多少時間がかかる。

pfoper@marya:~$ arduino-cli core install arduino:zephyr
    :

動作確認

まずは、普通のLチカをやってみたが動作しない。 色々と調べてみたが、どうやらarduino-cliを使って直接STM32をいじってはいけないらしい。

https://prahari.net/blog/arduino-uno-q-is-not-a-regular-arduino-what-i-learned-the-hard-way

また、MPUとの通信はシリアル経由ではなく、RPC経由で実施する必要があることも判明。(AIに聞いたときはシリアル経由でやるように言われてたけど、他のArduinoでの使い方とごちゃ混ぜになっているようで、実際には使えない模様(;;))

MPUのプログラムは色々と複雑(面倒)なので、Arduino App Lab経由で実装するのが一番正しそう。 App Labで開発後、EPICSとの通信部をRPCで実装する方針でやってみることとする。

RPC

Working with Arduino Router RPC Arduino UNO QのMCU⇄Linux間で値をやり取りする

とりあえず、Working with Arduino Router RPCのソースコードを使って、Lチカをしてみる。

MPU側

Arduino App Labを使って実装してみる。 App Labは起動時に接続可能なArduino UNO Qを探しに行くみたいで、起動画面に接続先リストが表示されるので、目的のUNO Qに接続する。 接続後はエージェントモードで動作するので、ソースコードもUNO Qに保存される。

blinkLEDというAppを作成して、以下の様なsketch.inoを作成。

#include "Arduino_RouterBridge.h"

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    
    Bridge.begin();
    Bridge.provide("set_led_state", set_led_state);
    Bridge.provide("get_led_state", get_led_state);
}

void loop() {}

void set_led_state(bool state) {
    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
}

書き終わったら、App Lab右上のRunボタンを押すことで、コンパイルと実行が行われる。

Linux側(C++)

まずは必要なパッケージをインストール

apt install libmsgpack-cxx-dev

Linux側のソースコード arduino_bridge.hppWorking with Arduino Router RPCにあるものをコピペして使う。 最近のC++難しい、、、

#include "arduino_bridge.hpp"
#include <iostream>
#include <thread>
#include <chrono>

int main() {
    ArduinoBridge bridge;

    std::cout << "Connecting to arduino-router..." << std::endl;
    if (!bridge.connect()) {
        std::cerr << "Failed to connect!" << std::endl;
        return 1;
    }

    std::cout << "Connected! Blinking LED..." << std::endl;

    bool state = false;
    for (int i = 0; i < 10; i++) {
        state = !state;

        if (bridge.notify("set_led_state", state)) {
            std::cout << "SET LED: " << (state ? "ON" : "OFF") << std::endl;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    bridge.disconnect();
    return 0;
}

コンパイル&実行

$ g++ -std=c++17 blinkLED.cpp -pthread -o blinkLED
$ ./blinkLED
Connecting to arduino-router...
Connected! Blinking LED...
SET LED: ON
SET LED: OFF
SET LED: ON
SET LED: OFF
SET LED: ON
SET LED: OFF
SET LED: ON
SET LED: OFF
SET LED: ON
SET LED: OFF

動くことは確認できた。

Linux側(python)

ついでにpython版も。

こちらもパッケージをインストール

# pip3 install msgpack --break-system-packages

ソースコード

from arduino_bridge import ArduinoBridge
import time

def main():
    bridge = ArduinoBridge()

    if not bridge.connect():
        print("Failed to connect")
        return

    print("Connected!")

    for i in range(10):
        state = i % 2 == 0
        bridge.notify("set_led_state", state)
        print(f"LED: {'ON' if state else 'OFF'}")
        time.sleep(0.5)

    bridge.disconnect()

if __name__ == "__main__":
    main()

実行

$ python3 ./blinkLED.py
Connected!
LED: ON
LED: OFF
LED: ON
LED: OFF
LED: ON
LED: OFF
LED: ON
LED: OFF
LED: ON
LED: OFF

こちらも動作する。

修正版C++用RPC ヘッダ

RPCでArduinoからデータを取ろうとするとコンパイルエラーになったので、AIで修正したヘッダファイルを追加しておく。

arduino_bridge.hpp

EPICS IOC

本番のEPICS IOCを作成する。 RPCを使う場合、asynPortDriverかsub(or aSub) record、又はpythonを使うかになるが、今回はasynPortDriverでやってみる。

ADC 予備実験

IOCを作る前に、通常のプログラムでデータが読めるか実験。

  • MPU(arduino)側

何も考えずに、データ要求が来たら生のADC値の返す。

#include "Arduino_RouterBridge.h"

#define MAX_CH  6

int adcPins[MAX_CH] = {A0, A1, A2, A3, A4, A5};

void setup() {
    // ADCは14bit
    analogReadResolution(14);
    // リファレンスはデフォルト3.3Vなので設定しない
    // analogReference();
  
    Bridge.begin();
    Bridge.provide("getADC", getADC);
}

void loop() {}

int getADC(int ch) {
    int value = -1;
    if((ch >= 0) && (ch < MAX_CH)) {
       value = analogRead(adcPins[ch]);
    }
    return value;
}
  • CPU(linux)側

各チャネルを10回読み込む

#include "arduino_bridge.hpp"
#include <iostream>
#include <thread>
#include <chrono>

int main() {
    ArduinoBridge bridge;

    std::cout << "Connecting to arduino-router..." << std::endl;
    if (!bridge.connect()) {
        std::cerr << "Failed to connect!" << std::endl;
        return 1;
    }

    std::cout << "Connected! ADC_test..." << std::endl;

    bool state = false;
    for (int i = 0; i < 10; i++) {

      for(int ch = 0 ; ch < 6 ; ch++) {
        auto rdStat = bridge.call("getADC", ch);
        if(rdStat.success) {
          int adcVal = rdStat.result.get().as<int>();
          std::cout << "GET ADC[" << ch << "]: " << adcVal << std::endl;
        }
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    bridge.disconnect();
    return 0;
}

きちんと動作はしたので、プログラム的には問題ない。

IOC

EPICS本体はインストール済み。 いつも通りmakeBaseAppで雛型を作成。今回は micon_testとした。 asynPortDriverの雛型はAIで作ってもらった。

  • configure/RELEASE.local

最近は、configure/RELEASEを変更せずに、configure/RELEASE.localに設定する方がはやり(?)らしい。

ASYN=$(EPICS_BASE)/../modules/soft/asyn/R4-46
  • micon_testApp/src/unoQRpcDriver.cpp

今回必要なのは、ADCからの受信のみなので、readInt32のみを実装

#include <iocsh.h>
#include <epicsExport.h>
#include <errlog.h>
#include "unoQRpcDriver.h"

unoQRpcDriver::unoQRpcDriver(const char *portName, const char *rpcEndpoint)
    : asynPortDriver(portName,
                     NUM_CHANNELS,                    /* maxAddr = 6 (ch 0~5) */
                     asynInt32Mask | asynDrvUserMask, /* インターフェース */
                     asynInt32Mask,                   /* 割り込みマスク */
                     ASYN_MULTIDEVICE | ASYN_CANBLOCK,
                     1, 0, 0)
      , rpcEndpoint_(rpcEndpoint)
{
    createParam(P_ChannelValueString, asynParamInt32, &P_ChannelValue);

    if(!bridge.connect()) {
      errlogPrintf("arduino-router connection fail.\n");
    }
}

asynStatus unoQRpcDriver::readInt32(asynUser *pasynUser, epicsInt32 *value) {
    int function = pasynUser->reason;
    int addr = 0;
    getAddress(pasynUser, &addr);

    if (addr < 0 || addr >= NUM_CHANNELS) {
        return asynError;
    }

    *value = 0;
    if (function == P_ChannelValue) {
      auto rdStat = bridge.call("getADC", addr);
      if(rdStat.success) {
        *value = rdStat.result.get().as<int>();
        //std::cout << "GET ADC[" << ch << "]: " << adcVal << std::endl;
      }
      // --- RPCコール例 ---
      // int result = rpcClient.call("readChannel", addr).as<int>();
      // *value = result;

      //*value = 0; // 取得した値を格納
      setIntegerParam(addr, P_ChannelValue, *value);
      return asynSuccess;
    }

    return asynPortDriver::readInt32(pasynUser, value);
}

// iocsh コマンド登録
extern "C" int unoQRpcDriverConfigure(const char *portName, const char *rpcEndpoint) {
    new unoQRpcDriver(portName, rpcEndpoint);
    return 0;
}

static const iocshArg initArg0 = {"Port name", iocshArgString};
static const iocshArg initArg1 = {"RPC Endpoint", iocshArgString};
static const iocshArg * const initArgs[] = {&initArg0, &initArg1};
static const iocshFuncDef initFuncDef = {"unoQRpcDriverConfigure", 2, initArgs};

static void initCallFunc(const iocshArgBuf *args) {
    unoQRpcDriverConfigure(args[0].sval, args[1].sval);
}

void unoQRpcDriverRegister(void) {
    iocshRegister(&initFuncDef, initCallFunc);
}
extern "C" {
    epicsExportRegistrar(unoQRpcDriverRegister);
}
  • micon_testApp/src/unoQRpcDriver.h
    #ifndef UNOQ_RPC_DRIVER_H
    #define UNOQ_RPC_DRIVER_H
    
    #include "asynPortDriver.h"
    #include "arduino_bridge.hpp"
    
    #define NUM_CHANNELS 6
    #define P_ChannelValueString "ADC_VAL" // asynInt32
    
    class unoQRpcDriver : public asynPortDriver {
    public:
        unoQRpcDriver(const char *portName, const char *rpcEndpoint);
    
        virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value) override;
    
    protected:
        int P_ChannelValue;
    
    private:
        std::string rpcEndpoint_;
        ArduinoBridge bridge;
    };
    
    #endif
    
  • micon_testApp/src/unoQRpc.dbd
    registrar(unoQRpcDriverRegister)
    
  • micon_testApp/src/Makefile
    # SPDX-FileCopyrightText: 2003 Argonne National Laboratory
    #
    # SPDX-License-Identifier: EPICS
    
    TOP=../..
    
    include $(TOP)/configure/CONFIG
    #----------------------------------------
    #  ADD MACRO DEFINITIONS AFTER THIS LINE
    #=============================
    
    #=============================
    # Build the IOC application
    
    PROD_IOC = micon_test
    # micon_test.dbd will be created and installed
    DBD += micon_test.dbd
    
    # micon_test.dbd will be made up from these files:
    micon_test_DBD += base.dbd
    micon_test_DBD += unoQRpc.dbd
    
    # Include dbd files from all support applications:
    #micon_test_DBD += xxx.dbd
    
    # Add all the support libraries needed by this IOC
    #micon_test_LIBS += xxx
    
    # micon_test_registerRecordDeviceDriver.cpp derives from micon_test.dbd
    micon_test_SRCS += micon_test_registerRecordDeviceDriver.cpp
    micon_test_SRCS += unoQRpcDriver.cpp
    
    # Build the main IOC entry point on workstation OSs.
    micon_test_SRCS_DEFAULT += micon_testMain.cpp
    micon_test_SRCS_vxWorks += -nil-
    
    # Add support from base/src/vxWorks if needed
    #micon_test_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary
    
    ifneq ($(ASYN),)
        micon_test_DBD += asyn.dbd
        micon_test_DBD += drvAsynIPPort.dbd            # IP Port Driver
    #    micon_test_DBD += drvAsynSerialPort.dbd       # Serial Port Driver
    #    micon_test_DBD += drvVxi11.dbd                # VXI-11 Driver
        micon_test_LIBS += asyn
    endif
    
    # Finally link to the EPICS Base libraries
    micon_test_LIBS += $(EPICS_BASE_IOC_LIBS)
    
    #===========================
    
    include $(TOP)/configure/RULES
    #----------------------------------------
    #  ADD RULES AFTER THIS LINE
    
  • micon_testApp/Db/unoQ_ADC.db
    record(longin, "$(USER):ADC:CH$(CH):RAW") {
        field(DTYP, "asynInt32")
        field(INP,  "@asyn($(PORT),$(CH))ADC_VAL")
        field(SCAN, "1 second")
    }
    
  • micon_testApp/Db/Makefile
    # SPDX-FileCopyrightText: 1998 Argonne National Laboratory
    #
    # SPDX-License-Identifier: EPICS
    
    TOP=../..
    include $(TOP)/configure/CONFIG
    #----------------------------------------
    #  ADD MACRO DEFINITIONS AFTER THIS LINE
    
    #----------------------------------------------------
    # Create and install (or just install) into <top>/db
    # databases, templates, substitutions like this
    #DB += xxx.db
    DB += unoQ_ADC.db
    
    #----------------------------------------------------
    # If <anyname>.db template is not named <anyname>*.template add
    # <anyname>_template = <templatename>
    
    include $(TOP)/configure/RULES
    #----------------------------------------
    #  ADD RULES AFTER THIS LINE
    
  • iocBoot/iocmicon_test/st.cmd
    #!../../bin/linux-aarch64/micon_test
    
    #- SPDX-FileCopyrightText: 2003 Argonne National Laboratory
    #-
    #- SPDX-License-Identifier: EPICS
    
    #- You may have to change micon_test to something else
    #- everywhere it appears in this file
    
    < envPaths
    
    cd "${TOP}"
    
    epicsEnvSet("PORT", "UNOQ1")
    
    ## Register all support components
    dbLoadDatabase "dbd/micon_test.dbd"
    micon_test_registerRecordDeviceDriver pdbbase
    
    unoQRpcDriverConfigure("$(PORT)", "")
    
    ## Load record instances
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=0,PORT=$(PORT)")
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=1,PORT=$(PORT)")
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=2,PORT=$(PORT)")
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=3,PORT=$(PORT)")
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=4,PORT=$(PORT)")
    dbLoadRecords("db/unoQ_ADC.db","USER=PFTEST:UNOQ1,CH=5,PORT=$(PORT)")
    
    cd "${TOP}/iocBoot/${IOC}"
    iocInit
    
    ## Start any sequence programs
    #seq sncxxx,"user=pfoper"
    

IOC実行

IOCを実行して、データが取れるかをテスト。 CH0をGNDに接続して開始して、途中でジャンパ―ピンを抜いて動作確認。 無事に動作した。

$ camonitor PFTEST:UNOQ1:ADC:CH0:RAW
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:40.115423 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:41.116116 15
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:42.115688 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:44.115871 1
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:45.115417 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:46.116083 2
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:47.115864 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:51.115959 1023
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:52.115446 1082
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:53.116152 1370
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:54.115828 1363
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:55.116057 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:56.115565 1
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:57.116184 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:58.115706 4
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:42:59.115186 0
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:43:05.115706 1
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-01 15:43:06.115149 0

I2C

M5stack ENV IIIを使ってI2C接続でのデータ取得をやってみる。 接続は、UNO QのArduino PIN

  • SCL: D21
  • SDA: D20
  • 5V : +5V OUT
  • GND: GND

Arduino(MPU)側

ADC取得に付け加える形にした。 各デバイスの制御用プログラムは、公式サイトから持ってきたものを使用する。 使用したファイルは、

これらをsketch.inoと同じディレクトリにコピー。 但し、そのままではコンパイルが通らないので、I2C_Class.cppを少し修正。

void I2C_Class::begin(TwoWire *wire, uint8_t sda, uint8_t scl, long freq) {
    _wire = wire;
    _sda  = sda;
    _scl  = scl;
    _freq = freq;
    _wire->end();
//    _wire->begin(static_cast<int>(_sda), _scl, freq);
    _wire->begin();
}

今回使用したsketch.ino。

#include <Wire.h>
#include "Arduino_RouterBridge.h"
#include "QMP6988.h"
#include "SHT3X.h"

#define MAX_CH  6

int adcPins[MAX_CH] = {A0, A1, A2, A3, A4, A5};

SHT3X sht31;
QMP6988 qmp6988;

void setup() {
    // ADCは14bit
    analogReadResolution(14);
    // リファレンスはデフォルト3.3Vなので設定しない
    // analogReference();
  
    Bridge.begin();
    Bridge.provide("getADC", getADC);
    Bridge.provide("getTemp", getTemp);
    Bridge.provide("getHum", getHum);
    Bridge.provide("getPress", getPress);

    //Wire.begin();
    sht31.begin(&Wire);
    qmp6988.begin(&Wire, QMP6988_SLAVE_ADDRESS_L);
 
}

void loop() {
  sht31.update();
  qmp6988.update();
}

int getADC(int ch) {
    int value = -1;
    if((ch >= 0) && (ch < MAX_CH)) {
       value = analogRead(adcPins[ch]);
    }
    return value;
}

float getTemp() {
  return sht31.cTemp;
}

float getHum() {
  return sht31.humidity;
}

float getPress() {
  return qmp6988.pressure/100;
}

asynPortDriver

修正したファイルは、unoQRpcDriver.hunoQRpcDriver.cpp。 あと、レコードファイルunoQ_I2C_ENV3.db を別途追加。

  • micon_testApp/src/unoQRpcDriver.h
    #ifndef UNOQ_RPC_DRIVER_H
    #define UNOQ_RPC_DRIVER_H
    
    #include "asynPortDriver.h"
    #include "arduino_bridge.hpp"
    
    #define NUM_CHANNELS 6
    
    // Int32(Longin)
    #define P_ChannelValueString "ADC_VAL" // asynInt32
    
    // Float64(ai)
    #define P_TempValueString  "TEMP_VAL"  // asynFloat64
    #define P_HumValueString   "HUM_VAL"   // asynFloat64
    #define P_PressValueString "PRESS_VAL" // asynFloat64
    
    class unoQRpcDriver : public asynPortDriver {
    public:
        unoQRpcDriver(const char *portName, const char *rpcEndpoint);
    
        virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value) override;
        virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value) override;
    
    protected:
        // readInt32
        int P_ChannelValue;
    
        // readFloat64
        int P_TempValue;
        int P_HumValue;
        int P_PressValue;
    
    private:
        std::string rpcEndpoint_;
        ArduinoBridge bridge;
    };
    
    #endif
    
  • micon_testApp/src/unoQRpcDriver.cpp
    #include <iocsh.h>
    #include <epicsExport.h>
    #include <errlog.h>
    #include "unoQRpcDriver.h"
    
    unoQRpcDriver::unoQRpcDriver(const char *portName, const char *rpcEndpoint)
        : asynPortDriver(portName,
                         NUM_CHANNELS,
                         asynInt32Mask | asynDrvUserMask |  /* インターフェース */
                         asynFloat64Mask | asynDrvUserMask,
                         asynInt32Mask|                     /* 割り込みマスク */
                         asynFloat64Mask,
                         ASYN_MULTIDEVICE | ASYN_CANBLOCK,
                         1, 0, 0)
          , rpcEndpoint_(rpcEndpoint)
    {
        // asynInt32
        createParam(P_ChannelValueString, asynParamInt32, &P_ChannelValue);
    
        // asynFloat64
        createParam(P_TempValueString,  asynParamFloat64, &P_TempValue);
        createParam(P_HumValueString,   asynParamFloat64, &P_HumValue);
        createParam(P_PressValueString, asynParamFloat64, &P_PressValue);
    
        if(!bridge.connect()) {
          errlogPrintf("arduino-router connection fail.\n");
        }
    }
    
    asynStatus unoQRpcDriver::readInt32(asynUser *pasynUser, epicsInt32 *value) {
        int function = pasynUser->reason;
        int addr = 0;
        getAddress(pasynUser, &addr);
    
        if (addr < 0 || addr >= NUM_CHANNELS) {
            return asynError;
        }
    
        *value = 0;
        if (function == P_ChannelValue) {
          auto rdStat = bridge.call("getADC", addr);
          if(rdStat.success) {
            *value = rdStat.result.get().as<int>();
          }
          setIntegerParam(addr, P_ChannelValue, *value);
          return asynSuccess;
        }
    
        return asynPortDriver::readInt32(pasynUser, value);
    }
    asynStatus unoQRpcDriver::readFloat64(asynUser *pasynUser, epicsFloat64 *value) {
        int addr = 0;
        int function = pasynUser->reason;
        char rpcFunc[16];
    
        memset(rpcFunc, 0x00, sizeof(rpcFunc));
    
        getAddress(pasynUser, &addr);
    
        *value = 0;
        if(function == P_TempValue) {
          sprintf(rpcFunc, "getTemp");
        }
        if(function == P_HumValue) {
          sprintf(rpcFunc, "getHum");
        }
        if(function == P_PressValue) {
          sprintf(rpcFunc, "getPress");
        }
        if(rpcFunc[0] != 0x00) {
          auto rdStat = bridge.call(rpcFunc);
          if(rdStat.success) {
            *value = rdStat.result.get().as<float>();
          }
          setDoubleParam(addr, function, *value);
          return asynSuccess;
        }
    
        return asynPortDriver::readFloat64(pasynUser, value);
    }
    
    // iocsh コマンド登録
    extern "C" int unoQRpcDriverConfigure(const char *portName, const char *rpcEndpoint) {
        new unoQRpcDriver(portName, rpcEndpoint);
        return 0;
    }
    
    static const iocshArg initArg0 = {"Port name", iocshArgString};
    static const iocshArg initArg1 = {"RPC Endpoint", iocshArgString};
    static const iocshArg * const initArgs[] = {&initArg0, &initArg1};
    static const iocshFuncDef initFuncDef = {"unoQRpcDriverConfigure", 2, initArgs};
    
    static void initCallFunc(const iocshArgBuf *args) {
        unoQRpcDriverConfigure(args[0].sval, args[1].sval);
    }
    
    void unoQRpcDriverRegister(void) {
        iocshRegister(&initFuncDef, initCallFunc);
    }
    extern "C" {
        epicsExportRegistrar(unoQRpcDriverRegister);
    }
    
  • micon_testApp/Db/unoQ_I2C_ENV3.db
    record(ai, "$(USER):ENV3:TEMP") {
        field(DTYP, "asynFloat64")
        field(INP,  "@asyn($(PORT),0)TEMP_VAL")
        field(SCAN, "1 second")
    }
    
    record(ai, "$(USER):ENV3:HUM") {
        field(DTYP, "asynFloat64")
        field(INP,  "@asyn($(PORT),0)HUM_VAL")
        field(SCAN, "1 second")
    }
    
    record(ai, "$(USER):ENV3:PRESS") {
        field(DTYP, "asynFloat64")
        field(INP,  "@asyn($(PORT),0)PRESS_VAL")
        field(SCAN, "1 second")
    }
    

st.cmd

st.cmdにレコードファイルを追加。

dbLoadRecords("db/unoQ_I2C_ENV3.db","USER=PFTEST:UNOQ1,PORT=$(PORT)")

これで動いた。

[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ADC:CH0:RAW
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:55.466526 1376
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:56.467686 1448
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:57.467789 1486
PFTEST:UNOQ1:ADC:CH0:RAW       2026-09-02 16:38:58.466519 1454
^C
[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:PRESS
PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:00.462276 1008.46
PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:01.461867 1008.45
PFTEST:UNOQ1:ENV3:PRESS        2026-09-02 16:39:02.461910 1008.44
^C
[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:TEMP
PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:05.452173 25.9026
PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:06.453314 25.9453
PFTEST:UNOQ1:ENV3:TEMP         2026-09-02 16:39:07.452354 25.9319
^C
[michkawa@erlserv2 ~]$ camonitor PFTEST:UNOQ1:ENV3:HUM
PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:10.457186 55.613
PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:11.457150 55.6039
PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:12.457282 55.6252
PFTEST:UNOQ1:ENV3:HUM          2026-09-02 16:39:14.457327 55.6115

Attachments (2)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.