ドット液晶 (128Wx64H) PC-PZ60D を使ってみる


    2006.05.02 〜2007.03.29

お知らせ

 ・ 最新版

以下の内容は作業した記録(のようなモノ)をだらだらと書きつづっただけのものです。作業が進むにつれ、それまで進めてきた内容に「ありゃりゃぁ、こりゃ失敗やったぁ〜」というのがたくさんあります(笑)。その時点では「まっ、こんなモンにしとこか?」と作ってはみたものの、少し後になって「げげげっ!」と慌てて修正した部分が・・・・・。

で、最後まで読んで初めて最初の方のアノ内容はちょっとマズイらしい…となったら、そりゃあんまりじゃん! …ということで、回路図やソフトの最新版へのリンクを作ってみました。

 回路図  H8/3694版(PDF) 2007.03.17 (57KB)  (別窓で開く)
 回路図  H8/3664版(PDF) 2007.03.17 (57KB)  (別窓で開く)
 ソフト  H8/3694(20MHz)版 [ PCPZ60soft.lzh ] 2006.09.21 (172KB)  (内容)
 ソフト  H8/3664(20MHz)版 [ LCD3664a.lzh ] 2006.12.18 (36KB) 3694版からの変更部分のみ

 ・ BUG 2007.03.21現在  (把握できていないヤツがまだまだあると思いますが・・・)

H8/3694版(2006.09.21)には以下の BUG を確認しています。H8/3664版(2006.12.18)では修正済です。
Time/DIV を 50mS 以上へ設定した時、25mS 時と同一のサンプルレートになります。
原因:タイマWのクロックを設定している部分 (PCPZ60.C の 590行付近、SampleTimeTabl[ ] の定義内容) にミスがあります。
なるべく早く修正する予定なのですが・・・

 ・ 未 ・・・  (作成の予定はあるのですが・・・)

H8/3664 の 16MHz 版 (3664 の最高周波数は 16MHz ですから・・・)

全てのソフトで AUTO Triger モード時、トリガがかかりません。(トリガロジック未実装…)
 ( NORMAL Triger モードはエッジドリガで動作します)

