2018年08月07日

LEDパネルコントローラ完成

前回の更新からフルカラードットマトリクスLEDパネルコントローラは大幅に進化を遂げました。
まずはこれを見てください。画質を上げて60FPSでの視聴をおすすめします。ヌルヌル感が違います。

動画を再生している下のパネルは、64*32ピクセルのLEDパネルを4枚、LEDパネルコントローラを2個、動画再生用にSTM32F401REを搭載しているNucleoボードを使用し、MicroSDカードの中に保存されている動画ファイルと音声ファイルを60FPSで同時に再生しています。普通に動画プレイヤーですね。
上にくっついているのは、64*32ピクセルのLEDパネルを1枚、LEDパネルコントローラを1個使用し、Arduino nanoで簡易的なオーディオレベルメーターを作ったものです。即席で作ったので、入力回路がモノラルでしか取り込んでなかったりなどちょっと雑&適当です。

で、どういう風に進化したのかといいますと、
リフレッシュレートが大幅に向上しました。
64*64ピクセルのLEDパネルでも最低で62FPS、最高で125FPS出るようになりました。
128*32ピクセルのLEDパネルでは、最低で97FPS、最高で129FPS出ます。
64*32ピクセルでは最低で122FPS、最高で249FPS出ます。
描画可能な色数が4096色から6229504色になりました。グラデーションも非常に美しく描画できます。
LEDパネルコントローラ内部でガンマ補正を行い、液晶モニタと比較しても遜色ない発色が可能になりました。
LEDパネルの輝度がかなり明るくなりました。視認性がかなり向上しました。
マイコンの出力にレベルシフタを接続することによってノイズのない安定した表示が可能になりました。
SPI通信でのコマンド制御に対応し、LEDパネル全体の明るさ、描画開始ピクセルと描画終了ピクセルを変更できるようになりました。
ジャンパ設定によって使用するLEDパネルの解像度が変更できるようになりました。
と、以上のような感じです。

上の動画でまず注目していただきたいのが、画面のフリッカ(LEDパネルのリフレッシュレートのせいで撮影した動画がちらついて見える現象)が全く見えないという点です。これは動画撮影するときのカメラの設定でも左右されるのですが、FPSが大幅に向上したことにより以前のLEDパネルコントローラではフリッカなしではどうあがいても撮影できなかったものが、完全にフリッカ無しで撮影できるようになっています。
ちなみにたまに動きが大きいと横筋が入ることがありますが、これは完全にカメラのせいです。肉眼では全く見えません。
もう一点注目していただきたいのが、背景の空の色です。以前のLEDパネルコントローラでは発色数が4096色であったために空の色などの微妙なグラデーションは色が分離して見えてしまっていましたが、新しいLEDパネルコントローラでは6229504色と表現できる色数が圧倒的に増えたため、微妙なグラデーションも非常に美しく描画できています。

個人的には、自分の技術力とSTM32F103C8T6でできる限界まで来たかなと思っています。

基板も作成し、こんな感じになりました。
DSC_2833
裏面にレベルシフタ用の74HCT245が2つ引っ付いています。
ハードウェアはシンプルです。一応外部にI2CとかUARTとかCAN/USBとか出てきているのですが、おそらく使用することはないでしょう・・・
というのも、LEDパネルが128*64ピクセルで1ピクセル当たり24bitなので60FPSで動画再生するには約12Mbpsもの通信速度が必要になります。I2CやCANやUARTでは通信速度が圧倒的に足りないです。
ちなみに動画のLEDディスプレイでは21Mbpsで通信しています。通信速度が高速なだけあって、配線が結構シビアでした。

今回、このLEDパネルコントローラの取説みたいなのを作ってみました。興味がありましたら是非ご覧ください。
必要なことはすべて書いてあると思います。
こちらからダウンロードできます。
一応ですが、間違いなどあるかもしれませんのでご了承ください。もし間違いなどを見つけた場合はこのブログ記事のコメント欄にて報告お願いいたします。
LEDパネルコントローラ取扱説明書v0.81
下にプレビューも置いときます。

LEDパネルコントローラ取扱説明書v0.81 by wata_net on Scribd



LEDパネルコントローラを使えばArduinoで簡単にLEDパネルを表示できるんだぞっていうのをアッピルするためにArduinoで基本図形を描画するサンプルプログラムを作成しました。
すいません、たぶんこれもウンコードだと思います・・・
プログラミングできる人は自分で書いたほうが良いかもしれませんね!
一応ここからダウンロードできます。LEDパネル解像度64*32ピクセル用です。#defineのところの大きさ変えると他の解像度でも対応できるかもしれません。
これも間違いなどあるかもしれませんのでご了承ください。もし間違いなどを見つけた場合はこのブログ記事のコメント欄にて報告お願いいたします。
LEDパネルコントローラサンプルプログラムforArduinoV0.8
下にプレビューも置いておきます。(シンタックスハイライトに縦スクロールバーつけるやつを学んだ)
どうやらprism.jsとかいうシンタックスハイライター、行数が長くなりすぎるとブログ記事ごとお亡くなりになされてしまうようです?最初理由が分からなくてブチギレそうになりましたが、うまく読み込まないブログ記事編集ページをダウンロードし、これまで書き込んだ内容を復活させるとともにシンタックスハイライターの中身を一部省略することで事なきを得ました。
また一から書き直さないといけなくなるかと思った。
#include <SPI.h>

#define SS 10 //LEDパネルスレーブセレクトピン
#define RST 9 //LEDパネルリセットピン

#define MATRIXLED_Y_COUNT 32 //LEDパネルの縦解像度
#define MATRIXLED_X_COUNT 64 //LEDパネルの横解像度
#define MATRIXLED_COLOR_COUNT 3 //LEDパネルの色数(RGBなので3)

#define FONT_DATA_PIXEL_SIZE_X 5 //フォントデータの横解像度
#define FONT_DATA_PIXEL_SIZE_Y 8 //フォントデータの縦解像度

//フォントデータ
const PROGMEM unsigned char Font_Data[192][5] =
{
//省略
};

//サンプル画像データ(64*32)
const PROGMEM unsigned char image[64*32*3] = {
//省略
};

SPISettings mySPISettings = SPISettings(8000000, MSBFIRST, SPI_MODE0);

void setup() {
pinMode(SS, OUTPUT);
digitalWrite(SS, HIGH);
pinMode(RST, OUTPUT);
digitalWrite(RST, LOW);

SPI.begin();
SPI.beginTransaction(mySPISettings);

draw_image_for_24bitBMP(0, 0, MATRIXLED_X_COUNT, MATRIXLED_Y_COUNT, image, 0xff);
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, 0xff);
}

