C++ Builder CE 周辺装置の駆動

WS2812BとシリアルI/FでLチカ

フルカラーLEDの集合体(単一やテープ状あるいは正方形に配列したものと多種存在)を駆動する仕組みはシリアルな信号を送ることで行います。シリアルな信号といえば古典的なRS-232Cという規格で定義されたものが有名ですが、古典的過ぎてつまりレガシー過ぎて、最近のPCには装備されていません。そういう場合のためにUSBにつないでRS-232CなI/F(正確にはなんちゃってRS-232Cでまず信号レベルが合致しません)を模倣するチップがあります。それを小型基板に実装したものの例が、

これです。これを使って、うまくタイミングを取るとWS2812BなLED群を駆動できるようですので、その具体的なやり方を記事にします。まずは基礎編ですね。

基板自体は、このサイズで、左側がUSB-Bメスで、右がシリアルI/F側(但しなんちゃってなので、TTLレベルつまり0-5Vレベル)使用時の基板が、

PC側に認識されると、上図のように“PWR”LEDが点灯します。ここでシリアルのポート番号を設定する必要があります。まずWindowsのデバイスマネージャーから、

上記をダブルクリック、

ポートの設定をクリック、

ここで詳細設定

左側一番上を“COM200”へ、で”OK”をクリック、COMポート番号を記録するか所望のものに設定しておく必要があります。筆者は習慣的に200にしています。デバイスマネージャーに戻って、

になっていればおけ。全部閉じます。

今回は上図の8素子直列のWS2812B素子を使います。amazonだと、

ユニバーサル基板に挿すために、L字ブラケットをはんだ付けしますけど、付けた状態では極めて脆いので細心の注意が必要です。2個セットを買いましたが、すでに1個壊れてこれだけ残っています。さて、一番下の素子レベルのドライバーですが、RS-232CなI/Fで駆動するので、C++から扱えるドライバーが必要です。自分で書いたものではありません。出典不明で恐縮ですが、まずヘッダーから、“SerialPort.h”は、

#ifndef SerialPort_H
#define SerialPort_H

#include <cstdlib>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <string>
#include <string.h>

#include <windows.h>
#include <conio.h>

#define RX_SIZE         4096    // in buffer size
#define TX_SIZE         4096    // out buffer size
#define MAX_WAIT_READ   5000    // max waiting time (in ms)




class SerialPort
{
public :
  SerialPort();
  virtual ~SerialPort();

  bool Open(std::string s);
  bool Read(char* buffer, unsigned int nBytesToRead, unsigned int *pBytesRead);
  bool Write(unsigned char *buffer, unsigned int nBytesToWrite, unsigned int *pBytesWritten);
  bool Close();

private :

  HANDLE	  	g_hCOM;
  COMMTIMEOUTS  g_cto;
  DCB         	g_dcb;

  void Init();

};

#endif

“SerialPort.cpp”は、

#include "SerialPort.h"

using namespace std;

SerialPort::SerialPort()
{

  this->Init();

}

SerialPort::~SerialPort()
{
  this->Close();
}

void SerialPort::Init()
{
  g_hCOM = NULL;
  COMMTIMEOUTS t_g_cto =
  {
    //MAX_WAIT_READ,  /* ReadIntervalTimeOut          */
    0,              /* ReadTotalTimeOutMultiplier   */
    //MAX_WAIT_READ,  /* ReadTotalTimeOutConstant     */
    0,              /* WriteTotalTimeOutMultiplier  */
    0               /* WriteTotalTimeOutConstant    */
  };

  memcpy(&g_cto, &t_g_cto, sizeof(t_g_cto));

  //  port configuration
  DCB t_g_dcb =
  {
    sizeof(DCB),        /* DCBlength            */
    3000000,    //19200,               /* BaudRate             */
    TRUE,               /* fBinary              */
    FALSE,              /* fParity              */
    FALSE,              /* fOutxCtsFlow         */
    FALSE,              /* fOutxDsrFlow         */
    DTR_CONTROL_ENABLE, /* fDtrControl          */
    FALSE,              /* fDsrSensitivity      */
    FALSE,              /* fTXContinueOnXoff    */
    FALSE,              /* fOutX                */
    FALSE,              /* fInX                 */
    FALSE,              /* fErrorChar           */
    FALSE,              /* fNull                */
    RTS_CONTROL_ENABLE, /* fRtsControl          */
    FALSE,              /* fAbortOnError        */
    0,                  /* fDummy2              */
    0,                  /* wReserved            */
    0x100,              /* XonLim               */
    0x100,              /* XoffLim              */
    8,                  /* ByteSize             */
    NOPARITY,           /* Parity               */
    ONESTOPBIT,         /* StopBits             */
    0x11,               /* XonChar              */
    0x13,               /* XoffChar             */
    '?',                /* ErrorChar            */
    0x1A,               /* EofChar              */
    0x10                /* EvtChar              */
  };

  memcpy(&g_dcb, &t_g_dcb, sizeof(t_g_dcb));
}


