masayasanのアイコン画像

プログラミング教育用 ブロック搬送機

masayasan 2021年04月30日に作成  (2021年04月30日に更新)

デモ動画

ここに動画が表示されます

装置概要

製造現場で使用されているロボットアーム及び搬送コンベアのミニチュアを用いて、シーケンス制御について学べる装置を制作した。

ロボットアームのモーション制御はArduino_Nanoで行い、コンベア制御、及びロボットアームの起動タイミングはObniz 1Yで行った。

シーケンス制御内容

ロボットアーム制御

ロボットアームが起動する前提条件として起動許可信号がONしていることが必須となる。
設備の異常などでロボットを停止しておきたい時は、起動許可信号をOFFにしておく。

起動許可信号がONしている状態では、ロボットは設定された位置を保持しようとする。
設定位置を現在位置と異なる値にすることでロボットは設定位値へ移動する。

コンベア制御

コンベアの制御は出発地点にブロックが置かれたら起動を開始し、最終地点に運ばれたら起動を停止する。

使用部品

  • Arduino Nano(互換品)・・・秋月電子
  • Obniz 1Y
  • [I2C接続小型キャラクタLCDモジュール] AE-AQM1602A(KIT)・・・秋月電子
  • [サーボモータ] Quimat QKY66・・・Amazon
  • ユニバーサルプレート・・・タミヤ工作
  • トラック&ホイールセット・・・タミヤ工作
  • プラ材5mm角棒・・・タミヤ工作
  • 3mm赤外線LED940nm・・・秋月電子
  • 3mmフォトレジスタ940nm・・・秋月電子
  • パーラービーズ・・・カワダ
  • 3・2mmビス、ナット
  • 電線

回路図

ロボットモーション製作用回路
ロボットモーションを作成するため、リモコンを接続し操作する。
サーボ角度を随時モニター出来る様に、I2C式のキャラクタディスプレイで現在角度を表示する。

Obniz接続回路
ロボットモーションはArduino側で予め作成しておき、Obniz側で起動信号とロボットモーションの番号を切り替え信号だけで動作出来る様にする。
ObnizはDCモーターを直接駆動させることが出来るので、モータードライブは使用せずにObnizの0,1ピンに直接DCモーターを配線する。

プログラミングコード

ロボットモーション

