AndroidスマホとRaspberry Pi Pico WをUSBシリアル通信接続してステッピングモータを制御する実験(Kotlinでスマホアプリ作成)


AndroidスマホとRaspberry Pi Pico WをUSBシリアル通信で接続してステッピングモータを制御する実験を紹介します。スマホ側のアプリはAndroid Studioを使用してKotlin言語で作成しました。
また、スマホ側のUSBシリアル通信用のライブラリーとしてusb-serial-for-androidを使用しました。

Raspberry Pi Pico W側のプログラムはArduino IDEを使用し、Arduino言語で作成しました。

1.動作動画

今回作成した実験装置を動作させた動画です。スマホとRaspberry Pi Pico WをUSB接続すると、アプリ起動許可画面が表示され、「OK」をタップするとアプリが起動されます。

アプリ画面の「接続」ボタンを押すとUSBシリアル通信が接続され、「CW」ボタンを押すとステッピングモータが時計方向に回転します。また「CCW」ボタンを押すとステッピングモータが反時計方向に回転します。

2.スマホ側のアプリに関して

2-1.スマホとRaspberry Pi Pico WのUSB接続に関して

使用したスマホにはType micro-BのUSBコネクタが付いていますので、Type micro-BからType A (メス)への変換ケーブル(OTGケーブル)を使用しました。それとType AとType micro-BのUSBケーブルを接続してスマホとRaspberry Pi Pico Wを接続しました。(図1)

図1.スマホとRaspberry Pi Pico WのUSB接続

2-2.usb-serial-for-androidに関して

USBシリアル通信を行うのに、usb-serial-for-androidのライブラリを使用しました。(詳細はhttps://github.com/mik3y/usb-serial-for-androidを参照して下さい)
以下でusb-serial-for-androidのライブラリを使用する手順に関して説明します。

2-3.usb-serial-for-androidを使用する手順

1.settings.gradle.kts(Project Settings)の設定
app→Gladle Scripts→settings.gradle.kts(Project Settings)のdependencyResolutionManagementのrepositories内に、
maven { url = uri(“https://jitpack.io”) }
を追加します。(下記6行目)

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

2.build.gradle.kts(Module:app)の設定
app→Gladle Scripts→build.gradle.kts(Module:app)のdependencies内に
implementation(“com.github.mik3y:usb-serial-for-android:3.8.1”)
を追加します。(下記7行目)

dependencies {

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("com.google.android.material:material:1.12.0")
    implementation("androidx.constraintlayout:constraintlayout:2.2.0")
    implementation("com.github.mik3y:usb-serial-for-android:3.8.1")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.2.1")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
3.AndroidManifest.xmlの設定
スマホのUSBにRaspberry Pi Pico Wが接続された場合に、今回作成したアプリの選択画面を表示するために、app→manifests→AndroidManifest.xmlに以下の記述を追加します。

<intent-filter>~</intent-filter>タグ内に
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
を追加します。(下記6行目)

また、
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
を以下の様に追加します。(下記10行目~12行目)
<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <meta-data
        android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
</activity>

4.device_filter.xmlの作製
Raspberry Pi Pico WのUSBのVendor IDは0x2e8a (11914)、Product IDは0xf00a (61450)です。
これをapp→res→xmlの下にdevice_filter.xmlと言う名称のxmlファイルを作成して、以下の様に記述することによって、スマホのUSBにRaspberry Pi Pico Wが接続された場合に、今回作成したアプリの選択画面を表示することができます。そのアプリ名称を選択しタップすると、アプリが起動されます。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--Raspberry Pi Pico W USB serial-->
    <usb-device vendor-id="11914" product-id="61450" /> <!--0x2e8a / 0xf00a: Pico W -->
</resources>

2-4.Kotlinプログラム概要

①USBシリアル通信の接続部分のソースコード

USBシリアル通信を接続する部分のKotlinのソースコードを以下に示します。

    //----USBシリアル通信接続の実施-------
    //Raspberry Pi Pico WのベンダーIDとプロダクトID
    val VENDOR_ID: Int = 0x2e8a     //11914
    val PRODUCT_ID: Int = 0xf00a    //61450
    //シリアルポート
    var port: UsbSerialPort? = null
    
    fun serialConnect(): Boolean {
        val usbManager: UsbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        var driver: UsbSerialDriver? = null
        for (dev in UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)) {
            val device: UsbDevice = dev.device
            //ベンダIDとプロダクトIDがPico Wと一致することを確認
            if ((device.vendorId == VENDOR_ID) && (device.productId == PRODUCT_ID)) {
                driver = dev
                break
            }
        }

        if (driver == null) {
            return false
        } else {
            if (usbManager.hasPermission(driver.device) == true) { //USB使用許可確認
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "USB使用パーミッションOK", Toast.LENGTH_LONG).show()
                }
            } else {
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "USB使用パーミッションNG", Toast.LENGTH_LONG).show()
                }
                return false
            }
        }

        val connection = usbManager.openDevice(driver!!.device)
        if (connection == null) {
            return false
        }

        port = driver!!.ports[0]
        port?.open(connection)
        port?.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
        port?.dtr = true    //Dtrをtrueにする
        port?.rts = true    //Rtsをtrueにする

        return true
    }

