16セグメントLEDを使ってPCサインボードを作る

16セグメントLEDでUSB経由PCコントロールのサインボードを作る・ソフト編


検索で直接ここに来られた方へ。
このプログラムは16セグメントLEDを使ったサインボードに関するプログラム部の解説です。
ハードはここを参照してください。


サインボードのプログラムはPIC側とPC側の両方が必要になります。
通常なら、PIC側はPIC18F14K50のUSB制御部分を作らなければなりませんが以下の様に、サボっています。
製作したのは、16セグメントLEDを制御する部分のみでUSB部分はそのまま利用しています。

まずは、microchip社からUSBのフレームワークを入手します。これは必須です。
インストール(ファイルを展開するだけ)すると、標準でC:ドライブにMicrochip Solutionsフォルダができます。
普通は、これを変更して目的のプログラムに仕立てるのですが、KBC-USBminiの説明書に、サンプルプロジェクトがダウンロードできるURLが記載されていました。
ここからプロジェクト一式を入手して、これを元に変更する事にしました。
無保証となっていましたが、少なくともそのまま動作するプログラムが手に入るので助かります。
コンパイラは、同じくmicrochip社からフリー(期間限定版)のC18コンパイラを入手します。
PICで遊ぶ人はMPLABをいれていない事は無いと思いますが、MPLABも必要です。

ここから、PICのソフトの変更に入ります。元になったのは共立電子からダウンロードしたプロジェクトです。
これに、同梱されている説明に従って、Microchip Solutionsフォルダに一式コピーします。

今回の追加変更はmain.cが殆どです。一式を公開するとマズそうなので、main.cのみを公開します
このmain.cを共立からダウンロードしたプロジェクトの中のmain.cと入れ替えて使用してください。

⇒USBの通信内容
・PC側から表示器に向かう(OUTパイプ)データの内容です。
一回の転送は64byteの固定長です。
使用しなかった部分は0xFFで埋めます。

表示
 +0 +1  +2〜+61 
 P コントロールbyte   表示データ(0x00終端)

EEROM書き込みと表示
 +0 +1  +2〜+61 
 W コントロールbyte   書き込み & 表示データ(0x00終端)

シフト表示のスピード設定
 +0 +1 
スピード (0x00〜0xFF)

状態の返送要求
 +0
 R

先頭のPやWはコマンドでASCIIコードによる文字です。
コントロールは現データの扱いを指定します。
コントロールbyteの内容
b7  b6  b5  b4  b3  b2  b1  b0 
即時更新  16文字表示   シフト表示


・表示器からPCに向かう(INパイプ)データの内容です。
一回の転送は64byteの固定長です。
使用しなかった部分にはゴミデータが入っています。

表示状態の返送要求に対する応答
 +0 +1 
 R 状態byte 

状態byteの内容
b7  b6  b5  b4  b3  b2  b1  b0 
EEROM書き込み中  - - 更新待ちデータあり


