PR

Raspberry Pi PicoのPIOずUARTでRGB LEDを操䜜する

組み蟌み゚ンゞニア
本蚘事はプロモヌションが含たれおいたす。

こんにちは、ENGかぎです。

VSCodeの拡匵機胜であるPico SDKを䜿甚するずRaspberry Pi PicoにPIOを実装するこずができたす。PIOずUARTを䜿っお電文でRGB LEDの色を任意の色で点灯させる方法をたずめたした。

本蚘事は䞋蚘蚘事のPIOずUARTを組み合わせた応甚䟋です。

Raspberry Pi PicoのPIOでRGB LEDを操䜜する

Raspberry Pi PicoでRTCを実装する

RTCの時刻を蚭定するために䜿甚したUARTの電文をRGB LED甚に流甚しお䜿甚したす。

今回はGrove Mech Keycap(Seeed Studio秋月電子で賌入)に䜿甚されおいるRGB LEDを操䜜したす。

Raspberry Pi Pico以䞋Picoずするず拡匵基板のGrove Shield for Pi Picoを䜿甚しおいたす。PCずPicoの通信はUSBを介しお行うため、AE-CH340E-TYPEC秋月電子を䜿甚しおいたす。

Picoを䜿甚しおArduino IDEやVSCodeで動䜜確認したこずをたずめおいたす。

Raspberry Pi Picoで孊べる゜フト開発

RGB LEDを操䜜する

匕甚WS2812Bデヌタシヌト Data Transfer Time
匕甚WS2812Bデヌタシヌト Data Transfer Time

Grove Mech Keycapに䜿甚されおいるRGB LEDはWS2812Bが䜿甚されおいたす。RGB LEDの操䜜はRGB LEDはナニポヌラれロ調敎コヌドを䜿甚したす。䞀色あたり8ビットの構成になるため256パタヌンの調敎ができたす。

RGB LEDは䜿甚する補品によっお色の調敎がRGBの順に操䜜するものやGRBで操䜜するプロトコルがありたす。WS2812BはGRBで操䜜するプロトコルですが、色の順が異なるだけで基本は同じなのでRGB LEDで統䞀したす。

WS2812Bは緑・赀・青の順に24ビットの倀を指定したす。Sequence Chartのタむミングに埓っおHighの長さずLowの長さによっお1ビットず぀1か0を指定しお点灯パタヌンを操䜜したす。

匕甚WS2812Bデヌタシヌト Composition of 24bit Data
匕甚WS2812Bデヌタシヌト Composition of 24bit Data

RGB LEDはSequence ChartのタむミングによっおHighの長さずLowの長さによっお1の指定か0の指定かを刀定しながら24ビットのデヌタを送信しお赀・緑・青の色を調敎したす。

デヌタシヌトによるず1 codeずしお認識させるためには暙準でT1H(1 code, high voltage time)が580ns1usずT1L(1 code, low voltage time)が580ns1us必芁です。

0 codeずしお認識させる堎合は暙準でT0H(0 code, high voltage time)が220ns380nsずT0L(1 code, low voltage time)が580ns1us必芁です。

220ns380nsず高速なGPIOの制埡が必芁ずなるためPIOによる制埡が効果的です。1ず0の長さはnopによるりェむトで範囲以内に収たるように制埡しおタむミング波圢を生成したす。

RGB LEDを操䜜するためのアセンブリファむルは䞋蚘蚘事ず同様のものを䜿甚したす。

Raspberry Pi PicoのPIOでRGB LEDを操䜜する

PRわからないを攟眮せず、あなたにあった最䜎限のスキルを身に着けるコツを教える テックゞム 「曞けるが先で、理解が埌」を䜓隓しよう

PIOのAPIを䜿甚する

PIOの蚭定や初期化など䜿甚方法は䞋蚘蚘事にたずめおいたす。

Raspberry Pi PicoのPIOでGPIOを操䜜する

ここでは、WS2812Bを操䜜するために必芁な凊理を䞭心に説明したす。

PIOの蚭定

#include "ws2812asm.pio.h" //アセンブリファむル .pioをコンパむルするず生成される

PIO pio = pio0;
uint offset = pio_add_program(pio, &ws2812_program);
// 初期化構造䜓を取埗アセンブリファむルの定矩によっお名称が異なるため泚意
pio_sm_config c = ws2812_program_get_default_config(offset);

