GPSモジュールから来たデータを変数に格納する

前回のプログラムは、GPSモジュールから来た情報をそのままUSBのシリアルに送っていただけである。
ただの通信テストに用いる場合に用いる手段である。
実際にはGPSモジュールから読み込んだデータを加工する必要があるので、変数に代入しておかないとならない。
ここでトラブルケースが多いので記載しておく。
手法としてはいくつかあるので、一つのサンプルとして受け止めてもらえればありがたい。
事実、大会で実際に使用したプログラムとは手法が違う。なぜ違う手法を使ったのか考えてもらいたい。

さて、プログラムの説明である。

Loopの中でBufferを監視しており、Bufferに何か入ったらGPS_Readという関数を呼び出している。
関数の中では、改行文字(アスキーコード 0x0A:Line feed)を検出するまでWhileループしている。
ここで再度Bufferが空かどうか確認しているのは、Bufferが空な状態で読んでしまうと、-1(0xFF)を受信してしまうことを恐れている。
Bufferが空でなければ1文字読みだして返り値用の変数に順次1文字づつ格納している。
最後に改行文字(今回はLine Feed文字)が来た後に、0x0を書き込み文字列の終わりであることを示している。この0x0が入った以降は、ゴミが入っていても文字列としては無視されるので、わざわざ消す必要がない。
文字列に入った文字はシリアルモニタに出力している。
この後の必要部分の抜き出し等は純然なプログラミングの世界となるので、他のWebや文献を参考にしてほしい。

#include <SoftwareSerial.h> // Software Serial library

SoftwareSerial GPS_Serial(5,6); // 5:RXD, 6:TXD

void GPS_Read(char *SoftSerialBuffer){
   int i = 0; // Move first character position
   char read_ch;
   while (1) {
      if (GPS_Serial.available()) { // Check empty buffer
         read_ch = GPS_Serial.read(); // Read one character from buffer
         SoftSerialBuffer[i] = read_ch; // Copy reading character to return-variable
         if (read_ch == 10) break; 
         i++;
      }
   }
   SoftSerialBuffer[i] = '\0'; // Set end-of-string as '\0' 
}

void setup() {  //***** set serial port speed (bps)
   Serial.begin(115200); // USB 
   GPS_Serial.begin(9600); // Software Serial setting for GPS module
}

void loop() {
   char GPS_Data_Raw[255]; // Buffer size is 254 characters. 
   Serial.println("Loop"); // Eye catcher
   if (GPS_Serial.available()) { // Check empty buffer
      GPS_Read(GPS_Data_Raw); // Call GPS_Read function routine
      Serial.println(GPS_Data_Raw);
   }
}

 

GPSモジュールとArduinoとの通信

Arduino UNOにはシリアル通信用のピンが1セット準備されているのだが、これは、シリアル通信変換回路を通じてUSBコネクタに通じているようである。
余談になるが、2017年の大会中の出来事で、デバッグ中にUSBケーブルをつけたままロボットカーが走り出してしまいUSBポートを物理的に破壊してしまった。(Arduino Unoではないが。)USBポートの破壊による回路欠損はその場ではんだごてを使って応急処置ができたものの、USBコネクタは使い物にならなくなってしまった。その時は、パソコンとシリアル端子を接続することで無事にダウンロードをすることができ九死に一生を得た。

UNOの場合は、シリアル通信が1セットだけである。しかし、実際のアプリ作成には、Arduino IDEのシリアルモニタでプログラムの動作を確認しながらGPSモジュールの通信を行うために、複数のシリアル通信を用いる必要がある。
複数のシリアル端子を持っているArduino MEGAを使うという手もあるが、Arduinoは入出力に使う端子を利用してシリアル通信を利用できる。
ソフトウェアシリアルと呼ばれており、ライブラリが準備されている。

作成したプログラムのデバッグ用にはUSB経由のシリアル通信を用い、GPSモジュールとの通信にはソフトウェアシリアルを用いるということである。下記はArduinoとGPSモジュールを接続してArduino IDEのシリアルモニタで観測した様子である。