void loop() {
unsigned char char_buff[12];
unsigned char char_buff1[12];
static unsigned char loop_count;

unsigned char x_start, y_start, x_end, y_end, radius;
unsigned char rand_0, rand_1, rand_2, rand_3;

unsigned char display_brightness = 0;

/*
* 点描画サンプル
*/
for(loop_count = 0;loop_count < 200;loop_count++){
sprintf(char_buff, "Dot %3u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

x_start = (rand() % MATRIXLED_X_COUNT);
y_start = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;

draw_pixel(x_start, y_start, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(5);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 直線描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "Line %3u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

x_start = (rand() % MATRIXLED_X_COUNT);
y_start = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;
x_end = (rand() % MATRIXLED_X_COUNT);
y_end = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;

draw_line(x_start, y_start, x_end, y_end, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 四角形描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "Rect %3u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

rand_0 = (rand() % MATRIXLED_X_COUNT);
rand_1 = (rand() % MATRIXLED_X_COUNT);
rand_2 = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;
rand_3 = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;

if(rand_0 <= rand_1){
x_start = rand_0;
x_end = rand_1;
}else{
x_start = rand_1;
x_end = rand_0;
}
if(rand_2 <= rand_3){
y_start = rand_2;
y_end = rand_3;
}else{
y_start = rand_3;
y_end = rand_2;
}

draw_rectangle(x_start, y_start, x_end, y_end, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 四角形(中埋め)描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "RectFill%2u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

rand_0 = (rand() % MATRIXLED_X_COUNT);
rand_1 = (rand() % MATRIXLED_X_COUNT);
rand_2 = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;
rand_3 = (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 1)) + FONT_DATA_PIXEL_SIZE_Y + 1;

if(rand_0 <= rand_1){
x_start = rand_0;
x_end = rand_1;
}else{
x_start = rand_1;
x_end = rand_0;
}
if(rand_2 <= rand_3){
y_start = rand_2;
y_end = rand_3;
}else{
y_start = rand_3;
y_end = rand_2;
}

draw_rectangle_fill(x_start, y_start, x_end, y_end, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 円描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "Circle %2u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

if((MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y) < MATRIXLED_X_COUNT){
radius = (rand() % ((MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 2) >> 1)) + 1;
}else{
radius = (rand() % ((MATRIXLED_X_COUNT - 2) >> 1)) + 1;
}

x_start = radius + (rand() % (MATRIXLED_X_COUNT - radius * 2));
y_start = radius + FONT_DATA_PIXEL_SIZE_Y + (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - radius * 2 - 1)) + 1;

draw_circle(x_start, y_start, radius, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 円(中埋め)描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "CircFill%2u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

if((MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y) < MATRIXLED_X_COUNT){
radius = (rand() % ((MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - 2) >> 1)) + 1;
}else{
radius = (rand() % ((MATRIXLED_X_COUNT - 2) >> 1)) + 1;
}

x_start = radius + (rand() % (MATRIXLED_X_COUNT - radius * 2));
y_start = radius + FONT_DATA_PIXEL_SIZE_Y + (rand() % (MATRIXLED_Y_COUNT - FONT_DATA_PIXEL_SIZE_Y - radius * 2 - 1)) + 1;

draw_circle_fill(x_start, y_start, radius, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* テキスト描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "Text %2u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

x_start = (rand() % (MATRIXLED_X_COUNT - (FONT_DATA_PIXEL_SIZE_X + 1) * 2));
y_start = FONT_DATA_PIXEL_SIZE_Y + 1 + (rand() % (MATRIXLED_Y_COUNT + 1 - 2 * (FONT_DATA_PIXEL_SIZE_Y + 1)));

sprintf(char_buff1, "%2u", loop_count);
draw_text(x_start, y_start, char_buff1, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* テキスト(背景単色埋め)描画サンプル
*/
for(loop_count = 0;loop_count < 50;loop_count++){
sprintf(char_buff, "TextFill%2u", loop_count + 1);
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);

x_start = (rand() % (MATRIXLED_X_COUNT - (FONT_DATA_PIXEL_SIZE_X + 1) * 2));
y_start = FONT_DATA_PIXEL_SIZE_Y + 1 + (rand() % (MATRIXLED_Y_COUNT + 1 - 2 * (FONT_DATA_PIXEL_SIZE_Y + 1)));

sprintf(char_buff1, "%2u", loop_count);
draw_text_fill_back(x_start, y_start, char_buff1, 0xff, 0xff, 0xff, (rand() % (0xff + 1)), (rand() % (0xff + 1)), (rand() % (0xff + 1)), display_brightness);
delay(50);
}
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);

/*
* 画像サンプル1
*/
draw_image_for_24bitBMP(0, 0, MATRIXLED_X_COUNT, MATRIXLED_Y_COUNT, image, display_brightness);
sprintf(char_buff, "image64*32");
draw_text_fill_back(0, 0, char_buff, 0xff, 0xff, 0xff, 0x00, 0x40, 0x40, display_brightness);
delay(1000);
draw_rectangle_fill(0, 0, MATRIXLED_X_COUNT - 1, MATRIXLED_Y_COUNT - 1, 0x00, 0x00, 0x00, display_brightness);
}


/*
* コマンド送信関数
* 描画開始ピクセル座標、描画終了ピクセル座標、LEDパネルの輝度を変更する
* 通常はユーザープログラムの中からはこの関数を呼び出す必要はなく、後述の描画関数の中でこの関数を呼び出している
* 引数1:描画開始ピクセルY座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数2:描画終了ピクセルY座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:描画開始ピクセルX座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数4:描画終了ピクセルX座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数5:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void send_command(unsigned char draw_start_pixel_y, unsigned char draw_end_pixel_y, unsigned char draw_start_pixel_x, unsigned char draw_end_pixel_x, unsigned char panel_brightness){
if((draw_start_pixel_y < MATRIXLED_Y_COUNT) && (draw_end_pixel_y < MATRIXLED_Y_COUNT) && (draw_start_pixel_y <= draw_end_pixel_y)){
if((draw_start_pixel_x < MATRIXLED_X_COUNT) && (draw_end_pixel_x < MATRIXLED_X_COUNT) && (((draw_start_pixel_x <= draw_end_pixel_x) && (draw_start_pixel_y == draw_end_pixel_y)) || (draw_start_pixel_y < draw_end_pixel_y))){
digitalWrite(RST, HIGH);
delayMicroseconds(5);
digitalWrite(SS, LOW);
SPI.transfer(draw_start_pixel_y);
SPI.transfer(draw_end_pixel_y);
SPI.transfer(draw_start_pixel_x);
SPI.transfer(draw_end_pixel_x);
SPI.transfer(panel_brightness);
digitalWrite(SS, HIGH);
}
}
}

/*
* 点を描画する関数
* 指定したピクセル座標に任意の色の点を描画する
* 引数1:点を描画するX座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:点を描画するY座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数4:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数5:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_pixel(unsigned char pixel_x, unsigned char pixel_y, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
if((pixel_x < MATRIXLED_X_COUNT) && (pixel_y < MATRIXLED_Y_COUNT)){
send_command(pixel_y, pixel_y, pixel_x, pixel_x, panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
digitalWrite(SS, HIGH);
}
}

/*
* 直線を描画する関数
* 指定した始点ピクセル座標から終点ピクセル座標まで任意の色で直線を描画する
* 引数1:直線の始点X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:直線の始点Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:直線の終点X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数4:直線の終点Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数5:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数8:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_line(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char end_pixel_x, unsigned char end_pixel_y, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
unsigned char dx, dy;
signed char sx, sy;
signed char diff = 0;
unsigned char x_loop = 0, y_loop = 0;

unsigned char draw_x_loop;

if((start_pixel_x < MATRIXLED_X_COUNT) && (end_pixel_x < MATRIXLED_X_COUNT) && (start_pixel_y < MATRIXLED_Y_COUNT) && (end_pixel_y < MATRIXLED_Y_COUNT)){
if(start_pixel_y == end_pixel_y){
if(start_pixel_x <= end_pixel_x){
send_command(start_pixel_y, end_pixel_y, start_pixel_x, end_pixel_x, panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = start_pixel_x;draw_x_loop <= end_pixel_x;draw_x_loop++){
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
}
digitalWrite(SS, HIGH);
}else{
send_command(start_pixel_y, end_pixel_y, end_pixel_x, start_pixel_x, panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = end_pixel_x;draw_x_loop <= start_pixel_x;draw_x_loop++){
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
}
digitalWrite(SS, HIGH);
}
}else{
dx = (end_pixel_x > start_pixel_x) ? (end_pixel_x - start_pixel_x) : (start_pixel_x - end_pixel_x);
dy = (end_pixel_y > start_pixel_y) ? (end_pixel_y - start_pixel_y) : (start_pixel_y - end_pixel_y);

sx = (end_pixel_x > start_pixel_x) ? 1 : -1;
sy = (end_pixel_y > start_pixel_y) ? 1 : -1;

if(dx > dy){
diff = -dx;
for(x_loop = 0;x_loop <= dx;x_loop++){
draw_pixel(start_pixel_x, start_pixel_y, color_r, color_g, color_b, panel_brightness);
start_pixel_x += sx;
diff += 2 * dy;
if(diff >= 0){
start_pixel_y += sy;
diff -= 2 * dx;
}
}
}else{
diff = -dy;
for(y_loop = 0;y_loop <= dy;y_loop++){
draw_pixel(start_pixel_x, start_pixel_y, color_r, color_g, color_b, panel_brightness);
start_pixel_y += sy;
diff += 2 * dx;
if(diff >= 0){
start_pixel_x += sx;
diff -= 2 * dy;
}
}
}
}
}
}

/*
* 四角形を描画する関数
* 指定した左上ピクセル座標から右下ピクセル座標まで任意の色で四角形を描画する
* 引数1:四角形の左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:四角形の左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:四角形の右下X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数4:四角形の右下Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数5:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数8:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
* 引数1〜引数4については以下の関係が成り立っている必要がある
* 四角形の左上X座標 <= 四角形の右下X座標
* 四角形の左上Y座標 <= 四角形の右下Y座標
*/
void draw_rectangle(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char end_pixel_x, unsigned char end_pixel_y, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
unsigned char draw_y_loop, draw_x_loop;

if((end_pixel_x < MATRIXLED_X_COUNT) && (end_pixel_y < MATRIXLED_Y_COUNT) && (start_pixel_x <= end_pixel_x) && (start_pixel_y <= end_pixel_y)){
for(draw_y_loop = start_pixel_y;draw_y_loop <= end_pixel_y;draw_y_loop++){
if((draw_y_loop == start_pixel_y) || (draw_y_loop == end_pixel_y)){
send_command(draw_y_loop, draw_y_loop, start_pixel_x, end_pixel_x, panel_brightness);
digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = start_pixel_x;draw_x_loop <= end_pixel_x;draw_x_loop++){
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
}
digitalWrite(SS, HIGH);
}else{
draw_pixel(start_pixel_x, draw_y_loop, color_r, color_g, color_b, panel_brightness);
draw_pixel(end_pixel_x, draw_y_loop, color_r, color_g, color_b, panel_brightness);
}
}
}
}

/*
* 四角形(内埋め)を描画する関数
* 指定した左上ピクセル座標から右下ピクセル座標まで任意の色で四角形(内埋め)を描画する
* 引数1:四角形の左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:四角形の左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:四角形の右下X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数4:四角形の右下Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数5:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数8:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
* 引数1〜引数4については以下の関係が成り立っている必要がある
* 四角形の左上X座標 <= 四角形の右下X座標
* 四角形の左上Y座標 <= 四角形の右下Y座標
*/
void draw_rectangle_fill(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char end_pixel_x, unsigned char end_pixel_y, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
unsigned char draw_y_loop, draw_x_loop;

if((end_pixel_x < MATRIXLED_X_COUNT) && (end_pixel_y < MATRIXLED_Y_COUNT) && (start_pixel_x <= end_pixel_x) && (start_pixel_y <= end_pixel_y)){
if((start_pixel_x == 0) && (end_pixel_x == (MATRIXLED_X_COUNT - 1))){
send_command(start_pixel_y, end_pixel_y, start_pixel_x, end_pixel_x, panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_y_loop = start_pixel_y;draw_y_loop <= end_pixel_y;draw_y_loop++){
for(draw_x_loop = start_pixel_x;draw_x_loop <= end_pixel_x;draw_x_loop++){
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
}
}
digitalWrite(SS, HIGH);
}else{
for(draw_y_loop = start_pixel_y;draw_y_loop <= end_pixel_y;draw_y_loop++){
send_command(draw_y_loop, draw_y_loop, start_pixel_x, end_pixel_x, panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = start_pixel_x;draw_x_loop <= end_pixel_x;draw_x_loop++){
SPI.transfer(color_r);
SPI.transfer(color_g);
SPI.transfer(color_b);
}
digitalWrite(SS, HIGH);
}
}
}
}

/*
* 円を描画する関数
* 指定した中心ピクセル座標から指定した半径で任意の色で円を描画する
* 引数1:円の中心X座標 設定可能な数値の範囲:円の半径〜LEDパネル横解像度 - 円の半径 -1
* 引数2:円の中心Y座標 設定可能な数値の範囲:円の半径〜LEDパネル縦解像度 - 円の半径 -1
* 引数3:円の半径 設定可能な数値の範囲:1〜(LEDパネルの小さいほうの縦もしくは横の解像度 - 1) / 2 (小数点以下切り捨て)
* 引数4:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数5:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_circle(unsigned char center_pixel_x, unsigned char center_pixel_y, unsigned char radius, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
signed short x, y, r;

if(((center_pixel_x + radius) < MATRIXLED_X_COUNT) && ((center_pixel_y + radius) < MATRIXLED_X_COUNT) && (radius <= center_pixel_x) && (radius <= center_pixel_y)){
r = (signed short)radius;
x = (signed short)radius;
y = 0;
while (x >= y) {
draw_pixel(center_pixel_x + x, center_pixel_y + y, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x - x, center_pixel_y - y, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x - x, center_pixel_y + y, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x + x, center_pixel_y - y, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x + y, center_pixel_y + x, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x - y, center_pixel_y - x, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x - y, center_pixel_y + x, color_r, color_g, color_b, panel_brightness);
draw_pixel(center_pixel_x + y, center_pixel_y - x, color_r, color_g, color_b, panel_brightness);
if ((r -= (y++ << 1) + 1) <= 0) r += --x << 1;
}
}
}

/*
* 円(中埋め)を描画する関数
* 指定した中心ピクセル座標から指定した半径で任意の色で円(中埋め)を描画する
* 引数1:円の中心X座標 設定可能な数値の範囲:円の半径〜LEDパネル横解像度 - 円の半径 -1
* 引数2:円の中心Y座標 設定可能な数値の範囲:円の半径〜LEDパネル縦解像度 - 円の半径 -1
* 引数3:円の半径 設定可能な数値の範囲:1〜(LEDパネルの小さいほうの縦もしくは横の解像度 - 1) / 2 (小数点以下切り捨て)
* 引数4:赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数5:緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_circle_fill(unsigned char center_pixel_x, unsigned char center_pixel_y, unsigned char radius, unsigned char color_r, unsigned char color_g, unsigned char color_b, unsigned char panel_brightness){
signed short x, y, r;

if(((center_pixel_x + radius) < MATRIXLED_X_COUNT) && ((center_pixel_y + radius) < MATRIXLED_X_COUNT) && (radius <= center_pixel_x) && (radius <= center_pixel_y)){
r = (signed short)radius;
x = (signed short)radius;
y = 0;
while (x >= y) {
draw_line(center_pixel_x - x, center_pixel_y + y, center_pixel_x + x, center_pixel_y + y, color_r, color_g, color_b, panel_brightness);
draw_line(center_pixel_x - x, center_pixel_y - y, center_pixel_x + x, center_pixel_y - y, color_r, color_g, color_b, panel_brightness);
draw_line(center_pixel_x - y, center_pixel_y + x, center_pixel_x + y, center_pixel_y + x, color_r, color_g, color_b, panel_brightness);
draw_line(center_pixel_x - y, center_pixel_y - x, center_pixel_x + y, center_pixel_y - x, color_r, color_g, color_b, panel_brightness);
if ((r -= (y++ << 1) + 1) <= 0) r += --x << 1;
}
}
}

/*
* テキスト(背景透過)を描画する関数
* 指定した左上ピクセル座標から任意の色でテキスト(背景透過)を描画する
* 引数1:テキスト左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度 - (テキスト文字数 * 1文字当たりの横フォントサイズ) - 2
* 引数2:テキスト左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度 - 1文字当たりの縦フォントサイズ - 2
* 引数3:文字列が格納されている配列の先頭アドレス
* 引数4:フォント赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数5:フォント緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:フォント青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
* テキストは背景透過なため、表示する文字列が変更される場合以前の表示内容の上に重ねて表示される点に注意
*/
void draw_text(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char* chara_array, unsigned char font_color_r, unsigned char font_color_g, unsigned char font_color_b, unsigned char panel_brightness){
unsigned char loop = 0, ascii, pixel_count_x = 0;
unsigned char pixel_data[MATRIXLED_X_COUNT + FONT_DATA_PIXEL_SIZE_X] = {};

unsigned char font_data_lead_loop;

unsigned char x_loop, y_loop;

if(start_pixel_x >= MATRIXLED_X_COUNT) return;
if((start_pixel_y + FONT_DATA_PIXEL_SIZE_Y) >= MATRIXLED_Y_COUNT) return;

while((chara_array[loop] |= '\0') && (pixel_count_x < MATRIXLED_X_COUNT))
{
pixel_count_x = loop * (FONT_DATA_PIXEL_SIZE_X + 1) + 1;
ascii = chara_array[loop] - 0x20;
for(font_data_lead_loop = 0;font_data_lead_loop <= FONT_DATA_PIXEL_SIZE_X;font_data_lead_loop++){
if(font_data_lead_loop < FONT_DATA_PIXEL_SIZE_X){
pixel_data[pixel_count_x + font_data_lead_loop] = pgm_read_byte(&Font_Data[ascii][font_data_lead_loop]);
}else{
pixel_data[pixel_count_x + font_data_lead_loop] = 0x00;
}
}
loop++;
}

pixel_count_x += FONT_DATA_PIXEL_SIZE_X;

if((start_pixel_x + pixel_count_x) >= MATRIXLED_X_COUNT) pixel_count_x = MATRIXLED_X_COUNT - start_pixel_x;

for(y_loop = 0;y_loop <= FONT_DATA_PIXEL_SIZE_Y;y_loop++){
for(x_loop = 0;x_loop < pixel_count_x;x_loop++){
if((((pixel_data[x_loop] >> (y_loop - 1)) & 0x01) == 0x01) && (y_loop != 0)){
draw_pixel((start_pixel_x + x_loop), (start_pixel_y + y_loop), font_color_r, font_color_g, font_color_b, panel_brightness);
}
}
digitalWrite(SS, HIGH);
}
}

/*
* テキスト(背景単色埋め)を描画する関数
* 指定した左上ピクセル座標から任意の色でテキスト(背景単色埋め)を描画する
* 引数1:テキスト左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度 - (テキスト文字数 * 1文字当たりの横フォントサイズ) - 2
* 引数2:テキスト左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度 - 1文字当たりの縦フォントサイズ - 2
* 引数3:文字列が格納されている配列の先頭アドレス
* 引数4:フォント赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数5:フォント緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数6:フォント青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数7:背景赤色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数8:背景緑色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数9:背景青色輝度値 設定可能な数値の範囲:0x00〜0xff
* 引数10:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_text_fill_back(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char* chara_array, unsigned char font_color_r, unsigned char font_color_g, unsigned char font_color_b, unsigned char back_color_r, unsigned char back_color_g, unsigned char back_color_b, unsigned char panel_brightness){
unsigned char loop = 0, ascii, pixel_count_x = 0;
unsigned char pixel_data[MATRIXLED_X_COUNT + FONT_DATA_PIXEL_SIZE_X] = {};

unsigned char font_data_lead_loop;

unsigned char x_loop, y_loop;

if(start_pixel_x >= MATRIXLED_X_COUNT) return;
if((start_pixel_y + FONT_DATA_PIXEL_SIZE_Y) >= MATRIXLED_Y_COUNT) return;

while((chara_array[loop] |= '\0') && (pixel_count_x < MATRIXLED_X_COUNT))
{
pixel_count_x = loop * (FONT_DATA_PIXEL_SIZE_X + 1) + 1;
ascii = chara_array[loop] - 0x20;
for(font_data_lead_loop = 0;font_data_lead_loop <= FONT_DATA_PIXEL_SIZE_X;font_data_lead_loop++){
if(font_data_lead_loop < FONT_DATA_PIXEL_SIZE_X){
pixel_data[pixel_count_x + font_data_lead_loop] = pgm_read_byte(&Font_Data[ascii][font_data_lead_loop]);
}else{
pixel_data[pixel_count_x + font_data_lead_loop] = 0x00;
}
}
loop++;
}

pixel_count_x += FONT_DATA_PIXEL_SIZE_X;

if((start_pixel_x + pixel_count_x) >= MATRIXLED_X_COUNT) pixel_count_x = MATRIXLED_X_COUNT - start_pixel_x - 1;

for(y_loop = 0;y_loop <= FONT_DATA_PIXEL_SIZE_Y;y_loop++){
send_command((start_pixel_y + y_loop), (start_pixel_y + y_loop), start_pixel_x, (start_pixel_x + pixel_count_x), panel_brightness);
digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(x_loop = 0;x_loop <= pixel_count_x;x_loop++){
if(y_loop == 0){
SPI.transfer(back_color_r);
SPI.transfer(back_color_g);
SPI.transfer(back_color_b);
}else if(((pixel_data[x_loop] >> (y_loop - 1)) & 0x01) == 0x01){
SPI.transfer(font_color_r);
SPI.transfer(font_color_g);
SPI.transfer(font_color_b);
}else{
SPI.transfer(back_color_r);
SPI.transfer(back_color_g);
SPI.transfer(back_color_b);
}
}
digitalWrite(SS, HIGH);
}
}

/*
* 画像を任意の場所に描画する関数
* 画像の左上のピクセル座標と画像の大きさを指定して任意の場所に画像を表示する
* 画像データは赤色緑色青色の8ビットデータが左上ピクセルから順番に格納された配列
* 引数1:画像の左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:画像の左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:画像の横幅のピクセルサイズ 設定可能な数値の範囲:1〜LEDパネル横解像度 - 画像の左上X座標
* 引数4:画像の縦幅のピクセルサイズ 設定可能な数値の範囲:1〜LEDパネル縦解像度 - 画像の左上Y座標
* 引数5:画像データが入っている配列の先頭のアドレス
* 引数6:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_image(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char image_size_x, unsigned char image_size_y, unsigned char* draw_data, unsigned char panel_brightness){
unsigned char draw_y_loop, draw_x_loop;

if(((start_pixel_x + image_size_x) <= MATRIXLED_X_COUNT) && ((start_pixel_y + image_size_y) <= MATRIXLED_Y_COUNT)){
if((start_pixel_x == 0) && (image_size_x == MATRIXLED_X_COUNT)){
send_command(start_pixel_y, (start_pixel_y + image_size_y - 1), start_pixel_x, (image_size_x - 1), panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_y_loop = 0;draw_y_loop < image_size_y;draw_y_loop++){
for(draw_x_loop = 0;draw_x_loop < image_size_x;draw_x_loop++){
SPI.transfer(*draw_data++);
SPI.transfer(*draw_data++);
SPI.transfer(*draw_data++);
}
}
digitalWrite(SS, HIGH);
}else{
for(draw_y_loop = start_pixel_y;draw_y_loop < (start_pixel_y + image_size_y);draw_y_loop++){
send_command(draw_y_loop, draw_y_loop, start_pixel_x, (start_pixel_x + image_size_x - 1), panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = start_pixel_x;draw_x_loop < (start_pixel_x + image_size_x);draw_x_loop++){
SPI.transfer(*draw_data++);
SPI.transfer(*draw_data++);
SPI.transfer(*draw_data++);
}
digitalWrite(SS, HIGH);
}
}
}
}

/*
* 24ビットビットマップ画像を任意の場所に描画する関数
* 画像の左上のピクセル座標と画像の大きさを指定して任意の場所に画像を表示する
* 画像データは24ビットビットマップ画像からヘッダ部分を削除し画像データ部分のみを取り出したもの
* 引数1:画像の左上X座標 設定可能な数値の範囲:0〜LEDパネル横解像度-1
* 引数2:画像の左上Y座標 設定可能な数値の範囲:0〜LEDパネル縦解像度-1
* 引数3:画像の横幅のピクセルサイズ 設定可能な数値の範囲:1〜LEDパネル横解像度 - 画像の左上X座標
* 引数4:画像の縦幅のピクセルサイズ 設定可能な数値の範囲:1〜LEDパネル縦解像度 - 画像の左上Y座標
* 引数5:画像データが入っている配列の先頭のアドレス
* 引数6:LEDパネル輝度 設定可能な数値の範囲:0x01〜0x0fの場合、その値をLEDパネルの輝度に反映 0x00の場合、LEDパネルコントローラのボリュームの値で輝度を設定
*/
void draw_image_for_24bitBMP(unsigned char start_pixel_x, unsigned char start_pixel_y, unsigned char image_size_x, unsigned char image_size_y, unsigned char* draw_data, unsigned char panel_brightness){
unsigned char draw_y_loop, draw_x_loop;

if(((start_pixel_x + image_size_x) <= MATRIXLED_X_COUNT) && ((start_pixel_y + image_size_y) <= MATRIXLED_Y_COUNT)){
if((start_pixel_x == 0) && (image_size_x == MATRIXLED_X_COUNT)){
send_command(start_pixel_y, (start_pixel_y + image_size_y - 1), start_pixel_x, (image_size_x - 1), panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_y_loop = 0;draw_y_loop < image_size_y;draw_y_loop++){
for(draw_x_loop = 0;draw_x_loop < image_size_x;draw_x_loop++){
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 2 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 1 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 0 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
}
}
digitalWrite(SS, HIGH);
}else{
for(draw_y_loop = 0;draw_y_loop < image_size_y;draw_y_loop++){
send_command((draw_y_loop + start_pixel_y), (draw_y_loop + start_pixel_y), start_pixel_x, (start_pixel_x + image_size_x - 1), panel_brightness);

digitalWrite(RST, LOW);
delayMicroseconds(5);
digitalWrite(SS, LOW);
for(draw_x_loop = 0;draw_x_loop < image_size_x;draw_x_loop++){
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 2 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 1 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
SPI.transfer(pgm_read_byte(((image_size_y - draw_y_loop - 1) * image_size_x + draw_x_loop) * MATRIXLED_COLOR_COUNT + 0 + draw_data + ((4 - ((image_size_x * MATRIXLED_COLOR_COUNT) % 4)) % 4) * (image_size_y - draw_y_loop - 1)));
}
digitalWrite(SS, HIGH);
}
}
}
}


実際に動作させてみた動画がこちらになります。

本当はスクロールとか入れてもいいかなと思ったんですが、なんか文字を横スクロールするだけだとあんまり応用が効かないよなぁ・・・とか思っていろいろ考えた挙句面倒くさくなったのでやめました。

そのうちLEDパネルのケースをアクリルで作ったこととか、SDカードから動画と音声を読み込んで再生するやつとか記事にしたいのですが、マイコン動画プレイヤーは完全自分オリジナルプログラム(要するにウンコードってことです)なため、公開できるレベルにするのがちょっと無理かもしれませんね・・・
一応今のところは再生と停止と一時停止と動画ファイル選択はできているのですが、再生するときのフレームレートはプログラムに直接書き込んであって草という感じです。
何とかしないとなぁ・・・

wata_net at 00:22コメント(0) この記事をクリップ!

2018年05月04日

フルカラードットマトリクスLEDパネル用のSPIインターフェイスを作る

最近AmazonでフルカラードットマトリクスLEDパネルがかなり安く売っているのを見つけました。
image17
横が64ピクセルで縦が32ピクセルなので合計で2048個ものRGBLEDが使用されていることになります。
それでいて3299円って極端に安いですよね。LED1個あたり1.61円です。
他にも探してみるといくつか見つかりました。

image18
これはLED1個0.81円

image19
こっちは1.76円

というわけで3mmピッチの64*32のパネルを禄に調べもせず2個衝動買いしてしまいました。
更に後から3mmピッチの64*64のパネルも購入しましたが、こちらはまだ届いていません。

これほど安くフルカラードットマトリクスLEDパネルが販売されているのは、おそらくデジタルサイネージの普及に伴ってLEDディスプレイ用として需要が拡大しているからなのでしょうね。

一応Amazonのリンクを貼っておきますが、今後値段が変更されたり取扱がなくなったりする可能性もあるのであしからず。
P3 RGBピクセルパネルHDビデオディスプレイ64×32ドットマトリックスSMD LEDディスプレイモジュール192×96mm
P3 SMD屋内rgb 32 S ledモジュールp3屋内フルカラーのための64 x64ピクセルledビデオ壁ledスクリーン電子スコアボード
P5 RGB ピクセルパネルHDビデオディスプレイ LEDディスプレイモジュール 320x160 1 / 16s 64×32

こちらが実際のフルカラードットマトリクスLEDパネルになります。
64*32のパネルを2枚連結しています。
DSCF1589
DSCF1588

さて、このフルカラードットマトリクスLEDパネルの表示方法ですが、信号規格はHUB75というものが使用されているようです。
HUB75については、ネットで調べれは他にわかりやすいサイトがいくつかあるのでここでの詳細な説明は省きますが、簡単に言うとパネルのLEDをダイナミック点灯させるための単純なシフトレジスタです。
そのためHUB75のパネルを表示させ続けるには常にデータを送り続ける必要があり、また基本的に各ドットのLEDのRGBはONもしくはOFFのみであるためマイコンで他の処理をこなしつつ階調表現まで行うのはかなり厄介です。

そこで、他のマイコンからSPI接続で簡単にフルカラードットマトリクスLEDパネルを表示できるようにするためのインターフェイスを作成しました。
使用したマイコンはSTM32F103C8T6です。このマイコンが載っているBluePillなどと呼ばれているボードがamazonでは500円程度で販売されており、入手しやすく安価であるためこれを選択しました。
DSCF1540

なにかの役に立つかもしれないので回路図と基板外形図も載せておきます。
bluepill_schbluepill_brd

目標仕様は、
SPI送信側は各ピクセルのRGB輝度情報を送信するだけで表示できるようにする。
対応解像度は64*32以上。できれば128*32。
フレームレートは60FPS以上。
色深度は4bit以上。
画面全体の輝度を可変できるようにする。
と定めました。
本当はSPI接続のフルカラー有機ELグラフィックディスプレイと同じような使用感を目指していたのですが、それをマイコンで実現するのはどうやら自分には無理だと感じたので早々に諦めました。
コマンドとデータに分けて送信したかったのですが、SPI受信側では実際に送信されるデータ数やタイミングを知る方法がわかりませんでした。(ポーリングや割り込みだとドットマトリクスパネルの表示に影響が出るためDMAを使うしか無い)そのため、単純にRGB4bitを垂れ流せばそのままパネルに表示される仕様としました。

まずは回路図です。
HUB75_sch
ご覧の通り、極めてシンプルです。ハードウェア的には何の面白みもない回路です。
R1でパネルの輝度を調整します。
SV2にフルカラードットマトリクスLEDパネルを接続します。
SV3にSPIを接続します。
MOSI:マスター出力スレーブ入力
SCK:シリアルクロック
NSS:スレーブセレクト
RST:マイコン内部のSPIのDMA転送をリセットする(このピンをトグルした後に1フレーム分のデータを転送します)
受信オンリースレーブなのでMISOはありません。

次にプログラムです。
自分のSTM32の開発環境は、STM32CubeMXとSW4STM32を使用しています。
開発の流れはまずSTM32CubeMXでマイコンのペリフェラルを設定したあとコード生成し、SW4STM32で生成されたプロジェクトを読み込みプログラムを書いてST-LINK/V2で書き込み兼デバッグを行っています。
以下のプログラムは自動生成された部分を省略して自分が書いたところを抜粋したものになります。
C言語独学マンが生成したウンコードなのでツッコミとかあればコメント欄にお願いします。

main.c
/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdlib.h>
/* USER CODE END Includes */

/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
#define MATRIXLED_R1(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, x))
#define MATRIXLED_G1(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, x))
#define MATRIXLED_B1(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, x))
#define MATRIXLED_R2(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, x))
#define MATRIXLED_G2(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, x))
#define MATRIXLED_B2(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, x))
#define MATRIXLED_ADDR_A(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, x))
#define MATRIXLED_ADDR_B(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, x))
#define MATRIXLED_ADDR_C(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, x))
#define MATRIXLED_ADDR_D(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, x))
#define MATRIXLED_CLOCK(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, x))
#define MATRIXLED_STROBE(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, x))
#define MATRIXLED_OUTENABLE(x) (HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, x))

#define ONBOARD_LED(x) (HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, x))

#define LED_RGB(x) {\\
GPIOB->BRR = (0x3f << 3);\\
GPIOB->BSRR = ((x & 0x3f) << 3);\\
}

#define LED_ADDR(x) {\\
GPIOB->BRR = (0x0f << 12);\\
GPIOB->BSRR = ((x & 0x0f) << 12);\\
}

#define LED_CLK_HIGH {\\
GPIOB->BSRR = (1 << 9);\\
}

#define LED_CLK_LOW {\\
GPIOB->BRR = (1 << 9);\\
}