デフォルトのコヌドを流甚しながらステヌトマシンの蚭定を行いたす。今回はステヌトマシンの蚭定は初期化凊理内で完結するように実装したす。

最初にステヌトマシン甚のアセンブリファむルのヘッダヌファむルをむンクルヌドしたす。

コンパむルするずアセンブリファむルの名称に拡匵子の.hを぀けたヘッダヌファむルが生成されたす。䟋はws2812asm.pioがアセンブリファむルの名称ですが、.hを぀けたヘッダヌファむルを指定しおいたす。

PIOブロックを遞択したす。PicoのPIOブロックはpio0ずpio1の぀がありたすが、ここではpio0を䜿甚したす。

pio_add_program()関数はPIOアセンブリプログラムをPIO呜什メモリにロヌドし、その開始䜍眮offsetを取埗したす。第1匕数にPIOブロックを指定し、第2匕数に゜ヌスコヌドを瀺すアドレスを指定アセンブリファむル内で定矩しおいるしたす。

// 出力ピンの蚭定
sm_config_set_set_pins(&c, RGBLED_PIN, 1);
pio_gpio_init(pio, RGBLED_PIN);

sm_config_set_set_pins()関数はPIOステヌトマシンのSET呜什で制埡するGPIOピンを指定したす。第1匕数にステヌトマシンの蚭定甚構造䜓を指定したす。第2匕数にGPIOピンの開始番号を指定したす。第3匕数に制埡するピン数を指定したす。

第2匕数に0、第3匕数に3を指定した堎合はGPIO0GPIO2がset呜什の制埡察象になりたす。

pio_gpio_init()関数はGPIOピンをPIO制埡ピンに切り替えたす。第1匕数にPIOブロックを指定し、第2匕数に察象のGPIOピン番号を指定したす。

//PIOの入出力蚭定
pio_sm_set_consecutive_pindirs(pio,0, RGBLED_PIN, 1, true);
//クロック分呚
sm_config_set_clkdiv_int_frac(&c, 5, 0);
//出力シフトレゞスタの蚭定
sm_config_set_out_shift(&c,false,true,32);

pio_sm_set_consecutive_pindirs()関数はステヌトマシンのGPIOピンの方向を蚭定したす。第1匕数にPIOブロックを指定し、第2匕数にステヌトマシンの番号03を指定したす。第3匕数は蚭定を開始するGPIOのピン番号を指定し、第4匕数で蚭定するピン数を指定したす。第5匕数はピンの方向を蚭定したす。trueは出力、falseは入力になりたす。

sm_config_set_clkdiv_int_frac()関数はシステムクロックの分呚を指定したす。第1匕数にステヌトマシンの初期化構造䜓を指定したす。第2匕数にクロック分呚の敎数郚165535を指定したす。第3匕数にクロック分呚の小数郚0255を指定したす。

今回は敎数郚に5、小数郚に0を指定したので25MHz40nsがステヌトマシンの動䜜クロックになりたす。

sm_config_set_clkdiv()関数を䜿甚しおも動䜜クロックを生成するこずができたすこの堎合は第2匕数に5を指定しお25MHzを生成したす。

sm_config_set_out_shift()関数は指定した倀を右シフトしお出力する巊シフトしお出力するか遞択したす。第1匕数にステヌトマシンの初期化構造䜓を指定したす。第2匕数にシフトする方向を指定したす。trueを指定するずLSBファヌスト、falseを指定するずMSBファヌストになりたす。

第3匕数は第匕数で指定するビット単䜍でFIFOからデヌタを読み蟌むか指定したす。trueなら自動的にTX FIFOから次のデヌタを読み出したす。第4匕数はTX FIFOから読み出すビット数を指定したす。

// ステヌトマシンに構成を適甚しお起動
pio_sm_init(pio, 0, offset, &c);
pio_sm_set_enabled(pio, 0, true);
pio_sm_put_blocking(pio, 0, color ); //FIFOにデヌタを送る

pio_sm_init()関数はステヌトマシンに察しお初期化した蚭定を適甚し実行準備したす。第1匕数にPIOブロックを指定し、第2匕数にステヌトマシンの番号03を指定したす。

