Category: 電子工作

◆M5Stackを使い音声と7セグ表示で測定値を教えてくれる
 「トーキングテスター」を作ってみました。(その1)

S__44556302



◆測定項目は、ボタンBにて、操作し、
 電圧、電流、抵抗、ロジックを切り替え、
 音声出力と7セグ表示させます。
 その1は、電圧測定のみ)

◆測定モードは、ボタンCにて操作し、
 手動測定、自動測定、リピート測定としました。
 ①手動測定・・・・ ボタンAを押したとき測定し出力。
 ②自動測定・・・・ 値の変化があった時、測定し出力。
 ③リピート測定・・ 3,5,7,15,31秒間隔でリピート測定し出力。

◆ハードは、出来る限りシンプルにしました。
 (その1は、抵抗2本とコンデンサ1個、ピンヘッダ)

1.準備
①まず最初に、M5Stackで喋らせるためには、音声合成機能を準備します。
  下記URLを参考に、導入し、
  サンプル発生までが出来ていること前提としています。
 *デモ版の為、ナ、マ行が「ヌ」と発音します。

 ダウンロード:SampleTTS.zip

②トーキングテスターは、上記「SampleTTS.ino」を改造します。

③M5Stackの標準のアナログ入力を使って、(GPIO36,35,26)
  電圧、電流、抵抗測定及びロジックテスター作ります。

④アナログ入力については、色々誤差などが有り大変でした。

⑤詳しくは、下記URLが大変参考になり、ありがとうございました。


①ADコンバータのアッテネータの設定を選びます。
 ブログのの内容と実際試してみて、「ADC_11db」を選びました。

以下ブログより抜粋(先人の方に感謝です。)
***********************************************************
  //  analogSetAttenuation(ADC_0db); //アッテネーターを設定
  //  analogSetAttenuation(ADC_2_5db);
  //  analogSetAttenuation(ADC_6db);
  //  analogSetAttenuation(ADC_11db);
***********************************************************
  データーシートのリンクがすぐに出てこないのですが、
  ADCのキャリブレーション方法について書いてある資料には、
  誤差が少ない範囲として、
  Atten0 (0dB)は、  100-950mV
  Atten1 (-2.5dB)は、100-1250mV
  Atten2 (-6dB)は、  150-1750mV
  Atten3 (-11dB)は、 150-2450mV
  となっていて、下の方もダメと書いてありましたorz
************************************************************

②上記より電圧測定範囲を1.2V~27V未満としました。

③電圧測定の入力部分は、抵抗分圧で10倍としました。
 手持ち部品の関係で2MΩと220KΩとしましたが、
 出来れば割り切れる1.8MΩと200KΩが良いです。

 入 力◎-----2MΩ----+->GPIO36(ADC)
              l
    GND◎-+-220KΩ--+
               l         l
               +-0.1uF----+-->GND

④AD変換後の電圧値の算出に
  「(float)ad*3.3/4096.0*10.0」
  ・最後の10.0は、入力抵抗の分圧の分です。
  の式を使って測定値を出していましたが、
  テスター値との乖離があまりにもあり、使えませんでした。

⑤採用したのは、大まかな実測値と計算にて電圧値を算出しました。
 結果、ほぼテスターと同じ様な値となりました。
 ・実測は、2V~27Vまで、1V間隔でAD変換値を記録し、vtblという
  配列に保存しました。2V以下は0.1V間隔でltblに保存しました。
 ・基本値は、配列の位置とし、1Vの間は、1V間の差分を10で割り、
  加算しました。

    計算式の概略
   ad=analogRead(AD_IN);
           for (i=0;i<28;i++) {
             if ((vtbl[i]<=ad)&&(ad<vtbl[i+1])) break;
           }
           v2=vtbl[i+1];
           v1=vtbl[i];
           v3=((ad-v1)/((v2-v1)/10.0))/10.0;
          adt=i+v3;

  シリアル出力
   ad=1076 i=10 v1=1060 v2=1182 v3=0.131148 adt=10.13 
   10.13ボルト.

   ad :AD変換値
   adt:測定電圧値(計算結果)

  以上により、ほぼテスター値とほぼ同じくらいの制度となりました。

3.電流測定、抵抗測定、ロジック測定について

  その1は、抵抗2本とコンデンサ1個の簡易版とし
  電圧測定のみとします。

4.測定値の7セグ表示について

①LCDの表示は、7セグメント表示が、かっこいいかなと色々検索してみましたら

    M5.Lcd.drawString("V",140,200,4);
    M5.Lcd.drawString(String(dt,2),x,80,7);     <--の最後の数値を「7」にすることで
                     7セグ表示出来ました。隠しコマンドかな

*ライブラリの使用例ではサイズとなっている。
  M5.Lcd.drawString("文字列", X, Y, {size}) 指定位置に文字列を表示

5.スケッチについて
 
**整理中

以上

 

メカナムホィール 車 に M5StickC を搭載し PS3コントローラ で操作し遊んでみた。





◆ブログ内参考
・I2C Wemos D1 mini Motor Shield を使かってみた
・ESP32 (M5StickC) で PS3 の コントローラ を使う


◆全体写真

S__44359708

◆メカナムホィールの動きとPS3コントローラの釦割り当て

4carps3
◆配線略図

配線略図

◆アマゾンで購入
Homyl DIY メカナムホイール 4輪 ロボットカーシャーシ キット STEM知育玩具 Arduino用


◆メカナムホィールを取り付けた所

S__44326927

◆モーターシールドのI2Cアドレスを表示させた所

S__44359711


◆PS3コントローラのバッテりー残量表示させた所

S__44359712






◆スケッチ例(あくまでも参考です。自己責任で)
//PS3コントローラ対応
//2020/8/22 メカナムホイール車用に改修
//http://gijin77.blog.jp/archives/26057910.html

#include <Wire.h>
#include <M5StickC.h>
#include <Ps3Controller.h>
#include "WEMOS_Motor.h"
/*
#define _CCW  1
#define _BK   2
#define _STOP 3
#define _STANDBY 4
*/
#define _FD  1
#define _BK  2

#define BTN_A 37
#define BTN_B 39
#define LED 10

// Global Variables

float vBatt, voltAve=3.7;
int bx=10;
int by=80;
int bw=20;
int bh=64;
int stop_flg=0;
int mrx=0;
int mry=0;
int mlx=0;
int mly=0;
int demoflg=0;
int pwr=50;
int xyoff=50;

int ps3bat; //PS3バッテリー残量

//Motor shield default I2C Address: 0x2F,0x30
//PWM frequency: 1000Hz(1kHz)
Motor MFR(0x2F, _MOTOR_A, 1000); //Motor A FR
Motor MFL(0x2F, _MOTOR_B, 1000); //Motor B FL
Motor MBR(0x30, _MOTOR_A, 1000); //Motor A BR
Motor MBL(0x30, _MOTOR_B, 1000); //Motor B BL

void setup() {
  pinMode(BTN_A, INPUT);
  pinMode(BTN_B, INPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED,HIGH);
  Serial.begin(115200);
  M5.begin();
  Wire.begin(0, 26); //SDA,SCL

  Serial.println("プログラム 開始");
  Ps3.begin("D8:A0:1D:58:4E:C6");

  for (int i=0;i<3;i++){
    M5.Lcd.fillScreen(GREEN);
    digitalWrite(LED,LOW);delay(250);
    M5.Lcd.fillScreen(BLACK);
    digitalWrite(LED,HIGH);delay(250);
  }

  M5.Lcd.setRotation( 0 ); //Mycar = 0
  M5.Lcd.setTextFont(1);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(5,0);M5.Lcd.print("Manual");
  dispvbatt();
  face();
}

