こんにちは、ENGかぴです。
Arduino環境でRGB LED用のAdafruit_NeoPixelライブラリを使用するとRGB LEDを様々なパターンで点灯させることができます。Arduino IDEにAdafruit_NeoPixelライブラリを追加してRGB LEDを操作する方法を説明しています。
Seeeduino XIAO用拡張ボード(Seeed Studio)及びGrove Mech Keycap(Seeed Studio)を使用しています。RGB LEDはMech keycapに実装されているものとRGB LEDはマイコン内蔵RGB LED 5mm PL9823-F5(秋月電子で購入)を使用しています。
Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
Adafruit NeoPixelライブラリを追加する
Arduino IDEでライブラリの追加を行います。ライブラリマネージャーを開いて検索欄に「pixel」(adafruitなど)などライブラリ名の一部を入力するとライブラリの候補が表示されます。候補の中から「Adafruit NeoPixel by Adafruit」をインストールします。
Adafruit NeoPixelライブラリはArduinoマイコンの操作をアセンブラ(機械語)に近づけることで処理による遅延を可能な限り減らすことで精度の良いタイミング波形を生成するように工夫されています。
Adafruit NeoPixelライブラリは上記のことを意識しなくてもメンバー関数をコールすることで点灯パターンを指定できるようになっています。マイコンのDOでRGB LEDを操作する場合のタイミング波形の考え方を下記記事にまとめています。
PICマイコン(PIC16F1827)でRGB LEDを操作する
ライブラリの準備と初期化
#include <Adafruit_NeoPixel.h>
#define NUMPIXELS2 2 // Popular NeoPixel ring size
//Adafruit_NeoPixelクラスの型の変数を宣言
Adafruit_NeoPixel pixels2(NUMPIXELS2, PIXEL_PIN2, NEO_RGB + NEO_KHZ400);
void setup() {
pixels2.begin(); //DOなどの設定を行う
pixels2.clear(); //色パターンクリア
pixels2.show(); //色パターンを表示(点灯)
}
ライブラリを使用するためAdafruit_NeoPixel.hをインクルードします。Adafruit_NeoPixelクラスの型の変数を宣言してインスタンス化します。インスタンス化した変数の第1引数にはRGB LEDを操作する数を指定します。第2引数にはRGB LEDを操作するDOピンを指定します。第3引数にはRGB LEDの色のタイプ及びタイミング波形の周波数を選択します。
PL9823-F5を操作する変数をpixels2としています。PL9823-F5を2つ使って色のパターンを表示するため第1引数に2を指定しています。PL9823-F5の点灯順であるRGBを指定するためNEO_RGBを指定し応答速度を調整するためNEO_KHZ400(400kHz)を指定して加算しています。
begin()関数はインスタンス化した変数で指定したDOのピンの設定を行います。clear()関数は点灯のパターンをクリアします。show()関数で指定した点灯のパターンでLEDを点灯します。
広告
PR:わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
ライブラリの使用例
for(uint16_t i=0; i<NUMPIXELS2; i++){
if(i == 0){
pixels2.setPixelColor(i,red2,green2,blue2);
}
else{
pixels2.setPixelColor(i,red3,green3,blue3);
}
pixels2.show();
}
色パターンの指定にはsetPixelColor()関数を使用します。第1引数には点灯させるLEDのインデックス番号を指定します。インデックス番号はRGB LEDを連結して点灯する場合の番号です。第2引数は赤色の点灯パターンを0~255で指定します。第3引数は緑色の点灯パターンを0~255で指定します。第4引数は青色の点灯パターンを0~255で指定します。
setPixelColor()関数で点灯パターンを指定してshow()関数で点灯させます。例では1番目のLEDと2番目のLEDを続けて操作するためfor分による繰り返し処理を行っています。
show()関数の内部で300us程度のウェイトが実装されているためPL9823-F5では各RGB LEDを操作する間隔を意識する必要はありません。(使用するRGB LEDによってはウェイトを追加する必要がある可能性があります)
OLEDにRGBの状態を表示する
OLEDの表示はU8g2ライブラリを使用しています。U8g2ライブラリの追加方法については下記記事を参考にしてください。
Seeeduino XIAOの拡張ボードのOLEDを使用する
本記事では表示に使用した関数について説明します。
char chr1[17];
u8x8.drawString(0,0,"Mech keycap "); //0行0列を指定して書き込み
//u8x8.setCursor(0, 0);
//u8x8.print("Mech keycap ");
sprintf(&chr1[0]," R%3d G%3d B%3d ",rd,grn,bl);//表示文字列を生成
u8x8.drawString(0,1, &chr1[0]); //0行1列を指定を指定して書き込み
drawString()関数は行と列および文字列を指定して書き込みを行う関数です。第1引数に行番号を指定します。行はセル群(5×8等のフォント)において左端の位置から右に向かって0~昇順になります。第2引数に列番号を指定します。列はセル群(5×8等のフォント)において上端から下に向かって0~昇順になります。
例では一番上の行(ライン)に”Mech keycap”の文字列を書き込んでいます。setCursor()関数でカーソル位置を指定してprint()関数で文字を書き込む方法と同等の処理になります。
sprintf()関数は文字列を生成する標準関数です。第1引数に生成後の文字列を格納するアドレスを指定します。第2引数に生成する文字列のフォーマットを指定します。%dなどの変換指定子を含めることでカウント値など任意の数値を文字列に変換することができます。
第3引数以降は第2引数内の変換指定子の数に応じて変換する変数を指定します。例では変数rd, grn,blを3桁の10進数で表示するように指定しています。
例では16文字の生成に対してNULL文字を考慮して17バイトの領域を確保しています。生成した文字列が確保した領域を超えてしまうと領域オーバーした部分の値が書き換わってしまうため予期しない動作不良の原因となるため注意が必要です。
動作確認
PL9823-F5のデータシートの実装例には抵抗値が記載されていませんが、電流制限の目的で220Ωの抵抗を実装しています。抵抗を実装した影響で各色の輝度に影響するため各色に対してカウント値に制限を設けています。赤が100、緑が200、青は制限せず255段階の変更としています。Grove Mech KeycapのLEDは制限せず255段階の変更としています。
Grove Mech Keycapのボタンを押すと赤→緑→青→赤→・・・繰り返しの順でパターン変更する色の種別を変更します。Grove Mech Keycapのボタンを長押しするとすべてのLEDを消灯します。
SW1を押すと選択している色に対してカウントを+1し、長押しすると連続して更新します。SW2を押すと選択している色に対してカウントを-1し、長押しすると連続して更新します。
OLEDにGrove Mech KeycapのLED、LED1、LED2のカウント値を表示します。OLEDの表示処理には遅延が発生するためにタイマを設けることで頻繁な更新を行わないようにしています。そのためOLEDの表示はボタン操作より僅かに遅れて表示します。
LED1のカウント値を基準にしてOLEDに4つのパターンでRGB LEDの点灯させました。LED1のカウント値はRGB LED1の下のラインがLED1のカウント値です。
パターン1は赤:100、緑:0、青:0とします。パターン2は赤:100、緑:0、青:255とします。パターン3は赤:0、緑:200、青:255とします。パターン4は赤:0、緑:200、青:1(0にすべきでしたが間違って1になっています)とします。
パターン1のLED1は赤色になっています。LED2はすべての色の合成で白色になっています。パターン2のLED1は赤と青の合成で紫色になっています。LED2はすべての色の合成で白色になっています。
パターン3のLED1は緑と青の合成で水色になっています。LED2はすべての色の合成で白色になっています。パターン4のLED1は緑でLED2は赤と緑の合成で黄色になっています。
Grove Mech KeycapのRGB LED及びLED1、LED2がSW1とSW2の組み合わせによって点灯のパターンが変更できていることが確認できました。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <Adafruit_NeoPixel.h>
#include <U8x8lib.h>
#include <Wire.h>
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define BUTTON_PIN 7
#define PIXEL_PIN 6
#define PIXEL_PIN2 1
#define PIN_DI1 4
#define PIN_DI2 5
#define NUMPIXELS 1 // Popular NeoPixel ring size
#define NUMPIXELS2 2 // Popular NeoPixel ring size
#define DI_FILT_MAX 4
#define TIME_FILTER_MAX 1
#define BTN_ON_KEEP 20
#define BTN_ON_CLR 300
#define LED_FL 5
#define LED_OLED 50
#define LED_OLED_AF 50
#define RED_LIMIT 100
#define GREEN_LIMIT 200
enum DI_NO{
DI1 = 0,
DI2,
DI3,
DI_MAX
};
typedef enum{
LED_R = 0,
LED_G,
LED_B,
LED_MAX
}LED_MODE_NO;
struct DIFILT_TYP{
uint8_t wp;
uint8_t buf[DI_MAX][DI_FILT_MAX];
uint8_t di[DI_MAX];
};
Adafruit_NeoPixel pixels(NUMPIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels2(NUMPIXELS2, PIXEL_PIN2, NEO_RGB + NEO_KHZ400);
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);
DIFILT_TYP difilt;
int8_t timdifilt;
int16_t timled;
int16_t timoled;
int16_t timbtnkeep[DI_MAX];
uint32_t beforetimCnt = millis();
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t red2;
uint8_t green2;
uint8_t blue2;
uint8_t red3;
uint8_t green3;
uint8_t blue3;
uint8_t colormd;
bool cntflg;
bool btnflg[DI_MAX];
/*** Local function prototypes */
void mainTimer(void);
void DiFilter(void);
void mainApp(void);
void BtnCnt(uint8_t md, bool plus);
void OlcdSet(void);
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLDOWN);
pinMode(PIN_DI1,INPUT_PULLUP);
pinMode(PIN_DI2,INPUT_PULLUP);
for( uint8_t i=0; i < 10; i++ ){
mainTimer();
DiFilter();
delay(10);
}
u8x8.begin();
u8x8.setFlipMode(1); // set number from 1 to 3
u8x8.setFont(u8x8_font_5x8_r);
u8x8.setCursor(0, 0);
u8x8.print("Mech keycap ");
u8x8.setCursor(0, 2);
u8x8.print("RGB LED1 ");
u8x8.setCursor(0, 4);
u8x8.print("RGB LED2 ");
red = 0;
green = 0;
blue = 0;
pixels.begin();
pixels.clear();
pixels.show(); // Initialize all pixels to 'off'
red2 = 0;
green2 = 0;
blue2 = 0;
red3 = 100;
green3 = 200;
blue3 = 255;
pixels2.begin();
pixels2.clear();
pixels2.show(); // Initialize all pixels to 'off'
}
void loop() {
mainTimer();
DiFilter();
mainApp();
}
/* メイン処理 */
void mainApp(void){
uint8_t i,j;
if( timled == TIME_UP){
timled = LED_FL;
if(cntflg){
cntflg = false;
pixels.setPixelColor(0,red,green,blue);
pixels.show();
for(uint16_t i=0; i<NUMPIXELS2; i++){
if(i == 0){
pixels2.setPixelColor(i,red2,green2,blue2);
}
else{
pixels2.setPixelColor(i,red3,green3,blue3);
}
pixels2.show();
}
timoled = LED_OLED;
}
}
if( timoled == TIME_UP ){
timoled = TIME_OFF;
OlcdSet();
}
for(i=0; i < DI_MAX; i++){
if( difilt.di[i] == 0){
if( btnflg[i] == false ){
btnflg[i] = true;
timbtnkeep[i] = BTN_ON_KEEP;
switch(i){
case 0:
BtnCnt(colormd,true);
cntflg = true;
break;
case 1:
BtnCnt(colormd,false);
cntflg = true;
break;
case 2:
timbtnkeep[i] = BTN_ON_CLR;
if(++colormd >=LED_MAX){
colormd = LED_R;
}
break;
}
}
else{
if(timbtnkeep[i] == TIME_UP){
switch(i){
case 0:
timbtnkeep[i] = BTN_ON_KEEP;
BtnCnt(colormd,true);
cntflg = true;
break;
case 1:
timbtnkeep[i] = BTN_ON_KEEP;
BtnCnt(colormd,false);
cntflg = true;
break;
case 2:
red = 0;
green = 0;
blue = 0;
red2 = 0;
green2 = 0;
blue2 = 0;
red3 = 100;
green3 = 200;
blue3 = 255;
colormd = LED_R;
cntflg = true;
pixels.clear();
pixels.show();
pixels2.clear();
pixels2.show();
for(j=0; j<DI_MAX; j++ ){
timbtnkeep[j] = TIME_OFF;
}
break;
}
}
}
}
else{
btnflg[i] = false;
timbtnkeep[i] = TIME_OFF;
}
}
}
/* Timer Management function add */
void mainTimer(void){
if ( millis() - beforetimCnt >= BASE_CNT ){
beforetimCnt = millis();
//10msごとにここに遷移する
if( timled > TIME_UP ){
timled--;
}
if( timdifilt > TIME_UP ){
timdifilt--;
}
if( timoled > TIME_UP ){
timoled--;
}
for(uint8_t i=0; i < DI_MAX; i++){
if( timbtnkeep[i] > TIME_UP ){
timbtnkeep[i]--;
}
}
}
}
/* DiFilter function add */
void DiFilter(void){
if( timdifilt == TIME_UP ){
timdifilt = TIME_FILTER_MAX;
difilt.buf[0][difilt.wp] = digitalRead(PIN_DI1);
difilt.buf[1][difilt.wp] = digitalRead(PIN_DI2);
difilt.buf[2][difilt.wp] = !digitalRead(BUTTON_PIN);
//MECH KEYCAPはボタンを押すとHなので反転する
for(uint8_t i=0; i<DI_MAX; i++){
if( difilt.buf[i][0] == difilt.buf[i][1] &&
difilt.buf[i][1] == difilt.buf[i][2] &&
difilt.buf[i][2] == difilt.buf[i][3] ){ //4回一致を確認
difilt.di[i] = difilt.buf[i][0];
}
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
}
}
/* ボタンによる値の更新 */
void BtnCnt(uint8_t md, bool plus){
switch(md){
case LED_R:
if(plus){
++red;
if(++red2 > RED_LIMIT){
red2 = 0;
}
if(++red3 > RED_LIMIT){
red3 = 0;
}
}
else{
--red;
if(--red2 == 255){
red2 = RED_LIMIT;
}
if(--red3 == 255){
red3 = RED_LIMIT;
}
}
break;
case LED_G:
if(plus){
if( ++green > GREEN_LIMIT ){
green2 = 0;
}
if( ++green3 > GREEN_LIMIT ){
green3 = 0;
}
}
else{
--green;
if(--green2 == 255 ){
--green2 = GREEN_LIMIT;
}
if(--green3 == 255 ){
--green3 = GREEN_LIMIT;
}
}
break;
case LED_B:
if(plus){
++blue;
++blue2;
++blue3;
}
else{
--blue;
--blue2;
--blue3;
}
break;
}
}
/* OLEDの文字をセット*/
void OlcdSet(void){
char chr1[17];
char chr3[17];
char chr5[17];
uint8_t rd= red;
uint8_t rd2= red2;
uint8_t rd3= red3;
uint8_t grn= green;
uint8_t grn2= green2;
uint8_t grn3= green3;
uint8_t bl = blue;
uint8_t bl2 = blue2;
uint8_t bl3 = blue3;
//u8x8.clear();//クリアすると処理がながくなる
//u8x8.setCursor(0, 0);
//u8x8.print("Mech keycap ");
u8x8.drawString(0,0,"Mech keycap ");//上のコメントアウトと同じ
sprintf(&chr1[0]," R%3d G%3d B%3d ",rd,grn,bl);
//u8x8.setCursor(0, 1);
//u8x8.clearLine(1);
//u8x8.write((uint8_t*)&chr1[0],16);
u8x8.drawString(0,1, &chr1[0]);
//u8x8.setCursor(0, 2);
//u8x8.print("RGB LED1 ");
u8x8.drawString(0,2,"RGB LED1 ");
sprintf(&chr3[0]," R%3d G%3d B%3d ",rd2,grn2,bl2);
//u8x8.setCursor(0, 3);
//u8x8.clearLine(3);
//u8x8.write((uint8_t*)&chr3[0],16);
u8x8.drawString(0,3, &chr3[0]);
//u8x8.setCursor(0, 4);
//u8x8.print("RGB LED2 ");
u8x8.drawString(0,4,"RGB LED2 ");
sprintf(&chr5[0]," R%3d G%3d B%3d ",rd3,grn3,bl3);
//u8x8.setCursor(0, 5);
//u8x8.clearLine(5);
//u8x8.write((uint8_t*)&chr5[0],16);
u8x8.drawString(0,5, &chr5[0]);
//u8x8.refreshDisplay();
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
【クリエイターズファクトリー】卒業がない!挫折する心配なし!Webスクール説明会申し込み
最後まで、読んでいただきありがとうございました。
更新頻度が高い場合SW1とSW2を連打しているとタイミングによってはOLEDの表示がバグるケースがありバグって画面が消えるとリセットしない限り復帰できなくなる問題が発生しました。更新の頻度と操作性を考慮した遅延時間の調整が必要だと感じました。