【説明】
3行目、4行目:
val VENDOR_ID: Int = 0x2e8a
val PRODUCT_ID: Int = 0xf00a
でRaspberry Pi Pico WのUSBのVendor IDとProduct IDを定義しています。

14行目、15行目:
device.vendorIdでUSB接続されているデバイスのVendor IDが得られます。
device.productIdでUSB接続されているデバイスのProduct IDが得られます。
Vendor IDとProduct IDがRaspberry Pi Pico Wのそれと一致する場合に、そのUSBシリアルドライバを選びます。

23行目:
if (usbManager.hasPermission(driver.device) == true) {
でUSBの使用許可が取れているかの確認をします。

前述したAndroidManifest.xmlとdevice_filter.xmlの記述がされている場合、スマホのUSBにRaspberry Pi Pico Wを接続された場合に、今回作成したアプリの選択画面が表示されます。
その時に、そのアプリ名称を選択しタップすると、アプリによるUSBの使用が許可された上でアプリが起動されますので、その手順を経た場合は、usbManager.hasPermission(driver.device)はtrueとなります。

何等かの状況でusbManager.hasPermission(driver.device)がfalseの場合は、USBの使用許可を要求する処理が必要ですが、簡略化のため今回のアプリでは無とします。

43行目、44行目:
port?.dtr = true
port?.rts = true
Raspberry Pi Pico Wとデータをやり取りするためには、USBシリアル通信ポートの信号線であるDtrとRtsをtrueにする必要があります。

②データの送信部分のソースコード

以下は、USBシリアル通信を使用して、例として文字列”CW\n”を送信する場合のソースコードです。

val data: String = "CW\n"
val byteData = data.toByteArray()  //バイト配列にする
port?.write(byteData, 1000)        //データ送信

【説明】
1行目、2行目:
val data: String = “CW\n”
val byteData = data.toByteArray()
で、文字列 “CW\n”を8bitのバイナリに変換してバイトアレイにします。

3行目:
port?.write(byteData, 1000)
でデータを送信します。第1引数は送信するデータのバイトアレイ、第2引数はオーバタイム時間(msec単位)です。

③データの受信部分のソースコード

以下は、USBシリアル通信を使用して、データを受信する部分のソースコードです。

val rcvBuffer: ByteArray = ByteArray(100)  //受信バッファ
val numBytes = port?.read(rcvBuffer, 20)

【説明】
2行目:
val numBytes = port?.read(rcvBuffer, 20)
でUSBシリアル通信ポートから受信データを呼び出します。readメソッドの第1引数は、受信データを格納するバイトアレイです。第2引数はオーバタイム時間(msec単位です)
numBytesは呼び出されたデータのバイト数です。

2-5.Kotlin全ソースコード

今回作成したアプリのKotlinの全ソースコードを以下に示します。データの受信部分は、スレッドで行っています。

このプログラムの動作は、アプリ画面の「接続」ボタンを押すとUSBシリアル通信が接続され、「CW」ボタンを押すとステッピングモータが時計方向に回転し、「CCW」ボタンを押すとステッピングモータが反時計方向に回転し、「切断」ボタンを押すとUSBシリアル通信が切断されます。

class MainActivity : AppCompatActivity() {
    //Raspberry Pi Pico WのベンダーIDとプロダクトID
    val VENDOR_ID: Int = 0x2e8a     //11914
    val PRODUCT_ID: Int = 0xf00a    //61450
    //シリアルポート
    var port: UsbSerialPort? = null
    //接続フラグ
    var connectFlag: Boolean = false
    //停止フラグ
    var stopFlag: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //-----押し釦スイッチ関係リスナ----------
        //CWボタンonTouchListenerを設定
        val cwButton = findViewById<Button>(R.id.cwButton)
        cwButton.setOnTouchListener(CwButtonOnTouchListener())
        //CCWボタンonTouchListenerを設定
        val ccwButton = findViewById<Button>(R.id.ccwButton)
        ccwButton.setOnTouchListener(CcwButtonOnTouchListener())
        //接続ボタンのonClickListenerを設定
        val connectButton = findViewById<Button>(R.id.connectButton)
        connectButton.setOnClickListener(ConnectButtonOnClickListener())
        //切断ボタンのonClickListenerを設定
        val disconnectButton = findViewById<Button>(R.id.disconnectButton)
        disconnectButton.setOnClickListener(DisconnectButtonOnClickListener())
    }

    //CWボタン押された時のonTouchListener
    private inner class CwButtonOnTouchListener : View.OnTouchListener {
        override fun onTouch(v: View?, event: MotionEvent?): Boolean {
            if (connectFlag == true) {   //接続中?
                if (event?.action == MotionEvent.ACTION_DOWN) { //ボタン押された
                    val data: String = "CW\n"
                    val byteData = data.toByteArray()      //バイト配列にする
                    port?.write(byteData, 1000)     //データ送信
                 } else if (event?.action == MotionEvent.ACTION_UP) {   //ボタン離された
                    val data: String = "STOP\n"
                    val byteData = data.toByteArray()      //バイト配列にする
                    port?.write(byteData, 1000)     //データ送信
                }
            }
            return true
        }
    }

    //CCWボタン押された時のonTouchListener
    private inner class CcwButtonOnTouchListener : View.OnTouchListener {
        override fun onTouch(v: View?, event: MotionEvent?): Boolean {
            if (connectFlag == true) {   //接続中?
                if (event?.action == MotionEvent.ACTION_DOWN) {     //ボタン押された
                    val data: String = "CCW\n"
                    val byteData = data.toByteArray()           //バイト配列にする
                    port?.write(byteData, 1000)         //データ送信
                } else if (event?.action == MotionEvent.ACTION_UP) {    //ボタン離された
                    val data: String = "STOP\n"
                    val byteData = data.toByteArray()       //バイト配列にする
                    port?.write(byteData, 1000)     //データ送信
                }
            }
            return true
        }
    }

    //接続ボタン押された時のonClickListener
    private inner class ConnectButtonOnClickListener : View.OnClickListener {
        override fun onClick(v: View?) {
            if (connectFlag == false) { //接続中?
                //シリアル通信接続と受信スレッドスタート
                stopFlag = false    //停止フラグクリア
                val connectThread = ConnectThread()
                connectThread.start()
            }
        }
    }

    //切断ボタン押された時のonClickListener
    private inner class DisconnectButtonOnClickListener : View.OnClickListener {
        override fun onClick(v: View?) {
            stopFlag = true     //停止フラグセット
        }
    }

    //接続と受信スレッド
    private inner class ConnectThread() : Thread() {
        private val RCV_BUF_SIZE: Int = 1024                        //受信バッファサイズ
        private val rcvBuffer: ByteArray = ByteArray(RCV_BUF_SIZE)  //受信バッファ
        private var rcvString: String? = null                       //受信データ文字列

        public override fun run() {
            serialConnect()     //USBシリアル通信接続の実施

            runOnUiThread {
               Toast.makeText(this@MainActivity, "USBシリアル通信接続完了", Toast.LENGTH_LONG).show()
            }

            connectFlag = true      //接続中フラグセット

            var numBytes: Int? = 0
            while (true) {
                try {
                    numBytes = port?.read(rcvBuffer, 20)
                }catch (e: IOException){
                    connectFlag = false //接続中フラグクリア
                    break
                }

                if (numBytes != 0) {
                    //受信データを文字列に変換する
                    var rcvData: String = String(rcvBuffer, 0, numBytes!!)

                    //前回までに受信したデータと結合
                    if (rcvString == null) {
                        rcvString = rcvData
                    } else {
                        rcvString = rcvString + rcvData
                    }

                    //ターミネータ"\r\n"検索
                    val termPos: Int = rcvString?.lastIndexOf("\r\n")!!
                    if (termPos != -1) {  //ターミネータ"\r\n"存在?
                        //"X:"を検索する
                        val XPos: Int = rcvString?.lastIndexOf("X:")!!

                        //"X:"が存在して、"\r\n"より前に存在
                        if ((XPos != -1) && (XPos < termPos)) {
                            //数値の部分を抜き出す
                            val posData: String = rcvString?.substring(XPos + 2, termPos)!!
                             runOnUiThread {     //UIスレッド
                                val posTextView = findViewById<TextView>(R.id.positionTextView)
                                posTextView.text = "X " + posData   //現在位置をTextViewに表示
                            }
                        }
                        //受信データストリングクリア
                        rcvString = null
                    }
                }

                if (connectFlag == false){  //接続中?
                    break
                }

                if (stopFlag == true){  //停止要求?
                    break
                }
            }

            port?.close()
            connectFlag = false     //接続中フラグクリア
        }
    }

    //----USBシリアル通信接続の実施-------
    fun serialConnect(): Boolean {
        val usbManager: UsbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        var driver: UsbSerialDriver? = null
        for (dev in UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)) {
            val device: UsbDevice = dev.device
            //ベンダIDとプロダクトIDがPico Wと一致することを確認
            if ((device.vendorId == VENDOR_ID) && (device.productId == PRODUCT_ID)) {
                driver = dev
                break
            }
        }

        if (driver == null) {
            return false
        } else {
            if (usbManager.hasPermission(driver.device) == true) { //USB使用許可確認
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "USB使用パーミッションOK", Toast.LENGTH_LONG).show()
                }
            } else {
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "USB使用パーミッションNG", Toast.LENGTH_LONG).show()
                }
                return false
            }
        }

        val connection = usbManager.openDevice(driver!!.device)
        if (connection == null) {
            return false
        }

        port = driver!!.ports[0]
        port?.open(connection)
        port?.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
        port?.dtr = true    //Dtrをtrueにする
        port?.rts = true    //Rtsをtrueにする

        return true
    }
}

