techno_memo

個人用の技術メモ。python・ROS・AI系のソフトウェア・ツールなどの情報を記載

ArduinoとPCの通信(シリアル通信)

この記事の目的

 ArduinoとPC間でのシリアル通信についてまとめる

1.Arduinoのシリアル通信機能

ArduinoでPCと信号の送受信を行う場合、USBケーブルを使ったシリアル通信が最も便利な手法である。

Arduino標準のSerialライブラリを使い下記の関数で信号の受け渡しを行うことができる。

参考サイト https://garretlab.web.fc2.com/arduino_reference/language/functions/communication/serial/

API リンク 概要
Serial.begin() リンク シリアル通信の初期化/bpsの指定
Serial.end() リンク シリアル通信の終了
Serial.setTimeout() リンク シリアル通信タイムアウト時間の指定
Serial.readStringUntil() リンク 指定した終端文字まで文字列を読み込む
Serial.println() リンク 指定した文字列を出力する

通信の速度はbpsで選択する。(300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 など。送受信に必要な周期とデータ量を考慮して設定する。)

上記の表で挙げたのは送受信するデータは文字列であることを想定している。数値データは文字列に変換しカンマ区切りのデータで一括して送信することができる。下記にサンプルコードを示す。下記はデータの桁数をあらかじめ定めておき、終端が区切り文字";"で出力されるデータの送受信をすることを想定している。

/* serial_com_test.cpp */

/* header file */
#include "stdafx.h"
#include "serial_com_test.h"

/* static variable */
static int apl_cyc_count = 0;

#define READ_BUFSIZE  (7)   //受信バッファサイズ x.x,x.x; を想定(終端文字は含まない)
#define SEND_BUFSIZE  (50)  //送信用バッファサイズ
#define SEND_DATA_SIZE (6)  //整数桁3 小数点1 小数点以下2 を想定

//デバッグ用変数
static float deb_val1 = 0.0;
static float deb_val2 = 0.0;
static int keta = 0;

//通信の初期化用関数
void com_init (void)
{
     //Serial Communication initialization
    Serial.begin(115200);      //boudrate 115200
    Serial.setTimeout(50);      //read timeout for serial communication
    Serial.print("Serial Communication Start!");
    return;
}

//シリアル通信読み込み用関数
void com_read (void)
{
    char buff_A[4];
    char buff_B[4];
    if(Serial.available())  //読み込むデータが存在する場合
    {
        //終端データ";"までのすべてのデータを読み込む
        String str_buf = Serial.readStringUntil(';');
        keta = str_buf.length();
        if (str_buf.length() == READ_BUFSIZE )
        {
            str_buf.toCharArray(buff_A,4);
            deb_val1 = atoi(buff_A);
            Serial.println(deb_val1);

            str_buf.toCharArray(buff_B,4,4);  //先頭バイトの指定
            deb_val2 = atof(buff_B);
            Serial.println(deb_val2);
        }
        Serial.println(buff_A);
        Serial.println(buff_B);
    }
    return;
}

//シリアル通信読み込み用関数
void com_send (void)
{
    float deb_send_val1;
    float deb_send_val2;

    char test_str[SEND_BUFSIZE]={'\0'}; //配列を初期化用データで埋める
    char *ptr = test_str;

    deb_send_val1 = deb_val1 + 10.0;
    deb_send_val2 = deb_val2 - deb_val1;

    /*Send Acceralation information*/
    dtostrf(deb_send_val1,SEND_DATA_SIZE,2,ptr);
    ptr = ptr + SEND_DATA_SIZE;
    *ptr=',';  //区切り文字_token 
    ptr = ptr + 1;
    dtostrf(deb_send_val2,SEND_DATA_SIZE,2,ptr);
    ptr = ptr + SEND_DATA_SIZE;
    *ptr=';';  //end_token 
    Serial.println(test_str);
    return;
}

/*arduino 初期化処理*/
void setup()
{
    /*Initialize Communication */
    com_init();
}