配線は、Arduino UNIの5V,GNDと5番、6番の各ピンを使用した。Arduino IDEのスケッチは下記のように組んでみた。ソフトウェアシリアルを組み込み、GPS_Serialと名前をつけて、5番、6番のピンを指定した。
その後、シリアルモニタ用には115200kbpsの通信速度を、GPSモジュール用のGPS_Serialには9600bpsの通信速度を定義した。プログラムとしては、ただ単にソフトウェアシリアルのバッファにたまった電文を読み(read)、USBを経由するシリアルに書き込んで(write)しているだけである。
これでとりあえずGPSモジュールからの信号もArduinoの中に取り入れたことになる。

#include <SoftwareSerial.h> // Software Serial library

SoftwareSerial GPS_Serial(5,6); // 5:RXD, 6:TXD

void setup() {
 //***** set serial port speed (bps)
 Serial.begin(115200); // USB
 GPS_Serial.begin(9600); // Software Serial setting for GPS module
}

void loop() {
 if (GPS_Serial.available()) {
   Serial.write(GPS_Serial.read());
 }
}


ステアリングサーボを動かす

ステアリングサーボはスピードコントローラと同じなのでプログラムの変更はほとんどないので、細かい説明は省略する。

サーボを動作させるためのSteering_Valの値は、以下のとおりである。
50:ステアリングを左に切った状態
90:中立
130:ステアリングを右に切った状態

また、シリアルモニタからの入力で3,4を入力することにより、Steering_Valの値が上下するようになっている。

#include <Servo.h> // Built-in Servo library
Servo servo[2]; // create servo object for speed controller and steering servo
int SerialInputValue; // Inputed value from serial monitor for debug

void setup() {

//***** set serial port speed (bps)
 Serial.begin(115200);

//***** set servo pin servo[0]..servo[1]
 servo[0].attach(9); // Pin 9 is for Stering Servo
 servo[1].attach(10); // Pin 10 is for Speed Controller

}

void loop() {
 static int Steering_Val = 90; // 50-90-130 Steering value (90:center)
 static int Motor_Val = 94; // 70-92 Speeding Value (70 is fast)

 SerialRead(); // read inputted numeric key
 if (SerialInputValue ==3 && Steering_Val >=52){ 
 Steering_Val -= 2; // '3' is inputted from serial monitor
 }
 if (SerialInputValue ==4 && Steering_Val <=128){
 Steering_Val += 2; // '4' is inputted from serial monitor
 }
 if (SerialInputValue ==1 && Motor_Val >=72){ //73
 Motor_Val -= 2; // '1' is inputted from serial monitor
 }
 if (SerialInputValue ==2 && Motor_Val <=100){ //83
 Motor_Val += 2; // '2' is inputted from serial monitor
 }

 servo[0].write(Steering_Val); // Servo output (Steering)
 Serial.print("Steering Value=");
 Serial.print(Steering_Val);
 
 servo[1].write(Motor_Val); // Servo output (Speeding)
 Serial.print(" Motor Speed=");
 Serial.println(Motor_Val);
}

下記は実験しているときの写真である。
ArduinoからはGNDと9Pin,10Pinの③本だけが出ている。それらがスピードコントローラやステアリングサーボと接続されている。

配線図としては以下となる。

スピードコントローラに与える値

スピードコントローラに与える値はどうすればよいか?
具体的には、
servo[1].write(Motor_Val);
のMotor_Valの値である。 各々実験してもらうことを前提に、このモータとスピードコントローラのケースを記載しておく。

69以下 最高速
70-92 スピードが段階的に変化(70側が高速、90側が低速)
93-174 停止
175 以上 最高速 ←正しく制御できていない

Arduinoでスピードコントローラ経由のモータを動かす

スピードコントローラやステアリングサーボはON/OFFの信号の比率で制御できる。PWM(Pulse Width Modulation:パルス幅変調)方式である。
今回の開発環境であるArduino IDEにはサーボ制御用のライブラリ(Servo)が用意されているので、これを用いるのが簡単である。

Arduinoのスケッチを抜き出してみると、準備として以下の定義が必要である。

#include <Servo.h> // Built-in Servo library
Servo servo[2]; // create servo object for speed controller and steering servo

その後Setup()内でピンの指定を行う。

