体操ブーブーをラジコンに改造する(6)

(前回)

 Copilot keybordでカイル君(「お前を消す方法」のイルカ)が出せるって知ってましたか?モジキはイルカ目当てでインストールしました。

前尾灯製作

 車みたいに走らせるなら必要だろうと思い3Dプリンタで作りました。一応白く塗装してみましたがぼってりしてしまいました。しっかり白サフ買って使えば良かったなと。

  • 前照灯(φ5mm白色x2)
  • 尾灯(φ3mm赤色x1)
  • 充電表示灯(φ3mm黄色x1)

 これらを埋め込んでます。

 豚本体に取り付けて前照灯、充電表示灯、尾灯を点けてみました。ガンダムのメインカメラっぽいデザインにしています。やはり塗装ムラが目立つな。

ソフトを作る

 Arduino IDEで書きました。ソフトウェアは動けばいいや派なのでお粗末なコードになっていますが記録として載せます。以下はコードを書くときに考慮した点です。

  • 再生ボタンが押された判定はボタンから指を離した時とする
  • 電池残量低下の判定は閾値付近でバタつかないようにする
  • (豚本体)起動時の大電流を抑えるため、DFPlayer miniのセットアップから500ms後にBluetoothのセットアップを行う
  • (豚本体)起動直後、ボタンが効かない間は前尾灯を点灯させて分かるようにする
  • (リモコン)ジョイスティックの傾き角度は起動時に読み取った初期値からの変位とする
  • (リモコン)方向指示器は自動車と同様に、旋回が終わった後自動消灯する

#include "Arduino.h"
#include "BluetoothSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <uTimerLib.h>

//バッテリ電圧監視
static const int RA = 10000;   // 3V3側 10k
static const int RB = 33000;   // GND側 33k
int LowBTtime=0; //バッテリ電圧低下時間
int LowBToccured=0; //バッテリ電圧低下が起こった(1=起こった)

int sound = 0;  //再生(0: 停止、1: 再生)
int nowplay = 0; //再生中フラグ
int rimokon_SND; //リモコンから受信した再生/停止の情報(0: 停止、1: 再生)
int Winker_L_EN = 0;           //方向指示器左点滅 イネーブル
int Winker_R_EN = 0;           //方向指示器右点滅 イネーブル
int playmode=0; //音源選択(0-9)
int playmode_hontai=0;  //本体側で設定する音源
int playmode_rimokon=0; //リモコン側で設定する音源

//バッテリ電圧低下警告閾値
#define BTRef 3.15 //バッテリ僅少表示閾値

//入出力ピン関係
#define Button_SND 36  //音源流すボタン
#define BUSY 35  //DFPlayer_Busy入力(再生中はL)
#define MODE_A 4  //モード選択スイッチ入力
#define MODE_B 16
#define MODE_C 17
#define MODE_D 18
#define Winker_L 22  //方向指示器左
#define Winker_R 23  //方向指示器右
#define Headlight 19  //前尾灯
#define MT_A1 25  //モータードライバ制御信号出力
#define MT_A2 26
#define MT_B1 32
#define MT_B2 13
#define BTSenseRef 21 //バッテリ電圧監視(3V3供給)
#define BTSense 34 //バッテリ電圧監視(入力)
#define DF_TX 33  //DFPlayer通信用
#define DF_RX 27  //DFPlayer通信用

BluetoothSerial SerialBT;
HardwareSerial myHardwareSerial(1); // use HardwareSerial UART1
DFRobotDFPlayerMini myDFPlayer;

TaskHandle_t thp[1];//マルチスレッドのタスクハンドル格納用

