と、ベタな文句しか出て来ないジジイと成り果てて居るが、ディスプレイ輝度の自動調整システムのプロトタイプが動き出したので、ちょっとうれしい。
先日検討して注文した光センサ基板(正確にはPCF8591 AD/DA基板, "YL-40"。以下、「基板」などと書く)が、数日前に届いた。中国からだが、今回は意外に速く、1週間くらいで届いた(と思ったが、実際には注文から2週間近く掛かった。1週間というのは、発送されてからだった)。EMS(かどうかは不明だが、郵便扱い)だと速いことがあるのかも知れない。
例によって非常に簡素な梱包で、基板の入ったビニル袋が汚くて、以前の汚ねえピンジャックを思い出したが、中身の基板はピカピカだったので安心した。これなら全然問題ない。早速電源を繋いでみたら、ちゃんと電源ランプが点いたので更に安心した。
ただ、一つ謎がある。電源スイッチ(下記の暫定接続ケーブルと一緒に付けた)を切った状態でも電源ランプが薄く点くのである。おそらく、I2Cの信号線(DataとClock)の電圧がIC PCF8591経由で漏れている(一種の逆流)のだと思う。余り良くない気がするが、壊れることはなさそうだ。
それに気を良くして、暫定版の接続ケーブルを作って※PCのVGAコネクタに繋いだら、Linuxのi2cdetectコマンドで以下のように基板のアドレス(0x48)に表示が出て、I2Cデバイスとして認識できていることが分かった。
$ sudo i2cdetect -y 2 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
※当初は、容易に延長できることやケーブルの細さ・柔からさなどを考えて、電話用のモジュラーコードを使おうと思って居たのだが、システム構成を確定させ、ある程度「行ける」ことが分かってから そこら辺をちゃんとしようと考え、まずは手持ちの長い(約3m)バラ線4本を(切らずに)そのまま使った(切ったら端切れが出来てしまって、最後に無駄になるので)。
同様に、VGAコネクタ(これを用意するのには なかなかの苦労があった。 → 下の「おまけ1」を参照)に電源スイッチを組み込もうと思って、結構苦労して付けられるようにしたのだが、実際にはコネクタがPC背面の込み入ったところにあるためにスイッチが操作しにくいから あっさり却下して、基板側のコネクタに仮付けした。
次に、I2C経由で基板上のADCとDACを動かしてみたら、これもあっさりとできた。i2cget, i2cset, i2ctransferコマンドを使うことで、基板上のADC(光, 温度, ボリュームが繋がっている)の値が読め、DAC(LEDが繋がっている)にアナログ電圧の出力ができた。
中国製品は当たり外れが激しいというのが常識だが、今回は当たりだった。きっと、この基板は、どこかのちゃんとした人が設計したもの(メーカーの評価基板?)が回り回って手に入るようになったのだろう。で、ADCやDACとは言ってもしょぼい簡素なものなので*、部品を載せて半田付けするだけで調整なしで動くから、部品が壊れてなくて半田が付いている限り、ちゃんと動くのだろう。
*簡素とは言っても、これがあれば、サウンドカードではできなかった、直流電圧が入出力できるから、PCでできることが格段に増える、超優れものだ。
基板に書かれている"YL-40"という型番(写真左下)がポイントで、Amazonで検索した時に(例)その番号が見え、"PCF8591 YL-40"で検索したら、Raspberry Piなどで使っている人が多いことが分かり、その関係の資料や情報が結構あったので、これを買うことにした。
ちなみに、買ったのはAmazonでなく、残ったポイントを使うために楽天にした。Amazonより高くて約700円だったが、ポイント利用で150円くらいになった。PCF8591が載っていて、外観が希望のものと同じで、一応 基板に"YL-40"が見えるものを選んだ。ただ、それには何の保証もなく、一種の賭けではあった。
余談ではあるが、明らかに勝算がなく、失敗した時の損失なども検討せずに 大言壮語してむやみに賭けをする大馬鹿者が居るが、言語道断だ。今回は150円だったから安心して賭けられたw
なお、基板には説明書も回路図も何も付いていないので、上のような情報がないと使うことはできないだろう。謎の多い基板だが、情報が多い点で当たりだった。ただ、これを何も知らない一般の方が買って どうにかできるものとは思えず、Raspberry Piなどで使った経験者が書いたものを参考にして使っているのだろうが、最初の方はどうしたのかなど、興味深い謎は多いw
それから、基板から取得した明るさの値からS170に設定する輝度を求めるための関係を調べるため、基板の光センサ(CdSセル)の特性を調べた。CdSセルの型番はもちろん分からず、データシートを参照できないためだ。基板はビニル袋に入れてS170の脚の上に設置した。ここだと机に向かった時に見えないのと、ディスプレイの熱が当たりにくいので良い。また、基板を垂直に近くして埃が溜まりにくくした。また、電源ランプが明る過ぎるので、テープを貼って隠した。
手元に光量(「照度」などいろいろあるが、正しい呼び方は不明)の分かる・設定できるライトなどないので、日光(カーテンも併用w)や部屋の照明(明るさが調整できる)を使い、基板から取得した明るさの値と、輝度が自動調整されるメインディスプレイ CX241のOSDに出る輝度と、それに合うサブディスプレイ S170の輝度を記録して、それらの関係を調べた。
足掛け2日くらい測って、関係が大体分かった。基板の明るさ値とS170の輝度(正確にはCX241の自動調整輝度に合う輝度)は2次曲線とか指数関数のような感じ(グラフの青線)だった。※ のだが、使ったLibreOffice Calcのそれらの近似では合わないので、とりあえず、イメージに近くなる3次の近似を使った。ただし、誤差や ばらつきの影響で、測定したデータを全部使うとまともな近似にならないので、「良さそうな」もの(イメージした曲線に乗っているもの)だけを選んで、その近似式を「作った」(近似式の実体は、グラフ上部の式か下記のコマンドの"be="以降を参照のこと)。
「なんだかなあ」だが、ちゃんとした使い方をすればできるのかも知れないし、自分で指数関数なりの近似式を求めればいいのだが、指数関数だと kAbx+C + D のように、底(A)や係数など(b, C, k, D)求めるべき項がいくつもあるし、そもそも底は何が適当なのか(e? 10? その他??)分からなかったのでパスしたw
※当初は、基板の明るさの値とS170の輝度の関係は単純な式(例: 直線)にはならないと思ったので、いくつかの測定値を用いて、LUT(変換テーブル)と線形補間にしようとしていた。が、線形補間が面倒なので、できれば単純な式にしたかった。それで測定を重ねてグラフを眺めたら、上のように指数関数的なことが分かった。
なお、CX241のOSDで表示される画面輝度(グラフの赤線)は遅延やバラつきがあるのか、今ひとつ基板の明るさ値との相関が悪かった。特に、基板の明るさ45-55辺りに対応する輝度に変化がないように思ったが、今見るとS170も同様の形状なので、基板の光センサ(CdS)の特性なのかも知れない。CdSについて調べた時に、前歴依存性というのが出て来たが、それだろうか? それとも、単にCX241の輝度調整特性(この辺りだけ、明るさへの追従が鈍い?)の問題だろうか?
試行錯誤の末にできたのが、この渾身の(怒涛かつ笑える)コマンドだ。たった(?)1行で、S170の輝度を、30秒ごとに、YL-40基板から取得した周囲の明るさに合わせて、CX241に合う明るさに自動調整する。
sudo sh -c 'proc_int=30; i2c_ch=2; yl40_addr=0x48; s170_hid=0; s170_FB=0x21; usbmc_cmd=~xxxxx/misc/usbmonctl/usbmonctl; sleep_t=0.2; while true do; date; d0=`i2ctransfer -y $i2c_ch w1@$yl40_addr 0 r1`; sleep $sleep_t; d=`i2ctransfer -y $i2c_ch w1@$yl40_addr 0 r1| sed -n "s/^0x//p" | tr a-f A-F`; echo "D0=$d" > /dev/null; di=`echo "ibase=16; $d"| bc`; echo "di=$di" > /dev/null; d2=`expr 255 - $di`; echo "L=$d2"; b_s170_e=`echo "be=0.0001972222 * $d2^3 - 0.009486435 * $d2^2 + 0.1930219 * $d2 + 13.56602; scale=0; bf10= (be*10)%10; if (bf10 >= 5) {bf= 0.5} else {bf= 0}; scale=3; be2= be - bf10/10; be2= be2 + bf; scale=1; be2/1.0" | bc -l`; b_s170=`$usbmc_cmd -g "F,$s170_FB" /dev/usb/hiddev$s170_hid | sed -nr "s/^([0-9]+) .+/\1/p"`; echo "Cur. S170 B=$b_s170"; echo "EB_S170=$b_s170_e"; b_s170_e_set=`echo "scale=0; $b_s170_e * 2 / 1.0" | bc -l`; echo "New S170 B=$b_s170_e_set"; $usbmc_cmd -s "F,$s170_FB=$b_s170_e_set" /dev/usb/hiddev$s170_hid; t0=`i2ctransfer -y $i2c_ch w1@$yl40_addr 1 r1`; sleep $sleep_t; t=`i2ctransfer -y $i2c_ch w1@$yl40_addr 1 r1 | sed -n "s/^0x//p" | tr a-f A-F`; ti=`echo "ibase=16; $t"| bc`; t=`expr 255 - $ti`; echo "T=$t"; echo; sleep $proc_int; done' |& tee -a ~/tmp/yl-40-lt-2021-07-30-1.log
※「プロト」だというのを免罪符に、シェルスクリプトにすらしていないし、無駄な処理が多い。 → さすがに起動が面倒なのでスクリプトにした時に、無害だけど誤りも見付けた。 (← 修正した。: 8/1 11:14)
簡単に処理の内容・動作の説明を書く。
- 動作条件などを設定する。: proc_int=30; ..の箇所
- 以下を繰り返す。: while true ..の箇所
- 現在の日時を表示する。: date; の箇所
- ADCの光センサを読む。: d0=`i2ctransfer と d=`i2ctransfer .. di=`echo の箇所
- 光センサのチャネル(AIN0)を読む。
- i2ctransferコマンドを使い、読み出すチャネルを指定してから読む。
- 変換時間の関係か、PCF8591のADCは前回変換した値が読み出されるため、少し(約0.2秒)間を開けて2回読んで現在の値を得る。
- 取得した値から明るさを求める。: d2=`expr 255 ..の箇所
- 値は反転している(明るいほど小さい)ので、最大値(255)から引いて反転させる。
- 明るさの値を表示する。: echo "L=$d2"; の箇所
- 明るさから近似式でS170に設定する輝度を求める。: b_s170_e=`echo .. be2= be2 + ..の箇所
- 設定する輝度は0.5単位なので丸める。
- 参考のため、S170の現在の輝度を取得する。: b_s170=`$usbmc_cmd ..の箇所
- usbmonctlコマンドを使う。
- 現在の輝度を表示する。: echo "Cur. S170 B= ..の箇所
- 上で求めたS170に設定する輝度を表示する。: echo "EB_S170= ..の箇所
- MCCS(DDC/CI)でS170に設定する輝度の値に変換する。: b_s170_e_set=`echo "scale= ..の箇所
- MCCSでの値は上の値を2倍した整数。
- MCCSとは書いたが、実際にはS170(M170もCX241も)は準拠していなくて、Featureの番号が全然違う。
- MCCSでの値は上の値を2倍した整数。
- S170に設定する輝度(変換後)を表示する。: echo "New S170 B= ..の箇所
- S170に輝度を設定する。: $usbmc_cmd -s ..の箇所
- usbmonctlコマンドを使う。
- S170の輝度はFeature 0x21で取得・設定可能。
- また、バックライトの輝度(推定)はFeature 0xceで取得可能。
- それらはM170では異なるし、CX241では不可。
- ついでにADCの温度センサを読む。: t0=`i2ctransfer -y .. ti=`echo "ibase=16 ..の箇所
- 光センサと同様に、温度センサのチャネル(AIN1)を2回読み、値を反転させる。
- 温度センサの値を表示する。: echo "T=$t"; の箇所
- 約30秒待つ。: sleep $proc_int の箇所
- 4から繰り返す。: done の箇所
実行すると、以下のように、基板から取得した明るさ("L=")、現在のS170の輝度("Cur. S170 B=")、明るさから計算したS170に設定する輝度("New S170 B")を表示する。なお、"EB_S170="はS170のOSDで表示・設定する輝度(上の測定や近似式を求める時に使った値)、"T="は基板に載ったサーミスタからの値(温度に依存する: ついでに出しているだけで、輝度設定には無関係 → この値と室温の関係は「おまけ4」を参照)である。
Fri Jul 30 21:00:12 JST 2021
L=33
Cur. S170 B=32
EB_S170=16.5
New S170 B=33
T=48
これを昨日の午後から試していた。周囲が明るい場合にS170が少し明る目だったので使うデータを調整して近似式を改良したあとは、夕方近くも夜も朝も概ね問題なく調整できて居たので、あとは(本当の)昼間も大丈夫なら、予想より楽に動いてしまったことになる。
ちなみに、普通に使っていて設定されたS170の輝度の範囲は15-23%程度で、カーテンを開けて明るくすると40%くらいになった。また、CX241が自動設定する輝度には下限があるようで、夜 照明を消しても15%程度だった。
まあ、それから「ちゃんと」作る訳で、そうすると途端に面倒なことがいろいろ出て来るので、いつものように誰かに「あとは頼んだ」と言いたいところだw
- 光センサ基板(PCF8591 AD/DA基板)が届いた。袋は汚なかった。
- が、基板はピカピカだった。
- 基板をPCに繋ぐための暫定ケーブルを作った。
- PCF8591 AD/DA基板をPCのVGAコネクタに繋ぎ、電源が入った。
- 光センサ基板の設置位置・方法を調整した。: S170の脚の上に置き、埃が溜まりにくいように垂直に近くした。
- S170輝度自動調整システムのプロトを上から見たところ。
- S170輝度自動調整システムのプロト: 前から基板は見えない。
- 光センサ基板の明るさ値とCX241の輝度とS170の合う輝度: 青: 基板-S170, 赤: 基板-CX241
- 光センサ基板の明るさ値とCX241に合うS170の輝度(抽出したもの)より近似式を作った。
- S170輝度自動調整システムのプロトを試用中。周囲の明るさに追従して、概ねCX241の輝度に合っている。通常(昼間)の場合
- 同、周囲が暗い場合 (照明を消し、カーテンを閉めた)
- 周囲が明るい場合は、S170が明る目になった。 → 近似式を調整した。
参考資料
主に以下が役に立った。
- Warren Gay: "Custom Raspberry Pi interfaces" (2017): Chap. 6 PFC8591 ADC
- 書籍だが、なぜか、ある大学のページ(工学部の学生実験用資料?)から参照できた。
- 回路図の(知識のある人なら、どうしたって間違いようのない)記号が誤っていたり(例: "VCC" → "YCC")、ちょっと変なところはあるが、随分参考になった。
- 使用されているプログラムのソース
- How to Interface PCF8591 ADC/DAC Analog Digital Converter Module with Raspberry Pi (2019)
- PCF8591 YL-40 AD DA Module Review (2012)
- PCF8591: An 8 bit ADC you can easily connect using I2C (2015?)
- John's Linux Blog: I2C Analog to Digital Converter (2012)
- LinuxのI2Cコマンド(i2cgetなど)を使う例
おまけ1: VGAコネクタの5Vピンを「何とか」した話
以前も書いたように、EIZOのディスプレイに添付のVGAケーブルには5Vのピン(ピン9)がないので、この用途には使えないことが分かった。が、VGAケーブルもコネクタも安くない。大体500円くらいする。中古屋も見たが、新品はやっぱりそのくらいだったし、ジャンク品ですら300円くらいだったので馬鹿らしくなった。テキトーな中古VGAケーブルなんて高くても100円くらいで買えると思ったが、今は世知辛いのか・・・
何とかならないものかとコネクタを眺めていたら、180°回転させればピン9が「できる」気がした。調べたら、180°回転させた状態でも、GNDやI2Cに使うピンも大丈夫そうだった。
早速工作した。それまでに、何とかしてピン9を追加できないかと思い、すごく苦労してコネクタをモールドから取り出して居たので、そのコネクタをシェルから外し、D形状の上部左右の角を削り、180°回転させてもシェルに入るようにした。更に、回転させたために中段の左端のピン(元のピン6)が余るので切った。どうにかできたものをPCに挿したら、見事に5Vが出た。
回転させたことで嵌ったのは、基板へのコードを繋ぐ時に中段のピンの番号を1つずらしていたために、最初は電源が出なかったことだ。上述の切ったピンに相当する「幻のピン6」を考慮していなかったためである。
それから少し綺麗にして、とりあえず完成となった。
なお、本文に書いたように、コネクタに電源スイッチを内蔵させたくて、モールドの一部を切ってスイッチを嵌め込めるようにもしたが、使いにくいので却下した。まあ、いくら苦労したって、「駄目なものは駄目」と捨てる勇気は重要だ。
- EIZOのVGAケーブルのコネクタ部は、予想以上に頑丈だった。 金属シェルの中にもモールド。。。
- EIZOのVGAコネクタは、9ピンに穴すら開いてなくて、全く使い物にならなかった。
- コネクタの底板?上部の角を削り、元のピン6(中段右端)を切り、180°回転させてシェルに納めた。
- 元のピン6(中段左端)を切った。
- 光センサ基板用VGAコネクタに電源スイッチを組み込めるようにした。
おまけ2: モジュラージャックを「何とか」した話
当初は電話線でVGAコネクタと基板を繋ぐことを考えていたので、モジュラージャック2個が必要だった。これは100円ショップで延長アダプタを買えば安いが、暇に飽かせて何とかした。
手持ちに電話用の3分岐のアダプタがあったので、それを「イッコニ化」wした(2個のジャックに分割した)。3分岐のために、丁度、2個のジャックからの線が使えるので良かった。ケースを切断し、少し綺麗にし、VGAコネクタに接着できるようにして完成となった。
これはまだ却下ではない。最終的に電話線で繋ぐ場合には生きる。が、何となく、そこまでする必要はなく、もっとテキトーに(例: 今のバラ線を少し綺麗にする。USBケーブルの線だけ使う。VGA側のジャックはなし)繋いでも充分な気がしている。
- モジュラージャックの2分岐アダプタを半分ずつに割り、2個のジャックにした。
- 分割したモジュラージャックの使用イメージ。左: PCのVGAコネクタ側, 右: 基板側
- 分割したモジュラージャックに側板を接着した。
- VGAコネクタにモジュラージャックを付けた場合のイメージ
おまけ3: M170の光センサ基板を単体で動かした話
寿命になったサブディスプレイ M170は、光センサ辺りが壊れたと思って居たが、実際には生きていた。ちょっと試してみたら、明るさに従った電圧が出るのを確認した。
接続コード(FFC)は6芯だが、2本ずつGND、電源、出力になっていた。電源は何Vか分からなかったが、基板にツェナーダイオードが載っていたので、多少高くても壊れないだろうと思って5Vを入れたら、ちゃんと動いた。: テスターで測ったら、明るさに比例した電圧が出て、光センサ(フォトダイオード)を塞いだ場合はほぼ0Vが、ライトで照らすとほぼ5Vが出た。
あの回路はフォトダイオードがLDOの電圧調整らしきピンに繋がっていて何とも謎なのだが、そういう使い方があるのか、実際にはLDOでない(I-V変換や温度補償素子?)のか。
光センサ基板が届く前は これを使おうと思って居たが、基板のセンサ(CdSセル)が結構使えそうだし、こちらだと温度特性が不明だしシステムのサイズが大きくなってしまうので、ひとまずは保留とした。
- M170の光センサ基板を動かしてみた。 部屋の明るさにて
- 同、プラ板で塞いだ場合
おまけ4: YL-40基板の温度センサ(サーミスタ)を試した。
輝度調整コマンド(その後、スクリプトにした)で ついでに表示していた温度センサ(サーミスタ)の値と その時の室温をグラフにしてみたら、測っている時からそんな気がしていたが、今ひとつ相関が悪い。
何となくリニアには見えるが、ばらつきが多い。ディスプレイの近くなので、その熱の影響があるのかも知れない。あと、サーミスタは電源ランプ(LED)のすぐ隣なので(ランプはCdSにも近いし、この配置は ひどい)、LEDの熱に影響されているのかも知れない。
まあ、これには期待して居なかったし、もう少し様子を見たり試してみたい。 (8/1 11:03)
PS. 気付いたら、前回の投稿から結構日が経っていた。何をしていたか記憶がないことはないが(とは言え、すぐには思い出せない)、意外に空いた。そうやってブログは廃れて行くのかも知れない・・・