servo[0].attach(9); // Pin 9 is for Stering Servo
servo[1].attach(10); // Pin 10 is for Speed Controller

Arduino UNOではPWMで使用できるピンが決まっているので、そのピンの番号を記載する必要がある。
この例ではスピードコントローラには10ピン、ステアリングサーボには9ピンを割り付けた。

ピンに出力を出すのは
servo[1].write(Motor_Val)
という記載を行う。 ここではMotor_Valという変数にスピードに相当する値を入れる。

実際に入れる数値は、スピードコントローラによって微妙に差があるので実機に合わせてテストするとよい。

ここで全く違う話になってしまうのだが、実験する過程では、シリアルモニタで数値を上下しながらテストできるようにする必要がある。
そこで、前出のデバック手法を用いる。 さらに、開発環境のIDE上で違うタブに分けた例とした。

下記はスピードコントローラのタブ部分(Speedcontroller.iso)

#include <Servo.h> // Built-in Servo library
Servo servo[2]; // create servo object for speed controller and steering servo
int SerialInputValue; // Inputed value from serial monitor for debug

void setup() {

//***** set serial port speed (bps)
 Serial.begin(115200);

//***** set servo pin servo[0]..servo[1]
 servo[0].attach(9); // Pin 9 is for Stering Servo
 servo[1].attach(10); // Pin 10 is for Speed Controller

}

void loop() {
 static int Steering_Val = 90; // 70-115 Steering value (90:center)
 static int Motor_Val = 94; // 70-92 Speeding Value (70 is fast)

 SerialRead(); // read inputted numeric key
 if (SerialInputValue ==1 && Motor_Val >=72){ //73
 Motor_Val -= 2; // '1' is inputted from serial monitor
 }
 if (SerialInputValue ==2 && Motor_Val <=100){ //83
 Motor_Val += 2; // '2' is inputted from serial monitor
 }
 servo[1].write(Motor_Val); // Servo output
 Serial.print("Motor Speed=");
 Serial.println(Motor_Val);
}

下記はスピードコントローラのタブ部分(SerialMonitor.iso)

void SerialRead(){
 int InValue;
 InValue = Serial.read(); // Read one character
 if(48 <= InValue and InValue <=57){ // Check between '0' to '9' by ascii code 
 SerialInputValue = InValue-48; // Calculate inputted numeric key
 } else SerialInputValue = -1; // Other keys or no input
}

IDEの表示は以下のようになっている。

スケッチのタブを分ける方法

これからスケッチを作る過程において関数などが増えることになるので、ここでスケッチをタブで分ける方法をメモしておく。
Arduino IDEの右の矢印のところをクリックするとプルダウンメニューにある”新規タブ”をクリックすればよい。

デバッグ方法について

Arduinoを使って何かを制御するときには、制御対象を見ながらプログラムを調整する必要が出てくる。 そこで使うのはシリアルモニタである。シリアルモニタにプログラムの変数を表示しながら調整するのだが、プログラムの変数の値も変更する必要が出てくる。

ここでは、シリアルモニタを利用して変数値を変更する手段の一例を示す。
最低限GPSロボットカーを動かすためには、スピードコントローラへの指示値やステアリングサーボへの指示値の実験に使う想定している。

void setup() {
   // set serial port speed (bps)
   Serial.begin(115200);

   // put your setup code here, to run once:
   pinMode(LED_BUILTIN, OUTPUT); // LED_BUILTINはArduinoにおける定数

}

void loop() {
   static int DelayTime= 500; // Default delay time (ms)
   int InValue; // シリアル通信で読み込んだ1文字のアスキーコードが入る。
   InValue = Serial.read(); // シリアル通信から読み込み

   if(InValue == 49 && DelayTime <= 900 ){ 
      DelayTime += 100;
   }
   if(InValue == 50 && DelayTime >= 100 ){
      DelayTime -= 100;
   }
   /*
   数字の1を入力するとアスキーコードの49が入る。
   数字の2を入力するとアスキーコードの50が入る。
   (900と100はdelay timeの上下限チェックである。)
   */

   Serial.print("InValue="); // Display data
   Serial.print(InValue);
   Serial.print(" DelayTime=");
   Serial.println(DelayTime);

   digitalWrite(LED_BUILTIN, HIGH); // LED control
   delay(DelayTime);
   digitalWrite(LED_BUILTIN, LOW);
   delay(DelayTime);
}

 