/*arduinoメインループ*/
void loop()
{
     //50msec 周期実行
    interval<5000>::run([]{
        switch (apl_cyc_count)
        {
            case 0:
                com_read();
                break;
            case 1:
                //アクチュエータの制御などを実施(仮)
                break;        
            case 2:
                com_send();;
                break;
            default:
                //no action
                //Serial.println("Default");
                break; 
        }
        if (apl_cyc_count>=2)
        {
            apl_cyc_count = 0;
        }
        else
        {
            apl_cyc_count++;
        }
    });
}

 上記スクリプトで送受信した結果はArduino IDE / Teraterm などで確認することができる。

ここで、 PCのPythonスクリプトArduinoを通信させたい場合はライブラリ『Pyserial』を利用すると便利である。

Pyserialは下記コマンドでインストールすることができる。

 pip install pyserial

Pyserialではポート名とボーレート、タイムアウト時間を指定して関数readlineでバッファ読み込み、writeでバッファ書き込みを行う。

読み込みについては、通信状態によって不安定になることがあるため区切り文字までの文字数を明示的にしておくほうが良い。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import serial
import threading
import time
import re

###########################################
###  Serial Socket Communication Class  ###
###########################################
class Class_Serial_Socket(threading.Thread):

    ###################################
    #Connection information ##########
    COM_PORT_NAME = 'COM4'           #for Windows
    #COM_PORT_NAME = '/dev/ttyACM0'  #for Ubuntu
    BAURATE = 115200

    #############################################
    ###  Serial Communication Initialization  ###
    #############################################
    def __init__(self):
        #Initialize value for threading
        threading.Thread.__init__(self)
        self.terminate_request = False

        #Vehicle Control Signals sending to Arduino
        self.debval1 = 0.0  #initialize debag value 1
        self.debval2 = 0.0  #initialize debag value 2

        self.counter = 1.0;

        #initialize serial communication socket information
        self.ser_sock = serial.Serial(self.COM_PORT_NAME,self.BAURATE, timeout=0.01, writeTimeout=0.01)
        self.send_flg = True
        self.RECEIVE_LENGTH = 16;

        time.sleep(3)

    ####################################
    ###  Update RX data from Arduino ###
    ####################################
    def update_RX_data_from_arduino(self,str_b):
        try:
            str = str_b.decode()                 #Arduinoから受信した文字列のデコード
            #str_splitted =str.split(',')
            if (len(str) == self.RECEIVE_LENGTH):
                str_splitted = re.split('[,;]', str) 
                self.debval1 = float(str_splitted[0])      #1つ目の数字の取得
                print('debval1',self.debval1)
                self.debval2 = float(str_splitted[1])      #2つ目の数字の取得
                print('debval2',self.debval2)
        except:
            pass

    ##################################
    ###  Update TX data to Arduino ###
    ##################################
    def update_TX_buffer_to_arduino(self):
        #Generate sending buffer to Arduino
        #debug
        send_buffer = str(self.counter + 2.0)
        tempbuf = str(self.counter)
        send_buffer = send_buffer + "," + tempbuf
        send_buffer = send_buffer + ";" #for charactor end token
        self.counter = self.counter + 1.0
        if self.counter > 5.0:
            self.counter = 1.0
        try:
            self.ser_sock.write(str.encode(send_buffer))
        except Exception as e:
            print("例外args:", e.args)

        #    pass
            #print("write_error\n")

    ###############################
    ###  Main Periodic Function ###
    ###############################
    def run(self):
        while(1):
            if self.terminate_request == True:
                break;  #Finish  (User request)
            if self.send_flg == True:
                #try:
                #    self.update_TX_buffer_to_arduino()
                #except:
                #    print("Serial Communication Error (PC->Arduino)")
                self.update_TX_buffer_to_arduino()
                self.send_flg = False
            else:
                str = ""
                try:
                    str = self.ser_sock.readline()
                    self.update_RX_data_from_arduino(str)
                except:
                    print("Serial Communication Error (Arduino->PC)")
                self.send_flg = True
            time.sleep(0.050)

if __name__ == '__main__':
        sock_controler = Class_Serial_Socket()                 #Initialize Serial Communication
        sock_controler.start()

サンプルコードはgithubで公開している。

github.com

github.com

次回はROS TopicをArduinoと送受信する方法について説明する。