//-----------------------------------------------------------------------------
void setup()
{
  Serial.begin(115200);
//  Serial.println("setup start");

  //起動時前尾灯を点灯(減光)
  pinMode(Headlight, OUTPUT);
  analogWriteResolution(Headlight, 8);
  analogWrite(Headlight,8);
  
  pinMode(MT_A1, OUTPUT);
  pinMode(MT_A2, OUTPUT);
  pinMode(MT_B1, OUTPUT);
  pinMode(MT_B2, OUTPUT);
  analogWriteResolution(MT_A1, 8);
  analogWriteResolution(MT_A2, 8);
  analogWriteResolution(MT_B1, 8);
  analogWriteResolution(MT_B2, 8);
  //モータ停止
  analogWrite(MT_A1, 0);
  analogWrite(MT_A2, 0);
  analogWrite(MT_B1, 0);
  analogWrite(MT_B2, 0);
  
  pinMode(BUSY, INPUT);
  pinMode(Button_SND, INPUT);
  pinMode(MODE_A, INPUT_PULLUP);
  pinMode(MODE_B, INPUT_PULLUP);
  pinMode(MODE_C, INPUT_PULLUP);
  pinMode(MODE_D, INPUT_PULLUP);
  pinMode(Winker_L, OUTPUT);
  pinMode(Winker_R, OUTPUT);
  pinMode(BTSenseRef, OUTPUT);
  pinMode(BTSense, INPUT);
  digitalWrite(BTSenseRef, HIGH);



  //乱数の種を生成
//  randomSeed(analogRead(39));
  
  //DFPlayerとの通信セットアップ
  myHardwareSerial.begin(9600, SERIAL_8N1, DF_RX, DF_TX); // RX, TX
//  Serial.println();
 // Serial.println(F("DFRobot DFPlayer Mini Demo"));
//  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  while (!myDFPlayer.begin(myHardwareSerial))
  {
    //Serial.print(".");
    delay(50);
  }
//  Serial.println(F("DFPlayer Mini online."));
delay(500);
  //音量設定
  myDFPlayer.volume(30);  //Set volume value. From 0 to 30

  //BT受信設定
  SerialBT.begin("TaisouB");

  //LED点滅
  TimerLib.setInterval_us(blink_Winker, 375000);

  //Core0で実行するタスク
  xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 3, &thp[0], 0); 


  analogWrite(Headlight,0);
}

//-----------------------------------------------------------------------------------------------
void loop()
{   
//  Serial.println("loop");
  //data_decode();
  
  //リモコンからのデータを読込
  if(SerialBT.available()){
//    Serial.println("BTAvailable");
    data_decode();
  }

  
  //バッテリ電圧監視 -----------------------------------------------------------
  float value = analogRead(BTSense);
  float voltage = value / 4095 * 3.6 * (RA + RB) / RB;
//  Serial.println(voltage);
  //バッテリ僅少時は左右の方向指示器を点灯
  if (voltage <= BTRef && LowBToccured==0) {
//    Serial.println("LOW Battery");
    Winker_L_EN=0;
    Winker_R_EN=0;
    LowBTtime++;
    //バッテリ電圧<=閾値が1秒間継続
    if(LowBTtime>20){
      LowBToccured=1;
      digitalWrite(Winker_L, HIGH);
      digitalWrite(Winker_R, HIGH);
    }
  }
    
  if(LowBToccured==1){
    if(voltage>=BTRef+0.07){
      digitalWrite(Winker_L, LOW);
      digitalWrite(Winker_R, LOW);
      LowBTtime=0;
      LowBToccured=0;
    }
  }
  delay(50);
}
//-----------------------------------------------------------------------------------------------
//音源再生/停止情報の更新
void Core0a(void *args){
//  delay(1000);
  while(1){
    
    playmode_hontai = !digitalRead(MODE_D) * 8 + !digitalRead(MODE_C) * 4 + !digitalRead(MODE_B) * 2 + !digitalRead(MODE_A) * 1;

    if(sound==1 && nowplay==0){
      //Serialaru.print("play");
      ctr_sound(playmode);
    }
    
      //本体又はリモコンの再生ボタン押下
    if(!digitalRead(Button_SND)){
      playmode=playmode_hontai;
      delay(50); //チャタリング防止
      while (!digitalRead(Button_SND)){delay(50);} //ボタンが離されるまで待機
      if(sound==1){
        sound=0;
      }else{
        sound=1;
      }
    }
    if(rimokon_SND==1){
      playmode=playmode_rimokon;
      delay(50); //チャタリング防止
      while (rimokon_SND==1){delay(50);} //ボタンが離されるまで待機
      if(sound==1){
        sound=0;
      }else{
        sound=1;
      }
    }
    delay(50);
  }
}

//方向指示器点滅
void blink_Winker() {
  if(LowBToccured==0){  //バッテリ電圧が低下しているときは点滅させない
    if (Winker_L_EN) {
      digitalWrite(Winker_L, !digitalRead(Winker_L));
    } else {
      digitalWrite(Winker_L, LOW);
    }
    if (Winker_R_EN) {
      digitalWrite(Winker_R, !digitalRead(Winker_R));
    } else {
      digitalWrite(Winker_R, LOW);
    }
  }
}