void loop() {
int rx,ry;
int lx,ly;

  M5.update();
  if ( M5.Axp.GetBtnPress() ) {
    // 電源ボタンを押すとパワーオフ
//      M5.Axp.pwrerOff();
  }
  if ( M5.BtnB.wasReleased() ) {
    // Bボタンを押すとリスタート
      esp_restart();
  }
  if ( M5.BtnA.wasReleased() ) {
    // Aボタンを押すとデモスタート
      demo_a();
  }

  if  (Ps3.isConnected()){
    dispvbatt();
    if (Ps3.data.button.l1){
//      Serial.println("l1 ボタン圧下");
      ps3bat=Ps3.data.status.battery;
      disp3batt(ps3bat);
      lcdout(GREEN,"Manual");
      delay(200);demoflg=0;
    } else if (Ps3.data.button.l2){ //バッテリ表示デモ
//      Serial.println("l2 ボタン圧下");
      for (int i=1;i<6;i++){
        disp3batt(i);
      }
      disp3batt(0xee);
      lcdout(GREEN,"Manual");
      delay(200);demoflg=0;
    } else  if (Ps3.data.button.select ){
//      Serial.println("select ボタン圧下");
      i2cck();
    } else if (Ps3.data.button.triangle){
//      Serial.println("triangle ボタン圧下");
      lcdout(WHITE,"DEMO A");delay(200);demoflg=1;
    } else if (Ps3.data.button.circle){
//      Serial.println("circle ボタン圧下");
      lcdout(WHITE,"DEMO B");delay(200);demoflg=2;
    } else if (Ps3.data.button.cross){
//    Serial.println("cross ボタン圧下");
      lcdout(WHITE,"DEMO C");delay(200);demoflg=3;
    } else if (Ps3.data.button.square){
      lcdout(WHITE,"DEMO D");delay(200);demoflg=4;
//      Serial.println("square ボタン圧下");
    } else if (Ps3.data.button.r3){
//      Serial.println("r3 ボタン圧下"); //デモリセット
      lcdout(GREEN,"Manual");
      delay(200);demoflg=0;
    } else if (Ps3.data.button.start){
//      Serial.println("start ボタン圧下"); //デモスタート
      if      (demoflg==1) demo_a();
      else if (demoflg==2) demo_b();
      else if (demoflg==3) demo_c();
      else if (demoflg==4) demo_d();
    } else if (Ps3.data.button.r1){
//      Serial.println("r1 ボタン圧下");
      lcdout(YELLOW,"pwr 50");
      pwr=50;delay(200);
    } else if (Ps3.data.button.r2){
//      Serial.println("r2 ボタン圧下");
      lcdout(YELLOW,"pwr100");
      pwr=100;delay(200);
    }
    rx=Ps3.data.analog.stick.rx;
    ry=Ps3.data.analog.stick.ry;
    lx=Ps3.data.analog.stick.lx;
    ly=Ps3.data.analog.stick.ly;

    if (abs(rx)<xyoff) rx=0;
    if (abs(ry)<xyoff) ry=0;
    if (abs(lx)<xyoff) lx=0;
    if (abs(ly)<xyoff) ly=0;
    
    rx = map(rx,-128,128,100, -100); // R-JOYSTICK X
    ry = map(ry,-128,128,100, -100); // R-JOYSTICK Y
    lx = map(lx,-128,128,100, -100); // L-JOYSTICK X
    ly = map(ly,-128,128,100, -100); // L-JOYSTICK Y

    if ((rx==0)&&(ry==0)&&(lx==0)&&(ly==0)&&(stop_flg==0)) {
        stop_flg=1;
        drvFR(_STOP,0);
        drvBR(_STOP,0);
        drvFL(_STOP,0);
        drvBL(_STOP,0);
        face();
    } else if ((mrx != rx)||(mry != ry)||(mlx != lx)||(mly != ly)){
      Serial.printf("1 RX=%4d RY=%4d LX=%4d LY=%4d pwrer=%d",rx,ry,lx,ly,pwr);
      Serial.println();
      stop_flg=0;
      BLE_4car(rx,ry,lx,ly);
    }
    mrx=rx;mry=ry;mlx=lx;mly=ly;
  }
  delay(100);
}

void i2cck(){
  int address;
  int error;

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("I2C scanning");
  Serial.println("I2C address scanning");
  for(address = 1; address < 127; address++ ) 
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if(error==0)
    {
      M5.Lcd.print(address,HEX);M5.Lcd.print(" ");
      Serial.println(address,HEX);
    }
    else {
      M5.Lcd.print(".");
 //     Serial.println(".");
    }

    delay(10);
  }
  Serial.println("End");
  delay(3000); 
  M5.Lcd.setTextSize(2); 
  M5.Lcd.fillScreen(BLACK);
  lcdout(GREEN,"Manual");
}

void BLE_4car(int rx,int ry,int lx,int ly) {
//int pwr=50;
/*
  +Y
+X  -X
  -Y
*/
  if ((ry> xyoff)&&(rx==0)) { ff(pwr);Serial.print("ff");}//前進
  if ((ry<-xyoff)&&(rx==0)) { bb(pwr);Serial.print("bb");}//後進
  if ((rx<-xyoff)&&(ry==0)) { ll(pwr);Serial.print("ll");}//左横移動
  if ((rx> xyoff)&&(ry==0)) { rr(pwr);Serial.print("rr");}//右横移動
  if ((ry> xyoff)&&(rx> xyoff)) { fr(pwr);Serial.print("fr");}//右前斜め
  if ((ry> xyoff)&&(rx<-xyoff)) { fl(pwr);Serial.print("fl");}//左前斜め
  if ((ry<-xyoff)&&(rx> xyoff)) { br(pwr);Serial.print("br");}//右後斜め
  if ((ry<-xyoff)&&(rx<-xyoff)) { bl(pwr);Serial.print("bl");}//左後斜め
 
  if ((ly> xyoff)&&(lx==0)) { ff(pwr);Serial.print("ff");}//前進
  if ((ly<-xyoff)&&(lx==0)) { bb(pwr);Serial.print("bb");}//後進
  if ((lx<-xyoff)&&(ly==0)) { rcir(pwr);Serial.print("rcir"); }//右旋回
  if ((lx> xyoff)&&(ly==0)) { lcir(pwr);Serial.print("lcir"); }//左旋回
  if ((lx<-xyoff)&&(ly> xyoff)) { ftr(pwr);Serial.print("ftr");} //前右折 Turn right
  if ((lx> xyoff)&&(ly> xyoff)) { ftl(pwr);Serial.print("ftl"); }//前左折 Turn left
  if ((lx<-xyoff)&&(ly<-xyoff)) { btr(pwr);Serial.print("btr"); }//後右折
  if ((lx> xyoff)&&(ly<-xyoff)) { btl(pwr);Serial.print("btl"); }//後左折
  Serial.println(" --> end ");
}

void drvFR(int fb,int po){
int h;
  if (po>bh) h=bh; else h=po;
  MFR.setmotor( fb,po);
  M5.Lcd.fillRect(bx+30, by-bh-2, 40, bh, BLACK);
  if (fb== _BK) {
    M5.Lcd.fillRect(bx+50, by-h-2, bw, h, RED);
  } else {
    M5.Lcd.fillRect(bx+50, by-h-2, bw, h, GREEN);
  }
}
void drvFL(int fb,int po){
int h;
  if (po>bh) h=bh; else h=po;
  MFL.setmotor( fb, po);
  M5.Lcd.fillRect(bx-10, by-bh-2, 40, bh, BLACK);
  if (fb==_BK) {
    M5.Lcd.fillRect(bx, by-h-2, bw, h, RED);
  } else {
    M5.Lcd.fillRect(bx, by-h-2, bw, h, GREEN);
  }
}
void drvBR(int fb,int po){
int h;
  if (po>bh) h=bh; else h=po;
  MBR.setmotor( fb,po);
  M5.Lcd.fillRect(bx+30, by+2, 40, bh, BLACK);
  if (fb== _BK) {
    M5.Lcd.fillRect(bx+50, by+2, bw, h, RED);
  } else {
    M5.Lcd.fillRect(bx+50, by+2, bw, h, GREEN);
  }
}
void drvBL(int fb,int po){
int h;
  if (po>bh) h=bh; else h=po;
  MBL.setmotor( fb, po);
  M5.Lcd.fillRect(bx-10, by+2, 40, bh, BLACK);
  if (fb==_BK) {
    M5.Lcd.fillRect(bx, by+2, bw, h, RED);
  } else {
    M5.Lcd.fillRect(bx, by+2, bw, h, GREEN);
  }
}

void ff(int pwr){//前進
  drvFR( _FD, pwr);
  drvFL( _FD, pwr);
  drvBR( _FD, pwr);
  drvBL( _FD, pwr);
}

void bb(int pwr){//後進
  drvFR( _BK, pwr);
  drvFL( _BK, pwr);
  drvBR( _BK, pwr);
  drvBL( _BK, pwr);
}

void rr(int pwr){//右横移動
  drvFR( _BK, pwr);
  drvFL( _FD, pwr);
  drvBR( _FD, pwr);
  drvBL( _BK, pwr);
}

void ll(int pwr){//左横移動
  drvFR( _FD, pwr);
  drvFL( _BK, pwr);
  drvBR( _BK, pwr);
  drvBL( _FD, pwr);
}

void fr(int pwr){//右前斜め
  drvFR(_STOP,0);
  drvFL( _FD, pwr);
  drvBR( _FD, pwr);
  drvBL(_STOP,0);
}

void fl(int pwr){//左前斜め
  drvFR( _FD, pwr);
  drvFL(_STOP,0);
  drvBR(_STOP,0);
  drvBL( _FD, pwr);
}

void br(int pwr){//右後斜め
  drvFR( _BK, pwr);
  drvFL(_STOP,0);
  drvBR(_STOP,0);
  drvBL( _BK, pwr);
}

