編集履歴一覧に戻る
miyavit0のアイコン画像

miyavit0 が 2025年01月31日18時01分52秒 に編集

初版

タイトルの変更

+

SPRESENSEで倒立振子に挑戦してみた話

タグの変更

+

SPRESENSE

+

倒立振子

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

製作品

本文の変更

+

# SPRESENSEで倒立振子 elchikaでSPRESENSEを使った倒立振子作品がなかったので、挑戦してみました。 ![キャプションを入力できます](https://camo.elchika.com/7c4271949758ddd354f63fcc1050a69ddc708747/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f32646335656466622d343235382d343465372d386232352d303865633163643132363866/) ## やりたいこと ・倒立振子のハード製作 ・SPRESENSEでのモータの駆動 ・カメラ映像の表示/撮影 ・IMUセンサで姿勢制御 ~~・WIfi/LTEで遠隔操作~~(次回) # 構成 部品は安く手に入るものを中心に構成を組み立てました ![キャプションを入力できます](https://camo.elchika.com/73fb9662cbc078d31274e72f2d5af966b9d71a64/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f62353139363464312d333939322d346633342d386663382d333565636665633761366563/) ## 部品 | 部品名 | 型式など | |:---:|:---| | SPRESENSEメインボード | | | SPRESENSE LTE拡張ボード | | | SPRESENSE HDRカメラボード | | | WiFiAddonボード is110B | | | Lipoバッテリ | 402030 | | バッテリ充電ボード | TP4056 | | エンコーダ付きギアードモータ | JGA25-371 | | モータドライバ | TB6612FNG | | 加速度センサ | GY-521 | # ハード設計 倒立振子でよくあるパターンがユニバーサル基板をフレームに、タミヤのギアモータなどを取り付けるものでした。 外装もなく縦長で見た目が微妙なので、簡易に3Dプリンタでケース・フレーム設計を行います。 全高を低く抑えたものを目標とします。 ## スタック方式 内蔵する各基板サイズも形状も違い、上部からピンヘッダで配線をするので、全高を抑えるには少し工夫が必要そうです。 機能ごとにケースを分け、上部に積み上げて取り付ける方式としました。 ![キャプションを入力できます](https://camo.elchika.com/87c92ac3e56028591dc29a12045fc4384f051f2e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f61356461653366312d643936322d343938372d386361332d363062626665366339633464/) これで、機能を追加したい場合も新たなモジュールケースを挟み込むことで、簡単に追加できる構造とします。 前後方向と上下方向を固定するために、左右から爪で押さえつける構造とすることで、ケースごとのねじなどの締結部品の数を省いています。 ![キャプションを入力できます](https://camo.elchika.com/e15d06351d3286334b8a304b0acab71b85828b13/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f64646339636632612d663962312d346263382d616633382d333561383233383036613034/) ## モータスペック確認 使用するギアードモータは定格12Vなのですが、12V電源を持ってくるのは結構大変です。 3~5Vで駆動ができればLipoバッテリやモバイルバッテリなどでも問題がないので、モータを低電圧で駆動ができるか確認してみます。 ![キャプションを入力できます](https://camo.elchika.com/2265a2c45c600ab4022a1ef69ebb41f93faef69b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f30383362336632342d643763632d343865342d386637362d656162663237373730303539/) モータ電圧を変化させて、電流がどの程度必要かをプロットしました。 ![キャプションを入力できます](https://camo.elchika.com/0653019732db55b13b7bd26be95cd5b65aa6d8d7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f65366466656532352d356539342d346536322d396639652d613939633065656163343635/) 動き出しの電圧は1.3Vくらいでした。 この結果であればLipoバッテリなどでもいけそうです。 ## 配線 ### IMU | IMUpin | SPRESENSE | |:---|:---| | VCC | JP1 10 | | GND | JP2 10 | | SCL | JP2 11 | | SDA | JP2 12 | ### モータドライバ モータドライバは、SPRESENSEとモータ用外部電源(バッテリ)、モータと接続します。 | モータドライバpin | | |:---|:---| | PWMA | CN9 9 | | AIN2 | CN9 7 | | AIN1 | CN9 8 | | STBY | JP1 10 | | BIN1 | CN9 10 | | BIN2 | CN9 12 | | PWMB | CN9 11 | | GND | | | VM | 外部電源+ | | VCC | JP1 10 | | GND | 外部電源GND | | AO1 | モータ1 赤 | | AO2 | モータ1 白 | | BO2 | モータ2 赤 | | BO1 | モータ2 白 | | GND | CN9 2 | # ハード製作 3Dプリンタで印刷しました。 ## 配線/はんだ モジュールのピンヘッダのはんだづけと、充電モジュールにLipoを繋ぎます。 ![キャプションを入力できます](https://camo.elchika.com/ca841ea4fab3e7614f36724f112e624e0da309c5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f36613435343735372d383665392d343363652d626434342d653165353239666261393533/) 加速度センサ・モータドライバも同様にピンをはんだ付けします ## 組立 モータとベース部品を繋げます。 ![キャプションを入力できます](https://camo.elchika.com/4ff9b02b1d7690f702f86eaa597d5707b0888af2/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f37383466616465342d336334352d343064372d386261352d623638393061356135613739/) 1段目のケースを取り付けます。 IMUは前方中心に設置しました。 ![キャプションを入力できます](https://camo.elchika.com/2fb4bf7af3a5a69a668d0657488e8c87af988fb4/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f37623936643831632d656639322d343462622d613438622d323538636434613164373038/) ケースを積み上げて、全部品を組付けました。 ![キャプションを入力できます](https://camo.elchika.com/467fc8f0fa95faeba9a431af0c3efb6d1f981539/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f63643533313765612d363037342d343631382d396162352d656637333033393737393739/) SPRESENSEには、電力供給+通信用に脇と正面から有線でアクセス可能にしました。 ![キャプションを入力できます](https://camo.elchika.com/3fd7a8ab95611561356233d701947f8851eb2c18/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f62306332643161302d663639352d343265352d623766352d633231663736363663353339/) # カメラ撮影 以下のコードでカメラ撮影が可能なことを確認しました。非常に簡単です。 ![キャプションを入力できます](https://camo.elchika.com/9e948a4ac93c7b8c3c0e7f86984bef5b4db610ae/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64646237303630662d363937362d346331332d626233362d6665346636666562626361332f38653030643365372d613263352d343666392d616430312d383337623633366638613765/) ```arduino:カメラ撮影 #include <SDHCI.h> #include <stdio.h> /* for sprintf */ #include <Camera.h> #define BAUDRATE (115200) #define TOTAL_PICTURE_COUNT (10) SDClass theSD; int take_picture_count = 0; void printError(enum CamErr err) { Serial.print("Error: "); switch (err) { case CAM_ERR_NO_DEVICE: Serial.println("No Device"); break; case CAM_ERR_ILLEGAL_DEVERR: Serial.println("Illegal device error"); break; case CAM_ERR_ALREADY_INITIALIZED: Serial.println("Already initialized"); break; case CAM_ERR_NOT_INITIALIZED: Serial.println("Not initialized"); break; case CAM_ERR_NOT_STILL_INITIALIZED: Serial.println("Still picture not initialized"); break; case CAM_ERR_CANT_CREATE_THREAD: Serial.println("Failed to create thread"); break; case CAM_ERR_INVALID_PARAM: Serial.println("Invalid parameter"); break; case CAM_ERR_NO_MEMORY: Serial.println("No memory"); break; case CAM_ERR_USR_INUSED: Serial.println("Buffer already in use"); break; case CAM_ERR_NOT_PERMITTED: Serial.println("Operation not permitted"); break; default: break; } } void CamCB(CamImage img) { /* Check the img instance is available or not. */ if (img.isAvailable()) { img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); Serial.print("Image data size = "); Serial.print(img.getImgSize(), DEC); Serial.print(" , "); Serial.print("buff addr = "); Serial.print((unsigned long)img.getImgBuff(), HEX); Serial.println(""); } else { Serial.println("Failed to get video stream image"); } } void setup() { CamErr err; Serial.begin(BAUDRATE); while (!Serial) { ; } while (!theSD.begin()) { Serial.println("Insert SD card."); } Serial.println("Prepare camera"); err = theCamera.begin(); if (err != CAM_ERR_SUCCESS) { printError(err); } Serial.println("Start streaming"); err = theCamera.startStreaming(true, CamCB); if (err != CAM_ERR_SUCCESS) { printError(err); } Serial.println("Set Auto white balance parameter"); err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT); if (err != CAM_ERR_SUCCESS) { printError(err); } Serial.println("Set still picture format"); err = theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG); if (err != CAM_ERR_SUCCESS) { printError(err); } } void loop() { sleep(1); if (take_picture_count < TOTAL_PICTURE_COUNT) { Serial.println("call takePicture()"); CamImage img = theCamera.takePicture(); if (img.isAvailable()) { char filename[16] = {0}; sprintf(filename, "PICT%03d.JPG", take_picture_count); Serial.print("Save taken picture as "); Serial.print(filename); Serial.println(""); theSD.remove(filename); File myFile = theSD.open(filename, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); } else { Serial.println("Failed to take picture"); } } else if (take_picture_count == TOTAL_PICTURE_COUNT) { Serial.println("End."); theCamera.end(); } take_picture_count++; } ``` # 制御 I2Cで取得したIMUのデータをもとにモータの回転を制御します。 傾き量に応じてモータのPWMを調整してバランスを取ります。 # 今後の課題 姿勢制御のパラメータ調整と 遠隔操作に次は挑戦したいと思います