寒い朝

コンピュータ系のことを書きます

AVRとLEDマトリクスで自然な階調表現

f:id:microwaver17:20161113214948j:plain

LEDマトリクスで画像の回転(90度単位ではない回転を含む)をする関係で自然な階調表現ができるプログラムを作りました。明るさに2値しか取れないと各種の補完アルゴリズムが使えず、回転してもまともな画像にならない。

使用した部品

すべて秋月電子で買いました。

  • AVR ATmega326P-PU
  • OSL641501-ARA
    • 8x8ドットのLEDマトリクス 200円
  • SN74HC595N

回路

f:id:microwaver17:20160829001443p:plain:w500

LEDへの電流の流れはシフトレジスタ側からマイコン側になっているので、シフトレジスタ側を1マイコン側を0にすると点灯します。

階調表現の方法

シフトレジスタ側が、ある1列に1を出力している間に、マイコン側でその列の各LEDをPWMで制御する。

PWMのパルスを8分の1に分け、8段階の明るさを作る・・・とやってしまうと不自然になる。物理的な輝度と、心理的な明るさは比例しないので滑らかなグラデーションにならない。

人間の知覚に合わせた色空間であるCIE Labによると、明るさ: L と輝度: Y の関係はザックリといえば下の式とグラフになります。

f:id:microwaver17:20160826000208p:plain:w400

このグラフから、0付近では  Y が少し変化すると  L は大きく変化する、1付近では  Y が大きく変化しても  L の変化は小さい、と読み取れる。言い換えれば、暗部の変化に敏感で、明部の変化に鈍感だ、ということが言えると思います。今回は考慮しなかったが、実際は目の順応や光源によっても変化する。

実装

今回は明るさを8段階、パルス幅を16段階に分けて表現する。どちらも段数が少ないが、あまり多くしても他の処理ができなくなるので。

明るさ パルス幅
0 0
1 1
2 1
3 1
4 3
5 5
6 9
7 15

明るさ 1, 2, 3 は同じパルス幅になっているが、16段階での分解能ではこれ以上分けられないのでしょうがない。

等分割した場合

明るい方の違いがわからず、急に暗くなっていくように見える

f:id:microwaver17:20160829203207j:plain:w400

波形
@grid on bright_7 __~~~~~~~~~~~~~~__ bright_6 __~~~~~~~~~~~~____ bright_5 __~~~~~~~~~~______ bright_4 __~~~~~~~~________ bright_3 __~~~~~~__________ bright_2 __~~~~____________ bright_1 __~~______________ bright_0 __________________
視覚特性を考慮した場合

5番目まではちゃんと階調が見える(カメラ越しでは分かりにくいが、肉眼ではもう少しよく見える)。

f:id:microwaver17:20160829203205j:plain:w400

波形
@grid on bright_7 __~~~~~~~~~~~~~~~__ bright_6 __~~~~~~~~~________ bright_5 __~~~~~____________ bright_4 __~~~______________ bright_3 __~________________ bright_2 __~________________ bright_1 __~________________ bright_0 ___________________
プログラム
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マトリクスで画像を回転」の予定です。