2021年2月21日日曜日

Nucleo64+ArduinoLCD+MPU6050の明日はどっちだ?

 お仕事関係で移動データを記録する必要があり、年末年始にArduino UnoにMPU6050とu-bloxのGPSつなげて、USB経由でデータを垂れ流すってのやりました、と言うのをチラッと言ったかと思います。でも、作りましたったって、Unoコンパチ機を使い、ユニバーサルになってるシールドにお馴染みGY-521とGPS6MV2をのせて、ソフトを切り貼りしただけのものなんですが、こいつから出てくるデータを眺めていて、『6軸の加速度センサって、そういや初めて使うや』、つまり、出てくるデータの意味を判ってない、と。
データ分析のために、本体上にロギング必要よね、あとで見るために、ということなんですが、今までいじってきたLCDモジュールにはすべてSDカードソケットがついているとはいえ、記録開始などの仕組みとしてハード的なボタン?タッチパネル?など考えたりすると、 Arduinoの少ないピン数とCPUパワー、箱も含めると結構大変そう。

とにかく持ち運べないと意味ないので、ブレボのままとはいかず箱には入れないと。そうすると、Arduino用の8bitパラレルのLCDシールドを使うと、結構しっかりArduinoと噛んでくれるんで、あとはMPU6050関係と電源と…と少し負担は減りそうですが、LCDは使わないピンまで一緒になっている関係で、さらに周辺を加えるとなると…と、結構考えあぐねてしまう事になりました。

そこで、以前(数年前)、ちょいとしたコントローラに、STMのNucleo-64(F411RE)を使ったことを思い出し、あれならArduino感覚で… 実際にArduino IDEでも使えるんですが、mbedでも切り張り的コーディングで結構動かせたよな、手元に、mbedが宣伝され出した頃のLPC11U35なボードがあるのでまずはそれで…なんて思ったのが運の尽き。
途中で思い出したんですが、あんときゃArduinoIDEでもmbedでもなく、SystemWorkbenchとCubeをつかったんだった…。

 

LCDから始めよう

まぁLCDじゃなくても良かったんですが、Arduinoでもその他Linuxが動くSBCでも動かしている経緯があるので、そこで使っているモジュールなら、何となく勘どころは判ります。で、オンラインIDEを使い、UniGraphic(これはArduino用のライブラリと類似)を使って確認します。
UniGraphicにはテスト用のコードが存在してるんですが、そこでは、シリアルを使ってデバッグメッセージ出力します。コードをオンラインIDEでビルドすると、「は?wait_ms()? Serial()? mbed2はもう古いからとっととmbed5に移れや」的なことを言われます。はいぃぃ?

 

mbed "6" ?????

このあたりなんの前知識もなく着手してしまったわけですが、そもそもmbed5ってどうすんの?オンラインIDE使えないの?と右往左往し、結局、CLI使うかmbed Studioなのね、ということになります。で、x86が必要、と。作業環境はまずaarch64なので、ほぼそこで挫折しかかったんですが、まぁ、仕方ないか、と。

大して使わないけど時に必要になる、エントリクラスのWindows機に、外付けUSBでLinuxをインストール(いや別にWindowsにインストールしても良かったんですが、あんまりWindowsでコードいじりたい、という気持ちがなく…)し、mbed Studioインストール、mbedOSは6.7.0でした。

で、驚くことにLPC11U35もmbed5ではサポート外となっており使えない。えぇぇ?? そこで仕方なくF411RE。

LCDには、F411REがきたこともあり、SPIタイプのILI9341じゃなく、8bitパラレルのILI9341を使います、遅いんですが。でもこれを使うと、LCDをどう安定して接続するか、という問題から逃げやすく、microSDソケット、I2CにLM75Aがついていて他のテストもできるね、というところです。
抵抗式ですがタッチパネルがついている(SPIのものでもタッチパネル自体はついてるんだけど、コントローラが乗ってない)のですが、これを読むにはアナログポートが必要だということと、LCDの制御信号、データと共用されているため、どうやったら手を出せるか、それを使うなら、改めて考える必要がありそうですので棚上げしておきます。

 

そしてどつぼに…

まぁ、UniGraphicは良いんですよ。それとLM75A動かして温度見るまでは比較的問題なく、シールド側でI2Cはプルアップされているので、MPU6050いやGY-521のI2Cに入っているプルアップを外してやれば、あとはreadとwriteやればデータは読み出せるので。
LM75AにしてもMPU6050にしても、先人がライブラリを作ってくれているんですが、低レベルのi2cコマンドのwrapperを作る際、『お作法』がmbedでは共通化されていないのかそれともmbed2 - mbed5の呪縛なのか、まちまちな実装になっています。結果、複数のI2Cデバイスをハンドリングしようとすると逆に、i2cデバイスなのに引数が違ったり、wrapper対策が必要になったりして全体的な見通しが悪くなる問題があります。このため、I2Cデバイスはすべてread()/write()でインターフェースすることにしました。
ここまでで、LM75AからもGY-521からも、データを読み出しLCD上に表示するところまで、実装できました。UniGraphicなどにあったwait_ms()は単にすべて1000倍してwait_us()を使うようにしてあることもあるとは思いますが、少し重たい感じになってます。

