LoginSignup
1
1

More than 3 years have passed since last update.

RaspberryPiZeroを使ってソフトウェアPWMで三色LEDを光らせる

Last updated at Posted at 2019-10-14

はじめに

PCを好きな色に光らせるために書いた記事です.RGBの三色LEDを好きな色に光らせるプログラムを作ってみました。RaspberryPiZeroにあるGPIOピンを使ってLEDを光らせます。ソフトウェアでPWM制御することで色の強弱を表現します。ソースコードは最後にまとめて載せます。

環境

Raspberry Pi Zero WH
Raspbian 10.0

説明

アルゴリズム

PWMの1周期で、最初に赤、緑、青すべてのLEDを点灯させ、RGB値に応じて適切なタイミングで消灯させます。
まず、RGB値を小さい順に並べます。次に、RGB値の小さいものから順に消灯するために、消灯する時間間隔を計算します。

LEDをON,OFFするスレッドを作り、RGB値の並べ替えや時間間隔を計算するのとは別にします。

ファイル

ledmain.cに点灯させる色の流れを記述しておきます。ledmain.cからledgpio.cを呼び出してGPIOを制御します。ledgpio.cではpigpioを使います。

最初にpinInitialise()を呼び、最後にpinTerminate()を呼びます。colorLed()またはgraduateLed()で色と点灯時間を指定してLEDを光らせます。

実行方法

  1. 下記のledgpio.cledgpio.hledmain.cを同じディレクトリに入れます。

  2. ledmain.cを適当に書き換えます。

  3. $ gcc -O3 -o ledmain -lpigpio -lpthread ledgpio.c ledmain.cコンパイルします。ledmainという実行ファイルができます。

  4. $ sudo ./ledmain実行します。

ソースコード

ledmain.c
#include <pigpio.h>
#include "ledgpio.h"

int main(void){
    int i;
    int pin[3] = {17, 18, 19}; // 赤: GPIO17ピン, 緑: GPIO18ピン, 青: GPIO19ピン
    pinInitialise(pin);

    for(i=0; i<100; i++){
        colorLed(1, 0, 0, 1000000, pin); // 赤、1000000マイクロ秒
        colorLed(0, 1, 0, 1000000, pin); // 緑、1000000マイクロ秒
        colorLed(0, 0, 1, 1000000, pin); // 青、1000000マイクロ秒
        graduateLed(0, 0, 1, 1, 0, 1, 2000000, pin); // 青→マゼンタ、2000000マイクロ秒
        graduateLed(1, 0, 1, 1, 0, 0, 2000000, pin); // マゼンタ→赤、2000000マイクロ秒
        graduateLed(1, 0, 0, 1, 1, 0, 2000000, pin); // 赤→黄、2000000マイクロ秒
        graduateLed(1, 1, 0, 0, 1, 0, 2000000, pin); // 黄→緑、2000000マイクロ秒
        graduateLed(0, 1, 0, 0, 1, 1, 2000000, pin); // 緑→シアン、2000000マイクロ秒
        graduateLed(0, 1, 1, 1, 1, 1, 2000000, pin); // シアン→白、2000000マイクロ秒
        colorLed(1, 1, 1, 1000000, pin);  // 白、1000000マイクロ秒
    }

    pinTerminate(pin);
    return 0;
}

ledgpio.h
#ifndef _LEDGPIO_H
#define _LEDGPIO_H

void pinInitialise(int pin[3]);
void pinTerminate(int pin[3]);
void colorLed(double red, double green, double blue, long time_usec, int pin[3]);
void graduateLed(double red1, double green1, double blue1,
                double red2, double green2, double blue2, long time_usec, int pin[3]);

#endif

ledgpio.c
#include <pigpio.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>

long period_usec = 5000; // PWMの周期

// スレッドに値を渡すための変数
pthread_t thread;
volatile int loop_on, renew, pin0, pin1, pin2;
volatile useconds_t time0, time1, time2, time3;

/**
 * @fn
 * LEDをON,OFFさせるためのスレッド
 */
