WS2812B NeoPixelを使ってみた

コンピュータ、組み込み

鮮やかに発光するNeoPixelのマトリクスボードを入手しました。
GPIO1本と簡単な命令で多くのLEDを個別に鮮やかな制御ができるのが魅力です。
砲弾型、サークル型、ライン型、マトリクス型、大小さまざまな形状があり順次簡単な使い方とともに紹介します。

今回紹介するもの

CJMCU-8*8 (WS2812B)

特徴

1本のGPIOだけ(電源とGNDは必要)で、複数のLEDをフルカラーで制御できる。
連結ができる。

製品情報
電源5V
LED数64個(横8, 縦8)
外観

前面

背面

使用感

配線もシンプルで、制御命令が非常に簡単です。
連結ができるので、1個のセルを複数連結することで大きなものや長い作品も簡単に作れます。
今回は8×8マトリクスを使用していますが、全点灯させると結構発熱します。
放熱板やファンを取り付けたいところです。

準備

配線

Raspberry Pi Pico配線CJMCU 8*8
VBUS+5V
GNDGND
GP0DIN

モジュールライブラリ

WS2812Bを制御するためのライブラリにはAdafruit NeoPixelライブラリを使用します。

検索neopixel
ライブラリ名Adafruit NeoPixel by Adafruit(x.x.x)※
※x.x.x Oct/2022 使用したバージョンは1.10.6

使い方

WS2812B 1個制御

説明

1個目のLEDを発光させます。
1秒間隔で、赤->緑->黄->青->紫->水->白 の順で点灯させます。

スケッチ

/**********************************************************************
【ライセンスについて】
Copyright(c) 2022 by tamanegi
Released under the MIT license
'http://tamanegi.digick.jp/about-licence/

【スケッチの説明】
動作確認は Raspberry Pi Pico で行っていますが、CPUは不問です。

WS2812を点灯させます。
1秒間隔で、赤->緑->黄->青->紫->水->白 の順で点灯させます。

【ライブラリ】
Adafruit NeoPixel by Adafruit 1.10.6

【準備】
Raspberry Pi Pico <-> W2182 Matrix 8x8
Vcc(5V)   <-> 5V
GND       <-> GND
GP0       <-> DIN

【バージョン情報】
2022/8/9 : 新規
2022/10/4 : YD-RP2040 実装WS2812 のサンプルプログラムを GP0にピン番号を変更
**********************************************************************/

#include <Adafruit_NeoPixel.h>

#define DIN_PIN 0            // NeoPixel の出力ピン番号はGP23
#define LED_COUNT 1           // LEDの連結数
#define WAIT_MS 1000          // 次の点灯までのウエイト
#define BRIGHTNESS 128        // 輝度
Adafruit_NeoPixel pixels(LED_COUNT, DIN_PIN, NEO_GRB + NEO_KHZ800);

void setup()
{
  pixels.begin();             //NeoPixel制御開始
}

void loop()
{
  pixels.clear();
  
  //pixels.Color(Red, Green, Blue)で、パレット情報を作成する。
  //赤点灯
  pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, 0, 0));
  pixels.show();
  delay(WAIT_MS);

  //緑点灯
  pixels.setPixelColor(0, pixels.Color(0, BRIGHTNESS, 0));
  pixels.show();
  delay(WAIT_MS);

  //赤 + 緑 で 黄点灯
  pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, BRIGHTNESS, 0));
  pixels.show();
  delay(WAIT_MS);

  //青点灯
  pixels.setPixelColor(0, pixels.Color(0, 0, BRIGHTNESS));
  pixels.show();
  delay(WAIT_MS);

  //赤 + 青 で 紫点灯
  pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, 0, BRIGHTNESS));
  pixels.show();
  delay(WAIT_MS);

  //緑 + 青 で 水点灯
  pixels.setPixelColor(0, pixels.Color(0, BRIGHTNESS, BRIGHTNESS));
  pixels.show();
  delay(WAIT_MS);

  //赤 + 緑 + 青 で 白点灯
  pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS));
  pixels.show();
  delay(WAIT_MS);
}

