前回はRaspberry Pi Pico を用いてバーサライタを製作しました。
Raspberry Pi Pico でバーサライタ製作 (C/C++) ーRaspberry Pi Picoへの道6ー
続きを見る
ここでは、表示映像の解像度向上のためにSPI複数出力とデュアルコア動作を検証しましたので報告します。
SPI複数出力
Raspberry Pi Pico にはハードウェアSPI端子が2個あります (SPI0, SPI1)。
この2つのSPI端子でそれぞれLEDテープを制御してみました。
[amazonjs asin=”B07VGH4XSQ” locale=”JP” title=”APA102 5050 SMD高輝度チップLEDピクセルフレキシブルストリップライトDC 5V (白 PCB, 1M 144leds IP20)”]
Cコード
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "apa102.pio.h"
#define PIN_CLK0 2
#define PIN_DIN0 3
#define PIN_CLK1 14
#define PIN_DIN1 15
#define N_LEDS 11
#define SERIAL_FREQ (30 * 1000 * 1000)
// Global brightness value 0->31
#define BRIGHTNESS 3
uint32_t color[] ={
0xFF0000,
0x00FF00,
0x0000FF,
0x00FFFF
};
void put_start_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, 0u);
}
void put_end_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, ~0u);
}
void put_bgr888(PIO pio, uint sm, uint32_t c) {
pio_sm_put_blocking(pio, sm,
0x7 << 29 | // magic
(BRIGHTNESS & 0x1f) << 24 | // global brightness parameter
c
);
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm0 = 0;
uint sm1 = 1;
uint offset = pio_add_program(pio, &apa102_mini_program);
apa102_mini_program_init(pio, sm0, offset, SERIAL_FREQ, PIN_CLK0, PIN_DIN0);
apa102_mini_program_init(pio, sm1, offset, SERIAL_FREQ, PIN_CLK1, PIN_DIN1);
while (true) {
for (int j = 0; j < 4; ++j) {
put_start_frame(pio, sm0);
put_start_frame(pio, sm1);
for (int i = 0; i < N_LEDS; ++i) {
put_bgr888(pio, sm0, color[j]);
put_bgr888(pio, sm1, color[3-j]);
}
put_end_frame(pio, sm0);
put_end_frame(pio, sm1);
sleep_ms(500);
}
}
}
PIO
SPI入力LEDをPIOなる機能で点灯制御しているのですが、2本制御の際はステートマシンなるモノを分けて使用するとよいようです。
PIOブロックは2個内蔵 (pio0, pio1)され、それぞれにステートマシン4個搭載されています (sm0~3)。
ここではpio0のsm0でSPI0を、sm1でSPI1を制御して2本のLEDを点灯しました。
狭ピッチバーサライタ
以前製作した狭ピッチのLEDバーサライタをRaspberry Pi Picoで制御してみます。
APA102-2020の再考 - バーサライタへの応用 –
続きを見る
このLEDはリフレッシュレートが低いためRGBそれぞれをフル輝度で使用しなくてはならず8色しか出力できませんが
2mm角のLEDが狭ピッチで配置されていますので解像度向上実験にはもってこいです。
構成
2つのSPI出力でLEDを64セル, 63セルずつ制御します。
[bc url=”https://www.adafruit.com/product/3776″]
動作
先ほどと同様に2個のSPI出力で制御し、回転速度470rpmで回してみたところ、計127個のLEDを1周 240分割で表示できました。
ESP32のデュアルコア制御でも200分割が限界でしたので、やはりRaspberry Pi Pico の高速IOは素晴らしいです。
デュアルコア制御
Raspberry Pi PicoはCortex-M0+を2個搭載しています。
更なる高解像表示を目指してデュアルコア制御を試します。
Cコード
core0で回転速度計測とSPI0のLED制御し、
core1でSPI1のLED制御しています。
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "apa102.pio.h"
#include "pico/multicore.h"
#define PIN_CLK0 2
#define PIN_DIN0 3
#define PIN_CLK1 14
#define PIN_DIN1 15
#define N_LEDS 64
#define Div 240
#define SERIAL_FREQ (30 * 1000 * 1000)
// Global brightness value 0->31
#define BRIGHTNESS 1
uint32_t color[] ={
0xFF0000,
0x00FF00,
0x0000FF,
0x00FFFF
};
unsigned long rotTime, timeOld, timeNow;
int numRot = 0;
int stateDiv = 0;
int stateDiv2 = 0;
int numDiv = 0;
int num = 0;
void gpio_callback() {
timeNow = time_us_64();
rotTime = timeNow - timeOld;
timeOld = timeNow;
numRot++;
if(numRot >= Div ) numRot = 0;
}
void put_start_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, 0u);
}
void put_end_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, ~0u);
}
void put_bgr888(PIO pio, uint sm, uint32_t c) {
pio_sm_put_blocking(pio, sm,
0x7 << 29 | // magic
(BRIGHTNESS & 0x1f) << 24 | // global brightness parameter
c
);
}
void core1_entry() {
PIO pio = pio0;
uint sm = 1;
uint offset = pio_add_program(pio, &apa102_mini_program);
apa102_mini_program_init(pio, sm, offset, SERIAL_FREQ, PIN_CLK1, PIN_DIN1);
while (true) {
if(stateDiv2 == 1){
put_start_frame(pio, sm);
for (int i = 0; i < N_LEDS; ++i) {
put_bgr888(pio, sm, color[num]);
}
put_end_frame(pio, sm);
stateDiv2 = 0;
}
}
}
int main() {
stdio_init_all();
gpio_set_irq_enabled_with_callback(22, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
multicore_launch_core1(core1_entry);
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &apa102_mini_program);
apa102_mini_program_init(pio, sm, offset, SERIAL_FREQ, PIN_CLK0, PIN_DIN0);
while (true) {
if(stateDiv == 1 && time_us_64() - timeOld > rotTime / Div * (numDiv)){
stateDiv = 0;
}
if(stateDiv == 0 && time_us_64() - timeOld < rotTime / Div * (numDiv + 1)){
stateDiv = 1;
stateDiv2 = 1;
put_start_frame(pio, sm);
for (int i = 0; i < N_LEDS; ++i) {
put_bgr888(pio, sm, color[num]);
}
put_end_frame(pio, sm);
numDiv++;
if(numDiv >= Div ) numDiv = 0;
num++;
if(num > 1 ) num = 0;
}
}
}
しかし、
デュアルコアでの1周の分割数の向上は確認できませんでした。。。
core1でのLED点灯制御は確実にできていると思うのですが。
core1の動かし方に何か問題があるのでしょうか?
参考
画像表示
浮世絵を表示してみました。
シングルでもデュアルでも1周240分割以上にすると表示がおかしくなります。
シングルコア
続きを見る
デュアルコア
続きを見る
おわりに
ここではRaspberry Pi Picoを用いて狭ピッチLEDバーサライタを製作しました。
高解像表示を目指しましたがデュアルコアによる向上は達成されませんでした。
しかしLED 127個で1周 240分割は過去最大の分解能ですので、ひとまず嬉しいです。
デュアルコアはについては引き続き勉強します。なにか間違ってるかもしれません。
それでは次の道でお会いしましょう!
追記
回転検出部を別コア化(2021/2/17)
以下のブログでPIOで別ステートマシンでのLED制御がすでに並列処理されているために、
デュアルコアによる表示解像度の向上がなかったことがわかりました。
Raspberry Pi Pico バーサライタで PIO を考えるーRaspberry Pi Picoへの道8ー
続きを見る
そこでステートマシンでの並列処理については理解できたので、LED制御と回転計測をデュアルコアで試してみました。
以下のようにcore0で回転計測、core1でPIOのステートマシン2個でLED(64セル、63セル)を制御してみます。
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "apa102.pio.h"
#include "pico/multicore.h"
#define PIN_CLK0 2
#define PIN_DIN0 3
#define PIN_CLK1 14
#define PIN_DIN1 15
#define N_LEDS 64
#define Div 240
#define SERIAL_FREQ (30 * 1000 * 1000 )
// Global brightness value 0->31
#define BRIGHTNESS 1
uint32_t color[] ={
0xFF0000,
0x00FF00,
0x0000FF,
0x00FFFF
};
unsigned long rotTime, timeOld, timeNow;
int numRot = 0;
int stateDiv = 0;
int numDiv = 0;
int num = 0;
void gpio_callback() {
timeNow = time_us_64();
rotTime = timeNow - timeOld;
timeOld = timeNow;
numRot++;
if(numRot >= Div ) numRot = 0;
}
void put_start_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, 0u);
}
void put_end_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, ~0u);
}
void put_bgr888(PIO pio, uint sm, uint32_t c) {
pio_sm_put_blocking(pio, sm,
0x7 << 29 | // magic
(BRIGHTNESS & 0x1f) << 24 | // global brightness parameter
c
);
}
void core1_entry() {
PIO pio = pio0;
uint sm0 = 0;
uint sm1 = 1;
uint offset = pio_add_program(pio, &apa102_mini_program);
apa102_mini_program_init(pio, sm0, offset, SERIAL_FREQ, PIN_CLK0, PIN_DIN0);
apa102_mini_program_init(pio, sm1, offset, SERIAL_FREQ, PIN_CLK1, PIN_DIN1);
while (true) {
if(multicore_fifo_pop_blocking() == 1){
put_start_frame(pio, sm0);
put_start_frame(pio, sm1);
for (int i = 0; i < N_LEDS; ++i) {
put_bgr888(pio, sm0, color[num]);
put_bgr888(pio, sm1, color[num]);
}
put_end_frame(pio, sm0);
put_end_frame(pio, sm1);
}
}
}
int main() {
stdio_init_all();
gpio_set_irq_enabled_with_callback(22, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
multicore_launch_core1(core1_entry);
while (true) {
if(stateDiv == 1 && time_us_64() - timeOld > rotTime / Div * (numDiv)){
stateDiv = 0;
}
if(stateDiv == 0 && time_us_64() - timeOld < rotTime / Div * (numDiv + 1)){
stateDiv = 1;
multicore_fifo_push_blocking(stateDiv);
numDiv++;
if(numDiv >= Div ) numDiv = 0;
num++;
if(num > 3 ) num = 0;
}
}
}
結果としましては大きな分解能向上はありませんでした。
1周 240分解能がギリギリ260分割できそうになるくらいの変化でした。
ステートマシンが完全に並列処理できていて、回転が470rpm、SPI信号が30MHzの場合、理論分解能値はだいたい
30MHz ÷ (32bit × 64セル + 64bit) × 60sec ÷ 470rpm = 1813分割
ですので、SPIクロックが遅すぎることはないようです。
まだまだデータ転送方法など工夫次第で高解像度化できそうですが、とにかくPIOは素晴らしいということがわかりました。