void *loop_thread(){
    int pin0L, pin1L, pin2L;
    useconds_t time0L, time1L, time2L, time3L;
    while(loop_on){
        if(renew){ // 色を更新する。
            renew = -1;
            pin0L = pin0;
            pin1L = pin1;
            pin2L = pin2;
            time0L = time0;
            time1L = time1;
            time2L = time2;
            time3L = time3;
            renew = 0;
        }
        gpioWrite(pin0L, PI_ON);
        gpioWrite(pin1L, PI_ON);
        gpioWrite(pin2L, PI_ON);
        usleep(time0L);
        gpioWrite(pin0L, PI_OFF);
        usleep(time1L);
        gpioWrite(pin1L, PI_OFF);
        usleep(time2L);
        gpioWrite(pin2L, PI_OFF);
        usleep(time3L);
    }
}

/**
 * @fn
 * 初期化する関数。最初に呼ぶ。
 * @param (pin) GPIOのピン番号
 */
void pinInitialise(int pin[3]){
    if(gpioInitialise() < 0) return;
    gpioSetMode(pin[0], PI_OUTPUT);
    gpioSetMode(pin[1], PI_OUTPUT);
    gpioSetMode(pin[2], PI_OUTPUT);
    gpioWrite(pin[0], PI_OFF);
    gpioWrite(pin[1], PI_OFF);
    gpioWrite(pin[2], PI_OFF);
    loop_on = 1;
    pin0 = pin[0];
    pin1 = pin[1];
    pin2 = pin[2];
    time0 = 0;
    time1 = 0;
    time2 = 0;
    time3 = period_usec;
    renew = 1;
    pthread_create(&thread, NULL, &loop_thread, NULL);
}

/**
 * @fn
 * 最後に呼ぶ。
 * @param (pin) GPIOのピン番号
 */
void pinTerminate(int pin[3]){
    loop_on = 0;
    pthread_join(thread, NULL);
    gpioWrite(pin[0], PI_OFF);
    gpioWrite(pin[1], PI_OFF);
    gpioWrite(pin[2], PI_OFF);
    gpioTerminate();
}

/**
 * @fn
 * 指定の色で光らせる。
 * @param (red) RGBの赤色成分
 * @param (green) RGBの緑色成分
 * @param (blue) RGBの青色成分
 * @param (time_usec) 光らせる時間。マイクロ秒。
 * @param (pin) GPIOのピン番号
 */
void colorLed(double red, double green, double blue, long time_usec, int pin[3]){
    double tr, tg, tb;
    clock_t start = clock();
    tr = period_usec * red;
    tg = period_usec * green;
    tb = period_usec * blue;
    while(renew == -1);
    // RGB値の小さい順にソート
    if(tr < tg){
        if(tb < tr){
            time0 = tb;
            time1 = tr - tb;
            time2 = tg - tr;
            time3 = 1.0 - tg;
            pin0 = pin[2];
            pin1 = pin[0];
            pin2 = pin[1];
        }else if(tg < tb){
            time0 = tr;
            time1 = tg - tr;
            time2 = tb - tg;
            time3 = 1.0 - tb;
            pin0 = pin[0];
            pin1 = pin[1];
            pin2 = pin[2];
        }else{
            time0 = tr;
            time1 = tb - tr;
            time2 = tg - tb;
            time3 = 1.0 - tg;
            pin0 = pin[0];
            pin1 = pin[2];
            pin2 = pin[1];
        }
    }else{
        if(tr < tb){
            time0 = tg;
            time1 = tr - tg;
            time2 = tb - tr;
            time3 = 1.0 - tb;
            pin0 = pin[1];
            pin1 = pin[0];
            pin2 = pin[2];
        }else if(tg < tb){
            time0 = tg;
            time1 = tb - tg;
            time2 = tr - tb;
            time3 = 1.0 - tr;
            pin0 = pin[1];
            pin1 = pin[2];
            pin2 = pin[0];
        }else{
            time0 = tb;
            time1 = tg - tb;
            time2 = tr - tg;
            time3 = 1.0 - tr;
            pin0 = pin[2];
            pin1 = pin[1];
            pin2 = pin[0];
        }
    }
    renew = 1;
    usleep(time_usec);
}