void bl(int pwr){//左後斜め
  drvFR(_STOP,0);
  drvFL( _BK, pwr);
  drvBR( _BK, pwr);
  drvBL(_STOP,0);
}

void rcir(int pwr){
  drvFR( _BK, pwr);//右旋回
  drvFL( _FD, pwr);
  drvBR( _BK, pwr);
  drvBL( _FD, pwr);
}

void lcir(int pwr){//左旋回
  drvFR( _FD, pwr);
  drvFL( _BK, pwr);
  drvBR( _FD, pwr);
  drvBL( _BK, pwr);
}

void ftr(int pwr){//前右折
  drvFR(_STOP,0);
  drvFL( _FD, pwr);
  drvBR(_STOP,0);
  drvBL( _FD, pwr);
}

void ftl(int pwr){//前左折
  drvFR( _FD, pwr);
  drvFL(_STOP,0);
  drvBR( _FD, pwr);
  drvBL(_STOP,0);
}

void btr(int pwr){//後右折
  drvFR(_STOP,0);
  drvFL( _BK, pwr);
  drvBR(_STOP,0);
  drvBL( _BK, pwr);
}

void btl(int pwr){//後左折
  drvFR( _BK, pwr);
  drvFL(_STOP,0);
  drvBR( _BK, pwr);
  drvBL(_STOP,0);

}

void mstop(){
  drvFR(_STOP,0);
  drvFL(_STOP,0);
  drvBR(_STOP,0);
  drvBL(_STOP,0);
  face();
  delay(1000);
}

void demo_a(){
int pwr=50;
int wt=1000;

  lcdout(GREEN,"DEMO A");
//前進
  ff(pwr);delay(wt/2);mstop();
//後進
  bb(pwr);delay(wt);mstop(); 
//前進
  ff(pwr);delay(wt/2);mstop();

//右横移動
  rr(pwr);delay(wt/2);mstop();  
//左横移動
  ll(pwr);delay(wt);mstop();
//右横移動
  rr(pwr);delay(wt/2);mstop();  

//右旋回
  rcir(pwr);delay(wt*3);mstop();  
//左旋回
  lcir(pwr);delay(wt*3);mstop();

//右前斜め
  fr(pwr);delay(wt/2);mstop();
//左後斜め
  bl(pwr);delay(wt/2);mstop();
//左前斜め
  fl(pwr);delay(wt/2);mstop();
//右後斜め
  br(pwr);delay(wt/2);mstop();
  demoend();
}

void demo_b() {
int pwr=50;
int wt=1000;
  lcdout(GREEN,"DEMO B");
//前進
  ff(pwr);delay(wt/2);mstop();
//右横移動
  rr(pwr);delay(wt/2);mstop();  
//後進
  bb(pwr);delay(wt);mstop(); 
//左横移動
  ll(pwr);delay(wt);mstop();
//前進
  ff(pwr);delay(wt);mstop();
//右横移動
  rr(pwr);delay(wt/2);mstop();  
//後進
  bb(pwr);delay(wt/2);mstop();      
  demoend();
}

void demo_c(){
int pwr=75;
int wt=700;
  lcdout(GREEN,"DEMO C");
//前進
  ff(pwr);delay(wt);
//右横移動
  rr(pwr);delay(wt);  
//後進
  bb(pwr);delay(wt);
//左横移動
  ll(pwr);delay(wt);
//右横移動
  rr(pwr);delay(wt); 
//前進
  ff(pwr);delay(wt);
//左横移動
  ll(pwr);delay(wt);
//後進
  bb(pwr);delay(wt);

//右前斜め
  fr(pwr);delay(wt/2);
//右旋回
  rcir(pwr);delay(wt*3);
//左旋回
  lcir(pwr);delay(wt*3);
//左後斜め
  bl(pwr);delay(wt/2);
  demoend();
}

void demo_d(){
int pwr=75;
int wt=500;
  lcdout(GREEN,"DEMO D");
 //前進
  ff(pwr);delay(wt);
//右横移動
  rr(pwr);delay(wt);  
//後進
  bb(pwr);delay(wt);
//左横移動
  ll(pwr);delay(wt);
//右横移動
  rr(pwr);delay(wt); 
//前進
  ff(pwr);delay(wt);
//左横移動
  ll(pwr);delay(wt);
//後進
  bb(pwr);delay(wt);

//右前斜め
  fr(pwr);delay(wt/2);
//右旋回
  rcir(pwr);delay(wt*3);
//左旋回
  lcir(pwr);delay(wt*3);
//左後斜め
  bl(pwr);delay(wt/2);
  demoend();
}

void demoend(){
  mstop();
  lcdout(GREEN,"Manual");
  demoflg=0;
}

void lcdout(int col,String msg){
  M5.Lcd.fillRect(0,0,79,32, BLACK);
  M5.Lcd.setCursor(5, 0);
  M5.Lcd.setTextColor(col);
  M5.Lcd.print(msg);
}

void face(){
 int x0=10;
 int y0=40;
 int w0=25;
 int h0=10;
 int col=TFT_ORANGE;
 M5.Lcd.fillRect(x0,y0,w0,h0,col);
 M5.Lcd.fillRect(x0+35,y0,w0,h0,col);
 M5.Lcd.fillCircle(x0+12,y0+25, 7,col);
 M5.Lcd.fillCircle(x0+12,y0+25, 5,BLACK);
 M5.Lcd.fillCircle(x0+48,y0+25, 7,col);
 M5.Lcd.fillCircle(x0+48,y0+25, 5,BLACK);
 M5.Lcd.fillTriangle(x0+30,y0+25, 30,90,50,90,col);
 M5.Lcd.fillRect(20,y0+60,40,15,col);
}

void dispvbatt(){
  vBatt= M5.Axp.GetVbatData() * 1.1 / 1000;
  M5.Lcd.fillRect(0,145,79,32, BLACK);
  M5.Lcd.setCursor(10,145);
  if (vBatt > voltAve) {
     M5.Lcd.setTextColor(GREEN);
  } else{
     M5.Lcd.setTextColor(RED);
  }
  M5.Lcd.printf("%4.2fv ", vBatt);
}

void disp3batt(int p3bt){
 int x0=20;
 int y0=30;
 int w0=40;
 int h0=120;
 int y1=22;
 int x1,w1;
 int i;

// M5.Lcd.fillRect(0,y0,79,h0,BLACK);
 M5.Lcd.fillScreen(BLACK);
 dispvbatt();
 lcdout(GREEN,"PS3Bat");

 x1=x0+1;w1=w0-2;
 if (p3bt==0xEE) {
   for(int j=0;j<6;j++){
     M5.Lcd.fillRect(x1,y0,w1,y1*5,GREEN);
     for(i=0;i<5;i++){
       M5.Lcd.drawRect(x0,y0+y1*i,w0,20,WHITE);
     }
     delay(500);
     M5.Lcd.fillRect(x1,y0,w1,y1*5,BLACK);
     for(i=0;i<5;i++){
       M5.Lcd.drawRect(x0,y0+y1*i,w0,20,WHITE);
     }
     delay(500);
   }
   M5.Lcd.fillScreen(BLACK);
   return;
 }
 i=5-p3bt;
 switch(p3bt){
   case 1:
        M5.Lcd.fillRect(x1,y0+y1*i,w1,20,RED);
        break;
   case 2:
        M5.Lcd.fillRect(x1,y0+y1*i,w1,y1*p3bt,YELLOW);
        break;
   case 3:
        M5.Lcd.fillRect(x1,y0+y1*i,w1,y1*p3bt,YELLOW);
        break;
   case 4:
        M5.Lcd.fillRect(x1,y0+y1*i,w1,y1*p3bt,GREEN);
        break;
   case 5:
        M5.Lcd.fillRect(x1,y0+y1*i,w1,y1*p3bt,GREEN);
        break;
 }
 for(i=0;i<5;i++){
   M5.Lcd.drawRect(x0,y0+y1*i,w0,20,WHITE);
 }
 delay(3000);
// M5.Lcd.fillRect(0,y0,79,h0,BLACK);
 M5.Lcd.fillScreen(BLACK);
}


◆HAT18650CでM5StickCにスケッチを書き込みするのにいちいち取り外すのが面等なので
 そのままで書き込めるようUSB Type-C から MicroBへの変換コネクタを作って内蔵してみました。
 Vbus,D-,D+,GNDの4本を接続しました。

HAT18650C-1


HAT18650C-2



・M5StickC で 接触確認アプリ (COCOA) の数を数えてみた。

◆下記参考URLよりAtom から M5StickCに変更。
◆釦Aでリスタート出来るように追加した。
◆バッテリー電圧の表示を追加した。

◆参考URL
M5 Atom Matrixで新型コロナウイルス接触確認アプリが周囲に何個あるか数えてみる


◆下記は、スーパーでのカウント数です。

4900


◆下記は、自宅にて
S__44122132


