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/
参考資料
- https://www.denshi.club/cookbook/arduino/arduinounoq/arduino-uno-qwo-riyou.html
- https://qiita.com/Corgeek/items/3821b23ad4deaad372c0
partition マージ
Arduino UNO Qのディスクはemmcしかないが、/パーティションが小さく、/home/arduinoにかなりの容量を割り当てているという変な構成になっている。
試しにEPICS baseをインストールすると容量の95%まで使ってしまったので、/home/arduinoを/にマージする。
Arduino UNO Q:ルートパーティションを拡張する パーティションをマージするスクリプト
/dev/mmcblk0p68 28954256 7736576 19950792 28% /
となる。
が、マージに失敗してLinuxが起動しなくなったのでemmcを初期化をしてみると、/home/arduinoは8GBになっていたので、マージしなくてもよかったかも。ユーザーパーティションがデカすぎて不評だったのかも。
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。
インストールしたモジュールは、
- snc_seq 2-2-9 https://github.com/epics-modules/sequencer/archive/refs/tags/R2-2-9.tar.gz
- asyn 4-46 https://github.com/epics-modules/asyn/archive/refs/tags/R4-46.tar.gz
- streamdevice 2.8.26 https://github.com/paulscherrerinstitute/StreamDevice/archive/refs/tags/2.8.26.tar.gz
環境変数は、/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.hppはWorking 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で修正したヘッダファイルを追加しておく。
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.dbdregistrar(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.dbrecord(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取得に付け加える形にした。 各デバイスの制御用プログラムは、公式サイトから持ってきたものを使用する。 使用したファイルは、
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/SHT3X.h
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/SHT3X.cpp
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/QMP6988.h
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/QMP6988.cpp
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/I2C_Class.h
- https://github.com/m5stack/M5Unit-ENV/blob/master/src/I2C_Class.cpp
これらを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.h と unoQRpcDriver.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.dbrecord(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)
- merge_arduino_partitions.sh (2.6 KB ) - added by 43 hours ago.
- arduino_bridge.hpp (5.9 KB ) - added by 43 hours ago.
Download all attachments as: .zip