//データ受信
void data_decode() {
  char recv_data[9]={0,0,0,0,0,0,0,0,0};//{0,0,0,1,1,255,0,0,255};
  
    SerialBT.readBytes(recv_data, 9);

    //受信データを機器の動作に反映
    ctr_light(recv_data);
    ctr_motor(recv_data);
    rimokon_SND=recv_data[0];
    playmode_rimokon=recv_data[1];
/*
    Serial.println(recv_data[0], DEC);
    Serial.println(recv_data[1], DEC);
    Serial.println(recv_data[2], DEC);
    Serial.println(recv_data[3], DEC);
    Serial.println(recv_data[4], DEC);
    Serial.println(recv_data[5], DEC);
    Serial.println(recv_data[6], DEC);
    Serial.println(recv_data[7], DEC);
    Serial.println(recv_data[8], DEC);
    Serial.println("");
  */  
}


//灯火制御
void ctr_light(char *dt) {
  int HR = dt[2]; //前尾灯
  Winker_R_EN = dt[3]; //方向指示器右
  Winker_L_EN = dt[4]; //方向指示器左

//前尾灯
  if(HR==1){
    analogWrite(Headlight, 255);}
    else{
      analogWrite(Headlight, 0);
    }
}

//モータ制御
void ctr_motor(char *dt) {
  analogWrite(MT_A1, dt[5]);
  analogWrite(MT_A2, dt[6]);
  analogWrite(MT_B1, dt[7]);
  analogWrite(MT_B2, dt[8]);
}


//音声制御
void ctr_sound(char Mode) {
  randomSeed(millis());
  nowplay=1;
  
  //再生中なら停止
  if (!digitalRead(BUSY)) {
    myDFPlayer.pause();
    //      Serial.println("pause");
    nowplay=0;
    sound=0;
    return;
  }

  int RandomNumber = random(9) + 1; //乱数は1~9の範囲
  //  Serial.print(RandomNumber,DEC);Serial.println("");

  switch (Mode) {

    case 1: //豚-ラジオ体操第一
      myDFPlayer.playFolder(1, RandomNumber);
      delay(500);
      //while(再生中&再生ボタン押されてない&リモコン再生ボタン押されてない)
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      //while(再生中&(再生ボタン押された||リモコン再生ボタン押された))
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(50, 1);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(50, 9);
      break;

    case 2: //豚-ラジオ体操第二
      myDFPlayer.playFolder(1, RandomNumber);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(60, 2);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(50, 9);
      break;

    case 3: //豚-ラジオ体操第三
      myDFPlayer.playFolder(1, RandomNumber);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(60, 3);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(50, 9);
      break;

    case 4: //豚-ラジオ体操第四
      myDFPlayer.playFolder(1, RandomNumber);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(60, 4);
      delay(500);
      while (!digitalRead(BUSY) && digitalRead(Button_SND) && rimokon_SND==0){delay(50);}
      delay(50);
      while (!digitalRead(BUSY) && (!digitalRead(Button_SND) || rimokon_SND==1)){delay(50);}
      myDFPlayer.playFolder(50, 9);
      break;

      
      break;

    default:
      break;
  }
  nowplay=0;
  sound=0;
  return;
}

#include "BluetoothSerial.h"
#include <uTimerLib.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

//バッテリ電圧監視
static const int RA = 10000;   // 3V3側 10k
static const int RB = 33000;   // GND側 33k
int LowBTtime=0; //バッテリ電圧低下時間
int LowBToccured=0; //バッテリ電圧低下が起こった(1=起こった)
//バッテリ電圧低下警告閾値
#define BTRef 3.2 //バッテリ僅少表示閾値

int BTCN_b_EN = 0; //無線接続表示灯点滅イネーブル
int default_L = 0;  //左スティック初期位置
int default_R = 0;  //右スティック初期位置

int Right_flag=0; //右方向転換を開始した
int Left_flag=0;  //左方向転換を開始した

//入出力ピン関係
#define BTSenseRef 14 //バッテリ電圧監視(3V3供給)
#define BTSense 27 //バッテリ電圧監視(入力)