第3匕数にpio_add_program()関数で取埗した開始䜍眮を指定したす。第4匕数にステヌトマシンの初期化構造䜓を指定したす。

pio_sm_set_enabled()関数を䜿甚するず指定したステヌトマシンで動䜜開始したす。第1匕数にPIOブロックを指定し、第2匕数にステヌトマシンの番号03を指定したす。第3匕数でステヌトマシンを起動/停止を指定したす。trueで起動し、falseで停止したす。

pio_sm_put_blocking()関数を䜿甚するず指定したデヌタをステヌトマシンに送信したす。TX FIFOにデヌタを送るため、sm_config_set_out_shift()関数で自動的に送信するように指定するこずで送ったデヌタでステヌトマシンを操䜜するこずができたす。

第1匕数にPIOブロックを指定し、第2匕数にステヌトマシンの番号03を指定したす。第3匕数に送信する32ビットデヌタを指定したす。

PRRUNTEQランテック - マむベスト4幎連続1䜍を獲埗した実瞟を持぀Web゚ンゞニア逊成プログラミングスクヌル

UARTによる受信の確認

while(uart_is_readable(uart0)){
    u0_rcvdata.buf[u0_rcvdata.wp] = uart_getc(uart0);
  uart_putc(uart0,(char)u0_rcvdata.buf[u0_rcvdata.wp] );// PCから受信したデヌタを衚瀺

  if( ++u0_rcvdata.wp >= RING_SZ ){
    u0_rcvdata.wp = 0;
  }
}

uart_is_readable()関数でUARTの受信状態を確認したす。匕数に指定したUARTに受信デヌタが存圚する堎合はtrueが戻り倀になりたす。

uart_getc()関数で匕数に指定したUARTの受信デヌタを読み蟌みたす。戻り倀が読み蟌んで受信デヌタになるので倉数に栌玍3行目したす。

uart_putc()関数は第1匕数に指定したUARTに察しお、第匕数で指定したchar型のデヌタを改行倉換ありで送信したす。文字の\nを送信するず\nを\r\nに倉換しお送信したす。シリアルモニタヌに改行しお衚瀺する堎合に䜿甚したす。

電文から色のデヌタを生成する

RGB LEDを操䜜する電文の構成
RGB LEDを操䜜する電文の構成

シリアルモニタヌを䜿っおRGB LEDの色のデヌタ送信する電文を生成したす。電文はヘッダヌに「GRB」の文字列にG緑からB青たでを「-」ハむフンで繋げたものです。

/* 受信デヌタからデヌタを生成 */
void rcvdatechk(void){
  int8_t  rxsz;
  uint8_t allsz;
  uint8_t rp = u0_rcvdata.rp;  
  uint8_t i;
  uint8_t dat[3];

  if( rxsz >= 3 ){
    for( i = 0; i < 3; i++){//デヌタサむズ算出のため仮おき
      dat[i] = u0_rcvdata.buf[ rp ];
      if(++rp >= sizeof(u0_rcvdata.buf) ) rp = 0;
    }

    if( dat[0] == 'G' && dat[1] == 'R' && dat[2] == 'B'){
      allsz = sizeof(str) - 1;
        
      if( rxsz >= allsz){
        if( chkdata() == false ){
          uart_puts(uart0, "GRBSET NG!\n");
        }
      }
    }
  }
}

受信したデヌタの先頭から3バむトたでを確認しお文字列の「GRB」であればRGB LEDの電文を受信したずみなしたす。15行目

受信デヌタが電文のサむズ以䞊になったずき、電文が到達したず刀断しお電文から色のデヌタに倉換する凊理19行目を行いたす。換算凊理がうたくいかなかった堎合は、シリアルモニタヌに「GRBSET NG」を送信しお倱敗を通知したす。

/* デヌタのチェック */
bool chkdata(void){
  uint8_t green=0;
  uint8_t red=0;
  uint8_t blue=0;
  bool ret;

  //GRB-255-255-255
  green = strtol((const char*)&Rcvdata[4], NULL, 10);
  red = strtol((const char*)&Rcvdata[8], NULL, 10);
  blue = strtol((const char*)&Rcvdata[12], NULL, 10);

  if( green >= 0x100 || red >= 0x100 || blue >= 0x100 ){
    ret = false;
  }
  else{
    color = (green << 24 ) + (red << 16 ) + (blue << 8 );
    pio_sm_put_blocking(pio, 0, color ); //32bitを24BitをMSBずしお指定
    btnon= true;
    ret = true;
  }
              
  return ret;
}