◆スケッチ例(cocoaM5StickC.ino)

#include <M5StickC.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

#define LED 10
int scanTime = 4; //BLEスキャン間隔
BLEScan* pBLEScan;

// 接触確認アプリのUUID
const char* uuid = "0000fd6f-0000-1000-8000-00805f9b34fb";
int deviceNum = 999;

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
       if(advertisedDevice.haveServiceUUID()){
            if(strncmp(advertisedDevice.getServiceUUID().toString().c_str(),uuid, 36) == 0){
                int rssi = advertisedDevice.getRSSI();
                Serial.print("RSSI: ");
                Serial.println(rssi);
                Serial.print("ADDR: ");
                Serial.println(advertisedDevice.getAddress().toString().c_str());
                Serial.println("Found!");
                deviceNum++;
            }
        }

    }
};

void disp(){    
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(10,20);M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(GREEN);M5.Lcd.print("COCOA");  
  if (deviceNum>9) M5.Lcd.setCursor(5,60); else M5.Lcd.setCursor(25,60);
  if (deviceNum>99) M5.Lcd.setTextSize(4); else M5.Lcd.setTextSize(6);
  M5.Lcd.setTextColor(WHITE);M5.Lcd.printf("%d", deviceNum);
  M5.Lcd.setCursor(0,120);M5.Lcd.setTextSize(1);
  M5.Lcd.setTextColor(CYAN);M5.Lcd.printf("BtnA=Restart");  
  M5.Lcd.setCursor(0,140);M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(GREEN);M5.Lcd.printf("Bt%3.1fV\n", M5.Axp.GetBatVoltage());
}

void Task1(void *pvParameters) {
  // loop()内に書くとボタン圧下が取れないのでマルチスレッド化してある
  while(1) {
    deviceNum = 0;
    BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
    disp();
    Serial.print("Devices found: ");
    Serial.println(deviceNum);
    Serial.println("Scan done!");
    pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory
  }
}

void setup() {
  M5.begin();
  Serial.begin(115200);
  Serial.println("Program Start...");
  pinMode(LED, OUTPUT);
  M5.Lcd.setRotation( 0 );
  M5.Lcd.fillScreen(GREEN);
  for(int i=0;i<3;i++) {
    digitalWrite(LED,LOW);delay(500);
    digitalWrite(LED,HIGH);delay(500);
  }
  disp();
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
  xTaskCreatePinnedToCore(Task1,"Task1", 4096, NULL, 3, NULL, 1);
}

void loop() {
  M5.update();
    if ( M5.BtnA.wasReleased() ) {
    // ボタンを押すとリスタート
      esp_restart();
    }
 }

************************************
*4秒周期の数と電源on(リスタート)からの
 トータルの数を表示するように追加修正


cocoa07074995

◆経過時間追加
S__44171275


◆スケッチ例(cocoaM5StickC_2.ino)
次の項目を追加修正した。
①スタートからのトータル表示
 (マックアドレスをチェックして違うものだけカウント)
②ボタンAでリスタート
③ボタンBで電源OFF
④スタートからの経過時間を表示

#include <M5StickC.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

#define LED 10
int scanTime = 4; //BLEスキャン間隔
BLEScan* pBLEScan;

// 接触確認アプリのUUID
const char* uuid = "0000fd6f-0000-1000-8000-00805f9b34fb";
int deviceNum = 0;
int cocoa = 999;
int skip_flg=0;
int hh=0;
int mm=0;
int ss=0;
unsigned long otime;

char  cocoa_buf[500][18] = {"01:23:45:57:89:ab","40:00:31:92:29:5a","01:23:45:57:89:ab","40:00:31:92:29:5a"};
char  c_buf[18] = "01:23:45:57:89:ab";

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
       if(advertisedDevice.haveServiceUUID()){
            if(strncmp(advertisedDevice.getServiceUUID().toString().c_str(),uuid, 36) == 0){
                int rssi = advertisedDevice.getRSSI();
                Serial.printf("RSSI: %d \n",rssi);
                strcpy(c_buf, advertisedDevice.getAddress().toString().c_str());
                Serial.printf("ADDR: %s \n",c_buf);
                Serial.println("Found!");
                cocoa++;
                skip_flg=0;
                for(int i=0;i<=deviceNum;i++) {
                  if (strcmp(cocoa_buf[i],c_buf)==0) {
                    skip_flg=1;
                    break;  
                  }
                }
                if (skip_flg==0) {
                  strcpy(cocoa_buf[deviceNum], c_buf);
                  deviceNum++;
                }
            }
        }

    }
};
void jikan(){
  otime = millis()/1000;
  ss=int(otime%60);
  mm=int((otime/60)%60);
  hh=int(otime/3600);
}
void disp(){    
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(10,5);M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(GREEN);M5.Lcd.print("COCOA");  
  if (cocoa>9) M5.Lcd.setCursor(5,40); else M5.Lcd.setCursor(25,40);
  if (cocoa>99) M5.Lcd.setTextSize(4); else M5.Lcd.setTextSize(6);
  M5.Lcd.setTextColor(WHITE);M5.Lcd.printf("%d", cocoa);
  M5.Lcd.setCursor(0,90);M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(YELLOW);M5.Lcd.printf("T=%d", deviceNum);
  M5.Lcd.setCursor(0,110);M5.Lcd.setTextSize(1);jikan();
  M5.Lcd.setTextColor(GREEN);M5.Lcd.printf("Time=%02d:%02d:%02d",hh,mm,ss);  
  M5.Lcd.setCursor(0,120);M5.Lcd.setTextSize(1);
  M5.Lcd.setTextColor(CYAN);M5.Lcd.printf("BtnA=Restart");  
  M5.Lcd.setCursor(0,130);M5.Lcd.setTextSize(1);
  M5.Lcd.setTextColor(CYAN);M5.Lcd.printf("BtnB=PowerOff");  
  M5.Lcd.setCursor(0,146);M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(GREEN);M5.Lcd.printf("Bt%3.1fV\n", M5.Axp.GetBatVoltage());
}

void Task1(void *pvParameters) {
  // loop()内に書くとボタン圧下が取れないのでマルチスレッド化してある
  while(1) {
    cocoa=0;
    BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
    disp();
    Serial.printf("Devices found: %d Total: %d \n",cocoa,deviceNum);
    for (int i = 0; i < deviceNum; i++){
      Serial.printf("%03d=%s \n",i+1,cocoa_buf[i]);
    }
    Serial.println("Scan done!");   
    pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory
  }
}

void setup() {
  M5.begin();
  Serial.begin(115200);
  Serial.println("Program Start...");
  pinMode(LED, OUTPUT);
  M5.Lcd.setRotation( 0 );
  M5.Lcd.fillScreen(GREEN);
  for(int i=0;i<3;i++) {
    digitalWrite(LED,LOW);delay(500);
    digitalWrite(LED,HIGH);delay(500);
  }
  disp();
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
  xTaskCreatePinnedToCore(Task1,"Task1", 4096, NULL, 3, NULL, 1);
}

void loop() {
  M5.update();
  if ( M5.BtnB.wasReleased() ) {
    // ボタンを押すとパワーオフ
      M5.Axp.PowerOff();
  }
  if ( M5.BtnA.wasReleased() ) {
    // ボタンを押すとリスタート
      esp_restart();
  }
}


以前作っていた、バランスロボットをついに立たせることが出来ました。




S__43638798

S__43638800

S__43638801


ハードは、下記のサイトを参考にしました。
M5StickC バランスロボ

ソフトは、下記のサイトを参考にしました。
実質500円&100Stepで作る超簡単「 ゆるメカトロ的 M5StickC 倒立振子」

M5StickCはMadgwickフィルタで倒立振子の夢を見るか?


■スケッチ

#include <M5StickC.h>

//PIDパラメータ調整
#define OFFSET             90.0                    // 重心 後方-←90°→+前方
#define COEF_P             35.0                    // Propotional
#define COEF_I             1.5                     // Integral
#define COEF_D             50.0                    // Differential

//Motor
#define LED_PIN            10                      // 内蔵LED

#define MOTOR_R 0x65 //A0=low, A1=open
#define MOTOR_L 0x63 //A0=high, A1=open


//MPU6886 https://github.com/m5stack/M5StickC/blob/master/src/utility/MPU6886.h
#define MPU6886_AFS        M5.MPU6886.AFS_2G       // Ascale [g]      (±2,4,8,16)
#define MPU6886_GFS        M5.MPU6886.GFS_1000DPS  // Gscale [deg/s]  (±250,500,1000,200)
#define DPS                1000                    // Gscale [deg/s]
#define CALIBRATION_COUNT  1000

//Function
#define min(a,b)           ((a) < (b) ? (a) : (b))
#define max(a,b)           ((a) > (b) ? (a) : (b))

