0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#からWS2812LEDを制御する

Posted at

ないよう

いわゆる「マイコン内蔵LED」を、PCからC#で制御します。

マイコン内蔵RGB LED WS2812B (2個入): LED(発光ダイオード) 秋月電子通商-電子部品・ネット通販

チップ単体のほか、amazon等でテープ状に実装済みの製品も各種販売されています。

今回は24bitのLEDを使用しています。32bitのLED(RGB+白)もありますが、若干の欠点等もあるので、各自必要に応じて選択してください。
ソースコードは24bit専用です。32bitを使う場合、あるいはそれらを組み合わせて使う場合は必要に応じて書き換えてください。

アダプタ

PCとの接続にはFT234Xを使いました。
FT232でも問題ないです。

あらかじめ、FT Progで論理を反転しておきます(設定を変える際はICを破壊しないように注意してください)。

タイミング

WS2812はHighの長さ、Lowの長さが、それなりに厳密に決められていますが、簡易的に使う程度なら、Highの長さだけ守っていれば問題ありません。

今回は、FT23xを3Mbaudに設定し、1bitをHighにすれば0.33..usec、2bitをHighにすれば0.66..usec、というタイミングに設定しました。
WS2812はHighが0.25-0.55usecなら0と認識し、0.65-0.95usecなら1と認識します。ということで、Highのタイミングはギリギリ満たしています。
UARTの5bitをWS2812の1bitに割り当てていますが、UART5bitは1.66..usecです。HighとLowのトータルは0.65-1.85usecの範囲なので、これも仕様の範囲内となります。一方で、Low単体での長さは、0で最大1.0usec、1で最大0.6usecですが、実際には1.33..usec/0.99..usecと、仕様を逸脱しています。
今のところは問題なく動いていますが、確実に問題なく動作するかは不明です。

信号

Inv UARTでは、0xFFを送信すると、スタートビットがHigh、その後の0bitはLow、ストップビットもLowで、10bit中で最初の1bitがHigh、残りの9bitがLowのパルスを送出できます。

image.png

0がスタートビット、1-8がデータビット、9がストップビット、です。


0xEFを送信すると、スタートビットがHigh、その後4bitがLow、次の1bitがHigh、その後の3bitがLow、ストップビットもLowで、10bitの中で最初の1bitがHigh、次の4bitがLow、次の1bitがHigh、次の4bitがLow、というパルスを送出できます。

image.png

0がスタートビット、1-8がデータビット、9がストップビットで、データビットはLSBファーストで転送されます。

パルスの幅が0.33..us、パルス周期が1.66..usのパルス列が作れるようになりました。


0xEEを送信すると、1つ目のパルス幅が広がります。

image.png

0xCFを送信すると、2つ目のパルス幅が広がります。

image.png

0xCEを送信すると、2つのパルス幅が広がります。

image.png

これで、短パルス0.33..us、長パルス0.66..us、パルス周期1.66..usのパルス列を2つ、送れるようになりました。
フルカラーLEDでは24bit必要なので、これを12回繰り返せば、必要なパルス列を送信できます。さらにLEDの個数分繰り返せば、それぞれのLEDに色を指定できるようになります。

ソースコード

上述の4種類のバイト列を、指定された色に従って送信するC#コードは以下のようになります。

class WS28xx_24bit : IDisposable
{
    private const int BaudRate = 3 * 1000 * 1000;

    private static readonly byte[] table = { 0xEF, 0xCF, 0xEE, 0xCE };
    private const int bytesPerColor = 8 / 2 * 3;

    private readonly SerialPort port;
    private readonly int chips;

    public WS28xx_24bit(string PortName, int Chips = 1)
    {
        port = new SerialPort(PortName, BaudRate);
        chips = Chips;
    }

    public void Dispose()
    {
        Close();
    }

    public void SendColors(Color[] color)
    {
        List<byte> list = new List<byte>(bytesPerColor * chips);

        foreach (Color c in color)
        {
            list.AddRange(convert24(c));
        }

        for (int i = color.Length; i < chips; i++)
        {
            list.AddRange(convert24(Color.Black));
        }

        port.Write(list.ToArray(), 0, list.Count);
    }