⇒割り込みルーチン
LEDの表示はダイナミック表示の場合は定期的に書き換えをしなければなりません。それに、時間がばらつくと、そのままLEDのちらつきとなって現れます。ダイナミック表示の場合、割り込みを使う方が簡単になります。
幸い、元のプログラムは10KHzで割り込みを使っていたので、そのまま失敬して、ここに16セグメントLEDに繋がっているシフトレジスタ(TC4094)にデータを送り出すルーチンを入れます。
シフトレジスタはデータとサンプリングクロックを使う方式で、SPIに近い形式です。
ハード的にはPIC18F14K50のSDOをデータに、SCKをクロックに結線しました。これを、内蔵ハードの同期シリアルでコントロールする作戦です。
実際は紆余曲折がありましたが、内蔵ハードでそのまま動きました。
ただ実験中の副産物で、10KHz割り込みの発生源をTMR2からCCPの一致割り込みに変更しています。
これはSPIの送信クロックがシステムクロックを分周したものか、TMR2から得られるクロックのどちらかを選ぶ仕様になっているためで、TMR2を使って、転送速度を任意に変えて実験したかったためです。
(TMR2をクロックにする場合は、PIC内部にハード的なエラーがあります。microchipからシリコンエラッタを入手すると回避方法が記載されています)
最終的には内部クロックで動作させましたので、TMR2は未使用で余っています。
SPIでハード的に8bitが転送されるので、送信レジスタにデータを入れてから割り込みを抜ければ、次の割り込みまでに転送されている寸法です。
4桁ダイナミックの部分には1組に付き、2個のTC4094が使われています。16桁表示では4ユニット、合計8個のTC4094が直列につながっています。
ソフト的には8byteのデータを送信すると全てのTC4094にデータが揃う事になります。
データが揃った時点でLT信号(PICのRC1ポート)を使ってシフトレジスタのデータをパラレルラッチに読み込ませます。
TC4094の良いところは、シフトレジスタ部分とパラレルラッチの両方が入っている点で、シフト中も出力データは変化しません。LT信号を使ってシフトデータをラッチさせれば、出力に反映される仕組みになっています。
今回の表示ユニットの構成はLT信号が全て並列に結線されているため、表示の切り替えタイミングは同時となり、バラ付きは発生しません。
ダイナミック表示は8バイト送信毎にLTの操作とLEDのコモン選択を移動する作業を4回繰り返して、一巡します。
従って、10KHz÷8byte÷4桁ダイナミック=312.5Hz がLEDのリフレッシュレートになります。
(リフレシュレートに余裕があるので、32桁表示にしても、間に合うハズです)
説明が前後しますが、シフトさせるデータを送り出す順番です。
先に送り出したデータが上位の桁に向かってシフトして行きます。最初に送り出したデータが最上位に、最後に送り出したデータが最下位の表示データとなります。
また1つの4桁ダイナッミク表示の中では、先にデータが入るTC4094がセグメントA1〜F、次につながっているTC4094がセグメントG1〜Mを担当します。

表示ルーチンに渡すデータは16bitとしてmainルーチン内で配列をグローバル宣言しています。
412行目:volatile WORD disp_data[16];
配列先頭が上位桁(左の桁)、16データ目が最下位桁(右の桁)に対応します。
8文字表示の場合はdisp_data[8]〜disp_data[15]に表示するデータを格納します。
格納形式ですが、文字コードではなく、セグメントデータそのものを入れます。

b15がセグメントMに、b0がセグメントA1のデータになります(1=点灯、0=消灯)
b15  b14  b13  b12  b11  b10  b9  b8  b7  b6  b5  b4  b3  b2  b1  b0 
 M  L  K  J  I  H  G2  G1  F  E  D2 D1  C  B  A2 A1 
disp_data[]のbit割り当て

その他の割り込みルーチン
10KHzを100分周した10mS単位のタイマーを追加して、シフト表示のタイミング用としました。
EEPROMに退避すべきデータが発生した場合はstring_buf2の内容をEEPROMに書き込んでいます。
mainではなく割り込み内でEEPROMに書き込んでいるのは、EEPROMの書き込みシーケンスを発動させるキーコードの書き込み途中で割り込みが発生すると、書き込みが起動しないための回避策です。
(割り込みルーチン内では、割り込みが発生しない理屈ですが、lowプライオリティーの割り込みルーチン内で作業しているためhighプライオリティーの割り込みが発生する可能性があります。今のところ不具合には遭遇していません)
それと、一度に書き込むデータ数が多いため、割り込みで自動的にEEROMに書かれた方が都合が良い面もあります。

⇒メインルーチン内部
表示データのハンドリングはメインルーチン内のProcessIO関数内にあります。
ProcessIOはメインの永久ループから定期的に呼び出されますが、タスクを回す方式で動作しているため、早急に終わらせなければ他のタスクが止まってしまいます(協調マルチタスクの超簡易版みたいな感じ)
時間待ち等で無効時間が発生する処理の場合はCPUを占有するのではなく、ステートマシンを構成して一回の占有時間を短くする配慮が必要です。
今回のLED表示では、時間待ちになる処理は無かっため、単純に処理しているだけです。

・シフト表示ルーチン
シフト表示はProcessIO関数の先頭付近、BlinkUSBStatus();の次からになります。
PCからの表示文字列(またはEEROMに記録された文字列)はstring_buf[]に格納しています。
string_bufは93byteのchar配列になっており、表示用のASCIIコードを格納します。
シフト表示では、表示位置に合わせてシフトした位置のstring_buf内容を16文字分、セグメントデータに変換してdisp_dataに格納する処理を行います。
このタイミングを作っているのがtimer1変数で、10mSを単位とした数値を入れると、割り込みルーチン内で0に向かってカウントダウンされるタイマーになっています。
timer1==0を捕らえてシフト処理を行っています。