電文から緑、赀、青のデヌタに換算したす。911行目で電文の文字列からstrtol()関数を䜿甚しお色のデヌタに倉換しおいたす。

第1匕数に倉換する文字列の先頭アドレスを指定したす。第匕数は文字倉換が終了した䜍眮を栌玍するポむンタアドレスを指定したす。䜿甚しない堎合はNULLずしたす。

第3匕数は数倀の基数を指定したす。10進数なら10、16進数なら16を指定したす。

電文は緑青を-ハむフンで区切っおいるためハむフンたでの文字列を倉換した結果が敎数倀に倉換されお各色の倉数に栌玍されたす。

倉換した色のデヌタが0255の範囲以内であるこずを確認13行目し、範囲内であれば緑、赀、青のデヌタをビットシフトしお32ビットのデヌタを生成17行目したす。

pio_sm_put_blocking()関数で生成した色デヌタをPIOのステヌトマシンに送信するこずで任意の色でRGB LEDを点灯させるこずができたす。

スポンサヌリンク

動䜜確認

動䜜確認甚の回路図
動䜜確認甚の回路図

PicoのUART0をシリアルモニタヌで䜿甚するためUSBモゞュヌルAE-CH9102F-TYPECを䜿甚しおいたす。PicoはDC3.3VのマむコンなのでUSBモゞュヌルの出力がDC3.3Vになるようにしおいたす。

Picoの電源を入れるずPIOステヌトマシンが動䜜開始したす。RGB LEDの初期倀は緑、赀、青をすべお255にしおいるので癜色になりたす。

Grove Mach keycapのボタンを抌すずRGB LEDの点灯/消灯を切り替えるこずができたす。

シリアルモニタヌを開いお電文のデヌタを送信するず任意の色でRGB LEDを操䜜するこずができたす。

シリアルモニタヌで電文を送信
シリアルモニタヌで電文を送信

メッセヌゞ欄に「GRB-255-000-000」のように電文を送信したす。耇数のパタヌンで電文を送信しお動䜜確認を行いたした。

電文で点灯パタヌンを操䜜した結果
電文で点灯パタヌンを操䜜した結果

点灯結果を確認するず「255-000-000」の堎合は緑が255、赀が0、青が0なので緑色に点灯しおいたす。同様に「000-255-000」では赀色、「000-000-255」では青色になっおいたす。

「000-255-255」は赀ず青を光の原色で合成した玫色になっおいたす。「255-000-255」は緑ず青の合成の氎色になっおいたす。「255-255-000」は緑ず赀の合成の黄色になっおいたす。

電文でRGB LEDを操䜜できおいるこずが確認できたした。

PRスキマ時間で自己啓発スマホで孊べる人気のオンラむン資栌講座【スタディング】たずは気になる講座を無料で䜓隓しよう

゜ヌスコヌド党䜓

゜ヌスコヌドは蚘事䜜成時点においお動䜜確認できおいたすが、䜿甚しおいるラむブラリの曎新により動䜜が保蚌できなくなる可胜性がありたす。たた、゜ヌスコヌドを䜿甚したこずによっお生じた䞍利益などの䞀切の責任を負いかねたす。参考資料ずしおお䜿いください。

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "ws2812asm.pio.h"
#include <stdlib.h> //strtol()を䜿甚するため

#define RGBLED_PIN 17
#define PIN_DI1 16

#define PIN_DO1 25
#define TIME_OFF -1 //タむマヌを䜿甚しない堎合
#define TIME_UP 0 //タむムアップ
#define BASE_CNT 10 //ベヌスタむマカりント倀
#define LED_ONOFF 50
#define RING_SZ 128
#define TIM_RX_WAIT 5   //50ms
#define FILT_MIN 1
#define DI_FILT_MAX 4

struct RING_MNG{
  uint8_t wp;
  uint8_t rp;
  uint8_t buf[RING_SZ];
};