    public void Clear(Color color)
    {
        Color[] tmp = new Color[chips];

        for (int i = 0; i < tmp.Length; i++)
        {
            tmp[i] = color;
        }

        SendColors(tmp);
    }

    public void Open()
    {
        if (!port.IsOpen)
        {
            port.Open();
        }
    }

    public void Close()
    {
        if (port.IsOpen)
        {
            port.Close();
        }
    }

    private static byte[] convert24(Color color)
    {
        return (new[] {
            table[color.G >> 6 & 0x03],
            table[color.G >> 4 & 0x03],
            table[color.G >> 2 & 0x03],
            table[color.G >> 0 & 0x03],

            table[color.R >> 6 & 0x03],
            table[color.R >> 4 & 0x03],
            table[color.R >> 2 & 0x03],
            table[color.R >> 0 & 0x03],

            table[color.B >> 6 & 0x03],
            table[color.B >> 4 & 0x03],
            table[color.B >> 2 & 0x03],
            table[color.B >> 0 & 0x03],
        });
    }
}

こんな感じで使います。

using (WS28xx_24bit ws28xx = new WS28xx_24bit("COM21", 32))
{
    ws28xx.Open();

    int a = 2;
    int b = 1;
    int c = 1;

    ws28xx.SendColors(new[]
    {
        Color.FromArgb(a, 0, 0),
        Color.FromArgb(b, b, 0),
        Color.FromArgb(0, a, 0),
        Color.FromArgb(0, b, b),
        Color.FromArgb(0, 0, a),
        Color.FromArgb(b, 0, b),

        Color.FromArgb(c * 1,c * 1,c * 1),
        Color.FromArgb(c * 2,c * 2,c * 2),
        Color.FromArgb(c * 4,c * 4,c * 4),
        Color.FromArgb(c * 8,c * 8,c * 8),
    });


    System.Threading.Thread.Sleep(1000);

    ws28xx.Clear(Color.Black);
}

こんな感じになります。

2019-04-03_14-37-56_IMGP2846.jpg


HSV色空間をRGBに変換するメソッドを用意して、以下のようにすれば、少しずつ色が変わる感じの照明が作れます。間接照明とかに使うとたのしいです。

DateTime start = DateTime.Now;

while ((DateTime.Now - start).TotalSeconds <= 30)
{
    // H: 色相
    // S: 彩度
    // V: 明度

    double h = (DateTime.Now - start).TotalSeconds / 10 % 1;
    double s = 0.5;
    double v = 0.25;

    ws28xx.Clear(HSV2RGB(h, s, v));

    System.Threading.Thread.Sleep(50);
}

線上のある1点を任意に光らせる、といった使い方ができるので、例えば、ブラウザ経由でキーワードを入力すれば、それに関係した本の位置が光で教えてくれて、色で関連度を表現する、みたいな本棚とか作ったら面白そうですね。

その他

あまり輝度を高くするとめちゃくちゃ発熱します。上記写真のタイプで、1mあたり45Wだそうです。ヤバイ。当然、電源にもそれだけの余裕が求められます。バスパワーで使う際は消費電力に注意してください。

最初、転送ごとにSerialPortをOpen/Closeしていたけど、特にCloseにめちゃくそ時間がかかるっぽいので開きっぱなしにするようにしました。

寝るときとかに眩しいので明度を下げようとしたけど、そうすると明るさの分解能が下がり輝度変化が大きくなってよりチカチカするように見えます。部屋に常設するなら、多少眩しくてもある程度明度を上げて分解能を稼いだほうが良です。

USB1本で数百個のLEDを24bitで制御できます。200個のLEDでも転送には10msec未満しか必要ないので、十分な速度で転送できるはずです。たとえ1000個でも、USBを2本くらい使えばいいだけなので、簡単です! レッツパーリィ!!

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?