変更

 ・ 型式 PC-PZ60D  ( 写真などは こちら

  2006.05.08  負電源のチャージポンプを2段から3段へ(電圧が少し足りなかった…)
  2006.05.09 「ハードウェアの構成を考える」一部修正 垂直タイミングの追加など
  2006.05.10 「ハードウェアの見直し」追加
  2006.05.13 「ハードウェアの構成を考える」FS のタイミング変更
  2006.05.20 「表示フォントを考える」FONT8Hr.lzh 変更(左右逆並びを修正)
  2006.05.29 「グラフィック関数…」Cソース差替え(テスト用を UP してました)
  2006.06.20 「オシロのスケール表示…」表示変更に伴いソース差替え(少し速く)
  2006.06.24 「オシロの波形表示…」リングバッファのポインタ変更(ひとつ後へ)
  2006.07.07 プログラムの関数名、変数名などの一部を変更(あちこちの矛盾を少し修正)
  2006.08.14 「水平ライン転送プログラム」変更(変な記述部分を修正)
  2006.09.16 「水平ライン転送プログラム」変更(高速サンプル時のノイズ低減)
  2007.03.21 回路図変更(主にチャージポンプ部分)


作業予定の項目 1〜13 2006.05.02  (14〜以降 は後から追加)

  1.ドット液晶について
  2.ハードウェアの構成を考える
  3.フレームレートを考える
  4.表示フォントを考える
  5.負電圧を考える
  6.基本部分を組立てる
  7.何か表示してみる
  8.文字を表示してみる
  9.グラフィック関数を考える
  10.オシロのスケール表示を考える
  12.オシロの波形表示を考える
  13.A/Dサンプルを考える

  14.画面書換え時間とタイミングを考える
  15. 最終章?「お礼」(とプログラム)
  16. 番外編1 画面のノイズを何とかしたい・・・
  17. 番外編2 H8/3664(SDIP:16MHz)版
  18. 気になっていたハードウェア部分 (BUG?) を修正

ドット液晶について

 ・ 型式 PC-PZ60D  ( 写真などは こちら

大阪の日本橋にある、とあるパーツショップ(デ*ット)で売られているドット液晶( 2006.05.02 現在 \180)です。国内大手メーカ M 社の製品ですが「ジャンク品扱い」(たぶん新品)です。とっくに製造中止のようですが、類似品なら探せばありそうな気がします。

近くでは類似品も手に入らない…けど、どうしても入手したい…という方がおられたら、掲示板かメールでお知らせください。多少の手持ちがある間ならお分けできるかも知れません。

 ・ 表示ドット数  128W x 64H

モノクロ、2諧調表示です。バックライトは付いていません。

 ・ ドライバIC  LC7942, LC7940 (SANYO)

7942 が1ヶ、 7940 が2ヶ実装されています。データシートはwebで探してください。(簡単に見つかると思いますので)

 ・ 駆動信号

表示データ(2階調なので '0' or '1')は、水平の1ライン単位でシリアルに入力します。他にはクロック信号、水平のリセット(水平同期?)信号、フレームのリセット(垂直同期?)信号の 「 4本 」 のみです。 概略は こちら をご覧下さい。

 ・ 負電圧

最近のドット液晶は +5V だけで動くものがほとんどと思いますが、コイツは古い(10年以上昔?の)タイプで、負電源を用意しないといけないようです。その電圧は -10V 〜 -11V 程度と思われますが、消費電流は少ないハズなので +5V からマイナス側へのチャージポンプでOKでしょう。コントラスト調整ボリウムは液晶本体へ付いていますので、固定電圧でOKです。

ハードウェアの構成を考える

とりあえず H8Tiny 3694 を使う予定で各項目を検討していきます。
 ・ VRAM その1

16桁×2行などの「キャラクタ液晶」はアスキーコードを書き込めば、それをフォントに展開して液晶に表示してくれました。しかし、ドット・グラフィック液晶はそういう機能がありませんので「全てのドットの白/黒を指定」してやる必要があります。

基本的にはパソコン用の「CRT/液晶モニタ」と同じですので、「何を表示するのか?」という情報は液晶の外部に持っておいて、表示のタイミングに合わせて液晶へ送ってやる事が必要です。「どうやって送るのか?」というのは後回しにして、とりあえず「表示する情報を持つ」事を考えます。

表示ドット数は 128×64 = 8192 ドット で、2階調(白か黒)表示なので 1ドット=1ビット を割当てて、 8192 ビット = 1K バイト が必要な VRAM の容量になります。

「最低」でも 1K バイト のメモリを VRAM として用意する必要がある…という事になります。

本当は「変更作業用のバッファ ( 1K バイト) 」も欲しいのですが、H8/3694 などは内蔵メモリが 2K バイトしか無いので、その全てを VRAM とそのバッファに使ってしまうと他の作業が何もできません。バッファの確保をあきらめるか? H8Tiny をあきらめるか? まっ、バッファは無くても何とか動くハズですので、とりあえずバッファはあきらめるという事で進めます。

・・・、デジタルオシロのようにひんぱんに画面を書換える場合、書換中の画面を表示することになり、画面の書換えシーケンスを工夫しないと、書換えのタイミングで表示がチラついたりコントラストが落ちたり…という事になりそうです。そういう書換えには「小細工」が必要になるかも知れません。

H8/3694 の内蔵 RAM から 1K バイト を VRAM として割当てることにします。
 ・ 表示データの(液晶への)転送

表示データは水平(横)ライン1本ぶんを「一定時間間隔ごとに」まとめて転送する必要があります。64本全ての水平ラインを転送したら1フレーム(画面)の終了です。

最初の水平1ライン(一番上の横ライン)を転送する時は次のような感じです。

  
  (この図は、液晶画面 128W x 64H をイメージしたものです)

転送は

  CK : データクロック
  SD : シリアルデータ入力
  HS : 水平データのセット(水平同期?)
  FS : フレームリセット (垂直同期?)


の各信号線を使い、次のようなタイミング となります
              │←←←←←←← 1本目 →→→→→→→│← 2本目
              │ dot1    dot2    dot3           dot128│
      ┬───┬───┬───┬───┬──‥┬───┬───┬──
  SD  ┴───┴───┴───┴───┴──‥┴───┴───┴──
      ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
  CK  ┘ └─┘ └─┘ └─┘ └─┘ └‥┘ └─┘ └─┘ └
       ┌─┐                 ┌─┐
  HS  ─┘ └───────────────‥─┘ └───────
           ┌─────────────‥────┐
  FS  ─────┘                  └──────
水平ラインの信号は CK 同期(立下りエッジ)で 128 ドットぶんをシリアルに入力し、最後の( 128 ドット目の)表示ドットのデータに重ねて HS が出力されます。 FS は最初の水平ラインの間、ONになります。

これらのタイミング作成は 「 ソフトウェアのみ 」 で行う ことを考えます。
しかし、ソフトだけでは処理時間の心配があり、間に合わなければ PLD や FPGA など、外部のハードウェアに頼るしかありません。 「 処理時間 」 の検討は次項で行います。

 ・ VRAM その2

ソフトによる処理時間の検討の前に、VRAM のメモリと、液晶の表示ドット位置の対応を決めておきます。

 画面のドット位置
  ┌───────────────────────────┐
  │┌───┬───┬───┐     ┌───┬───┐│
  ││ 1,  1│ 1,  2│ 1,  3│・・・・・│ 1,127│ 1,128││
  │├───┼───┼───┘     └───┼───┤│
  ││ 2,  1│ 2,  2│             │ 2,128││
  │└───┴───┘             └───┘│
  │  ・                     ・  │
  │  ・                     ・  │
  │┌───┐                 ┌───┐│
  ││64,  1│・・・・・・・・・・・・・・・・・│64,128││
  │└───┘                 └───┘│
  └───────────────────────────┘
   左上のドットを [1,1] として、右下のドットを [64,128] とします。
   (ドット位置の表記は '0' ではなく '1' からが一般的なようです)

VRAM は、水平線単位で転送するため、表示画面の「横方向」を同一バイトへパッキングします。一番上の水平ラインをバイト単位で色分けすると次のようになります。16バイト/水平ライン です。

  

VRAM の1バイトは、 b0 をドット位置番号の小さいものへ割当てます。
VRAM の最初の1バイト(と2バイト)の割当ては次のようになります。
  VramTop + 0                                       VramTop + 1
 ┌──┬──┬──┬──┬──┬──┬──┬──┐┌──┬──┬──┬…
 │1, 1│1, 2│1, 3│1, 4│1, 5│1, 6│1, 7│1, 8││1, 9│1,10│1,11│…
 └──┴──┴──┴──┴──┴──┴──┴──┘└────────┴…
   bit0  bit1  bit2  bit3  bit4  bit5  bit6  bit7    bit0  bit1  bit2

フレームレートを考える

 ・ フレームレートとは

通常、1秒間の画面書換え回数をフレームレートと呼んでいます。 この回数が一定以上なら人間の目ではチラツキを感じなくなるそうです。日本や米国の(地上波アナログ)テレビは、30フレーム/秒(実際は「飛び越し操作」というトリック?で60フレーム相当)です。ヨーロッパでは25フレーム/秒(同様に50フレーム相当)です。

理想は 60フレーム/秒!、 とりあえず、30フレーム/秒 を目標とし、
最低でも 15フレーム/秒 を目指す事にします。

で、もしソフトによるタイミング作成がこれでは間に合わない場合、外部に PLD とか FPGA が必要になってしまいます。(ソレはなるべく避けたい…のですが…)

 ・ 「あたり」を付ける

今回の計画では「ソフトによるタイミング作成」が間に合うかどうか? …というのは、ハードウェアの構成を左右する程の大問題であり、「後で何かあったらソフトで対応」という手段は取れません! この問題がクリアにならないと、ハードウェアの設計ができない!わけです

で、「仮定の(実現可能な)」ハードウェアを用意して、ちょっと調べてみよう…と。

CPU (ボード)は、秋月で一番安い? H8/3694 ( AE-3694, \1650 : 2006.05.04 現在) を前提にしていますが、H8 Tiny (300H) であれば、そのまま当てはまると思います。 CPU クロックは 20MHz で考えます。

 ・ ポートの割当

CPU は H8/3694 (Tiny) で、各制御信号をどのポートへ割当てるか? …ですが、速度を考えると 「全ての信号を同一ポートに割当てる」 のが有利です。というのは「タイミングの変更結果を一度で出力できる」からです。なにせ 8192 ビット も出力しますので、例え1命令(出力ポートへデータ転送)の増加でもその影響は「8000倍!」になってしまいますから・・・。

で、とりあえず手元にころがっている AE-36xx (秋月の H8/3694 基板)で、テスト用に組んだモノをを流用(!)するとして、その空きポートを探した結果、ポート1(同一ポートならどのポートでもOK)へ割当てることにしました。(当面、外部割込み IRQ0-3 を使う予定は無い?ので)
  P14 : CK データクロック
  P15 : SD シリアルデータ入力
  P16 : HS 水平データのセット(水平同期?)
  P17 : FS フレームリセット (垂直同期?)


 ・ 水平ラインの転送時間

液晶の縦方向表示ドット数は 64 ですので、30フレーム/秒なら、
毎秒 64ライン × 30フレーム = 1920回 の転送が必要になり、
許される最大処理時間は 520μS という事になります。15フレーム/秒なら、その倍です。

 ・ 仮想コーディング

実際の処理時間がどの程度になるのか? ・・・ これは、余程の達人でもない限り予想は難しそうです。もちろん達人には程遠い私に予想ができるハズも無く、もう「仮想的にコーディングしてみて」その結果を見るしかありません。まだ「実機で動かす」事はできませんので、BUGがまぎれ込んでいても発覚しませんから、注意して作業します…。

それにしても・・・、コーディングから実行時間がわかる!ってのはいいですね。キャッシュとかややこしいパイプラインとかがある「最近の CPU 」は走らせてみないとわからない場合が多く、こういう小さい部分でも正確な実行時間を求めるのは決して簡単ではありません。そういう意味で 時代遅れのロートル(言葉が古い!)職人 にとって H8 は 手に馴染む 道具?なのかも知れません。

という事で、以下、試しに水平ライン転送の主な部分を書いてみました。
        MOV.B   #16,R2H             ; 2  16byte/Line
?200:                               ; ----- Byte Loop -----↓↓↓↓↓
        MOV.B   @ER6,R0L            ; 4  VRAM data
        INC.W   #1,R6               ; 2  VRAM address
        MOV.B   #8,R2L              ; 2  8dots
?300:                               ; ----- dot Loop -----↓↓↓↓↓
        MOV.B   @LCD_Port:8,R1H     ; 4
        BSET    #LCD_CK,R1H         ; 2  CK=Hi
        MOV.B   R1H,@LCD_Port:8     ; 4             out
        SHLR.B  R0L                 ; 2  CY = dot (ON/OFF)
        BST     #LCD_SD,R1H         ; 2  set SD
        DEC.B   R1L                 ; 2  Last Dot ?  (of H-Line)
        BEQ     ?400                ; 4           NO then JP
        MOV.B   R1H,@LCD_Port:8     ; 4             out
        BCLR    #LCD_CK,R1H         ; 2  CK=Lo
        MOV.B   R1H,@LCD_Port:8     ; 4             out
        DEC.B   R2L                 ; 2  8bits (1byte) All ?
        BNE     ?300                ; 4
                                    ; ----- dot Loop -----↑↑↑↑↑ 36
        DEC.B   R2H                 ; 2  1Line End ?
        BNE     ?200                ; 4
                                    ; ----- Byte Loop -----↑↑↑↑↑ 4832
                                     ↑
                               実行ステート数
これを見ると、1ドットの出力に要するステート数は 36 になっています。

1バイトの展開と出力には 8 ビット x 36 = 288 ステート
1バイトの読出しなどには          14 ステート
ですので、1バイトの処理には       302 ステート が必要です。


水平ラインは16バイトですので、全部で 16 x 302 = 4832 ステート となり
1ラインの送出に要する時間は 約242μS となります。 ( CPU 20MHz )

これなら、その他の雑処理?などを含めても300μSもあれば充分ですから、
30フレーム/秒はクリアできそう です。

ということで、液晶の駆動タイミング制御はソフトで行う 事にきめます。実際には タイマ割込みで1ライン転送関数を起動する わけですが、所詮ソフトの動作ですので実行タイミングに「ゆらぎ」(ジッタ)が出るのは避けられません。 結果、 表示画面がちらつく ことになると思います…。どの程度のちらつきになるのか? 何とか見られる程度だとは思うのですが・・・。

表示フォントを考える

 ・ 漢字?

漢字のフォントを自分で作成された方もおられるかも知れませんが、私にはちょっと無理な気配です。仮にフォントがあっても H8/3694 のフラッシュ ROM (32KB) には格納スペースがありません。

という事で、漢字の表示はパスします。
表示は半角英数字のみですが、キャラクタフォントを作ればグラフィックキャラクタが使えます。

 ・ フォントのドット数

縦は 8ドット/文字 で問題ないと思いますが、横のドット数は少し考えてしまいました。というのも「キャラクタ液晶」では「20 桁」というのは普通に売ってますが、このドット液晶だと、横8ドット では 16 桁 しか表示できません( 128dot ÷ 8 = 16 ) 。 また、正方形画素だと 8 x 8 の半角文字は「間延び」した感じになってしまいます。
 □□□■■□□□□■■■■■□□ 1 □□□■□□□■■■■□□
 □□■□□■□□□■□□□□■□ 2 □□■□■□□■□□□■□
 □■□□□□■□□■□□□□■□ 3 □■□□□■□■□□□■□
 □■□□□□■□□■■■■■□□ 4 □■□□□■□■■■■□□
 □■■■■■■□□■□□□□■□ 5 □■■■■■□■□□□■□
 □■□□□□■□□■□□□□■□ 6 □■□□□■□■□□□■□
 □■□□□□■□□■■■■■□□ 7 □■□□□■□■■■■□□
 □□□□□□□□□□□□□□□□ 8 □□□□□□□□□□□□□
 1234567812345678   1234561234561
 └──────┘└──────┘   └────┘└────┘
で、見栄えと 20 桁 の表示を目指して 横 6ドット のフォント にしてみます。
キャラクタ液晶も、そのフォントは 横5 x 縦7 ですので、文字間スペースの1ドットを考慮すると、キャラクタ液晶と同一のフォントとなります。

この事で必然的に 文字の展開が VRAM のバイト境界をまたぐ 事になり、文字を展開する VRAM の位置計算が複雑になりますが、横8ドット のフォントでも 「 任意のドット位置から描画 」 しようとすればバイト境界をまたぐのは避けられませんから、大差はありません。

ということで、 表示フォントは 横6 x 縦8 に決定です。(文字間のスペースを含む)

 ・ フォントの構成

横方向へ連続して文字を表示した時、文字どうしの表示が 「 くっつかない 」 ように、フォントの一番左の1ドットはスペースにしておきます。でも、グラフィック画面へ文字を表示するときは、その背景がどうなっているのか不明ですので 文字の右側もスペースにしておく と見やすくなります。例えば、黒の背景へ 'H' の文字をスペースを取らずにそのまま表示すると・・・
 ■■■■■■■■■■            ■■■■■■■■■■
 ■■■■■■■■■■            ■■■■■■■■■■
 ■■■■■■■■■■   □■□□□■   ■■□■□□□■■■
 ■■■■■■■■■■   □■□□□■   ■■□■□□□■■■
 ■■■■■■■■■■   □■□□□■   ■■□■□□□■■■
 ■■■■■■■■■■   □■■■■■   ■■□■■■■■■■
 ■■■■■■■■■■ + □■□□□■ = ■■□■□□□■■■
 ■■■■■■■■■■   □■□□□■   ■■□■□□□■■■
 ■■■■■■■■■■   □■□□□■   ■■□■□□□■■■
 ■■■■■■■■■■   □□□□□□   ■■□□□□□□■■
 ■■■■■■■■■■            ■■■■■■■■■■
 ■■■■■■■■■■            ■■■■■■■■■■
となって、ちょっと見づらい感じになります。
で、フォントを 「 横7ドット 」 にして一番右のドットをスペースにすると
 ■■■■■■■■■■■             ■■■■■■■■■■■
 ■■■■■■■■■■■             ■■■■■■■■■■■
 ■■■■■■■■■■■   □■□□□■□   ■■□■□□□■□■■
 ■■■■■■■■■■■   □■□□□■□   ■■□■□□□■□■■
 ■■■■■■■■■■■   □■□□□■□   ■■□■□□□■□■■
 ■■■■■■■■■■■   □■■■■■□   ■■□■■■■■□■■
 ■■■■■■■■■■■ + □■□□□■□ = ■■□■□□□■□■■
 ■■■■■■■■■■■   □■□□□■□   ■■□■□□□■□■■
 ■■■■■■■■■■■   □■□□□■□   ■■□■□□□■□■■
 ■■■■■■■■■■■   □□□□□□□   ■■□□□□□□□■■
 ■■■■■■■■■■■             ■■■■■■■■■■■
 ■■■■■■■■■■■             ■■■■■■■■■■■
となります。
文字を横に連続して表示する場合、7ドット目(一番右:スペース)へ次の文字の1ドット目(一番左:スペース)を重ねて表示すれば、結果は「横6ドット」になります。

縦方向の一番下は、一部の小文字とカーソル用に空けておきます。

実際には、2進数で動く CPU にとって ‘7’ というのはキリが悪いので、もう1ドット(のダミーデータ)を足して 横8 x 縦8 のフォントデータ とします。( VRAM 展開時にダミーの1ドットを捨てます)

 ・ フォントのデータ構造

VRAM からの出力は 「 水平ライン単位 」 で行われるので、水平方向のドットが連続している(同一のバイト内に存在する)のが都合がいいため、以下のような(バイトへの)パッキングとします。
  01234567
 ┌────────┐
 │□■□□□■□□│ → これを1バイトへパッキング(左側が bit0 )
 └────────┘    このバイトデータは 0x22 となります。

  □■□□□■□□ → 1バイト目 0x22   □■□□□■□□ → 2バイト目 0x22   □■□□□■□□ → 3バイト目 0x22   □■■■■■□□ → 4バイト目 0x3e   □■□□□■□□ → 5バイト目 0x22   □■□□□■□□ → 6バイト目 0x22   □■□□□■□□ → 7バイト目 0x22   □□□□□□□□ → 8バイト目 0x00
で、データの表現としては
  struct ascii_font {
      :
      0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00,   // 'H'
      :
  } AsciiFont ;
もしくは
    .DATA.B    H'22, H'22, H'22, H'3e, H'22, H'22, H'22, H'00    ; 'H'
となります。

 ・ フォントの作成 その1 

私が以前から使っているのは 「 フォントのデータをキャラクタで描いておいて、専用プログラムでアセンブラのソースへ落とす 」 というものです(大昔に作ったヤツなので…、今ならアセンブラは有り得ない?)。中心となる部分は次のような感じです。(ソース ANK8Hr.lzh 31KB 2006.05.20 )
/* ------------------------------------------------------------------------ */
/*  ANKフォントファイルを読み出してアセンブラのソースへ変更する          */
/* ------------------------------------------------------------------------ */
u_char      src_file[] = "ANK8H.FNT";   // アセンブラソース ファイル名
u_int       AAA = 0x81 + (0x97 * 256);  // 全角「@」  sjis=8197, jis=2177
u_int       ZSP = 0x81 + (0x40 * 256);  // 全角「 」  sjis=8140, jis=2121
#define     BUF_LEN     80+2            // 行バッファ長
int  main (int argc, char *argv[])
{
    int     i, j, row_dat, row_ct;
    char    font_file[80];                      // ファイル名
    char    font_buf[9+1][BUF_LEN];             // フォント マップデータ
    union   font {
                    u_char  bufc[BUF_LEN];
                    u_int   bufi[BUF_LEN/2];
            } font_wk;
    FILE    *fp_i, *fp_o;
    printf("Font file (8x8) --> %s (for Assemble)   05.01.13 Jaku.\n", src_file);
    if (argc != 2) {                /* コマンド・ライン パラメータの確認   */
        printf("\x1b[32m\n");
        printf("独自フォントファイルを読み出してアセンブラ用のソースファイルを出力する。\n");
        printf("独自フォントファイル(8×8ドット)のフォーマットは以下の通り。\n");
        printf("\n");
        printf(":30                    (←アスキーコード)\n");
        printf("  @@@   ; 0    (表記内容は全角文字)\n");
        printf(" @   @  ; 1\n");
        printf(" @  @@  ; 2\n");
        printf(" @ @ @  ; 3\n");
        printf(" @@  @  ; 4\n");
        printf(" @   @  ; 5\n");
        printf("  @@@   ; 6\n");
        printf("        ; 7\n");
        printf("\n");
        printf("使い方: A>\x1b[mFONT8H  ANK5FONT.MAP\n\x1b[32m");
        printf("\n");
        printf("  この例だと フォントファイル「 ANK5FONT.MAP 」を読み出して、アセンブラの\n");
        printf("  ソースファイルへ変更し、「 %s 」として出力する。\n", src_file);
        printf("  注:出力ファイル名は  %s  固定\n", src_file);
        printf("\x1b[m\n");
        return (1);
    }
    strcpy(font_file, argv[1]);                         // フォントファイル名
    if ( (fp_i = fopen(font_file, "r")) == NULL ) {
        printf("\x1b[31mfile not found %s\x1b[m\n", font_file);
        return (-1);
    }
    if ( (fp_o = fopen(src_file, "w")) == NULL ) {
        printf("\x1b[31mfile not open %s\x1b[m\n", src_file);
        return (-1);
    }
    row_ct = 0;
    while ( (fgets(font_buf[row_ct], BUF_LEN, fp_i)) != NULL ) {
        printf("\x1b[34m%s\x1b[m", font_buf[row_ct]);
        if (font_buf[row_ct][0] == '\n')    continue;   // RETのみの行は読飛ばし
        if (font_buf[row_ct][0] == ':')     row_ct = 0; // ASCII コードの行
        if (row_ct < 8) {                               // 1文字分のフォントデータ読込
            row_ct++;
            continue;
        }
        for (row_ct=1; row_ct<=8; row_ct++) {       // フォントマップ書き出し
            if (row_ct == 1)    fprintf(fp_o, "\n");
            fprintf(fp_o, "     ; %s", font_buf[row_ct]);
        }
        for (row_ct=1; row_ct<=8; row_ct++) {       // 上からデータ1行づつ処理
            strcpy (font_wk.bufc, font_buf[row_ct]);
                                                    // ---- マップ→ビット展開
            if (row_ct == 1)    fprintf(fp_o, " .DATA.B ");
            row_dat = 0x00;
            for (i=0; i<8; i++) {
                row_dat = row_dat >> 1;         //  ドット並び −MSBが右−
                if ( font_wk.bufi[i] == AAA)    row_dat += 0x80;
                //  row_dat = row_dat << 1;     //  ドット並び −MSBが左−
                //  if ( font_wk.bufi[i] == AAA)    row_dat += 0x01;
            }
            fprintf(fp_o, "H'%03X", row_dat);
            if (row_ct < 8)     fprintf(fp_o, ",");             // 最終桁以外
        }
        fprintf(fp_o, " ; %s", font_buf[0]);
        row_ct = 0;
    }
    fprintf(fp_o, "%c", '\x1a');
    fclose(fp_i);
    fclose(fp_o);
    return (0);
}
 ・ フォントの作成 その2

しかし! 世の中にはやっぱりスゴイ人がいるわけで、以下は 「 H8メーリングリスト」 の書込みで教えてもらったものです。千葉さんとおっしゃる方です。
#define  s  ((((((((0
#define  X  )*2+1
#define  _  )*2
    unsigned char ANKFONT[]={
        s _ _ _ X X _ _ _ ,  // 00 font1
        s _ _ X _ _ X _ _ ,  // 01
        s _ _ X _ _ X _ _ ,  // 02
        s _ _ X X X X _ _ ,  // 03
        s _ X _ _ _ _ X _ ,  // 04
        s _ X _ _ _ _ X _ ,  // 05
        s _ X _ _ _ _ X _ ,  // 06
        s _ X _ _ _ _ X _ ,  // 07

s _ X X X X X _ _ , // 00 font2 s _ X _ _ _ _ X _ , // 01 s _ X _ _ _ _ X _ , // 02 s _ X X X X X _ _ , // 03 s _ X _ _ _ _ X _ , // 04
....以下すきなだけ、1文字につき8行で
}; #undef s #undef X #undef _
プリプロセッサ をこういう風に使うなんて ・ ・ ・  私もこういうセンスが欲しい・・・
(いつも余りにも 「 どんくさい 」 ので・・・)

負電圧を考える

 ・ 電源はどうする?

最近は +5V 単一電源で動くものがほとんどみたいですが、この(ジャンク品扱いの)液晶はちょっと古く?て、負電源がないと動かないようです。 どの程度の電圧で動くのか?調べた結果は -10V 程度かな?というものでした。

で、その電源をどうやって用意するか? 一番簡単なのは 電池 (006P) ですが、本体の電源とは別に 「 スイッチ 」 が必要になりますし、その入り/切りのタイミングがちょっと心配です。専用の電源を用意するのは大変ですし、 「 スイッチ 」 の問題が残ります。

消費電流は極小のハズですので、74HC シリーズのインバータでチャージポンプを考えてみます。

   

こんな感じで何とかなりそうです。

入力の方形波は CPU のタイマから適当な周波数 (数十 KHZ ) を出力すれば問題無いと思います。コンデンサは私の在庫が沢山ある1μにしていますが、 CPU が出力する周波数を適当に選べば 0.1μ 〜 10μ 程度の範囲で使えると思います。

ただし、 CPU の出力端子はドライブ能力が不足しているケースがあることと、 CPU の GND へ余り電流を流さないため、 CPU 出力は一度 74HC などで受けて( CPU の負荷を軽くして)から使った方がいいケースも多いと思いいます。どうするかはケースバイケースですが・・・。( H8/3694 の P76 なら初段の HC04 を省いて CPU 直接でもちゃんと動きます)

RS232C 用に MAX232 とかを使用していれば、その負電圧( MAX232 なら 6番ピン)がそのまま使えるかも?知れません。(ちょっと苦しいかな?)

 ・ 負電圧の 「 放電スイッチ 」

液晶用の負電源は消費電流がとても少ないこともあって、そのままだとメインの電源が落ちた後も、コンデンサにはずぅっ〜と -13V とかが溜まったまま ・ ・ ・ という事になりかねません。 で、 CPU の電源が落ちたときは、すみやかに負電源を放電するのが安心です。

先の回路へ 「 放電スイッチ? 」 を追加すると

  

こんな感じでしょうか? “ ON ” は CPU の適当な出力ポートへ接続しておき、ポートの入力モード、もしくは Low 出力で、負電源の放電モードとなります。負電源の出力を有効にするには “ ON ”に Hi レベルを出力します。( +5V 電源へ接続してもいいのですが・・・)

CPU のピンへ「定格を超えた負電圧」が印加される危険はあるのですが、壊れる可能性はまず無い?と思われますので、アマチュアレベルとしてはこれでいいかな?と考えています。(もちろん、プロには許されるハズもありませんが・・・  心配な方は +5V へ接続して下さい)

これで液晶の負電源は全て CPU からコントロールできることになります。(その必要性は別にして…)

基本部分を組立てる

 ・ CPU と液晶の接続など

接続と言っても、液晶だけですので簡単です。液晶の各信号線はそのまま H8-CPU のピンへ接続し、負電源( -10V 程度)はチャージポンプで作成します。チャージポンプの起動は H8/3694 の タイマV出力 TMOV (P76) で行います。負電源の NO/OFF は CPU の P75 へ接続していますが、不安な方は VCC への接続でいいと思います。電源電圧 VCC は +5V です。



 ・ チャージポンプの動作確認

組立てたら、チャージポンプの部分の動作をチェックしてみます。 CPU へ簡単なプログラムを用意して、 P76 へ方形波を出力して観察します。駆動周波数は約 5KHz にしてみました。

注意が必要なのは「チャージポンプの駆動(方形波出力)中は決して P75 を Lo レベルとか、入力モード( Hi Z )にしない」事です。ソレをすると、74HC04 の出力をトランジスタでショートすることになります。
void main(void)
{
    InitialIO();            // 内臓I/O初期化
    P75 = 1;                // 負電源出力 ON
    while (1) {             // メインループ
        dword   i;
        dword   n;
        P75 = 1;            // 念のため
        n = 75;             // チャージポンプ駆動周波数を決める…適当に
        while (--n > 0) ;
        P76 = ~P76;         // 方形波作成
    }
}
次の図は各部の波形です。
  A:  CH1  黄色  チャージポンプ一段目出力    2V/div
  B:  CH2  水色     〃   二段目出力    2V/div
  C:  CH3  紫色     〃   三段目出力    2V/div
  D:  CH4  緑色  最終出力                    2V/div
  時間軸は 25uS/div

無負荷 液晶を接続
     

無負荷で約 -13V 、液晶を接続した状態で約 -12V ですので、問題なく使ええそうです。ただ、最終出力を見ると少し電圧が下がっているようなので。駆動周波数をもう少し上げた方がいいと思われます。
 ・ チャージポンプの放電確認

負電源出力の放電を調べてみました。まず、チャージポンプの駆動(方形波出力)を停止し、その後に P75 を Lo レベルへ落とします。
void main(void)
{
    InitialIO();                    // 内臓I/O初期化
    P75 = 1;
    while (1) {                     // メインループ
        dword   i;
        dword   n;
        for(i=10000; i != 0; i--) { // チャージポンプ動作期間
            P75 = 1;                // 念のため
            n = 75;                 // 駆動周波数を決める 適当に
            while (--n > 0) ;
            P76 = ~P76;             // 方形波作成
        }
        n = 75; while (--n > 0) ;   // 時間待ち
        P75 = 0;                    // 負電源出力 OFF
        n = 3000000; while (--n > 0) ; // 負電源放電期間
        P75 = 1;                    // 負電源出力 ON
    }
}
次の図は各部の波形です。
  CH1  黄色  負電源出力制御     (P75)    5V/div
  CH2  水色  チャージポンプ一段目出力    2V/div
  CH4  緑色  最終出力                    2V/div

250mS/div 500uS/div
     

ちょっと時間はかかっていますが、何とか放電しているようです。

何か表示してみる

 ・ 固定データでテスト画面表示

とりあえず「何でもいいから?」表示するために、画面へ固定データを転送してみます。

VRAM とかの設定は手間なので、表示データは定数の配列で用意します。そして、全ての水平ラインのデータを用意するのは面倒なので、一種類の水平ラインのデータを全ラインへ表示することにします。かなりの手抜きですが、とりあえず 「 表示動作が確認できればよし 」 ということで・・・。あっ、 「 I/Oは私のテスト用基板に合わせて初期化してあります 」 ので、お手持ちの環境に合わせて変更してください。
//  PC−PZ60D(ドット液晶  128W x 64H )  2006.05.07

#include "3694Cnst.h" // 基本定数定義 #include "3694Mmap.h" // 内臓I/O
#define H_DOT_MAX 128 #define V_DOT_MAX 64
#define LCD_CK P14 #define LCD_SD P15 #define LCD_HS P16 #define LCD_FS P17
void DrvH(byte); // 水平ライン テスト駆動 void StartTMV(void); // タイマV カウント開始 void initialTMV(void); // タイマV 初期化 void VeeOn(void); // 負電源 出力 ON void InitialIO(void); // 内臓I/O初期化
void main(void) { InitialIO(); // 内臓I/O初期化 VeeOn(); // 負電源 出力 ON StartTMV(); // タイマV カウント開始 while (1) { byte b; for (b=0; b < V_DOT_MAX; b++) { DrvH(b); // 水平ライン テスト駆動 } } } // ----------------------------------- // 水平ライン テスト駆動 // ----------------------------------- // Max.Max 0.0 0.1 0.2 0.3 0.Max 1.0 1.1 // ┬───┬───┬───┬───┬──‥┬───┬───┬─ // SD ┴───┴───┴───┴───┴──‥┴───┴───┴─ // ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─ // CK ┘ └─┘ └─┘ └─┘ └─┘ └‥┘ └─┘ └─┘  //  ┌───┐               ┌───┐ // HS ─┘   └─────────────‥─┘   └──── //   ┌───┐ // FS ──┘   └────────────‥──────────
const byte Hdata[] = { 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // 0 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 } ;
void DrvH(byte b) { byte h; byte *p;
p = Hdata; // 表示データ
LCD_CK = 0; LCD_SD = 0; LCD_HS = 0; LCD_FS = 0; for (h=0; h < H_DOT_MAX-1; h++) { LCD_CK = 1; LCD_SD = *p++; LCD_CK = 0; } // ---- 最後のドット LCD_CK = 1; if (b==V_DOT_MAX-1) LCD_FS = 1; // 最後の水平ライン LCD_SD = *p; LCD_HS = 1; LCD_CK = 0; LCD_HS = 0; LCD_FS = 0; }
void VeeOn(void) // 負電源 出力 ON { P75 = 1; }
void StartTMV(void) // タイマV カウント開始 { initialTMV(); }
void initialTMV(void) // タイマV 初期化 { TCRV0 = 0x01; TCSRV = 0x03; TCORA = 0xff; // タイムコンスタント A TCORB = 0xff; // タイムコンスタント B TCNTV = 0; // タイマV カウンタ }
void InitialIO(void) // 内臓I/O初期化 { PMR1 = 0x00; // ポートモード (端子機能選択) PDR1 = 0x00; // ポート出力データ PCR1 = 0xf4; // ポート入出力  0:入力, 1:出力 PUCR1 = 0x0f; // 入力プルアップMOS 1:ON PDR2 = 0x00; // ポート出力データ PCR2 = 0x00; // ポート入出力  0:入力, 1:出力 PMR5 = 0x00; // ポートモード (端子機能選択) PDR5 = 0x00; // ポート出力データ PCR5 = 0x00; // ポート入出力  0:入力, 1:出力 PDR7 = 0x00; // ポート出力データ PCR7 = 0x70; // ポート入出力  0:入力, 1:出力 PDR8 = 0x20; // ポート出力データ PCR8 = 0x20; // ポート入出力  0:入力, 1:出力 }
これで、何とか「縦線」だけですが表示できました。

液晶テスト画面1

動作波形は次の通りです。
  CH1  黄色  FS (P17)   5V/div
  CH2  水色  HS (P16)   5V/div
  CH3  紫色  SD (P15)   5V/div
  CH4  緑色  CK (P14)   5V/div

1uS/div 100uS/div
     

左は 「 一番下の水平ラインの最終ドット(フレーム最終ドット)付近 」です。
右は「水平ライン2本ぶん」で、1本の転送は約470μSです。

 ・ ここでちょっとハードウェアの見直しを・・・

実は・・・、このページを作成することになったのは 「 ¥180 のジャンク液晶の存在を、とある方のホームページの掲示板へ書き込んだ 」 のがきっかけなんです。そこで話題が少し盛り上がった頃、(酔った勢い?で)「えいやぁ!」と作成を開始して…、 CPU は、秋月の安いヤツというだけで H8/3694 に決めたのがちょっとした?不注意の始まりでした(笑)。

酔った勢い?(笑)もあって、あまり細かい事は抜きにして「とりあえず何か表示」しようと進めてきたのですが、この期に及んで?不都合点が色々と発生してしまいました。
とりあえず、「何かを表示する」事は何とかできたので、さて、次へ・・・と思ったら、なんとまぁ根本的な問題点が色々と出てきてしまった…という、何ともみっともない展開です。

まず、 タイマが3ヶしか無い 事をちゃんと検討していませんでした。(「きょうび」の CPU でタイマが不足するなんて… 普通のアプリでは有り得ない!と思い込んでいたんです)何とも情けない限りですが、タイマの使用予定は次の通りです。

  1)液晶水平ライン毎の転送スタートタイミング検出(割込)
  2)デジタルオシロ(もどき)用A/Dサンプル周期決定
  3)標準タイマ(割込:通常 10mS )
  4)負電源チャージポンプ駆動

で、 H8/3694 には、タイマA、タイマV、タイマW の3ヶしか無く、その割当ては必然的に決まってしまいます。

  タイマW A/Dサンプル周期駆動(16ビットカウンタが必要)
  タイマV 液晶水平ライン転送起動(正確な周期が必要)
  タイマA 正確な 10mS は無理でも「タイマ割込」は必要

と、全てのタイマが使用済となってしまい、負電源チャージポンプを駆動するタイマはもう残っていません!

仕方が無いので、チャージポンプの駆動は水平ラインの駆動に追加して行うことを考えます。ただし、水平ライン転送周期は 520uS (30frames/S) ですので、駆動周波数が 1KHz 程度になってしまい、チャージポンプのコンデンサが 1μ では不足で 10μ 程度に変更しておく必要があります。



もしくは、 CPU からの駆動は止めて、シュミットなどの発信回路でトリガするように変更するか…です。その場合、放電回路の入力は +5V (Vcc) へ接続して下さい。

でも、こうした場合、 電源がONの間、液晶にはずっと負電源がかかった状態 になります。フラッシュROMへの書込み中も含めて、液晶へのデータ転送が止まる時間が長くなる時は「負電源をOFFにする」方が安心かも知れません。まぁそうしなくても、すぐに壊れることは無さそうですが・・・。


もう一つ、 H8/3694 は タイマからA/Dのトリガができない 事を知りました(この期に及んで・・・笑)。うぅ〜ん、いまどき?こういう CPU がまだあるんだ… と、自分の検討ミスを棚に上げてため息をついてしまいました。

で、仕方が無いので、タイマWの出力 FDIOD(P84) を ADTRG(P55) へ( CPU の外部で)接続して使うことにします。 ・ ・ ・ とまぁ、早くも満身創痍状態になってしまいました(笑)。

 ・ まだあった!勘違い!!!

なんと、「 自分のホームページへ載せたドキュメント 」 通りのタイミングで作成したソフトの動作がおかしい!(水平ラインの表示が1本ズレる…) なぜ?????

なんと、タイミングのドキュメントが間違っていました!
ドライバICのタイミングを見直すと、今まで載せていたドキュメントは「別物」でした。(なんと情けない! で、こっそり?直しました:笑)

間違えていたのは FS のタイミングで、正しくは 「 FS は最初の(一番上の)水平ラインのデータ転送中は H になる 」 というもので、以下が正しいタイミングです。
              │←←←←←←← 1本目 →→→→→→→│← 2本目
              │ 1dot    2dot    3dot           128dot│
      ┬───┬───┬───┬───┬──‥┬───┬───┬──
  SD  ┴───┴───┴───┴───┴──‥┴───┴───┴──
      ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
  CK  ┘ └─┘ └─┘ └─┘ └─┘ └‥┘ └─┘ └─┘ └
       ┌─┐                 ┌─┐
  HS  ─┘ └───────────────‥─┘ └───────
           ┌─────────────‥────┐
  FS  ─────┘                  └──────
尚、 FS の ON 幅は、最初の水平ラインの表示の1ドット幅だけでもOKです。
( CP の立下りエッジで確定)

 ・ もう一つ・・・

負電源のチャージポンプを CPU 外部の発振器で駆動した場合、電源ONの間はずっと液晶へ負電源がかかったままとなります。通常はこれで何の問題も無いのですが、デバッグやフラッシュROMへの書込みでで 水平ラインの転送を止めた時 などは負電源をOFFにした方が安心です。

そうなると、負電源は CPU からコントロールした方が都合がよくなります。で、またまた配線の変更をしたのでした・・・。

 ・ ちゃんとした?関数を作ってみる

なんだかんだ…と、何回も「ふりだし」へ戻りながら(笑)何とか書いた「水平ラインの転送関数」です。これでやっと VRAM へ書いたものが液晶画面へ表示できるようになりました。
// PCPZ-60D  水平ライン転送関数テスト  2006.05.13

void main(void) { word w; initial_io(); // 内臓I/O初期化 initial_LCD(); // LCD 初期化 for (w=0; w< 1023; w+=65) { Vram[w] = 0x55; } Vram[1023] = 0xaa; while (1) { byte b; for (b=0; b< V_DOT_MAX; b++) { Drv_PCPZ60D_H(); // LCD Hライン駆動 } } }
; ============================================================================
VRAM_TOP: _Vram: .RES.B VRAM_LEN ; VRAM _VramAdr: .RES.W 1 ; 転送中の VRAM アドレス
; -------------------------------------------------------------------- ; LCD Hライン駆動 ; -------------------------------------------------------------------- ; レジスタ使用一覧 ; R0L : VRAM data [ B7 --> B0 ] --> CY --> LCD ; R1H : Port-data to @LCD_Port:8 ; R1L : dot counter H_DOT_MAX --> 0) ; R2H : byte counter 16(bytes/line) --> 0 ; R2L : bit counter 8 --> 0 ; R6 : VRAM address ; -------------------------------------------------------------------- ; ポート割付け LCD_Port .EQU PDR1 LCD_CK .EQU 4 LCD_SD .EQU 5 LCD_HS .EQU 6 LCD_FS .EQU 7
_Drv_PCPZ60D_H: PUSH R2 PUSH R6 MOV.B #H_DOT_MAX,R1L ; Dot counter MOV.W @_Vram_ADR,R6 ; H-Line Top MOV.B @LCD_Port:8,R1H ; Port data CMP.W #VRAM_TOP,R6 BNE ?100 BSET #LCD_FS,R1H ; set FS ON ?100: MOV.B #16,R2H ; 16(bytes) x 8(bits) = 128dots / line ?200: ; ----- Byte (8dots) Loop ------------ MOV.B @ER6,R0L INC.W #1,R6 MOV.B #8,R2L ?300: ; ----- Dot Loop --------------------- BSET #LCD_CK,R1H ; set CP MOV.B R1H,@LCD_Port:8 ; out SHLR.B R0L ; CY = dot (ON/OFF) BST #LCD_SD,R1H ; set SD DEC.B R1L ; Last Dot ? (dot counter) BNE ?400 ; NO then JP BSET #LCD_HS,R1H ; Last Dot (HS) ON ?400: MOV.B R1H,@LCD_Port:8 ; out BCLR #LCD_CK,R1H ; set CK-Lo MOV.B R1H,@LCD_Port:8 ; out DEC.B R2L ; 8bits (1byte) All ? ----- Dot Loop - BNE ?300 DEC.B R2H ; 1Line End ? ------------ Byte Loop - BNE ?200 BCLR #LCD_SD,R1H ; clear SD BCLR #LCD_HS,R1H ; clear HS MOV.B R1H,@LCD_Port:8 ; out BCLR #LCD_FS,R1H ; clear FS MOV.B R1H,@LCD_Port:8 ; out CMP.W #VRAM_TOP+VRAM_LEN-1,R6 ; VRAM End ? BCS ?600 ; NO then JP MOV.W #VRAM_TOP,R6 ?600: MOV.W R6,@_VramAdr ; H-Line Top (for next transefer) ; ------------------------------------ BNOT #6,@PDR7 ; DC−DC制御(−10V:LCD用) ?900: ; ------------------------------------ POP R6 POP R2 RTS
ちょっと見づらいのですが…、液晶の画面はこんな感じです。

液晶テスト画面2

動作波形は次の通りです。
  CH1  黄色  FS (P17)   5V/div
  CH2  水色  HS (P16)   5V/div
  CH3  紫色  SD (P15)   5V/div
  CH4  緑色  CK (P14)   5V/div

最初の水平ライン開始付近 最終と最初の水平ライン 2.5uS/div 50uS/div
     

心配していた水平ライン転送の 実行時間 ですが、224uS 程度で収まっています。
転送処理で、バイトからドットへの展開をループで行っていますが、これをループではなく(マクロの展開などで)ベタで書けば、あと 40uS 程度は短くなりそうです。

実機では、水平ライン転送の駆動は メイン関数からではなく、520uS 毎のタイマ割込 で行います。

 ・ Cで書いてみる

次のフォントの展開へ・・・と思ったのですが、アセンブラでこれだけいけるんなら、Cでも何とかなるのでは?…と、書いてみる事にしました。

で、書いてみたところ、 Cで書いても実行時間はほとんど変わらない! という事になりました!!! 意気込んで書いたアセンブラは何だったんだぁ〜 と、何だか「時代遅れのアセンブラを中心に」というここのホームページの趣旨をカンプ無きまでに打ちのめしてくれた日立のコンパイラでした。老兵は消え去るのみ ・ ・ ・ という言葉が浮かんできて、ロートルエンジニアは黄昏を感じるのでした。(笑)
// PCPZ-60D  水平ライン転送関数テスト  2006.05.15

#define VRAM_TOP 0xf800 #define VRAM_END 0xfbff #define LCD_Port PDR1 #define CK_ON 0x10 #define SD_ON 0x20 #define HS_ON 0x40 #define FS_ON 0x80 #define CK_OFF 0xef #define SD_OFF 0xdf #define HS_OFF 0xbf #define FS_OFF 0x7f
void DrvH2() { byte out; byte dotct; byte vd; byte *p;
dotct = 128; // dot counter out = LCD_Port; // out data p = VramAdr; // 転送する水平ラインの VRAM アドレス if (p == (byte *)VRAM_TOP) out |= FS_ON; // Top ラインなら FS ON LCD_Port = out; while (dotct) { // 全ドット転送ループ byte i; vd = *p++; // VRAM data for (i=0; i<8; i++) { out |= CK_ON; // CK Hi if (vd % 2) out |= SD_ON; // set SD else out &= SD_OFF; vd /= 2; if (--dotct == 0) out |= HS_ON; // HS Hi LCD_Port = out; out &= CK_OFF; // CK off LCD_Port = out; } } out &= HS_OFF; LCD_Port = out; out &= FS_OFF; LCD_Port = out; if (p > (byte *)VRAM_END) p = (byte *)VRAM_TOP; VramAdr = p; // 転送する水平ラインの VRAM アドレス // P76 = !P76; // 負電源チャージポンプ駆動  P76 = ~P76; // 負電源チャージポンプ駆動 2006.08.14 }
; ============================================================================ 以下はコンパイラの出力を整理したものです。
_DrvH2: ; function: DrvH2 PUSH.W R5 MOV.B #-128,R5H ; dotct = 128; MOV.B @65492:8,R0L ; out = LCD_Port; MOV.W @_VramAdr:16,R1 ; p = VramAdr; CMP.W #-2048,R1 ; if (p == (byte *)VRAM_TOP) BNE L48:8 BSET.B #7,R0L ; out |= FS_ON; L48: MOV.B R0L,@65492:8 ; LCD_Port = out; BRA L50:8 ; while L49: MOV.B @ER1+,R5L ; vd = *p++; MOV.B #8,R0H ; for (i=0; i< 8; i++) { L51: BSET.B #4,R0L ; out |= CK_ON; BTST.B #0,R5L ; if (vd % 2) BEQ L52:8 ; else BSET.B #5,R0L ; out |= SD_ON; BRA L53:8 L52: BCLR.B #5,R0L ; out &= SD_OFF; L53: SHLR.B R5L ; vd /= 2; DEC.B R5H ; if (--dotct == 0) BNE L54:8 BSET.B #6,R0L ; out |= HS_ON; L54: MOV.B R0L,@65492:8 ; LCD_Port = out; BCLR.B #4,R0L ; out &= CK_OFF; MOV.B R0L,@65492:8 ; LCD_Port = out; DEC.B R0H ; i++; BNE L51:8 L50: MOV.B R5H,R5H ; while (dotct) { BNE L49:8 BCLR.B #6,R0L ; out &= HS_OFF; MOV.B R0L,@65492:8 ; LCD_Port = out; BCLR.B #7,R0L ; out &= FS_OFF; MOV.B R0L,@65492:8 ; LCD_Port = out; CMP.W #-1025,R1 ; if (p > (byte *)VRAM_END) BLS L55:8 MOV.W #-2048,R1 ; p = (byte *)VRAM_TOP; L55: MOV.W R1,@_VramAdr:16 ; VramAdr = p; BNOT.B #6,@65498:8 ; P76 = ~P76;
POP.W R5 ; } RTS
最適化の前は 900uS 程度だったのですが、「スピードの最適化」を ON にすると、上のコードを出力し、結果は 236uS 程で動作しています。アセンブラ記述との差は 5% 余り で、これなら C で充分です。

P76 の定義は次のように書いています。
struct bit {
    unsigned char   b7  : 1;
    unsigned char   b6  : 1;
    unsigned char   b5  : 1;
    unsigned char   b4  : 1;
    unsigned char   b3  : 1;
    unsigned char   b2  : 1;
    unsigned char   b1  : 1;
    unsigned char   b0  : 1;
} ;
#define PDR7_BIT    (*(volatile struct bit *)0xFFDA)
#define     P76     PDR7_BIT.b6
でも、コンパイラの最適化スイッチには賛否両論あって簡単には決められないかも知れませんね。私はこの結果を見て小さいプログラムならコンパイラのスイッチに頼ろうか?とよろめいてしまいました。(最適化スイッチの中には「安全な最適化」なんてのもあったりして… ちょっと考えてしまうのも確かですが:笑)

でも、初志貫徹?で、水平ラインの転送関数はアセンブラ記述で行く ことにします。

文字を表示してみる

 ・ フォントの表示位置

文字の表示位置は「ドット単位で指定」できるようにしたいと思います。というのも、この液晶は 128W x 64H で画面が狭く、グラフィック表示に文字を重ねる場合、文字位置がドット単位で指定できないと不便と思われるからです。また、文字フォント(の横幅)を6ドットにしたため、どの道バイト境界だけの処理では済まないこともあります。

で、表示のイメージ(例)として 14ドット目 から表示する場合の水平ラインを考えてみます。
                                  ここから表示
                             1         ↓          2                   3
  Dot位置  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
          ┌┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬
  VRAM    │      +0      │      +1      │      +2      │      +3
          └┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴
                                       0 1 2 3 4 5 6
                                      ┌┬┬┬┬┬┬┐
  一文字目                            │ ■■■■■ │  ■ は字体の実体
                                      └┴┴┴┴┴┴┘
                                                   0 1 2 3 4 5 6
                                                  ┌┬┬┬┬┬┬┐
  二文字目(連続表示の場合)                      │ ■■■■■ │
                                                  └┴┴┴┴┴┴┘
元のフォントデータの横方向は8ビットですが、展開は7ビットで行います。
『表示フォントを考える』 参照 )