#include<Servo.h> //#define DEBUG int MODE = 1; #include "I2CLiquidCrystal.h" #define I2CADDR_LCD (0x3E) // AQM1602XAのI2Cアドレスは0x3E I2CLiquidCrystal g_i2clcd(I2CADDR_LCD, 4, 5); // i2c_addr, sda, scl //11,5:I2CLCD Servo RB1_1servo; Servo RB1_2servo; Servo RB1_3servo; Servo RB1_4servo; Servo RB2_1servo; Servo RB2_2servo; int pos1_1 = 90; int pos1_2 = 90; int pos1_3 = 0; int pos1_4 = 100; int pos2_1 = 90; int pos2_2 = 90; int pos1[9][4] = {{90, 90, 0, 100}, {90, 20, 50, 100}, {90, 0, 0, 100}, {90, 0, 0, 10}, {90, 30, 0, 10}, {38, 20, 0, 10}, {38, 0, 0, 100}, {38, 0, 50, 100}, {38, 100, 50, 100} }; //原位置、p1'、p2、ブロックキャッチ、p3、p4、開放、退避、p5' int pos2[9][2] = {{90, 90}, {30, 20}, {30, 0}, {50, 20}, {50, 180}, {0, 180}, {110, 20}, {110, 80}, {170, 80}}; //原位置(p1)、p1'、受け取り(p2)、p2'、p3、廃棄(p3')、p4、p5、受け渡し(p5') int NUM1 = 0; int NUM2 = 0; int SPEED = 5; bool DIRECTION = 0; void setup() { Serial.begin(9600); pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(A0, INPUT);//RB1 運転許可 pinMode(A1, INPUT);//RB1 次動作支持 pinMode(A2, INPUT);//RB1 原位置復帰 pinMode(A3, INPUT_PULLUP); pinMode(10, INPUT);//RB2 運転許可 pinMode(11, INPUT);//RB2 次動作指示 pinMode(12, INPUT);//RB2 原位置復帰 g_i2clcd.begin(); g_i2clcd.clear(); // LCD画面消去 RB1_1servo.attach(2); RB1_2servo.attach(3); RB1_3servo.attach(4); RB1_4servo.attach(5); RB2_1servo.attach(6); RB2_2servo.attach(7); pinMode(13, OUTPUT); RB1_1servo.write(pos1_1); RB1_2servo.write(pos1_2); RB1_3servo.write(pos1_3); RB1_4servo.write(pos1_4); RB2_1servo.write(pos2_1); RB2_2servo.write(pos2_2); } void loop() { digitalWrite(8, LOW); //RB1動作停止中 digitalWrite(9, LOW); //RB2動作停止中 LCD(); #ifdef DEBUG debug(); #else RB1MOVE(); RB2MOVE(); #endif } void LCD() { Serial.print("ph"); Serial.println(digitalRead(A3)); Serial.print("{"); Serial.print(pos1[NUM1][0]); Serial.print(","); Serial.print(pos1[NUM1][1]); Serial.print(","); Serial.print(pos1[NUM1][2]); Serial.print(","); Serial.print(pos1[NUM1][3]); Serial.print("}"); Serial.print("{"); Serial.print(pos1_1); Serial.print(","); Serial.print(pos1_2); Serial.print(","); Serial.print(pos1_3); Serial.print(","); Serial.print(pos1_4); Serial.println("}"); g_i2clcd.setCursor( 0, 0 ); g_i2clcd.print("{"); g_i2clcd.print(pos1_1); g_i2clcd.print(","); g_i2clcd.print(pos1_2); g_i2clcd.print(","); g_i2clcd.print(pos1_3); g_i2clcd.print(","); g_i2clcd.print(pos1_4); g_i2clcd.print("}"); g_i2clcd.setCursor( 0, 1 ); g_i2clcd.print("{"); g_i2clcd.print(pos2_1); g_i2clcd.print(","); g_i2clcd.print(pos2_2); g_i2clcd.print("}"); g_i2clcd.print("NUM"); g_i2clcd.print("("); g_i2clcd.print(NUM1); g_i2clcd.print(","); g_i2clcd.print(NUM2); g_i2clcd.print(")"); delay(500); g_i2clcd.clear(); // LCD画面消去 } //デバッグ用 #ifdef DEBUG void debug() { if (MODE == 1) { while (digitalRead(A0) == 1) { if (DIRECTION == 0) { pos1_1 = pos1_1 + 1; delay(SPEED); RB1_1servo.write(pos1_1); digitalWrite(8, LOW); } else { pos1_1 = pos1_1 - 1; delay(SPEED); RB1_1servo.write(pos1_1); } if (pos1_1 <= 0) { pos1_1 = 0; DIRECTION = 0; digitalWrite(8, HIGH); delay(1000); } if (pos1_1 >= 180) { pos1_1 = 180; DIRECTION = 1; } } while (digitalRead(A1) == 1) { if (DIRECTION == 0) { pos1_4 = pos1_4 + 1; delay(SPEED); RB1_4servo.write(pos1_4); digitalWrite(9, LOW); } else { pos1_4 = pos1_4 - 1; delay(SPEED); RB1_4servo.write(pos1_4); } if (pos1_4 <= 0) { pos1_4 = 0; DIRECTION = 0; digitalWrite(9, HIGH); delay(1000); } if (pos1_4 >= 180) { pos1_4 = 180; DIRECTION = 1; } } while (digitalRead(A2) == 1) { pos1_2 = pos1_2 + 1; delay(SPEED); RB1_2servo.write(pos1_2); digitalWrite(13, HIGH); if (pos1_2 > 180) { pos1_2 = 180; } } while (digitalRead(10) == 1) { pos1_2 = pos1_2 - 1; RB1_2servo.write(pos1_2); delay(SPEED); digitalWrite(13, LOW); if (pos1_2 < 0) { pos1_2 = 0; } } while (digitalRead(11) == 1) { pos1_3 = pos1_3 + 1; delay(SPEED); RB1_3servo.write(pos1_3); digitalWrite(13, HIGH); if (pos1_3 > 180) { pos1_3 = 180; } } while (digitalRead(12) == 1) { pos1_3 = pos1_3 - 1; RB1_3servo.write(pos1_3); delay(SPEED); digitalWrite(13, LOW); if (pos1_3 < 0) { pos1_3 = 0; } } } else if (MODE == 2) { while (digitalRead(A0) == 1) { pos2_1 = pos2_1 + 1; delay(SPEED); RB2_1servo.write(pos2_1); if (pos2_1 >= 180) { pos2_1 = 180; } } while (digitalRead(A1) == 1) { pos2_1 = pos2_1 - 1; delay(SPEED); RB2_1servo.write(pos2_1); if (pos2_1 <= 0) { pos2_1 = 0; } } while (digitalRead(A2) == 1) { pos2_2 = pos2_2 + 1; delay(SPEED); RB2_2servo.write(pos2_2); if (pos2_2 >= 180) { pos2_2 = 180; } } while (digitalRead(10) == 1) { pos2_2 = pos2_2 - 1; delay(SPEED); RB2_2servo.write(pos2_2); if (pos2_2 <= 0) { pos2_2 = 0; } } } } #else //ROBO1 void RB1MOVE() { while (digitalRead(A0) == 1) { if (digitalRead(A1) == 1 && pos1_1 == pos1[NUM1][0] && pos1_2 == pos1[NUM1][1] && pos1_3 == pos1[NUM1][2] && pos1_4 == pos1[NUM1][3]) { NUM1 = NUM1 + 1; if (NUM1 > 8) { NUM1 = 0; } } else if (digitalRead(A2) == 1) { NUM1 = 0; } if (pos1_1 < pos1[NUM1][0]) { pos1_1 = pos1_1 + 1; RB1_1servo.write(pos1_1); delay(SPEED); } else if (pos1_1 > pos1[NUM1][0]) { pos1_1 = pos1_1 - 1; RB1_1servo.write(pos1_1); delay(SPEED); } if (pos1_2 < pos1[NUM1][1]) { pos1_2 = pos1_2 + 1; RB1_2servo.write(pos1_2); delay(SPEED); } else if (pos1_2 > pos1[NUM1][1]) { pos1_2 = pos1_2 - 1; RB1_2servo.write(pos1_2); delay(SPEED); } if (pos1_3 < pos1[NUM1][2]) { pos1_3 = pos1_3 + 1; RB1_3servo.write(pos1_3); delay(SPEED); } else if (pos1_3 > pos1[NUM1][2]) { pos1_3 = pos1_3 - 1; RB1_3servo.write(pos1_3); delay(SPEED); } if (pos1_4 < pos1[NUM1][3]) { pos1_4 = pos1_4 + 1; RB1_4servo.write(pos1_4); delay(SPEED); } else if (pos1_4 > pos1[NUM1][3]) { pos1_4 = pos1_4 - 1; RB1_4servo.write(pos1_4); delay(SPEED); } if (pos1_1 == pos1[NUM1][0] && pos1_2 == pos1[NUM1][1] && pos1_3 == pos1[NUM1][2] && pos1_4 == pos1[NUM1][3]) { digitalWrite(8, LOW); //動作停止 digitalWrite(9, HIGH); //完了 } else { digitalWrite(8, HIGH);//動作中 digitalWrite(9, LOW);//動作中 } } } //ROBO2 void RB2MOVE() { while (digitalRead(10) == 1) { if (digitalRead(11) == 1 && pos2_1 == pos2[NUM2][0] && pos2_2 == pos2[NUM2][1]) { NUM2 = NUM2 + 1; if (NUM2 > 8) { NUM2 = 0; } } else if (digitalRead(12) == 1) { NUM2 = 0; } if (pos2_1 < pos2[NUM2][0]) { pos2_1 = pos2_1 + 1; RB2_1servo.write(pos2_1); delay(SPEED); } else if (pos2_1 > pos2[NUM2][0]) { pos2_1 = pos2_1 - 1; RB2_1servo.write(pos2_1); delay(SPEED); } if (pos2_2 < pos2[NUM2][1]) { pos2_2 = pos2_2 + 1; RB2_2servo.write(pos2_2); delay(SPEED); } else if (pos2_2 > pos2[NUM2][1]) { pos2_2 = pos2_2 - 1; RB2_2servo.write(pos2_2); delay(SPEED); } if (pos2_1 == pos2[NUM2][0] && pos2_2 == pos2[NUM2][1]) { digitalWrite(8, LOW); //動作停止 digitalWrite(9, HIGH); //完了 } else { digitalWrite(8, HIGH);//動作中 digitalWrite(9, LOW);//動作中 } } } #endif