//キャリブレ結果
double offsetX;              //Gyro X
float  base_angle;           //Accl X
//ローテーション結果
float dpsX;                  //Gyro X
float angleX;                //Accl X
//積分
double gyro_angle_x = 0;
float  preInterval  = 0;

// I2Cドライバー
void drvMotor(byte adr, int16_t pwr) { //pwr -255 to 255
  byte dir, st;
  if (pwr < 0) dir = 2;
  else dir =1;
  byte ipower=(byte) (abs(pwr)/4); //
  if (ipower == 0) dir=3; //brake

  //constrain(x, a, b) xがa以上b以下のときはxがそのまま返ります。xがaより小さいときはa、bより大きいときはbが返ります。
  ipower = constrain (ipower, 0, 63);
  st = drv8830read(adr);
  if (st & 1) drv8830write(adr, 0, 0);
  drv8830write(adr, ipower, dir);
}

void drv8830write(byte adr, byte pwm, byte ctrl) {
  Wire.beginTransmission(adr);
  Wire.write(0);
  Wire.write(pwm*4+ctrl);
  Wire.endTransmission(true);
}

int drv8830read(byte adr) {
  Wire.beginTransmission(adr);
  Wire.write(1);
  Wire.endTransmission(false);
  Wire.requestFrom((int)adr, (int)1, (int)1);
  return Wire.read();
}


void setup() {
  //初期化
  M5.begin();
  M5.Lcd.setRotation(1);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("<B-Robot v1>");
  //Motor設定
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

// I2C設定
  Wire.begin(0, 26, 400000); //SDA, SCL
//  Wire.setClock(50000);
  drvMotor(MOTOR_R,0);
  drvMotor(MOTOR_L,0);

  //MPU設定
  M5.MPU6886.Init();
  M5.MPU6886.SetGyroFsr(MPU6886_GFS);
  M5.MPU6886.SetAccelFsr(MPU6886_AFS);
  delay(1000);

  //キャリブレーション
  calibration();
//  base_angle-=1.5;
//  offsetX+=0.6;

  M5.Lcd.setRotation(2);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 140);
  M5.Lcd.printf("BT%3.1fV\n", M5.Axp.GetBatVoltage());
  face();
}

void loop() {
  static  int   Duty;
  float         power, powerP, powerD;
  static float  powerI = 0;
  double        angle;

  //MPUローテーション
  calcRotation();
  //モーター制御
  angle = angleX;
  M5.Lcd.setCursor(0, 0);
  if ((base_angle>=0)&&(base_angle<=1.0)) {
    M5.Lcd.setTextColor(GREEN);
  } else {
    M5.Lcd.setTextColor(RED);
  }
  M5.Lcd.printf(">%3.1f", base_angle);

  if ((angle - OFFSET > -20) && (angle - OFFSET < 20)) {    // 30度以下だけモーター駆動
    powerP = (angle - OFFSET ) / 90;                        // angle=0~180  → -1~1
    powerI += powerP;
    powerD = dpsX / DPS;                                    // 角速度 → -1~1
    power = powerP * COEF_P + powerI * COEF_I + powerD * COEF_D;
    power = max(-1, min(1, power));                         // -1~1
    //モーター駆動
    Duty = (int)(255* power );
    drvMotor(MOTOR_R,Duty);
    drvMotor(MOTOR_L,Duty);
    digitalWrite(LED_PIN, HIGH);
  } else {                                                  // 転倒したら停止
    drvMotor(MOTOR_R,0);
    drvMotor(MOTOR_L,0);
    powerI = 0;
    digitalWrite(LED_PIN, LOW);
  }
  M5.update();
  if (M5.BtnA.wasReleased()) {
    M5.Lcd.fillScreen(GREEN);
    ESP.restart();
    while (true) ;
  }
}

//キャリブレーション関数
void calibration() {
  float raw_gyro_x, raw_gyro_y, raw_gyro_z;
  float raw_acc_x, raw_acc_y, raw_acc_z;
  float acc_x = 0, acc_y = 0, acc_z = 0;
  offsetX = 0;
  for (int i = 0; i < CALIBRATION_COUNT; i++) {
    M5.MPU6886.getGyroData(&raw_gyro_x, &raw_gyro_y, &raw_gyro_z);
    M5.MPU6886.getAccelData(&raw_acc_x, &raw_acc_y, &raw_acc_z);
    //ベースプログラムとM5StickCの組付け位置の違いによる座標系変換含む
    offsetX += -raw_gyro_x;
    acc_x   += -raw_acc_x;
    acc_y   +=  raw_acc_y;
    acc_z   += -raw_acc_z;
  }
  //gyro offset
  offsetX /= CALIBRATION_COUNT;
  //加速度から角度を算出
  acc_x   /= CALIBRATION_COUNT;
  acc_y   /= CALIBRATION_COUNT;
  acc_z   /= CALIBRATION_COUNT;
  base_angle = (atan2(acc_y, acc_z + abs(acc_x)) * 360 / 2.0 / PI) + 180;
}

//MPUローテーション関数
void calcRotation() {
  float raw_acc_x, raw_acc_y, raw_acc_z, raw_t, raw_gyro_x, raw_gyro_y, raw_gyro_z ;
  float acc_angle_x;
  M5.MPU6886.getGyroData(&raw_gyro_x, &raw_gyro_y, &raw_gyro_z);
  M5.MPU6886.getAccelData(&raw_acc_x, &raw_acc_y, &raw_acc_z);
  //ベースプログラムとM5StickCの組付け位置の違いによる座標系変換
  raw_gyro_x *= -1;
  raw_acc_x  *= -1;
  raw_acc_z  *= -1;
  //
  acc_angle_x = (atan2(raw_acc_y, raw_acc_z + abs(raw_acc_x)) * 360 / 2.0 / PI) + 180;
  dpsX = raw_gyro_x - offsetX;
  //積分
  float now_time = millis();
  float interval = now_time - preInterval;  //前回からの経過時間
  preInterval = now_time;
  gyro_angle_x += dpsX * (interval * 0.000999999);
  //相補フィルター
  angleX = (0.996 * gyro_angle_x) + (0.004 * acc_angle_x) + base_angle;
}

void face(){
 int x0=10;
 int y0=40;
 int w0=25;
 int h0=10;
 int col=TFT_ORANGE;
 M5.Lcd.fillRect(x0,y0,w0,h0,col);
 M5.Lcd.fillRect(x0+35,y0,w0,h0,col);
 M5.Lcd.fillCircle(x0+12,y0+25, 7,col);
 M5.Lcd.fillCircle(x0+12,y0+25, 5,BLACK);
 M5.Lcd.fillCircle(x0+48,y0+25, 7,col);
 M5.Lcd.fillCircle(x0+48,y0+25, 5,BLACK);
 M5.Lcd.fillTriangle(x0+30,y0+25, 30,90,50,90,col);
 M5.Lcd.fillRect(20,y0+60,40,15,col);
}


・最近の新型コロナの影響で非接触型の温度計を作ってみました。
 毎日の体温の目安になるのではと思っています。

■画面の説明
上から
①周囲温度
②ターゲット温度
③MIN,MAX温度
④釦Aを押したときのターゲット温度(Hold)

*釦Bを押すとリスタートし、データをクリア出来ます。


S__43614232


S__43614231


mlx90615


■使用部品
①M5StickC
③ピンヘッダ、ピンソケット(手持ちの部品)


■ライブラリは、下記のものを使いましたが、そのままではエラーが発生しましたので
ヘッダーファイルを編集しました。


下記の三つのファイルを同じフォルダに保存してコンパイルしました。
mlx90615.ino
mlx90615.h
mlx90615.cpp

■スケッチ

//mlx90615.ino

// 2020/03/06 Yoshito Hashiguchi

#include <M5StickC.h>
#include <Wire.h>
#include "mlx90615.h"

MLX90615 mlx;

float maxtemp=0.0;
float mintemp=100.0;
float hold=0.0;
float objtemp=0.0;
float ambitemp=0.0;

void setup() {

  Serial.begin(115200);
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(BLACK);

  Wire.begin(0, 26, 400000); //SDA, SCL
  Serial.println("Melexis MLX90615 infra-red temperature sensor");
  Serial.print("Sensor ID number = ");
  Serial.println(mlx.get_id(), HEX);
}

int ct=0;