#define LED_STB_HIGH {\\
GPIOB->BSRR = (1 << 10);\\
}

#define LED_STB_LOW {\\
GPIOB->BRR = (1 << 10);\\
}

#define LED_OE_HIGH {\\
GPIOB->BSRR = (1 << 11);\\
}

#define LED_OE_LOW {\\
GPIOB->BRR = (1 << 11);\\
}

#define ONBOARD_LED_TOGGLE {\\
GPIOC->ODR ^= (1 << 13);\\
}

#define HIGH 1
#define LOW 0

#define COLOR_R 0
#define COLOR_G 1
#define COLOR_B 2

#define R1_SET 0x0001
#define G1_SET 0x0002
#define B1_SET 0x0004
#define R2_SET 0x0008
#define G2_SET 0x0010
#define B2_SET 0x0020

#define MATRIXLED_X_COUNT 64
#define MATRIXLED_Y_COUNT 32
#define MATRIXLED_COLOR_COUNT 3

#define MATRIXLED_MAX_Y_ADDRES 16

#define MATRIXLED_PWM_RESOLUTION 16

#define ADC_DIVIDE_VALUE 445
#define TIM2_ARR_OFFSET_VALUE 5

unsigned char Flame_Buffer[MATRIXLED_Y_COUNT][MATRIXLED_X_COUNT][MATRIXLED_COLOR_COUNT];