2-6.アプリ画面のレイアウト

作製したアプリ画面のレイアウトを以下に示します。
アプリ画面のレイアウトは、Buttonを4個とTextViewを1個配置しました。表1に各Viewのidと役割に関して記載します。

名称種類id役割
CWボタンButtoncwButtonステッピングモータを時計回り方向に回転させる
CCWボタンButtonccwButtonステッピングモータを反時計回り方向に回転させる
接続ボタンButtonconnectButtonUSBシリアル通信を接続する
切断ボタンButtondisconnectButtonUSBシリアル通信を切断する
現在位置表示TextViewpositionTextViewステッピングモータの回転現在位置を表示する
表1.各Viewの役割とid

画面レイアウトに対応したXMLのコード(app→res→layout→activity_main.xml)を以下に示します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/positionTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="64dp"
        android:layout_marginTop="32dp"
        android:text="X 0"
        android:textSize="48sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/cwButton"
        android:layout_width="147dp"
        android:layout_height="74dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="32dp"
        android:text="CW"
        android:textSize="34sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/positionTextView" />

    <Button
        android:id="@+id/ccwButton"
        android:layout_width="147dp"
        android:layout_height="74dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="24dp"
        android:text="CCW"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/positionTextView" />

    <Button
        android:id="@+id/connectButton"
        android:layout_width="147dp"
        android:layout_height="74dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="80dp"
        android:text="接続"
        android:textSize="34sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/cwButton" />

    <Button
        android:id="@+id/disconnectButton"
        android:layout_width="147dp"
        android:layout_height="74dp"
        android:layout_marginTop="80dp"
        android:layout_marginEnd="24dp"
        android:text="切断"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ccwButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

