AIと電子工作系ブログ

趣味でやっている電子工作とtennsorflow等を使った何かを書いていきます。

WS2812C-2020(マイコン内臓RGBLED)をPIC12F1822で動作させてみた

秋月電子で売られているWS2812C-2020(マイコン内臓RGBLEDをPIC12F1822で動かしてみました。
今回紹介する基板4枚を使ってLED照明にすることができます。
この記事ではWS2812C-2020をPIC12F1822での動かし方について解説していきます。


秋月で売っているWS2812C-2020とPIC12F1822のURLです。
akizukidenshi.com
akizukidenshi.com


WS2812C-2020の詳しい動作については下記で説明していますので、知りたい方は参照してください。
www.soiyasoiya.com



WS2812C-2020とPIC12F1822を動作させるための回路は下図です。
回路CADはEAGLEを使用して書きました。

LED基板回路図


JLCPCBで注文できるようのガーバーを下記URLに置いていますのでほしい方はどうぞ。
drive.google.com


PIC12F1822用のプログラム(mplabのプロジェクトファイル)も下記URLに用意しています。
(私の環境では実際に動作することはを確認していますが、信号についてデーターシートの範囲内を守れていないので確実に動くとは確約できません)
drive.google.com


プログラムはMPLABのMCCを使用してマイコンの設定部分は生成しています。
私が作成した部分の動作について解説していきます。


メイン関数のプログラムは下記です。

void main(void)
{
    SYSTEM_Initialize();

    PORTAbits.RA2=0;
    ct_led=0;
    while (1)
    {
        for(ct_led=0;ct_led<=15;ct_led++){
            RGB_data(255,255,255);
        }
        __delay_ms(10);
    }
}

SYSTEM_Initialize();はピン設定、クロック、ウォッチドックタイマの初期化を行っています(MCC自動生成)。

RGB_data(255,255,255);でLEDが白色になるようにデータを送ります。
RGB_data(RED,BLUE,GREEN);で入れられる数値は0~255までの間です。

RGB_data(255,255,255);をfor文で15回繰り返した後、delayで10mS何もしない時間を入れています。
delayを最後に入れないとデータが区切られないためLEDの色反映が行われません。

関数呼び出しの流れは
main→RGB_data(char RED,char GREEN,char BLUE)→LED_out_data(char d1,char d2...char d24)→code_1() か code_0()という4段階になっていますのでこれから関数についての説明していきます。



次にRGB_data関数の説明をします。
RGB_data関数は下記です。

void RGB_data(char RED,char GREEN,char BLUE){
    char d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d12,d13,
             d14,d15,d16,d17,d18,d19,d20,d21,d22,d23,d24;
    d1=GREEN&0b10000000;
    if(d1>=1)d1=1;
    d2=GREEN&0b01000000;
    if(d2>=1)d2=1;
    d3=GREEN&0b00100000;
    if(d3>=1)d3=1;
    d4=GREEN&0b00010000;
    if(d4>=1)d4=1;
    d5=GREEN&0b00001000;
    if(d5>=1)d5=1;
    d6=GREEN&0b00000100;
    if(d6>=1)d6=1;
    d7=GREEN&0b00000010;
    if(d7>=1)d7=1;
    d8=GREEN&0b00000001;
    if(d8>=1)d8=1;

    d9=RED&0b10000000;
    if(d9>=1)d9=1;
    d10=RED&0b01000000;
    if(d10>=1)d10=1;
    d11=RED&0b00100000;
    if(d11>=1)d11=1;
    d12=RED&0b00010000;
    if(d12>=1)d12=1;
    d13=RED&0b00001000;
    if(d13>=1)d13=1;
    d14=RED&0b00000100;
    if(d14>=1)d14=1;
    d15=RED&0b00000010;
    if(d15>=1)d15=1;
    d16=RED&0b00000001;
    if(d16>=1)d16=1;
    
    d17=BLUE&0b10000000;
    if(d17>=1)d17=1;
    d18=BLUE&0b01000000;
    if(d18>=1)d18=1;
    d19=BLUE&0b00100000;
    if(d19>=1)d19=1;
    d20=BLUE&0b00010000;
    if(d20>=1)d20=1;
    d21=BLUE&0b00001000;
    if(d21>=1)d21=1;
    d22=BLUE&0b00000100;
    if(d22>=1)d22=1;
    d23=BLUE&0b00000010;
    if(d23>=1)d23=1;
    d24=BLUE&0b00000001;
    if(d24>=1)d24=1;
    
    LED_out_data(d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d12,d13,d14,d15,d16,d17,d18,d19,d20,d21,d22,d23,d24);
}

RGB_data(RED,BLUE,GREEN);で渡された数値を2進数化してd1~d24の中に入れています。
なぜこんな長くなるような書き方をしているかというと、PIC12F1822でWS2812C-2020を制御しようとすると処理能力とI/Oの応答速度がかなりギリギリのためこのような記述になっています。


次にLED_out_dataではRGB_data(char RED,char GREEN,char BLUE)にて1と0に分けたデータをここで判定して1の時はWS2812C-2020への信号として1の波形が出力されるよう code_1()を呼び出し、
0の出力を行うときはcode_0()を呼び出すようにしています。
d1~d24までの24bit分のデータをここで送ります。

void LED_out_data(char d1,char d2,char d3,char d4,char d5,char d6,char d7,char d8,
          char d9,char d10,char d11,char d12,char d13,char d14,char d15,char d16,
              char d17,char d18,char d19,char d20,char d21,char d22,char d23,char d24){
    if(d1==1){
        code_1();
    }else{
        code_0();
    }
    if(d2==1){
        code_1();
    }else{
        code_0();
    }
    if(d3==1){
        code_1();
    }else{
        code_0();
    }
    if(d4==1){
        code_1();
    }else{
        code_0();
    }
    if(d5==1){
        code_1();
    }else{
        code_0();
    }
    if(d6==1){
        code_1();
    }else{
        code_0();
    }
    if(d7==1){
        code_1();
    }else{
        code_0();
    }
    if(d8==1){
        code_1();
    }else{
        code_0();
    }
    
    if(d9==1){
        code_1();
    }else{
        code_0();
    }
    if(d10==1){
        code_1();
    }else{
        code_0();
    }
    if(d11==1){
        code_1();
    }else{
        code_0();
    }
    if(d12==1){
        code_1();
    }else{
        code_0();
    }
    if(d13==1){
        code_1();
    }else{
        code_0();
    }
    if(d14==1){
        code_1();
    }else{
        code_0();
    }
    if(d15==1){
        code_1();
    }else{
        code_0();
    }
    if(d16==1){
        code_1();
    }else{
        code_0();
    }

    if(d17==1){
        code_1();
    }else{
        code_0();
    }
    if(d18==1){
        code_1();
    }else{
        code_0();
    }
    if(d19==1){
        code_1();
    }else{
        code_0();
    }
    if(d20==1){
        code_1();
    }else{
        code_0();
    }
    if(d21==1){
        code_1();
    }else{
        code_0();
    }
    if(d22==1){
        code_1();
    }else{
        code_0();
    }
    if(d23==1){
        code_1();
    }else{
        code_0();
    }
    if(d24==1){
        code_1();
    }else{
        code_0();
    }
}


最後の関数であるcode_1()とcode_0()はI/Oポート出力を行います。
I/O出力はPORTAのRA2(PIC12F1822の5番ピン)から出力されます。
PORTAbits.RA2=1;はRA2をHigh出力に、PORTAbits.RA2=0;でLow出力にしています。
asm("NOP");はアセンブラ記述で何もしない命令を行っています。
このマイコンは32MHzで動作させていますがPICシリーズの8bitマイコンは4クロックで1命令となるため、
asm("NOP");は125nSの待ちとなります。

void code_1(void){
    PORTAbits.RA2=1;
    asm("NOP");
    asm("NOP");
    asm("NOP");
    PORTAbits.RA2=0;
}

void code_0(void){
    PORTAbits.RA2=1;
    PORTAbits.RA2=0;
}

ここで疑問を持たれる方もいるかもしれませんが、code_0()関数内の
PORTAbits.RA2=1;
PORTAbits.RA2=0;
では125nSずつで計250nSの信号になってしまうのではと疑問を持たれるかもしれませんが、
I/Oポートの実際の出力は立ち上がりと立ち下がりで反映されるまでに時間が多少かかり125nSぴったりの信号にはなりません。(立ち上がりと立下りまでの反映時間は同じではありません)
PORTAbits.RA2=0;が長くなるのは次のデータを送るまでにLED_out_data関数内のif分で判定に時間を取られるのと関数で呼び出すまでのオーバーヘッドが存在するため125nSより長くなります。

実際にcode_1を呼び出したときの波形が下画像です。
5V(High)の区間が約0.6μS、0V区間(Low)が約2μSになっています。
LEDに1codeの信号を伝える場合の範囲はHigh voltage timeが580nS~1μSの間でLow voltage timeも580nS~1μSの間に入らないといけないのですが、私が作ったこのプログラムではLow voltage timeがオーバーしています。
ただ、このプログラムは実際にLEDに1の信号と認識され制御できているため実際の仕様範囲はもっと広いものだと思われます。

code_1の波形

0codeの信号は5V区間は0.4μS、0V区間は1.8μSです。High voltage timeは220nS~380μSの間、Low voltage timeは580nS~1μSですがどちらも超過しています。
ポートのHighとLowの切り替えは最速で行われていますがこの最小単位が0.4μSなので、このルールを守るにはI/Oポートの反応時間がこれよりも短い物かつ処理も高速化する必要があるため16bitマイコンを持ってこないと間に合わないと思います。
今回は一応動いてるのでこれで良しとしておきます...

code_0の波形

↓仕様書に記載されている1codeと0codeの時間表と図です。




最後に実際の動作画像と色変更の仕方について紹介します。
メイン関数内のRGB_data(255,255,255);の数字を変更することで色を変えることができます。
RGB_dataのデータの順番はRED,BLUE,GREENの順になっています。
数値を変えたときの画像が下記になります。

RGB_data(255,0,0);

赤色点灯

RGB_data(0,255,0);

緑点灯

RGB_data(0,0,255);

青色点灯

RGB_data(128,0,128);

紫色点灯

基板を増やしての制御と赤外線で明るさ制御もできるようにしたので気が向けばこれについても記事にしようと思います。




uSとnSを間違えている箇所があったため修正しました。(2024年9月30日)