/**
 * @fn
 * 色1から色2にだんだん変わるように光らせる。
 * @param (red1) 開始色の赤色成分
 * @param (green1) 開始色の緑色成分
 * @param (blue1) 開始色の青色成分
 * @param (red2) 終了色の赤色成分
 * @param (green2) 終了色の緑色成分
 * @param (blue2) 終了色の青色成分
 * @param (time_usec) 光らせる時間。マイクロ秒。
 * @param (pin) GPIOのピン番号
 */
void graduateLed(double red1, double green1, double blue1,
        double red2, double green2, double blue2, long time_usec, int pin[3]){
    long t, switchTime;
    int i, o, o0[4], o1[4], o2[4], oo0, oo1, oo2, p0, p1, p2;
    double ts[3], te[3], x, x01, x02, x12, xo[5], r_time_usec, aa0, aa1, aa2, aa3, bb0, bb1, bb2, bb3;
    clock_t start = clock();

    ts[0] = period_usec * red1;
    ts[1] = period_usec * green1;
    ts[2] = period_usec * blue1;
    te[0] = period_usec * red2;
    te[1] = period_usec * green2;
    te[2] = period_usec * blue2;

    // RGBを小さい順にソート
    if(ts[0] < ts[1]){
        if(ts[1] < ts[2]){
            o0[0] = 0;
            o1[0] = 1;
            o2[0] = 2;
        }else if(ts[0] < ts[2]){
            o0[0] = 0;
            o1[0] = 2;
            o2[0] = 1;
        }else{
            o0[0] = 2;
            o1[0] = 0;
            o2[0] = 1;
        }
    }else{
        if(ts[0] < ts[2]){
            o0[0] = 1;
            o1[0] = 0;
            o2[0] = 2;
        }else if(ts[1] < ts[2]){
            o0[0] = 1;
            o1[0] = 2;
            o2[0] = 0;
        }else{
            o0[0] = 2;
            o1[0] = 1;
            o2[0] = 0;
        }
    }
    if(ts[o0[0]] == ts[o2[0]] && te[o1[0]] > te[o2[0]]){
        o = o1[0];
        o1[0] = o2[0];
        o2[0] = o;
    }
    if(ts[o0[0]] == ts[o1[0]] && te[o0[0]] > te[o1[0]]){
        o = o0[0];
        o0[0] = o1[0];
        o1[0] = o;
    }
    if(ts[o1[0]] == ts[o2[0]] && te[o1[0]] > te[o2[0]]){
        o = o1[0];
        o1[0] = o2[0];
        o2[0] = o;
    }

    // RGBのグラフの交点を求める
    if((ts[o0[0]]>=ts[o1[0]]) != (te[o0[0]]>=te[o1[0]]) && ts[o0[0]] != ts[o1[0]]){
        x = ts[o1[0]] - ts[o0[0]];
        x01 = x / (x - te[o1[0]] + te[o0[0]]);
    }else{
        x01 = 1.0;
    }
    if((ts[o0[0]]>=ts[o2[0]]) != (te[o0[0]]>=te[o2[0]]) && ts[o0[0] != ts[o2[0]]]){
        x = ts[o2[0]] - ts[o0[0]];
        x02 = x / (x - te[o2[0]] + te[o0[0]]);
    }else{
        x02 = 1.0;
    }
    if((ts[o1[0]]>=ts[o2[0]]) != (te[o1[0]]>=te[o2[0]]) && ts[o1[0]] != ts[o2[0]]){
        x = ts[o2[0]] - ts[o1[0]];
        x12 = x / (x - te[o2[0]] + te[o1[0]]);
    }else{
        x12 = 1.0;
    }
    xo[0] = 0.0;
    if(x01 < x02){
        if(x02 < x12){
            xo[1] = x01;
            xo[2] = x02;
            xo[3] = x12;
            o0[1] = o1[0];
            o1[1] = o0[0];
            o2[1] = o2[0];
            o0[2] = o1[0];
            o1[2] = o2[0];
            o2[2] = o0[0];
            o0[3] = o2[0];
            o1[3] = o1[0];
            o2[3] = o0[0];
        }else if(x01 < x12){
            xo[1] = x01;
            xo[2] = x12;
            xo[3] = x02;
            o0[1] = o1[0];
            o1[1] = o0[0];
            o2[1] = o2[0];
            o0[2] = o2[0];
            o1[2] = o0[0];
            o2[2] = o1[0];
            o0[3] = o0[0];
            o1[3] = o2[0];
            o2[3] = o1[0];
        }else{
            xo[1] = x12;
            xo[2] = x01;
            xo[3] = x02;
            o0[1] = o0[0];
            o1[1] = o2[0];
            o2[1] = o1[0];
            o0[2] = o1[0];
            o1[2] = o2[0];
            o2[2] = o0[0];
            o0[3] = o1[0];
            o1[3] = o0[0];
            o2[3] = o2[0];
        }
    }else{
        if(x01 < x12){
            xo[1] = x02;
            xo[2] = x01;
            xo[3] = x12;
            o0[1] = o2[0];
            o1[1] = o1[0];
            o2[1] = o0[0];
            o0[2] = o2[0];
            o1[2] = o0[0];
            o2[2] = o1[0];
            o0[3] = o1[0];
            o1[3] = o0[0];
            o2[3] = o2[0];
        }else if(x02 < x12){
            xo[1] = x02;
            xo[2] = x12;
            xo[3] = x01;
            o0[1] = o2[0];
            o1[1] = o1[0];
            o2[1] = o0[0];
            o0[2] = o1[0];
            o1[2] = o2[0];
            o2[2] = o0[0];
            o0[3] = o0[0];
            o1[3] = o2[0];
            o2[3] = o1[0];
        }else{
            xo[1] = x12;
            xo[2] = x02;
            xo[3] = x01;
            o0[1] = o0[0];
            o1[1] = o2[0];
            o2[1] = o1[0];
            o0[2] = o2[0];
            o1[2] = o0[0];
            o2[2] = o1[0];
            o0[3] = o2[0];
            o1[3] = o1[0];
            o2[3] = o0[0];
        }
    }
    xo[3] = 1.0;

    // PWMで光らせる.
    r_time_usec = 10.0 / time_usec;
    for(i=0; xo[i]<1.0; i++){
        switchTime = (long)(time_usec/10 * xo[i+1]);
        oo0 = o0[i];
        oo1 = o1[i];
        oo2 = o2[i];
        bb0 = ts[oo0];
        bb1 = ts[oo1]     - ts[oo0];
        bb2 = ts[oo2]     - ts[oo1];
        bb3 = period_usec - ts[oo2];
        aa0 = (te[oo0]               - bb0) * r_time_usec;
        aa1 = (te[oo1]     - te[oo0] - bb1) * r_time_usec;
        aa2 = (te[oo2]     - te[oo1] - bb2) * r_time_usec;
        aa3 = (period_usec - te[oo2] - bb3) * r_time_usec;
        while(renew == -1);
        pin0 = pin[oo0];
        pin1 = pin[oo1];
        pin2 = pin[oo2];
        t = clock() - start;
        while(t <= switchTime){
            while(renew == -1);
            time0 = aa0 * t + bb0;
            time1 = aa1 * t + bb1;
            time2 = aa2 * t + bb2;
            time3 = aa3 * t + bb3;
            renew = 1;
            usleep(period_usec);
            t = clock() - start;
        }
    }

}

速度重視でプログラム組んだらソースコード長くなってしまいました(汗)

2019/10/16追記
@fujitanozomu 様からコメントをいただき,グローバル変数をvolatile宣言しました.

エラー対処

initMboxBlock: init mbox zaps failed というエラーが出ることがあります。$ sudo rebootでRaspberryPiZeroを再起動すればなおります。

参考

PWM制御

明るさを変化させてみよう(PWMでアナログ出力)

GPIO

Raspberry Pi ZeroでLチカ - Developers Note

pigpio

C言語でpigpio.hを使う(raspberry pi-pigpio_if2.h)

pigpioのCインターフェイスを使いたい デジタル入出力編

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1