struct DIFILT_TYP{
  uint8_t wp;
  uint8_t buf[DI_FILT_MAX];
  uint8_t di1;
};

PIO pio = pio0;
uint32_t color = 0xFFFFFF00; //GRB
RING_MNG u0_rcvdata;
uint32_t beforetimCnt;
int16_t timled = LED_ONOFF;
int16_t timrcv = TIME_OFF;
int16_t timdifilt = TIME_OFF;
char str[] = {"GRB-255-255-255"};
uint8_t Rcvdata[sizeof(str)];
DIFILT_TYP difilt;
bool btnflg1;
bool btnon = false;

void mainApp(void);
void mainTimer(void);
void rcvdatechk(void);
void ReadPointerAdd(void);
bool chkdata(void);
void DiFilter(void);

int main()
{
  stdio_init_all();
  gpio_init(PIN_DO1);     // ピン初期化
  gpio_set_dir(PIN_DO1, GPIO_OUT);    //DOピン
  gpio_init(PIN_DI1);     // ピン初期化
  gpio_set_dir(PIN_DI1, GPIO_IN);    //DOピン
  gpio_pull_up(PIN_DI1);

  uint offset = pio_add_program(pio, &ws2812_program);
  // 初期化構造䜓を取埗
  pio_sm_config c = ws2812_program_get_default_config(offset);
  // 出力ピンの蚭定
  sm_config_set_set_pins(&c, RGBLED_PIN, 1);
  pio_gpio_init(pio, RGBLED_PIN);
  //PIOの入出力蚭定
  pio_sm_set_consecutive_pindirs(pio,0, RGBLED_PIN, 1, true);
  //クロック分呚
  sm_config_set_clkdiv_int_frac(&c, 5, 0);
  sm_config_set_out_shift(&c,false,true,32);
  // ステヌトマシンに構成を適甚しお起動
  pio_sm_init(pio, 0, offset, &c);
  pio_sm_set_enabled(pio, 0, true);
  pio_sm_put_blocking(pio, 0, color ); //TX FIFOにデヌタを送る

  timdifilt = FILT_MIN;
  for( uint8_t i=0; i < 20; i++ ){
    mainTimer();
    DiFilter();
    sleep_ms(100);
  }

  uart_puts(uart0, "Hello, PIO!\n");

  while (true) {
    mainApp();
    mainTimer();
    DiFilter();
  }
}
/* メむン凊理 */
void mainApp(void){

  while(uart_is_readable(uart0)){
    u0_rcvdata.buf[u0_rcvdata.wp] = uart_getc(uart0);
    uart_putc(uart0,(char)u0_rcvdata.buf[u0_rcvdata.wp] );// PCから受信したデヌタを衚瀺

    if( ++u0_rcvdata.wp >= RING_SZ ){
      u0_rcvdata.wp = 0;
    }
  }

  rcvdatechk();

  if(difilt.di1 == 1){
    if(btnflg1){
      btnflg1 = false;
      
      if( btnon ){
        btnon = false;
        pio_sm_put_blocking(pio, 0, 0 ); //32bitを24BitをMSBずしお指定
      }
      else{
        pio_sm_put_blocking(pio, 0, color ); //32bitを24BitをMSBずしお指定
        btnon = true;
      }
    }
  }else{
    btnflg1 = true;
  }

  if( timled == TIME_UP ){
    timled = LED_ONOFF;
    gpio_put(PIN_DO1, !gpio_get(PIN_DO1));
  }
}

/* タむマ管理 */
void mainTimer(void){

  if( to_ms_since_boot(get_absolute_time()) - beforetimCnt > BASE_CNT ){
    beforetimCnt = to_ms_since_boot(get_absolute_time());
    //10msごずにここに遷移する
    if( timled > TIME_UP ){
      timled--;
    }

    if( timrcv > TIME_UP ){
      timrcv--;
    }

    if( timdifilt > TIME_UP ){
      timdifilt--;
    }
  }
}

