// file:20220114AvatarGyro.ino // 傾きに逆らって顔を向ける。Arduino NANO, OLED:SSD1306, GYRO;L3GD20 // 2022/01/14 ラジオペンチ http://radiopench.blog96.fc2.com/ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define L3GD20_ADDR 0x6a // ジャイロのI2Cアドレス #define L3GD20_CTRL_REG1 0x20 // ジャイロの制御レジスタアドレス // 目の寸法 #define EYE_X1 30 // 向かって左の眼の中心位置 #define EYE_X2 96 // 向かって左の眼の中心位置 #define EYE_Y 19 // 眼の縦方向の位置 #define EYE_R 6 // 眼の半径 #define EYE_RL 7 // 見開いた眼の半径 // 口の寸法 #define MO1_X 44 // 口の左上座標 X #define MO1_Y 44 // 口の左上座標 Y #define MO1_L 40 // 口の長さ #define MO1_H 3 // 口の高さ #define MD_TH 700 // 動き検知スレッショルド // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 OLED(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); int dx, dy, dz; // 表情の変化量 int rotX, rotY, rotZ; // 現在の角度 int rotX0, rotY0, rotZ0; // ゼロ補正量 void setup() { pinMode(13, OUTPUT); Wire.begin(); Serial.begin(115200); OLED.begin(SSD1306_SWITCHCAPVCC, 0x3C); // OLEDの設定(I2Cアドレス 0x3c or 0x3d) Serial.println("x, y, z"); // シリアルデーターの項目名を表示 gyroSetup(); // ジャイロのモード設定 zeroCalib(); // ジャイロのゼロキャリブレーション OLED.setTextColor(WHITE); // 文字色=白 OLED.clearDisplay(); OLED.display(); } void loop() { static int m = 0; // まばたきタイマー sensAngle(); // 現在の姿勢を計測 if (isMove() == 1) { // 動きが検出されたら digitalWrite(13, HIGH); faceB(100); // 少し大きな目で動いた方向を見る m = 0; digitalWrite(13, LOW); } m++; // まばたきタイマーカウントアップ if ( m < 15) { // まばたきタイマーが設定値以内だったら、 faceA(100); // 普通の顔 } else { // まばたきのタイミングが来てたら、 faceC(200); // まばたきの顔を表示 m = 0; // 次回に備えてまだ滝タイマーをリセット } } int isMove() { // 動きの有無判定 int moved = 0; if (rotX > MD_TH) { dx = -10; } else if (rotX < -MD_TH) { dx = 10; } else { dx = 0; } if (rotY > MD_TH) { dy = -7; } else if (rotY < -MD_TH) { dy = 7; } else { dy = 0; } if (dx != 0 || dy != 0) { // XかY軸が動いていたら moved = 1; // フラグを立てる } return moved; } void sensAngle() { // ジャイロの値を読む int hi, lo; lo = L3GD20_read(0x28); hi = L3GD20_read(0x29); rotX = ((hi << 8) | lo) - rotX0 ; // X軸 lo = L3GD20_read(0x2A); hi = L3GD20_read(0x2B); rotY = ((hi << 8) | lo) - rotY0 ; // Y軸 lo = L3GD20_read(0x2C); hi = L3GD20_read(0x2D); rotZ = ((hi << 8) | lo) - rotZ0 ; // Z軸 Serial.print(rotX); Serial.print(", "); // 結果を表示 Serial.print(rotY); Serial.print(", "); Serial.print(rotZ); Serial.println(); } void gyroSetup() { // ジャイロのモード設定 Wire.beginTransmission(L3GD20_ADDR); Wire.write(L3GD20_CTRL_REG1); Wire.write(0x0f); Wire.endTransmission(); } void zeroCalib() { // ジャイロのゼロ点補正 int xs = 0; // sum値用変数 int ys = 0; int zs = 0; rotX0 = 0; // ゼロ補正無しにして、 rotY0 = 0; rotZ0 = 0; for (int i = 0; i < 100; i++) { // 100回 sensAngle(); // 測定して xs += rotX; // 値を累積 ys += rotY; zs += rotZ; delay(10); } rotX0 = xs / 100; // ゼロ補正量を設定 rotY0 = ys / 100; rotZ0 = zs / 100; } byte L3GD20_read(byte reg) { // ジャイロの指定アドレスのデーターを読む Wire.beginTransmission(L3GD20_ADDR); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(L3GD20_ADDR, 1); return Wire.read(); } void faceA(long t) { // 普通の顔 eraceFace(); eye1(); mouce1(); OLED.display(); delay(t); } void faceB(long t) { // 見開いた目の顔 eraceFace(); eye2(); // ちょっと大きな目 mouce1(); OLED.display(); delay(t); } void faceC(long t) { // 見開いた目の顔 eraceFace(); eye3(); // 細い目(まばたき) mouce1(); OLED.display(); delay(t); } void eye1() { // 普通の目 OLED.fillCircle(EYE_X1 - dx, EYE_Y + dy, EYE_R, WHITE); // 丸で目を描く OLED.fillCircle(EYE_X2 - dx, EYE_Y + dy, EYE_R, WHITE); // 丸を目を描く } void eye2() { // 見開いた目 OLED.fillCircle(EYE_X1 - dx, EYE_Y + dy, EYE_RL, WHITE); // 丸で目を描く OLED.fillCircle(EYE_X2 - dx, EYE_Y + dy, EYE_RL, WHITE); // 丸を目を描く } void eye3() { // 細い目 OLED.fillRect(EYE_X1 - EYE_R, EYE_Y, EYE_R * 2, 2, WHITE); // 2重線で目を書く OLED.fillRect(EYE_X2 - EYE_R, EYE_Y, EYE_R * 2, 2, WHITE); } void mouce1() { // 普通の口 OLED.fillRect(MO1_X - dx, MO1_Y + dy, MO1_L, MO1_H, WHITE); // デフォルトの口 } void eraceFace() { // 顔をクリア OLED.fillRect(0, 0, 128, 64, BLACK); // 黒で塗りつぶす }