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を対象にします。
制御に使う信号はデータのみで、複数個をカスケードに接続できます。
![v01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/e692dfc2-c652-2a56-515b-7aefc4f0fa78.png)

このLEDは、RESET後に最初に受信した24bitのデータでLEDを点灯し、それ以降のデータはDOから出力します。色を変える場合はRESETを送ってからデータを送ることになるため、接続するLEDの数で更新周期に制約が出てきます。
![v06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/a514ebc6-0b41-345c-d092-4b2a0631c34c.png)

##制御フォーマット/タイミング
1bitの通信タイミングは以下の通りです。
![v02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/3e85e258-ea4f-272c-06fc-c0cd2fa52b14.png)
![v04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/69b87c42-de08-25ff-cd77-99ea7c2440c1.png)
周期の最大値が規定されていないことから、H/Lの期間の比率でビットの判定をしているのではなく’H'の期間で判定していると思われます。
立上りから0.4us以内であれば'0'、1.0usであれば'1'となります。

データはMSBからGRBの順に送ります。
![v03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/941b2555-86b5-37d6-0729-899ea4da39d2.png)

#制御方法
ソフト制御で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

#コンポーネントの設定
![v05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/c42a86f6-e6e6-9ace-dd73-e227703b4255.png)
クロックは16MHzを2分周とし、Masterモードとします。
ポートが4本割り当てられますが、使用するのはMOSIだけです。

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



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

```C++
#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[]に格納します。

##送信処理

```C++
/* 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データを送信し、データ送信中であればデータ送信を行います。送信データ数により終了判断を行います。

おわり