LCDライブラリ<I2CLiquidCrystal.h>

#include <Wire.h> class I2CLiquidCrystal : public Print { private: int _numlines = 2; int m_i2caddr_lcd = 0; int m_pin_sda = 4; int m_pin_scl = 5; public: I2CLiquidCrystal( int i2caddr_lcd, int pin_sda, int pin_scl ) { m_i2caddr_lcd = i2caddr_lcd; m_pin_sda = pin_sda; m_pin_scl = pin_scl; } void begin() { Wire.begin(); _command( 0x38 ); _command( 0x39 ); _command( 0x14 ); _command( 0x7F ); _command( 0x54 ); _command( 0x6C ); delay(200); _command( 0x38 ); _command(0x0C); _command(0x06); clear(); // Clear display } void clear() { _command( 0x01 ); delayMicroseconds(1060); // need 1.06msec } void setCursor( uint8_t col, uint8_t row ) { static const uint8_t row_offsets[2] = {0x00, 0x40}; if( row >= _numlines ) { row = _numlines - 1; } _command( 0x80 | ( row_offsets[row] + col ) ); } size_t write(uint8_t value) { _send(value, HIGH); return 1; } private: void _command( uint8_t value ) { _send( value, LOW ); delayMicroseconds(27); // need 26.3microsec } void _send( uint8_t value, uint8_t mode ) { Wire.beginTransmission(m_i2caddr_lcd); if( mode ) { // print mode Wire.write(0x40); } else { // command mode Wire.write(0x00); } Wire.write(value); Wire.endTransmission(); } };

