M5stack BasicのGroveポートに一個つないでた時には動いたNeoHex(WS2812B)が、
その列にエンコーダーユニット (M5Stack-U135)をつなぐと0番と1番のLEDだけ最大発光の緑や黄色になる奇妙な現象が発生。
まぁ何のことはなく、NeoHexはI2Cじゃないので無理にほかのと繋ぐと妙な挙動をするというだけの話でした。
NeoHexはGPIO26に、ほかのI2C系はGPIO21につなぐ事で無事に動作しました。
実際のところ


動いたスクリプト
/* * M5Stack Basic v2.7 NeoHex Encoder Controller * * 【接続方法】 * - NeoHex: GPIO26 (3線通信 WS2812B) * - エンコーダーユニット (M5Stack-U135): I2C (GPIO21/22) * * 【注意事項】 * - I2Cと3線通信は異なる通信方式のため、同じピンは使用不可 * - NeoHexは37個のWS2812B LEDを使用 * - エンコーダーで明度を0-254の範囲で調整可能 */ #include <Arduino.h> #include <M5Stack.h> #include <FastLED.h> #include <Wire.h> // 色定数の定義(M5Stack用) #ifndef BLACK #define BLACK 0x0000 #endif #ifndef WHITE #define WHITE 0xFFF #endif // NeoHexの設定 #define LED_PIN 26 // データピン(GPIO26) #define NUM_LEDS 37 // NeoHexのLED数(37個) #define LED_TYPE WS2812B // LEDタイプ #define COLOR_ORDER GRB // カラーオーダー // エンコーダーユニット(M5Stack-U135)の設定 #define ENCODER_ADDR 0x40 // エンコーダーユニットのI2Cアドレス #define ENCODER_REG 0x10 // エンコーダー値のレジスタアドレス(0x10から2バイト) // デバッグ用の設定 #define DEBUG_MODE true // デバッグモードを有効にする // LED配列の定義 CRGB leds[NUM_LEDS]; // グローバル変数 int16_t lastEncoderValue = 0; uint8_t currentBrightness = 0; // 関数宣言 int16_t readEncoderValue(); void setNeoHexBrightness(uint8_t brightness); void updateDisplay(int16_t encoderValue, uint8_t brightness); void testInitialLEDs(); void scanI2CDevices(); void initializeEncoder(); void setup() { // M5Stack Basic v2.7の初期化 M5.begin(); // I2C通信の初期化 Wire.begin(); // シリアル通信の初期化 Serial.begin(115200); delay(1000); Serial.println("M5Stack Basic v2.7 NeoHex Encoder Controller Started"); // 画面の初期化 M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextColor(WHITE); M5.Lcd.setTextSize(2); M5.Lcd.setCursor(10, 10); M5.Lcd.println("NeoHex Encoder"); M5.Lcd.setTextSize(1); M5.Lcd.setCursor(10, 40); M5.Lcd.println("Rotate encoder to adjust brightness"); // デバッグ情報を表示 Serial.printf("LED Pin: %d\n", LED_PIN); Serial.printf("Number of LEDs: %d\n", NUM_LEDS); Serial.printf("LED Type: WS2812B\n"); Serial.printf("Color Order: GRB\n"); Serial.printf("Encoder I2C Address: 0x%02X\n", ENCODER_ADDR); // FastLEDの初期化(段階的に実行) FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS); FastLED.setBrightness(0); // 最初は明度0に設定 // 電源制限設定(M5Stack Basic対応) //FastLED.setMaxPowerInVoltsAndMilliamps(5, 1000); // 5V, 1000mA制限 // 全LEDを確実にクリア(複数回実行) for (int clearCount = 0; clearCount < 3; clearCount++) { FastLED.clear(true); // バッファをゼロで埋める for (int i = 0; i < NUM_LEDS; i++) { leds[i] = CRGB::Black; } FastLED.show(); delay(50); } // 明度を段階的に上げる for (int brightness = 0; brightness <= 255; brightness += 10) { FastLED.setBrightness(brightness); delay(10); } FastLED.setBrightness(255); Serial.println("NeoHex and Encoder initialized successfully"); Serial.println("Rotate encoder to adjust brightness (0-254)"); // I2Cスキャン scanI2CDevices(); // エンコーダーユニットの初期化 initializeEncoder(); // 初期テスト testInitialLEDs(); } void loop() { // エンコーダーの値を読み取り int16_t encoderValue = readEncoderValue(); // デバッグ用:常にエンコーダー値を表示 static unsigned long lastDebugTime = 0; if (millis() - lastDebugTime > 1000) { Serial.printf("Debug - Encoder Value: %d, Last Value: %d\n", encoderValue, lastEncoderValue); lastDebugTime = millis(); } // エンコーダー値が変化した場合のみ処理 if (encoderValue != lastEncoderValue) { // エンコーダー値を0-254の明度にマッピング // エンコーダーの範囲を仮定(-30から30の範囲) uint8_t brightness = map(encoderValue, -30, 30, 0, 254); brightness = constrain(brightness, 0, 254); Serial.printf("Encoder changed: %d -> %d, Brightness: %d\n", lastEncoderValue, encoderValue, brightness); // NeoHexの明度を設定 setNeoHexBrightness(brightness); // 画面表示を更新 updateDisplay(encoderValue, brightness); lastEncoderValue = encoderValue; currentBrightness = brightness; } delay(50); // 短い遅延でスムーズな動作 } // エンコーダーの値を読み取る関数 int16_t readEncoderValue() { // I2C通信の開始(レジスタアドレスを指定) Wire.beginTransmission(ENCODER_ADDR); Wire.write(ENCODER_REG); uint8_t error = Wire.endTransmission(false); // falseで再起動を防ぐ if (error != 0) { Serial.printf("I2C Error: %d\n", error); return lastEncoderValue; // 前回の値を返す } // データの要求(2バイト) Wire.requestFrom(ENCODER_ADDR, 2); if (Wire.available() >= 2) { uint8_t lowByte = Wire.read(); uint8_t highByte = Wire.read(); int16_t value = (int16_t)((highByte << 8) | lowByte); Serial.printf("Raw encoder bytes: 0x%02X 0x%02X, Value: %d\n", highByte, lowByte, value); return value; } else { Serial.printf("I2C read failed, available: %d\n", Wire.available()); return lastEncoderValue; // 前回の値を返す } } // NeoHexの明度を設定する関数 void setNeoHexBrightness(uint8_t brightness) { Serial.printf("Setting brightness to: %d\n", brightness); // 段階的なクリア処理 FastLED.clear(true); // バッファをゼロで埋める for (int i = 0; i < NUM_LEDS; i++) { leds[i] = CRGB::Black; } FastLED.show(); delay(5); // 全LEDに白色を設定(段階的に) for (int i = 0; i < NUM_LEDS; i++) { leds[i] = CRGB(brightness, brightness, brightness); // 白色で点灯 } FastLED.show(); delay(10); Serial.println("LEDs updated"); } // 画面表示更新 void updateDisplay(int16_t encoderValue, uint8_t brightness) { M5.Lcd.fillRect(10, 60, 300, 40, BLACK); M5.Lcd.setCursor(10, 60); M5.Lcd.printf("Encoder: %d", encoderValue); M5.Lcd.setCursor(10, 80); M5.Lcd.printf("Brightness: %d", brightness); } // 初期LEDテスト void testInitialLEDs() { Serial.println("Starting initial LED test..."); // 段階的なLEDテスト for (int i = 0; i < 3; i++) { Serial.printf("Test cycle %d\n", i + 1); // 赤色で全LED点灯 FastLED.clear(true); // バッファをゼロで埋める for (int j = 0; j < NUM_LEDS; j++) { leds[j] = CRGB(50, 0, 0); // 赤色(明度を下げる) } FastLED.show(); delay(300); // 緑色で全LED点灯 FastLED.clear(true); // バッファをゼロで埋める for (int j = 0; j < NUM_LEDS; j++) { leds[j] = CRGB(0, 50, 0); // 緑色(明度を下げる) } FastLED.show(); delay(300); // 青色で全LED点灯 FastLED.clear(true); // バッファをゼロで埋める for (int j = 0; j < NUM_LEDS; j++) { leds[j] = CRGB(0, 0, 50); // 青色(明度を下げる) } FastLED.show(); delay(300); // 消灯 FastLED.clear(true); // バッファをゼロで埋める for (int j = 0; j < NUM_LEDS; j++) { leds[j] = CRGB::Black; } FastLED.show(); delay(300); } // 最終的な完全消灯処理(複数回実行) for (int clearCount = 0; clearCount < 5; clearCount++) { FastLED.clear(true); // バッファをゼロで埋める for (int i = 0; i < NUM_LEDS; i++) { leds[i] = CRGB::Black; } FastLED.show(); delay(100); } Serial.println("Initial LED test completed"); } // I2Cデバイススキャン関数 void scanI2CDevices() { Serial.println("Scanning I2C devices..."); int deviceCount = 0; for (uint8_t address = 1; address < 127; address++) { Wire.beginTransmission(address); uint8_t error = Wire.endTransmission(); if (error == 0) { Serial.printf("I2C device found at address 0x%02X\n", address); deviceCount++; } } if (deviceCount == 0) { Serial.println("No I2C devices found!"); } else { Serial.printf("Found %d I2C device(s)\n", deviceCount); } } // エンコーダーユニットの初期化関数 void initializeEncoder() { Serial.println("Initializing encoder unit..."); // エンコーダーユニットの初期化コマンドを送信 Wire.beginTransmission(ENCODER_ADDR); Wire.write(0x00); // 初期化レジスタ Wire.write(0x01); // 初期化コマンド uint8_t error = Wire.endTransmission(); if (error == 0) { Serial.println("Encoder unit initialized successfully"); } else { Serial.printf("Encoder initialization failed with error: %d\n", error); } delay(100); // 初期化待機 }
プルアップ抵抗まわりの問題の可能性も
テープ型の事例になりますが、通信用のポートをプルアップしてるとダメというケースもある様子。
GPIOを10番に変えたら、期待通りに光りました。
— Sadakata Lab (@Sadakata_Lab) 2022年4月23日
私の凡ミスだった。
M5Stamp-C3の基板で使えるGPIOの本数は9本ある。
そのうち1本(GPIO8)だけプルアップされていた。それを選んで使っていたのだ(笑)
何故GPIO8だけプルアップされているのかな?
何処にも繋がってないのよね。 pic.twitter.com/YjaYHMmkrJ