« 2013年1月27日 - 2013年2月2日 | トップページ | 2013年3月17日 - 2013年3月23日 »

2013年2月10日 - 2013年2月16日の1件の記事

2013年2月14日 (木)

STM8Sでグラフィック液晶に文字を表示することに成功

 STM8Sで外付けフラッシュメモリーを読み書きするルーチンがひとまず動いた。このプロジェクトの当面の目標は、このフラッシュに文字フォントデータを入れて、STM8Sでグラフィック液晶に文字を表示することである。

 このモノクログラフィック液晶(AitendoのJCG12864A37 以降GLCD)を制御するソフトは、AVRで開発が済んでいる。STM8SではSPIのところを書き換えるだけで容易に動くはずだ。今度付けたフラッシュメモリは大容量(512KB)なので、日本語フォントを入れて表示することも出来る。

S_p2125686

 ここまでやれば、STM8Sの顔も立つだろう。漢字がLCDで表示できるとなると色々なアプリケーションに使える。他の用途も考えられるので、電波時計にするかを今決めてしまうこともない。

 GLCDを動かす前に、大事な作業工程が残っている。外付けフラッシュメモリにフォントデータを収容する手順である。当初はメモリをソケットから外して、SDカードが読めるAVRなどのマイコンで転送する予定だったが、途中で気が変わった。STM8SのUARTのファイル転送に挑戦する。

UARTからのファイル転送の仕様を考える(1/30/2013)
 そのきっかけは、常用のターミナルソフトであるTeraTermにファイル転送機能が標準でついていることに気づいたからである。そう言えば昔のパソコン通信全盛の頃はファイル転送は日常茶飯事のことだった。

 別のマイコンでSDカードからフォントデータを入れるにしても、そのマイコンには今のフラッシュの書き込みソフトを新たに開発しなければいけない。どうせ開発するなら難易度は少し高くなるがSTM8SでUARTのファイル転送ソフトを作ったほうが手間が省ける。

 フォントデータの大きさは、1バイトの半角フォント(6X12ドット)なら5KBほどだが、日本語フォントともなると、12ドットフォントでも240KBはある。STM8SのUARTで、この程度の規模のデータが安定して受け取れなければいけない。

 STM8S側のUART受信ソフトの開発は、厳しい信頼性(1バイトでもずれると後のデータは全滅)が求められる。しかし挑戦しがいのある開発テーマでもある。安定したファイル転送を実現するための仕様の検討に入った。

 UARTの信頼性はフロー制御をすれば飛躍的に高くなるが、ハードソフトとも、もうひと手間、複雑になるので余りやりたくない。とすると、UART受信は待ちを一切いれず一気に読み切る必要がある。

 この前の受信割り込みルーチンではフラグを立てるだけだったが、今度は、割り込みルーチンでしっかりバッファーに貯める方式に変える。読みながら、フラッシュにブロック単位に書いていくことになるので、必然的にバッファー2つを使うダブルバッファー方式ということになる。

 さらにバッファーからフラッシュへ書き込むSPIインターフェースは、必ず処理の途中でUARTから割り込まれる。処理が分断されても問題ないかどうかの確認が必要だ。それにフラッシュメモリの書き込み時間も問題である。

 STM8SはSRAMが2KBしかないのでバッファーサイズは、余り大きくは出来ない。小さくしていくと、フラッシュの書き込み時間とUARTの転送時間が近くなり、バッファーオーバーランの心配がある。

 ロジアナで見ると64バイト程度のフラッシュ書き込み(2Mbps)は2ms以下である。UARTの64バイト受信は13ms程度(38.4kbps)であるので、そう神経質になることはないが、念のため実測しておくことにした。セクター消去の時間がロジアナでは計測不能なのでこの時間も測っておきたい。

 LEDの点滅に使っているタイマーの1tick(10ms)を減らして1msとし、フラッシュ書き込みの両側にコードを入れて実際の速度を測る。最初、10ms近くあり慌てたが、UART送信時間を含めて測っていたことがわかり、書き込み直後の時間をワークに入れてやり直した。その結果、転送時間はスペックどおり1msであった。ロジアナの1.6msとも符合する。

 一方、フラッシュメモリの消去は、セクター単位(64KB)なので時間がかかる。スペックによれば全消去(512KB)が2秒なので、0.3秒程度のはずだが、実測の結果は0.6秒だった。まあ、これは、ファイル転送の前に手動でやっておけば良い話なので関係ない。

 またSPIが割り込みに耐えられるかは、STマイクロのライブラリのコードを確かめた。その結果、単にレジスターとのデータの出し入れだけで、いわゆるAtomicオペレーション(分割できない複数の命令の処理)はないことがわかった。これなら割り込みを受けても心配ない。

 安定したファイル転送のための要件は満足しているようだ。残るはオペレーション手順である。転送を指示するコマンドの入力で、PCからのUART送信をひたすら待ち(タイムアウトを設けるが)、ファイル転送が終わったあとは、暫く待って一定期間データが来なくなれば終了と判断する、というプロトコルである(これがあとで大紛糾するのだが、このときは露知らず)。