volatile unsigned char TIM2_Updated_Flag, TIM3_Updated_Flag;

/* USER CODE END PV */

/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void fill_flame_buffer_color(unsigned char, unsigned char, unsigned char);
void fill_flame_buffer_random(void);
void fill_flame_buffer_testpattern(void);

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned short matrixled_X_loop_count, matrixled_Y_loop_count;
unsigned char pwm_count;
unsigned short led_rgb_temp;

unsigned short ADC_value;

float display_brightness_temp;

/* USER CODE END 1 */

/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN 2 */
fill_flame_buffer_testpattern();

HAL_SPI_Receive_DMA(&hspi1, Flame_Buffer, (MATRIXLED_Y_COUNT * MATRIXLED_X_COUNT * MATRIXLED_COLOR_COUNT));
HAL_TIM_Base_Start_IT(&htim3);

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
HAL_ADC_Start_DMA(&hadc1, &ADC_value, 1);
display_brightness_temp = (float)ADC_value / ADC_DIVIDE_VALUE;
for(pwm_count = 1;pwm_count < MATRIXLED_PWM_RESOLUTION;pwm_count++){
for(matrixled_Y_loop_count = 0;matrixled_Y_loop_count < MATRIXLED_MAX_Y_ADDRES;matrixled_Y_loop_count++){

if(matrixled_Y_loop_count == 1) TIM2->ARR = (uint16_t)(pwm_count * (display_brightness_temp + 1) + TIM2_ARR_OFFSET_VALUE);
TIM2_Updated_Flag = RESET;
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
__HAL_TIM_ENABLE(&htim2);
LED_OE_LOW;

for(matrixled_X_loop_count = 0;matrixled_X_loop_count < MATRIXLED_X_COUNT;matrixled_X_loop_count++){
led_rgb_temp = 0;

if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B1_SET;

if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B2_SET;

LED_RGB(led_rgb_temp);

LED_CLK_HIGH;
LED_CLK_LOW;
}
while(TIM2_Updated_Flag == RESET);

LED_ADDR(matrixled_Y_loop_count);
LED_STB_HIGH;
LED_STB_LOW;

//フレームバッファにSPIからDMA転送している最中に読みだそうとすると一瞬フリーズする問題
//TIM2の割り込みがたまに他の割り込みとバッティングする問題
}
}
while(TIM3_Updated_Flag == RESET);
TIM3_Updated_Flag = RESET;