結果

1個だけ点灯させました。
使用したモジュールは 8×8 合計64個ありますので、次のスケッチで紹介します。

ゲーム

8×8 のWS2812Bを使ってゲームを作りました。
スケッチは近日公開する予定です。
[2023/4/2]
著作権上の問題がありますのでソース公開は自粛して紹介にとどまらせてもらいます。

乱数のブロック生成と左右、落下コントロール

ゲームの趣旨に触れない程度に、乱数による色ブロックの生成とコントローラを使った左右と落下モーションについてのスケッチを公開します。

WS2812B(8×8)モジュールにブロックを1個、乱数により色を生成します。
I2Cコントローラモジュールを使用して左右ボタンによる移動と、下ボタンによる落下モーションを行います。

配線

このプロジェクトではI2Cコントローラモジュールを使用します。
Qwiicコネクタの有るモデルであればQwiic接続を行ってI2C制御ができます。

I2Cコントローラモジュールを使用しない場合、スケッチ中で使用するボタンは3個(左右と下)ですので、タクトボタンの3個を組み合わせて同等の動作をすることができます。

Raspberry Pi Pico配線WS2812B 8×8配線I2C Controler
3V3Qwiic(VCC)
GNDGNDQwiic(GND)
GPIO16(I2C0 SDA)Qwiic(SDA)
GPIO17(I2C0 SCL)Qwiic(SCL
VBUS(5V)VCC
GPIO2DIN

スケッチ

/**********************************************************************
【ライセンスについて】
Copyright(c) 2022 by tamanegi
Released under the MIT license
'http://tamanegi.digick.jp/about-licence/

【スケッチの説明】
WS2812(8x8)モジュールにブロックを1個、乱数により色を生成します。
I2Cコントローラモジュールを使用して左右ボタンによる移動と、下ボタンによる落下モーションを行います。

【ライブラリ】
Raspberry Pi Pico/RP2040 > Raspberry Pi Pico
Adafruit NeoPixel by Adafruit 1.10.6

【準備】
Raspberry Pi Pico <-> I2Cコントローラモジュール(Qwiic)
GND               <-> GND
3V3               <-> 3V3
GPIO16(I2C0 SDA)  <-> SDA
GPIO17(I2C0 SCL)  <-> SCL

Raspberry Pi Pico <-> WS2812 8x8
GND               <-> GND
5V                <-> VCC
GPIO2             <-> DIN

【バージョン情報】
2023/4/2 : 新規
**********************************************************************/

#include "Wire.h"
#include <Adafruit_NeoPixel.h>

//==<<マクロ定義>>===========================================================================================
//コントローラモジュールに対する設定
#define I2C_ADDR  (0x10)            //スレーブ側に指定するI2Cアドレス
#define I2C_SDA   (16)
#define I2C_SCL   (17)

//スクリーンに対する設定
//WS2812のMatrixサイズ違いや、連結して使用する場合はこちらの値を変更する
#define SCREEN_Y              (8)                   //画面サイズは8 x 8
#define SCREEN_X              (8)                   //2次元配列は Xが(0 to 7), Yが(0 to 7)

#define BLOCK_NOTHING         (0)                   //ブロックが存在しない状態

#define WS2812LENGTH          (SCREEN_Y * SCREEN_X)
#define WS2812_DIN            (2)                       //WS2812 DINピン          マイコンにより使用するピンを選択

//ゲーム設定
#define BLOCK_COLORMAX        (8)                   //ブロックの種類は 1~7の 7種類、0はブロック無しの扱い
#define BLOCK_TYPES           (7)                   //ブロックの登場種類の数 3 最大(BLOCK_COLORMAX - 1)種類

//==<<列挙型、構造体>>=========================================================================================
//ゲーム中の状態遷移ステートマシン
typedef enum
{
  E_PlayingState_Create = 0,                           //ブロック生成
  E_PlayingState_Falling,                              //ブロック落下中
  E_PlayingState_BlockLanding,                         //ブロック着底
  E_PlayingState_Judge,                                //消去判定
} E_PlayingState;


//画面のブロック状態
typedef struct
{
  char  DcMapBlock[SCREEN_X][SCREEN_Y];               //ブロックの存在と色 0は存在しない
} TMAP;

//落下中のブロック情報
typedef struct
{
  //Mainは主軸側
  char  DcMainBlock;              //落下中のブロック色(種類)
  long  DlMainX;                  //落下中のブロック位置、軸側
  long  DlMainY;
} TOperationBlock;

//ゲーム中の繊維状態ステートマシン
typedef struct
{
  E_PlayingState  DePlayingState;       //ゲーム中遷移状態
} TGameState;

//==<<関数プロトタイプ宣言>>==================================================================================

static void CreateNewBlock(void);
static void LandingBlock(void);

static void ShowScreen(void);
static void ShowOperationBlock(const boolean VisibleOn);
static boolean OperationBlock(void);


static void CreatePalette(void);
static void ClearMAP(void);
static void InitGame(void);


//==<<モジュール内変数>>======================================================================================

Adafruit_NeoPixel pixels(WS2812LENGTH, WS2812_DIN, NEO_GRB + NEO_KHZ800);       //WS2812 ライブラリオブジェクト

unsigned long    mDulPalette[BLOCK_COLORMAX];     //パレットデータ
TMAP    mDtMap;                                   //画面のブロック状態
TOperationBlock mDtBlock;                         //落下ブロック情報
TGameState mDtGameState;                          //各モードの遷移状態

void setup()
{
  //==<<使用するペリフェラルを初期化>>==
  Serial1.begin(115200);                           //シリアル通信(デバッグ出力用)

  //ターゲットがRP2040系の場合
  Wire.setSCL(I2C_SCL);
  Wire.setSDA(I2C_SDA);
  Wire.begin();                                   //I2Cマスター通信開始(コントローラモジュール用)
  
  //ターゲットがESP32系の場合
//  Wire.begin(I2C_SCL, I2C_SDA);
  
  pixels.begin();                                 //画面 WS2812の初期化

  //==<<変数などの初期化>>==
  randomSeed(100);                                //乱数の種

  CreatePalette();                                //パレット作成
  

  //==<<ゲームの初期化>>==
  InitGame();

  
}

void loop() 
{

  switch (mDtGameState.DePlayingState)
  {
    case E_PlayingState_Create:                   //==<<ブロック生成ステート>>==
      CreateNewBlock();
      mDtGameState.DePlayingState = E_PlayingState_Falling;         //ブロック生成後は落下ステートへ遷移
      break;

    case E_PlayingState_Falling:                  //==<<ブロック落下ステート>>==
      if(OperationBlock() == true)                                             //ブロック操作
      {
        //着底判定されていたらブロックマップに反映して、次の状態に遷移        
        LandingBlock();                                             //着底したブロックをブロックマップに反映する
        mDtGameState.DePlayingState = E_PlayingState_Create;        //ブロック生成に遷移
      }
      else
      {
      }
      delay(100);

      break;
  }

  ShowScreen();

}


//==<<ゲーム実行中関連>>========================================================================================================

//着底したブロックをブロックマップに反映する
static void LandingBlock(void)
{
  //主軸ブロックの反映
  mDtMap.DcMapBlock[mDtBlock.DlMainX][mDtBlock.DlMainY] = mDtBlock.DcMainBlock;

}

//コントローラの入力確認と、ブロックの操作(左右移動、落下、回転)
//I2Cコントローラモジュールから入力キーを読み取る
//下ボタンの押下で、ブロックが床に着底した判定を行う。
//戻り値 : false = 着底していない, true = 着底した
static boolean OperationBlock(void)
{
  char DcInput = 0;                 //コントローラからの入力値保存

  boolean DbJudgeLandingOn = false;   //着底判定    : false = 着底していない, true = 着底した

  //I2Cモジュールからの読み取り処理
  if(Wire.requestFrom(I2C_ADDR, 2) != 0)
  {
    DcInput = Wire.read();        //読み取りを行う
  }
  else
  {
    //読み取りに失敗したら入力は無かったことにする。
    return false;
  }

//Serial1.printf("OperationBlock : Input = %02x\r\n", DcInput);

  //Inputの内容は各ビットの H/L 判定
  //全8bit 並び 7, 6, 5, 4, 3, 2, 1, 0(bit) : 7bit = 上, 6bit = 左, 5bit = 下, 4bit = 右,   3bit = 上側, 2bit = 下側, 1bit = 左側, 0bit = 右側

  //同時押しの優先順位 上下、 左右
  //移動の判定
  if((DcInput & 0x10) != 0)      //上の判定
  {
    //上はゲーム中使用しない
  }
  if((DcInput & 0x20) != 0)     //下の判定
  {
    if(mDtBlock.DlMainY < (SCREEN_Y - 1))        //床の着底判定
    {
      if(mDtMap.DcMapBlock[mDtBlock.DlMainX][mDtBlock.DlMainY + 1] == BLOCK_NOTHING)
      {
        mDtBlock.DlMainY ++;                    //ブロックと干渉していない
      }
      else
      {
        DbJudgeLandingOn = true;                //ブロックと干渉したので着底判定
      }
    }
    else
    {
      DbJudgeLandingOn = true;                  //床に着底した判定
    }
  }

  if((DcInput & 0x40) != 0)      //左の判定
  {
    if(mDtBlock.DlMainX > 0)      //左壁面接触判定
    {
      if(mDtMap.DcMapBlock[mDtBlock.DlMainX - 1][mDtBlock.DlMainY] == BLOCK_NOTHING)
      {
        mDtBlock.DlMainX --;               //ブロックと干渉していない
      }
    }
  }

  if((DcInput & 0x80) != 0)     //右の判定
  {
    if(mDtBlock.DlMainX < (SCREEN_X - 1))        //床の着底判定
    {
      if(mDtMap.DcMapBlock[mDtBlock.DlMainX + 1][mDtBlock.DlMainY] == BLOCK_NOTHING)
      {
        mDtBlock.DlMainX ++;                    //ブロックと干渉していない
      }
    }
  }

  return DbJudgeLandingOn;               //着底した場合はtrue, していなければfalse
}

//新しいブロックを生成する。
//スクリーン最上部、中央位置にランダムでブロックの色を作成する。
static void CreateNewBlock(void)
{
  const long StartPosX = (SCREEN_X / 2);          //ブロック出現位置 X軸全幅の中央
  const long StartPosY = 0;                       //Y軸は一番上部
  
  //新しいブロックの種類(色)を生成。 乱数 + 1 は0より大きい数を生成するため。
  long MainBlock = random(BLOCK_TYPES) + 1;
//Serial1.printf("CreateNewBlock : Main = %d, Sub = %d\r\n", MainBlock, SubBlock);       //生成ブロックの確認

  //新しいブロックの情報を保存
  mDtBlock.DcMainBlock = MainBlock;               //Main側
  mDtBlock.DlMainX = StartPosX;
  mDtBlock.DlMainY = StartPosY;
}

//マップ情報に基づいて画面のの表示を更新する
//落下中の操作ブロックの表示を行う
static void ShowScreen(void)
{
  long x = 0;
  long y = 0;
  long ybuf = 0;    //y軸計算バッファ
  long n = 0;       //WS2812の何個目になるか n = (y * SCREEN_Y) + x
  long Palette;

  pixels.clear();
  //マップに基づいて表示する
  for ( y = 0; y < SCREEN_Y; y ++)
  {
    ybuf = y * SCREEN_Y;

    for( x = 0; x < SCREEN_X; x ++)
    {
      n = ybuf + x;

      Palette = mDulPalette[(long)mDtMap.DcMapBlock[x][y]];
      pixels.setPixelColor(n, Palette);
    }
  }

  if(mDtBlock.DcMainBlock != BLOCK_NOTHING)
  {
    ShowOperationBlock(true);
  }
  else
  {
    ShowOperationBlock(false);
  }

  pixels.show();
}

//操作ブロックの表示の切り替え
//引数 
//VisibleOn : false = 操作ブロックを表示する, true = 表示ブロックを非表示にする
static void ShowOperationBlock(const boolean DbVisibleOn)
{
  //引数により作成するパレットを変更する。
  //true なら操作ブロックの種類(色), false なら非表示(黒)
  long DlMainPalette = (DbVisibleOn == true) ? mDulPalette[(long)mDtBlock.DcMainBlock] : mDulPalette[0];
  long n = 0;

  //操作ブロックの表示
  //Mainブロックが0以外なら操作中と判定して表示する
  if(mDtBlock.DcMainBlock != 0) 
  {
    //主軸側のブロック表示
    n = (mDtBlock.DlMainY * SCREEN_Y) + mDtBlock.DlMainX;
    pixels.setPixelColor(n, DlMainPalette);
  }
  pixels.show();
}

//==<<初期化関連>>========================================================================================================
//WS2812に発色させるパレットを作成する
//ブロックの明るさや、色合いを変える場合はこちらの関数内のpixels.Color()RGBの色調を調整する。
static void CreatePalette(void)
{
  const unsigned char constBrightness = 8;                      //WS2812最大輝度 (MAX255)  明るさ調整

  //パレットは BLOCK_COLORMAXで指定した数分設定する。
  //BLOCK_COLORMAX が 8の場合は 0 ~ 7 の合計8色
  //Index 0 はブロックが存在しない色として使用します。

  //ゲームに登場する色数は BLOCK_COLORで決まります。
  //BLOCK_COLORMAX が 8で、BLOCK_COLOR が 3の場合、準備した色は8色(0含めて)でも、ゲームで登場する色数は 3色です。
  mDulPalette[0] = pixels.Color(0, 0, 0);                                                //黒(無発光、ブロックがないところ) 
  mDulPalette[1] = pixels.Color(constBrightness, 0, 0);                                  //赤
  mDulPalette[2] = pixels.Color(0, constBrightness, 0);                                  //緑
  mDulPalette[3] = pixels.Color(constBrightness, constBrightness, 0);                    //黄
  mDulPalette[4] = pixels.Color(0, 0, constBrightness);                                  //青
  mDulPalette[5] = pixels.Color(constBrightness, 0, constBrightness);                    //紫
  mDulPalette[6] = pixels.Color(0, constBrightness, constBrightness);                    //水
  mDulPalette[7] = pixels.Color(constBrightness, constBrightness, constBrightness);      //白
}

//マップを初期化する。
static void ClearMAP(void)
{
  long x = 0;
  long y = 0;

  for( x = 0; x < SCREEN_X; x ++)
  {
    for ( y = 0; y < SCREEN_Y; y ++)
    {
      mDtMap.DcMapBlock[x][y] = BLOCK_NOTHING;
    }
  }
}

//ゲームの最初に一回初期化する。
static void InitGame(void)
{

  //スクリーン情報の初期化
  ClearMAP();

  mDtGameState.DePlayingState = E_PlayingState_Create;                                    //はじめはブロック生成から

}

//==<<デバッグ関数>>=======================================================================

結果

Raspberry Pi Pico関連リンク

コメント

タイトルとURLをコピーしました