擬似コーディングでダブルバッファーシステムを設計する(2/1/2013)

 ブロックレコードの転送では定番のダブルバッファー方式である。この方式ならUARTとの衝突を心配する必要がない。バッファーを2つ用意し、片側のバッファーにUARTの受信データを貯めながら、もう一方のバッファーから余裕を持ってフラッシュに書き込んでゆく。

 ちょうど良い機会なので、擬似コーディングの始めから、少し忠実にメモに書き出してみた。以下、こんな感じである。

 ------ダブルバッファー処理の擬似コーディング開発メモ------

 バッファーの状態を 0...未使用  1...書き込み中 2...満杯で転送待ち とする。

 バッファーA、Bの状態遷移表(真理値表)は、
        A  |  B 
        -------
        0  |  0     初期状態。とりあえずAから書き始める
        1  |  0     バッファーAに書き始めたとき
        2  |  0     バッファーAが一杯になり書き込み先をBに変更
        2  |  1     バッファーBに切り替えて書き込み
        0  |  1     Aを外付けフラッシュに転送した。書き込みはBで変わらず
        0  |  2     バッファーBが満杯になった。Aに切り替え
        1  |  2     バッファーAに書いている。
        1  |  0     Bを外付けフラッシュに転送した。最初から2番目に戻る

上の条件を整理していくと、ifを使ったロジックが作れる。一旦作っておいて、条件の聞き方を工夫する。整理すると条件式を減せるときがある。

まず、UART受信割り込みルーチンでは、A、Bをバッファーのステイタスだとすると、
if(A == 0) {
    if(B == 1) バッファーBにデータを移す
    else バッファーAにデータを移す
  }
else {
    if(A == 1) バッファーAにデータを移す
    else バッファーBにデータを移す
 }

 これくらい抽象度が高いと、ソースコードで見るより一目瞭然でロジックを確認できる。バッファーが満杯になって、受信したデータの書き込み先を切り替えるところと、満杯になったデータをフラッシュに書き出すところとは、処理するところが別々でクリティカルなところなので十分動作を確かめておく。

 このあとの処理として、それぞれのバッファーのポインターの処理(満杯になれば、ポインターを0にもどし、ステイタスを2にする)もここで必要である。

 次に、メインルーチンでは、満杯になったバッファーデータをフラッシュに転送していくが、このロジックは、これより簡単になる。
