AVRとLEDマトリクスで自然な階調表現
LEDマトリクスで画像の回転(90度単位ではない回転を含む)をする関係で自然な階調表現ができるプログラムを作りました。明るさに2値しか取れないと各種の補完アルゴリズムが使えず、回転してもまともな画像にならない。
使用した部品
すべて秋月電子で買いました。
- AVR ATmega326P-PU
- OSL641501-ARA
- 8x8ドットのLEDマトリクス 200円
- SN74HC595N
- 普通のシフトレジスタ 40円
回路
LEDへの電流の流れはシフトレジスタ側からマイコン側になっているので、シフトレジスタ側を1、マイコン側を0にすると点灯します。
階調表現の方法
シフトレジスタ側が、ある1列に1を出力している間に、マイコン側でその列の各LEDをPWMで制御する。
PWMのパルスを8分の1に分け、8段階の明るさを作る・・・とやってしまうと不自然になる。物理的な輝度と、心理的な明るさは比例しないので滑らかなグラデーションにならない。
人間の知覚に合わせた色空間であるCIE Labによると、明るさ: と輝度: の関係はザックリといえば下の式とグラフになります。
このグラフから、0付近では が少し変化すると は大きく変化する、1付近では が大きく変化しても の変化は小さい、と読み取れる。言い換えれば、暗部の変化に敏感で、明部の変化に鈍感だ、ということが言えると思います。今回は考慮しなかったが、実際は目の順応や光源によっても変化する。
実装
今回は明るさを8段階、パルス幅を16段階に分けて表現する。どちらも段数が少ないが、あまり多くしても他の処理ができなくなるので。
明るさ | パルス幅 |
---|---|
0 | 0 |
1 | 1 |
2 | 1 |
3 | 1 |
4 | 3 |
5 | 5 |
6 | 9 |
7 | 15 |
明るさ 1, 2, 3 は同じパルス幅になっているが、16段階での分解能ではこれ以上分けられないのでしょうがない。
等分割した場合
明るい方の違いがわからず、急に暗くなっていくように見える
波形
視覚特性を考慮した場合
5番目まではちゃんと階調が見える(カメラ越しでは分かりにくいが、肉眼ではもう少しよく見える)。
波形
プログラム
char bright_curve[8] = { 0, 1, 1, 1, 3, 5, 9, 15, }; void refresh_display() { for (short line = 0; line < 8; line++){ // 1列目の時だけシフトレジスタに1を入力 if (line == 0){ PORTB |= _BV(PORTB3); }else{ PORTB &= ~_BV(PORTB3); } // シフトレジスタの内容を一つずらす PORTB |= _BV(PORTB1); PORTB &= ~_BV(PORTB1); PORTB |= _BV(PORTB2); PORTB &= ~_BV(PORTB2); // LEDのPWM制御 // forループの周期が一定とみなし、タイマを使わずにPWMを出力する // 内蔵タイマを使えば正確な波形が出せるが、他の処理に割ける時間が減ってしまう for (short bright = 0; bright < 16; bright++){ for (short num_led = 0; num_led < 8; num_led++){ short value = p_frame_buffer[num_led * 8 + line]; if (bright_curve[value] > bright){ PORTD &= ~_BV(i); //点灯 }else{ PORTD |= _BV(i); //消灯 } } } PORTD = 0xff; //全消灯 } }
Conclusion
人間の視覚特性に合った明るさで表示することは少しできたが、PWMの分解能が16段階では不十分で、暗部の表現がかなり限られている。
次回は「AVRとLEDマトリクスで画像を回転」の予定です。