ONBOARD_LED_TOGGLE;
}
/* USER CODE END 3 */

}

/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN 4 */
void fill_flame_buffer_color(unsigned char red, unsigned char green, unsigned char blue){
unsigned char red_temp, green_temp, blue_temp;
unsigned char fill_buffer_loop_x, fill_buffer_loop_y;

if(red < MATRIXLED_PWM_RESOLUTION) red_temp = red;
else red_temp = MATRIXLED_PWM_RESOLUTION - 1;

if(green < MATRIXLED_PWM_RESOLUTION) green_temp = green;
else green_temp = MATRIXLED_PWM_RESOLUTION - 1;

if(blue < MATRIXLED_PWM_RESOLUTION) blue_temp = blue;
else blue_temp = MATRIXLED_PWM_RESOLUTION - 1;

for(fill_buffer_loop_y = 0;fill_buffer_loop_y < MATRIXLED_Y_COUNT;fill_buffer_loop_y++){
for(fill_buffer_loop_x = 0;fill_buffer_loop_x < MATRIXLED_X_COUNT;fill_buffer_loop_x++){
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_R] = red_temp;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_G] = green_temp;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_B] = blue_temp;
}
}
}

void fill_flame_buffer_random(void){
unsigned char fill_buffer_loop_x, fill_buffer_loop_y;

srand(rand());

for(fill_buffer_loop_y = 0;fill_buffer_loop_y < MATRIXLED_Y_COUNT;fill_buffer_loop_y++){
for(fill_buffer_loop_x = 0;fill_buffer_loop_x < MATRIXLED_X_COUNT;fill_buffer_loop_x++){
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_R] = rand() % MATRIXLED_PWM_RESOLUTION;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_G] = rand() % MATRIXLED_PWM_RESOLUTION;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_B] = rand() % MATRIXLED_PWM_RESOLUTION;
}
}
}

void fill_flame_buffer_testpattern(void){
unsigned char fill_buffer_loop_x, fill_buffer_loop_y;

for(fill_buffer_loop_y = 0;fill_buffer_loop_y < MATRIXLED_Y_COUNT;fill_buffer_loop_y++){
for(fill_buffer_loop_x = 0;fill_buffer_loop_x < (MATRIXLED_X_COUNT / 12);fill_buffer_loop_x++){
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_G] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x][COLOR_B] = (MATRIXLED_PWM_RESOLUTION - 1);

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12)][COLOR_R] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12)][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12)][COLOR_B] = (MATRIXLED_PWM_RESOLUTION - 1);

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 2][COLOR_R] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 2][COLOR_G] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 2][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 3][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 3][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 3][COLOR_B] = (MATRIXLED_PWM_RESOLUTION - 1);

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 4][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 4][COLOR_G] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 4][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 5][COLOR_R] = (MATRIXLED_PWM_RESOLUTION - 1);
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 5][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 5][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 6][COLOR_R] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 6][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 6][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 7][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 7][COLOR_G] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 7][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 8][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 8][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 8][COLOR_B] = 0;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 9][COLOR_R] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 9][COLOR_G] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 9][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 10][COLOR_R] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 10][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 10][COLOR_B] = 0;

Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 11][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 11][COLOR_G] = 0;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 11][COLOR_B] = 0;

if(fill_buffer_loop_x < (MATRIXLED_X_COUNT % 12)){
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 12][COLOR_R] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 12][COLOR_G] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
Flame_Buffer[fill_buffer_loop_y][fill_buffer_loop_x + (MATRIXLED_X_COUNT / 12) * 12][COLOR_B] = fill_buffer_loop_y * (MATRIXLED_PWM_RESOLUTION - 1) / MATRIXLED_Y_COUNT;
}
}
}
}

/* USER CODE END 4 */

/* 略 ------------------------------------------------------------------------*/


stm32f1xx_it.c
/* 略 ------------------------------------------------------------------------*/

/* USER CODE BEGIN 0 */
#include <stdlib.h>

#define LED_OE_HIGH {\\
GPIOB->BSRR = (1 << 11);\\
}

#define LED_OE_LOW {\\
GPIOB->BRR = (1 << 11);\\
}

#define ONBOARD_LED_TOGGLE {\\
GPIOC->ODR ^= (1 << 13);\\
}