押しボタンスイッチ(2)

デジタル入力には大きく分けて二種類ある。
一つはON/OFFの状態を見るためのステータス入力。
もう一つは状態のONになった瞬間やOFFになった瞬間を見るためのエッジ入力である。
(入力の名称はここで勝手につけている。)

スタートスイッチなどは、操作者の手で押す可能性があり、
確実にスイッチを押すために、長めに押す場合がある。
通常の入力でロジックを作成してしまうと、
ロジックが2回スタートを検知してしまう場合がある。
もちろん、連続して2回検知しても問題のないロジックであれば問題ない。
問題のあるケースの一例として、スタートボタンが押されたときに、
自動的に過去情報を修正してキャリブレーションしているようなケースだと、
2回スタートボタンが押されると過去情報がなくなってしまったりする。
(ちょっと‏事例を作るために強引な説明だが (-_-;))

今回のスイッチは、OFFの状態で1となり、ONになったときに0となる。
ロジック上は1→0になったときに1周期だけONにするファンクションが望ましい。

ロジックはそんなに難しくなく、前回が1で今回が0となる関数 F_TRIGを
作成すればよい。(falling-edge trigger)

F_TRIGの中で変数にstaticで宣言しているが、
これは呼ばれる毎に変数がクリアされないようにしている。
ただ、このロジックには欠点があり、複数個所から呼ばれると正しく動作しない。
この辺りは、いくつか解決策があるので、工夫してみてほしい。

—-スケッチの例

const int START_SW_PIN_NO = 4;

void setup() {

   //***** set serial port speed (bps)
   Serial.begin(115200);

   //***** set pin mode
   pinMode(START_SW_PIN_NO, INPUT_PULLUP); // Enable pullup for input mode.
}

void loop() {
   int start_sw, trig;
   start_sw=digitalRead(START_SW_PIN_NO);
   trig=F_TRIG(start_sw);
   Serial.print("Start Switch =");
   Serial.print(start_sw);
   Serial.print(" Trigger=");
   Serial.println(trig);
   delay(200);
}

int F_TRIG(int X){
   int Q;
   static int lastX;
   if(X==0 && lastX==1){
      Q=1;
   }else{
      Q=0;
   }
   lastX=X;
   return Q;
}

 

押しボタンスイッチ(1)

ロボットカーで必須となる素材の一つは押しボタンである。
Arduinoではピンのモードを指定して用いるが、
多くの資料ではデジタル入力としてINPUTというモードを使用している。
しかし、目的がスイッチの入力を見るだけのケースが多いので、
その場合は、INPUT_PULLUPのモードで省配線できる場合がある。

 

この事例の場合は、以下の挙動となる。
スイッチ オフの場合:start_sw=1
スイッチ オンの場合:start_sw=0
—-スケッチの例

const int START_SW_PIN_NO = 4;

void setup() {
   //***** set serial port speed (bps)
   Serial.begin(115200);

   //***** set pin mode
   pinMode(START_SW_PIN_NO, INPUT_PULLUP); // Enable pullup for input mode.
}

void loop() {
   int start_sw;
   start_sw=digitalRead(START_SW_PIN_NO);
   Serial.print("Start Switch =");
   Serial.println(start_sw);
}

 

開発環境とLED点滅

開発環境例
・Windows 10 Home
・Arduino UNO
・Arduino IDE 1.8.1

Arduinoの基本的な使い方はたくさん紹介があるので、ここでは省略。

以下は、ボード上のLEDをチカチカさせる例

/// スケッチの例 LED
 void setup() {
   // put your setup code here, to run once:
   pinMode(LED_BUILTIN, OUTPUT);
   // LED_BUILTINはArduinoにおける定数
 }

void loop() {
   // put your main code here, to run repeatedly:
   digitalWrite(LED_BUILTIN, HIGH);
   delay(1000); // delay 1000 ms
   digitalWrite(LED_BUILTIN, LOW);
   delay(1000);
 }