#define MODE_A 26  //モード選択スイッチ入力
#define MODE_B 25
#define MODE_C 33
#define MODE_D 32
#define Button_SND 18 //再生ボタン
#define Button_Winker_L 17  //方向指示器左
#define Button_Winker_R 16  //方向指示器右
#define Button_Headlight 4 //前尾灯
#define Button_SW_R 39//押込スイッチ右
#define Button_SW_L 36//押込スイッチ左
#define Stick_R 35  //ジョイスティック右
#define Stick_L 34  //ジョイスティック左
#define BTAlert 23  //電池残量表示灯
#define BTCN 22 //無線接続表示灯

BluetoothSerial SerialBT;
String MAC="?????????";
uint8_t address[6]={?????????};
bool connected;

uint8_t send_data[9]={0,0,0,0,0,0,0,0,0};

void setup(){
  Serial.begin(115200);
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
  pinMode(Button_SND, INPUT_PULLUP);
  pinMode(MODE_A, INPUT_PULLUP);
  pinMode(MODE_B, INPUT_PULLUP);
  pinMode(MODE_C, INPUT_PULLUP);
  pinMode(MODE_D, INPUT_PULLUP);
  pinMode(Button_Winker_L, INPUT_PULLUP);
  pinMode(Button_Winker_R, INPUT_PULLUP);
  pinMode(Button_Headlight, INPUT_PULLUP);
  pinMode(Button_SW_R, INPUT);
  pinMode(Button_SW_L, INPUT);
  pinMode(BTAlert, OUTPUT);
  pinMode(BTCN, OUTPUT);
  pinMode(BTSenseRef, OUTPUT);
  digitalWrite(BTSenseRef, HIGH);
  
  //LED点滅
  TimerLib.setInterval_us(BTCN_b, 375000);

  //スティック初期位置読込
  default_L=analogRead(Stick_L)>>3;
  default_R=analogRead(Stick_R)>>3;
  
  //電池残量表示灯と無線接続表示灯を消す
  digitalWrite(BTAlert, LOW);
  digitalWrite(BTCN, LOW);
  
  SerialBT.begin("TaisouB", true);

  BTCN_b_EN=1;
  connected = SerialBT.connect(address);
  BTmonitor(0);
  if(connected){
    digitalWrite(BTCN,HIGH);
    Serial.println("Connected");
  }else{
    while(!SerialBT.connected(10000)){
      digitalWrite(BTCN,LOW);
      Serial.println("disConnected");
      SerialBT.connect(address);
    }
  }
  BTCN_b_EN=0;
/*
  if (SerialBT.disconnect()) {
    Serial.println("Disconnected Succesfully!");
  }
  // this would reconnect to the name(will use address, if resolved) or address used with connect(name/address).
  */

}