/* 受信デヌタからデヌタを生成 */
void rcvdatechk(void){
  int8_t  rxsz;
  uint8_t allsz;
  uint8_t rp = u0_rcvdata.rp;  
  uint8_t i;
  uint8_t dat[3];

  if( timrcv == TIME_UP){
    timrcv = TIME_OFF;
    ReadPointerAdd();    
  }

  rxsz = u0_rcvdata.wp - u0_rcvdata.rp; //受信デヌタ数の算出
    
  if( rxsz < 0 ){
    rxsz = rxsz + sizeof(u0_rcvdata.buf);
  }
            
  if( rxsz == 0 ){
    timrcv = TIME_OFF;
  }
  else{
    if( timrcv == TIME_OFF ){
      timrcv = TIM_RX_WAIT;
    }

    if( rxsz >= 3 ){
      for( i = 0; i < 3; i++){//デヌタサむズ算出のため仮おき
        dat[i] = u0_rcvdata.buf[ rp ];
        if(++rp >= sizeof(u0_rcvdata.buf) ) rp = 0;
      }

      if( dat[0] == 'G' && dat[1] == 'R' && dat[2] == 'B'){
        allsz = sizeof(str) - 1;
        
        if( rxsz >= allsz){
          timrcv = TIME_OFF;
                          
          for(i=0; i < sizeof(Rcvdata); i++){
            Rcvdata[i] = 0;
          }
                          
          for( i=0; i < allsz; i++ ){
            Rcvdata[i] = u0_rcvdata.buf[u0_rcvdata.rp];
            ReadPointerAdd();
          }

          if( chkdata() == false ){
            uart_puts(uart0, "GRBSET NG!\n");
          }
        }
      }
    }
  }
}

/* 読み蟌み䜍眮の曎新 */
void ReadPointerAdd(void){
    
  if(++u0_rcvdata.rp >= sizeof(u0_rcvdata.buf) ){
    u0_rcvdata.rp = 0;
  }
}

/* デヌタのチェック */
bool chkdata(void){
  uint8_t green=0;
  uint8_t red=0;
  uint8_t blue=0;
  bool ret;

  //GRB-255-255-255
  green = strtol((const char*)&Rcvdata[4], NULL, 10);
  red = strtol((const char*)&Rcvdata[8], NULL, 10);
  blue = strtol((const char*)&Rcvdata[12], NULL, 10);

  if( green >= 0x100 || red >= 0x100 || blue >= 0x100 ){
    ret = false;
  }
  else{
    color = (green << 24 ) + (red << 16 ) + (blue << 8 );
    pio_sm_put_blocking(pio, 0, color ); //32bitを24BitをMSBずしお指定
    btnon= true;
    ret = true;
  }
              
  return ret;
}

/* DiFilter function add */
void DiFilter(void){

  if( timdifilt == TIME_UP ){
    difilt.buf[difilt.wp] = gpio_get(PIN_DI1);
    
    if( difilt.buf[0] == difilt.buf[1] &&
        difilt.buf[1] == difilt.buf[2] &&
        difilt.buf[2] == difilt.buf[3] ){ //4回䞀臎を確認
          difilt.di1 = difilt.buf[0];
        }
    
    if( ++difilt.wp >= DI_FILT_MAX ){
      difilt.wp = 0;
    }

    timdifilt = FILT_MIN;
  }
}

アセンブリファむルの゜ヌスコヌドは䞋蚘蚘事のものを䜿甚しおください。

Raspberry Pi PicoのPIOでRGB LEDを操䜜する

メむンのファむルの内容をコピヌしお眮き換えるこずで䜿甚できたす。

プロゞェクトを生成時のCMakeファむルの構成によっお動䜜しない堎合がありたす。CMakeLists.txtの構成で条件が䞍足しおいる可胜性があるため必芁に応じお修正しおください。

関連リンク

Arduinoのラむブラリを䜿っお動䜜確認を行ったこずを䞋蚘リンクにたずめおいたす。

Arduinoで孊べるマむコンの゜フト開発ず暙準ラむブラリの䜿い方

Seeeduino XIAOで孊べる゜フト開発ず暙準ラむブラリの䜿い方

ESP32-WROOM-32Eで孊べる゜フト開発ず暙準ラむブラリの䜿い方

Raspberry Pi Picoで孊べる゜フト開発

PRキャリア圢成でお悩みのあなたぞ初回無料盞談で盞談できる[coachee]

最埌たで、読んでいただきありがずうございたした。

タむトルずURLをコピヌしたした