ATmega328PBを使う (4) SPIでマイコン内蔵RGB LEDを点ける


初めに

LEDをPWMで点灯させると明るさは調整できますが色見は調整できません。プラモデルを電飾する場合は、色見の調整を行いたい場合があります。RGBそれぞれのLEDの明るさを調整して色を作ることができます。マイコン内蔵RGB LEDであれば個々のLEDをPWMで明るさ調整する必要がありません。

作例はこちら
https://www.youtube.com/watch?v=kqoh1bq-qb4

マイコン内蔵RGB LED

マイコン内蔵RGB LEDは色々ありますが、ここでは秋月電子で販売しているsk6812-mini,SK6812 SIDEを対象にします。
制御に使う信号はデータのみで、複数個をカスケードに接続できます。

このLEDは、RESET後に最初に受信した24bitのデータでLEDを点灯し、それ以降のデータはDOから出力します。色を変える場合はRESETを送ってからデータを送ることになるため、接続するLEDの数で更新周期に制約が出てきます。

制御フォーマット/タイミング

1bitの通信タイミングは以下の通りです。


周期の最大値が規定されていないことから、H/Lの期間の比率でビットの判定をしているのではなく’H'の期間で判定していると思われます。
立上りから0.4us以内であれば'0'、1.0usであれば'1'となります。

データはMSBからGRBの順に送ります。

制御方法

ソフト制御で1us以下のタイミングを作るには処理を占有させる必要があります。また送信中は割り込み等で中断させることもできません。それだと使いづらいのでSPIを使って実装します。
1bitのデータ送信の期間を1byteに割り当て、Hのパルスを作成します。
1bitの長さはMin1.2usなのでこの値を基にSPIのクロックを決めます。
1.2us/8=0.15us 
クロックにすると6.67MHzとなりますが、今回使うATmega328PB_Xplained_Miniのクロックは16MHzなのでこのクロックは作れません。近い値で16MHzを2分周した8MHzとします。
T0Hの長さは2bit幅とし0.25us、T1Hの長さは5bit幅とし0.625usとします。

SPIが転送していない期間の出力は’L'なので、外部でHC04等のインバータで反転させます。

SPI

コンポーネントの設定


クロックは16MHzを2分周とし、Masterモードとします。
ポートが4本割り当てられますが、使用するのはMOSIだけです。

処理

10ms毎に転送を行います。
手順
1 RGBデータから転送データを作成
2 Resetを送信
3 LED数分データ転送

RGBデータから転送データを作成

#define RGB_LED_TRAN_NUM    (24)            /* RG  LED 1個当たりのデータ転送数 */
#define RGB_LED_L_DATA      (0b00111111)
#define RGB_LED_H_DATA      (0b00000111)
#define RGB_LED_RESET_LENGTH    (10)        /* Resetデータ長 */
#define RGB_CONV_COUNT      (8)

typedef enum {
    td_State_Reset = 0,
    td_State_Transfer,
    td_State_Idle,
} td_State;


static uint8_t grb[RGB_LED_MAX_NUM][RGB_COLOR_NUM];             /* RGB data */
static uint8_t sdLedData[RGB_LED_MAX_NUM * RGB_LED_TRAN_NUM];   /* SPI送信データ */
static td_State rgbLed_State;                                   /* 制御状態 */

static uint16_t rstTranCount;                   /* reset送信データ数 */
static uint8_t rgbLedTranCount;                 /* RGB LEDデータ数 */
static uint8_t rgbLedNumOfTran;                 /* RGB LED転送数 */
static uint8_t rgbLedLenNum;                    /* RGB LED数 */

static const uint8_t rgbLedResetData = 0xFF;    /* Reset Data */

/* RGB LED転送開始 */
/* 転送データ作成 */
/* 送信開始 */
void rgbLed_start(void)
{
    /* RGB LED転送データ作成 */
    rgbLed_makeSendData();
    /* SPIデータ送信開始 */
    rgbLed_StartTran(); 

}

/* RGBデータから送信データを作成 */
static void rgbLed_makeSendData(void)
{
    uint8_t bitMask;
    uint8_t rgbData;
    uint8_t sendData;
    uint8_t sdPt;

    sdPt = 0;
    for (uint8_t ledNo = 0; ledNo < rgbLedLenNum; ledNo++) {                    /* LED数繰り返し */
        for (uint8_t rgbNo = 0; rgbNo < RGB_COLOR_NUM; rgbNo++) {               /* RGB分繰り返し */
            bitMask = 0b10000000;
            rgbData = grb[ledNo][rgbNo];
            for (uint8_t cnvCount = 0; cnvCount < RGB_CONV_COUNT; cnvCount++) { /* 8bit繰り返し */
                sendData = 0x00;
                if ((rgbData & bitMask) != 0x00) {
                    sendData = RGB_LED_H_DATA;
                } else {
                    sendData = RGB_LED_L_DATA;
                }
                sdLedData[sdPt] = sendData;
                bitMask = bitMask >> 1;
                sdPt++;
            }
        }
    }
}

関数 rgbLed_startは、転送データ作成を行った後、SPI転送を開始します。
rgbLed_makeSendDataでは、色データのgrb[][]の内容を1bit毎チェックし、1byteの転送データに変換します。それを転送用バッファのsdLedData[]に格納します。

送信処理

/* RGB LED送信開始処理 */
static void rgbLed_StartTran(void)
{
    rgbLed_State = td_State_Reset;
    rstTranCount = 0;
    SPI_MS_Transmit(rgbLedResetData);   /* transmit Reset Data */
}

/* SPI割り込み */
void rgbLed_spi_intr(void)
{
    switch (rgbLed_State)
    {
        case td_State_Reset:        /* Reset出力中 */
            if (rstTranCount > RGB_LED_RESET_LENGTH) {
                /* データ送信開始処理 */
                rgbLed_StartDataTran();
            } else {
                /* Reset送信処理 */
                rgbLed_ResetTran();
            }
            break;
        case td_State_Transfer:     /* データ送信処理 */
            rgbLed_DataTran();
            if (rgbLedTranCount >= rgbLedNumOfTran) {
                /* 送信終了 */
                rgbLedTranCount = 0;
                rgbLed_State = td_State_Idle;
            }
            break;
        default:
            /* 送信完了処理 */
            rgbLed_State = td_State_Idle;
            break;
    }   
}

/* Reset送信処理 */
static void rgbLed_ResetTran(void)
{
    SPI_MS_Transmit(rgbLedResetData);   /* transmit Reset Data */
    rstTranCount++;
}

/* データ送信開始処理 */
static void rgbLed_StartDataTran(void)
{
    rgbLed_State = td_State_Transfer;
    rgbLedTranCount = 0;
    rgbLed_DataTran();
}

/* データ送信処理 */
static void rgbLed_DataTran(void)
{
    SPI_MS_Transmit(sdLedData[rgbLedTranCount]);
    rgbLedTranCount++;
}

関数rgbLed_StartTranにより転送を開始します。
1byte転送終了後に割り込みが発生します。
割り込み処理ではReset送信中であればResetデータを送信し、データ送信中であればデータ送信を行います。送信データ数により終了判断を行います。

おわり