void loop() {

  M5.update();
  if ( M5.BtnA.wasPressed() ) {
    hold=mlx.get_object_temp();
  }
  if ( M5.BtnB.wasPressed() ) {
    esp_restart();
  }
  ct++;
  if (ct>100) {
    ambitemp=mlx.get_ambient_temp();
    objtemp=mlx.get_object_temp();
    if (mintemp>objtemp) mintemp=objtemp;
    if (maxtemp<objtemp) maxtemp=objtemp;
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0,0);
    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN); 
    M5.Lcd.printf("ambient temp\n");
    M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);   
    M5.Lcd.printf("%3.1f\n",ambitemp);
    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN); 
    M5.Lcd.printf("\nterget  temp\n");
    M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);   
    M5.Lcd.printf("%3.1f\n",objtemp);
    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(CYAN);  
    M5.Lcd.printf("\nBtnB=Restart\n");
    M5.Lcd.setTextColor(GREEN); 
    M5.Lcd.printf("Battery:%3.1fV\n", M5.Axp.GetBatVoltage());

    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN);  
    M5.Lcd.printf("\n min   max\n"); 
    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(WHITE);   
    M5.Lcd.printf(" %3.1f  %3.1f\n",mintemp,maxtemp);
    
    M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(CYAN);   
    M5.Lcd.printf("\nBtnA = hold\n");
    M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);     
    M5.Lcd.printf("%3.1f\n",hold);

 
    ct=0;
  }
  delay(10);
}

■ヘッダーファイル

//mlx90615.h

#ifndef __MLX90615_H__
#define __MLX90615_H__

#include "Wire.h"

// default MLX90615 I2C address
#define MLX90615_I2C_ADDR 0x5B

// ROM - ID number
#define MLX90615_REG_ID_LOW 0x1E
#define MLX90615_REG_ID_HIGH 0x1F
// RAM - ambient temperature register
#define MLX90615_REG_TEMP_AMBIENT 0x26
// RAM - object temperature register 
#define MLX90615_REG_TEMP_OBJECT 0x27

class MLX90615 {
 public:
  MLX90615(uint8_t i2c_addr = MLX90615_I2C_ADDR);
  void begin();
  uint32_t get_id();
  float get_object_temp();
  float get_ambient_temp();

 private:
  uint8_t i2c_addr_;
  uint16_t read_word16(uint8_t reg);
};
#endif // __MLX90615_H__

■C++ソースファイル

//mlx90615.cpp

#include "mlx90615.h"

MLX90615::MLX90615(uint8_t i2c_addr) {
  i2c_addr_ = i2c_addr;
}

void MLX90615::begin() {
  Wire.begin();
}

uint32_t MLX90615::get_id() {
uint32_t id;
id = read_word16(MLX90615_REG_ID_LOW);
id |= (uint32_t)read_word16(MLX90615_REG_ID_HIGH) << 16;
return id;
}

float MLX90615::get_ambient_temp() {
  float temp;
  temp = read_word16(MLX90615_REG_TEMP_AMBIENT) * 0.02 - 273.15;
  return temp;
}

float MLX90615::get_object_temp() {
  float temp;
  temp = read_word16(MLX90615_REG_TEMP_OBJECT) * 0.02 - 273.15;
  return temp;
}

uint16_t MLX90615::read_word16(uint8_t reg) {
  uint16_t data;
  Wire.beginTransmission(i2c_addr_);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(i2c_addr_, (uint8_t)3);
  data = Wire.read();       // read low byte
  data |= Wire.read() << 8; // read high byte
  Wire.read(); // read and discard PEC (packet error code)
  return data;
}





・M5StickC FM90 BD6211 バッテリー を使って倒立振子を作ってみました。

■最初M5StickCのバッテリーだけで実験していたのですが、直ぐに切れてしまい
 面倒だったので、バッテリーを積んでみました。









br-1


br-2


■使用部品
①M5StickC
②ギヤードモーター FM90(秋月)
③タイヤ FS90R対応用(秋月)
④フルブリッジドライバ 7V BD6211F-D2(秋月)
⑤SOP8P DIP変換基板(秋月)
⑥バッテリー3.7V 1600mAh(中古モバイルバッテリー部品取り)
 ・バッテリーは、HAT端子に接続
⑦3Dプリンタ作成ケース
⑧その他配線部品等


参考は、下記URL


この中のパラーメータ変更
//PIDパラメータ調整
#define OFFSET 86.0  //91.5=>86.0 // 重心 後方-←90°→+前方
#define COEF_D 55.0 //50  ->55   // Differential

  //キャリブレーション
  calibration();
  base_angle-=1.2; //追加調整
  offsetX+=0.6;          //追加調整


遂に手に入れました。年と共に目も手も衰えて来ましたので文明の利器に頼ろうと思いました。
非常に楽に取れます。


S__43474953


自動走行するラジコンカー DonkeyCar 3 を作成途中ですの備忘録を順次残しておきます。

参考URL:FaBo DonkeyCar Docs(1.DonkeyCar3の構築)


1.まずラズパイのイメージを作成します。
 すでにDonkeyCar3の入ったイメージファイルを下記よりダウンロードします。 
  donkey3_1_tf1_13_1.zip 

 ①ダウンロードしたをRaspberry Piのイメージを解凍し、microSDカードに焼き込みます。
 ②焼き込みが、終わったら、一回抜いて、再度挿入し、下記の内容のWiFi設定ファイル 「wpa_supplicant.conf」をテキストエデッタで作成しセーブします。


country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="your network name" <---ここにSSID
    psk="your password"       <---ここにパスワード
    scan_ssid=1
}

 ③出来たら、SDカードをDonkeyCarのRaspPi3に挿入し、電源をOnにします。
 ④PCよりssh pi@donkeypi.localで繋ぎます。IPアドレスが分かれば、IPアドレスでもOK
 ⑤パスワードは、「raspberry」です。

ーーーー次回


・IBM Watson が提供するプログラム可能なボール紙ロボット TJBot を作ってみました。

 完成後の写真(左右逆となっている)

S__43261962


S__43261964


・下記URLに作り方が、出ています。

1.まず、厚紙の型を作る必要があったので、型のPDFデータをダウンロードします。
 ①原紙がB4の為、私の持っているA4プリンタで印刷できない事から、A4二枚に半分に
  分け、PDF出力後、A4の紙に印刷しました。
 ②それを写真の工作用紙に貼り付け、カッターでカットしていきました。
 ③カラー用紙の裏面に張り付けたため、完成時、逆の物となりました。

 100均Candoで購入

S__43261968

S__43261967


2.インストールガイドを参照しながらインストールしていきましたが、二年前のガイドだったので所々うまくインストール出来無かったので、ここに現時点でのやり方を書いておきます。
 ①まず、最新版のイメージをダウンロードし、SDカードに書き込みます。
 ②環境を日本語にし、WiFiに接続します。
 ③ガイド「3.4.4 OS と Node-RED の更新」 の手順通りOSをアップデートします。

 $ sudo apt-get update
 $ sudo apt-get upgrade
 $ sudo apt-get dist-upgrade
 $ update-nodejs-and-nodered <---が、うまくいかない。

 ④node-red関係のアップデートが、ガイドのやり方でうまくいかず、
  下記URLを参考に次のコマンドでアップデートしました。

  $ bash <(curl -sL https://raw.githubusercontent.com/node-red/raspbian-deb-
   package/master/resources/update-nodejs-and-nodered)
 
 ■参考URL:Raspberry Pi のNode-REDをアップデートorインストールする方法


 ⑤ガイド「3.5 Node-RED に必要なモジュールを追加する」
  をガイドにそって実行します。

    *コピペ用に記載します。
$sudo npm --unsafe-perm -g install node-red-node-watson node-red-contrib-camerapi
$sudo reboot 

 ⑥ガイド 「3.6 オーディオとカメラの設定」
  をガイドにそって実行します。

    *コピペ用に記載します。
$sudo nano /boot/config.txt
dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4

$amixer -D hw:1 sset Mic 100% 
$amixer -c0 sset PCM 100% unmute

 *オーディオの再生確認用
$speaker-test -t sine -f 600
$aplay /usr/share/sounds/alsa/Front_Center.wav

 *音が出なかった時など、下記を参考にしました。
   Raspberry Pi で再生と録音を行う


 ⑦ガイド「5. Watson とつないだフローを動かす」
  ここから、ガイドには、「ユーザー名」と「パスワード」を入力するように
  有りますが、IBM Cloud のダッシュボードの中に見つかりませんでした。
  色々試したところ、この二つは、ブランクで、APIKeyの入力が必要でした。
  これで、動くことが分かりました。

注)サンプルの中のvisual.json(画像認識)の顔認識でエラーが出て実行できません。
  原因は、 昨年途中からWatson VRの顔認証機能が廃止されたようです。

3.TJBotを電源ON(起動)した時や、リブートした時、node-redに接続するタイミング
 を把握するために、緑のLEDを点灯させ、IPアドレスをスピーカーから発生してくれ
 るようにしてみました。

 ①まず下記URLを参照して日本語音声合成をインストールする。

 ②次に下記シェルスクリプトを作成する。(コピペする)

pi@raspberrypi:~ $ sudo nano jtalk.sh

#!/bin/sh
TMP=/tmp/jsay.wav
echo "$1" | open_jtalk \
-m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice \
-x /var/lib/mecab/dic/open-jtalk/naist-jdic \
-ow $TMP && \
aplay --quiet $TMP
rm -f $TMP

 ③実行権を設定する。