3.実験装置に関して

今回作成した実験装置では、ステッピングモータは28BYJ-48とそのドライバーボードのセットを使用しました。このステッピングモータとドライバーボードは複数のネット通販から購入することが出来ます。
コントローラとしてはRaspberry Pi Pico Wを使用しました。(ステッピングモータ駆動の詳細に関してはこちらを参照して下さい)

28BYJ-48ステッピングモータとドライバ
28BYJ-48ステッピングモータ裏面
実験装置

今回使用したステッピングモータの主な仕様は表2の通りです。このモータは1/64のギアで減速されていて、1-2相励磁の場合にステップ角は5.625°/64なので4096ステップでモータ軸が1回転します。最大自起動周波数が500ppsなので、500パルス/sec以下で起動する必要があります。起動後は徐々に加速を行った場合に、最大で1000パルス/secまで仕様上は可能と思われます。
ただし、Arduino IDEのStepperライブラリーのstep()関数を使用した場合の励磁方式は2相励磁のようです。そのため2048ステップでモータ1回転となります。

項目仕様
相数4相
励磁方式1-2相励磁方式
ステップ角5.625°/64   (減速比1/64)
電圧5VDC
相抵抗22Ω ± 7% 25°C
最大応答周波数1000pps
最大自起動周波数500pps
引き込みトルク800gf.cm / 5VDC 400pps
表2.28BYJ-48ステッピングモータの主な仕様