問題はSDカード。

まず、シールド型のLCDのSDカードは、ICSPのSPIを使用していて、加えてCSが、Nucleo側でSPIに割り当てられないところにでています。Nucleo-64はUno互換コネクタがあるとはいえICSPなんてサポートしてないので、 ICSP部分に普通の2.54mmピッチピンヘッダを置こうとすると、Nucleo-64自身と干渉して、シールドを搭載できません。
このため、シールドを分解してICSP用の6pinピンソケットを外し改めて配線などするか、ICSPピンソケットからL字のピンヘッダ(モジュールに使用して、基板と平行にピンを引き出すためのもので、普通にL字ヘッダとして売られているものとは異なり、短辺側に、プラスチックの連結構造があるタイプが必要です)を使用してSPIを引っ張り出す必要があります。
シールドは、基板とLCDがクッション入りの両面テープでしっかり貼られており、これを分解しようとして以前LCDを破損したことがあるのでその方法はとらず、ピンヘッダを使う事にします。
また、LCDのArduinoコネクタから、SDcardのCSがいる部分のピンを引っこ抜いて、あらためケーブル設置し、SPI CSとして利用できるようにします。ついでにICSPにあるLCDリセットも、Nucleoのリセット端子につなげてやります。これでILI9341にも確実にリセットをかけられます。

 



 

ハードウエア的な作業は以上で、Nucleo側は、SPI2が使いやすい位置にあるのでそれ使いたいんですが、それだけでSPI2を使ってSDカード使えるのかと思ったらさにあらず、ここからが本番。

どうやってmbed5(以降、のつもり)でSDカード扱うのか探してみると、まずSDFileSystemが引っかかります。なになに?、これを使うには、mbed_add.jsonに宣言が必要?、はいはい、 あれ、でもそんなファイル無いと調べると、プロジェクトルートに新規に作成しろと。書き方もよく判んないけど、はいはい。
で、ライブラリをインポートしてみると、その時点でエラー起こしてるじゃん。はて?、と思うとこいつはもうmbed6では使えない、mbed6ではSDBlockDeviceを使え、それと、ボードのサポートリストはmbed5に比して大幅に圧縮してるんで、ハードウエア記述用のスクリプトを調べろ、と言われます。

で、SDBlockDevice.hをincludeさせようとすると、見つからない、と言われる。いやいや、あるじゃん…。これでひとしきり悩んだんですが、mbed_app.jsonで記述するボード名、これが問題で、Targetに指定しているものと同じ(NUCLEO-F411RE)かと思っていたら然にあらず、コンパイラで指定されているボード名?NUCLEO_F411RE、である必要があった、と。mbed_app.jsonに追加したのが有効になってなかったのね…。最終的にはこんな感じ。

-- mbed_app.json

 {
    "target_overrides": {
      "NUCLEO_F411RE": {
          "target.features_add" : ["STORAGE"],
          "target.components_add" : ["SD"],
          "sd.SPI_MOSI" : "PB_15",
          "sd.SPI_MISO" : "PB_14",
          "sd.SPI_CLK"  : "PB_13",
          "sd.SPI_CS"   : "PB_12"
      }
    }
}

 

このあたりを直して、出来上がったのが次のコード。GPSのデータも記録したいところなんですが、まずは6軸センサのデータをSDカードに書けるようになったので、最初の目的は果たせそうです。  


--- main.cpp
#include "PinNames.h"
#include "stdio.h"
#include "mbed.h"
#include "BufferedSerial.h"
#include "string"
#include "Arial12x12.h"
#include "Arial24x23.h"
#include "ILI9341.h"
#include "BlockDevice.h"
#include "SDBlockDevice.h"
#include "FATFileSystem.h"
#include <cstdio>

static BufferedSerial pc(USBTX, USBRX, 115200); // for debug console

DigitalIn trig(PB_2);   // push button sw

// For ILI9341 Arduino Shield
// power-on reset does not work well, has to connect LCD_reset to reset pin.
PinName myport[8] = {PA_9, PC_7, PB_6, PA_7, PB_5, PA_5, PB_10, PA_8};
ILI9341 myLCD(BUS_8, myport, PB_0, PH_1, PA_4, PA_1, PA_0,"myLCD");

I2C LM75A(I2C_SDA, I2C_SCL);
I2C MPU6050(I2C_SDA, I2C_SCL);

#define LM75A_addr (0x48 << 1)     //0b100100_0
#define MPU6050_addr (0x68 << 1)   //0b110100_0

char i2c_cmd[2];
char i2c_dat[14];    // first byte is address, max 14 bytes read data

int16_t ax, ay, az; // Accelerometer x/y/z
int16_t gx, gy, gz; // Gyroscope x/y/z
int16_t tp;         // Temp sensor on the module

// SDcard for data storage
//SDBlockDevice bd(MBED_CONF_SD_SPI_MOSI, MBED_CONF_SD_SPI_MISO, MBED_CONF_SD_SPI_CLK, MBED_CONF_SD_SPI_CS);
BlockDevice *bd = BlockDevice::get_default_instance(); // That's all right..
FATFileSystem fs("fs");