pi@raspberrypi:~ $ sudo chmod +x jtalk.sh

 ④次にIPアドレスを取得し、音声案内と緑のLEDを点灯させる下記、
  自動実行シェルスクリプトを作成する。

pi@raspberrypi:~ $ sudo nano autorun.sh

#!/bin/sh
ipadd=""
while [ "$ipadd" = "" ]
do
    ipadd=`ip a show wlan0 | grep "inet " | cut -f6 -d ' ' | cut -d '/' -f1`
    wait 1
done
gpio -g mode 23 out
gpio -g write 23 1
rtn=`/home/pi/jtalk.sh IPアドレスは${ipadd}です`


 ⑤実行権を設定する。
pi@raspberrypi:~ $ sudo chmod +x autorun.sh


 ⑥最後に、起動時に自動実行するように「/etc/rc.local」の「exit 0」の前に追記する。

pi@raspberrypi:~ $ sudo nano /etc/rc.local

#!/bin/sh -e
.
.
sh /home/pi/autorun.sh & <---この行を追記する。

exit 0

 以上で起動時にIPアドレスを教えてくれる。


4.これから、色々プログラムして行きたいと思います。

 以上

・Pch パワーMOSFET (2SJ334) を使って 暗くなるとスイッチが入る ハイサイド・スイッチ を作ってみた。

 *一応仕様としては、5V~24V 5A程度と考えています。
 *2SJ334の規格は、60V 30A

①回路図を下記に示します。
mosfetsw
②タッパーの中に組み入れてみました。

mosfet

・I2C Wemos D1 mini Motor Shield 使かってみた

・ESP32 (M5Stack) で 安価な I2C モータードライバ を使ってみました。
 *Wemos D1 mini Motor Shield (i2c interface, TB6612 based) 


msr1


1.安価なのは良いのですが、そのままではM5STACKで認識しない事がわかりました。
 よって、Motor Shieldのファームウェアの再プログラミングが、必要な事が分かりました。

 次からの説明は、Windows10 x64で実行した物です。

2.最初に下記より書き込みツールをダウンロードします。
 STシリアルブートローダーを使用したSTM32用のオープンソースフラッシュプログラム
 (stm32flash.exe)

3.次にバイナリファイルを下記よりダウンロードします。
 *書き換えにより、アドレス設定用のランドは、無効となります。
 標準のものは、I2Cアドレスが、「0x30」に固定の為、複数枚使う場合は、アドレスごと(アドレス0x2d、0x2e、0x2f、0x30)にコンパイルされたファームウェアが、必要です。
 下記は、4つの異なるアドレスのファームウェアファイルを含んだzipファイルです。
https://github.com/pbugalski/wemos_motor_shield/files/1434679/motor_shield_XX_bin.zip

4.2,3のファイルを同じフォルダに解凍、展開しておきます。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2019/12/19  10:26             3,116 motor_shield.bin
2017/11/02  00:39             3,104 motor_shield_2D.bin
2017/11/02  00:39             3,104 motor_shield_2E.bin
2017/11/02  00:39             3,104 motor_shield_2F.bin
2017/11/02  00:39             3,104 motor_shield_30.bin
2016/02/11  23:44               280 README.txt
2016/02/11  22:04            13,265 stm32flash-manual.pdf
2016/02/10  17:14           252,776 stm32flash.exe
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

5.Motor Shield に書き込むための前準備をします。
 ① Wemos D1 Miniを使う場合

 ①-1  空のスケッチをD1 Miniにアップロードします。これにより、シリアルデータがUSBポートからMotor Shield に、D1 Miniを経由して渡されます。

空のスケッチ:
//------------------------------------------------------------------------
void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}
//------------------------------------------------------------------------

 ①-2 D1 Miniとの結線
 *下記の写真のように結線します。
 *Motor Shield を書き込みモードにするためにRTSは、3vに結線します。

S__43188227


 ② USB-SERIALインターフェースを使う場合
 
 *下記の写真のように結線します。
 *Motor Shield を書き込みモードにするためにRTSは、3vに結線します。

S__43196430


参考:Motor Shield Rev.1.0.0 回路図
motorshieldRev1

 
 ③次のコマンドを実行します。
  重要:COM6を自分のCOMポート番号に置き換えて下さい。 
  *COMポートをデバイスマネージャにて確認


1>C:\motorshield\stm32flash-0.5-win64>stm32flash.exe COM6
*ボードを認識しているか確認します。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stm32flash 0.5

http://stm32flash.sourceforge.net/

Interface serial_w32: 57600 8E1
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0444 (STM32F03xx4/6)
- RAM        : 4KiB  (2048b reserved by bootloader)
- Flash      : 32KiB (size first sector: 4x1024)
- Option RAM : 16b
- System RAM : 3KiB
-------------------------------------------------------------------------------------

2>C:\motorshield\stm32flash-0.5-win64>stm32flash.exe -k COM6
*これにより、シールドのロックが解除されます。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stm32flash 0.5

http://stm32flash.sourceforge.net/

Interface serial_w32: 57600 8E1
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0444 (STM32F03xx4/6)
- RAM        : 4KiB  (2048b reserved by bootloader)
- Flash      : 32KiB (size first sector: 4x1024)
- Option RAM : 16b
- System RAM : 3KiB
Read-UnProtecting flash
Done.
-------------------------------------------------------------------------------------

3>C:\motorshield\stm32flash-0.5-win64>dir
*ファイル名の確認
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ドライブ C のボリューム ラベルは S3SDD00-960 です
 ボリューム シリアル番号は 44C7-1584 です

 C:\motorshield\stm32flash-0.5-win64 のディレクトリ

2020/01/04  13:17    <DIR>          .
2020/01/04  13:17    <DIR>          ..
2019/12/19  10:26             3,116 motor_shield.bin
2017/11/02  00:39             3,104 motor_shield_2D.bin
2017/11/02  00:39             3,104 motor_shield_2E.bin
2017/11/02  00:39             3,104 motor_shield_2F.bin
2017/11/02  00:39             3,104 motor_shield_30.bin
2016/02/11  23:44               280 README.txt
2016/02/11  22:04            13,265 stm32flash-manual.pdf
2016/02/10  17:14           252,776 stm32flash.exe
2020/01/04  13:17                56 wemos_motor_shield.txt
               9 個のファイル             281,909 バイト
               2 個のディレクトリ  492,288,159,744 バイトの空き領域
-------------------------------------------------------------------------------------

4>C:\motorshield\stm32flash-0.5-win64>stm32flash.exe -f -v -w motor_shield_2F.bin COM6
*ここで希望のI2cアドレスのファイルを、書き込みます(例では「0x2F」)。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stm32flash 0.5

http://stm32flash.sourceforge.net/

Using Parser : Raw BINARY
Interface serial_w32: 57600 8E1
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0444 (STM32F03xx4/6)
- RAM        : 4KiB  (2048b reserved by bootloader)
- Flash      : 32KiB (size first sector: 4x1024)
- Option RAM : 16b
- System RAM : 3KiB
Write to memory
Erasing memory
Wrote and verified address 0x08000c20 (100.00%) Done.
-------------------------------------------------------------------------------------


6.書き込みが終わったら、配線をM5Stackに繋ぎ変え、I2Cで認識するか確認します。

*「0x2F」「0x30」として、認識しています。

S__43188229

確認用のスケッチ:
//------------------------------------------------------------------------

#include <M5Stack.h>

void setup() 
{
  M5.Lcd.begin();
  M5.Lcd.setRotation(1);
  M5.Lcd.fillScreen( BLACK );
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(YELLOW);  
  M5.Lcd.setTextSize(3);

  M5.Lcd.fillScreen( BLACK );
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("M5Stack I2C Tester");
    
  Wire.begin();

  delay(3000);
  M5.Lcd.fillScreen( BLACK );
}

int textColor=YELLOW;

void loop() 
{
  int address;
  int error;
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("scanning Address [HEX]");
  
  for(address = 1; address < 127; address++ ) 
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if(error==0)
    {
      M5.Lcd.print(address,HEX);M5.Lcd.print(" ");
    }
    else M5.Lcd.print(".");

    delay(10);
  }

  if(textColor==YELLOW) textColor=GREEN;
  else textColor=YELLOW;
  M5.Lcd.setTextColor(textColor,BLACK);
  delay(5000);  
}
//------------------------------------------------------------------------

 
7.ライブラリは、下記よりダウンロードします。
 Wemos D1ミニモーターシールド(i2cインターフェイス、TB6612ベース)用のArduinoライブラリ。

参考URL:モーターシールドの再プログラミング


ESP32で懐かしいCP/Mを動かしてみました。

①ESP32でVGA出力が、出来て、PS2のキーボードとマウスが使えました。
 (USB-PS2変換コネクタ経由のUSBマウスは、使えませんでした。)
