はじめに
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を光らせます。
実行方法
-
下記の
ledgpio.c
、ledgpio.h
、ledmain.c
を同じディレクトリに入れます。 -
ledmain.c
を適当に書き換えます。 -
$ gcc -O3 -o ledmain -lpigpio -lpthread ledgpio.c ledmain.c
でコンパイルします。ledmain
という実行ファイルができます。 -
$ sudo ./ledmain
で実行します。
ソースコード
#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;
}
#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
#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制御
GPIO
Raspberry Pi ZeroでLチカ - Developers Note