フォントの字体は左の1ビットと右の2ビットがスペースとなっていて、左(LSB)から7ビットを展開すれば、左右の1ビットが空白で、中心の5ビットが字体の実体となります。連続して表示する場合、前の文字の7ビット目(スペース)と新しい文字の1ビット目(スペース)を重ねて表示することで、文字どうしは6ドット間隔で並びます。

上記の場合、二文字目は20ドット目から、三文字目は26ドット目からとなります。

なんと言っても「面倒」なのは 1文字のフォントを数ビットづつ2バイトの VRAM へ展開する必要がある という事でしょうか。

まぁ「(液晶の画面で)結果が見える」ので、デバッグは難しくない(?)だろう…と、深く考えずに展開の関数を書いてみます。

 ・ 表示関数(アセンブラ版)
        ; --------------------------------------------------------------------
        ; ドット位置指定 6ドットピッチ 1文字表示 2006.05.20
        ; --------------------------------------------------------------------
        ; CALL  R0 : 表示キャラクタコード ASCII (0x20 - 0x7F)
        ;       R1L: 表示位置 X (ドット位置: 0 - H_DOT_MAX-1 )
        ;       R1H:      Y (ドット位置: 0 - V_DOT_MAX-1 )
        ;
        ; 文字の表示位置は「文字の左上」の点(座標)で指定する
        ;
        ;  ■□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;  □□□□□□□
        ;
        ; void  disp_ank6c(unsigned int ascii, unsigned char x, unsigned char y)

_disp_ank6c: PUSH R2 PUSH R3 PUSH R4 PUSH.L ER5 PUSH.L ER6 MOV.W #VRAM_END,E5 ; E5 : #VRAM_END MOV.W #H_DOT_MAX/8,E6 ; E6 : #16 ; ----- check Range X,Y -------------- CMP.B #H_DOT_MAX-7,R1L ; x BCC ?900:16 CMP.B #V_DOT_MAX,R1H ; y BCC ?900:16 ; ----- check disp-char ASCII ? ------ CMP.B #H'80,R0L ; Ascii ? BCC ?900:16 ; NO then JP SUB.W #H'0020,R0 ; Offset of Font-Tabel BCS ?900:16 ; NO then JP ; ----- get Font-table address ------- SHLL.W R0 ; R0 x 8 SHLL.W R0 SHLL.W R0 ADD.W #_FontTbl8ankL,R0 ; R0 : Font-table address ; ----- get VRAM Address ------------- MOV.B R1H,R6L ; [Y]: Line offset MOV.B #0,R6H SHLL.W R6 ; R6 x 16 (16bytes / H-line) SHLL.W R6 SHLL.W R6 SHLL.W R6 MOV.B R1L,R2L ; [X]: dot position SHLR.B R2L ; 1/8 (dot --> byte) SHLR.B R2L SHLR.B R2L ; R2L: byte(s) offset from Line-top MOV.B #0,R2H ADD.W R2,R6 ; R2 : byte(s) offset from Line-top ADD.W #VRAM_TOP,R6 ; R6 : VRAM address (LSB byte) AND.B #B'00000111,R1L ; R1L: dot(s) offset from byte-boundary ; ----- make VRAM Mask --------------- MOV.W #H'007F,R5 ; expansion is 7bits ('1'=mask) MOV.B R1L,R1H ; dot(s) offset from byte-boundary BEQ ?250 ?200: SHLL.W R5 DEC.B R1H BNE ?200 ?250: NOT.W R5 ; R5 : VRAM Mask data (new Font field) ; ===== Copy Font to VRAM ============ MOV.B #8,R2L ; R2L: V-dots ( Font data = 8bytes ) ?300: ; ===== copy H-Line Loop ============= MOV.B @ER0+,R3L ; Font data MOV.B #0,R3H ; R3 : Font data MOV.B R1L,R1H ; dot(s) offset from byte-boundary BEQ ?400 ?350: SHLL.W R3 ; R3 : Font data (add offset) DEC.B R1H BNE ?350 ?400: MOV.B @ER6,R4L ; VRAM (LSB) (don't read Word !) MOV.B @(1,ER6),R4H ; R4 : VRAM data AND.W R5,R4 ; Mask OR.W R3,R4 ; set New Font MOV.B R4L,@ER6 ; to VRAM (LSB) (don't write Word !) MOV.B R4H,@(1,ER6) ADD.W E6,R6 ; R6 : VRAM next Line (pointer) CMP.W E5,R6 ; VRAM End ? BCC ?900 ; YES then JP DEC.B R2L ; R2L: V-dots ( Loop count ) BNE ?300 ; ============= copy H-Line Loop ===== ?900: POP.L ER6 POP.L ER5 POP R4 POP R3 POP R2 RTS
これで何とか表示できるようです。適当に文字を表示してみました。表示位置がドット単位なので「一部を重ねて表示」とかもできます。