②CP/Mの中にあったMBASICを久しぶりに動かしてみました。
③インベーダーゲームも懐かしく出来ました。

ライブラリは、下記URLよりダウンロードできます。
https://github.com/fdivitto/fabgl?fbclid=IwAR13n8KkWvauuTyDwfkfc8cty6OnW5jge6WlB1tMEiuNtNm7l6CjAcc_jfg


CP/Mの起動画面

S__43139082


・懐かしのインベーダーゲームも出来ちゃいます。

S__43147266


・修正版回路図
元図の中に、手持ちの抵抗値が無かったので、朱書きの値の物を使いました。
400Ω --> 390Ω
800Ω --> 820Ω
2KΩ ---> 2.2KΩ

ESP32_VGA



・元の回路図


schema




・リモコンカーをジョイスティック (HP3-185-A) でコントロールするために、
 ESP32 を内蔵させて BLE 対応に改造してみました。


S__42303500



 ・改造前
jt改造前


 ・改造後
jt改造後1

 ・改造後 バッテリーを追加
jt改造後2


FOLYWLIJEOILQ5U.LARGE





・BeetleCをM5StickCにジョイスティックを付けてBLE経由で遊んでみました。
 あと左右の動きが、イマイチでしたので後輪を取り外し、キャスターを取り付てみました。






beetleC改造-2




・ダイソーで売っているスマホ用Bluetoothリモートシャッター(AB Shutter3)改造しました。

改造前
 9ピン・iOSボタン    :VolumeUp キー を押したことと等価
 8ピン・Androidボタン:先に Returnキー(Enterキー)を押す(EnterしてVolumeUp)


この改造で
 9ピン・iOSボタン    :VolumeUp  キー を押したことと等価
10ピン・Androidボタン:VolumeDownキー を押したことと等価
となり、リモートでボリュームのコントロールとカメラのシャッターを操作することが
出来るようになりました。

改造後
ABS3-3


改造前
ABS3-1

ABS3-2


基板上でのパターンは、8ピンがAndroidボタン、9ピンがiOSボタン、10ピンがオープン、
11ピンがGNDといった接続。

ABSt3



中のIC「ST17H26」のピン配置図とピン仕様

ST17H26-1

ST17H26-2


詳しくは、下記サイトで

・M5StickC 搭載 BeetleC に近接センサ「APDS9930」を取付し、
 PS3のコントローラで遊んでみました。
 まだまだ、スピードが出すぎて、コントロールは難しいです。






APDS9930

・M5StickC 搭載 web 操縦 リモコンカーを作ってみました。

前方衝突防止にAPDS9930近接センサーを搭載しました。
約10cmに壁があると停止又はバックするようにしました。
動画は、デモモードで自動で動いています。




wificar2




wificar3

・M5StickC を使って WiFi 操縦 ミニカーを作ってみました。

S__41738280

S__41738281


前面に近接センサーを付け、追突を防止しようと考えました。
そのため、前後で i2c を 2チャンネル 使う方がスマートにできそうです。

最初は、「M5StickC非公式日本語リファレンス」を参照して
下記のスケッチでテストしましたが、ダメでした。
..

M5.begin();
Wire.begin(32, 33); //SDA,SCL
Wire1.begin(0, 26); //SDA,SCL
...

色々テストしていて、理由は、分かりませんが、
一応両方動くようになりました。

Wire1.begin(SDA,SCL)は、使わないで
1.setup()の初期化の時に、それぞれWire.begin(32,33)/(0,26)を設定
2.loop()の中では、近接センサを呼び出す前に都度Wire.begin(32,33);設定
3.終わったらWire.begin(0,26);を再設定する
void setup() {
.......
M5.begin();
Wire.begin(32, 33); //SDA,SCL
// Initialize APDS-9930 (configure I2C and initial values)
if ( apds.init() ) {
Serial.println(F("APDS-9930 initialization complete"));
} else {
Serial.println(F("Something went wrong during APDS-9930 init!"));
}
Wire.begin(0, 26); //SDA,SCL
Wire.setClock(50000);
drvMotorL(0);drvMotorR(0); // STOP
.......
}
void loop(){
.....
Wire.begin(32, 33); //SDA,SCL
if ( !apds.readProximity(proximity_data) ) {
Serial.println("Error reading proximity value");
} else {
Serial.print("Proximity: ");
Serial.println(proximity_data);
}
if (proximity_data>500) {
x=0;y=0;
}
Wire.begin(0, 26); //SDA,SCL
.....
}

・ダイソーのおもちゃ自動車にM5StickCを積んで、スマートフォン(blynk)から
 コントロールして遊んでみました。

・さらに小さいのも作ってみました。


bcar-0


bcar-01


bcar-1


bcar-2



bcar-c1


bcar-c2


bcar-c3





倒立振子(バランスロボット)を作ってみました。
ハードは、出来たのですが、ソフトがうまくいかずまだ自立してません。
これからが、大変です。


b-robot-1



b-robot-2




b-robot-3




b-robot-4


・100均のおもちゃでも倒立振子に挑戦
 こちらも、まだ自立しません。

S__41271302


S__41271301


S__41271300





・M5Stack で ニキシー管 時計 を作ってみました。

下記のブログを参照して、ニキシー管時計を作ってみました。
ニキシー管としてはあり得ませんが、画像として、年月日を追加して、
表示してみました。

詳細は下記のブログを参照下さい。
macsbug M5Stack Nixie tube Clock:

年月日を追加した表示の写真


M5Stack_Nixie_tube_Clock


・水センサーを使って、Ambientへ雨と土壌の湿度を送ってみた。


1.実際は、雨用と土壌用を並列に繋いであります。

試験時の写真①
試験時①

試験時の写真②
試験時②

水センサーの回路図
回路図

雨センサー設置写真
雨センサー

土壌センサー設置写真
土壌センサー


・ESP-01用自動書き込み器

ESP-01を自動で書き込む為の回路を追加した。

①CHG340より、DTR,RTSを引き出し
②デュアルMOSFET μPA2753GR(10V4A)に接続し、
③ESP-01のRST,GPIO0に接続し
自動でスケッチを書き込めるようにした。


      ↓↓↓↓ ESP-01 & リレー ↓↓↓↓

ESP-01-Relay


     ↓↓↓↓ CHG340 & uPA2753GR 写真 ↓↓↓↓

CHG340自動書き込み器

     ↓↓↓↓ CHG340 & uPA2753GR 回路図 ↓↓↓↓ 
ch340gAutpprog

ドコモ フォトパネル04(800x480 50P)と
NTT フレッツ・ミルエネ専用端末 WDPF-703SE-E(800x480 50P)の
液晶パネルを、HDMIモニタとして復活させました。

また、ESP32のI2C接続で、コントローラ(RTD2662)のファームウェアの
読み書きが出来るようになりました。

これから色々遊びたいと思っています。


HDMI VGA 2AV 信号入力コレクタ基板 コントローラ基板
https://www.amazon.co.jp/gp/product/B06XWVNBXT/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1




raspi-hdmi

RTD2662-HDMI-LCD

「BME280」のアドレス変更(0x77)

温度・湿度・気圧センサーのBME280をI2C接続する時のアドレスは、初期状態で
0x76となっています。(SDO/AD0がGNDに繋がっている。)
このセンサーを2個つなぎたい場合、アドレスの変更が必要です。
下の写真のように、パターンカットと半田ブリッジすることで0x77に変更出来ます。
(SDO/AD0をVDDIOに繋ぐ。)


bme280-0x77

「wifi中継器」を作ってみました。
ESP32やESP8266でWiFiを使って色々遊んでいて、他でデモなどする時に自宅の開発環境が
使えた方が良い時があります。こんな時に役に立ちそうなWiFi中継器をesp8266へ入れてみました。

(単純に自宅内のWiFiの電波の弱い所をカバーするWiFi中継器としても使えます。)
下記は、備忘録です。
http://skyzoo.sx3.jp/hp/esp32.html#a10


esp_wifi_rep

ESP32でwifi関連のプログラム組んでいて、そのままシリアルモニタでデバッグする時、
win10自作PCのUSB電源容量不足で度々リブートしまいます。

それを防ぐために外部AC電源アダプタを繋げるようにしてみました。
(PC側5Vラインは、繋いでいません。)

S__39804947

aitendoで面白い基板を見つけました。(3枚で100円)
勝手に命名しました装置名「Nenaizo」
Iot機器をモバイルバッテリーで試験する時、消費電流が少なくてバッテリーがスリープしてしまう事があると思います。
それで、単純に100Ω2本をパラに接続し50Ωにして、100mA流し続けスリープを防止する物を作ってみました。
もっとスマートにするには、下記のような物もあります。
https://github.com/akita11/USBload


S__39804950

↑このページのトップヘ