void loop(){

  //ジョイスティック角度(0-512)
  int Stick_R_val=analogRead(Stick_R)>>3;
  int Stick_L_val=analogRead(Stick_L)>>3;
  int Forward_val=0;
  int Back_val=0;
  int Right_val=0;
  int Left_val=0;
  int A1=0;
  int A2=0;
  int B1=0;
  int B2=0;
  //通信表示灯制御
  if(SerialBT.connected(10000)){
    digitalWrite(BTCN,HIGH);
  }else{
    Serial.println("Reconnect with Button");
    digitalWrite(BTCN,LOW);
    SerialBT.disconnect();
    SerialBT.connect();
  }

  //左右スティック押込みでランプテスト
  while(!digitalRead(Button_SW_L)){
    while(!digitalRead(Button_SW_R)){
      delay(50);
      digitalWrite(BTCN,HIGH);
      digitalWrite(BTAlert, HIGH);
    }
  }

  //左右スティック角度読込
  if(Stick_L_val>default_L+30){
    Forward_val=Stick_L_val-default_L;
  }
  if(Stick_L_val<default_L-30){
    Back_val=default_L-Stick_L_val;
  }

  if(Stick_R_val>default_R+30){
    Right_val=Stick_R_val-default_R;
  }
  if(Stick_R_val<default_R-30){
    Left_val=default_R-Stick_R_val;
  }

  A1=(Forward_val-Right_val)*1.7;
  A2=(Back_val-Right_val)*1.7;
  B1=(Back_val-Left_val)*1.7;
  B2=(Forward_val-Left_val)*1.7;

  //超信地旋回(左スティックが中立の時)
  if(Forward_val==0 && Back_val==0){
    A1=Left_val*1.7;
    A2=Right_val*1.7;
    B1=Left_val*1.7;
    B2=Right_val*1.7;
  }
  
//左右方向転換が始まればそれぞれのフラグを1
  if(Left_val>0){Left_flag=1; Serial.println("Left flag set");}
  if(Right_val>0){Right_flag=1; Serial.println("Right flag set");}

//方向転換が終了すればフラグクリアし方向指示器を消灯
  if(Left_val==0){
    if(Left_flag==1){
        send_data[4]=0; Left_flag=0;
        Serial.println("Left flag cleared");
    }
  }
  if(Right_val==0){
    if(Right_flag==1){
      send_data[3]=0; Right_flag=0;
        Serial.println("Right flag cleared");
    }
  }

  if(A1>255){A1=255;}
  if(A1<=0){A1=0;}
  if(A2>=255){A2=255;}
  if(A2<=0){A2=0;}
  if(B1>=255){B1=255;}
  if(B1<=0){B1=0;}
  if(B2>=255){B2=255;}
  if(B2<=0){B2=0;}
  
  send_data[5]=A1; //右モータ正転(前進)
  send_data[6]=A2;  //右モータ逆転(後退)
  send_data[7]=B1;  //左モータ正転(後退)
  send_data[8]=B2;  //左モータ逆転(前進)
  
  //再生モード読込
  int playmode = !digitalRead(MODE_D) * 8 + !digitalRead(MODE_C) * 4 + !digitalRead(MODE_B) * 2 + !digitalRead(MODE_A) * 1;
  send_data[1]=playmode;

  //再生ボタンが押された(押している間だけ1)
  if(!digitalRead(Button_SND)){
    delay(100);
    send_data[0]=1;
  }else{
    send_data[0]=0;
  }

  //方向指示器ボタンが押された(左右)
  if(!digitalRead(Button_Winker_L)){
    delay(100);
    if(send_data[4]==0){
      send_data[4]=1;
    }else{
      send_data[4]=0;
    }
  }
  if(!digitalRead(Button_Winker_R)){
    delay(100);
    if(send_data[3]==0){
      send_data[3]=1;
    }else{
      send_data[3]=0;
    }
  }
  
  //前尾灯ボタンが押された
  if(!digitalRead(Button_Headlight)){
    delay(100);
    if(send_data[2]==0){
      send_data[2]=1;
    }else{
      send_data[2]=0;
    }
  }

  SerialBT.write(send_data, 9);
  
  BTmonitor(1);
  
  Serial.println(Left_val, DEC);
  Serial.println(Right_val, DEC);
  Serial.println(send_data[0], DEC);
  Serial.println(send_data[1], DEC);
  Serial.println(send_data[2], DEC);
  Serial.println(send_data[3], DEC);
  Serial.println(send_data[4], DEC);
  Serial.println(send_data[5], DEC);
  Serial.println(send_data[6], DEC);
  Serial.println(send_data[7], DEC);
  Serial.println(send_data[8], DEC);
  Serial.println("");
  
  delay(50);
}

void BTmonitor(int waitEN){
  //バッテリ電圧監視 -----------------------------------------------------------
  float value = analogRead(BTSense);
  float voltage = value / 4095 * 3.6 * (RA + RB) / RB;
  Serial.println(voltage);
  //バッテリ僅少時は電池残量表示灯を点灯、そうでないときは消灯
  if (voltage < BTRef && LowBToccured==0) {
    LowBTtime++;
    //バッテリ電圧<=閾値が1秒間継続
    if(waitEN==1){
      if(LowBTtime>20){
        LowBToccured=1;
        digitalWrite(BTAlert, HIGH);
      }
    }else{
      LowBToccured=1;
      digitalWrite(BTAlert, HIGH);
    }
  }
    if(voltage>=BTRef+0.07){
      digitalWrite(BTAlert, LOW);
      LowBTtime=0;
      LowBToccured=0;
    }
  
  delay(50);
}

//方向指示器点滅
void BTCN_b() {
  if (BTCN_b_EN) {
    digitalWrite(BTCN, !digitalRead(BTCN));
  }
}

(続き)

タイトルとURLをコピーしました