というわけで、無事にLチカが終わって何をしようかというところで、
WS2812B(NeoPixel)を光らせるプログラムを作ってみましたと。
Arduinoならライブラリもあって簡単そうなんだけど、
この先やりたいことを考えるとMounRiver Studioでやっとくのが良さそうだったので
とりあえずやってみました。
色々試行錯誤した痕跡を残したコードがこちら。
void Ws2812bShow(uint8_t* pixels, uint32_t numBytes)
{
uint8_t* ptr = pixels;
uint8_t* end = ptr + numBytes;
uint8_t p = *ptr++;
uint8_t bitMask = 0x80;
while (1) {
if (p & bitMask) { // ONE
// High 580-1000nsec
// GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
// GPIO_Write(GPIOD, 0x01);
GPIOD->BSHR = 1;
__asm volatile (
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop;"
);
// Low 580-1000nsec
// GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_RESET);
// GPIO_Write(GPIOD, 0x00);
GPIOD->BCR = 1;
__asm volatile (
"nop; nop; nop; nop;"
);
} else { // ZERO
// High 220-380nsec
// GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
// GPIO_Write(GPIOD, 0x01);
GPIOD->BSHR = 1; //220nsec
// __asm volatile (
// "nop;"
// );
// Low 580-1000nsec
// GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_RESET);
// GPIO_Write(GPIOD, 0x00);
GPIOD->BCR = 1;
__asm volatile (
"nop; nop; nop; nop; nop;"
);
}
if (bitMask >>= 1) {
// Move on to the next pixel
asm("nop;");
}
else {
if (ptr >= end) {
break;
}
p = *ptr++;
bitMask = 0x80;
}
}
Delay_Us(250);
}
基本的な作りはAdafruitのライブラリのソースから。(256行目~)
https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp
// GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
// GPIO_Write(GPIOD, 0x01);
GPIOD->BSHR = 1;
ハマったポイントとしてはここ。
GPIOのサンプルソースでは、ピンの出力をON/OFFするのに
GPIO_WriteBit関数を使っているのですが、この関数を使うと1->0に500マイクロ秒ほどかかってしまい、
T0H=220~380ナノ秒を満たせません。
GPIO_Writeを使っても同じ。
おそらく関数呼び出しのスループットで処理時間がかかってしまっているのが原因。
ということで、ライブラリの方のやり方を参考にして
GPIOD->BSHR = 1; //GPIO ポートDの1bit目をセット
GPIOD->BCR = 1; //GPIO ポートDの1bit目をクリア
とすると呼び出しのスループット無しでビット操作ができました。
ただ、これでも1->0は220ナノ秒ほどかかるので、
WS2812Bの制御に使うにはギリギリのタイミングでした。
あとは、単純な条件分岐だけでもちょっと変更すると命令数が増減して
160ナノ秒単位でズレていくので、処理を全部決め打ちしてからの
nop;数での微調整が必要でした。
オシロスコープとにらめっこするのはお手軽な「電子工作」からかけ離れるので
あまり好きじゃないのですが。
まあ、一度作ってしまえば同じ処理で使い回せるので最初に使い勝手の良い関数を作ってしまえば
こっちのものという感じです。
今日はここまでにしてまた少しずつ進めます。