液晶テスト画面3

1文字の展開に要する時間は表示位置によって(バイト境界からのズレで)変化し、30〜55μSです。(H8-3694 / 20MHz)

 ・ 表示関数(C版)

で、前の「水平ライン駆動関数」ではCで書いてもスピード的には問題が無かった(…というより、アセンブラとほとんど変わらなかった)ので、調子に乗って(笑)ここでもCで書いてみました。
        // -----------------------------------
        // フォント展開  2006.05.20
        // -----------------------------------
        // 表示位置は画面左上を 0, 0 としてドット単位で指定する

extern byte FontTbl8ankL[]; // Font-table address
union vd { word w ; struct { unsigned char h :8 ; // ビット割付けに順注意! unsigned char l :8 ; } b ; } ;
void disp_ank(word c, byte x, byte y) { byte i; byte *vramp, *fontp; union vd vram_data; byte dot_offset; word font_data, vram_mask;
if (x > H_DOT_MAX-7) return; // 範囲チェック if (y > V_DOT_MAX-1) return; if (c < 0x20) return; // Font は 0x20 から if (c > 0x7f) return; // 0x7f まで c -= 0x20;
fontp = FontTbl8ankL; // Font data table fontp += c * 8; // 8bytes / character vramp = Vram; vramp += (word)y * 16 + (word)x / 8;// 16bytes/H-line, 8dots/byte dot_offset = x & 0x07; // x % 8
vram_mask = 0x007f << dot_offset; // expansion is 7bits ('1'=mask) vram_mask = ~vram_mask;
for (i=0; i<8; i++) { // 8 H-line / Font font_data = (word)*fontp++ << dot_offset; vram_data.b.l = *vramp; // VRAM data LSB vram_data.b.h = *(vramp+1); // VRAM data MSB vram_data.w &= vram_mask; vram_data.w |= font_data; *vramp = vram_data.b.l; *(vramp+1) = vram_data.b.h; vramp += 16; // 16bytes / H-line if ((word)vramp > VRAM_END) break; } }
こんな感じで何とか文字の展開はできているようです(アセンブラ版と同じ画面になります)。最適化スイッチONで、実行時間は 33〜71μSでした。これなら余ほどのことが無い限りCで十分な気がします。(やっぱりアセンブラはもういらん・・・)

 ・ 表示してみたら・・・

なんと、前にここへアップしたフォントの左右が逆になっていました!
ミラー文字が表示されたのでした・・・。(「見える」のですぐに発覚!)
何ともお粗末なミスが連続していて、申し訳ないやら恥ずかしいやら…。
(またしても「こっそり」直しておきました)

グラフィック関数を考える

 ・ 座標系

前の 「ハードウェアの構成を考える」 では、液晶画面の左上のドットを [1,1] としたのですが、これは液晶の取説にならったものです。実際の画面座標は「左上を [0,0] とするケースがほとんどと思いますので、ここでも 「 画面の座標は左上を [0,0] 」 とします。

 画面の座標
  ┌───────────────────────────┐
  │┌───┬───┬───┐     ┌───┬───┐│
  ││ 0,  0│ 0,  1│ 0,  2│・・・・・│ 0,126│ 0,127││
  │├───┼───┼───┘     └───┼───┤│
  ││ 1,  0│ 1,  1│             │ 1,127││
  │└───┴───┘             └───┘│
  │  ・                     ・  │
  │  ・                     ・  │
  │┌───┐                 ┌───┐│
  ││63,  0│・・・・・・・・・・・・・・・・・│63,127││
  │└───┘                 └───┘│
  └───────────────────────────┘
   左上の座標を [0,0] として、右下の座標を [63,127] とします。

で、メモリに余裕があれば実際にはもう少し大きい「仮想画面」へ描画を展開しておいて、 128H x 64V の実画面(ウィンドゥ)へ表示するようにすれば上下左右にスムーススクロールができて、それなりに面白いのですが ・ ・ ・ H8/3694 の RAM 2KB ではちょっと無理ですね。

 ・ とりあえず「点」の描画を考える

考える…というほどのことは無く、単に位置の計算をしてビットを立てるだけです・・・
        ; CALL  R0L: x  0-127
        ;       R0H: y  0-63
        ; void  pset(unsigned char x, unsigned char y);

_pset: ; ----- check Range X,Y -------------- CMP.B #H_DOT_MAX,R0L ; X BCC ?900 CMP.B #V_DOT_MAX,R0H ; Y BCC ?900
MOV.B R0L,R1L ; X dot(s) AND.B #B'00000111,R1L ; R1L: X dot(s) offset MOV.B R0L,R1H ; X dot(s) SHLR.B R1H ; X/8 dot --> byte SHLR.B R1H SHLR.B R1H ; R1H: H offset (byte(s)) MOV.B R0H,R0L ; Y Line(s) MOV.B #H_DOT_MAX/8,R0H ; byte(s) / Line MULXU.B R0H,R0 ; R0 : V offset (byte(s)) ADD.B R1H,R0L ; add H offset ADDX #0,R0H ; R0 : VRAM offset (byte(s)) ADD.W #VRAM_TOP,R0 ; R0 : VRAM address of Cursor BSET R1L,@ER0 ; ----- Bit SET ---------------------- ?900: RTS
Cで書くともっと簡単で
void  p_set(byte x, byte y)
{
    byte    dot_offset;
    byte    *vramp;

if (x > H_DOT_MAX-1) return; // 範囲チェック if (y > V_DOT_MAX-1) return;
vramp = Vram; vramp += (word)y * 16 + (word)x / 8; // 16bytes/H-line, 8dots/byte dot_offset = x & 0x07; // x % 8 *vramp |= 0x01 << dot_offset; }
とまぁ特に問題は無いと思います。ビットを落とせばクリアですし、反転すれば画像の白黒が反転します。以後、グラフィック関連の各関数は基本的にこの「点描画」の関数を呼ぶことにします。

 ・ Line文を考える

クラフィック関数?というほどに大げさではないのですが、点の描画まできたので、次は「線を引く」Line関数を考えてみます。

DOS や Win のコンパイラなら Line 文で全て解決するのですが、流石に H8Tiny の世界ではそういうライブラリは一般的ではないようです。ネットを探せばCで書かれたものは色々とあるようですが、アセンブラのものは知りませんので、一応作ってみることにします。

で、実際に線を引く場合、X と Y の座標を計算しながら「連続した(ように見える)点を描いていく」ことになります。次のような (x1,y1) - (x2,y2) の2点間だとこんな感じでしょうか。

  Line1   Line2   Line3

尚、「 x1 → x2 」の間のドット数より「 y1 → y2 」の間のドット数が多いと、 X 方向のドット刻みより Y 方向のドット刻みの方が細かいことになり、 X 方向で1ドット単位の点を描いても Y 方向にはスキマが出来てしまう事になります。

Line4
その場合は、Y 方向へ1ドット刻みで点を描くことになります。

要は、 X 方向のドット数と Y 方向のドット数を比較して、 X 方向に刻むのか? Y 方向に刻むのか? を分ける必要があるということです。

また、横方向だけの線分、縦方向だけの線分であれば座標の計算はとてもシンプルになりますので「例外処理」にした方がいいと思われます。

で、次が(ちょっと…いや相当…長くなりますが)アセンブラで書いたモノです。
        ; LINE
        ; (xs, ys) から (xs, ys) まで直線を描く
        ; CALL  R0L: x1
        ;       R0H: y1
        ;       R1L: x2
        ;       R1H: y2
        ; void  line(unsigned char x1, unsigned char y1,
        ;            unsigned char x2, unsigned char y2);
_line:
        PUSH.L  ER2
        PUSH.L  ER3
        PUSH    R4
        PUSH    R5
        PUSH.L  ER6
                                        ; ----- 範囲チェック
        CMP.B   #H_DOT_MAX,R0L          ; x1
        BCC     ?900:16
        CMP.B   #V_DOT_MAX,R0H          ; y1
        BCC     ?900:16
        CMP.B   #H_DOT_MAX,R1L          ; x2
        BCC     ?900:16
        CMP.B   #V_DOT_MAX,R1H          ; y2
        BCC     ?900:16

MOV.W R0,E2 ; E2 : start CMP.B R0L,R1L ; x1 - x2 = 0 なら例外処理(縦線) BEQ ?600:16 CMP.B R0H,R1H ; y1 - y2 = 0 なら例外処理(横線) BEQ ?700:16 ; ------------------------------------ ; 始点終点間のX幅とY幅の大小を調べる ; ------------------------------------ SUB.B R1L,R0L ; R0L: X (signed) BPL ?100 MOV.B R0L,R2L ; R0L: 負数→絶対値 MOV.B #0,R0L SUB.B R2L,R0L ?100: SUB.B R1H,R0H ; R0H: Y (signed) BPL ?150 MOV.B R0H,R2H ; R0H: 負数→絶対値 MOV.B #0,R0H SUB.B R2H,R0H ?150: CMP.B R0L,R0H ; X幅 と Y幅 の比較 BCC ?300:16 ; X < Y ?200: ; ===== X > Y ======================== MOV.W E2,R0 ; E2 : start MOV.W R1,R3 ; R3 : end CMP.B R0L,R1L BCC ?220 MOV.W R0,R3 ; exchange start <--> end MOV.W R1,E2 ?220: ; xs < xe BSR _pset:16 ; Draw Point (xs, ys) Start point MOV.W E2,R0 ; E2 : xs,ys(start) SUB.B R0H,R3H ; R3H: ye - ys (signed) ADD.B #-1,R3H ; 始点を含む ys-->ye 間のドット数 BMI ?230 ADD.B #2,R3H ; R3H: Y (signed : ±1〜±64) ?230: MOV.B R3H,R5L EXTS.W R5 ; R5 : Y (signed : ±1〜±64) MOV.B R3H,R4L ; Y (signed : ±1〜±64) ADD.B #1,R4L ; (四捨五入…零捨一入?) (half adjust) SHAR.B R4L ; ÷2 EXTS.W R4 ; R4 : Y/2 (signed) SUB.B R0L,R3L ; R3L: xe - xs (0〜127) INC.B R3L ; 始点を含む xs-->xe 間のドット数 EXTU.W R3 ; R3 : X (dot count : 1〜128)
MOV.B R3L,R2L ; X (dot count) DEC.B R2L ; R2L: Loop counter (X-1 → 0) MOV.W #1,E3 ; E3 : N (1 → X) ?250: MOV.W R5,R6 ; R6 : Y (signed) MULXS.W E3,ER6 ; ER6: Y * N (signed) ADD.W R4,R6 ; add Y/2 (四捨五入…零捨一入?) DIVXS.W R3,ER6 ; R6L: dY (Y*N + Y/2) / X MOV.W E2,R0 ; xs, ys ADD.B R6L,R0H ; R0H: yn = ys + dY MOV.W E3,R6 ; N ADD.B R6L,R0L ; R0L: xn = xs + N BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 INC.W #1,E3 ; E3 : N DEC.B R2L ; R2L: Loop counter BNE ?250 BRA ?900:16 ?300: ; ===== X < Y =============== or X = Y MOV.W E2,R0 ; E2 : start MOV.W R1,R3 ; R3 : end CMP.B R0H,R1H BCC ?320 MOV.W R0,R3 ; exchange start <--> end MOV.W R1,E2 ?320: ; ys < ye BSR _pset:16 ; Draw Point (x, y) !xxR0,R1 MOV.W E2,R0 ; E2 : xs,ys(start) SUB.B R0L,R3L ; R3L: xe - xs (signed : ±1〜±127) ADD.B #-1,R3L ; 始点を含む xs-->xe 間のドット数 BMI ?330 ADD.B #2,R3L ?330: MOV.B R3L,R5L EXTS.W R5 ; R5 : X (signed : ±1〜±128) MOV.B R3L,R4L SHAR.B R4L EXTS.W R4 ; R4 : X/2 (signed) SUB.B R0H,R3H ; R3H: ye - ys (0〜63) INC.B R3H ; 始点を含む ys-->ye 間のドット数 MOV.B R3H,R3L EXTU.W R3 ; R3 : Y MOV.B R3L,R2L ; Y DEC.B R2L ; R2L: Loop counter (Y-1 → 0) MOV.W #1,E3 ; E3 : N (1 → Y) ?350: MOV.W R5,R6 ; R6 : X (signed) MULXS.W E3,ER6 ; ER6: X * N (signed) ADD.W R4,R6 ; add X/2 (四捨五入…零捨一入?) DIVXS.W R3,ER6 ; R6L: dX (X*N + X/2) / Y MOV.W E2,R0 ; xs, ys ADD.B R6L,R0L ; R0L: xn = xs + dX MOV.W E3,R6 ADD.B R6L,R0H ; R0H: yn = ys + N BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 INC.W #1,E3 ; E3 : N DEC.B R2L ; R2L: Loop counter BNE ?350 BRA ?900
?600: ; ----- X=0(縦線) --------------- ; E2 : start MOV.W R1,R3 ; R3 : end BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 MOV.W E2,R0 ; start CMP.B R0H,R3H ; Y=0 なら終了 BEQ ?900 BCC ?620 MOV.B R0H,R2L ; exchange R0H <--> R3H MOV.B R3H,R0H MOV.B R2L,R3H MOV.W R0,E2 ?620: BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 MOV.W E2,R0 INC.B R0H MOV.W R0,E2 CMP.B R0H,R3H BCC ?620 BRA ?900
?700: ; ----- Y=0(横線) --------------- MOV.W R0,E2 ; E2 : start MOV.W R1,R3 ; R3 : end BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 MOV.W E2,R0 CMP.B R0L,R3L BCC ?700 MOV.B R0L,R2L ; exchange R0L <--> R3L MOV.B R3L,R0L MOV.B R2L,R3L MOV.W R0,E2 ?720: BSR _pset:16 ; Draw Point (xn, yn) !xxR0,R1 MOV.W E2,R0 INC.B R0L MOV.W R0,E2 CMP.B R0L,R3L BCC ?720 ?900: POP.L ER6 POP R5 POP R4 POP.L ER3 POP.L ER2 RTS
で、またまたCでも書いてみました。 
        // 線描画
        // (x1,y1) - (x2,y2) の間を直線で結ぶ

void line2(byte x1, byte y1, byte x2, byte y2) { int X, Y, hX, hY, dX, dY, n; byte temp;
if (x1 > H_DOT_MAX-1) return; // 範囲チェック if (x2 > H_DOT_MAX-1) return; if (y1 > V_DOT_MAX-1) return; if (y2 > V_DOT_MAX-1) return;
if (x1 == x2) { // 「縦線」 (例外処理) lineV(x1, y1,y2); return; } if (y1 == y2) { // 「横線」 (例外処理) lineH(y1, x1,x2); return; }
X = (int)x2 - (int)x1; Y = (int)y2 - (int)y1; if (X < 0) X = 0 - X; else X = X; if (Y < 0) Y = 0 - Y; else Y = Y; // ----------------------------------- if (X > Y) { // X > Y (Xで刻んで行く) // ----------------------------------- if (x1 > x2) { // 始点の X座標が大きければ temp = x1; //  始点と終点の座標を入れ替える x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } // 始点X < 終点X pset(x1, y1); // 始点
X = (int)x2 - (int)x1; // X : +1〜+127 X += 1; // 始点を含む x1 --> x2 間のドット数 Y = (int)y2 - (int)y1; // Y : ±1〜±63 if (Y < 0) Y -= 1; // 始点を含む y1 --> y2 間のドット数 else Y += 1; hY = Y / 2; for (n = 1; n < X; n++) { dY = (Y * n + hY) / X; // (dY : signed) pset(x1 + n, y1 + dY); } // ----------------------------------- } else { // X < Y (Yで刻んで行く) // ----------------------------------- if (y1 > y2) { // 始点の X座標が大きければ temp = x1; //  始点と終点の座標を入れ替える x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } // 始点Y < 終点Y pset(x1, y1); // 始点
X = (int)x2 - (int)x1; // X : ±1〜±127 if (X < 0) X -= 1; // 始点を含む x1 --> x2 間のドット数 else X += 1; Y = (int)y2 - (int)y1; // Y : +1〜+63 Y += 1; // 始点を含む y1 --> y2 間のドット数 hX = X / 2; for (n = 1; n < Y; n++) { dX = (X * n + hX) / Y; pset(x1 + dX, y1 + n); } } }
// ----------------------------------- // 縦線描画 // -----------------------------------
void lineV(byte x, byte y1, byte y2) { byte n, y; pset(x, y1); if (y1 == y2) return; if (y1 > y2) { n = y1; y1 = y2; y2 = n; } y = y2 - y1; for (n=1; n<=y; n++) pset(x, y1 + n); }
// ----------------------------------- // 横線描画 // -----------------------------------
void lineH(byte y, byte x1, byte x2) { byte n, x; pset(x1, y); if (x1 == x2) return; if (x1 > x2) { n = x1; x1 = x2; x2 = n; } x = x2 - x1; for (n=1; n<=x; n++) pset(x1 + n, y); }
ここで、恒例?の実行時間を比べてみます。描画は液晶画面の左上から右下です。
line(0, 0, 127, 63);
line2(0, 0, 127, 63);  ←スピードの最適化 ON

実行時間 点描画関数は
アセンブラ版
点描画関数は
C版
アセンブラ版 920μS 1400μS
C版 830μS 1250μS

なんと アセンブラよりCの方が速い! という結果になりました。
それも 10%も速い! と、私がいかに ヘボ プログラマーか!ということが証明されたのでした・・・(涙)。

Cの吐き出したコードはまだ見ていない(今、ちょっと時間が…)のですが、ちょっとビックリです。アセンブラ版の方は座標の計算方法を工夫しないと(素直に書いたのでは)Cに太刀打ちできないようです。

circle とかの関数もやりたかったのですが、とりあえず「線」が書ければデジタルオシロ(もどき?)の波形表示は何とかなる… ということで、次へ進むことにします。

オシロのスケール表示を考える

 ・ Div 表示を考える
標準オシロ画面
普通のオシロの画面表示は右のような感じでしょうか。
横 10Div 縦 8Div で、各 Div は5ヶに細分割されています。

しかし、今回使用している液晶画面は 128W x 64H であり、「5」で割切れる値ではありません。

もちろん、画面の中で「5で割切れるエリア」のみ使用する(例えば 12W x 60H とか)方法もあると思いますが、どのみち 10Div x 8Div とかの画面は不可能ですから、ここは一般的なオシロの画面を気にせずに?検討してみます。

とにかく「画面が狭い!」ので、市販品のオシロのように画面で波形表示部分と文字情報表示部分を分けるのは無理と思われます。「全画面で波形表示」 を行い、「文字情報は波形表示エリア内の隅の方へ表示」 することにします。

実際の Div 数をどうするか考えると、縦の Div 数は、4〜6 あたりが候補になりそうです。Div 内の細分割(サブスケール)は、必然的に、2〜4となります。

分割Div数 Divあたり
のドット数
Div細分割
(サブスケール)
Div細分割
ドット数
4 Div 16ドット 4分割 4ドット
5 Div 12ドット 4分割 3ドット
6 Div 10ドット 2分割 5ドット

数字を並べるだけではイメージがつかめませんので、パソコン上で書いてみました。
(実際の液晶で書いた方がもっとよくわかるかも知れませんが…パソコンの方が早いので)
サンプル波形として緑色で sin 波を入れてみましたが、液晶はモノクロですので右の図が実際のイメージになります。(黄色は液晶の全表示エリアです)

OscilloScreen1 OscilloScreen1b

OscilloScreen2 OscilloScreen2b

OscilloScreen3 OscilloScreen3b

OscilloScreen4 OscilloScreen4b

OscilloScreen5 OscilloScreen5b

と、並べて見ると、やっぱり液晶画面の小ささを思い知ることになりました。

もし、CPU の ADRef が +5V で、電圧軸を 1V にしたい!ということであれば、縦 5 Div がいいかも知れません。もしくは、縦を 10 ドット/ Div にして、空いたスペースへ計測条件などを表示するのも良さそうです。

Line4 Line4

今回は小さな液晶の「画面を全部使う」事にして Div は「 横 8 Div 縦 4 Div 」 をメインに進め、余裕があれぱ 「 横 8 Div 縦 5 Div (10ドット/div) 」 もやってみることにします。

縦 4 Div ではかなり「荒い」気もしますが、画面が小さいので「精度」を求めるには無理がありますし、「趣味のおもちゃ」ということで… とりあえず先へ進みます。

 ・ 液晶へ Div を表示してみる

横 8 Div 縦 4 Div を実際に液晶画面へ表示してみます。
液晶Scale画面1
横 8 Div 縦 5 Div (10ドット/div) を実際に液晶画面へ表示してみます。
液晶Scale画面2

スケール描画のソース( 横 8 Div 縦 4 Div )はこんな感じです。
void  drawSubScaleH(byte y)             // 横スケール描画
{
    byte    x;
    for (x=4; x< H_DOT_MAX; x+=4)  pset(x, y);
}

void drawSubScaleV(byte x) // 縦スケール描画 { byte y; for (y=4; y< V_DOT_MAX; y+=4) pset(x, y); }
void drawScale(void) // 横 8div 縦 4div スケール描画 { byte *p; byte i;
p = (byte *)VRAM_TOP; // 外枠描画 上 for (i=0; i< 16; i++) { *p = 0xff; p += 1; } p = (byte *)VRAM_TOP + 1008; // 下 for (i=0; i< 16; i++) { *p = 0xff; p += 1; } p = (byte *)VRAM_TOP + 16; // 左 for (i=1; i< 63; i++) { *p = 0x01; p += 16; } p = (byte *)VRAM_TOP + 31; // 右 for (i=1; i< 63; i++) { *p = 0x80; p += 16; }
drawSubScaleH( 1); drawSubScaleH( 16); drawSubScaleH( 32); drawSubScaleH( 48); drawSubScaleH( 62); drawSubScaleV( 1); drawSubScaleV(126); for (i=16; i< H_DOT_MAX; i+=16) { drawSubScaleV(i); } }
実行時間は、約 1.3mS 程度のようです。(画面クリアは除く)
まぁスピードを考えて書いていないので仕方が無いのですが、これではちょっと時間がかかりすぎです。画面クリア(アセンブラ)が 150μS 程度なので、同じくらいで何とかしたいものです。( VRAM バッファがあれば多少は許されるのですが… なにせ VRAM 直接の操作なので…)

で、仕方なくアセンブラで書いてみました。(また、長いのですが・・・) 
                                        ; 横 8 div  縦 4 div
drawScale1:
                                        ; ----- 外枠描画 ---------------------
        XOR.L   ER0,ER0
        NOT.L   ER0                     ; ER0: H'FFFF
        MOV.W   #VRAM_TOP,R1            ; 上
        BSR     drawScaleH1:16
        MOV.W   #VRAM_TOP+1008,R1       ; 下
        BSR     drawScaleH1:16
        MOV.W   #VRAM_TOP+16,R1         ; 左
        MOV.B   #V_DOT_MAX-2,R0H        ;  長さ
        MOV.B   #H'01,R0L
        BSR     drawScaleV1:16
        MOV.W   #VRAM_TOP+31,R1         ; 右
        MOV.B   #V_DOT_MAX-2,R0H        ;  長さ
        MOV.B   #H'80,R0L
        BSR     drawScaleV1:16
                                        ; ----- 垂直スケール描画
        MOV.L   #H'01000100,ER0
        MOV.W   #VRAM_TOP+64,R1
        BSR     drawScaleH2:16
        MOV.W   #VRAM_TOP+128,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+192,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+320,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+384,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+448,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+576,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+640,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+704,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+832,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+896,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+960,R1
        BSR     drawScaleH2
                                        ; ----- 水平スケール描画
        MOV.L   #H'11111111,ER0
        MOV.W   #VRAM_TOP+256,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+512,R1
        BSR     drawScaleH2
        MOV.W   #VRAM_TOP+768,R1
        BSR     drawScaleH2

BRA drawScalee:16
drawScaleH1: ; ----- 水平連続ライン描画 MOV.L ER0,@ER1 ADDS.L #4,ER1 MOV.L ER0,@ER1 ADDS.L #4,ER1 MOV.L ER0,@ER1 ADDS.L #4,ER1 MOV.L ER0,@ER1 RTS
drawScaleV1: ; ----- 垂直連続ライン描画 ; R0L: 横方向ドット位置 ; R0H: 縦方向の長さ(ドット数) ?100: MOV.B R0L,@ER1 ADD.W #16,R1 DEC.B R0H BNE ?100 RTS
drawScaleH2: ; ----- 水平ラインサブスケール描画 MOV.L ER0,@ER1 AND.W #H'FDFF,E0 ADDS.L #4,ER1 MOV.L ER0,@ER1 ADDS.L #4,ER1 MOV.L ER0,@ER1 ADDS.L #4,ER1 OR.B #H'80,R0L ; 例外処理:外枠(右) MOV.L ER0,@ER1 AND.B #H'3F,R0L RTS
これでやっと 140μS といったところです。
(アランブラ版は外枠の部分のサブスケールを削除したものです)

注意が必要なのは、スケールは「ONビットのORではなくバイトデータの上書き」で描画していることです。先に波形を描画してからスケールを書くと「波形が消え!」ます。画面がモノクロなので「後からスケールを書く」という事は無いだろう・・・ということでこうなっています。

オシロの波形表示を考える

 ・ 波形データのバッファ長を考える

液晶の横方向は 128 ドットですので、データは 128 ヶあれば(実際には外形の描画で2ドット使っているので 126 ドットで)いいのですが、「方形波などのエッジ変化」を表示するためにはちょっと不足しています。

128 ヶのデータで 128 ドットを描画すると、各データ毎に1ドットづつ横軸を進める必要があり、そのために縦方向に真っ直ぐな線?が引けなくなります。
例えば、A/D 結果をドットで表したものが Fig1 であった場合、その表示は Fig2 のようにしたいわけですが、実際には Fig3 のように「段」が付いてしまいます。
Wave1Wave2Wave2
これを避けるには、1ドットへ2ヶ以上のデータを表示するしか手が無さそうです。1ドットへ2ヶのデータを表示すれば少しは見栄えが良くなるかも?と考えたのですが、実際には「見た感じ」ほとんど変わりませんでした。

この部分で「見栄え」を良くするためには最低でも「1ドットに8データ」くらいは表示しないといけないようです。その場合、液晶の横方向は 128 ドットですので、その8倍のデータとなると1Kバイトとなり、メモリが足りません・・・。

この「見栄えの問題」、何とかしたかったのですが、どうにも良いチエが浮かびません(どなたか教えて下さい! 笑)。仕方が無いので、1データ/1ドットとして 、見栄えのしない画面になるのは我慢することにします。

ということで、波形データ(サンプリングデータ)のバッファ長は 128 バイト にします。表示は2チャネル を予定していますので、合計で 256 バイトのデータバッファが必要となります。

 ・ トリガ点の表示を考える

サンプルデータをどんな構成のバッファへ格納し、どこのデータを画面のどの位置から表示するのか?という事を考えてみます。

データのサンプルはトリガ条件を満たすまで連続で行う必要があり、トリガ検出後も必要な数のデータサンプルを続ける必要があります。トリガ点がサンプルデータのどこに存在するかは全く不定ですので、バッファは必然的に リングバッファ になりそうです。

リングバッファは次のようなイメージになります。 
RingBuff2 普通のメモリから仮想的に作成したリングバッファと、書込み位置を記録する書込ポインタ WP を用意します。

WP が指す場所には前回(最終サンプル)のデータが入っています。新しいサンプルデータは WP を +1 して、その WP が指す場所へ格納されます。データサンプルが続く限り、WP はこのリング上をグルグル回るわけです。
WP は「最後にサンプルしたデータ位置」(最新のデータが入っている)を指します。WP+1の場所には一番古いデータが残っています。トリガ位置は ポインタ TP に記録します。
RingBuff3 サンプリング終了時、左図のようになっていたならば、「A」が一番古いデータで画面の左端に表示され、「B」のデータがトリガ点、「C」のデータが一番新しいもので画面の右端に表示される事になります。
 ・ スケーリングなどを考える

サンプリングデータが8ビット長であっても、液晶の縦方向は64ドットしか無いので、少なくともデータ値を1/4にしないと表示できません。

縦 5 Div (10ドット/div) にした場合、1/5(正確には 50/256)にする必要があります。

本当は、スケーリングもスイッチ操作などで可変にし、ゼロレベルの位置も同様に可変にしたいのですが、それは次のバージョンアップの機会に譲って、今回はそれらの機能の組込は見送ることにします。

というか、なかなか進まないので、面倒くさい機能は先送りしてしまおう(笑)というわけです。
 ・ マーカを考える

GNDレベル、トリガレベル、トリガポジション等の表示も必要になります。
と言っても画面が小さいので大きな矢印などは無理ですから「注意して見ないと気が付かないほど小さい?」マーカーになりそうです。次のようなマークを考えてみました。(背景との境界として周囲に1ドットのスペースを入れています)
    □
   □■□
  □■■■□
   □■□
    □
Marker1 イメージはこんな感じでしょうか。

Marker2 上の例だと、「外枠のサブスケール」がちょっと重い感じですので、その部分(サブスケール)を削ってみました。

何だかちょっとマヌケな感じもしますが、画面が小さいのであまりゴチャゴチャしない方がいいかな?ということで、このイメージで行くことにします。


尚、とりあえず 「GNDレベルは画面の一番下へ固定」 という事で、入力のA/D結果(0〜5V)を画面の上下一杯に表示するという事に(ややこしい機能は後回しに…)したいと思います。トリガレベルを画面の左側へ表示し、トリガポジションを画面の上側に表示する ことにします。
 ・ 実際に表示してみる
液晶Scale画面3 とりあえずCで書いてみましたが、その表示を見た結果、マーカで外枠が消えるのは、やはり見栄えが悪い…ということで、外枠と重なる部分のスペースは削除しました。

予想通り、かなり見にくいマーカですが、とりあえずコレで進めることにします。画面いっぱいに表示されているノコギリ波はテスト用にサンプルバッファへセットした擬似波形です。

波形は2チャネルの表示を予定しています。(今回のテストでは1チャネルのみの表示です)
extern  byte    *AdWp;          // A/Dサンプルデータの書込みポインタ
extern  byte    AdBuf0[];       // A/D CH0 sample data buffer (Length=128)
extern  byte    AdBuf1[];       //     CH1
extern  byte    DotPY;          // ドット描画位置 (DP) Y (起点)
extern  byte    DotPX;          //           X

extern void pset(byte, byte); // ドット表示 extern void psetDp(byte, byte); // ドット表示(位置記憶:起点) extern void pclr(byte, byte); // ドット消去
// ----------------------------------- // 線描画 (相対) // ----------------------------------- // 現在位置 - (x2,y2) の間を直線で結ぶ // 現在位置は DotPX, DotPY を参照する
void lineto(byte x2, byte y2) { byte x1, y1; x1 = DotPX; // ドット描画位置 (DP) X (今回起点) y1 = DotPY; //           Y DotPX = x2; // ドット描画位置 (DP) X (次回起点) DotPY = y2; //           Y line(x1, y1, x2, y2); } // ----------------------------------- // トリガポジション表示 // -----------------------------------
void drawTrigPosi(byte x) { pclr(x-1,1); pset(x,1); pclr(x+1,1); pclr(x-2,2); pset(x-1,2); pset(x,2); pset(x+1,2); pclr(x+2,2); pclr(x-1,3); pset(x,3); pclr(x+1,3); pclr(x,4); } // ----------------------------------- // トリガレベル表示 // -----------------------------------
void drawTrigLevel(byte y) { pclr(2,y-2); pclr(1,y-1); pset(2,y-1); pclr(3,y-1); pset(1,y ); pset(2,y ); pset(3,y ); pclr(4,y); pclr(1,y+1); pset(2,y+1); pclr(3,y+1); pclr(2,y+2); } // ----------------------------------- // 波形描画 // ----------------------------------- // ch 入力チャネル番号 0 or 1
void drawWave(byte ch) { byte *dp; byte b; word buf_end;
buf_end = (word)AdBuf0 + 128; dp = AdWp +1; // A/Dサンプルデータの書込みポインタ if (dp > (byte *)buf_end) dp -= 128; if (ch != 0) { // CH1 dp += 128; buf_end += 128; } psetDp(0, V_DOT_MAX - (*dp++ / 4) - 1); // 始点描画 & 起点記録 if (dp > (byte *)buf_end) dp -= 128; for (b=1; b < 128; b++) { if (dp > (byte *)buf_end) dp -= 128; // リングバッファ lineto(b, V_DOT_MAX - (*dp++ / 4) - 1); } }
// --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- void main(void) { byte b; byte *dp; word w;
InitialIo(); // 内臓I/O初期化 VeeON(); // 負電源 出力 ON InitialLCD(); // LCD 初期化 ClrScreen(); // 画面クリア drawScale(0); // スケール描画
b = 0; dp = AdBuf0; for (w=0; w<256; w++) { // サンプルバッファへ擬似波形をセット *dp++ = b; b += 7; }
AdWp = AdBuf0 + 127; // ポインタをバッファの最後へ drawWave(0); drawTrigLevel(40); // トリガレベル表示 drawTrigPosi(16); // トリガポジション表示
while (1) { for (b=0; b < V_DOT_MAX; b++) { Drv_PCPZ60D_H(); // LCD Hライン駆動 } } }

A/Dサンプルを考える

 ・ 最高速サンプリングを考える

H8/3694 の内臓 A/Dコンバータの変換時間 は、CLK が 20MHz の時 3.5μS/チャネル とありますので、2チャネルのスキャンモードですと、7μSサンプル となります。液晶の表示は、16ドット/div なので 7μSサンプルなら、112μS/div となり、これが CLK 20MHz 時の最高スピードになります。

しかし、この値はあくまで A/Dコンバータのスピードの限界であり、ソフトによる A/D結果の処理時間は考慮していません。A/Dコンバータがどんなに速くても、そのデータ処理が間に合わなければ意味が無いことになります。

7μSの間にデータの処理は間に合うのか? という部分を詳細に検討する必要がありますが、過去の経験から言えば「メチャクチャ厳しい!」気がします。

水平ライン転送などの割込処理がありますので、「7μS毎」の A/Dデータ処理をポーリングで…というわけには行きません。そのスピードを考えると最上位の割込みで処理する必要がありそうです。(と言っても H8Tiny の割込は1レベルですので… 割込禁止期間には要注意!です)

まず、7μSのうち、他の処理などに必要な時間を考えてみます。(全て 20MHz 時)
  1) 割込み応答時間(最大 37ステート) 1.85μS 必要です
  2) 水平ライン転送時間        3〜3.5μS 必要です
  3) 多重割込の準備時間         2.5μS 程度必要です
もうこれだけで 7μS以上 になってしまいます!
(水平ライン転送中、バックグランド処理用のタイマ割込は停止!ですね)

割込み応答時間 は、CPUが現在実行中の命令終了後、割込み処理を開始するまでに必要な時間です。 H8Tiny の場合、15 〜 37 ステートとなっていますので、20MHz であれば、0.75uS〜1.85uS となります。最長になるのは MULXS.W とか DIVXS.W の 23 ステート命令を実行している(し始めた)時に割り込みが発生した場合です。
プログラムで多数をしめる命令はほとんど 2〜8ステートですし、割込発生のタイミングは命令の実行開始時ではなく実行途中でしょうから、実際の割込応答は 20 ステート弱が平均値だと思われます。それでも1μ弱となり、避けることができないロス(?)タイムとなります。

水平ライン転送時間 は、必要な転送周期と、その転送にかかる時間で決まってしまいます。画面の更新を毎秒30回とすれば、64Line x 30回 = 1920 で、1秒 ÷ 1920 = 520uS となりますので、520uS 毎の転送が必要になるわけですが、その転送時間は 224uS 程度です(チューニングで少しは速くなるけど…)。この「転送作業」は A/Dの動作に関係なく行う必要があり、常に CPU時間を要求する処理です。それは、224uS ÷ 520uS = 43% となり、7μSの内の 43% (3uS) を必要としているわけです。

多重割込みの準備時間 は、例えば水平ライン転送の割込み起動中でも(最優先の)A/D変換終了割込みが起動できるよう、CPUを割込許可状態にするための時間です。
割込みが発生すると CPUは直ちに割込禁止状態になり、その直後の「割込み応答時間中」 (0.75〜1.85uS) は、「新たな発割込みが発生」しても受付けられません。この後で割込み処理プログラムが走り、再度割込みを許可した後にやっと「新たに発生した割込み」が(多重割込みで)起動可能となります。しかし、このとき、全ての割込みに対してその起動が可能になりますので、例えば(緊急性の低い)バックグランド処理用のタイマ割込みとかも当然発生するわけです。しかし、これはジャマ者!以外の何者でもありませんので、再度の割込許可の前にこれらの不要な割込みを禁止しておく必要があります。(割込優先レベルが複数あればコレは不要なのですが H8は1レベルなので…)

 ・ 現実的なサンプリングスピードを考える

サンプリングしたデータの処理内容を考えてみます。
  1) A/D 結果の読込み     上位8ビットのみ使用
  2) サンプルバッファへ格納  リングバッファ
  3) トリガの確認       トリガの判定と残りのサンプル数をセット
  4) サンプル終了の確認    終了ならタイマを止めて終了フラグをセット
といったところでしょうか?

実際のオシロで使われているのは 1, 2.5, 5 の系列ですので、100uS/div が無理なら次は 250uS/div となり、250uS ÷ 16 = 15.6uS がサンプリング周期となります。

この場合、水平転送に割かなければならない時間は 15.6 x 43% = 6.7uS となり、残りは 15.6 - 1 - 6.7 = 7.9uS となって、少し希望が見えてきます。同様に 500uS/div なら 16.8uS となります。

…ということで、もし 7.9uS で間に合えば 250uS/div が実現できることになりますので、とりあえずソレを目指して「仮の」コーディング(アセンブラ)でおおよその時間を調べてみます。(以下のコーディングは未チェックです!)
        .SECTION        ADBUFF, DATA, LOCATE=H'FC00     ; FC00-FCFF
_AdBuf0:        .RES.B  H_DOT_MAX       ; A/D CH0 バッファ
_AdBuf1:        .RES.B  H_DOT_MAX       ; A/D CH1 バッファ

_AdWp: .RES.W 1 ; A/Dサンプルデータの書込みポインタ _AdTrigP: .RES.B 1 ; ADトリガ位置(ポインタ値) _AdDispP: .RES.B 1 ; AD(トリガ点の)表示位置 _AdCt: .RES.B 1 ; AD変換停止までのサンプル数 _AdTrigLv: .RES.B 1 ; ADトリガレベル
_AdFlag: .RES.B 1 ; AD制御フラグ
AD_RUN .EQU 0 ; ADサンプル中 0:no 1:yes AD_TRG_RDY .EQU 1 ; トリガ前サンプル数OK 0:yet 1:OK AD_TRG_PASS .EQU 2 ; トリガ条件検出 0:yet 1:OK AD_TRG_POL .EQU 3 ; ADトリガ極性 0:+ 1:- SING_TRIG .EQU 5 ; シングルトリガ 0:Normal 1:Single AUTO_TRIG .EQU 6 ; オートトリガ 0:Normal 1:Auto H_DISP .EQU 7 ; 電圧軸表示 0:4div 1:5div
; -------------------------------------------------------------------- ; V25 0032 A/D変換終了 【割込処理】 ; -------------------------------------------------------------------- ; 【注意】 ;  トリガは CH0 固定 ;  _AdWp は 最終データの位置を保持 ;  _AdFlag をレジスタへコピーして操作し、最後に書き戻している _irqAD: PUSH R0 ;6 PUSH R1 ;6 PUSH R2 ;6 MOV.B @_AdFlag:8,R1H ;4 A/D関連フラグ ; ----- A/D結果の読出 ------------- MOV.B @ADDRAH:8,R0L ;4 CH0 A/D結果(8ビットのみ読出) MOV.B @ADDRBH:8,R0H ;4 CH1 A/D結果 ; ----- A/D格納バッファポインタ --- MOV.B @_AdDispP:8,R2H ;4 R2H: トリガ点前表示サンプル数 CMP.B R6L,R2H ;2 トリガ前サンプル数OK? BCC ?100 ;4          まだ ならJP BSET #AD_TRG_RDY,R1H ;2 トリガ前サンプルデータOK ?100: MOV.W @_AdWp,R2 ;6 A/Dサンプルデータの書込みポインタ ; ----- 前回データ読出(トリガ用) --- MOV.B @ER2,R1L ;4 CH0 前回データ ; ----- 格納ポインタ 更新 ----------- INC.B R2L ;2 ADDS #1,ER6 BCLR #7,R2L ;2 リングバッファ処理 EF00-EF7F MOV.W R2,@_AdWp ;6 ADバッファのポインタ 更新 ; ----- A/D結果 格納 ------------- MOV.B R0L,@ER6 ;4 CH0 今回データ MOV.B R0H,@(H_DOT_MAX,ER6) ;6 CH1   〃 BTST #AD_TRG_PASS,R1H ;2 トリガ条件検出? 0:まだ 1:済み BNE ?300 ;4 トリガ検出済みならJP ; ----- トリガ前データ数確認 --------- BTST #AD_TRG_RDY,R1H ;2 トリガ前サンプルデータOK? BEQ ?400 ;4            でなければJP ?200: ; ----- トリガ条件 確認 ------------- MOV.B @AdTrigLv:8,R2H ;4 R2H: ADトリガレベル BTST #AD_TRG_POL,R1H ;2 ADトリガ極性? 0:+, 1:- BNE ?250 ;4 ; ----- ↑エッジ トリガ CMP.B R2H,R1L ;2 前回データ R1L はトリガレベル以上? BCC ?400 ;4               ならJP CMP.B R2H,R0L ;2 今回データ R0L はトリガレベル以上? BCC ?260 ;4               ならJP BRA ?400 ;4 ?250: ; ----- ↓エッジ トリガ CMP.B R1L,R2H ;2 前回データ R1L はトリガレベル以下? BCC ?400 ;4               ならJP CMP.B R0L,R2H ;2 今回データ R0L はトリガレベル以下? BCS ?400 ;4            でなければJP ?260: ; ----- トリガ!!! BSET #AD_TRG_PASS,R1H ;2 トリガ条件検出 フラグ ON MOV.B R2L,@_AdTrigP:8 ;4 ADトリガ位置(データ位置 0-127) MOV.B @_AdDispP:8,R2H ;4 R2H: トリガ点前表示サンプル数 MOV.B #H_DOT_MAX-1,R2L ;2 R2L: 表示総ドット数 SUB.B R2H,R2L ;2 トリガ点後(右側)表示サンプル数 MOV.B R2L,@_AdCt:8 ;4 AD変換停止までのサンプル数 BRA ?400 ;4 ?300: ; ----- 既にトリガ検出済み MOV.B @_AdCt:8,R2L ;4 R2L: AD変換停止までのサンプル数 DEC.B R2L ;2 MOV.B R2L,@_AdCt:8 ;4 AD変換停止までのサンプル数 更新 BNE ?400 ;4 ; ----- 全サンプル取得済み BCLR #ADCSR_ADIE,@ADCSR:8 ;8 AD変換終了割込 禁止(サンプル終了) BCLR #AD_RUN,R1H ;2 ADサンプル停止 BRA ?500 ;4 ?400: ; 次のADデータ採取へ ?500: ?900: MOV.B R1H,@_AdFlag:8 ;4 A/D関連フラグ 書き戻し POP R2 ;6 POP R1 ;6 POP R0 ;6 RTE ;10
こんな感じでしょうか?
このコーディングですと、最長(トリガ検出)時は 145 ステート (7.5uS) 、平均的なサンプル状態(トリガ待ちの状態)で 136 ステート (6.8uS) となります。

これは、250uS/div 時に許容される最大(平均)時間 7.9uS を、ホンのわずかですが下回っています! このままのコーディングで動作すれば、その他の処理に使えるCPUの空き時間は (7.9uS - 6.8uS) ÷ 15.6uS = 7% となり、何とか動きそうです・・・。

ただし、水平ライン転送割込みが最長割込応答時間 (1.85uS) で発生し、直後に発生した A/D変換終了割込みでトリガ検出した場合(要は最悪のタイミング!) 2uS 程度不足することになります!。 その場合、サンプルしたA/Dデータの処理が終わらない内に次のA/D変換作業(ハードウェア)によってA/Dデータレジスタが書き換わりますが、書き換わる前のデータ(その割込で処理すべきデータ)は、割込処理の最初に読み出しているので、たぶん…大丈夫だと思われます。(この状態は連続しないので…)

しかし、動作テストで「アチャーーー」となって何かの処理の追加が必要になったりするとアウトです。これ以上は「出たとこ勝負」の雰囲気ですね。(笑)
 ・ A/Dのトリガ

H8/3694 は内臓タイマから A/D変換のトリガができませんので、内臓タイマで作成したパルスを一度 CPUの外部出力ピンへ出して、それを CPUの A/Dトリガ入力ピンへ入力します。

「ハードウェアの見直し」で触れましたが、 H8/3694 のタイマは3ヶしか無く、その割当ては必然的に決まってしまいます。

  タイマW A/Dサンプル周期駆動(16ビットカウンタが必要)
  タイマV 液晶水平ライン転送起動(正確な周期が必要)
  タイマA バックグランド処理用(10mS程度)「タイマ割込」が必要

タイマW はPWMモードで使用し、出力チャネルは D の予定ですので、 30pin と 16pin を CPU外部で接続しておきます。
  ┌──────────┐
  │H/3694       │30pin
  │          FTIOD(P84)│→→→┐
  │          │   │
  │          ADTRG(P55)│←←←┘
  │          │16pin
  └──────────┘
ADTRIG 入力の最小パルス幅は 2クロック となっていますので、出力パルスはその倍(4クロック:0.2μS)以上とすれば安心です。( GRD へは GRA - 4 を設定します)

サンプルレート と タイマW の設定は次のようになります。
S/Div Sample Time タイマ入力CLK GRA
250uS 15.6uS φ (0.05uS) 312
500uS 31.25uS φ (0.05uS) 624
1mS 62.5uS φ (0.05uS) 1250
2.5mS 156.25uS φ (0.05uS) 3125
5mS 312.5uS φ (0.05uS) 6250
10mS 625uS φ (0.05uS) 12500
25mS 1.5625mS φ (0.05uS) 31250
50mS 3.125mS φ/2(0.1 uS) 31250
100mS 6.25mS φ/4(0.2 uS) 31250

タイマWの初期化はこんな感じでしょうか。
    // タイマW 初期化
    //   クリア   コンペアマッチAでクリア
    //   出力    FTIOD
    //   割込    無し

void initialTmw(void) { TMRW.BYTE.TMR = 0x04; // TMRW タイマモード TMRW.BYTE.TCR = 0xB0; // TCRW  制御 TMRW.BYTE.TSR &= 0x00; // TSRW  ステータス TMRW.BYTE.TIOR0 = 0x03; // TIOR0 入出力制御 TMRW.BYTE.TIOR1 = 0x20; // TIOR1 入出力制御 TMRW.BYTE.TIER = 0x00; // TIERW 割込制御 TMRW.TCNT = 0x0000; // カウント開始時の初期値 TMRW.GRA = 1250; // 1mS/div (とりあえず) TMRW.GRD = 1246; // GRA - 4 TMRW.TMR.BIT.CTS = 1; // タイマW カウント開始 }
 ・ CPUのAD入力回路

本来なら「入力アンプ」などが必要になるのですが、ちょっと手を抜いて「測定信号は低インピーダンス出力」という前提の入力回路になっています。私の場合、0−5Vだけの範囲であれば、この程度で何とかなるケースがほとんどです。

入力のコンデンサ (103) がちょっと大きいので、このままで前段にオペアンプを入れる場合、容量負荷に弱いモノは要注意です。
AD入力回路 SW入力回路

今回、A/Dとして2チャネルを使っているわけですが、残りの6チャネルはA/Dとしては使いませんのでスイッチ(時間軸の切替とかに使う)を接続しておきます。こちらの回路もかなりの手抜き(笑)になっています。
 ・ 実際のサンプリング動作

現在動作している2チャネルのサンプリングは次の通りです。
_irqAD:
        PUSH    R0
        PUSH    R1
        PUSH    R2
        MOV.B   @ADCSR:8,R0L            ; AD制御/ステータスレジスタ
        AND.B   #B'01011111,R0L         ; 変換終了フラグクリア、変換開始クリア
        MOV.B   R0L,@ADCSR:8
                                        ; ----- A/D結果の読出 -------------
        MOV.B   @ADDRA:8,R0L            ; CH0 A/D結果(上位8ビットのみ読出)
        MOV.B   @ADDRB:8,R0H            ; CH1 A/D結果
        MOV.B   @_AdFlag:8,R1H          ; A/D関連フラグ
        MOV.W   @_AdWp,R2               ; A/Dサンプルデータの書込みポインタ
                                        ; ----- A/D格納バッファポインタ ---
        MOV.B   @_AdDispP:8,R1L         ; R1L: トリガ点前表示サンプル数
        CMP.B   R2L,R1L                 ; トリガ前サンプル数OK?
        BCC     ?100                    ;          まだ ならJP
        BSET    #AD_TRG_RDY,R1H         ; トリガ前サンプルデータOK
?100:                                   ; ----- 前回データ読出(トリガ用) ---
        MOV.B   @ER2,R1L                ; CH0 前回データ
                                        ; ----- 格納ポインタ 更新 -----------
        INC.B   R2L                     ; ADDS  #1,ER2
        BCLR    #7,R2L                  ; リングバッファ処理  (BufferLen=128)
        MOV.W   R2,@_AdWp               ; ADバッファのポインタ 更新
                                        ; ----- A/D結果 格納 -------------
        MOV.B   R0L,@ER2                ; CH0 今回データ
        MOV.B   R0H,@(H_DOT_MAX,ER2)    ; CH1   〃
        BTST    #AD_TRG_PASS,R1H        ; トリガ条件検出?      0:まだ  1:済み
        BNE     ?300                    ; トリガ検出済みならJP
                                        ; ----- トリガ前データ数確認 ---------
        BTST    #AD_TRG_RDY,R1H         ; トリガ前サンプルデータOK?
        BEQ     ?400                    ;            でなければJP
?200:                                   ; ----- トリガ条件 確認 -------------
        MOV.B   @_AdTrigLv:8,R2H        ; R2H: ADトリガレベル
        BTST    #AD_TRG_POL,R1H         ; ADトリガ極性?      0:+,  1:-
        BNE     ?250
                                        ; ----- ↑エッジ トリガ
        CMP.B   R2H,R1L                 ; 前回データ R1L はトリガレベル以上?
        BCC     ?400                    ;               ならJP
        CMP.B   R2H,R0L                 ; 今回データ R0L はトリガレベル以上?
        BCC     ?260                    ;               ならJP
        BRA     ?400
?250:                                   ; ----- ↓エッジ トリガ
        CMP.B   R1L,R2H                 ; 前回データ R1L はトリガレベル以下?
        BCC     ?400                    ;               ならJP
        CMP.B   R0L,R2H                 ; 今回データ R0L はトリガレベル以下?
        BCS     ?400                    ;            でなければJP
?260:                                   ; ----- トリガ!!!
        BSET    #AD_TRG_PASS,R1H        ; トリガ条件検出 フラグ ON
        MOV.B   R2L,@_AdTrigP:8         ; ADトリガ位置(データ位置 0-127)
        MOV.B   @_AdDispP:8,R2H         ; R2H: トリガ点前表示サンプル数
        MOV.B   #H_DOT_MAX-1,R2L        ; R2L: 表示総ドット数
        SUB.B   R2H,R2L                 ; トリガ点後(右側)表示サンプル数
        MOV.B   R2L,@_AdCt:8            ; AD変換停止までのサンプル数
        BRA     ?400
?300:                                   ; ----- 既にトリガ検出済み
        MOV.B   @_AdCt:8,R2L            ; R2L: AD変換停止までのサンプル数
        DEC.B   R2L
        MOV.B   R2L,@_AdCt:8            ; AD変換停止までのサンプル数 更新
        BNE     ?400
                                        ; ----- 全サンプル取得済み
        BCLR    #TMRW_CTS,@TMRW:8       ; TimerW stop
        BCLR    #ADCSR_ADIE,@ADCSR:8    ; AD変換終了割込 禁止
        BCLR    #AD_RUN,R1H             ; ADサンプル停止
        BCLR    #TMRW_CTS,@TMRW:8       ; TimerW stop
        BCLR    #ADCSR_ADIE,@ADCSR:8    ; AD変換終了割込 禁止
?400:
        MOV.B   R1H,@_AdFlag:8          ; A/D関連フラグ
        POP     R2
        POP     R1
        POP     R0
        RTE
心配だった動作時間は以下のようになりました。

    水平は 2.5uS/div 。(波形クリックで拡大表示)

緑は、A/D トリガ入力(16ビットタイマ出力)で、
紫は、A/D 変換終了割込処理(サンプリング)の実行時間(Low)です。

サンプリングの設定は 250uS/div なので、A/D サンプル周期は 15.6uS になっています。
上の波形を見ると、外部からの A/D トリガ(16ビットタイマから)の後、約 8uS 経過後 A/D 変換終了割込処理が始まっています。(2チャネルの A/D 変換時間は 7uS です)

A/D 変換終了割込の処理時間は 6.5uS ほどで、次の A/D 変換開始(外部トリガ入力の立下りエッジ)の 1uS 余り前に終了しています。これならギリギリではありますが、何とか動きそうです。

ということで、最速のサンプリングは 250uS/div という事になりました。

画面書換え時間とタイミングを考える

 ・ 表示(VRAM)書換えタイミングを考える

デジタルオシロ(もどき?)の動作では、常に画面の表示データが変化するわけですが、その表示データの書換えタイミングを考えてみます。まっ「画面の書換え」と「水平ラインの転送」の動作タイミングが重なった場合、どやねん?…というわけです。

実際には「水平ライン転送」は割込みで起動し、「画面の書換え」はメインから起動する事になりますので、「水平ラインの転送」が優先的に動作することになります。

仮に、「画面の書換え」に要する時間が水平ラインの転送周期より短い場合(今回のハードウェアでは有り得ないことですが…)を考えると以下のようになります。
  画面書換え    ├──・・・・・・・───┤
  水平ライン転送     ├─────┤         ├─────┤
という感じになり、画面を短い周期でひんぱんに書き換える場合、書換え中に転送される水平ラインデータの「書換え状態」が問題になります。というのは、オシロのようなビットマップ画面の書換えは、まず画面クリアをした後、スケール描画に続いて波形描画(あるいはその逆)などという順序で行いますので、ちょうど画面をクリアした直後に「水平ラインの転送」が起動された場合、その水平ラインは何も表示されないことになります。
  書換え処理    画面クリア        スケール描画  波形描画
           ├───┤         ├───┤├──────┤
  画面書換え    ├────・・・・・・・・・────────────┤
  水平ライン転送       ├───────┤
仮に上のようなタイミングで動作した場合、このとき転送される水平ラインは「空白データ」(クリアされた画面データ)となりますので、その水平ラインは何も表示されない(白く抜ける)事になります。これが「たまに起きる」だけならほとんど問題は無さそうですが、ひんぱんに画面を書き換える(オシロのような)場合には画面のチラツキが目立つようになります。最悪のタイミングを考えると画面に白抜けの水平ラインが目立つ事になるかも知れません。

これを回避するには「水平ラインの転送」が行われない時間帯(その転送が終わった直後から)に「画面の書換え」を行うようようにする必要がありそうです。要は 画面の書換え中に水平ラインの転送が発生しないように「同期」して動作 させようというわけです。
  水平ライン転送  ├─────┤            ├─────┤
  画面書換え          ├───────┤
 ・ 同一画面の表示時間を考える

次は「書換えた画面が表示されている時間」を考えてみます。

例えば、画面の変更後、画面の表示時間(1/30秒)以内に再び画面が書き換えられてしまったら、前の画面が正しく表示されることは無さそうです。書き換えた画面をちゃんと表示するには、書き換えた後、その画面が最低1回は表示されるのを待つ必要があります。一度画面を書き換えたら最低でも「1画面の表示時間」の間は次の画面書換えを禁止 しておく必要があるということです。

画面の表示と書換えを効率よく行うには、1画面の表示期間が終了した(画面の一番下の水平ラインの転送が終了した)タイミングで次の画面書換えを行う事になりそうです。
                   旧画面          新画面
  画面表示       …──────────────┤├───────
  最終の水平ライン転送  ├───┤
  画面書換え           ├──────┤
  最初の水平ライン転送                 ├───┤
画面の書換えが充分に短い時間(水平ラインの転送周期から、水平ラインの転送時間を引いた残りより短い時間)であれば、これで問題は無さそうですが、H8/3692 の実際の画面書換えはそんなに速くありません。次の項でそのあたりを考えてみます。

 ・ 表示(VRAM)書換え時間を考える

次に表示データ(VRAM)の書換えに要する時間を考えてみます。
前の項では「表示の書換えが充分に短い時間で終わる」場合を考えたのですが、実際にはそんなに速く書換えが終わることはありません。
短時間で VRAM を書き換えるには、前もって VRAM のバッファを用意し、そこへ表示データを用意(ここでは時間がかかっても良い)しておいて、一気に VRAM へコピーするなどが考えられますが、 2KB しか無い H8/3694 の 内臓RAM ではそのバッファの確保さえ出来ません・・・(涙)。

で、実際の表示書換え時間を調べてみました。
  画面クリア         0.15mS
  スケール描画        0.4mS
  チャネル1波形描画     5〜30mS
  チャネル2波形描画     5〜30mS
  マーカ、タイムDiv描画  0.5mS
波形の描画時間は波形の複雑さによって大きく変化します。
以下、表示波形と描画時間の調査結果?です。(同一信号:250μS/Div〜10mS/Div)

  時間軸  5mS/Div
  黄色   スケール描画処理時間
  水色   CH0 波形描画時間(上側)
  紫色   CH1 波形描画時間(下側)
  緑色   マーカ、タイムDiv描画時間

250uSdiv
500uSdiv
1mSdiv
2r5mSdiv
5mSdiv
10mSdiv

これらの動作時間には「水平ラインの転送」(割込処理)時間も含まれています。
当然ながら、描画が複雑になるほど時間がかかっています。(当たり前ですが…)
単純な波形でも 10mS は必要で、とても水平ラインの転送周期(520μS)に収まるモノではあり得ません。

仮に画面の書換え時間を 15mS とすれば、1画面の書き換え中に 30本近い水平ラインの転送が行われる事になります。これは画面の半分に相当する水平ラインの本数です。要は画面を書き換えている間に、その書き換え中の画面の半分は「書換え」と「表示」が同時進行 しているわけです。
 ・ この「同時進行」をもう少し考えてみます

書換え動作処理時間は、画面消去が約 150uS 、スケール描画は約 400uS ほどです。表示動作( H8 の RAM から液晶への転送)は一番上の水平ラインから行いますので、書換えがスタートした付近の実際の動作タイミングは次のようになると予想されます。
                        224uS
           │←←←← 520uS→→→│← →│
            64本目         1本目         2本目
  水平ライン転送  ├───┤      ├───┤      ├───┤
  画面消去         ├──┤
  スケール描画          ├───・・・・・───┤
  CH0 波形描画                      ├──・・・・・─‐
               │←→│←← 400uS + 224uS→→│
                150uS
64本目(前の画面の最後)の水平ライン転送終了後、直ちに画面の書換えを開始したとして、次の画面の最初(1本目)の水平ラインの転送が始まるまでに終わるのは画面消去だけです。次のスケール描画は、約半分ほど描画が進んだ状態で1本目の水平ラインの転送が始まってしまいます。もしここで「1本目の水平ラインに描画されるスケール」がまだ描けていない場合、1本目の水平ラインは空白が表示され、白く抜けたラインになってしまいます。

ということは、1本目の水平ラインの転送が始まるまでに1本目の水平ラインの描画を終えておく必要があるわけです。すなわち、スケールの描画は上から 行わなければならないことを意味します。

で、2本目の水平ラインの転送までにスケールの描画は終わりますので、2本目以下の水平ラインが表示されないという心配は無くなります。

しかし、波形の表示はどうなるのでしょう?
おおよその感じはここまでの説明でつかんでいただけると思います。要は、画面の上の方の書換えは最初に!やらないと表示されないという事なのです。

仮に、画面の書換えに15mSを要したとすると、その書換えが終わった時点で画面の上半分は(水平ラインの)転送が終わっています。もし、書換えの最後の方で画面の上部を書換えたとしたら、その部分は(今回表示する画面としての転送は終わっているので)転送中の画面としては表示されないという事です。その書換え部分が表示されるには「更に1画面ぶんの表示期間を待つ」必要があるわけです。もちろん、その「待つ」間は画面の書き換えをしないことが条件になります。

例えば、全ての波形を描画した後で(最後に)時間軸の値(1mS/div …とか)を「画面の右上に表示」したとしたら、その表示は書換えた大部分の表示に対して「1画面遅れて表示される」事になります。
            │← 1/30秒 →│
  画面表示タイミング ┼──────┼──────┼──────┼─────…
  画面書換え1           ├──┤
  大部分の書換え1画面表示     ├──────┼──────┤
  最後に書換えた「時間軸」の表示         ├──────┤
  画面書換え2                         ├──┤
  書換え2画面表示                       ├─────…
これだと、仮に 1/15秒毎に画面を書換えた場合、時間軸の表示はその他の表示に比べて 1/2 の期間(デューティー50%)しか表示されませんので「半分の濃度」となり、コントラストが薄い状態で表示されます。実際には「薄い上にちらついて見える」事になります。

しかし・・・悲しいことに「 VRAM のバッファが確保できない」状態では、この現象を回避する妙案は無いようです。とりあえずは、「画面の上の方から優先的に書換える」しかありません。

後は、画面の書き換えサイクルを可能な限り遅くする(書換えた画面をなるべく長時間表示する)事でしょうか。私の場合、現在、最速で毎秒10回の書換え にしています。これで特に「遅い」と感じることは無いようです。

で、今動いているプログラムの表示に関するタイミングをオシロで調べてみました。

  時間軸 10mS/div(左)、25mS/div(右)
  黄色  サンプル終了後、表示中の画面転送が終わるまで待つ期間
  青色  画面書き換え期間(最新の A/D 結果を表示するため)
  紫色  現在転送中の画面の転送終了待ち期間
  緑色  その後の1画面の表示期間(この間は画面書き換え禁止で待つ)

     

と、画面の書換えは約0.1秒に1回(毎秒10回)となっています。
尚、現在は画面を書換えた後で更に1画面の表示を待ってから次のサンプリングを行っていますが、これでは時間のかかるサンプリング(時間軸の値が大きい:長い)場合、画面の更新が「待っている時間」(1/30秒)遅れるので(それがどうした!と言われそうですが…)画面の書換えが終わったら直ちに次のサンプリングへ進むように直す予定です。

最終章 お礼 (とプログラム)

 ・ お礼

ここまでご覧いただいた皆さん、有難うございます。
特に、作成途中から(多くのBUGの被害を含めて)ご覧いただいた方には大変感謝しております。おかげさまで何とかここまでやってくる事ができました。

本当は、マンマシンI/Fの部分なども章を割いて書く必要があるのがも知れませんが、とりあえず何とか波形の表示ができるようになったので、ひとまず終了とさせていただきます。
有難うございました!!!
 ・ 最終? 回路図

CPU 基板は秋月電子で売られている 3694CPU 基板を使っていますので、申し訳無いのですが、この回路図は実際のテスト回路と多少異なっています。実際にはもう少し色々な回路が載っているのですが、この回路図は「これだけあれば動く(だろう?)」というレベルのモノになっています。

CPU の内臓フラッシュの書込み環境は「 自前 」のモノを対象にしていますので、皆さんお使いの回路に変えていただく必要がありそうです。

アナログ入力は「かなりの手抜き」回路になっています(すみません)。このままの回路で「外部に何かつなぐ」場合はご注意ください。(特に容量負荷に弱い**社のオペアンプなどをそのままつなぐとマズイかも?)

同様に「スイッチ入力回路」も手抜きの極み?みたいな回路です。頻繁にスイッチ操作を行うと早い時期に接点不良とかになります…というか、業務用では有り得ない回路です。でも、数千回程度ならコレで充分と考えて「趣味ならこれでいいかも?」と手を抜いてしまいました。(すみません…ちょっとでも少ない部品で組立てようとした…おうちゃくモンは私です)

負電圧の作成回路(チージポンプ)ですが、基本的には -10V あれぱ全く問題ありません。ので、現行 74HC14 3段の -12V 以上というのは液晶モジュールのスペックオーバかも知れません。で、回路図には未チェックと記述してある 4069 などに関しても調べてみました。手持ちの部品で調べた結論は、4584(東芝)は使えそう、4069 は使えないメーカがあるかも?というものでした。これはあくまで「私の手持ち部品」で調べた結果であり、決して一般的に言える事では無いのですが・・・。

初段(回路図はH8)も含めて全て同一パッケージのゲートを使って実負荷でテストした結果、出力電圧は次のようになりました。

 IC型式  メーカ   製造時期   出力電圧   備考
 ------------------------------------------------------------------------------
 4069  モトローラ 1979! −8.59V  濃くはならないが充分見える
 4069  東芝    1989  −8.79V  充分見える
 4069  日立    90年代? −7.65V  薄くて実用にならない

 4584  東芝    1999  −9.39V  問題無しの実用レベル
 4584  東芝    2005  −9.89V  言うこと無し

という事で、「もし」このデータが一般的な値であるとすれば(無理がありますが…)、74HC14 とか 74HC04 よりも 4584 の方がベストという事になるのですが・・・。

CPU 基板の市販品としては、やっぱり秋月電子です(何たって安い!)が、トラ技 2004/4 号の付録基板の方が使いやすいかも知れません。同じ基板はサンハヤトでも売っていますが値段が(秋月に比べて)だいぶ高いのが辛いところです。(「止むを得ない事情」はよく解るのですが…)

CPU チップは 3664 でも(中身は 3694 と同じなので)そのまま動くハズです。一部、3694 には有っても 3664 には無いピンがありますが、それさえ気を付けてもらえば(別のピンへふってもらえば)シュリンクDipパッケージが使えるので QFP が苦手な方にはいいかも知れません。ただし、こちらは最高動作周波数が 16MHz で 20MHz での動作は出来ない事になっています…  が、 20MHz の水晶を付けて動かないモノはまず無い?と思われます(笑)。決してオススメできることではありませんが…、プロには絶対に許されない事でも「アマチュアが趣味でやるなら」まぁいいんじゃない?ということで・・・(笑)。3664 でやりたいけどサンプル部分のソフト変更は面倒…という方はお試しあれ。(←って、おいおい!)
 ・ プログラム

大変遅くなってしまいましたが(まだ動作テスト中の)プログラムをやっとUPします。

  (旧版 PCPZ60soft.lzh(2006.09.10)は削除しました : 後方に最新版があります)

最近ほとんど空き時間が確保できない状態が続いていて、ちょっと手抜き状態なのですが「時間切れ」ということで恥をさらすことにしました。(涙)

かなりピンボケですが、左(上)がサンプル動作中の画面、右(下)が停止中です。

 

実のところ、250uS/div では他の割込で水平ラインの転送に遅れが出ている部分があるようで、画面に「横線」(ノイズです)が入ったりしています。時間に余裕が出来た時点で直す予定です。(今はちょっと・・・時間が・・・)

番外編1 画面のノイズを何とかしたい・・・

 ・ 画面ノイズ ...

250uS/div の場合、実際の画面は次の通りです。写真で見ると単に上から2番目の div へ横線が入っているだけですが、実際にはこの「横線がチラつく!」ので、とても目について!、実用的とは言いがたい画面表示になります。以前のプログラムでは目立たなかったのですが、色々と処理を追加していくうちにこうなってしまったようです。

 

表示画面のスケールのドット間隔は4(ドット)ですから、この写真でみると上から21〜22本目の水平ラインで異変が起きています。更に…、このピンボケ写真でははっきり見えませんが、その7〜8本下の水平ラインでは逆に色が薄くなっています。
 ・ 画面ノイズ ... その原因を考える

まず、各横線(水平ライン)の色(濃さ)が異なるということの意味を考えてみます。

この液晶の駆動は「水平ライン単位」で行っています。
液晶ユニットにすれば、ホスト側から送られてくる「水平ラインのデータ」を、送られてくる順に画面の上から表示しているだけで、各水平ラインの色の違い(薄い/濃い)など知ったこっちゃ無い! …という事になります。

じゃぁいったい色の違いはどこで起きているのか?
それは、水平ラインの転送間隔で決まってくるんです。LCDは、送られてきた水平ラインの転送が終了した時点で、その水平ラインを表示します。その表示は次の水平ラインの転送が終了するまで続き、次の水平ラインの転送終了とともに次の水平ラインの表示へ切替わります。

仮に、水平ラインの転送状態が次のような状態( Hi が転送作業中)だったとすれば、1,3,5の各水平ラインの表示時間は波形の下側に示す長さとなります。
この場合、‘1’が普通の濃さとすれば、‘3’は濃く、‘5’は薄くなるわけです。

  ┌┐  ┌┐  ┌┐     ┌┐  ┌┐ ┌┐  ┌┐  ┌┐
──┘└──┘└──┘└─────┘└──┘└─┘└──┘└──┘└──
  1   2   3      4   5  6   7   8
   ├───┤   ├──────┤   ├──┤
     1        3        5

でも…、水平ラインの伝送作業はタイマ割込みによる駆動ですから「正確な周期」で動作しているハズです。こういう(上の例のような)事態はちょっと想像できません。

何が原因なのか?もう少し考えてみます。(って「原因は一つ」しか無いのですが…)
基本的に「最優先の割込はA/Dサンプル」であり、「次の優先割込順位は水平ラインの転送」ですから、水平ラインの転送に「待った!」をかけているのはA/Dサンプルの割込しか有り得ません…。

でも、A/Dサンプルの処理時間はせいぜい6〜7μSのハズですから、その間、水平ラインの転送が遅れたって知れています。水平ラインの転送間隔は520μSですから、6〜7μSはその1%強でしかなく、その程度の時間差が「目立つ」ほどの色の差になるとは考えられません。どうなっているのでしょう?

で、実際の動作波形を調べてみました。
次の波形は A/Dサンプルと表示のタイミングを調べたものです。

  時間軸: 2.5mS/div
  黄色 : 最終水平ライン転送終了(から最初の水平ライン転送開始の期間)
  水色 : 水平ライン転送時間(転送中は Hi )
  紫色 : startSample() 実行期間
  緑色 : A/D サンプル(割込処理)実行期間

  

A/Dサンプル開始直後から水平ラインの転送タイミングが変化している様です。が…、よく見えませんので(横4倍に)拡大してみます。



どうやら、23本目からの水平ライン転送時間( Hi )がかなり長くなっているようです。今度はこの部分のみを観測してみます。時間軸は 500uS/div です。

  

水平ラインの転送中にA/Dサンプルに割込まれて転送を中断され、転送の終了までにかなりの時間がかかっているようです。細かい部分をもう少し見たいので、これも(横4倍に)拡大してみました。



少しづつ事態が見えてきました。少し安心できたのは、各水平ラインの転送が伸びてはいるものの「次の転送までに少し空き時間がある(波形が一度 Low になっている: Hi の連続では無い)」ということです。これは「水平ラインの転送周期内に何とか転送が終わっている」事を意味します。まぁ実際にはどのくらい空いているのか?この波形の精度では何とも判断できませんが・・・。

ついでに?もう一つ、A/Dサンプルがスタートした最初の水平ライン転送の部分を更に拡大してみてみます。今度は 100uS/div です。

  

一応、水平ライン転送周期(約 520uS)の間には何とか転送が終わっているようですが、余裕はわずかです。これも(横4倍に)拡大してみます。



時間軸は 100uS/div です。startSample() 実行(紫色)後、A/Dサンプル処理(緑色)が 15.6uS 毎に動作を始めます。以前の実行時間見積り(A/Dサンプルを考える)では、A/Dサンプル(割込)処理時間は 7uS 弱で、250uS/div のサンプル動作を行うと「CPUの空き時間は 7% 程度」ということでした。

水平ラインの転送周期は、約 520uS ですので、7% という事は 520uS x 7% = 36.4uS となり、250uS/div のサンプリング時、水平ラインの転送時間は 520 - 36.4 = 483.6uS で、36.4uS が空き?時間となります。

これは、上の波形とほぼ一致!する値です。
何のことは無い…、各動作タイミングはほぼ当初の予定通りで「正しく動いている」らしい…事がわかりました。

おっと、ついついCPUの動作を追いかけるのに夢中で、画面ノイズのことを忘れていました。正しく動いているらしい…のに画面にノイズが出る…なんてシャレにもなりません。

水平ラインの表示の濃さは「その次の水平ラインの転送時間」で決まるわけで、これらの観測結果を少しだけまとめてみました。結果を擬似的に(少し大げさに:多少のウソ?を込めて…)書くと次のようになります。

             ┌┐
startSample() ──────┘└────────────‥‥───────────
               ┌┐┌┐┌┐┌┐┌┐┌┐  ┌┐
A/D Sample  ────────┘└┘└┘└┘└┘└┘└‥‥┘└─────────

タイマ割込 Tmv    ↑    ↑    ↑    ↑    ↑    ↑
       ─┐  ┌─┐  ┌───┐┌───┐┌‥‥─┐┌─┐  ┌─┐
水平ライン転送 └──┘ └──┘   └┘   └┘   └┘ └──┘ └─

   転送終了 ↓    ↓      ↓    ↓    ↓     ↓

水平ライン表示─┼────┼──────┼────┼─‥‥─┼──┼────┼─
   色の濃さ   普通    濃い!    普通   ‥‥  薄い  普通


LCD側からすれば、水平ラインの表示を始めるのは「転送が終了してから」になるわけで、各水平ラインの転送終了から次の転送終了までが表示時間ですので、上の図を見ると一目瞭然、A/Dサンプルがスタートした直後に「濃い」表示になる事が納得できます。

水平ラインの転送開始はタイマ割込で駆動されるので正確なのですが、その転送時間はA/Dサンプル(割込処理)の有無で大きく変わってしまうわけで、転送の終わりから次の転送の終わりまでの時間が「揺らいで」濃く表示されたり薄く表示される水平ラインが出現します。

周期の「正確さ」を期するのは、水平ラインの転送「開始」タイミングではなく、「終了」タイミングであるということです。ここを何とかしない限り、この「画面ノイズ」は無くならないようです。うぅ〜ん・・・。
 ・ 画面ノイズ ... その対策を考える

ポイントは一つしか無さそうです。
水平ラインの転送「開始」ではなく「終了」のタイミングを合わせること。しかし、割込みで「転送開始」を起動している現在の構造では「終了」のタイミングをコントロールする事はまず無理のようです。

「終了」をコントロールするにはどうしたらいいのか?
ここで「終了」の意味を考えてみます。今回の液晶ユニットに実装されてい液晶コントローラが「転送終了」を認識するのはどのタイミングなのか? コントローラ LC7940YC のデータシートを見ると、LOAD 入力であることがわかります。回路図では HS と名前を付けているラインです。

LC7940YC は、CP(回路図では CK )の立下りエッジで LOAD(回路図では HS )をサンプルし Hi であれば、水平ラインの「転送終了」と認識するようです。

という事は、この HS の出力部分をタイマ割込みで起動できればいいことになります。要は、水平ラインのデータは前もって転送しておき「最後のデータ」の転送途中で待たせておいて…、タイマ割込みで HS を出力すればいいわけです。
  Dot  124 125 126 127             0   1   2   3       126 127         0   1
      ┬─┬─┬─┬─‥‥───┬─┬─┬─┬─┬‥─┬─┬─‥───┬─┬
  SD  ┴─┴─┴─┴─‥‥───┴─┴─┴─┴─┴‥─┴─┴─‥───┴─┴
      ┌┐┌┐┌┐┌─‥‥──┐┌┐┌┐┌┐┌┐┌‥┐┌┐┌─‥──┐┌┐┌
  CK  ┘└┘└┘└┘     └┘└┘└┘└┘└┘ └┘└┘    └┘└┘
                 ┌─┐                ┌─┐
  HS  ────────‥‥─┘ └────────‥─────‥─┘ └──
  割込処理  ────┤‥‥├────────────────┤‥├─────
  タイマ割込 Tmv          ↑                                    ↑
今まで割込処理では「水平ラインの最初から転送」していたのですが、これを「前の水平ラインの転送の最後」から処理するようにしようというわけです。具体的には、上の図のように、最後のドットのデータ転送の途中で割込処理を終了し、次の割込処理ではその続き( HS の出力)からスタートするわけです。

で、水平ライン転送のコーディングを直してみました。 
_drv_PCPZ60D_H:
        PUSH    R6
        MOV.B   @LCD_Port:8,R1H         ; R1H: Port data
                                        ; ------------------------------------
                                        ; 水平ライン 最終ドットの後始末
                                        ; ------------------------------------
        BSET    #LCD_HS,R1H             ; Last Dot  (HS) ON
        MOV.B   R1H,@LCD_Port:8         ;                               out
        BCLR    #LCD_CK,R1H             ; set CP-Lo
        MOV.B   R1H,@LCD_Port:8         ;                               out
        BCLR    #LCD_SD,R1H             ; clear SD
        BCLR    #LCD_HS,R1H             ; clear HS
        MOV.B   R1H,@LCD_Port:8         ;                               out
        BCLR    #LCD_FS,R1H             ; clear FS  (条件判定の時間をカット)
        MOV.B   R1H,@LCD_Port:8         ;                               out
                                        ; ------------------------------------
        MOV.B   #H_DOT_MAX,R1L          ; R1L: Dot counter
        MOV.W   @_VramAdr,R6            ; R6 : H-Line Top address
        CMP.W   #VRAM_TOP,R6
        BNE     ?100
        BSET    #LCD_FS,R1H             ; set FS ON  (Top Line !)
?100:
?200:                                   ; ----- Byte (8dots) Loop ------------
        MOV.B   @ER6,R0L
        INC.W   #1,R6
        MOV.B   #8,R0H
?300:                                   ; ----- Dot Loop ---------------------
        BSET    #LCD_CK,R1H             ; set CP-Hi
        MOV.B   R1H,@LCD_Port:8         ;                               out
        SHLR.B  R0L                     ; CY = dot (ON/OFF)
        BST     #LCD_SD,R1H             ; set SD
        DEC.B   R1L                     ; Last Dot ?  (dot counter)
        BEQ     ?400                    ;          YES then JP  (goto END)
        MOV.B   R1H,@LCD_Port:8         ;                               out
        BCLR    #LCD_CK,R1H             ; set CP-Lo
        MOV.B   R1H,@LCD_Port:8         ;                               out
        DEC.B   R0H                     ; 8bits (1byte) All ? ----- Dot Loop -
        BNE     ?300
        BRA     ?200
?400:                                   ; ----- Last Dot ---------------------
        MOV.B   R1H,@LCD_Port:8         ;                               out
        CMP.W   #VRAM_TOP+VRAM_LEN-1,R6 ; VRAM End ? (フレーム転送終了?)
        BCS     ?600                    ;          NO then JP
                                        ; ----- フレーム(1画面)転送終了 ---
        MOV.B   @_DispFlameN:8,R0L      ; フレーム表示回数(表示毎に+1)
        BMI     ?500                    ; (オーバフロー対策 128以上は無変化)
        INC.B   R0L
        MOV.B   R0L,@_DispFlameN:8
?500:
        BSET    #FLM_END,@_LcdFlag:8    ; フレームエンド(最終H走査線転送)
        MOV.W   #VRAM_TOP,R6
?600:
        MOV.W   R6,@_VramAdr            ; H-Line Top  (for next transefer)
                                        ; ------------------------------------
        BNOT    #6,@PDR7                ; DC−DC制御(−10V:LCD用)
                                        ; ------------------------------------
?900:
        POP     R6
        RTS
で、修正後の動作を調べてみます。
まず、水平ラインの処理時間と「 HS 」信号の関係は次のようになりました。

  時間軸: 25uS/div
  黄色 : HS
  水色 : 水平ライン転送(割込)処理(Hi が転送中)

  

HS は、処理開始直後に出力されていて、目論見?通りにはなっているようです。転送処理に要する時間は約 218μS となっています。

次に、A/Dサンプル開始時の動作を確認してみます。

  時間軸: 250uS/div
  黄色 : HS
  水色 : 水平ライン転送時間(転送中は Hi )
  紫色 : startSample() 実行期間
  緑色 : A/D サンプル(割込処理)実行期間

  

予定通り、割込処理の最初で HS がちゃんと出力されているようです。結果、HS の間隔(各水平ラインの「濃さ」を支配)はA/Dサンプル動作に大きな影響を受ける事無く一定の間隔を保って動作しているようです。細かい部分も見たいので(横4倍に)拡大してみます。
  

A/Dサンプルが始まって2本目の水平ラインの転送は、HS の出力中にA/Dサンプルに割込まれて Hi の期間が延びていますが正常に動作しているようです。念のため、A/Dサンプル開始後の最初の水平ライン転送部分をみてみます。時間軸は 100μS/div です。

  

(横4倍に)拡大したものです。



当初の目的通りに動作しているようです。偶然ですが、A/Dサンプルが始まって2本目の水平ライン転送で、HS の出力中にA/Dサンプルに割込まれているのがよく分かります。

続いて、A/Dサンプルの処理時間も確認しておきます。
左側は 2.5μS/div 、右側が 1μS/div です。

    

サンプリングは 250uS/div ですので、周期は 15.6uS です。サンプリング(A/D割込)の処理時間は(この計測時) 6.3uS 程度となっています。ただし、トリガなどの例外処理が入ると、もう少し処理時間が延びると思われます。

 ・ 修正プログラム1  PCPZ60soft.lzh (172KB 2006.09.21)

修正したプログラムを実際に動かした時の液晶画面です。

 

画面上から22本目のあたりに出ていた水平ラインのノイズは全く目立たないレベルになっています。

プログラムに関する細かい部分は・・・ lzh の ReadMe.txt でお許し下さい。

番外編2 H8/3664(SDIP:16MHz)版  2006.11.09

 ・ 回路図  ( PDF ファイル )

やっとこさ回路図を書きました。
(いくら時間が無いとはいってもちょっと遅すぎ… m(_ _)m )

3694版からの変更ですが・・・
3694で6ヶのスイッチを接続しているPBポートは 3664には4ヶしか無く、内2ヶはA/Dの入力に使いますので、2本しか残りません。なので、スイッチ6ヶはそっくり「ポート5」へ移動という事にしました。これ以外のポートはそのままで問題なさそうです。

変更になったポートは

      PB2     空きへ
      PB3     空きへ
  SW1 --> P50 へ接続
  SW2 --> P51 へ接続
  SW3 --> P52 へ接続
  SW4 --> P53 へ接続
  SW5 --> P54 へ接続
      P55     ADTRG として使用(変化無し)
  SW6 --> P56 へ接続
      P57     空へ

 ・ CPUクロックの問題

3664 は 16MHz が上限となっていますので、20MHz の3694 と同じソフトは使えません。タイマ等の設定値を変えてやる必要があります。
少し後になりますが、16MHz で動くソフトをUPする予定です。

クロックを 16MHz へ落とした場合、 250uS/div のサンプリングは無理っぽいので 500uS/div からの動作になりそうです。

しかし…、この問題には絶対に他人にオススメできない?「奥の手」が・・・
そうです! 3664 のクロックを「規定外!」の 20MHzで動かすことです。
全くの個人的な過去の経験から言えば 99.9% は動くと思います。
(プロには禁じ手ですが・・・)

 ・ 3664 用プログラム(20MHz!版) LCD3664.lzh (36KB 2006.12.17)

大変遅くなってしまいましたが、やっと H8/3664 版の動作確認が終わりました。ただし、「 20MHz版 」です。(H8/3664 の最高動作周波数は 16MHz です!)

動作確認に使用したのは秋月の AE-3664(通販コード K-00006)です。以前、テスト用に組み立てたボードへ便乗?(コネクタ等を追加)しています。この組立キットに付属しているセラミック発振子は(当然)16MHz ですが、これを 20MHz へ交換してあります。(これまた秋月の P-147 ですが…)

H8/3664 20MHz 1 H8/3664 20MHz 1

私のところでは 20MHz でも全く問題なく動作していますが、なにぶん「メーカ保証外」の動作ですので、うまく動かない場合(なんてマズあり得ない?…とは思いますが…)は発振子を 16MHz へ戻してくださるようお願いします。
( 16MHz 版のプログラムは近いうち?に用意する予定です)

・・・と、ここまできて「新たなBUG」が発覚してしまいました。
何気なく時間軸のスイッチを押して・・・ 5mS 10mS 25mS 50mS となった時、表示がおかしい!!! 調べたら、タイマWのクロックを設定している部分にミスがありました。
m(_ _)m

慌てて修正したモノです。 LCD3664a.lzh (36KB 2006.12.18)
3694版も直さなければならないのですが、他の変更と含めて直す予定です。
…と、まぁ情けない限りですが、今日はこのあたりで・・・。

気になっていたハードウェア部分 (BUG?) を修正  2007.03.20 - 2007.03.29

 ・ 修正

回路図に関して、個人的にはほとんど気にしていない部分なんですが、プロから見ると「許せない!」部分が「多々」あると思います!(笑)。少し迷ったのですが、その一部を修正しました。(ついで?にコネクタ部分の信号名の記述ミスも修正しています)

例えば、液晶の負電圧作成用チャージポンプでは「ゲート出力でダイレクトに大容量コンデンサをドライブしている」部分があったりして、「そんなん やっちゃダメじゃん」というご感想をお持ちいただいた方もたくさんおられると思います。まぁプロなら「ダメじゃん!」程度では済まないわけで、大目玉!は間違いないところでしょうし、こんな回路ばっか書いてるとその内クビですよね。・・・ということでちょっとだけですが回路図を修正してみました。

 ・ 回路図

回路図は H8/3664(SDIP) 版 と H8/3694(QFP48) 版 があります。

   H8/3694版(PDF) 2007.03.17 (57KB)
   H8/3664版(PDF) 2007.03.17 (57KB)

 ・ チャージポンプ部分の変更

回路図を変更したなかで一番大きいのはチャージポンプ駆動ゲート出力へ直列に抵抗を入れた部分です。この部分について少し考えてみます。

まず修正前の回路について…。
CPump003.PNG 修正前の回路ではコンデンサ(10μF)を74HCのゲート出力で直接ドライブしていました。直列に入っているのはダイオードのみで、電源ON時のコンデンサへの突入電流(左図の水色の経路を流れる電流)を制限する「抵抗」は入っていません。

じゃ、突入電流は無限大???(誰がその値を決めている?)

電源(今テスト用に使っているのはジャンクで買った携帯電話の充電器 5.0V 0.5A)の容量が有限ですので無限大の電流は不可能ですが、それでも電源容量いっぱいの電流が仮に 74HC の出力に流れたら、瞬時にして 74HC は焼けてしまいます(笑)。

実際には上の回路で 74HC04 が瞬時に焼ける事は無い様で、「誰か?」が電源ON時の突入電流を制限しているハズです。もちろん配線(銅?)の電気抵抗とかもあると思われますが、それらは全部合わせてもせいぜい1Ω以下で、一番大きな要素は 74HC04 のゲート出力のチャネル抵抗だと思われます。

74HC04 の出力チャネル抵抗は出力レベルや温度で異なりますが、データシート等から推測すると、おおよそ 40〜80Ω 程度と思われます。単純に言えば、ICの内部で、出力に直列に 40〜80Ω 程度の抵抗が入っているという事です。(あっ、電源電圧でも変わりますが、今回は5V固定なので…)

チャネル抵抗を 50Ω と仮定すると「突入電流」は(単純計算だと…)
  (5V - 0.8V) ÷ 50Ω ≒ 84mA  にもなります。(0.8V は Di の順方向電圧降下)
実際には、メーカの特性グラフを見ると、+5V電源時、出力をショートさせた時の電流が、吸込み/吐出しともに(25℃の代表特性で)約 50mA 弱となっていますので、もう少し小さい値になると思われますが、それでも相当たくさんの電流が流れることになります。

しかし・・・、74HC04 の出力許容電流値(最大定格)は 25mA なんですよね!

メーカのデータシートを読むと、「最大定格」というのは絶対に超えてはならない値で、瞬時たりともオーバーすることが許されていません!
・・・という事は・・・
この回路は電源を入れる度に(いや、ひょっとしたら通常動作時にも…?)最大定格を軽〜く超える状態を繰り返すわけで、いつ壊れても不思議ではありません。いや、現実に動いているのが不思議な?状態にさえ思えます。

実際にはほとんどのケースでそんなに簡単に壊れることは無いようですが、あくまでメーカの保証外での動作になってしまいます。

で、結局、変更した結果はどやねん!ということですが

CPump002.PNG 単に 74HC04 の最大定格出力電流 25mA を超えないように抵抗を入れただけです。

抵抗値は (5V - 0.6V) ÷ 25mA ≒ 180Ω です


実際に組立てた「3段」の回路は次の図です。

CPump001.PNG

CPumpDisChg01.PNG
本当は放電用のトランジスタもホトカプラなどへ変更するべきところなのですが・・・今回は見送ってしまいました。

(右の回路にすると、放電の駆動論理が現在の回路とは逆になります!)


 ・ 変更した結果、電圧はどうなった?

もちろん!下がりました。おおよそ2V余りの低下です。
例によって?手持ちの何種類かのICでテストしてみました。結果は以下の通りです。出力に直列に挿入した180Ωの影響が大きいようで、相対的にICの種類による差はかなり小さくなっています。

    ICの型式    ロット番号     出力電圧
  --------------------------------------------------
  TC74HC04   9837    −9.49V
  TC74HC14   9423    −9.53V
  TC74AC04   0440    −9.75V
  TC74AC14   0525    −9.76V

液晶はこれらの電圧でも十分実用的なコントラストは得られるのですが…周囲温度の変化(などで液晶のコントラストはけっこう変化)もあるので、もう少し電圧が欲しい・・・感じです。

じゃ、チャージポンプをもう一段増やすのか?(最初、2段でテストして「ちょっと」足りなかったので3段にした経緯があります) …となるのですが、ちょっと考えてみます。

現在、3段で10V弱ですから1段あたり3V強になります。
欲しいのは 10.5〜11V 付近なので、もう一段増やすのはちょっと大げさ?です。
で、ダイオードに注目しました。

現在使っているのは、ごく普通(型式は忘れましたが…数年前に秋月で買った 500本 \500 )の小信号スイッチング用シリコンダイオードです。

4本も使ってますから単純に考えても
  0.6V x 4 = 2.4V  の電圧降下です。これをショットキー型に変えると・・・
  0.3V x 4 = 1.2V  程度で、あと1Vは上がるのでは? というもくろみです。

で、現在、この普通のシリコンダイオードでどの程度の電圧降下になっているのか?調べてみました。

右の波形は初段のダイオードの両端のモノです。
電圧軸は 200mV/Div  時間軸は 250uS/Div です。

実際の回路での動作中のものですのでマイナス側にも振れていますが、「順方向電圧降下」はプラス側です。
(GNDはスケール左側の緑色4の矢印 : スケール右側の緑色矢印はトリガレベル : スケール上側の灰色矢印はトリガポイント)

おおよそ 0.63V 程度の電圧降下になっているようです。
CPump04s.PNG

このダイオードをショットキー型へ変更してみます。(実際は、先に組立てた回路のシリコンダイオードの「上」へショットキーダイオードを並列に付け足しただけです)
ショットキーダイオードは、これまた秋月のヤツ (I-01370 : RB751S-40 : 25ヶで \100 : 2007.03.25 現在) です。でも、これはチップ部品(それも 1608 )なので、苦手な方も多いかも知れませんね。同じく秋月で売っている 1SS108 はリード品ですが、こちらは 500本単位での販売で \2,500 です。

私自身は最近、手組のちょっとした回路でもチップ部品を使うことが増えてきました。なんといっても小さいので「後からの手直し」(こういうエエカゲンな性格なので…手直しが多いんですよね、ホント)にはとても便利なのですが・・・ヘッドルーペが欠かせないのが年寄には辛いところです(笑)。
普通のシリコンダイオードからショットキーへ変更して、電圧降下はどうなっているのか?調べてみました。

電圧軸は 200mV/Div  時間軸は 250uS/Div で
計測条件などは前のものと全く同一です。

おおよそ 0.32V 程度の電圧降下になっているようで、これなら全体で1V程度の電圧UPが望めそうです。
CPump05s.PNG

で、普通のシリコンダイオードの時と同一のICで出力電圧を調べてみました。

    ICの型式    ロット番号     出力電圧
  ----------------------------------------------------
  TC74HC04   9837    −10.56V
  TC74HC14   9423    −10.57V
  TC74AC04   0440    −10.86V
  TC74AC14   0525    −10.88V



これなら充分なコントラストが得られそうだ・・・と写真に撮ったものの、なんと、ピンボケのボケボケでした。 m(_ _)m

でも、必要以上に濃い表示になっている雰囲気が少しは出ているでしょうか?

これで何とかコントラストは問題ないレベルになったようです。(ホッ…)
PCPZ_CGPump70326

・ 変更したチャージポンプ回路の動作

で、変更した結果、チャージポンプのゲート出力電流は本当に最大定格内に収まっているのか?(一応、そのハズなんですが…) もし収まっているなら、どれくらい流れているのか? 調べてみました。

CPump07s 74HC04 の初段の、出力へ直列に入れた抵抗 (180Ω) の両端の電圧波形です。

計測条件は 25mS/div, 1V/div で、スケール上側の灰色矢印が「電源ON」のタイミングです。

最初の突入電流のピーク(プラス側)は、180Ω の両端に 約 1.8V ですから 10mA 程で、次の突入電流(マイナス側)は -3.8V で 21mA 程度です。充分に最大定格内に収まっています。(ホッ…)

CPump08s 電源投入時の部分を少し拡大してみます。

計測条件は 2.5mS/div, 1V/div で、スケール上側の灰色矢印が「電源ON」のタイミングです。

問題は無さそうです。


続いて2段目の波形も見てみます。計測条件は一段目と同じです。
左側が 25mS/div 、右側が 2.5mS/div で電圧軸は共に 1V/div です。

CPump09 CPump10

一段目とは極性が逆転していますが、さほど変わらず、問題は無さそうです。
続いて3段目も見ておきます。
左側が 25mS/div 、右側が 2.5mS/div で電圧軸は共に 1V/div です。

CPump11 CPump12

3段目は部分的なショートに備えて(まず必要ないとは思ったのですが…)430Ω になっています。その 430Ω の両端にかかる電圧は 3V 程度ですので 3V ÷ 430Ω = 7mA と、全く問題はありません。

430Ω は心配のし過ぎ?で、180Ω で充分なのかも知れません。
と、これで何とか突入電流の「最大定格オーバ」は心配せずとも良さそうです。

まっ、本人は根っからのデタラメ人間ですから、「定格オーバ? …はぁ?」みたいな感じで、最初っから、ほとんど心配していなかったりします・・・(笑)、
でも、部品はやっぱメーカの指示通りに使うのが正しい姿勢ですから…、たとえアマチュアでも(いやアマチュアだからこそ???)定格オーバはいけませんね。
(…と、一応、書いておこう・・・ 笑)

最後に、変更した回路での -10V の立ち上がりを見ておきます。

CPump13 時間軸は 50mS/div です。

水色が電源電圧で、5V/div 。
黄色が -10V の立ち上がり波形で 2V/div です。

これで見ると、-10V の立ち上がりには 0.4秒 余りかかっているようです。
 ・ 余談


ダイオードの順方向電圧降下





/// ・・・ 次は、ソフトを直さなくっちゃ! (ついでに「整理」もしたい)


Top