Obnizブロックコード

操作画面

外観写真

外観1
外観2
外観3

不具合項目

  • 穴あけしたプラ棒に赤外線LEDとフォトトランジスタを隣接させたが、対象物を近づけなくてもフォトレジスタが反応してしまった。
    LEDの設置場所を変更してみたが、微妙にベルト部分に反応してしまう為信頼性に欠ける。
    また、フォトレジスタを上向きに取り付けている為、蛍光灯の光にも反応してしまう。
    結果として、コベンアの間に設置したフォトレジスタが上手く機能しなくて、No.1ロボット起動した後の自動制御が不完全になってしまった。(現状タイマー制御でないと自動でNo.2ロボットまで搬送出来ない。)

  • No.2ロボットのサーボモーターに動作不良があった。改造した影響かは不明。

  • コンベアを起動するとNo.1ロボットが暴走することがあった。
    DCモーター側にノイズ対策をする必要がある。

総論

不具合項目に関しては、部品の交換や選定をし直して動作不良が無いように改良したい。

今回Obnizを初めて利用したが、簡単にGUIアプリが作成できて、その管理もクラウドで行えた。
電子工作初心者でも、アイデア次第で様々なデバイスを作ることが出来るのではないだろうか。

masayasan さんが 2021/04/30 に 編集 をしました。 (メッセージ: 初版)
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
masayasan さんが 2021/04/30 に 編集 をしました。
ログインしてコメントを投稿する