・USBからのデータハンドリング
USBのOUTパイプの受信ルーチンを変更して、表示文字をstring_buf2[]に代入する処理を行います。
代入先がstring_bufではなく、string_buf2になっているのは、いきなりstring_bufに代入すると有無を言わせず表示が更新されてしまうためで、一旦string_buf2に代入して、ころあいを見てstring_bufにコピーしています。
このため、表示文字のバッファーは二段構成になっています。
表示のコントロールを入れるbyte変数もdisp_modeとdisp_mode2の二段になっています。

**C18コンパイラでは一つのモジュールで使える変数のサイズが256byteの様です(理解不足と思われるが)
このため、string_buf2はexternを使い、他のモジュールで宣言しています。
もしこのプログラムを移植されるのであれば、main.c以外のモジュールに以下の宣言を追加してください。
extern unsigned char string_buf2[93];
unsigned char string_buf2[93];
私は、新たなモジュ−ルを作るのが面倒だったので一緒のプロジェクトにあるusb_device.cに入れました。

セグメントデータはROMの上に16bit長で作成しました。
ROM WORD seg_data[]= 以下セグメントデータ
作った順番が適当なので、ASII文字からセグメントデータへの変換テーブル ascii_pt[] を同時に作っています。

⇒その他の情報
EEPROMの内容は、電源を入れた際に表示されます。
一番最初のEEPROMの内容はmain.cの239行目から定義しています。
#pragma romdata eedata_scn=0xf00000
rom BYTE dummy[] = {0,0,0};
rom BYTE ctrl = 0b00000001; //bit1=0:8文字表示、bit0=1:シフト表示
//表示文字(シフト表示)先頭に16文字、後方に7文字の空白が必要
rom BYTE eedata_values[] = "                ** 16seg Display Control by KBC-USBmini Ver1 **       ";
#pragma romdata
この部分のコードはEEPROMに配置されるべきデータですが、リンクファイルに定義がないとエラーになる様です。
削除してもPCから書き込みできるので、問題ありません。
またはリンクファイル「rm18f14k50.lkr」の中のCODEPAGEに次の行を追加します。
CODEPAGE NAME=eedata START=0xF00000 END=0xF000FF PROTECTED


⇒PC側のプログラム
KBC-USBminiのプロジェクトはUSBのHIDを使ってPCと接続しています。HIDならドライバがwindowsに入っているので、用意する必要がありません。
一方、PCのアプリはC#で書かれています。
コンパイラはmicrosoft社からフリーのVisual studio 2008 exepress が入手できます。
他の言語で書き直す根性もないので、C#でゴリゴリ作りました。
とは言っても、殆どできているソフトを変更しただけですが。

C#使いではありませんので、見よう見真似で作っています。
なれた人からみれば、バカな事やっているな...って事になってそうですが、大目に見てください。

こちらのプログラムの元はやはりmicrochip社のMicrochip Solutionsフォルダに入っています。
共立が追加した部分は殆ど変更しましたので、こちらは全て公開しても問題ないと思います。
なので、こちらからダウンロードしてください

解凍して出来たフォルダの中の「HID PnP Demo.csproj」をダブルクリックするとMicrosoftのC#2008が起動します。
microchipの元になったプログラムは「C:\Microchip Solutions」「USB Device - HID - Custom Demos」「Generic HID - PnP Demo - PC Software」「Microsoft Visual CSharp 2005 Express」の中です。C# 2008で起動すると2005から自動変換されます。
C#プログラムの中でUSBとのやり取りは
private void ReadWriteThread_DoWork(object sender, DoWorkEventArgs e)
のスレッドで行われています。
スレッドはwindowsメッセージ関係とは非同期に実行されていると思われます。
USBの送信や受信のデータはコントロールに渡したり、コントロールから指定する事になりますが、スレッド内から直接コントロールのプロパティにデータを書き込むと、不具合があるかも知れません。このため、コントロールが発生させるイベントからスレッドへの中継も含めて、変数で中継しています。
ボタンやスライドバーのイベントはスレッドに渡すデータを組み立てているだけです。

ちなみに、このスレッドは、USBのIN方向にデータを定期的にポーリングするために利用されている様ですが、今回の表示ボードでは、OUT方向の一方通行しか利用していません。

最終目的は、メールサーバにアクセスして、メールが到着すれば、メッセージを表示させたいと思っていますが、出来ていません。
根性が続かない...