#define MATRIXLED_X_COUNT 64
#define MATRIXLED_Y_COUNT 32
#define MATRIXLED_COLOR_COUNT 3

extern unsigned char Flame_Buffer[MATRIXLED_Y_COUNT][MATRIXLED_X_COUNT][MATRIXLED_COLOR_COUNT];
extern unsigned char TIM2_Updated_Flag, TIM3_Updated_Flag;

/* USER CODE END 0 */

/* 略 ------------------------------------------------------------------------*/

/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
HAL_SPI_DMAStop(&hspi1);
HAL_SPI_Receive_DMA(&hspi1, Flame_Buffer, (MATRIXLED_Y_COUNT * MATRIXLED_X_COUNT * MATRIXLED_COLOR_COUNT));

/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */

/* USER CODE END EXTI0_IRQn 1 */
}

/* 略 ------------------------------------------------------------------------*/

/**
* @brief This function handles TIM2 global interrupt.
*/
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET){
LED_OE_HIGH;
TIM2_Updated_Flag = SET;
}

/* USER CODE END TIM2_IRQn 0 */
HAL_TIM_IRQHandler(&htim2);
/* USER CODE BEGIN TIM2_IRQn 1 */
if(TIM2_Updated_Flag == SET){
HAL_TIM_Base_Stop_IT(&htim2);
}

/* USER CODE END TIM2_IRQn 1 */
}

/**
* @brief This function handles TIM3 global interrupt.
*/
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
if(__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE) != RESET){
TIM3_Updated_Flag = SET;
}

/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */

/* USER CODE END TIM3_IRQn 1 */
}

/**
* @brief This function handles SPI1 global interrupt.
*/
void SPI1_IRQHandler(void)
{
/* USER CODE BEGIN SPI1_IRQn 0 */
//SPI通信にてエラーが発生した時にここに飛ぶためエラー処理を行う必要がある
ONBOARD_LED_TOGGLE;

/* USER CODE END SPI1_IRQn 0 */
HAL_SPI_IRQHandler(&hspi1);
/* USER CODE BEGIN SPI1_IRQn 1 */

/* USER CODE END SPI1_IRQn 1 */
}

/* 略 ------------------------------------------------------------------------*/


上から順番に抜粋して説明していきます。

main.c
#define MATRIXLED_X_COUNT 64
#define MATRIXLED_Y_COUNT 32
#define MATRIXLED_COLOR_COUNT 3

#define MATRIXLED_MAX_Y_ADDRES 16

#define MATRIXLED_PWM_RESOLUTION 16

#define ADC_DIVIDE_VALUE 445
#define TIM2_ARR_OFFSET_VALUE 5


stm32f1xx_it.c
#define MATRIXLED_X_COUNT 64
#define MATRIXLED_Y_COUNT 32
#define MATRIXLED_COLOR_COUNT 3

ここでマトリクスパネルの解像度を変更出来ます。解像度を変更する際はmain.cとstm32f1xx_it.cのどちらも書き換える必要があります。(プリプロセッサとかヘッダファイルがよく分かっていないのです。)
MATRIXLED_X_COUNT:横解像度 最大128
MATRIXLED_Y_COUNT:縦解像度 最大32
MATRIXLED_COLOR_COUNT:色数 RGBだから3色 ここは弄る必要はない
MATRIXLED_MAX_Y_ADDRES:マトリクスパネルのABCDのアドレス選択の最大値 例えばABCまでであれば8 ABCDEまであるHUB75Eはこのプログラムでは未対応
MATRIXLED_PWM_RESOLUTION:PWM分解能
ADC_DIVIDE_VALUE:輝度調整用の可変抵抗のADC値を割り算する値 値を小さくすると最大輝度がより明るくなるが、小さくしすぎるとLEDの輝度を決定しているTIM2の動作時間がフレームレートを決定しているTIM3の動作周期を超えてしまうためフレームレートが低下する
TIM2_ARR_OFFSET_VALUE:TIM2の最小値 あまり弄らないほうが良い

main.c
void fill_flame_buffer_color(unsigned char, unsigned char, unsigned char);
void fill_flame_buffer_random(void);
void fill_flame_buffer_testpattern(void);

fill_flame_buffer_colorはバッファを引数で渡されたRGB値で埋める関数です。
fill_flame_buffer_randomはバッファをランダム値で埋める関数です。呼び出すたびにランダム値は変化します。
fill_flame_buffer_testpatternはバッファをテストパターンで埋める関数です。コントラストの確認とかによく使います。

main.c
unsigned char Flame_Buffer[MATRIXLED_Y_COUNT][MATRIXLED_X_COUNT][MATRIXLED_COLOR_COUNT];

パネルのRGBピクセルに対応したバッファをRAMに確保しています。
SPIによって受信したピクセルデータはここに直接DMA転送されます。
例えば、MATRIXLED_PWM_RESOLUTIONが16だった場合に一番左上のピクセルを白色に表示させる場合は
Flame_Buffer[0][0][0] = 0x0f;
Flame_Buffer[0][0][1] = 0x0f;
Flame_Buffer[0][0][2] = 0x0f;

とすれば良いことになります。

main.c
if(matrixled_Y_loop_count == 1) TIM2->ARR = (uint16_t)(pwm_count * (display_brightness_temp + 1) + TIM2_ARR_OFFSET_VALUE);

TIM2はLEDのON時間を決定しており、PWM段階に応じてTIM2の自動再ロードレジスタの値を変えることによってLEDのON時間を可変しています。
なめらかな輝度階調表現を得るには必須です。詳細は後述します。

main.c
for(matrixled_X_loop_count = 0;matrixled_X_loop_count < MATRIXLED_X_COUNT;matrixled_X_loop_count++){
led_rgb_temp = 0;

if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B1_SET;

if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B2_SET;

LED_RGB(led_rgb_temp);

LED_CLK_HIGH;
LED_CLK_LOW;
}

バッファからマトリクスパネルに出力するデータに変換し出力しているところのループです。
まぁ、見たまんまですね。あんまり頭がいい方法とは思えず、なにかもっと高速でいいやり方があるに違いないと思っているのですが、RGBデータから変換しなければならないという縛りがあるためこうせざるを得ない状況です。
RAMがたっぷりあれば、予めRGBデータからGPIOにそのまま出力できるデータに変換してダブルバッファな感じでDMA転送を行えばかなり高速化できると思うのですが、あいにくSTM32F103C8T6はRAMが20KBしか無いため無理でした。
また、フレキシブルメモリコントローラがあればこれを利用できたかもしれませんが、STM32F103C8T6にはありません。
今の所、ここが一番処理に時間がかかってフレームレート向上のネックになっているところです。
次にPWMのループかなと思います。

main.c
//フレームバッファにSPIからDMA転送している最中に読みだそうとすると一瞬フリーズする問題
//TIM2の割り込みがたまに他の割り込みとバッティングする問題

これなんですが、上のコメントはおそらくダブルバッファを採用すれば解決できる問題です。しかしRAM容量が足りないので今の所放置です。
下のコメントは、TIM2の割り込み優先度を最高にすることで一応は解決したと考えています。

stm32f1xx_it.c
/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
HAL_SPI_DMAStop(&hspi1);
HAL_SPI_Receive_DMA(&hspi1, Flame_Buffer, (MATRIXLED_Y_COUNT * MATRIXLED_X_COUNT * MATRIXLED_COLOR_COUNT));

/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */

/* USER CODE END EXTI0_IRQn 1 */
}

これは外部割り込みが発生した時に呼び出される割り込みハンドラです。
で、どこのピンの外部割り込みなのかというと、上の回路図のSV3のコネクタのRSTピンです。
SPIマスタ側のマイコンからRGBデータが送られてくるわけですが、スレーブ側のマイコンはどれがデータの頭なのかデータは何個なのか知る方法が分からなかったので、データ送信開始前にSPIのDMA転送を停止させ、送信するデータは1フレーム分まとめて送信されるという前提でSPIのDMA転送を開始させています。


HUB75での輝度階調表現について

HUB75は通常では各ピクセルはONもしくはOFFしか出来ず、これを輝度階調表現させるには、LEDのON時間を変化させながら同じフレームを何回も表示させなければいけません。
しかし、単純にピクセルの輝度情報に比例してLEDのON時間を変化させただけでは全く滑らかに輝度が変化しているようには見えないのです。
こんな感じになります。
DSCF1604
暗いところから急に明るくなって、以降はほとんど明るさに変化が無いように見えますね。
なぜならば人間の目の輝度に対する感度は対数的に変化するからです。
それはどういうことかと言うと、輝度が低い時は僅かに輝度が上がっただけでもかなり明るくなったように感じるが、輝度が高い時は輝度が更に上がってもほとんど変化していないように見えるのです。その特性を相殺して明るさがなめらかに変化しているように感じさせるためには、輝度を指数的に変化させなければなりません。
しかし、マイコンに指数計算をやらせるととっても時間がかかって厳しいので今回は二次関数的に変化させることとしました。
二次関数的に変化させたいということは、
ON時間=PWM値^2
という式が成り立つようにしたいということです。
しかし、今回のプログラムではON時間がどんどん累積するので実際のところは
実ON時間=PWM現在値^2+前回ON時間
となります。
ここで前回ON時間は
前回ON時間=PWM前回値^2
PWM前回値=PWM現在値-1
よって
前回ON時間=(PWM現在値-1)^2
つまりON時間は
ON時間=PWM現在値^2+前回ON時間-(PWM現在値-1)^2
として計算すればいいことになります。式をいい感じにすると
ON時間-前回ON時間=2*PWM現在値-1
となります。つまりTIM2の自動再ロードレジスタARRの値は2*PWM現在値-1にすればいいことが分かりますが、この値が小さくなりすぎるとTIM2の割り込み周期が短くなりすぎてまともに動作しないので、定数項-1を内包した形で
TIM2->ARR = 2*PWM現在値+TIM2ARR最小値
としておきます。
また、今回は可変抵抗によってパネルの輝度を変化させたいのでPWM現在値の係数2を内包させた形で輝度係数を与えます。
すると
TIM2->ARR = PWM現在値*輝度係数+TIM2ARR最小値
となります。PWM現在値*輝度係数の項が0になるとPWM現在値にかかわらずTIM2の自動再ロードレジスタARRの値がTIM2ARR最小値で一定になってしまうので
TIM2->ARR = PWM現在値*(輝度係数+1)+TIM2ARR最小値
とします。そうすればプログラム中の
TIM2->ARR = (uint16_t)(pwm_count * (display_brightness_temp + 1) + TIM2_ARR_OFFSET_VALUE);
と同じ形になりました。
実際のところ、こうしてTIM2のON時間を計算してやることによって階調表現の滑らかさがかなり改善します。
DSCF1601
前の画像と見比べてみると一目瞭然です。
パネルに画像を表示させる場合、コントラストがかなり変化します。