unsigned short backgroundcolor=Black;
unsigned short foregroundcolor=White;

char orient=3;
unsigned int record = 0;


int main()
{
    myLCD.set_orientation(orient);
    myLCD.background(backgroundcolor);    // set background to black
    myLCD.foreground(foregroundcolor);    // set chars to white
    myLCD.cls();                          // clear screen
    myLCD.locate(0,0);
    myLCD.set_font((unsigned char*) Arial12x12,32,127,false); //variable width disabled
    
    // initialization
    // GYRO_CONFIG(0x1B)
    //  set Gyroscope resolution to +/- 250deg/sec
    // ACCEL_CONFIG(0x1C)
    //  set Accelerometer resolution to +/- 2G
    // PWR_MGMT_1(0x6B)
    //  set CLKSEL to GyroX reference
    //  exit from sleep mode
    myLCD.printf ("Mounting SD Card.\n");
    int err = fs.mount(bd);
    myLCD.printf ("%s\n",(err ? "Mount Failed. :(" : "OK"));
    // Check if data.txt exists
    FILE *f = fopen ("/fs/data.txt", "r+");
    if (!f) {   // file does not exsts.
        myLCD.printf ("File does not exists. Create..\n");
        f = fopen ("/fs/data.txt", "w+");
        fclose(f);
    }
    
    i2c_cmd[0]=0x75;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x75 = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x1B;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x1B = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x1C;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x1C = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x6B;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x6B = %x\r\n",i2c_dat[0]);
   
    i2c_cmd[1]=i2c_dat[0] & 0xbf;   // clear sleep bit
    MPU6050.write(MPU6050_addr,i2c_cmd,2, true);
    MPU6050.read(MPU6050_addr,i2c_dat,1);
    //myLCD.printf ("Reg 0x6B = %x\r\n",i2c_dat[0]);  // check

    while(1) {                       // clear screen
        myLCD.locate(0,100);
        if (!trig) {
            record = !record;
        }

        //myLCD.printf("Display ID: %.8X\r\n", myLCD.tftID);
        // Read LM75A temp sensor
        //temperature = LM75A.read_T();
        i2c_cmd[0] = 0x0;
        LM75A.write(LM75A_addr,i2c_cmd, 1);
        LM75A.read(LM75A_addr | 1, i2c_dat, 2);
        unsigned short temperature = ((i2c_dat[0] << 8) + i2c_dat[1]) >> 8;

        // Read MPU6050 accelerometer & gyro
        i2c_cmd[0]=0x3B;
        MPU6050.write(MPU6050_addr,i2c_cmd,1);
        MPU6050.read(MPU6050_addr|1,i2c_dat,14);
        ax=(((int16_t)i2c_dat[0]) << 8) | i2c_dat[1];
        ay=(((int16_t)i2c_dat[2]) << 8) | i2c_dat[3];
        az=(((int16_t)i2c_dat[4]) << 8) | i2c_dat[5];
        tp=(((int16_t)i2c_dat[6]) << 8) | i2c_dat[7];
        gx=(((int16_t)i2c_dat[8]) << 8) | i2c_dat[9];
        gy=(((int16_t)i2c_dat[10]) << 8) | i2c_dat[11];
        gz=(((int16_t)i2c_dat[12]) << 8) | i2c_dat[13];
        float temp = (tp / 340) + 36.53;

        myLCD.printf ("Accelerometer:\r\n");
        myLCD.printf ("  AX : %6d\r\n",ax);
        myLCD.printf ("  AY : %6d\r\n",ay);
        myLCD.printf ("  AZ : %6d\r\n",az);
        myLCD.printf ("Temp : %6d\r\n",int (temp));
        myLCD.printf ("Gyroscope\r\n");
        myLCD.printf ("  GX : %6d\r\n",gx);
        myLCD.printf ("  GY : %6d\r\n",gy);
        myLCD.printf ("  GZ : %6d\r\n",gz);
        myLCD.printf ("System Temp=%d\r\n",int(temperature));

        if (record) {
            myLCD.printf ("Recording.....\n");
                FILE *f = fopen("/fs/data.txt","a");    // open with append mode
                err = fprintf (f,"AX: %6d, AY: %6d, AZ: %6d, GX: %6d, GY: %6d, GZ: %6d\r\n",ax,ay,az,gx,gy,gz);
                if (err < 0) {
                    myLCD.printf ("Write to SD failed.\n");
                    fclose (f);   // close each time
                }
                err = fclose (f);
        }
        else {
            myLCD.printf("Waiting........\n");
        }
        wait_us(1000000);
    }
}

mbedは、なんでだかやってみないと判らない、ということの繰り返しで、思ったよりもだいぶ苦労してしまいました。ハードウエア記述がソースから切り出され、IDE側でハンドリングしようとするくだりは、u-boot device treeの頃を思い出すようです。つまり、いかんせんそれまでのmbed2での蓄積が大きくて、先人さま方のblogもなかなか欲しい状況にヒットしない、ということになります。しかしまぁ、Arduinoとmbed、どっちかだけで済ませたいなぁ…。