はじめに
はじめまして。とある大学でUnityを使用した福祉関係の研究をしている学部4年生です。自分の備忘録的にまとめたものが誰かの役に少しでも立てばと思い、今回初めて投稿いたします。つたない文章になると思いますが、ご覧いただけると幸いです!
目的
Arduinoに接続したロータリーエンコーダをコントローラとして使用し、回転数をプレイヤーの移動に反映させることが今回の最終目的ですが、今回は試験として、ArduinoとPythonを使用して入力データのCSV出力まで行いたいと思います。
エンコーダ
今回は、Arduino互換のスターターキットに入っていたKY-040というロータリーエンコーダを使用します。
エンコーダの仕様
動作電圧:5V
パルスサークル:20
GND:接地端子
"+":5V電源
CLK:エンコーダピンA
DT:エンコーダーピンB
SW:Selectスイッチ(シャフトを押し込むと動作)
ソースコード
色々な参考サイトに転がっているロータリーエンコーダの値読み取りを行っているスクリプトを試してみたところ、ゆっくりだと正しく動作をするが、少し早めにエンコーダを回してみると値が変更されなかったり、うまくいかないことが多かったのですが、原因はシリアル通信とエンコーダの値を毎回読み取る処理の重たさが原因であったと考えられます。
今回は、丸ちゃんの技術ノート様(https://maruchan-note.com/2022/07/01/20220701/)
を参考にさせてもらいながらソースコードを記述しました。
下記のように割り込み処理で値の変更をするattachInterrupt関数を使用したエンコーダの値の取得するスクリプトに変更したところ、値が飛んだりすることなく正確に取得することができるようになりました。
const int pinA = 3; // KY-040のCLKにつなぐ(割り込みPin)
const int pinB = 4; // KY-040のDTにつなぐ
int encoderPosCount = 0; //カウント数
unsigned long time = 0;
void setup() {
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
attachInterrupt(digitalPinToInterrupt(pinA), pulseCounter, CHANGE); // pinAの値が変化したときに割り込み処理を実行する
Serial.begin(9600);
}
void loop() {
time = millis();
Serial.print(time);
Serial.print(" , ");
Serial.println(encoderPosCount);
}
void pulseCounter() {
if (digitalRead(pinA) ^ digitalRead(pinB)) //排他的論理和
{
encoderPosCount++; // カウンターを加算
} else {
encoderPosCount--; // カウンターを減算
}
}
データの取得
データはシリアル通信経由でプログラム開始時からの時間[ミリ秒]とカウンタの値を下記のような形で出力し、その値をpython経由でCSVファイルに書き込む処理を行っています。
Serial.print(time);
Serial.print(" , ");
Serial.println(encoderPosCount);
//シリアルモニタには " 0 , 0 " の形で表示されています
Pythonスクリプトの実装の流れ
モジュールのインストール
まずは必要なモジュールをインストールしましょう。
- pyserial:シリアル通信用のモジュール
- csv:CSV形式のファイルを扱うためのモジュール(Python標準で入っているのでインストール不要です)
import serial
import csv
try:
# Create a CSV file
csv_file = open('data.csv', 'w', newline='')
print("CSV file created")
csv_writer = csv.writer(csv_file)
# Open the serial port
serial_port = 'COM3'
ser = serial.Serial(serial_port, 9600)
print(f"Serial port {serial_port} opened")
# Read data from the serial port and write it to the CSV file
while True:
data = ser.readline().decode().strip() # Read a line of data from the serial port
if data:
print(f"Data read from serial port: {data}")
csv_writer.writerow([data])
print(f"Data written to CSV: {data}")
csv_file.flush() # Flush the CSV file to write the data to disk
except serial.SerialException as e:
print(f"Error opening serial port: {e}")
except Exception as e:
print(f"An error occurred: {e}")
finally:
# Close the CSV file and serial port
if 'csv_file' in locals():
csv_file.close()
print("CSV file closed")
if 'ser' in locals():
ser.close()
print("Serial port closed")
コードの説明
-
モジュールのインポート:
import serial import csv
serial
モジュールとcsv
モジュールをインポートします。 -
CSVファイルの作成:
csv_file = open('data.csv', 'w', newline='') print("CSV file created") csv_writer = csv.writer(csv_file)
data.csv
という名前のCSVファイルを作成し、書き込みモードで開きます。csv.writer
を使ってCSVファイルに書き込むためのオブジェクトを作成します。 -
シリアルポートのオープン:
serial_port = 'COM3' ser = serial.Serial(serial_port, 9600) print(f"Serial port {serial_port} opened")
COM3
ポートを9600ボーで開く設定になっています。serial.Serial
を使ってシリアルポートを開きます。 -
データの読み取りとCSVファイルへの書き込み:
# Read data from the serial port and write it to the CSV file while True: data = ser.readline().decode().strip() # Read a line of data from the serial port if data: print(f"Data read from serial port: {data}") csv_writer.writerow([data]) print(f"Data written to CSV: {data}") csv_file.flush() # Flush the CSV file to write the data to disk
while
ループ内でシリアルポートからデータを読み取り、それをデコードして余分な空白を取り除きます。データが存在する場合、そのデータをCSVファイルに書き込みます。 -
例外処理:
except serial.SerialException as e: print(f"Error opening serial port: {e}") except Exception as e: print(f"An error occurred: {e}")
シリアルポートを開く際に発生したエラーやその他の例外をキャッチし、それぞれのエラーメッセージを表示します。
-
リソースのクリーンアップ:
finally: # Close the CSV file and serial port if 'csv_file' in locals(): csv_file.close() print("CSV file closed") if 'ser' in locals(): ser.close() print("Serial port closed")
finally
ブロックでは、CSVファイルとシリアルポートを閉じます。locals()
関数を使って、csv_file
とser
がローカルスコープに存在するかどうかを確認し、それぞれを閉じる処理を行います。これにより、リソースのリークを防ぐことができます。
注意
ArduinoIDEなどでシリアルモニタを開いている場合、Pythonのスクリプトがシリアルポートを開くことができず、エラーになってしまうので、必ずPythonスクリプトの実行前にシリアルモニタを閉じるようにしましょう。
おわりに
いかがだったでしょうか?今回はArduinoに接続されたロータリーエンコーダの値をシリアル通信で読み取りCSV形式のデータに出力する方法をご紹介しました。
エンコーダの種類はほかにもいろいろありますが、今回のスクリプトの応用で色々なデータをarduinoから送ることができますので、試してみると面白いかもしれませんね。
最後までご覧いただきありがとうございました。