ON時間を1次関数的に変化させた場合
DSCF1567

ON時間を2次関数的に変化させた場合
DSCF1582
本来指数関数的に変化させるべきところを2次関数で代用してるのでまだだいぶ足りない感はありますが、かなりコントラストが改善していることが分かります。


さて、このプログラムで解像度64*32までであれば60FPSで問題なく動作します。
しかし64*32のパネルを2枚直列に接続し解像度を128*32にすると、フレームレートが42FPSくらいまで低下し肉眼でもチラツキが目立つようになりかなり厳しいものがあります。
ここで、MATRIXLED_PWM_RESOLUTIONを8にすると色深度が犠牲になりますが60FPS出るようになります。

色深度4ビットのときの表示がこれです。
DSCF1582

対して色深度3ビットの時の表示がこれです。
DSCF1608
髪のあたりを比較するとわかりやすいですが、色数が少なくなり、色の再現性が悪化しています。

つまり、
解像度を犠牲にして色深度とフレームレートを取るか
フレームレートを犠牲にして解像度と色深度を取るか
色深度を犠牲にして解像度とフレームレートを取るか
の3パターンのトレードオフになるわけです。できればどれもあまり犠牲にはしたくないものです。

そこで、苦肉の策でフレームレートをすこし犠牲にする方向でできるだけちらつきが目立たないようにするため、インターレース方式を採用してみることにしました。
インターレースについては詳しく解説はしませんが、水平走査を1本飛ばしで交互に表示させることによりフレームレートの2倍の周波数でフィールドを更新するため同じフレームレートでもちらつきが目立ちにくくなるというものです。

プログラムは変更した箇所だけ抜粋します。

main.c
  /* USER CODE BEGIN WHILE */
while (1)
{

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
HAL_ADC_Start_DMA(&hadc1, &ADC_value, 1);
display_brightness_temp = (float)ADC_value / ADC_DIVIDE_VALUE;
for(pwm_count = 1;pwm_count < MATRIXLED_PWM_RESOLUTION;pwm_count++){
for(matrixled_Y_loop_count = interlace_count;matrixled_Y_loop_count < MATRIXLED_MAX_Y_ADDRES;matrixled_Y_loop_count += 2){

if((matrixled_Y_loop_count == 2) || (matrixled_Y_loop_count == 3)) TIM2->ARR = (uint16_t)(pwm_count * (display_brightness_temp + 1) + TIM2_ARR_OFFSET_VALUE);
TIM2_Updated_Flag = RESET;
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
__HAL_TIM_ENABLE(&htim2);
LED_OE_LOW;

for(matrixled_X_loop_count = 0;matrixled_X_loop_count < MATRIXLED_X_COUNT;matrixled_X_loop_count++){
led_rgb_temp = 0;

if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G1_SET;
if(Flame_Buffer[matrixled_Y_loop_count][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B1_SET;

if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_R] >= pwm_count) led_rgb_temp |= R2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_G] >= pwm_count) led_rgb_temp |= G2_SET;
if(Flame_Buffer[matrixled_Y_loop_count + MATRIXLED_MAX_Y_ADDRES][matrixled_X_loop_count][COLOR_B] >= pwm_count) led_rgb_temp |= B2_SET;

LED_RGB(led_rgb_temp);

LED_CLK_HIGH;
LED_CLK_LOW;
}
while(TIM2_Updated_Flag == RESET);

LED_ADDR(matrixled_Y_loop_count);
LED_STB_HIGH;
LED_STB_LOW;

//フレームバッファにSPIからDMA転送している最中に読みだそうとすると一瞬フリーズする問題
//TIM2の割り込みがたまに他の割り込みとバッティングする問題
}
}
while(TIM3_Updated_Flag == RESET);
TIM3_Updated_Flag = RESET;

ONBOARD_LED_TOGGLE;

if(interlace_count == 0) interlace_count++;
else interlace_count = 0;
}
/* USER CODE END 3 */

結果は比較的良好で、肉眼ではそれほどちらつきは気にならなくなりました。

以上でフルカラードットマトリクスLEDパネル用のSPIインターフェイスの作成は一区切りとすることにしました。
128*32解像度ではプログレッシブ表示で60FPS出せなかったのがかなり心残りですが、どうやら今の自分のレベルでは無理なようです。
最後に、今回作成したプログラムのプロジェクトファイルを丸揚げしておきます。
ダウンロードして解凍し、STM32CubeMXやSW4STM32などにインポートします。
あわよくば誰かが完璧に改良してくれることを狙っています。
HUB75フルカラードットマトリクスLEDパネル用SPIインターフェイスプログレッシブ版プロジェクトファイル
HUB75フルカラードットマトリクスLEDパネル用SPIインターフェイスインターレース版プロジェクトファイル

DSCF1619
DSCF1578
DSCF1580
DSCF1581

wata_net at 06:48コメント(0) この記事をクリップ!

2018年05月03日

ブログのスタイルを少しだけ変更

このブログを初めたのが2008/02/09、今から10年前のことになります。
その当時自分が使ってたPCは確か富士通のDESKPOWER CシリーズのC3/55Lの中古だったと思います。
C3/55Lは発売が2000年春ということで2008年当時としても骨董PCではありましたが、256MBのPC100メモリを2枚載せたりCPUをPentum3の1GHzに交換したりして使ってたような記憶があります。
で、当時のディスプレイ事情ですが、C3/55Lは15型の1024*768の液晶ディスプレイがくっついているので当然それを使っていました。
他にもブラウン管のCRTモニタがまだ現役で使われていたと思います。
そんな時期に開設されたこのブログは記事の横幅が525pxとなっていました。おそらく2008年当時ならばこれが普通だったのではと思います。
しかし現在はというとモニタは1920*1080のFullHDは当たり前、最近4K解像度もだいぶ普及しつつあります。
そんなご時世に横幅525pxはさすがに窮屈なのではないかと以前から思っておりました。
そこで全く分からないCSSを勘でいじって横幅を広くしてみました。
若干いい感じになったかなと思ったのですが、そうするとデフォルトのフォントが小さすぎる気がします。
そもそもデザインのテンプレートみたいなのを使って今どきな感じに一新すべきかなと思ったりします。
正直今まで生きてきてこういうWebサイトの事について一切関心も興味も持たなかったのでどうすれば良いのかよくわからないです。
まぁ気が向いたらなんとかしましょうかね〜〜

wata_net at 02:16コメント(0) この記事をクリップ!

2018年04月21日

これまでに作ったものまとめ

これまでに作ったけどブログでは紹介していなかったもののまとめです。
本当は回路やプログラム載せてアレコレ解説したいのですが、文章に起こすのが結構めんd・・・大変なので画像とちょっとした紹介にとどめておきます。
このなかでやる気が出たものはもしかすると詳細記事を作るかもしれません。



2016年2月頃
コンデンサマイク用マイクアンプ
DSC_0012a
電源はUSBから5Vを取っています。USBの電源ラインはかなりノイズまみれでこのノイズの除去に苦労した思い出があります。結局低周波ノイズだけは取り切れずに出力に重畳してしまっています。
マイクラをボイチャしながらマルチプレイする時に重宝しましたが、現在マイクラ自体をほとんどプレイすることがなくなってしまったのでそれに伴ってこのマイクアンプの使用機会もなくなっていまいました。



2016年3月頃
汎用三相インバータをPC電源のケースに入れたもの
DSC_0077
AC100V、200V切り替え可能になっています。また外部にスイッチやボリュームを接続して始動停止、正逆転、速度調整ができるようになっています。
これはかなり使いやすく、現在でも小容量の三相誘導モータを回転させるときに重宝します。



2017年1月頃
降圧チョッパ式定電流レーザーダイオードドライバ
DSC_0541
降圧チョッパ式の定電流レーザーダイオードドライバは以前このブログで紹介しましたが、その後小型化してレーザーポインターに組み込めるようしたものを作成していました。
TPS5450を使用しており、電源は18650を2セル直列にしたものを想定しています。最大電流は2Aまで、表面実装の半固定抵抗で調整が可能です。
また、出力のONOFFはICのイネーブル機能を用いており、接点容量の小さいタクトスイッチでも問題なく使用出来ます。

DSC_0542
組み上げるとこのようになります。直径がちょうど18650と同じくらいになるようにしてあり、18650を使用する既製品のLEDライトを改造して組み込むことが出来ます。
実際にこれで1Wの445nmレーザーポインタを作成し、それ自体は現在でも存在していますが、そもそも用途がないので使用機会も無いです。