bool SerialPort::Open(std::string nId)
{

  nId = "\\\\.\\COM" + nId;
  std::wstring FilePath(nId.begin(), nId.end());

  g_hCOM = CreateFile(FilePath.c_str(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL);
  if (g_hCOM == INVALID_HANDLE_VALUE)
    return false;

  SetupComm(g_hCOM, RX_SIZE, TX_SIZE);

  if (!SetCommTimeouts(g_hCOM, &g_cto) || !SetCommState(g_hCOM, &g_dcb))
    {
      CloseHandle(g_hCOM);
      return false;
    }

  PurgeComm(g_hCOM, PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_RXABORT);
  EscapeCommFunction(g_hCOM, SETDTR);
  return true;

}

bool SerialPort::Close()
{

    return CloseHandle(g_hCOM);
}

bool SerialPort::Read(char *buffer, unsigned int nBytesToRead, unsigned int *readBytes)
{
  int tmp(0);

  *readBytes = 0;
  while (*readBytes < nBytesToRead)
    {

      if (!ReadFile(g_hCOM, buffer + *readBytes, nBytesToRead - *readBytes, (DWORD*)&tmp, NULL))
        return false;

      *readBytes += tmp;
    }
  return true;
}

bool SerialPort::Write(unsigned char *buffer, unsigned int nBytesToWrite, unsigned int *pBytesWritten)
{
  int tmp(0);

  *pBytesWritten = 0;
  while (*pBytesWritten < nBytesToWrite)
    {

      if (!WriteFile(g_hCOM, buffer + *pBytesWritten, nBytesToWrite - *pBytesWritten, (DWORD*)&tmp, NULL))
        return false;

      *pBytesWritten += tmp;
    }
  return true;
}

一応クラスですね。さて、ws2812bを扱うのにもクラスを使いましょうかね。ここ以降は一応筆者のオリジナルです。

まず、“ws2812b.h”は、

#ifndef __ws2812b
#define __ws2812b

#include <stdint.h>
#include "SerialPort.h"


struct CCRGB {
    union {
        struct {
            union {
                uint8_t r;
                uint8_t red;
            };
            union {
                uint8_t g;
                uint8_t green;
            };
            union {
                uint8_t b;
                uint8_t blue;
            };
        };
        uint8_t raw[3];
    };

    inline CCRGB& setRGB(uint8_t nr, uint8_t ng, uint8_t nb)//__attribute__((always_inline))
    {
              r = nr;
              g = ng;
              b = nb;
              return *this;
     }
};


class ws2812b {
	public:

	ws2812b( int numofleds, CCRGB* leds );
	void showleds();
    void clear();
	private:
        const unsigned char table[4] = { 0xef, 0xcf, 0xee, 0xce };

		int lednum;
        CCRGB* array;
		SerialPort port;
        unsigned bufsize;
		unsigned char* obuf;

};

#endif

“ws2812b.cpp”は、

#include <stdio.h>
#include "SerialPort.h"
#include "ws2812b.h"

ws2812b::ws2812b( int numleds, CCRGB* leds)
{
	//SerialPort wrap;
	//black.setRGB(0, 0, 0) :
	if( !port.Open("200"))
	{
		fprintf(stderr, "can't open port\n");
		return;
	}
	lednum = numleds;
	array = leds;
	bufsize = 8 / 2 * 3 * numleds;
	obuf = (unsigned char*)malloc(bufsize);	// total bytes required
	if( !obuf ){
		fprintf(stderr, "can't alloc output buffer\n");
		return;

	}
}

void ws2812b::showleds()
{
	unsigned int	tr;
	int index = 0;
	for( int i = 0 ; i < lednum ; i++ ){
		obuf[index++] = table[array[i].g >> 6 & 0x03];
		obuf[index++] = table[array[i].g >> 4 & 0x03];
		obuf[index++] = table[array[i].g >> 2 & 0x03];
		obuf[index++] = table[array[i].g >> 0 & 0x03];
		obuf[index++] = table[array[i].r >> 6 & 0x03];
		obuf[index++] = table[array[i].r >> 4 & 0x03];
		obuf[index++] = table[array[i].r >> 2 & 0x03];
		obuf[index++] = table[array[i].r >> 0 & 0x03];
		obuf[index++] = table[array[i].b >> 6 & 0x03];
		obuf[index++] = table[array[i].b >> 4 & 0x03];
		obuf[index++] = table[array[i].b >> 2 & 0x03];
		obuf[index++] = table[array[i].b >> 0 & 0x03];

	}
	port.Write(obuf, bufsize, &tr);
}

void ws2812b::clear()
{

	for (int i = 0; i < lednum; i++)
		array[i].setRGB(0, 0, 0);
	showleds();




}

です。いつもとは逆で、Unit1.cppやらフォームを示す前に、動作している状況を動画で示しましょうか。

“Run”で、

RGBの値やPeriodを変えると、

Unit1.cppは、

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "ws2812b.h"
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

#define NUMLEDS 8

ws2812b* aled;

CCRGB leds[NUMLEDS];
CCRGB tables[NUMLEDS];


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{

	aled = new ws2812b(NUMLEDS, leds);
	aled->clear();

		//leds[(int)(lastl*NUMLEDS/2)].setRGB(3*r,0,0);

    	aled->showleds();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
	for (int i = 1; i < NUMLEDS+1 ; i++) {
		leds[i - 1].setRGB(StrToInt(LabeledEdit1->Text), StrToInt(LabeledEdit2->Text), StrToInt(LabeledEdit3->Text));
		aled->showleds();
		Sleep(StrToInt(LabeledEdit4->Text));
		leds[i-1].setRGB(0,0,0);
	}

	//getchar();
    aled->clear();

}
//---------------------------------------------------------------------------

これだけです。一応所望の動作はしていますね。

プロジェクトの構成は上図のようになっています。少し趣向を変えて、別のプログラムですが、

BinaryStepsをクリックすると、

Unit1.cppは、

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "ws2812b.h"
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

#define NUMLEDS 8

TForm1 *Form1;
CCRGB leds[NUMLEDS];
ws2812b* aled;//= new ws2812b(NUMLEDS, leds);

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{


	aled = new ws2812b(NUMLEDS, leds);
	aled->clear();


}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// B

   //aled->clear();
	for (int i = 1; i < NUMLEDS+1 ; i++) {
		leds[i - 1].setRGB(0, 0, 30);
		aled->showleds();
		Sleep(700);
		leds[i-1].setRGB(0,0,0);
	}

	//getchar();
	aled->clear();

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
//  R
	int k;

	//aled->clear();
	for (k = 0; k < NUMLEDS ; k++) {
		leds[k].setRGB(30, 0, 0);
		aled->showleds();
		Sleep(700);
		leds[k].setRGB(0,0,0);
	}


	aled->clear();

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
//  G

   //aled->clear();
	for (int i = 0; i < NUMLEDS ; i++) {
		leds[i].setRGB(0, 30, 0);
		aled->showleds();
		Sleep(700);
		leds[i].setRGB(0,0,0);
	}

	aled->clear();

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
	//aled->clear();


	for( int i = 0 ; i < NUMLEDS ; i++ ){
		leds[i].setRGB(30,0,0);
		aled->showleds();
		Sleep(700);
		leds[i].setRGB(0,0,0);
		//aled->clear();
	}
	aled->clear();

}
//---------------------------------------------------------------------------
void __fastcall TForm1::BinaryStepsClick(TObject *Sender)
{
	//aled->clear();
	int seed = 1;
	for (int i = 0; i < NUMLEDS ; i++) {
		leds[i].setRGB(seed, seed, seed);
		aled->showleds();
		Sleep(700);
		seed *= 2;
		// 1,2,4,8,16,32,64,128
		//leds[i].setRGB(0,0,0);
	}
    Sleep(500);

	aled->clear();

}
//---------------------------------------------------------------------------

上図の最後がBinaryStepsのコードで、文字通りseed=1から初めて、倍々に明るさを増やしていって表示しているので、左から1,2,4,8,16,32,64,128の明るさのサンプルです。128でも相当な眩しさですよね。255(max)を試す前に、電流負荷を測ってみるのが吉ですが、目を痛めそうなので、止めておきます。基礎編は以上です。次はもう少し実用的(派手ともいふ)なプログラムを紹介しましょう。

コメント