4.実験装置回路図

図2に実験装置の回路図を示します。1個のステッピングモータをRaspberry Pi Pico Wに接続しています。

28BYJ-48ステッピングモータのドライバーボードはテキサスインスツルメンツのULN2003ANと言うダーリントントランジスタアレイが使用されています。ドライバーボード自体の資料が無かったので、テスタと基板のパターンを見て回路を調べました。

ステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP0~GP3に接続しました。

使用したスマホにはType micro-BのUSBコネクタが付いていますので、Type micro-BからType A (メス)への変換ケーブル(OTGケーブル)とType A (オス)とType micro-BのUSBケーブルを使用してスマホとRaspberry Pi Pico WをUSB接続しています。

図2.実験装置の回路図

5.Raspberry Pi Pico Wのプログラム

Raspberry Pi Pico W側のプログラムはArduino IDEを使用して、Arduino言語で作成しました。
これは以前「Raspberry Pi Pico Wを使用してステッピングモータをUSBシリアル通信で制御する実験」の記事で紹介しましたプログラムと同一です。詳細は以前の記事を参照して下さい。(以前の記事はこちら

概要は、USBシリアル通信で送られた以下コマンドデータに従ってステッピングモータが動作します。
 ”CW\n”  CW方向に回転
 ”CCW\n”  CCW方向に回転
 ”STOP\n”  回転停止

移動中は、USBシリアルポートに、ステッピングモータの回転現在位置を以下の形式で送信します。
X:「パルス位置」「ターミネータ(”r¥n”)」

“X:45\r\n”

ここではプログラム(スケッチ)のコードのみを示します。

//--Raspberry Pi Pico W シリアル通信JOG運転テストソフト-------------
#include <Stepper.h>

#define RPM_SPEED 10      //モータ回転速度(rpm)
#define STEPS 2048        //モータの1回転のステップ数

//軸の現在位置記憶用データ(符号付きステップ位置)
long current_pos = 0;

//経過時間判断用データ
unsigned long last_time = 0;       //前回の時間記憶
unsigned long delay_time = 0;      //処理待ち時間(次の処理をするまでの時間)

//シリアルデータ受信処理用データ
char rdBuf[100];        //受信データバッファ
int rdNo = 0;           //受信データ数
String s_rcvData;       //受信文字列

//回転指令フラグ
bool cw_mov_flag = false;   //CW回転指令フラグ
bool ccw_mov_flag = false;  //CCW回転指令フラグ

//ステッピングモータ初期設定
Stepper stepper(STEPS, 0, 2, 1, 3);//ステッピングモータ設定(1回転2048ステップ)

void setup() {
  Serial.begin(9600);         //USBシリアル通信初期化

  pinMode(0, OUTPUT);       //ステッピングモータ(青線)
  pinMode(1, OUTPUT);       //ステッピングモータ(桃線)
  pinMode(2, OUTPUT);       //ステッピングモータ(黄線)
  pinMode(3, OUTPUT);       //ステッピングモータ(橙線)

  //ステッピングモータ回転速度設定
  stepper.setSpeed(500);    //ステッピングモータの回転速度500rpm

  //処理待ち時間の設定
  delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
}

void loop() {
  //シリアルデータ受信処理
  bool res = readSerial();

  if (res == true){        //受信完了
    com_interpreter();     //受信コマンド解釈処理
  }

  //前回処理からの経過時間計算
  unsigned long pass_time;
  unsigned long now_time = micros();    //現在時刻読出し(usec単位)
  if (now_time >= last_time){
    pass_time = now_time - last_time;   //経過時間計算
  }else{
    //オーバフローを加味して経過時間計算
    pass_time = now_time - (0xffffffffUL - last_time + 1);
  }
  
  //モータ回転処理
  if (pass_time >= delay_time){     //時間到達?
    last_time = now_time;
    if (cw_mov_flag == true){       //CW回転?
      stepper.step(-1);             //-1ステップ回転
      current_pos--;                //現在位置マイナス

      //現在位置をPCへシリアル通信
      serial_pos();
    }
    else if (ccw_mov_flag == true){ //CCW回転?
      stepper.step(1);              //1ステップ回転
      current_pos++;                //現在位置プラス

      //現在位置をPCへシリアル通信
      serial_pos();
    }
  }
}

//現在位置をパソコンにシリアル通信
void serial_pos(){
  String text_x = String(current_pos);
  String text = String("X:" + text_x);
  Serial.println(text);     //現在位置をPCへ送信
}

//パソコンからの受信処理
//戻り値: ture 受信完了、false 受信未完了
bool readSerial()
{
  bool rdEndFlag = false; //受信完了フラグ

  int rNo = Serial.available();
  if (rNo > 0){          //受信データ有り
    //'\n'までの受信データを読み込む
    s_rcvData = Serial.readStringUntil('\n');
    rdEndFlag = true;   //受信完了フラグセット
  } else {
    rdEndFlag = false;  //受信完了フラグリセット
  }

  return rdEndFlag;
} 

//パソコンからの受信データコマンド解釈
void com_interpreter()
{
  if (s_rcvData.startsWith("CW") == true){        //"CW"?
    ccw_mov_flag = false;                         //ccw回転フラグリセット
    cw_mov_flag = true;                           //CW回転フラグセット
  }else if (s_rcvData.startsWith("CCW") == true){ //"CCW"?
    cw_mov_flag = false;                          //cw回転フラグリセット
    ccw_mov_flag = true;                          //CCW回転フラグセット
  }else if (s_rcvData.startsWith("STOP") == true){//"STOP"?
    cw_mov_flag = false;                          //cw回転フラグリセット
    ccw_mov_flag = false;                         //ccw回転フラグリセット
  }
}

6.最後に

今回作成したアプリでは、スマホとRaspberry Pi Pico WをUSBシリアル通信で接続して、ステッピングモータを制御しました。
Raspberry Pi Pico Wでは、Wi-FiやBluetoothが使用出来るので、スマホと通信するのにUSBを使用する必要性は少ないかもしれませんが、スマホをマイコンボードの操作機器として使用する方法の一つとしてトライしました。

尚、AndroidスマホとRaspberry Pi Pico WとのWi-Fi通信やBlutooth通信に関しては、以下の記事を参考にして下さい。
・スマホとPico WとのWi-Fi通信に関する記事はこちら
・スマホとPico WとのBluetooth通信に関する記事はこちら


PAGE TOP