2017年4月頃
マイコンを使い始めた
DSC_0825
作成物ではないのですが、自分の中では結構大事件だったので載せます。
これまでは自分はマイコンは一切使用しない回路ばかり作っていました。それは何故かと言うと元々自分はプログラミングアレルギー持ちで、プログラムを目にすると目のかゆみやくしゃみ鼻水蕁麻疹が発生し、プログラムを書こうものならアナフィラキシーショックを起こしてしまうためプログラムに関わると命の危険さえありました。
というのは冗談ですが、それほどプログラミングが超苦手でなおかつ大嫌いでした。
自分がこれほどまでにプログラミングを嫌いになるのにはいろいろな出来事があったのですが、ここで書くようなことではないですね・・・
それほどまでに嫌っていたプログラミングを克服出来たのがこの事件になります。
克服出来たのにもいろいろな理由がありましたが、それもここで書くようなことではないかな・・・
ちなみに写真はATtiny2313Aを使ったただのカウンタです。

DSC_0925
その後PICに手を出しました。2017年5月頃です。これはPIC16F873かな?確か時計を作ってた気がします。
他にもPIC16F877を使ってPCに使用されるファンの回転速度をPIDを用いて一定に制御するとかしてました。

DSC_1048
RXマイコンにも触手を伸ばしました。2017年6月頃です。昔ながらの8bitマイコンからいきなり高性能な32bitマイコンに手を出し若干戸惑った記憶があります。写真はRX621でI2C接続のOLEDグラフィックディスプレイに数字を表示させてみています。

DSC_1736
マイコン工作ではおそらくもっとも有名なArduinoUnoも使ってみました。これは2018年2月なのでちょっと前のことです。Arduinoは確かにとっても簡単で頭に浮かんだ動作をマイコンのペリフェラルをあまり意識することなくすぐコーディング出来ます。しかしPWM出力の周波数や分解能が気になったりGPIOの操作速度が気になったりすると結局レジスタを直接叩くことになります。
写真はArdinoUNO(のパチモノ)で温度を測定しています。

DSC_1748
で、現在はSTM32マイコンを使っています。STM32マイコンはおそらく世界中で最もスタンダードな組込み用CPUとなっているCortexCPUを搭載しており、またペリフェラルも非常に豊富で柔軟性が高く、高性能なのに価格が安いということで今後はこれを使っていきたいなと思っています。
ただ欠点は、日本語の情報が少ないことですかね。あとどうやらHALドライバにバグが多いようです。まぁなんとかなります。
ちなみに写真はADCしてDMA転送し、PWM出力をしています。



2017年5月頃
ブラシレスDCモータ用ドライバ回路(試作)
DSC_2008
確かこれが一番最初に作ったブラシレスモータードライバです。これ以前はブラシレスモータを回すのは何やらすごい難しいらしいという印象を持っており、ブラシレスモータを回すのに一種の憧れみたいなものがありました。しかし実際のところはホールセンサによってロータ磁界角度を検出する方法を用いて単純にモータを回すだけなら結構簡単にできるんですよね。
これはLB11620Tという専用のドライバICを使っています。



2017年8月頃
ブラシレスDCモータ用制御回路基板
DSC_1386
ブラシレスモータをもっと簡単に回せるように、また汎用性を持たせるためにドライバICと周辺回路をプリント基板に起こしたものです。ICはLB11697Vです。この回路にゲートドライバ回路と三相ブリッジ回路を追加するだけでブラシレスモータを回せるようになります。

DSC_1174
例えばこのような感じです。これはDC24Vくらいのモータを想定したパワー回路を接続しています。

DSC_2020
これは制御回路基板に汎用インバータのパワー回路部分を接続し、200Vの大型のブラシレスモータを回せるようにしたものです。写真のモータは外見がインダクションモータと同じですが中身はブラシレスモータです。
こんな感じで使用するモータに合わせてパワー回路部分を自由に設計することが出来ます。



2017年8月頃
15V4出力絶縁DCDCコンバータ
DSC_2015
UC3843を使用した絶縁コンバータです。
三相ブリッジのゲートドライバに電源を供給するために作成しました。一応1出力500mAは取れる設計ですが、出力の整流ダイオードにVfが1Vもあるダイオードを使ってしまったためにあまり効率はよろしくありません。
トランスの巻線には三層絶縁電線を使用しています。



2017年10月頃
282V出力アクティブPFC回路
DSC_2017
FAN6961を使用した電流臨界型力率改善回路です。出力電圧はDC282Vで確か350Wくらいで設計したと思います。
三相200Vで動作する機器をAC100Vで使用するために作成しました。




2018年1月頃
電池6V入力を想定した12V4A出力昇圧DCDCコンバータ回路
DSC_1681
NJM2360AD(MC34063互換)を使用した昇圧DCDCコンバータです。
実際に電池を電源にした時に最大負荷が取れるのかはさておき、設計上は12V4Aの出力が可能です。
効率はほとんどの負荷領域で85%以上、高いところでは90%くらいで割と優秀です。
負荷状況にもよりますが、一度昇圧を開始すると入力電圧1.2Vくらいまで下がっても出力電圧12Vを維持出来ます。



2018年2月頃
50WLED用12V入力昇圧定電流ドライバ回路
DSC_1715
UC3843を使用した昇圧タイプの50WLEDドライバ回路です。
外部からPWM信号を入力することでLEDの輝度を調整可能で、出力オープン時の過電圧保護回路も搭載しています。



2018年3月頃
高機能LEDチェッカー
DSC_1837
せっかくArduinoを買ったのだからなにか作って形にしたいということで出来上がったのがこれです。
出力電流を分解能1mAで最大60mAまで設定でき、LEDの順方向電流と順方向降下電圧を表示させることが出来ます。また、同時に設定した電源電圧で設定した電流を流すための抵抗値を計算して表示しています。抵抗値の表示を有効数字2桁でmΩ、Ω、kΩ、MΩ表記可能なのが小さいこだわりです。
また出力オープン時の過電圧保護と過電流保護機能も搭載しており、それぞれ設定可能になっています。

DSC_2024
定電流制御部分はArduinoではなくOPアンプで別に回路を組んでいます。Arduinoは遅いのでArduinoで定電流制御させようとすると多分安定動作しないと思います。

DSC_1775
電流精度もそこそこです。




現在作成中
HUB75フルカラードットマトリクスLED用SPIインターフェース
DSC_2029
作成といっても、ハードウェア的な要素はほぼなく殆どがプログラムの作成となっています。
HUB75というフルカラードットマトリクスLEDを制御するための規格?があるのですが、これは単純なシフトレジスタなため表示させ続けるためには常にデータを送信し続けなければならず、また階調表現も出来ないためこれを制御するのは結構厄介です。
そこでSPI接続のグラフィックディスプレイのように簡単に制御できるようにしようというのが目的です。
今の所、4096色表現可能で90FPSくらい出せています。FPSが多少犠牲になりますがスクロール表示も可能です。
実際にはFPSは60固定にし、輝度を調整できるようにする必要があります。
ドットマトリクスLED側に使っているマイコンはSTM32F103C8T6です。ラズパイでHUB75を制御している例は結構よく見ますが、秋月で一番安いZero WHでも2160円もします。リソースはマイコンに比べると圧倒的ですがやはりこの値段でドットマトリクスLED専用に組み込むのはちょっと勿体無いですよね。そこでもっと安いSTM32F103C8T6でも出来ないかといろいろやっているというわけです。
STM32F103C8T6が載っているBluePillなどと呼ばれているマイコンボードはAmazonでは1個500円程度、Aliexpressでは1個200円程度で販売されており他の用途に流用することなく基板に直接はんだ付けするような使い方でも惜しくないほど安いです。
SPI送信側のマイコンは何でも良いのですが、写真ではSTM32F401REが載ったnucleoボードを使用しています。



最後に今後作りたいもの(あくまで予定)

オーディオスペクトラムアナライザ
STM32マイコンで音声信号をAD変換し、CMSIS-DSPを使ってFFTしてドットマトリクスLEDに表示させる
ドットマトリクスLEDのSPIインターフェース作ってるのはこのためです。

ブラシレスモータのセンサレスFOC
STから最近出たSTM32 PMSM FOC Software Development Kitを使ってブラシレスモータのセンサレスFOC(磁界方向制御)をする
面白そう(小並感)
そもそもブラシレスモータというのは回転子の永久磁石による磁界方向と固定子のコイルによる合成磁界方向が直角に交わっている時に最大のトルクを発生しますが、ホールセンサで固定子の磁極を120度おきに検出してコイル電流を切り替えていたのではどうしても回転子の磁界方向と固定子の合成磁界方向が直角ではない期間が発生します。そのため、あまり効率がよろしくない(超適当(よく分かっていない))のですが、回転子の磁界をもっと細かく検出し、固定子の合成磁界が回転子磁界と常に直角になるように制御するのがFOCです。(というのが自分の理解ですが間違ってるかもしれないです)
回転子の磁界方向を細かく検出する方法ですが、エンコーダを使用するとコストがかかるためモータに流れる電流を検出し回転子の磁界方向を推定するのが今のトレンドのようです。
自分にはさっぱり分からないですね・・・
というのが正直な感想ですが、STmicroが1ヶ月ちょっと前にSTM32 PMSM FOC Software Development Kitなどというものを発表しました。
モータ制御の設計を迅速化・簡略化する新しいSTM32ソフトウェア開発キットを発表
これを使って環境を構築し、パソコンからポチポチするだけで簡単にブラシレスモータのセンサレスFOCができるようです。
これはぜひとも試してみたいというわけです。

まぁあくまで予定ですけどね。

wata_net at 23:21コメント(0) この記事をクリップ!
プロフィール

wata

Recent Comments
記事検索
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード
  • ライブドアブログ