if( A ==2){
バッファーAのデータをEEPROMに転送し、フラッシュアドレスを進める
バッファーステイタスAを0にする。

if( B == 2){
バッファーBのデータをEEPROMに転送し、フラッシュアドレスを進める
バッファーステイタスBを0にする。

 UART受信割り込みでは、もうひとつ重要な処理を忘れていた。ステイタス0から1にするときの処理である。バッファーAが0のときは、最初のifで判別できるので、バッファーAに移すときに、ステイタスを1にしてやればよい。

 バッファーBは、最後のバッファーBに移すif elseのところであらためて、ステイタスが0かを確認したうえで、1にする。もっとも、ここは真理値表を良く見れば、必ず0か1なので(ここのステイタスBが2ということは、オーバーランエラー)、ifで聞くくらいなら常に1にしておく方が早いかもしれない。

 最後にバッファーに満杯にならずにデータ受信が終了した時のバッファーに残った最終データの送出処理と、実際のファイル転送をやるときのオペレーションにあわせたロジックを考えておく必要がある。
           --------------開発メモ終了------------- 

念入りな擬似コーディングをしたところは完璧。他はボロボロ(2/4/2013)

 周到にロジックを考えたので、ソースコードの完成は早かった。意気揚々とファイル転送のソフトのテストに入った。とりあえず、データが来なかった時のタイムアウトは20秒、データが来たあと(ファイル転送終了)のタイムアウトは、PC側がファイル転送を中断することもありえるので5秒とした。

Dblbuf

 ところが、これが不安定なのである。うまく行く時もあるが、何故か途中でファイル転送が中断され、残ったデータが終了後のコマンドプロンプトに流れ込んでハングする。奇怪なのは、ロジアナを入れると転送が失敗することである。

 ロジアナのプローブは動いている時と止まっている時とはインピーダンスが違うのか?まさか。UART(TTL)にはプルアップ抵抗も何も入れていない。それが悪いのか。そんな馬鹿な。しかし、ロジアナを動かさない時はOKでも、動かすと駄目になることは間違いない。頭を抱えた。

Dbl_cons

 しばらく悩んだが何のことはない、こちらのミスだった。ファイル転送の開始が遅れると、5秒タイムアウトが効いてデータを受信した途端、転送終了とみなしていた。要するにロジアナを動かす時のオペレーションの単なる遅れの差だけだったのである。ロジアナさん疑って失礼しました。

 しかし、これが解決した後もしつこいトラブルに悩まされた。転送が終わったあとのUARTにゴミが出て正しく文字が出力されない。データは2KB近くまでOKになっているが、ときどき失敗し、UARTにゴミが残る。タイムアウトのタイマーは、フラッシュに書き込むときでなく、UARTのデータ受信の度にリセットするようにしたのだが、それでもうまく行かないときがある。

 擬似コーディングを念入りにやったおかげで、ダブルバッファーのロジックは完璧に動いている。うまく行く時は、2KB以上のデータを間違いなく転送するのに、うまく行かない時は、たかだか400バイト程度でも途中で転送が止まってしまう。

 原因がわからない。ファイル転送のあとのタイムアウトを5秒から10秒程度にすると少し改善されるが、それでも不安定である。ダブルバッファーのところは完全なのに、タイムアウトのリセットまわりは良い加減なロジックで組み立てたのでボロボロだ。

変数の宣言ミス。やっとのことで安定してファイル転送がOKになった(2/5/2013)
 原因究明を続けた結果、どうみても転送終了を決めるカウンターが割り込みルーチンとメインルーチンの間で正しく受け渡されていないような気がしてきた。 UART受信の度に、このカウンターは0に戻されるのだが、これがどうも0に戻っていないようである。

 ファイル転送のタイムアウトは5秒後で、もしこのカウンターを0にしていないという仮説を取り入れると、これまで起きている様々なトラブルの症状はすべて説明することができる。問題のカウンターは、外部変数のlap_cntという変数である。メインのmain.cで定義し、uart2.cのUART関数で外部参照している。コンパイラーはエラーも警告も出していない。

 ソースリストを見ていて気が付いた。メインでの変数宣言は、int16_t lap_cnt; で、uart2.cの外部参照宣言は、extern uint16_t lap_cnt; だ(傍線 筆者)。あれ、タイプが違う。ふーむ。普通は、同じ名前をつけるとエラーになるか少なくとも警告が出るが、今回は何も出ていない。

しかし、コンパイラーによっては、こういう外部変数の参照解決には甘い(厳密にチェックしない)ことがある。特にこのRaisonanceのCコンパイラーは以前もヘッダーファイルまわりで散々な目にあっている。もしかしたら、これが悪さをしているのかもしれない。

 こんなことで直るとは思えないが、タイプを揃えてテストしてみた。何と、何と、これですべてが解決したのである。タイムアウトぎりぎりに、どんな大きなファイル転送をしても問題ない。40KBを超えるファイルも1バイトの誤りもなく転送に成功した。

 要するに、UART受信の度にカウンターをリセットしているはずが、やっていなかったために、5秒以内にファイル転送を終えなければ必ず何らかの不具合が起きていたのだ。

 わかってしまえば、何ともお粗末な話である。同じ変数だと思っていたが、考えてみれば、コンパイラーにとっては、タイプが違うので全然別の変数で、外部参照宣言では同一変数名はチェックしようがないのだろう。コンパイラーを疑って悪いことをした(でも警告ぐらい出してよね、と恨み節)。

 UARTでゴミが出るのは、ターミナルソフト、PCハード、USB-UART(TTL)アダプターのどこかにハードのフロー制御がされていて、UARTの受信を途中で止めると、残っていたデータがファイル転送を終えたコマンドプロンプトに現れて字化けをしていたものと思われる。当初はUARTが壊れているかと疑っていた。UARTさん、ごめんなさい。

 いやあ反省、反省である。擬似コーディングを念入りにしたダブルバッファーのところは、ピタリと動いて、そうでないやっつけで書いたところはバグの出まくりである。いい加減なカットアンドトライでロジックを作ると碌(ろく)なことはない。今度の開発では、つくづくこのことを痛感した。

GLCDドライバーをSTM8Sに移植する(2/7/2013)
 いつもの大騒ぎながら何とかファイル転送は成功した。これでフォントデータはいつでもPCからファイル転送で、コマンド一発でフラッシュに収容することが出来る。200KB以上のデータも問題なく入り、ファイル比較ソフトで検証しても一字もエラーは出ない。好調である。

 勢いに乗って、いよいよ次の工程、AVRで開発したグラフィックLCDのドライバーST7565.cのSTM8Sへの移植にとりかかる。変更しなければならないところは、2つ。ひとつは、SPIの原始関数と、もうひとつはフォントデータの取り出し方である。

SPIの部分は機械的な変更だが、フォントの部分は少し厄介だ。オリジナルは、.oファイルでプログラムフラッシュ上に展開されているので、すべてのキャラクターのフォントデータは一発でリニアに辿り付けるが、今度はそうはいかない。

 外付けフラッシュ上のデータは、今度のメモリ(M25P40)ではリニアで読み込めるとしても、文字キャラクターを読む度に、その都度フラッシュから読み出す手順が加わる。それに複数のフォントの対応でフォントデータの属性を換えるときに、いちいちフラッシュに読みに行くことは避けたい。

 久しぶりの本格的な、C言語の開発である。フォントの属性を決めるフォント構造体を定義して、ここに各フォントの属性(縦横のフォントサイズ、フォントデータの始点フラッシュアドレスなど)を入れる。

 あらかじめ、Open_Font()のような関数でフラッシュからフォントファイルのヘッダーを読み込み、このポインターを各グラフィック関数に渡すことにした。これで、いちいちヘッダーを読み直すオーバーヘッドがなくなる。構造体のポインターはChaNさんのFatFSで盛大に使われている。時々、そのソースをカンニングしながら、せっせとコーディングに励んだ。

Stm8

SPIの移植はすぐ済んだが、フォントデータが出てくるまで一苦労(2/11/2013)
 SPIの方はすでにフラッシュメモリで動いているので、移植はGLCDのSPIの原始関数をSTM8S用に換えるだけである。GLCDの操作に必要なI/Oピンも一緒に定義する。

 フォントデータのハンドリングを含めて、ソースコードがあらかた出来上がった。ハードウエアの準備はブレッドボード上のGLCDのジャンパーを、つなぎかえるだけだから簡単に終わった。さあ、テストである。これまで書いてきたコードが思ったように動くか。わくわくどきどきの瞬間である。

 GLCDは、最初は文字ではなくグラフィックの直線が出るかのテストをする。文字フォントは画面に出す前に、UARTのコンソールにフォントデータを表示するテストルーチンを組み込んで、こちらで確認する。いつもの逐次開発法である。

 電源を入れてUARTからコマンドを打つ。画面は全く反応なし。うーむ、残念。鉄則に従って、直したところを重点的に調べる。問題なさそうだ。面倒だが、再びロジアナのプローブをつなぎ直して波形を見る。あ、あ、追加したGLCDのI/Oピンで動いていないところがある。

  ロジアナの威力は絶大である。リセットとコマンド/データ切り替えのポートの定義を忘れていた。ピンだけ定義しても動くわけはない。あわてて修正。よーし、画面に変化が出た。ゴミだらけの画面だが動いたことには変わりはない。

 フォントデータの出力はもう少し手間がかかった。最初は、読んできたフォントサイズのXYの値がでたらめで表示が出来ない。バイナリが表示できるターミナルソフトで読むと、.oファイルは、実際のファイルの前に、余分なヘッダーをつけていることがわかった。しかも、長さが実際のデータより短い。

Photo

 .oファイルでなく、.fntファイルを使えばソフトの変更はしないですむことに気づいた。長さが違うのは、ファイル転送がバイナリーモードでないため0x20以下の制御コードが無視されていた。考えてみれば、検証のときは200KBが通ったと言っても全部テキストファイルだった。やれやれ。

GLCDにグラフィックは出たが文字が乱れる。ハードエラーだった(2/12/2013)

 画面に少しづつ画が出始めてきた。画面の汚れは、初期化のミスだった。フォントのハンドリングも細かい不具合がとれて文字らしくなってきた。しかし、読むたびにフォントデータがずれる。フラッシュメモリだけのときは、こんなに不正確ではなかった。

S_p2125683

 その日は画面の記念撮影をしただけで店仕舞いする。寝ながら色々原因を考える。GLCDとSPIを共用してからのエラーだ。原因はハードくさい。はっと気づいたことがある。フラッシュメモリには、電源にパスコン(0.1μF)を入れていない。これだ。

 次の日、久しぶりにハンダごてに火を入れて、フラッシュメモリ(M25P40)にコンデンサーをハンダ付けする。さあ、どうだ。おおお、良いぞ。フォントが正しく表示される。うーむ、まだ、たまにキャラクターがずれる(その後、ブレッドボードの接触不良がわかり、ほぼ完全になった)。

 フォントが少し乱れるが、何とかSTM8SでGLCDに文字フォントを出すことには成功したようだ。達成感で胸がふくらむ。SPIにプルアップ抵抗を追加すれば、もっと正確になるかもしれない。とりあえず、このあたりで一段落することにする。ソースと回路図を公開することにしよう。

 ただ、ソースコードは、まだ開発途上の半かけで、回路図もブレッドボードを使った、あくまでも暫定的な回路図であることをお断りしておく。STM8Sの公開ソースが少なそうなので、とりあえず少しでもお役にたてたらという気持ちである。

Glcd_stm8s

 使用SRAMは、1800バイトでほぼ満杯だが(ファイル転送ルーチンを別にすればもっと下がる)、フラッシュは17KBとまだ半分しか消費していないのでアプリケーションには余裕がある。

  これからSTM8Sで開発される方の参考までに、所長がこれまでに、このRaisonanceのCコンパイラーとリンカーで遭遇した注意点(不具合の可能性もあり)をまとめておく(前回の分も含む)。

  • ヘッダーファイルを使った関数のプロトタイプ宣言で、エラーが出たり警告が出て別ファイルにした関数の引数渡しがおかしくなることがある(原因不明。当該関数をひとつのファイルにまとめて回避)。警告は同一ソースを変更せず2回コンパイルすると、なくなってしまうので注意。
  • 外部変数と外部参照宣言はタイプが違うと同じ名前にしても警告にもエラーにならない。当然別の変数として取り扱われるので大はまりとなる。これはもしかして他のコンパイラーもそうかもしれないが、重複名の定義は確かリンカーで警告を出してくれたような。
  • dataという変数名は予約語である。SRAM上に定義する変数の識別子のひとつだそうだ。エラーの説明が実態と全く違うので最初は気づかずにはまった。
  • memcpyなどの標準関数が、<string.h>などとしても正常に組み込まれていない。エラーにもならない。最初GLCDの画面が汚れていたのはこれが原因(自前でクリアする手順をつけて回避)。

 以下にSTVD(STMicro Visual Develop)のプロジェクトフォルダーを固めたものを置きます。ライブラリのリンクは、ご自分の環境に合わせて必要なファイルをプロジェクトに入れてください。(STM8S_StdPeriph_Lib_V2.1.0を使っています) STVDの環境で、glcd.stwを開けば一式が読み込まれます。Buildは最初からやってください。

「JCG12864.zip」をダウンロード

| | コメント (2) | トラックバック (0)

« 2013年1月27日 - 2013年2月2日 | トップページ | 2013年3月17日 - 2013年3月23日 »