アクセスカウンタ

プロフィール

ブログ名
KazHatブログ
ブログ紹介
Palmwareを気の向くまま作っています。
このブログでは、最近購入したSheevaPlug+について書いていこうと思います。Linuxも初めての初心者に使えるかどうかわかりませんが、先人の知恵を借りながらチャレンジしていきます。
PICはじめました。最近は、ロビのことしか書いていませんね。
zoom RSS

長年のナゾ@キーボードonWin10、ようやく解決

2016/08/16 18:03

私のPCのキーボードは、Unixマシンを使っていたことや、emacsを常用していたことから、キーボードのAの左隣のキーは、ずっとCtrlキーとして使用していました。

そのため、通常左CtrlキーとCaps Lockキーを入れ替えを何らかのソフトや設定で行っていたのでした。

 

しかし、昨年Windows10にアップグレードした時、使っているキーボードの挙動がおかしくなっていました。それは、左CtrlキーもCaps Lockキーも同じキーコード、つまり、同じ機能になってしまったのです。そのため、元のCaps LockキーにCtrlキーの機能を割り当てると、左下のCtrlキーもCtrlキーの動作のままとなり、Caps Lockを行うボタンがなくなってしまっていました。

 

Caps Lockを使うことはないので、それでも良いと思っていましたが、今日、ふと、デバイスマネージャーのキーボードのドライバの詳細をみたら、見慣れないCtrl2Cap.sysというファイルが設定されていることに気づきました。

これは、何だろうと調べてみると、Mark Russinovichという方が作成したCtrl2capというソフトということがわかりました。

そこには、アンインストールのやり方(ctrl2cap.exe /uninstallと実行)も書かれており、実行したら、Ctrl2capのドライバが削除されました。(管理者権限のコマンドプロンプト上で実行し、要再起動)

このドライバ、c:\Windows\System32\driversの下にあったのですが、日付が2012年と古く何か怪しいと感じたのですよね。それから、調べてみたら上記の情報にたどり着いたという次第。

 

まったく記憶がありませんが、何かの時に、これをインストールしてみたのでしょう。でもその存在を忘れ、確かWindows7の頃からレジストリに書き込む手法をとっていて、なぜかWindows10にアップグレードした時、そのドライバがSystem32に残っていたので、自動的に適用されてしまったのではないか推察しています。

 

このCtrl2capをアンインストールすると、当該フォルダのctrl2cap.sysは、簡単に削除できました。

すると、Caps Lockと左Ctrlキーは、それぞれ元の機能に戻りました。

 

そして、いつものようにregeditを起動して、

\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

の下に、Scancode Mapを作成します。これは、Keyboard Layoutの所で、マウス右クリック→新規→バイナリ値で新しい名前を作成して、名前をScancode Mapに変更します。

そして、それをダブルクリックして、編集画面を出し、次を設定すれば完了です。
00 00 00 00 00 00 00 00
05 00 00 00 1d 00 3a 00
3a 00 1d 00 01 00 29 00
29 00 01 00 00 00 00 00

上記は、データだけなので、ご注意を。

終端コードの0000まで、5組のデータがあることを示しており、1d00が左Ctrl、3a00がCaps、Esc=0100、半角/全角=2900で、それぞれ、コードを入れ替えていることを示しているとのこと。

 

ともかく、古いドライバを間違って使っていたことが分かって、めでたしめでたしということでした。

 

では、また。

記事へブログ気持玉 / トラックバック / コメント


ダイソン掃除機にPICが入っている

2016/08/12 22:52

少し、PICをいじる時間がなくなっている今日この頃です。

 

たまたま、興味深い記事を見かけました。

 

ダイソンの掃除機の分解解析記事です。日経テクノロジーに掲載の「分解スペシャリストが見た!スゴイ製品その中身」という連載で、「

人気も吸引力も衰え知らず、Dysonのサイクロン掃除機」という記事です。

Dyson DC62という機種を分解して、解析したというものです。

なお、リンクの記事は、8/26までだけ一般無料公開ということなので、お早めに。

 

興味を持ったのは、Dysonの掃除機のメイン基板にPIC16F1936のSOPタイプのものが使用されているという所です。普通(?)の家電で、PICが使用されているものがあることを初めて知りました。
マイコン制御という言葉はかなり昔から聞きましたけどね。最近は死語かも。

 

上記は、メイン基板ですが、バッテリ部のサブ基板というものもあり、そちらには、PIC16LF1827が使われているとのこと。

 

16F1936は、使ったことがありませんが、16F1827は、PIC時計に使いました。

16F1936は、28ピン、8KW、25I/O、最大動作周波数32MHzという感じのスペックで、特筆するような機能はなさそうです。

 

実は、我が家では、かなり前のDC12という機種のDyson掃除機を使っています。もしかしたら、これにもPICが入っているのかもしれません。

なんとなく、親近感がわきました。

記事へブログ気持玉 / トラックバック / コメント


SheevaPlug+からWindows10のファイルアクセス

2016/08/11 18:15

Windows10のアニバーサリー版がリリースされたというニュースがありました。

順次自動アップデートされるという話でしたが、私のPCには、1週間経過した8/9になっても、その気配がありません。待ちきれなくなり手動でアップデートしました。

 

Windowsメニューに出方など色々変わっている(改善されている)ところもあるようですが、それはネットの情報にお任せしたいと思います。

 

このアップデートが原因かどうかわかりませんが、SheevaPlug+からWindowsのファイルアクセスができなくなってしまいました。

その回復に少し苦労したので、その経緯を書いておきます。

 

以前記事にしたように、リモートアクセスのプログラムを動かすなど、ファイルの共有で対応しているので、ファイルアクセスができなくなると、所望の動作ができなくなるので困ります。

 

ファイルの共有は、sambaの機能を使って、SheevaPlug+側でPCのフォルダをマウントすることで実現しています。

 

例えば、以下のような感じでマウントします。

sudo mount -t cifs //192.168.1.12/sharedFile /media/sharedFile/ -o iocharset=utf8,file_mode=0777

 

192.168.1.12というのは、LAN内で割り振られているWindowsPCのURLです。

sharedFileというフォルダを、SheevaPlug+の/media/sharedFileというディレクトリに割り付けるわけです。そのほかのオプション、cifsは、Windowsのファイル共有サービスを使ってアクセスできるようにするものみたいです。文字コードをiocharsetで指定しています。utf8にしているのは、SheevaPlug+側がそれでないとうまく漢字を表示できないからです。file_modeは、unixのファイル属性でおなじみのもので、ここでは、読み書きOKの設定にしています。

 

一方、Windows側では、sharedFileというフォルダのプロパティで共有設定を行っておく必要があります。

image

共有タブの共有...を押して、出てきたウインドウで、Everyoneを選び、追加ボタンを押します。

image

アクセス許可レベルは、読み取り/書き込みとして、フルアクセス可能に設定しました。

最後に、右下にある共有ボタンをおして、次のウインドウで、終了ボタンを押して、設定完了です。

 

普通なら、ここで、SheevaPlug+側で先ほど示したように以下のようにマウントすればOKのはずです。

sudo mount -t cifs //192.168.1.12/sharedFile /media/sharedFile/ -o iocharset=utf8,file_mode=0777

しかし、Permission deniedというエラーが出て、マウントできません。

今までできていたはずなのに、結構な時間解決できず、調べる時間もなく放置状態でした。

 

ようやく時間ができて、よく見てみると、上記のsharedFileのプロパティ画面の一番下の下の資格の中、パスワード保護のところを見ると、共有フォルダにアクセスするには、ユーザーアカウントとパスワードが必要と書かれていることに気づきました。

設定を変更した方がよさそうなので、リンクをクリックしてみました。

 

ウインドウが出てきました。大きく3つの設定区分がありました。

ゲストまたはパブリックのところが、以下のように、ファイルとプリンタの共有を無効にするとなっていたので、有効する方を選択しました。

image

さらに、すべてのネットワークの所をクリックして広げると、2か所関係ありそうな箇所がありました。

パブリックフォルダーの共有の所と、パスワード保護共有の所です。

image

これを上記のキャプチャのように設定して、変更の保存ボタンを押したら、このウインドウが消えました。

 

そうして、再度SheevaPlug+側でマウントさせてみると、ようやく、マウントされ、ファイルにアクセスできました。

 

昔は、どうやったのか記憶にないのですが、たぶん、ちゃんと設定したのでしょうけど、やり方をすっかり忘れてしまったということだと思います。

 

これで、また元通りになりました。

 

今回は、これだけ。

記事へブログ気持玉 / トラックバック / コメント


焦電型赤外線センサ (D203B) を使ってみた

2016/07/21 20:36

焦電型赤外線センサ、いわゆる人感センサを使ってみようと思います。

 

すでにたくさん情報あるので、大した情報にはならないかもしれませんけど。

 

秋月電子さんから、焦電型赤外線センサD203Bを購入しました。100円でした。

それと、フレネルレンズ S9001も合わせて購入しました。このレンズは、Senbaの焦電センサがすっぽり入るとしか書かれていませんが、D203BがNanyang Senba社となっているので、これだと判断したわけです。

 

ネットでよく見かける記事では、センサはAKE-1 (RE-210)を使ったものがほとんどでした。

これを使ったら、情報も多く簡単そうです。

でも、レンズのことを考えると、ちょっと心配でした。

 

図面を見る限り、RE-210の外形が8.3mmΦ (カタログには、RE-200Bしか掲載されていなかったので)で、D203Bが8.2mmΦと違いがあります。 一方、フレネルレンズ側で、センサがはまるところの内径が8.2mmです。

 

秋月さんのページのコメントにあるようにD203Bなら、すっぽり入るでしょうけど、RE-210だときつかったりして入りにくかったり、使えないと困ります。

 

心配なので、D203Bを購入することに決めたわけです。

もう一つの心配の使用例がほとんど見つからないという所は、参考回路を参考にすることで対処することにします。

 

回路

回路は、D203B参考技術資料焦電型赤外線センサーとPIC12F675を使った人間検知LED照明制御回路の実験アナログ工作にはまる:焦電型赤外線センサーユニットの制作に記載の回路図を参考にさせていただきました。

 

部品の種類を削減するため、最終的に以下のような回路にして実験です。

PICは、12F1822をとりあえず使うことにしました。

 

image

 

焦電センサからの出力をアンプで増幅して、PICでAD変換して検出しますが、その時の値を確認するため、例のディスプレイとEUSARTと接続してデバッグします。

また、LEDも付けて、人を感知したらLEDを光らせる実験もできるようにしました。

 

回路は、焦電型赤外線センサーとPIC12F675を使った人間検知LED照明制御回路の実験のものとほとんど変わりません。

2段目のアンプのバイアス電圧は、手持ちの抵抗を使ったので、10Kになっていますが、VDD/2を作っているだけなので、同じですね。

 

第1段の非反転増幅で22.3倍、第2段の反転増幅で21.3倍と、合わせて約474倍の増幅をしています。D203Bのアプリケーション例では、1段目は同じですが、2段目が180倍増幅になっていたので、約10倍もの差があります。アプリケーション例では電圧が12Vとなっていたので、3.3V位を考えていた私の回路を考慮すると、まあまあのところでしょうかね。

 

もっと詳しい説明は、赤外線検出ユニット 回路説明をご覧になるのがよいと思います。

私が説明しなかった、コンデンサに関する説明もされています。

 

電源を入れて、2段目のオペアンプの出力電圧を手持ちのマルチメータで測定してみると、通常時1.7V前後、手をかざすと、2V程度になっていました。

電圧は、かなり揺らいでいました。1.7Vというのは1.6V〜1.8V位ですかね。

実際には、人がセンサの感知範囲に入ると、2Vぐらいになった後、1.7Vに戻り、さらに、感知範囲から人がいなくなると、今度は、1.3Vに下がるという反応を示していました。Webのどこかで見たような記憶があるのですが、センサは、赤外線が変化したことを出力しているようです。一種の微分波形のようなものと思えばよいですかね。

 

今回は、人がセンサの範囲外から範囲内に入ったことを検出するだけにしてみました。

つまり、定常電圧から、一定レベル以上の電圧になったら、人が範囲内に入ったと判断するわけです。

 

PICのプログラムとしては、増幅したセンサからの出力AD変換させ、適当なしきい値を超えたら人を感知したと判断させればよいので簡単そうです。

 

プログラムの作成:準備編

では、PICのプログラムを始めましょう。

 

すでに説明していますが、たまに書いておかないと古いものを検索するのも面倒になるので、詳細を書いていきます。

 

いつものように、MPLAB X IDEを立ち上げて、新規プロジェクトを作成します。

imageを押して開始します。

  1. Choose Project:Standalone Projectを選択して、Next >を押します。
  2. Select Device:今回使用するPIC12F1822を探して選択して、Next >を押します。
  3. Select Header:特に何もせず、Next >を押します。
  4. Select Tool:書き込み用ツールを選択します。私の場合PICkit2しか持っていませんが、今回は赤ランプが付いていて選択できません。次に進めるため、緑ランプのものを適当に選択して、Next >を押します。MPLAB Xの統合機能が使えないですが、大きな問題はないと思っています。
  5. Select Plugin Board:ここは、自動的にスキップされました。
  6. Select Compiler:XC8を使うので、その最新版のXC8 (v 1.37)を選んで、Next >を押します。
  7. Select Project Name and Folder:プログラムを格納するフォルダとプロジェクト名を設定します。Encodingは、Shift_JISがよいでしょう。 Set as main projectにチェックが入っていた方がコンパイルする時、面倒がないと思います。以下のような感じです。

image

最後にFinishを押すと完了です。

 

プロジェクトの準備ができたら、まずは、MCCを使って、基本のプログラムを生成してもらいます。

imageを押して開始します。

以下は、すでに設定が済んだ状態のキャプチャですが、説明しやすいと思って、最初に掲載しておきます。

image

最初は、左上のProject Resourcesの中、Systemの下の3項目だけが登録されているはずです。

 

最初に、System Moduleを選択 (初期時に選択されていると思います)します。Easy Setupで、500kHz動作にしてみました。そんなに高速動作はいらないと思ったからです。

そして、PLL EnabledとLow-voltage programming Enableの2つのチェックを外しました。

これで、System Moduleのところは、完了です。

 

次に、Pin Moduleと思いましたが、必要なリソースを追加します。

左中央のDevice Resourcesから今回必要なADCと、EUSARTを追加します。

ADCをクリックして展開し、ADCが現れるので、それをダブルクリックします。そうすると、左上のProject ResourcesのPeripheralsにADCが追加されるはずです。

同様に、EUSARTをクリックして、その下のEUSARTをダブルクリックします。

そうすると、Project ResourcesのPeripheralsにEUSARTも追加されるはずです。

 

また、画面下部にPin Managerの所が、初期状態から変わるはずです。

 

ここで、Project ResourcesのPin Moduleをクリックします。

そして、どのピンを有効にするか設定していきます。それは、下部のPin Managerのウインドウ内で行っていきます。

 

今回、PIC 12F1822の8ピンDIP品を使っているので、Packageの所でPDIP8を選択すると、実際のPin Noと表示が合います。12F1822では、どのPackageを選択しても、Pin Noは同じですね。

 

ADCは、アンプの出力をつないだRA2ピンの所をクリックします。すると、緑の背景で、カギがかかったアイコンに変わり、Pin Managerウインドウの上にあるPin Moduleというウインドウに1行追加されると思います。

そうしたら、いくつか設定しておくとよいかもしれません。設定するのは、Custom Nameの所、ここは、プログラムするときわかりやすい名前にすればよいと思います。今回は、PIRとしました。それと、初期時WPUにチェックがついていたので、ここのチェックを外しました。

 

そして、EUSARTの所は、ディスプレイとつなぐピンの設定として、TXの所で、RA4をクリックします。RXの方は設定しません。

上のPin Moduleウインドウで、RA4の所を設定します。Start HighとOutputにチェックを入れました。

 

最後のピン設定は、LED用にGPIOのoutputの所で、RA5をクリックします。

Pin Moduleの設定では、Custom Nameの所をLEDにしてみました。 Outputにはチェックが入っていると思いますが、WPUの方は、チェックを外します。

 

これで設定は完了です。右上のウインドウには、使用するピンの所が緑色に変わります。

ピン名で設定できるところには、自分で命名したPIRとかLEDという名称が表示されるので、配線する時も間違いにくくなっています。

Pin Moduleの完了した時の状態が以下となります。

image

RA0,RA1は、プログラムを書き込むときにPICkit2と接続するDATとCLKになるので、使用を避けています。デバッグ中は、PICkit2と接続したままにしているので。

 

もう少し設定が残っています。Project ResourcesのADCをクリックします。

ここでADCの設定を行います。基本的に初期状態のままでよいかと思いました。Result Alignmentの所は、rightにしました。これは、10ビットのAD変換した数値を16ビットに詰め込むときに上位ビットのMSBから詰めていくか、LSBから詰めていくか指定するものです。通常は、数値として計算しやすい、LSBが詰めるright (右詰め)が使いやすいと個人的には思っています。

他は、変更していませんが、キャプチャしておきます。

 

image

 

最後に、EUSARTの設定します。ここは、デフォルトのままですね。

非同期、9600ボー、8ビット、パリティなしという状態です。これは、受信側の設定と合わせる必要があります。

image

 

これで、プログラムを生成します。

左上のProject Resourcesの右のGenerateボタンを押します。この時、設定ミスとかで、上記キャプチャでも黄色のびっくりマークのタブが見えますが、そこでエラーがあると生成されません。Notifications:0でなく、1以上の数値の時は、中身をチェックする必要があります。中には、エラーでないものも含まれるので、0以外の数値になっていても生成できる場合もありますが、中身はチェックしておくとよいと思います。

 

ちゃんと生成できると、下部のPin ManagerのウインドウがOutputに切り替わり、Generation completeの文字が表示されていれば、OKです。

 

そうしたら、Project Resourcesのウインドウの上に見えるProjectsのタブを押して、ファイルが見えるようにしましょう。

 

私の場合は、以下のような感じになりました。

image

 

プログラム作成編

これから、main.cを書き換えていきます。その他のプログラムは、必要に応じて参照するとよいと思います。

 

main.cをダブルクリックすると、ファイルが開き、ウインドウ中央部に表示されます。

 

今回は、PIRピンの電圧をAD変換して、ある一定の値になったら、LEDをonさせるという形にしようと思います。

 

void main()内に必要なコードを加えていきます。

初期化は、SYSTEM_Initialize();により、行われるようにすでにプログラムが書かれていると思います。

今回は、割り込みも使わないので、コメントになっている割り込み関係の所には手を付けません。

 

そして、以下のような感じにしました。while (1) { }は、最初から記載があると思います。それを置き換える感じでプログラムを書きます。

 

adc_result_t data;

 

__delay_ms(1000);
EUSART_Write(0x0c); //画面クリア
SetCursor(0, 0);
DisplayChar("PIR");

 

while (1)
{
    // Add your application code

    // PIRからの入力レベルをAD変換
    data = ADC_GetConversion(PIR);
   
    // AD変換した値をEUSARTに出力
    SetCursor(0, 0);
    DisplayValHex(data >> 8);
    DisplayValHex(data & 0xff);
    __delay_ms(500);

 

    if (data > 0x240) LED_PORT = 1;
    else LED_PORT = 0;

}

たったこれだけという感じですが、これだけです。 (ディスプレイ関係の関数は除く)

 

whileの無限ループの前に、4行処理があります。これは、別途作成したデバッグ用のLCDディスプレイにメッセージを表示するもので、画面をクリアして、PIRという文字を試しに表示しているだけdす。 EUSART_Write()という関数は、自動的に定義されるものです。これで通信してくれます。SetCursorとDisplayCharは独自関数ですが、説明は省略します。ここも、中身は、EUSART_Writeを行うのですが、わかりやすく、カーソル位置を設定したり、文字列を表示できるようにしたものです。以前示したと思うので、それを探してください。

 

whileの中がメインのプログラムとなります。

PIRと名付けたピンの電圧をAD変換して、16ビットのデータとしてdataという変数に格納しています。

そのdataをディスプレイに16進数で表示させるための4行があります。

そして、最後のif文以下2行が、そのdata値に応じて、LEDを光らせるかどうかを決めています。

 

これをコンパイルして、エラーがなければ、バイナリが生成できるはずです。このプログラムで、256バイトというサイズでした。

 

ここの0x240というのは、ディスプレイに表示された数値を見て、適当に決めました。

前の方で書いたように、通常時、1.7Vくらい、人を感知すると2Vぐらいになりますので、そのレベルに応じた値を設定したことになると思います。揺らぎがあるので、少し余裕を持たせた方がよいかとは思います。

 

今回、VDD=3.3Vで動作させているので、10ビットのAD変換では、通常時1.7Vだと、1.7V/3.3V*1024=527=0x20fになり、2Vだと、2V/3.3V*1024=620=0x26cというレベルになると考えられます。

少し余裕をもった0x240というのは、ちょうどよい数値ではないでしょうか。

 

手をかざすと、LEDが光り、すぐ消えます。これは、センサが人を感知したという変化があったときだけ電圧が上がるからです。そのまま手をかざしておいて、今度は、手をセンサから外すと、電圧は1.4Vとかいうレベルに下がります。この時を検出するように今回はプログラムしていませんから、何も起こりませんけど、LCDディスプレイには、小さい数値が表示されました。

 

これをベースに、人を感知したら、タイマを起動して、一定時間点灯するというプログラムを作れば、前回作ったタッチセンサ付き5灯LEDスタンドのようにタッチせずとも、人を感知して点灯するものが作れますね。

 

意外と簡単に、焦電型赤外線センサが機能したので、ほっとしています。

これも、プログラムの作成で面倒な設定をMCCがやってくれるおかげです。これで、プログラムサイズを最適化して、小さくしてくれると最高なのですけどね。それは割に合わないので、できるだけメモリ容量の大きなものを購入するという方法で対応することにしたと以前に書きましたね。

 

注意事項が1つ。電源を入れてしばらくは、安定しないのか、12〜13秒ほど経過すると、LEDが付きっぱなしの状態になります。その後、1分ほどは手をかざそうが何をしようが反応しません。

最初、ハードがおかしいのではないかと色々いじってしまったりしまいましたが、放っておくのが一番だという結論です。 ネットでも、電源を入れて10秒後に突然出力が1になり、それから30秒は、そのままだったという情報がありました。通常の動きを検出するまで、40秒かかったということですね。私の場合より短いですが、RE210を使われた話なので、違いはあるのでしょう。

 

ちなみに今回の工作の費用は、センサ100円、フレネルレンズ40円、LM358 20円(5個100円)、抵抗計9個(4種類) 9円相当(100本入り100円x4)、コンデンサが電解3個 30円(1個10円)、セラ3個(0.01uF) 30円 (10個入り100円)、PIC 12F1822が100円、LED1個20円位という感じでしょうか。

合計で、350円位といった所でしょうかね。センサモジュールだと、400〜500円位ですから、珍しくお安くできました。なお、ブレッドボードとか、配線代は入れていません。

 

最後に、今の実物の状態の写真を提示します。

DSCN0322

一番左側のブレッドボードが今回製作したもの、その右側は、前回紹介したデバッグ用ディスプレイです。さらに右側に「ほぼPICkit2」が少し見えています。

ディスプレイのブレッドボードには、赤と黒のコードで電源が供給され、黄色のコードがEUSARTの信号線がつながっています。

本体のブレッドボードの中央が焦電型赤外線センサ、右上は、PICkit2からのICSP接続用のケーブルです。電源もここから供給してもらっています。右下にLEDを載せています。

 

では、また。

記事へブログ気持玉 / トラックバック / コメント


グラフィックLCDを使ってみる その4

2016/07/16 11:13

説明を続けます。

 

今回は、キャラクタコード0x20(スペース)から、0x7fまでを作ることを前提にプログラムしています。

それ以外の場合は、プログラム内の$codeの値を適切に設定して下さい。ここは、コメントを作るだけなので、間違っていても大勢に影響はありませんけどね。

 

入力するビットマップデータとして、今回は、以下のようなビットマップファイルを作りました。

フォントは、8x8ドットとなっています。区切りを示すドットが左と、下にあります。

image_thumb1_thumb

 

このフォントは、Palmプログラムで使ったもの (今回少し修正しましたけど)です。

ファイル形式は、モノクロでないと、変換できませんので、ご注意を。

 

プログラム内に記載している通り、

perl bmp2txt.pl ***.bmp [output.txt]

とperlが動くシェル上で実行してください。最後のoutput.txtは、省略可で、省略した場合、指定したビットマップファイル名に.txtがついたファイルが生成されます。

私の場合は、Windows10上で、cygwinを動かして、そこで、perlを実行しています。

 

なお、生成されたファイルは、テキストファイルですが、改行コードがUNIXスタイルなので、メモ帳ではきれいに表示されませんので、ご注意ください。私は、Meadow (emacs)でテキストは見ています。

それをMPLAB X IDE上のプログラムにコピーすれば完成となります。

 

一方、MPLAB Xで作成するメインプログラムは、以下のようなもので動作確認しました。

完全なものではありませんが、簡単なものなので、不足分は、すぐわかると思います。

 

LCDg_init();

LCDg_cls();

 

LCDg_pos(0, 0);
for (ii = 0; ii < 96; ii++) {
    LCDg_print(st);
    st[0]++;
}

 

__delay_ms(2000);
__delay_ms(2000);

 

LCDg_cls();


LCDg_setSize(2); //0:Normal, 2:縦2倍, 4:縦横2倍

LCDg_print("AQM1248A Graphic LCD\n");
__delay_ms(2000);
LCDg_print("3 font size !!\n");
__delay_ms(2000);

 

char message[10] = "\nTest000";
const char hexch[] = "0123456789ABCDEF";

 

LCDg_cls();
ii = 0;
LCDg_setSize(2); //0:Normal, 2:縦2倍, 4:縦横2倍
while (1) {
    message[7] = hexch[ii & 0x0f];
    message[6] = hexch[(ii >> 4) & 0x0f];
    message[5] = hexch[(ii >> 8) & 0x0f];
    LCDg_print(message);
    ii++;

    __delay_ms(500);
}

 

このプログラムでは、初期化後、画面をクリアし、定義したキャラクタコード0x20〜0x7fを表示します。16文字x6行表示できますので、全部の文字の状態が確認できるはずです。

その後、4秒ほどwaitした後、縦2倍のフォントで、AQM1248A Graphic LCD他を表示します。

 

そして、その後、Test000と表示します。0.5秒毎に、番号を+1しながら、表示をします。

この時、番号は、16進数になっています。

これは、スクロール機能を確認する意味で入れています。

この例では、縦2倍フォントなので、3行表示になっていますが、LCDg_setSize();の引数を0か4にすることで、フォントサイズを変更できますので、試してみて下さい。

 

表示状態は、以下のような感じです。なかなかいい感じだと自己満足しています。

DSCN0313_thumb[11]

DSCN0313_thumb[10]

DSCN0314_thumb[8]

最初の写真は、前回示した写真と同様の全体像ですが、8x8ドットで、定義した全96文字が、16文字x6行で表示されています。その拡大したものが2番目の写真となります。

最後の写真は、縦2倍表示を示したもので、16文字x3行で、メッセージが表示されています。

 

全体写真をご覧になってお分かりの通り、ブレッドボードの配線は、ジャンパタイプから変更したので、以前キャラクタLCDだけを接続していた時より、かなりすっきりしていると思います。

 

これでテストはできましたので、以前と同様に、EUSARTで他のPICからデバッグ情報を受信して、表示するということが、このグラフィックLCDでもできるようになります。

こちらは、外形がほぼキャラクタLCDと同じですが、最大16文字x6行と、多くの情報を表示できるので、重宝するのではないか考えています。

 

画面表示文字のバッファ変数を大きく取り、画面から消えた情報もある程度保持するようにして、スクロールボタンをつけたら、もっと多くの行数を表示できたりすると思います。

色々とアイデアが浮かびますが、それは、デバッグで使いながら、必要に応じて対応していくようにしたいと思います。

 

これで、今回のお話は、完結です。では、また。

記事へブログ気持玉 / トラックバック / コメント


グラフィックLCDを使ってみる その3

2016/07/16 11:11

フォントの作成に関して書いておきます。

 

フォントデータをプログラムに埋め込むのは、結構面倒だと思います。

すでに、このLCDを使って、プログラムソースを開示している方のデータから頂くのもよいのですが、自分で作るのが、好みのものを作れたり、その時々の条件にあったものを用意でき良いだろうと思っています。そして、最初の1〜2文字は、キャラクタLCDのデータシートに書いてあるフォントを見ながら、バイナリデータに頭で変換しながら、プログラムソースを作るということをやり、表示確認を行いました。

でも、数文字ならその方法でもよいかもしれませんが、今回予定している96文字も作るという作業はやってられません。

 

そこで、ふと思い出したのです。Palmプログラムで作ったフォントデータがあったのを。

MemoViewerというPalmwareでは、自作のフォントで等幅表示をさせました。

 

その時、ベースとなるフォントデータをビットマップで作っていました。

そのビットマップデータは、Windowsの標準ソフトであるペイントを使って作っていました。

この方法だと、少しフォントの形を変えたいという時も簡単です。

 

ペイントでなくても構いませんが、お絵かきソフトで、形状を見て、修正するのが一番良い方法だったからです。また、そのファイルフォーマットがわかるもので、かつ簡単な構造という要求も加わります。それは、以下で。

 

Palmwareでは、フォントデータをテキストの形で、リソースとして与えてコンパイルしていました。

テキストというのは、例えば、数字の5を以下のような形で記述することです。

----
###-
#---
#---
##--
#-#-
--#-
--#-
##--
----
----

 

こういうテキストデータをモノクロビットマップからperlプログラムを使って、生成したのです。

このpalm用のプログラムをそのままでは、今回の用途としては使えませんが、今回のプログラムソース用のデータも作れるはずと、プログラムを改造を試みました。

 

で、どんなperlプログラムかというと、以下のようなものです。

結構力づくで変換していますが、PICの動作に関係なく、たまに使うだけのもですから、お許しください。

 

bmp2txt.plというファイル名としています。

#
# bmp2txt.pl : モノクロbmpをフォントリソースに変換
# 2016/7/10 Copyright Kazuki Ohno
#
# bmpには、最下行に、文字ごとの切れ目マークを入れる。文字の左下に点があること。
# 左端には、行を示す点が必要。
#
# Usage: perl bmp2txt.pl ***.bmp [output.txt]
# 入力:モノクロbmpファイル
# (1)左端に行を示す点を上側と下側に入れる。
# 上端の点は、データ領域に含まれ、下端の点は、含まれない
# これにより、各行が離れたデータを処理が可能となる。
# (2)各文字の左下に点を区切りとして入れてあること。この点は、領域外。
# (3)行の最後は、3ドットの点を区切りとして入れてあること。
# 例:以下のようなモノクロビットマップとなる
# ■:黒ドット(位置を示すマーカ)、□:白ドット、○データ領域
# ■□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# □□○○○○○○○○○○□○○○○□○○○
# ■□■□□□□□□□□□□■□□□□■□□
# X方向は、等間隔の必要なし
#
# 出力:テキストファイルを生成
# bmpファイルと同じディレクトリに***.bmp.txtというファイル名ができる。

 

# ビットマップフォーマット
#  00   2   bfType          "BM"    BMPファイル識別符号
#  02   4   bfsize                  ファイルサイズ
#  06   2   bfReserved1     0       予備
#  08   2   bfReserved2     0       予備
#  0A   4   bfOffbits               ファイル内のイメージデータ開始位置
#  0E   4   biSize          40      ヘッダのサイズ
#  12   4   biWidth                 イメージの幅(ピクセル)
#  16   4   biHeight                イメージの高さ(ピクセル)
#  1A   2   biPlanes                イメージのプレーン数
#  1C   2   biBitCount              ピクセルあたりのビット数、
#                                     1,4,8,16,24,32 の何れか.
#                                     (但し、16,32 は圧縮のみ)
#  1E   4   biCompression           圧縮型式
#                                     0  非圧縮、
#                                     0  以外圧縮されている(説明省略)
#  22   4   biSizeImage             イメージデータのサイズ
#  26   4   biXPixelsPerMeter       対象デバイス水平解像度
#                                     (ピクセル / メートル)
#  2A   4   biYPixelsPerMeter       〃 垂直解像度(〃)
#  2E   4   biClrUsed               使用する色数
#  32   4   biClrImportant          重要な色数.0 の時全色が重要
#  36       bmiColors         Color Table (RGBQUAD の配列)開始.
#                             各エントリーは 4 ビット、
#                               (rgb 強度 + 予備(rgbReserved=0)).
#                             非圧縮の場合、
#                               biClrUsed が 0 の場合のエントリー数は、
#                               biBitCount の値に応じて、
#                                 1   2 エントリ
#                                 4   16
#                                 8   256
#                                 24  0.
#                               biClrUsed が非 0 の場合のエントリー数は、
#                               biClrUsed の値に等しい.
#
# bfOffbitsに格納された位置から bitmap データが記述される.
#
# bitmap データは画像のピクセルごとの Color Table のインデックスまたは
#rgb 値である.
# データは画像のピクセルが、左から右へ向かう順に、1行ずつ保存される.
# それぞれの行は、4バイトの倍数になるようにゼロパディングされる.
# 行は画像の下から上へ向かう順に保存される.

 

# 切り上げ関数
sub ceil{
    ( $_[0] == int($_[0]) ? $_[0] : int($_[0] + 1) );
}

 

# ビットマップの左端のドットのY座標を探して返す
sub searchYdot {
    while ($Hcoord < $biHeight) {
# グローバルの$Hcoordの現在の座標位置から最初のドットが見つかるまでデータを読み出す
        $rpos = $bfOffbits + &ceil($biWidth/32)*4*($biHeight-$Hcoord-1);
        seek(IN, $rpos, 0);
        read(IN, $buffer, 4);
        $Hcoord++;

        $data=unpack( "N", $buffer);    #符号なしcharとしてunpack
#        printf("%x: %x\n", $rpos, $data);

        #左端は、文字の上端と下端のペアで区切り
        if (($data & 0x80000000) == 0) {    #黒ドットは、0
            return ($Hcoord-1);    # 見つかったY座標値を返す
        }
    }
    return -1; # エラー時は、-1
}


# ビットマップの各行の下端のドットのX座標を探す
sub searchXdot {
    my ($y) = @_;
    while ($Wcoord < $biWidth) {
        # $Wcoordの最初のデータを読み出す
        $rpos = $bfOffbits + &ceil($biWidth/32)*4*($biHeight-$y-1)+ int($Wcoord/32)*4;
        seek(IN, $rpos, 0);
        read(IN, $buffer, 4);

        $data=unpack( "N", $buffer);    #符号なしcharとしてunpack
#        printf("0x%x: %x\n", $rpos, $data);

        # 32ドットの中に黒ドットがあるかチェック
        $xsub = $Wcoord & 0x1f;
        $Wcoord = $Wcoord & 0xffffffe0;
        while ($xsub < 32) {
            my $mask = 0x80000000 >> $xsub;
            if (($data & $mask) == 0) {    #黒ドットは、0
                $Wcoord = $Wcoord + $xsub + 1;
                return ($Wcoord-1);
            }
            $xsub++;
        }
        $Wcoord = $Wcoord + 32; # 4バイト単位なので、32ドット分加える
    }
    return -1; # エラー
}


# 128x48ドット グラフィックLCD用のフォントをbmpデータから作成

 

$filename = shift;
$address  = 0;

 

open( IN, "$filename" ) or die "Can't open $filename\n";
$fileOut=shift;
#第2引数がない時は、元のファイル名に.txtをつける。
if ($fileOut eq '') { $fileOut=$filename.".txt"; }
open( OUT, ">$fileOut" );

 

binmode( IN );    # 入力するbmpファイル
binmode( OUT ); # 出力テキストファイル
#ファイルは、UNIX形式の改行であること、そうでないとできたファイルがおかしくなる

 

read( IN, $bfType, 2); # BMという識別子が取得されるはず、チェックしていない
printf( "bfType = %s\n", $bfType );
$address  += 2;

 

read( IN, $buffer, 4); # ファイルサイズ
$bfsize=unpack( "V", $buffer);
printf( "bfsize = %x\n", $bfsize );
$address  += 4;

 

read( IN, $buffer, 4); # 読み飛ばし
$address  += 4;

 

read( IN, $buffer, 4); # データ開始位置を示すオフセット
$bfOffbits=unpack( "V", $buffer);
printf( "bfOffbits = %x\n", $bfOffbits );
$address  += 4;

 

read( IN, $buffer, 4); # ヘッダサイズ
$biSize=unpack( "V", $buffer);
printf( "biSize = %d\n", $biSize );
$address  += 4;

 

read( IN, $buffer, 4); # イメージのXビット幅
$biWidth=unpack( "V", $buffer);
printf( "biWidth = %d\n", $biWidth);
$address  += 4;

 

read( IN, $buffer, 4); # イメージのYビット幅
$biHeight=unpack( "V", $buffer);
printf( "biHeight = %d\n", $biHeight );
$address  += 4;

 

# その他のデータはスキップ

 

$code=0x20;

 

# 全データを読み込むループ
$Hcoord = 0;
while ($Hcoord < $biHeight) {

    # 左端にある行区切り情報を探し、最初の行の範囲を取得
    $startY = &searchYdot(); # 開始Y座標
    if ($startY < 0) {
        last;    # ドットデータが見つからない時は、終了
    }
    printf("\nstartY=%d, ", $startY);

 

    $endY = &searchYdot(); # 終了Y座標
    printf("endY=%d\n", $endY);

 

    # 読み込む1行分のデータ範囲が分かったので、1文字分ずつ読み込む
    $Wcoord = 1; # 左端は、マーカーなので、読み飛ばす必要あるため
    $endX = &searchXdot($endY);

 

    while ($Wcoord < $biWidth) {
        $startX = $endX; #前のendXが、次のstartXとなる
        printf("startX=%d, ", $startX);

        $endX = &searchXdot($endY);
        printf("endX=%d\n", $endX);

# もし、startXとendXが連続している時は、3ドットの区切りかチェック
        if ($startX+1 == $endX) {
            printf(OUT "\n");
            last;
        }

 

# ここまでで得たキャラクタの矩形領域をサーチして、データを作成する
        $xx = 0;
        for ($ii = $startX; $ii < $endX; $ii++) {
            $chdata[$xx] = 0;
            $yy = 0;
            for ($jj = $startY; $jj < $endY; $jj++) {
                $rpos = $bfOffbits + &ceil($biWidth/32)*4*($biHeight-$jj-1)+ int($ii/32)*4;
                seek(IN, $rpos, 0);
                read(IN, $buffer, 4);
                $data=unpack( "N", $buffer);    #符号なしcharとしてunpack
               
                $mask = 0x80000000 >> ($ii & 0x1f);

                if (($data & $mask) ==0) { # 黒ドットなら0
                    $chdata[$xx] = $chdata[$xx] | (1 << $yy);
                }
                $yy++;
            }
            $xx++;
        }
        # できた$chdataを出力
        printf(OUT "{");
        for ($ii = 0; $ii < ($endX-$startX); $ii++) {
            printf(OUT "0x%02x, ", $chdata[$ii]);
        }
        printf(OUT "}, // '%c'\n", $code);
        $code++;

    }
}

close( IN );
close( OUT );

 

printf( "%s 生成完了\n", $fileOut );

# end of file

プログラム内容は、見てもらえばわかるはずですので、説明はしません。

特にプログラム最初の所に説明がありますので、お読み下さい。

 

続きがありますが、また、字数制限のため、ここでいったん切ります。

記事へブログ気持玉 / トラックバック / コメント


グラフィックLCDを使ってみる その2

2016/07/16 10:37

では、ソースプログラムを提示します。

 

以下は、lcd.cとして作ったもので、従来のキャラクタLCDのソースに追加しました。

/*
* AQM1248A-RN 48x128ドット SPI接続グラフィックLCD
* 制御ルーチン
*/

//------ Command Write -----
void LCDgCmd(uint8_t data) {

    CSB_PORT = 0;
    RS_PORT = 0;
    SPI2_Exchange8bit(data);
    CSB_PORT = 1;
}

//------ Command Data -----
void LCDgData(uint8_t data) {

    CSB_PORT = 0;
    RS_PORT = 1;
    SPI2_Exchange8bit(data);
    CSB_PORT = 1;
}

/*
* AQM1248A グラフィック液晶の初期化
*/
void LCDg_init() {

    LCDgCmd(0xae); // Display = OFF
    LCDgCmd(0xa0); // ADC = normal
    LCDgCmd(0xc8); // Common output = revers
    LCDgCmd(0xa3); // bias = 1/7

    // 内部レギュレータを順番にON
    LCDgCmd(0x2c); // power control 1
    __delay_ms(2);
    LCDgCmd(0x2e); // power control 2
    __delay_ms(2);
    LCDgCmd(0x2f); // power control 3

    // コントラスト設定
    LCDgCmd(0x23); // Vo voltage resistor ratio set
    LCDgCmd(0x81); // Electronic volume mode set
    LCDgCmd(0x1c); // Electronic volume value set

    // 表示設定
    LCDgCmd(0xa4); // display all point = normal
    LCDgCmd(0x40); // display start line = 0
    LCDgCmd(0xa6); // display normal/revers = normal
    LCDgCmd(0xaf); // display = ON

    // 8x8ドット表示のデフォルト
    LCDg_setSize(0);
}

/*
* 1文字表示
*/
void LCDg_chr(uint8_t data) {
    int ii;

    for (ii = 0; ii < 8; ii++) {
        LCDgData(font[data - 0x20][ii]);
    }
}

void LCDg_str(char *str) {
    while (*str) //文字列の終わり(00)まで継続
        LCDg_chr(*str++); //文字出力しポインタ+1
   
}

/*
* 128x48ドット
* ドット単位で、X,Y座標を引数とする
* ただし、Y方向は、8ドット単位に丸められる
*/
void LCDg_pos(uint8_t x, uint8_t y) {
    // X方向は、ドット単位
    LCDgCmd(0x10 | (x >> 4));
    LCDgCmd(x & 0x0f);
   
    // Y方向は、8ドット単位
    LCDgCmd(0xb0 | (y >>3));
   
}

void LCDg_Update() {
    char ii, jj, kk;
    char trans[] = {0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f,
        0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff,};

    switch (YscrMax) {
        case 3:
            for (jj = 0; jj < YscrMax; jj++) {
                LCDg_pos(0, jj * 16);
                for (ii = 0; ii < XscrMax; ii++) {
                    for (kk = 0; kk < 8; kk++) {
                        LCDgData(trans[font[Screen[ii][jj] - 0x20][kk] & 0x0f]);
                        if (XscrMax == 8)
                            LCDgData(trans[font[Screen[ii][jj] - 0x20][kk] & 0x0f]);
                    }
                }
            }
            for (jj = 0; jj < YscrMax; jj++) {
                LCDg_pos(0, jj * 16 + 8);
                for (ii = 0; ii < XscrMax; ii++) {
                    for (kk = 0; kk < 8; kk++) {
                        LCDgData(trans[font[Screen[ii][jj] - 0x20][kk] >> 4]);
                        if (XscrMax == 8)
                            LCDgData(trans[font[Screen[ii][jj] - 0x20][kk] >> 4]);
                    }
                }
            }
            break;
        default:
            for (jj = 0; jj < YscrMax; jj++) {
                LCDg_pos(0, jj * 8);
                for (ii = 0; ii < XscrMax; ii++) {
                    LCDg_chr(Screen[ii][jj]);
                }
            }
            break;
    }
}


/*
* テキスト表示のクリア
* Screen配列に空白を書き込み、Updateする
*/
void LCDg_cls() {
    int ii, jj;

    for (jj = 0; jj < 6; jj++) {
        for (ii = 0; ii < 16; ii++) {
            Screen[ii][jj] = 0x20;
        }
    }
    LCDg_Update();
    // 画面消去した後は、カーソル位置を0,0にする
    ScreenPosX = 0;
    ScreenPosY = 0;
}

/*
* カーソル位置設定
*/
void LCDg_setCursorPos(char x, char y) {
    ScreenPosX = x;
    ScreenPosY = y;
}

/*
* Scren配列に文字列を書き込み、画面を更新
* 画面いっぱいになったら、スクロールする
* カーソル位置を意識せずに、一種の表示ターミナルとして使用可能
*/
void LCDg_print(char *str) {
    int x, y;
   
    while (*str) {
        if (ScreenPosY >= YscrMax) {
            // スクロールをここに置くことで、最終行が空行に見えることがなくなる
            for (y = 0; y < YscrMax - 1; y++)
                for (x = 0; x < XscrMax; x++)
                    Screen[x][y] = Screen[x][y + 1];
            for (x = 0; x < XscrMax; x++)
                Screen[x][y] = ' ';
            ScreenPosY--;
        }
        switch (*str) {
            case '\n':
                ScreenPosX = 0;
                ScreenPosY++;
                break;
            default:
                Screen[ScreenPosX][ScreenPosY] = *str;
                ScreenPosX++;
                if (ScreenPosX >= XscrMax) {
                    ScreenPosX = 0;
                    ScreenPosY++;
                }
        }
        *str++;
    }
    LCDg_Update();
}

/*
* 文字サイズの設定
*/
void LCDg_setSize(char size) {
    switch (size) {
        case 2: // 縦2倍サイズ
            YscrMax = 3;
            XscrMax = 16;
            break;
        case 4: // 縦横2倍サイズ
            YscrMax = 3;
            XscrMax = 8;
            break;
        default:
            YscrMax = 6;
            XscrMax = 16;
            break;           
    }
}

このプログラムは、素直に作っているので、特に説明は必要ないと思います。

 

それと、ヘッダとして、lcd.hを作りました。

char Screen[16][6]; //16文字x6行分のキャラクタコードを格納
char ScreenPosX, ScreenPosY;
char XscrMax;
char YscrMax;


void LCDgCmd(uint8_t data);
void LCDgData(uint8_t data);
void LCDg_init();

void LCDg_chr(uint8_t data);
void LCDg_str(char *str);
void LCDg_pos(uint8_t x, uint8_t y);
void LCDg_Update();
void LCDg_cls();
void LCDg_setCursorPos(char x, char y);
void LCDg_print(char *str);
void LCDg_setSize(char size);

// グラフィック用 キャラクタデータ
// bmpファイルから、perl bmp2txt.pl [bitmap.bmp] で生成する
const char font[96][8] = {

        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}, // 
〜 省略 〜

};

 

長くなってしまうので、フォント部のデータは省略しています。

フォント部については、次の記事で説明します。

 

グラフィックLCD用の関数は、LCDgで始まるようにしました。

LCDgに続く名前で記述していきます。

最初のCmdとDataは、コマンドとデータをPICからLCDへ送信するものです。

_initは、初期化です。

 

_chrは、1文字だけ表示、_strは、文字列を表示させる関数です。

_posは、表示させる位置をします。X方向は1ドット単位で指定できますが、Y方向は、8ドット単位になっています。Y方向が8ドット単位なのは、プログラムの簡略化のため、手を抜いているからでもあります。

 

_Updateは、画面更新を行います。これは、キャラクタ単位のバッファを持って、縦スクロールを実現させるため、画面全体を再描画するために使います。

 

_clsは、画面全体の消去、_setCursorPosは、キャラクタ単位の座標を指定します。

_prnitは、先の_strとは異なり、画面に表示する文字列を格納するキャラクタ単位のバッファに書き込むものです。その後、_Updateを行うと、実際に描画されます。

 

最後の_setSizeは、文字のサイズ指定です。デフォルトの8x8ドットサイズのほか、縦に2倍と、縦横各2倍のサイズに変更できます。これは、文字を描画する時に、1ドットを縦に2倍したら、縦横2倍にしたりして描画しているので、TrueTypeフォントのような高級なことは行っていませんが、簡単に実装できるので、サポートしました。

 

96文字も定義しているので、768バイトもメモリを消費します。使っているPICは、16F1829なので、8Kワードしかありませんから、結構厳しい量です。256文字全部定義したら、2KBのサイズになります。こういう用途には、PICは向きませんね。

記事へブログ気持玉 / トラックバック / コメント


グラフィックLCDを使ってみる

2016/07/15 22:13

先日は、PICのデバッグのため、8文字x2行のI2C接続LCDを使いました。

 

実は、もう一つLCDを購入してありました。

それは、AQM1248Aという48x128ドットのグラフィックLCDです。

秋月さんのページから頂きました。

表示域が約3cmx1cmと小さいですが、取り回しは良さそうです。

 

今までキャラクタタイプのLCDしか使っていませんでしたので、グラフィックLCDは、気になっていたのです。キャラクタタイプですと、表現に限界があり、PIC時計とか、結構苦労しましたから。

しかし、ここの所、やることが多くて、なかなか試す時間がなかったのですが、ようやく出番となったわけです。

 

このLCDは、ピッチが1.27mmなので、そのままブレッドボードで使うことはできませんが、変換基板があり、それにより、2.54mmという使いやすいピッチに変えてもらうと使えるようになります。

もちろん、それも一緒に購入したわけです。 しかし、完成品ではないので、変換基板にはんだ付けを行わないといけません。

上記の写真の下辺に見える端子が、はんだ付けが必要な1.27mmピッチの部分、左端に見えるのが、変換後の2.54mmピッチの端子です。

 

この1.27mmというのは、私の経験上の最小ピッチです。さて、うまくできるのか。

途中、はんだブリッジとか発生させてしまったりしましたが、何とか、はんだ付けを完了させました。

この工作は、キャラクタ液晶のはんだ付けと同じ時に行ったので、ずいぶん時間が経過してしまいました。ちゃんとはんだ付けができたかは、動作させてみないと、ちゃんと動くかわかりませんから、早く確認しておきたかったのですけどね。

 

さて、確認するためには、PICと接続しないといけません。どのようなハードにしようかと考えましたが、以前作成した8文字x2行LCDのボードに追加するのが良いのではないかと思いました。

 

このグラフィックLCDは、SPI+RSの4ピンと接続させますが、信号線の本数が少ないので、I2C LCDを接続したPIC16F1829にまだ、接続する余裕があったからです。

 

AQM1248Aの変換基板から出ている信号は、計7ピン。VDD/GNDを除くと、5ピンが信号で、/RESETと/CSは、変換基板上でプルアップされていました。残るは、SPIのSCLKとSDIの2ピンとRSを加えた、3ピンで、4ピンは、必要ないのではないかと思いました。つまり、/CSはGND固定させて使えば問題なかろうと思ったわけです。

しかし、これが後々問題となりますけど、この時は、そうは思っていませんでした。

 

AQM1248Aは、SPI接続なので、I2Cとは違う設定が必要です。SPI接続は初めてですがMCCが助けてくれるでしょう。

 

ベースプログラム、設定は、PIC USARTで通信にトライのものをベースとしました。キャラクタLCD

は、接続したままにしています。

 

ハードは、上述のように、AQM0802Aを接続したPIC16F1829は、まだ、ポートが余っているので、グラフィック液晶も追加して、接続します。でも、この時点では、どのピンに接続するか決めていません。どう接続するかは、MCCで、MSSP2を追加してから考えようと思いました。

 

従来のキャラクタLCDを接続した時の設定は、

image

としていました。

 

SPIのため、MSSP2を追加してみると、SCK2はRB7、SDI2はRB5、SDO2はRC1 or RA5となっています。

ところが、RB5のピンが、EUSARTのRXとバッティングしてしまうのです。

そこで、EUSARTのRXピンをRC5に変更しました。これは、EUSART側しか、選択変更できなかったからです。そうすると、以下のような設定となりました。

image

上記では、SPIのほかに必要なRS用にRA5を汎用出力として設定してあります。これは、余っているピンならどこでも構わないのですけれど、何となく決めました。

 

そうすると、SCK2 (10ピン)、SDI2 (12ピン)、SDO2 (15ピン)がSPI用となりますが、SDI2は今回は、使いません。このグラフィックLCDからリードすることができず、ピンもないからです。

そして、新たな接続方法がきまったので、LCDの変換基板とPICを接続しました。もちろん、ブレッドボード上にて。PICのSDO2を、LCDのSDIに接続するところだけが注意するところですかね。

 

電源は、その通りに接続しますが、それに加えて、/CSもLow固定としました。/RESETは、プルアップされていて、動作に問題なければオープンでよいということなので、そのままどこにも接続しませんでした。

 

MSSP2の設定は、以下のような感じです。ModeをSPI Masterにして設定していきます。

imageimage

これは、グラフィック液晶のマニュアルに従い、SCLKは、HighでIdle状態で、クロックの立ち上がりで、SDIのデータを取り込みますから、Clock Edgeの設定は、LowからHighになる、即ち、ActiveからIdleになる時という選択肢を選んだ訳です。

上記の右側のキャプチャは、レジスタの設定がわかるようにしています。

 

そして、いつものように、Generateボタンで、プログラムを生成します。

SPIはどのように使ったらよいかは、生成されたspi2.hとspi2.cのコメントを読むと大体わかります。

初期化は、いつものようにmcc.c内で行われるので、データの書き込み方法がわかれば、プログラムできそうです。

 

まずは、LCDの初期化から。これは、AQM1248Aのマニュアルに記載の例に従って、書いていきました。

その時、コマンド、データ書き込み用のサブルーチンも、例にならって作成しました。

実際のコードは後ほど提示します。

ここで、一番重要なのはPICからLCDへのSPI通信で、命令・データを書き込みを行う関数が必要です。これは、上述のspi2.hの中から、 SPI2_Exchange8bit(uint8_t data)が使えそうだと思いました。

 

初期化ルーチンができたところで、まだ、LCDにデータの書き込みをするようにプログラムは作っていませんが、とりあえず、コンパイルして、動かしてみました。

 

あれれ、、、全然動作しているように見えません。画面にゴミデータすら表示されなかったからです。

 

とりあえず、LCD上に電源がちゃんと印加されているか確認してみたり、SPIのクロックスピードが速すぎて、タイミングがまずいのかなと思って、設定を変更してみたり、上述のClock Edgeの設定が間違っているのかと逆の設定をしてみたり、いろいろやりました。

 

でも、何度か、電源を入れたり切ったりしていたら、ある時、画面にゴミデータが表示されたのですよ。 でも、再度電源を入れ直すと、また表示されなくなります。非常に不安定です。

 

ここで、少し考えました。 今回、/CSを接続例とは異なり、ピン数を減らそうとGND固定にしてしまったのが、いけなかったのではないかと。

 

そこで、MCCにて、RA4 (3ピン)をCSBという名前で追加割り当てを行い、LCDの2ピンと接続しました。

 

そして、コマンド、データを送出する時だけ、CSB=Lとなるようにプログラムを書き換えました。

コンパイルして、PICに書き込み、動作確認です。

 

やったー、成功です。 安定して、画面にゴミデータが表示されるようになりました。

こうなると、がぜんやる気が出てきます。あとは、プログラムを作っていくだけです。 ちゃんと、データを表示するようにプログラムします。

ちゃんと表示できました。以下の写真のような感じです。

DSCN0317

上部がグラフィックLCD、下部がキャラクタLCDです。

 

ここで、ソースを提示しようとしましたが、サイズオーバーになったので、ここでいったん区切ります。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDスタンド運用開始:タッチセンサの調整、消費電流

2016/07/06 20:48

タッチセンサの調整

 

前回の記事で5灯LEDスタンドの製作は完了しました。 (そのはずでした)

あの後、むき出しになっていたスタンドの基部を組み直し、元のようにスタンドの形に戻しました。

 

そして、動作の最終確認を開始しました。

だけど、あれっ、タッチセンサが動作しません。 ただ、物理スイッチの方は、問題なく、動作します。

 

いろいろやってみると、スタンドの足の部分を包み込むように手を乗せると、反応したりします。

でも続けて、同じようにタッチしても反応しません。

非常に反応が悪すぎる感じです。

 

どうも、タッチセンサ回りの環境がふたを開けていたときと変わってしまったようです。

つまり、電気的にみると、PICから見える容量値が変わってしまったのでしょう。

そのため、手でちょっと触っただけでは、例のしきい値を超える容量の違いを生み出せなくなったように思われます。

 

現在、そのしきい値は、デフォルトのままで、100になっています。

これを調整してみたいと思います。 また、スタンドはばらしています。

 

本来であれば、LCDディスプレイと接続して、タッチの有無でどのような値になるか見たいところです。しかし、ご存知の通り、プログラムサイズがぎりぎりの状況で、また、スタンドの中に入れた状態で配線を引き出すのも面倒な作業になるので、実験的に、しきい値を変更させる方法で対応することにしました。

少しいい加減ですが、ホビーなので良しとしましょう。

 

しきい値を50まで下げて見たところ、指で触るとちゃんとスイッチの役割を果たすようになりました。ノイズに弱くなっているかもしれませんが、これでいきます。

 

このしきい値の話とは異なりますが、プログラムを作っている時は気になりませんでしたが、普通に使っていると反応速度が遅いような気がします。500kHz動作だとこんなもんなのかな。

 

まあ、これで、使ってみます。

 

消費電流

ここで、1つすっかり忘れていたことを思い出しました。

 

このスタンドは、単3電池3本使っています。 これで、それなりの期間使いたいと思っています。

そのためには、消費電流を抑える必要があります。その消費電流が、最終的にいくらになったのか調べるのを忘れていたのです。

 

当初以下のように考えていました。

 

8ピンのPIC12F1822を使って、一定時間だけ光らせるようにする。消費電力を極力減らす。
1クリックで、1分間点灯。

 

これで、1年持たせるには、1日どのくらいの回数点灯できるか?

 

アルカリ単3×3本=2000mAhx3=6000mAh の電力があると言われています。

フル点灯時、87mA流していましたから、

6000mAh/(87mA*60s)=4137回 点灯させられる計算をなるはずです。

これは、1日5回点灯させても、2年以上もつ計算となります。


その時、スタンバイ電流=200uAだとすると、1年で、200uA*24hr*365day=1752mAhなので、
上式にスタンバイ電流の分を差し引いて計算すると、(6000-1752)/(87mA*60s)=2929回となります。これは、1年間で点灯できる回数です。1日当たり8回となります。

 

この計算なら、実使用上、十分だろうと思っていたのです。

 

さて、実際にプログラムを完了したこのスタンドは、どのくらいスタンバイ電流がながれているでしょうか、また、明るさの違いでどの位か調べていきましょう。

 

測定してみたところ、大体410uAでした。 上記の見込みより2倍位大きくなりました。

これはタッチセンサを常に動作させているためかもしれません。

 

この場合、1年分のスタンバイ電流は、410uA*24hr*365day=3591mAhとなりました。

そうすると、(6000-3591)/(87mA*60s)=1661回となります。1日当たりにすると、4.5回となります。

私の使い方だと、十分だと思いました。実際には、60sは少し長すぎるので、30sに設定していますし、大体良い感じに仕上がったのではないでしょうか。

 

一方のLEDを点灯した時はどうなるか。

実は、測定できませんでした。なぜか、LEDが一瞬点灯して、すぐに消灯してしまいます。タッチしても、ボタンを押しても、同様です。

 

なぜだろう。。。。。。。

 

一晩考えました。

 

電流測定は、ご承知の通り、回路と直列に測定器を挟まねばなりません。

この時、理想的には、測定器の内部抵抗は0Ωが望ましいですけど、実際はそんなことはないでしょうから、多少なりとも抵抗があるはずです。

 

上記の通り、消灯時は、0.4mA程度しか流していませんから、10Ωの抵抗がついても、4mVの電圧降下にしかなりません。この位は、動作に全く問題ないのでしょう。

 

一方、点灯した時は、90mA近く流すので、10Ωも抵抗がつけば、900mV=0.9Vと、4.5Vの電圧だったとすると、一瞬で、3.6Vまで降下してしまいます。

 

内部抵抗がいくらかはわかりませんが、これが、問題を引き起こしているのではないかと思いました。

 

それなら、適当な非常に小さい抵抗をつなげて、電圧を観測するのがよいだろうと思いました。

しかし、残念ながら、手元に適当な抵抗がなく、断念しないといけませんでした。

 

で、LED点灯時と消灯時で電圧にどんな差が生じているのか見てみました。点灯すると、0.05V下がっていることは測定できました。

しかし、たぶん点灯初期、トランジスタがONした時には、スパイクノイズのような電流が流れ、電圧ももっと降下していたかもしれません。 マルチメータでは検出できませんし、オシロも持っておりませんので、ここまでです。

 

何かいい方法がないかなと、ハードを眺めていたら、ノイズ吸収のパスコンが助けになるかもと、思いつきました。私のハード全然、パスコンつけていませんでしたから。

ちょうど、0.1uFのセラコンがあったので、VDD-VSS間につけてみました。暫定なので、PICのソケットにパスコンを押し込むような形でつなげました。1ピンと8ピンが対象なので、ちょうど差しやすかったので、助かりました。

 

そうしたら、マルチメータを電流計モードで挟んでもPICは、適切に動作するようになりました。

めでたしめでたしです。

 

測定結果は、以下のようになりました。

モード 電流値
スタンバイ時 (消灯時) 0.45 mA
Bright=0 (デフォルトの最も明るい) 82 mA
Bright=1 40 mA
Bright=2 20 mA
Bright=3 (暗い) 9.7 mA

 

Brightの値は、1段階で、点灯時間を半減しさせるようにPWM制御しました。 それに比例するように、電流値が小さくなっています。 非常によい測定結果となりました。

ちゃんとPWM動作できていることが、目で見た明るさ以外の指標で、確認できましたね。

 

フル点灯時の電流が、元のPICを使わなかった時の電流値より小さいのは、SWの代わりに入れたトランジスタ2SA1015と可変抵抗の影響でしょう。

 

そうすると、一番明るい時は、上記の計算とさほど変わりませんから、計算しませんが、1日当たり、1回30秒の点灯を9回行って、1年間、電池交換なしで使える計算となります。

 

輝度を1つ落として、使おうと思っているので、2年程度持つ計算になります。十分ですね。1日に9回も点灯しなさそうなので、もっと使えるだろうと思います。

 

これで、自動消灯機能付きのLEDスタンドの完成としたいと思います。

 

もっと、電池寿命を延ばしたいなら、タッチセンサをやめるというのも1つのアイデアかもしれません。 明るさを維持したまま、できるだけ長い時間使いたいという要求もあるでしょうから。

 

その場合、ボタンが1つだけになるので、モードの変更もボタンで行わねばなりませんが、長押し以外に、ダブルクリック等を使って、複数の状態を得ることができると思いますので、同等の機能を持たせることはできると思います。 その時は、プログラム領域もタッチセンサを削除する分広がりますから、対応は可能になると思っています。 ご興味があれば、実験、工作して見てください。

 

今回1つ入らなかったのは、モード変更した時、その情報をEEPROMに書き込むことです。

情報を書き込めれば、電池交換しても、モードは維持できますが、今回は、電池交換するたびに、モード設定はやり直さないといけません。頻繁に電池交換はないでしょうから、問題となるわけではありませんけどね。

 

では。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDスタンド完成かな 後編

2016/07/03 17:17

前の記事からの続きとなります。

 

main.cのプログラムは、以下となりました。

#include "mcc_generated_files/mcc.h"

#include "main.h"
#include "mcc_generated_files/mtouch/mtouch_button.h"
#include <stdio.h>


/*
* PIC16F1829コントロールLCDに表示
*/
char CursorX, CursorY;

/*
* カーソル位置の設定
* 左上(0,0)から開始
*/
void SetCursor(char x, char y) {
    EUSART_Write(1);
    EUSART_Write(x);
    EUSART_Write(y);
}

/*
* int変数の値をカーソル位置に表示
*/
void DisplayVal(int val) {
    char valStr[10];
    char *str;

    sprintf(valStr, "%d", val);
    str = valStr;
   
    while (*str)
        EUSART_Write(*str++);
   
}

/*
* 文字列をカーソル位置に表示
*/
void DisplayChar(char *str) {
    while (*str)
        EUSART_Write(*str++);
   
}

/*
* LEDの点灯⇔消灯を切り替える
*/
void LEDtoggle() {
    TRISA5 = T2CONbits.TMR2ON; //TMR2オンだったら1でHi-Zに。逆は出力可に。
    T2CONbits.TMR2ON ^= 1; //タイマの停止、開始で対応
}

/*
* LED点滅
*   count数だけ点灯、time:点灯消灯時間(ms)
* timeに255以上設定したいので、intに変更。
*/
void FlashLED(char count, int time) {
    char jj;
    int kk;

    for (jj = 0; jj < count * 2; jj++) {
        for (kk = 0; kk < time / 10; kk++) __delay_ms(10);
        LEDtoggle();
    }

}


/*
                         Main application
*/
void main(void) {
    char pushed; // タッチボタンが押されていたかどうか保持
    mtouch_sensor_sample_t data; // タッチセンサ

    // initialize the device
    SYSTEM_Initialize();
    INTCONbits.TMR0IE = 0;
   
    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    // 変数の初期化
    BtnStatus = 0;
    EUSART_Write(0x0c);
    pushed = 0;
    OnTime = On30s; // OnTimeは、秒単位。初期値は、30s点灯
    Mode = ModeNormal;
    Bright = 0;

    while (1) {
        // Add your application code
        MTOUCH_Service_Mainloop();
        if (MTOUCH_Button_isPressed(Sensor_AN2)) {
            // タッチセンサが押された時の処理
            // モードにより動作が変わる
            if (!pushed) {
                // 初めて押した時だけ処理 (押し続けても連続処理はしないように)
                pushed = 1;
                switch (Mode) {
                    case ModeNormal:
                        LEDtoggle();
                        if (T2CONbits.TMR2ON == 1) {
                            // 点灯したらタイマ2を開始する
                            IntCount = 0;
                            TMR0_Reload();
                            INTCONbits.TMR0IE = 1;
                        } else {
                            // 消灯時は、タイマ2停止
                            INTCONbits.TMR0IE = 0;
                        }

                        break;
                    case ModeSetBright:
                        Bright = (Bright + 1) % 4;
//                        Bright = (Bright + 1) % 5; //0(明)-4(暗)
                        EPWM_LoadDutyValue(0x4f >> Bright);

                        EUSART_Write('0' + Bright);
                        EUSART_Write(10);
                        break;
                    case ModeSetOnTime:
                        if (OnTime == On30s) {
                            OnTime = On1min;
                            FlashLED(1, 300);
                        } else if (OnTime == On1min) {
                            OnTime = On2min;
                            FlashLED(2, 150);
                        } else if (OnTime == On2min) {
                            OnTime = On30s;
                            FlashLED(1, 50);
                        }
//                        DisplayChar("Time=");
//                        EUSART_Write('0' + OnTime / 30);
//                        EUSART_Write(10);
                        break;
                }
            }
        } else {
            pushed = 0; //タッチをやめた(手を離した)
        }

        //長押し判定
        if (LongPush > Long2) {
            if (Mode == ModeSetBright) {
                // モードが変わった時だけ実行
                Mode = ModeSetOnTime;
                FlashLED(2, 100); //100ms間隔で2回点滅
            }
        } else if (LongPush > Long1) {
            if (Mode == ModeNormal) {
                // モードが変わった時だけ実行
                Mode = ModeSetBright;
                FlashLED(1, 100); //100ms間隔で1回点滅
            }
        } else {
            Mode = ModeNormal;
        }

        // 自動消灯タイマのチェック
        if (IntCount / 100 > OnTime) {
            // OnTimeに設定した時間経過したら消灯
            INTCONbits.TMR0IE = 0;
            TMR2_StopTimer(); //消灯
            TRISA5 = 1;
        }

//        data = MTOUCH_Button_Reading_Get(Sensor_AN2);
//        EUSART_Write(12); // cls
        SetCursor(0, 0);
//        DisplayVal(data);
        DisplayChar("Mode=");
        EUSART_Write('0' + Mode);
        EUSART_Write(10);
       
    }
}

黄色くマークした所が主な変更点ですかね。

 

ここからは、細かい調整を使いながら行い、使いやすくしていこうかと思っています。

ということで、これで、本5灯LEDスタンドは完成と言ってもよいかと思います。

 

では、また。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDスタンド完成かな 前編

2016/07/03 17:17

いよいよ佳境に入りました。

 

今回は、PWMを追加して、明るさをコントロールすることに挑戦です。

さて、どうなることやら。

 

前回のプロジェクトをコピーして、TouchLED_v6を作りました。

 

例によって、MCCをOpenして設定をします。

PWMは、CCP (Compare/Capture/PWM)の機能の1つですから、ECCPをクリックして追加してみます。

 

開いたら、ECCP modeをEnhanced PWMにします。そうすると、以下の画面になりました。

image

PWMには、Timer2しか使えないようですね。そうしたら、割り込みタイマは、Timer1とかに変更しないといけませんね。

まず、そちらを片付けてしまします。

いったんECCPを削除してしまいます。

 

Timer2は、8ビットでしたから、Timer0を代わりに使ってみることにします。

タイマ0は、少し使い方が違いますが、まずは、似た感じに設定してみます。

 

以下のようにしてみました。

image

 

TMR2の設定はそのままで、Generateしてみます。

差分の表示がされますが、すべて、前回プログラムするため必要だったものですね。

 

生成されたファイルを見ると、予想された通り、tmr0.c、tmr0.hができています。

割り込みを有効化させるチェックボックスにチェックしておきましたので、interrupt_manager.cにTMR0_ISR()が追加されていました。

 

TMR2_ISR()とは、異なり、その中に直接ユーザ指定のコードを書くようにコメントされていました。

そこに、前回TMR2_DefaultInterruptHandle()内に書いた下記ルーチンをTMR0_ISR()に移します。

 

以下を移動しました。

IntCount++;
if (BtnStatus) {
    // ボタンを押下した時に実行する
    BtnStatus = BtnStatus << 1;
    if (SW_b_GetValue() == 0) {
        // SWが押下状態
        BtnStatus = BtnStatus | 1;
        LongPush++;
    }
    if ((BtnStatus & 0x0f) == 0b0111) {
        // 3回連続押下状態だったら
        LED_b_Toggle(); // LED点灯/消灯
    } else if (BtnStatus == 0) {
        TMR2_StopTimer(); //←ここは間違いです(後述)
        IntCount = 0;
    }
}

 

以下も追加が必要です。

#include "pin_manager.h"
#include "../main.h"

 

コンパイルして、動作を確認しましょう。前回と同じになりました。

MCCのおかげで、簡単にタイマの入れ替えができました。

 

では、もう一度MCCに戻って、TMR2は削除してしまいましょう。

コンパイルしたら、、、あれ、エラーです。

うっかりしていました。プログラムのそこここで、TMR2_StartTimer()と、TMR2_StopTimer()を使っていました。これをTMR0に変更したらいいのだろうと思いましたけど、そんなものはないみたいです。

 

仕方がないので代わりに、    INTCONbits.TMR0IE = 0として、割り込みを止めるしかないようです。

TMR2_StartTimer()→TMR0_Reload();  INTCONbits.TMR0IE = 1;

TMR2_StopTimer()→INTCONbits.TMR0IE = 0;

 

main.c、pin_manager.c、tmr0.cにありました。

 

気を取り直して、コンパイルしました。問題なく生成できました。

それをPIC12F1822に書き込んで動作を確認しました。 問題ありませんでした。

 

これで、MCCから、TMR2が削除されました。

 

ここからが、今回の本題です。

再度、ECCPを追加します。 モードをEnhanced PWMに変更します。

 

設定の前に、Notificationsを見ます。WARNINGが出ている項目を確認します。

3つありました。

image

PWM出力は、LEDのON/OFF制御しているRA5にしないといけませんね。

Pin Managerのウインドウで、ECCP P1Aの行のRA5をクリックします。

そうしたら、NotificationのWARNINGが2つ消えました。

 

残ったのは、Timer2の設定ですね。 削除してしまったTMR2を再度加えます。

そうしたら、WARNINGは消えました。

 

では、ちゃんとECCPの設定をしましょう。

Timerは、Timer2しか選択できないので、そのまま。

PWM Duty Cycleは、まずは100%にしてみます。 ここは、プログラムで変更するので、暫定値ですね。

PWM modeは、singleにします。P1M<1:0>=00ということですね。

PWM pins polarityは、active lowにします。LowになったらLED点灯にしたので。

Steeringは、有効にして、P1AからだけPWMが出力されるようにします。

以下のように設定しました。

image

 

次にTMR2の設定です。10msを周期にしてみました。これで、1秒間に100回点滅を繰り返すことになります。倍速の液晶が120Hzなので、十分だろうと見込みました。

image

ここまでで、設定は完了です。Notification:6となっていますが、WARNINGはありません。

 

この時、ピンは以下のような設定になっていました。

image

 

ここで、Generateします。いつものようにForce Update on Allです。

いつものように差分表示がされました。

生成されたコードより増えた部分は、緑色に塗られています。これは、生成以前のコードに、追加してプログラムを作った部分だと判断されているようで、ほぼ問題ないと思われます。

一方、青く塗られている部分は、生成されたコード自体に手が加わったと判断された箇所のようで、要注意だと思われます。

中には全然関係なさそうなものもあります。例えば、生成コードが

while (1)

{

と2行に渡っているのを、

while (1) {

と1行にしただけとかです。ここは、エディタでTABを押すとそのように整形されるので、そっちに合わせて欲しいですけどね。

また、割り込みの設定が初期時、コメントになっていたのを外した箇所なども青くなります。

 

今回、新しいパターンが現れました。下記のようなものです。pin_manager.cの差分を表示したところです。

image

 

APFCONがマージ後(右側)、0x00にされています。これはまずいです。0x01に変える必要があります。

これは、PWMの出力のP1AをRA5にしたからです。APFCONのbit0は、CCP1の出力をRA2かRA5のどちらかにすることができますが、初期値から変わったからです。

 

そこで、左側の57行の左の矢印を押して、右側のマージ後のファイルを左の設定に置き換えました。

今後、注意したいと思います。もし、これを見落としていたら、また、動かないというバグに悩まされるところでした。

 

ここで、とりあえずコンパイルしてみました。エラーがでます。

これは、従来RA5をGPIOにしていて、単純にOn/Offして、LEDを制御していましたけど、PWMに変更したのに伴い、LED_b_SetHigh()といったものが消えたためでした。

 

新たにソースファイルに加わったepwm.h, epwm.cを開いてみてみます。

初期化は、いいでしょう。mcc.cの初期化から自動的に呼び出されていますから。

EPWM_LoadDutyValue()というのが、輝度変更に有用そうです。

ここでは、PWM出力を制御するものはありませんね。

 

PWMを動かすのは、TMR2でしたから、そちらを見てみます。

TMR2_StartTimer()、TMR2_StopTimer()というのが使えそうです。

Toggleは、どうしましょうね。TMR2ONは読み出しもできるので、レジスタを反転させて対応してみます。

 

main.cと、tmr0.cに修正箇所がありました。

 

でも結果として、これだと、ダメでした。タッチ、SWでLEDが点灯しましたが、消灯しなくなったのでです。

 

うまく止めないと、PWMの出力がLED消灯状態にならなかったからでしょう。PWMの初期化という手もあるでしょうけど、出力をTRISAで制御した方が簡単そうです。

でも、RA5をHi-Zにした時、フローティングさせる訳にはいきませんので、WPUを有効化しました。

 

MCCで、Pin Moduleを開くと、RA5の設定も見えますので、WPUにチェックを入れました。これで、Weak Pull upが有効になり、Hi-Zにした時、VDDレベルとなり、LEDは消灯します。

 

基本的にTRISA5を1か0に設定して、Hi-Zか、PWMの出力とできますが、タイマがカタカタ動いているのも消費電力的に気持ちがよくないので、TMR2_StopTimer()は併用してプログラムしました。

 

これで、コンパイルして、動作を確認してみます。

。。。

ああ、よかった。元の動作と同じになりました。

現在Dutyは100%なので、明るさも前と同じはずです。

 

最後の機能、明るさの設定の取り込みに入ります。

 

先ほど見つけたEPWM_LoadDutyValue()を使ってみます。

最初は、全部が変わるようにmain()の中の初期化のところで設定してみます。

 

50%にしたら、違いが分かるかな。

ここで、どんな値を入れるべきか悩みました。50%とかパーセンテージで入れられると簡単ですけど、CCPRレジスタとして使われる10ビットの値を設定するようです。

 

100%の時、CCPR1L=0x13で、DC1B=0b11でした、つまり、0x4fが最大値ということになります。

 

半分の0x27にしてみました。少し暗くなったどうかという感じでよくわかりません。

極端に0x9にしてみました。今度は、はっきりと暗いというのが分かります。

 

これで、大丈夫そうです。

 

では、組み込んでみます。以前ボタン長押しで、ModeSetBrightというモードを作っておきましたので、そこで、タッチしたら明るさを変更していくようにすれば、良いですね。

 

今確認したら、現在99.7%、2042バイトになっています。たぶん、これ以上何も入れられませんね。とりあえず、デバッグも大体終わりましたから、表示系を削除したら良いですかね。

 

冗長になっているコードを治す必要もあるかもしれません。

 

加えたいのは、次の2行です。

 

                        Bright = (Bright + 1) % 5; //0(明)-4(暗)
                        EPWM_LoadDutyValue(0x4f >> Bright);

 

でも、メモリオーバーでした。最初のBrightの計算式をコメントにしたら、99.9%、2045バイトで、コンパイルできました。

 

プログラムの最適化を図ります。

不要な変数のint→char化をしました。

アルゴリズムの簡素化。

上記の%5という、中途半端な剰余が意外とメモリを食うようなので、2のべき乗の4にしてみました。

 

というようなことをやってみたら、何とか、99.4%、2035バイトと2Kバイトに収まりました。

 

まだ、表示機能は残しています。

書き込んで動作確認をしました。

 

ボタン長押しで、明るさ調整モードに入れ、タッチすると、明るさが変わるのが分かります。

LCDに表示させているBright変数もちゃんと見えています。

 

何とか、2KBに予定していた全部の機能を入れることができました。

 

また、文字数オーバーになってしまったので、続きは次の記事にて。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDスタンドをPIC12F1822でコントロール その2

2016/07/02 17:58

では、先ほどコンパイルできましたので、これを先日提示した5灯LEDスタンドに組み込んだPIC12F1822に書き込みます。また、6ピンのRA1とLCDディスプレイを接続すれば、状態が見て取れます。

 

電源を入れて、動作を確認してみます。

まずは、SWボタンを押して、LED点灯。もう一回SWを押すと消灯します。 OKです。

タッチの方もタッチして点灯、もう一度タッチすると消灯します。 OKです。

タッチした後、30秒ほど待つと、自動消灯します。 初期値がそのようになっていますから。 OKです。

 

次は、ボタンの長押しをします。5秒程度押しっぱなしにすると、1回点滅し、LCD上は、Mode=1と表示されます。 初期時は、Mode=0でしたので、OKです。

ここは、まだプログラムができていないので、ボタンを押して元のモードに戻します。Mode=0になりました。

 

今度は、10秒ほど押しっぱなしにします。途中5秒経過すると、上述のような動作を行い、10秒経過すると、今度は、2回点滅します。そのとき、LCDには、Mode=2と表示されれば、OKです。 そうなりました。

 

この状態で、タッチしてみます。タッチすると、1回長めの点滅をします。この時、LCDには、Time=2という表示がされます。これは、プログラムでは、OnTime=On1minだということを示してます。

さらにタッチすると、2回点滅して、Time=4と表示されます。これは、On2minの設定がされたという意味ですね。

さらにタッチすると、1回短く点滅して、Time=1と表示されました。これで元のOn30sの設定に戻ったことになります。

 

下の写真は、今の状態を示しています。LCDには、Mode=0、Time=2と表示されています。

後ろの方、右側にPIC12F1822と黄色いボタンが見えます。左の白いのが、LEDスタンドの足の部分です。

DSCN0311_thumb[1]

適当な状態で、SWを押すと、Mode=0とノーマルモードに戻ります。

 

そこで、タッチして、LEDを点灯させ、消灯までの時間を観察すると、設定した時間になっていれば、プログラムは正常に動いていることになります。

 

大丈夫です。ちゃんとできました。

 

今回は、ここまでにして、次回は、PWM制御で、明るさのコントロールを組み込むことにチャレンジしてみます。 もしかしたら、プログラムのサイズとの戦いになるかもしれません。

 

その前にディスプレイのハードをきれいにした方がよいかな。

 

では。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDスタンドをPIC12F1822でコントロール

2016/07/02 17:57

さて、mTouchの理解もできたので、本体を作っていきたいと思います。

 

先日書いたように、2KWしかないPIC 12F1822なので、プログラムサイズが心配です。

ちょっとしたことをプログラムしたら、サイズオーバーのためのコンパイルエラーとなってしまいました。何とかしないと、全然前に進めません。

mTouchが一番サイズを食っているのですが、これがないと話にならないので、どうしようかと思っていました。 その時、ディスプレイの機能を外してみたら、95%くらいのサイズになり、コンパイルできたので、これで、プログラムを作っていきました。

 

ところが、やはりバグというのはあり、プログラムを見ているだけではどうしても、何が問題かなかなかわかりません。やはりせっかく作ったディスプレイに内部変数を表示するなりして、効率よくデバッグしたくなりました。

 

少し、ディスプレイ用のサブルーチンを眺めていたら、基本的にEUSART_Write()だけで制御できるようにしていることに気づきました。 これがそんなに大きいサイズを占めるのかなと疑問に思い、一部だけディスプレイ機能を復活させて、コンパイルしてみると、ちゃんとサイズに入ります。

 

よくよく見ていくと、どうもsprinfを使っている、DisplayVal()がなければ、コンパイルに問題ないことが分かりました。sprintfのサイズが大きいのでしょう。そこで、このDisplayVal()は使わず、DisplayCharとEUSART_Write()だけ使って、内部変数を表示させるようにしました。1バイト変数なら、文字に置き換えることで、表示が可能です。

例えば、モードなら、3つの値しか取らず、0〜2ですから、1文字の'0'にModeを加えて、表示すれば、0〜2をLCDに表示できます。

 

これにより、デバッグが簡単にでき、勘違いしていたものをすぐに突き止めることができました。

 

プログラムで何を実現しようとしているかをまず示しておきます。

  1. タッチセンサで、一定時間だけLEDを点灯
  2. SWを押すと、LED ON/OFF (自動消灯しない)
  3. SW長押しで、設定モードに、再度SW押すと通常モードに。
    5s長押しすると、1回点滅し、そこで手を離せば、設定モード1になる。
    10s長押しすると、2回点滅し、そこで手を離せば、設定モード2になる。
    モード1. 明るさの設定:5段階
    モード2. 点灯時間の設定:30s、1分、2分
    それぞれ、タッチするごとに設定が変わる。
    明るさの方は、その明るさで点灯させればよく、気に入った明るさになったらSWを押して完了。
    点灯時間の方は、LEDの点滅回数でお知らせ、SWを押すと完了。 タッチするごとに、短い1回点滅で30s、長めの1回点滅で1分、短い2回点滅で2分の設定となる。以降30sの設定に戻って繰り返し。

今回は、モード1を除く部分を作りました。

明るさ調整は、PWMを利用する予定なので、新規機能になりますから。

 

ソースを示します。前回のTouchLED_v4で作ったプロジェクトは踏襲していることにご注意下さい。

 

4つのファイルに手が入っています。main.h, main.c, pin_manager.c, tmr2.cです。

 

main.hは、定義だけですけど、以下を2つの#ifdef __cplusplusの間に入れました。

//グローバル変数定義
    char BtnStatus; // ボタン押下なら1、10msごとにキャプチャしていく
    int LongPush; //ボタンをどのくらい押したか
#define NoPush 0
#define Short 1
#define Long1 500
#define Long2 1000

    int IntCount; // 割り込みタイマのカウント。10ms毎にカウントアップ
//    char LEDon = 0; //LEDの点灯状態を保持。1:On、0:Off
    //これは不要。LED_b_GetValue()を使えばOK。ただし、0の時がOn

    char Mode; //モード
#define ModeNormal 0
#define ModeSetBright 1
#define ModeSetOnTime 2

    char Bright; //0:Default(明るい)-4(暗い)
    int OnTime; //0:30s, 1:1m, 2:2m
#define On30s 30 //s単位
#define On1min 60
#define On2min 120

    // プロトタイプ
    void FlashLED(char count, char time);

 

次は、main.cです。こちらは、以下のようになっています。先頭のコメントを除く全文。

まだ機能していない、ModeSetBrightという明るさ調整のルーチンが書けるようになっています。

/*
* PIC16F1829コントロールLCDに表示
*/
char CursorX, CursorY;

/*
* カーソル位置の設定
* 左上(0,0)から開始
*/
void SetCursor(char x, char y) {
    EUSART_Write(1);
    EUSART_Write(x);
    EUSART_Write(y);
}

/*
* int変数の値をカーソル位置に表示
*/
void DisplayVal(int val) {
    char valStr[10];
    char *str;

    sprintf(valStr, "%d", val);
    str = valStr;
   
    while (*str)
        EUSART_Write(*str++); 
}

/*
* 文字列をカーソル位置に表示
*/
void DisplayChar(char *str) {
    while (*str)
        EUSART_Write(*str++); 
}

/*
* LED点滅
*   count数だけ点灯、time:点灯消灯時間(ms)
*/
void FlashLED(char count, char time) {
    char jj, kk;

//    LED_b_SetHigh(); //LED消灯
    for (jj = 0; jj < count * 2; jj++) {
        for (kk = 0; kk < time / 10; kk++) __delay_ms(10);
        LED_b_Toggle(); //LED反転
    }

}


/* 
  Main application
*/
void main(void) {
    char pushed; // タッチボタンが押されていたかどうか保持
    mtouch_sensor_sample_t data; // タッチセンサ

    // initialize the device
    SYSTEM_Initialize();
    TMR2_StopTimer();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    // 変数の初期化
    BtnStatus = 0;
    EUSART_Write(0x0c);
    pushed = 0;
    OnTime = On30s; // OnTimeは、秒単位。初期値は、30s点灯
    Mode = ModeNormal;
           
    while (1) {
        // Add your application code
        MTOUCH_Service_Mainloop();
        if (MTOUCH_Button_isPressed(Sensor_AN2)) {
            // タッチセンサが押された時の処理
            // モードにより動作が変わる
            switch (Mode) {
                case ModeNormal:
                    if (!pushed) {
                        pushed = 1;
                        // 初めて押した時だけ処理 (押し続けても連続処理はしないように)
                        LED_b_Toggle(); //状態反転
                        if (!LED_b_GetValue()) {
                            // 点灯したらタイマ2を開始する
                            IntCount = 0;
                            TMR2_StartTimer();
                        } else {
                            // 消灯時は、タイマ2停止
                            TMR2_StopTimer();
                        }
                    }
                    break;
                case ModeSetBright:
                    FlashLED(1, 200);

                    break;
                case ModeSetOnTime:
                    if (!pushed) {
                        pushed = 1;
                        if (OnTime == On30s) {
                            OnTime = On1min;
                            FlashLED(1, 300);
                        } else if (OnTime == On1min) {
                            OnTime = On2min;
                            FlashLED(2, 150);
                        } else if (OnTime == On2min) {
                            OnTime = On30s;
                            FlashLED(1, 50);
                        }
                        DisplayChar("Time=");
                        EUSART_Write('0' + OnTime/30);
                        EUSART_Write(10);
                    }
                    break;
            }
        } else {
            pushed = 0; //タッチをやめた(手を離した)
        }

        //長押し判定
        if (LongPush > Long2) {
            if (Mode == ModeSetBright) {
                // モードが変わった時だけ実行
                Mode = ModeSetOnTime;
                FlashLED(2, 100); //100ms間隔で2回点滅
            }
        } else if (LongPush > Long1) {
            if (Mode == ModeNormal) {
                // モードが変わった時だけ実行
                Mode = ModeSetBright;
                FlashLED(1, 100); //100ms間隔で1回点滅
            }
        } else {
            Mode = ModeNormal;
        }

        // 自動消灯タイマのチェック
        if (IntCount / 100 > OnTime) {
            // OnTimeに設定した時間経過したら消灯
            TMR2_StopTimer();
            LED_b_SetHigh(); //消灯
        }

//        data = MTOUCH_Button_Reading_Get(Sensor_AN2);
//        EUSART_Write(12); // cls
        SetCursor(0, 0);
//        DisplayVal(data);
        DisplayChar("Mode=");
        EUSART_Write('0' + Mode);
        EUSART_Write(10); 
    }
}

 

次が、pin_manager.cですね。IOC割り込みの所だけ掲載します。main.hのincludeをお忘れなく。

void PIN_MANAGER_IOC(void)
{   
    // interrupt on change for group IOCAF
    if(IOCAFbits.IOCAF4 == 1)
    {
        IOCAFbits.IOCAF4 = 0;
        // Add handler code here for Pin - RA4
        if (BtnStatus == 0 && SW_b_GetValue() == 0) {
            // 連続して割り込み発生しても2重に処理しないように
            BtnStatus = 1; // チャタリング防止のため、ボタンが押されてもすぐに処理しない
            LongPush = 0;
            // タイマ2のカウント開始
            IntCount = 0;
            TMR2_StartTimer();
        }
    }
}

 

最後に、tmr2.c。これも、MCCが自動生成したファイルに手を加えたところだけを掲載します。

こちらは、main.hとpin_manager.hのincludeが必要です。

 

void TMR2_DefaultInterruptHandler(void){
    // add your TMR2 interrupt custom code
    // or set custom function using TMR2_SetInterruptHandler()
    IntCount++;
    if (BtnStatus) {
        // ボタンを押下した時に実行する
        BtnStatus = BtnStatus << 1;
        if (SW_b_GetValue() == 0) {
            // SWが押下状態
            BtnStatus = BtnStatus | 1;
            LongPush++;
        }
        if ((BtnStatus & 0x0f) == 0b0111) {
            // 3回連続押下状態だったら
            LED_b_Toggle(); // LED点灯/消灯
        } else if (BtnStatus == 0) {
            TMR2_StopTimer();
            IntCount = 0;
        }
    }
   
}

 

これらをコンパイルすると、1957バイト、95.6%のサイズとなりました。

残り100バイト弱ですね。

 

少し、長くなりすぎたようで、以下に書いていた動作確認の記録は、次の記事にします。

記事へブログ気持玉 / トラックバック / コメント


MCCが生成したmTouchのプログラムの中身を調査 その2

2016/06/30 22:03

もう少し、mTouchのプログラムについてみていきます。

 

MTOUCH_Button_Service()を呼び出しているのが、MTOUCH_Service_Mainloop()です。

これは、実際にmain()ルーチンで使用した関数でしたね。

 

この中では、実際には4つの関数が処理されていました。

MTOUCH_Sensor_Sampled_Reset();
Sensor_SampleAll();

Button_ServiceAll();
MTOUCH_Tick();

 

最初に、サンプル値をリセット、サンプリングの実施、Button_ServiceAll()はMTOUCH_Button_Service()を呼び出しているだけで、上記で述べたボタンの処理がされます。

最後の関数は、スキップした関数ですね。中身はまったく何もありませんでした。タイミング調整用でしょうかね。

 

Sensor_SampleAll()は、MTOUCH_Button_Service()を呼び出しているだけでした。

MTOUCH_Button_Service()は、mtouch_sensor.cで定義されていました。

主な処理は、以下の3つでした。

Sensor_Acquisition(sensor);

Sensor_RawSample_Update(sensor);
Sensor_setSampled(sensor);
callback_sampled(sensor);

後ろから片付けます。最後のは、callbackの設定ですね。

その前のsetSampledはサンプリングされたというフラグみたいなものを設定しているだけでした。

その前のRawSample_Update()は、サンプリングされた値を生の値packet_sampleという変数を、配列変数に入れているだけでした。

 

では、このpacket_sampleは、どこで作られるか。

最初のSensor_Acquisition()が本丸ということですね。

ここでは、Sensor_Acq_ExecutePacket()を呼び出していました。

 

エラー処理とか色々あって、階層が深いですね。

Sensor_Acq_ExecutePacket()を見ると、ようやく、実体が見えてきました。

ここでは、ADCの設定を行って、その他、変数の初期化を行って、Sensor_Acq_ExecuteScan()を呼び出しています。

これが実体のようです。

コメントを読むと、センサを1回サンプリングするという書かれています。

上述のpacket_sampleに値がセットされている文も見えます。

 

さあ、何を行っているか詳しく見てみましょう。

 

packet_counterを見て、偶数なら、MTOUCH_CVD_ScanA()、奇数ならMTOUCH_CVD_ScanA()を実行していました。例のマシン語で書かれた処理ルーチンです。

このカウンダで、32回分サンプリングしていました。つまり、VDDプリチャージで計測する方法を16回、VSSプリチャージで計測する方法を16回行い、それらをうまく加算します。容量が大きいとき大きな数値になるような計算になっていました。

 

これにより、10ビットしかないADCの結果が15ビット相当に上がるわけです。これが生の値が16ビット変数だったわけでした。それゆえに、実験でみた22000という、10ビットADCではありえない結果が求められていたわけなのでした。わかってくると、すっきりしてきますね。

 

ここを見ると、やはり、端子をある電圧に充電して、その電圧をADCで計測するというように見えます。

 

プログラムの詳細を見る前に、ここで、少し調査しました。思っていた測定手法と異なっていたからです。

容量検知(CPS)モジュールをテストしてみますのページに静電容量の検出方法が3つ書いてありました。 その中で、タッチセンサとして、CSMという静電容量式センシングモジュールという手法で検出しているやり方を取っている報告しか、今まで見ていませんでした。 でも今回のプログラムでは、電圧を検知していますから、3つのうちのどうも、CVDという静電容量式分圧器という手法が気になります。

 

調べてみると、見つかりました。mTouch? センシング ソリューションのアクイジション手法 : 静電容量式分圧器 AN1478という日本語訳の資料がMicrochip社から提供されていたのです。もちろん、英語版もありました。日本語にもなっているので助かります。また、Capacitive Touch Using Only an ADC (“CVD”) AN1298という資料も参考になりました。

 

これで、ほぼ動作原理はわかりました。

 

最初の方に、手作業でのCVDの実装を推奨していないと書かれています。
センサのSNR (Signal Noise Ratio)のためには、タイミングが重要なので、実装する場合の言語として推奨できるのはアセンブルだけと書かれており、それがソースにも反映されていたようです。

 

動作原理を書いておきたいと思います

  1. コンデンサのプリチャージ
    2つのコンデンサを逆の電圧に充電します。
    2つのコンデンサと書かれていますが、一方がタッチセンサ用の導体CBASE、一方はPIC内の寄生容量CHOLDを指しています。
    充電する方法は、2種類あり、1回目に実行する方をサンプルAとして、外部センサをVssに放電、内部センサをVDDに充電するようです。2回目は、サンプルBで、外部センサをVDDに充電、内部センサをVssに放電と、逆にしています。
    これが、関数CVD_ScanAとCVD_ScanBに相当するものですね。
  2. コンデンサの接続とセトリング
    外部と内部のコンデンサをショートして、平衡状態にさせます。これをsettling 移行と言っているようです。
  3. A/D変換
    平衡状態になった電圧を測定します。この電圧は、電荷保存則により、外部静電容量Cbaseと内部静電容量Choldにプリチャージされた電荷を、両者の容量の和で割った値になります。
    Q=CVという関係式と、ショートした後の合成容量Cbase+Choldを使って計算できます。
    サンプルAの時:Cbase*0V+Chold*VDD=(Cbase+Chold)*V という式が成り立つので、V=VDD*Chold/(Cbase+Chold)となります。外部容量は手で触ったりすると大きくなり、ADCで測定する電圧は、小さくなります。
  4. プリチャージ電圧の反転と反復
    上記ステップ1〜3を今度は、サンプルBの条件で実行します。
  5. 1〜4で、2つのADCの結果が取得されます。その差をセンサ値として使います。
    プログラムでは、サンプルB値+(1024-サンプルA値)となっており、1024分(オフセット値)加算されていました。これが、上述したうまく加算の意味です。
    これにより、常に正値になるので計算上良いのだろうと思いました。値は、1〜2047を取ることになります。容量が大きくなる=タッチすると、値は大きくなります。
    プログラムでは、これを16セット繰り返して、加算していました。それより、10ビットだったADCの精度が、差動と16セット=4ビット分増えて、15ビット相当の数値が取得されることになります。
    ホビーではあまり気にする必要もないと思いますが、ノイズの問題についても記載がありました。低周波ノイズの場合、サンプルAとサンプルB間の時間が短ければ、同じノイズの影響を受けた値同士の差分を取ることで除去されます。
    サンプルAとサンプルB間の時間をランダム化すると、ノイズがたまたま打ち消されないような周波数に対して軽減効果があると書かれています。
    さらにサンプルAと次のサンプルA間の時間をランダム化すると、サンプリングレートの高調波ノイズを減衰できるとあります。
    この2つに出てくるランダム化(文書では無作為化)のため、乱数が必要だったようです。
    乱数により、2つの測定の間隔をランダム化させていたということでした。
    その他、内外の容量差が大きすぎる場合の手法として、ダブルCVDやハーフCVDという方法の記載がありました。これは、小さい方の容量に対して、アクイジションを挟んで2回プリチャージを行う手法でした。詳しくはドキュメントを読んで下さい。
    また、MCCの設定にもあったアクティブガード付きCVDスキャンについて記載があります。これは、センサの容量を小さくして感度を上げるために使うようです。

上述を図示したものを資料からいただきました。

下記は、VDDにCHOLDに充電して、INPUT側は0Vの状態にした後、ショートさせる絵ですね。

image

下記が横軸を時間軸として、各部の電圧をプロットしたものとなります。

image

最初、赤点線で示されているように、VDDに充電して、外部のセンサは0Vにして、その後、ショートして、両者が同じ電位になります。そのときの電圧がVAと示されています。その時の値をADCで測定します。ここまでが、SampleA、プログラム上ScanAという処理になります。

その後、今度は、逆の状態から初めて、VBという電圧を測定しているのが、SampleBになります。

 

その2つの差を計算すれば、原理的にはOKですが、正負両方の状態になるので、プログラム上は、SampleA側はVDDからみた値の1024-VA、SampleB側は、VSSから素直に見たVBを使って、両者を加算することで、同じ尺度で、2倍の精度になる形になります。

 

SampleA側をマイナスとしていることで、タッチして容量が大きくなって、VA値が小さくなると、プログラム上生の値が大きくなるという結果になるわけです。

 

実際のプログラムでは、少しこの動作と異なっていました。

上記の場合は、測定対象のピン以外のピンにVDDを出力することで、VDDをADCに取り込んでいました。そのため、1ピン余計に消費していました。

MCCのプログラムでは、センサに使うピンだけを使って処理がされていました。

動作を書いておくと、

  1. 測定に使うANピンをデジタル出力にして、1=VDDを出力。
  2. ADCのCHSをそのピンに接続。そうすると、ADC内部のCHOLDに充電(プリチャージ)されます。
  3. ADCをDAC側に接続を変更。ここがまだ、謎ですが、Hi-Z状態になっていれば、2で充電した状態が維持されます。
  4. 測定に使うANピンをデジタル出力の0=VSSに切り替え。これで、外部の容量が0Vに放電されます。
  5. ADCを再度測定ピンに接続します。これにより、外部の容量と内部容量CHOLDがショートされ、電位が平衡状態に移行します。
  6. ある時間経過したら、ADCを開始して、その電圧を測定します。

これを今度は、逆のプリチャージ電圧で上記を実行します。

 

この時、タイミングが非常に重要になるので、アセンブリ言語で書かれていることが分かりました。

容量にチャージされた電荷は、時間とともにリークという現象で消えていきますし、ScanAとScanBでタイミングが異なると、条件の違う電圧を図ってしまいますから。

 

ノイズもある周期をもつので、上述したように、乱数を使って、ノイズの影響を受けないようにと工夫がされていて、すごいと思いました。

 

本当に最後に残った1つの疑問は、上記3に書いたCHOLDに充電したのち、一時的にそれを切り離すため、DAC側に切り替えているところですね。 データシートを見ても、ADCに接続するDACの出力がHi-Zになるとは書いていません。DAC出力には、トランスファゲートがあり、Hi-Zに切り離せるようになっていますけど。

DAC関係の設定はされていないので、おそらくDACは、0V出力状態になっているのかなと推定しています。もし、そうなら、0VプリチャージするScanBは問題ありませんが、VDDプリチャージするScanAは、せっかくプリチャージした電荷がなくなってしまい問題です。でも、ちゃんと動いているので、Hi-Zになっているものとみなしておこうと思います。そうしないと、つじつまがあいませんので。

何か見落としているのでしょう。

 

ちょっと大変でしたけど、これで、心置きなくタッチセンサをプログラムで使えると思います。

mTouchの話は、ここまで、ではまた。

記事へブログ気持玉 / トラックバック / コメント


MCCが生成したmTouchのプログラムの中身を調査

2016/06/30 20:59

さて、TouchLED_v4の中に生成されているMCC関係のプログラムを見ていきたいと思います。

今回は、興味のない方には、面白くない内容かと思います。ご了承ください。

 

でも、この調査は、覚書として重要だと思っています。

 

Headerファイルと、Sourceファイルがありますが、タッチセンサに関係すると思われるファイルは、以下の通り。

mtouch_sensor_abstraction.h

mtouch.h, mtouch.c

mtouch_sensor_scan.h, mtouch_sensor_scan.c

mtouch_sensor.h, mtouch_sensor.c

mtouch_button.h, mtouch_button.c

mtouch_random.h, mtouch_random.c

 

ヘッダとCプログラムで、関係するものは同じ行にまとめました。6種類のファイルがあるということですかね。

 

最初のabstractionという単語が入っているファイルは、前にも書いたように概要というか、これだけをわかっていれば、使えるよという情報のように思えます。でも、これだけでは不足ですけど。

その中には、2つの関数が示されており、

MTOUCH_Sensor_wasSampled (sensor)
MTOUCH_Sensor_RawSample_Get (sensor);

となっています。
前者はサンプリングが終わったかどうかを調べるもので、後者は生のサンプル値を取得するものですね。これは、先日のタッチセンサの動作確認で使いました。

また、このファイルには、センサの名称が列挙されています。今回はSensor_AN2という名前で1つだけ定義されています。これは、MCCのmTouchの定義の時、Hardware SensorsのNameから来ていると思われます。今回センサは、1個だけ定義しましたから。

 

次に、mtouchです。

ここには、4つの関数が提示されています。

void    MTOUCH_Initialize ()
void    MTOUCH_Service_Mainloop ()
void    MTOUCH_Tick ()
void    MTOUCH_Notify_InterruptOccurred ()

最初のは、その名の通り、初期化ですね。これは、mcc.cの中で行われているSYSTEM_Initialize()から呼ばれています。

ここを少し中まで入ってみてみると、センサとボタンの初期を行っておりました。

センサの初期化では、AN2のポートの設定を行っていました。デジタル、出力、WPUなしと設定していました。(mtouch_sensor.cにて)

ボタンの初期化は、変数の初期化だけですね。(mtouch_button.cにて) ここのボタンは、タッチセンサをボタンのように使うという意味のボタンで、物理的なスイッチのボタンではないので、混乱しないようにして下さい。

 

次のService_Mainloopは、メインループ内で、記述せよということで、先日のプログラムでも使っていましたよね。

 

最後の2つは、保留にしておきます。後者の割り込みは使っていませんし。

 

次に、mtouch_sensor_scanを見てみます。2つの関数 (ScanAとScanB)がありました。

そのプログラムは、なんとアセンプリ言語で書かれていました。コメントが書いてあるので、見ていくと、ADCの処理をしているようです。

端子にある時間プリチャージして、その後、その電圧を測定しているような動作みたいです。

プリチャージする電圧が、1すなわちVDDか、0すなわちVSSの2種類あるようです。

 

あれあれ、イメージしていた容量測定方法と違うようですね。イメージしていたのは、容量を充放電して、発振させ、容量に応じて変化する周波数を、カウンタで計測するというやり方でしたから。それは、CPS (Capacitive Sensing) モジュールという手法です。

これは、要チェックですね。後でしっかり確認します。

 

では、次のファイルmtouch_sensorを見てみましょう。

ここには、4つの関数が定義されています。

MTOUCH_Sensor_Initialize
MTOUCH_Sensor_Service
MTOUCH_Sensor_Sampled_Reset
MTOUCH_Sensor_SetSampledCallback(void (*callback)())

上記で述べたように、センサの初期化、センサ値の取得、センサのサンプリング値のリセットと、コールバックの設定用といった感じのものだと思われます。

 

さあ、次は、本命のmtouch_buttonです。

ここには、12個もの関数が定義されていました。

初期化 (Initialize)、再測定 (Recalibrate)、サービス、ボタンが押された (isPressed) という関数がありますね。

他には、取得 (Get)の名称がついたものが、6個と、Setの名称がついたものが2つあります。

Getの方は、Deviation (偏差)、Reading、Baseline (基準値)、Scaling、Threshold (しきい値) という名称が見られます。

Setは、ScalingとThresholdの2つです。ここで、MCCで設定した値が設定されるのでしょう。

これを調べれば、疑問だった問題は解決できるような気がします。

 

でも、先に最後に残ったrandomをみてしまいましょう。中身をみたら、ガロアのLFSRアルゴリズムで疑似乱数の発生を行っていただけでした。(詳細はソースを見るなり、Google先生に聞くなりしてくださいね。) なぜここにあるのか不明ですけど。

 

ここまで、全体をざっと確認しました。

さて、調べるべきは、mtouch_buttonでしたね。

 

いくつか重要そうな変数が現れます。それらがどのようなものかまとめてみました。

  1. baseline:タッチしていない時のセンサ値。触っていない時のセンサの値を使って値を更新していきます。環境要因で揺らぐのに追従させるためだと思います。
    24ビットの数値変数でした。実際には、16ビット+4ビット(16回分)の実質20ビットの数値が格納されると思われます。
    どのようにこの変数が設定されるかというと、(それまでのbaseline値)*15/16+(新たにサンプリングした値)を新たなbaseline値と保持していきます。この計算の16は、BASELINE_GAIN=4 (これで平均する個数を指定)で指定されています。プログラムでは、2^4=16を活用しています。
    上記計算式は、大雑把に言えば、2^4=16回の平均値みたいな値を保持していることになります。 ただし、式からわかるように、全体を16で割ったりしていませんので、実際には、生の値の16倍の値を保持することになり、16ビット変数では不足になり、24ビット変数にしているようです。
    このbaselineは、サンプリング毎にこの計算をしているわけではなく、押していない状態の時(後述するNotPressed時)、かつ、10回に1回だけ計算しています。 この頻度は、MTOUCH_BUTTON_BASELINE_RATE=10と定義されています。
    つまり、時間的には長めの間隔で更新されているということです。
    ふつうは周囲の環境とか電源電圧、温度などで、触っていない状態の容量値はゆっくりしか変化しないと思われるので、それに即したプログラムになっていると思われます。
    実際にこのbaseline値を使うときは、元のスケールになるように、4ビットシフトして返すBaseline_Get()関数を使うようにしているようです。
  2. reading:Button_Service()が呼び出された時、サンプリングした値が入ります。16ビット値=0〜65535。
    この値を使って、押されたかどうかを判断します。
    注意すべきは、この値も1回毎の生の値ではなく、READING_GAIN=2で示された2^2回の平均みたいな値をとっています。
    GAINの値が2ですから、(現在のreading値)*3/4+(新たな生の値)/4という計算をして、新たなreading値としています。 更新は毎回行うのが、baselineと異なるところですね。こちらは変化を感知しないといけませんから、毎回実行しないといけないわけです。
  3. deviation:偏差と訳せばよいでしょうか。reading-baselineを、scalingの値だけ右シフト(1/2^n)したもの。8ビット値=0〜255。
    ここで書いたbaselineは、GAIN分をなくしたものになっているので、生の値と同じ尺度になっていることに注意して下さい。わかりにくく、申し訳ありません。
    両方とも16ビット値で、差を取っても16ビットになるのがふつうですけど、それをscaling分シフトさせることで、255までの8ビットの数値にしているようです。scalingの値が重要ですね。
  4. threshold:しきい値。1〜127の値。
    3で求めたdeviationが、このthreshold値を超えると、ボタンを押したと判定されることになります。
    MCC内でThresholdの値を変更する実験をしてみたら、127までしか入力できないようになっていました。
    この範囲にうまく入るように、Deviation Scalingを設定するみたいですね。Scalingは1〜16までの数値だけを受け付けるようです。

このボタンの処理は、ステートマシンとして処理されており、初期化、NotPressed、Pressedという3つの状態が定義されていました。

 

初期化が終わると、NotPressed、つまりボタンが押されていない状態となり、センサの状態を測定して、偏差deviationが、thresholdを超えているか確認しています。そして、thresholdを超えたら、Pressedという押された状態に遷移するというな動きになっています。

さらに、Pressed状態で、センサの状態を監視して、thresholdの1/2を下回ったら、NotPressedという状態に戻ります。

 

もし、deviationが連続16回負になってしまったら、baselineの初期化を行うようになっていました。

これは、例えば、初期値を取得時、触ってしまっていて、大きな値になってしまった場合に、再初期化するような事態ではないかと思われます。

 

センサの数値は常に揺らいでいるので、baseline値より小さくなり、deviationが負になることはしょっちゅうあると思われますが、16回連続というのは、普通あり得ない状態ということだろうと思われるので、それが選択されたものと推察します。なお、16回という回数は、MTOUCH_BUTTON_NEGATIVEDEVIATIONとして定義されていました。

 

くどいかもしれませんが、

deviationは、基本的に正の値。

deviation > threshold になったら、押したと判定し、Pressed状態に遷移、

その後、deviation < threshold/2 になったら、離したと判定し、NotPressed状態に遷移となります。

 

これらを行う元締めは、どの関数かというと、MTOUCH_Button_Service()でした。

その中でやっていることをまとめると、

  1. サンプリングされていたら、Button_Reading_Update()で、reading変数を更新。
    この値が、MTOUCH_Button_Reading_Get()で返されます。
  2. その後、Button_Deviation_Updateを行っています。
    deviationは、上述の通りreading値とbaseline値 (初期値なので、触っていない時の値ですね)の差を取り、scaling分小さくした数値。
  3. そして、ボタンの押下判断をして、ステートマシンの更新を行います。
    そのステートマシンの状態に応じて、処理が行われます。

だいたい動作がわかってきました。

 

今回の私のハードだと、前回実験したように、タッチの有無で、大体1000位の差が生じました。

そして、プログラムは、訳が分からなったので、デフォルトのまま、Scaling=3、Threshold=100と設定していました。

上記調査結果を踏まえると、deviation = (reading-baseline)/2^scaling をthresholdと比較して、押下の判断がされていたのでした。

実験の結果の数値を当てはめると、1000/2^3=125となりますから、しきい値の100をうまい具合に超えていることがわかります。そのため、適切に動いたということのようです。偶然でしたけど、ちゃんと動いていることがここで確認できました。

 

これで、ScalingやThresholdにどのような数値を設定するかわかりました。

 

ここまできましたけど、まだわかっていないのが、センサの生の値、つまりタッチセンサの容量をどのように取得しているのかというところですね。

もう少し解読を進めます。

 

まだ、長くなりそうなので、ここまででいったん区切りたいと思います。

続きは、次の記事にて。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDランプをPIC 12F1822でコントロールする ステップ4

2016/06/29 20:50

今度は、内部の状態がわかるように、USARTを使って、通信できるようにしておきたいと思います。

 

EUSARTの追加

前回のプロジェクトをコピーして、TouchLED_v4を準備しました。

 

例によって、MCCから始めます。

今頃ですが、ツールバーに image というアイコンがあることに気づきました。

これを押すと、MCCがOpen/Closeします。

 

さて、PICからデータを通信して出力させる機能を追加したいと思います。

LEDランプを制御しているPIC 12F1822は、送信側なので、PIC USARTで通信にトライ (後編)の記事が参考になります。まあ、そのつもりで、実験していたわけですけど。

 

MCCのDevice ResourceからEUSARTをダブルクリックで開きます。

設定は、9600ボー、8ビット、パリティなし、非同期と、受信側に揃えます。

デフォルト状態がそれになっていますね。

また、送信側なので、Enable Transmitにチェックをいれました。

image

そうすると、自動的にRA0が選択されました。RA4は使用済みなので、そうなるのでしょうね。

違うかもしれません。前の記事を読むと、どうもデフォルトがRA0になるようでもあります。

 

以上で、今回の設定は完了です。

 

Generateを押して、生成します。

 

あとは、プログラム中で、必要な個所で、EUSART_Write()を実行すればよいはずです。

 

まずは、試しに、前の記事と同じように、文字を順々に送信するようにプログラムしてみます。

main()内のwhile(1)のループ内に以下を追加しました。

 

EUSART_Write(code);
code++;
if (code > 'z') code=' ';

変数codeは、

unit8_t code=’ ‘;

とループ外で設定してあります。初期値は、スペース=0x20です。

 

コンパイルして、書き込みます。

 

そして、PIC 12F1822の7ピンRA0=TXと、PIC 16F1829の12ピンRB5=RXを接続しました。

今回は、電源も共通化させました。

 

そして、電源を入れると、どうなるでしょうか。

おおー、確かに動作します。ちゃんとLCDに文字が表示されました。ただし、ディレイもないので、かなり早く表示が流れます。

 

まあ、すでに確認していた動作なので、実際には、それほど感動はありませんでしたけど。

これは、まだ、途中確認のものなので、先に進みましょう。

 

ここで重大なことに気づきました。12F1822の現時点のファイルサイズが80%を超えていたのです。このPICは、2Kワードしかないのに、すでに1700ワードを超えている状態です。

mTouchの処理を加えたところで、急激にバイナリサイズが増大したようです。

確認してみると、v2は、たったの163ワードでした。

ところが、v3だと、1625ワードと10倍にもなっていました。

 

これは、困りました。以前にMCCを使わずにプログラムで実現できていた機能が、MCCを使ったら、入りきらない可能性も出てくるかもしれませんから。

寄り道をして調べると、メモリ容量が2倍の12F1840なるPICがあるようです。秋月電子でも今は、取り扱っているみたいですね。Webを見たら、お客様のご要望で販売開始しましたとありましたから、私と同じような方がいらっしゃったのでしょう。

 

前回、追加というかデバッグ用に12F1822を追加で1個購入しておいたのですけど、その時、気づいておけばよかったと反省です。50円高いだけです。相対価格だと1.5倍ですけど。

 

18F26K22の購入を決めたときは、メモリ容量を最大のものが良いとして購入したのに。

 

残した機能は、LEDの明るさをPWMで変更できるようにすること、点灯時間を変更できるようにすることです。それらをボタンとタッチセンサで対応させることです。

 

まあ、今は、やれるところまで、やってみましょう。

 

それはとりあえず次回に残して、今回は、タッチセンサの挙動の確認を進めます。

 

センサの値を表示させる実験

mtouch_sensor_abstraction.hに書いてあった関数を試してみます。

それは、MTOUCH_Sensor_RawSample_Get()です。

この関数で、タッチセンサの生の値が取得できそうです。

取得されるデータは、16ビットの数値みたいです。main()内のwhileループ内で、使ってみました。

どんな値になるかは、その結果を表示させないと状態が分かりませんから、先のEUSARTが役に立ちます。

 

まずは、プログラムを書き換えます。手を加えたのは、main.cだけです、以下のようにしました。

#include "mcc_generated_files/mcc.h"

#include "main.h"
#include "mcc_generated_files/mtouch/mtouch_button.h"
#include <stdio.h>

char CursorX, CursorY;

 

void SetCursor(char x, char y) {
    EUSART_Write(1);
    EUSART_Write(x);
    EUSART_Write(y);
}

void DisplayVal(int val) {
    char valStr[10];
    char *str;

    sprintf(valStr, "%d", val);
    str = valStr;
   
    while (*str)
        EUSART_Write(*str++);
   
}

void DisplayChar(char *str) {
    while (*str)
        EUSART_Write(*str++);
   
}

/*
                         Main application
*/
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR2_StopTimer();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    BtnStatus = 0;
    EUSART_Write(0x0a);
    mtouch_sensor_sample_t data; // タッチセンサ
           
    while (1)
    {
        // Add your application code
        MTOUCH_Service_Mainloop();
        if (MTOUCH_Button_isPressed(Sensor_AN2)) {
            // タッチセンサが押された
            LED_b_Toggle(); 
        }

 

        data = MTOUCH_Sensor_RawSample_Get(Sensor_AN2);
        EUSART_Write(12); // cls
        SetCursor(0, 0);
        DisplayVal(data);

       
    }
}

黄色でマークしたところが追加個所です。メインループの中で、毎回センサの値を読みだして、LCDに表示させるような処理にしてみました。

後述する新たに加えたSetCursor()関数というのを使って、画面の定位置に表示させるようにしました。

 

LCDディスプレイ (PIC 16F1829)のプログラムの改良

こちらは、LCDディスプレイがつながっているPIC 16F1829のプログラムの話になります。

LCD上の表示位置を決めるために、新たなコマンドをPIC USARTで通信にトライ (前編)で作ったプログラムに追加しました。

文字コード1が来たら、その後の2バイトをX、Y座標としてカーソル位置を変更します。

main()内のwhileの所だけを抜き出して掲載します。

    while (1) {
        // Add your application code
        LCD_posxy(currentX, currentY); // 位置指定

        data = EUSART_Read(); // Read data received

        switch (data) {
            case 12: //クリア
                LCD_cmd(0x01); // Clear Display
                break;
            case 10: //改行
                currentX = 0;
                currentY++;
                if (currentY >= 2) currentY = 0;
                break;
            case 1: // カーソル位置設定
                currentX = EUSART_Read(); // Read data received
                currentY = EUSART_Read(); // Read data received
                break;
            default:
                LCD_dat(data); // 文字を表示

                currentX++;
                if (currentX >= 8) {
                    currentX = 0;
                    currentY++;
                    if (currentY >= 2) currentY = 0;
                }
                break;
        }

    }

 

10を改行、12を画面クリアにしたのは、昔のASCIIコードを思い出したからです。PC-8001という今やだれも知らない本当に初期のパソコンは、BASICで、文字12のコードをprintすると、画面クリアとなっていました。

AQM0802Aの0〜5の文字コードは、ユーザ定義ができる文字ですが、ユーザ定義文字を使わないようにして、そこに各種特殊命令をまとめてもよいかもしれません。この辺は、おいおい考えたいと思います。

 

さて、これでどのように結果になったか。

 

LCDディスプレイに、数値が表示されました。

タッチしていない時は、大体22000くらい、タッチした時は23000を超えるくらいの数値が出ています。

 

差が見えているので、大体、良さそうですけど、やっぱり何をしているのかわかりません。

でも、今回は、目的としたPICの内部状態をLCDに1本の信号線で表示できましたので、ここまでとしたいと思います。これでデバッグしたい時、少しは楽ができそうです。

 

次は、現在ブラックボックスになっている、MCCが生成したプログラムの中身を理解したいと思います。

 

では。

記事へブログ気持玉 / トラックバック / コメント


5LEDスタンド PIC 12F1288制御のハードの作成

2016/06/26 20:27

しまった、この記事をアップしておくのを忘れました。

今は、ブレッドボードではなく、ここで作った実際のハードで動作させていることをお知らせしておきます。文面は少し、現状では違和感あることはご了承ください。

 

大体構想が固まってきたので、回路を決めます。

 

以下の様にしました。

image

 

以前掲載した回路図から変わった所は、5灯のLEDをドライブする必要から、トランジスタを追加しているところです。

以前書いたように、87mAも流していましたので、PICのポートではドライブできないからです。確か20mAまででしたか。 データシートみたら、絶対最大定格で25mAとなっていますが、動作条件としては、5V電源で、High出力時3.5mA、Low出力時8mAとなっていますね。

 

また、オリジナルのものは、10Ωの抵抗を介してLEDに電流を流していましたが、今回は、トランジスタが間に入るため、10Ωより小さい抵抗にする必要があるかと思って、可変抵抗を入れてみました。

 

一応、ブレッドボードで動作確認しました。

誤算だったのは、可変抵抗値を10Ωから変えても、明るさが目で見てわかるレベルでなかったことです。なんでだろう?どこかで計算間違いしたかな。

 

2SA1015トランジスタ部でVCE=-0.3Vと書いてあったので、電源4.5VからVCE分降下して、かつ、白色LEDのVF=3.5V位と言われているので、抵抗部は、0.7V位になってしまうと考えたのです。

そこに87mA流すには、8Ωにしないと元と同じ明るさにならないと計算したのです。

 

せっかく少し高い価格の可変抵抗を購入したので、使う計画は変更なしで行きます。なお、抵抗値は、20Ωのものを購入しました。それは、元の明るさが少し明るすぎるような気もしていたので、抵抗値を少し高めにするのも良いかなと思っていたからでもあります。

 

タッチセンサは、RA2に接続を変更しました。

 

ピンが余っているので、スイッチも付けました。RA4に接続です。ただし、秋月の10円のタクトスイッチに変更します。元のスイッチは、トグル型というか、一度押すとオンして、もう一度押すとオフするタイプでした。オルタネートタイプというのが適切みたいです。これですと、2回押さないと、元の状態に戻らないので、今の構想に合わないので、不採用となりました。

元のスイッチの形状に合わせて、加工した基板にタクトスイッチを取り付けました。

少し適当すぎる工作でお恥ずかしいですが、下の写真のように作りました。

DSCN0272

そして、はめ込んだのが下の左の写真です。うまくはめることができました。

ちなみに、元のスイッチが入っていた時の写真は右になります。

DSCN0273DSCN0259

 

そして、回路図に合わせて工作をします。まずは、位置決めから。

大体以下のような形にしようと考えました。まだ、はんだ付け前です。

DSCN0277

 

そして、出来上がったのが、以下の写真です。

DSCN0287DSCN0289

半固定抵抗の向きは変更しました。この方が配線が短くできるので。

 

これを見て作ろうという人はいないとは思いますけど、覚書として、少し説明しておきます。

赤、白、青の配線が上に伸びています。赤と青はLEDに接続されています。青い方がGND側です。白の配線は、静電容量センサとして、導電性の板に接続します。

黒と、緑は、スイッチの両端ですね。左の写真の基板の上端に5ピンつながったソケットが見えますが、これは、PICkit2と接続するピンソケットとなります。左からVPP、VDD、GND、PGD、PGCの順でPICkit2と同じ並びになっています。GNDがPICの8ピンと並ぶようにすると、PGDが7ピン、PGCが6ピンと、最短で接続できる計算です。VPPは、黄色の配線で4ピンと接続しています。VDDは、赤で1ピンと接続、残りの接続は回路図と見比べてみて下さい。

 

それを100円の5LEDスタンドに入れた写真も加えておきます。

DSCN0290

タッチセンサ用の電極は、アルミホイルを暫定的に使っています。白い上側のふたの裏面にテープで貼りつけた状態になっています。ちょっとかっこわるいですが、アルミホイルも立派な導電体ですし、加工が手軽ですね。問題があれば何か考えます。

 

まだ、電池との接続はしていません。デバッグ中は、ほぼPICkit2から電源供給するので。

ハードは、結構コンパクトにまとめられたかなと思っています。ねじ止め用の穴も基板にはありませんが、動かして使うものでもないですし、蓋をしてしまえば、ほとんど動かないと思いますけど、気になったら、適当に接着してしまえば良いかと思っています。

 

唯一問題なのは、タクトスイッチの高さが高くて、元のスイッチの部品をつけて蓋をすると、押しっ放しの状態になってしまう点です。これは、タクトスイッチの黄色の部分を少し削るか、元のスイッチの部品(プラ)を削るかのどちらかを行って対応する予定です。

 

ハードが大体できあがりましたので、ソフトの作成に取り掛かりたいと思います。

 

今回は、ここまで。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDランプをPIC 12F1822でコントロールする ステップ3

2016/06/26 20:18

さて、今度は、タッチセンサを動かしたいと思います。

 

前回と同様に、TouchLED_v3をv2をコピーして作り、Main Projectに変更します。

 

MCCの設定

Device Resourcesから、mTouchを追加します。

Button and Proximity ConfigurationとAFA (Automatic Frequency Adapatation)というものが表示されています。

 

Notificationsに1つWARNINGが出ていて、読むと、mTouchを使うには、ADCが必要だから、ADC moduleを追加しなさいと書かれています。

 

では、1つずつ対応していきましょう。

 

まずADC moduleを追加してみます。

Device Resourcesの中からADC→ADCダブルクリックで追加します。

さて、何をどう設定したらよいか???さっぱりわかりませんので、とりあえず放置します。

 

容量測定は、どうやっていたかを思い出すと、PIC内部で、測定対象のピンに対して、充放電を繰り返し、一定時間内で何回それが発生したかを見ることで、容量を把握するということでした。

この時、2つの閾値を設定して、上限の閾値に達したら、放電に切り替え、下限の閾値に達したら、今度は、充電に切り替えるということをPIC内部で行っていました。

人 (プログラム) がチェックするのは、そのオシレータが何周期したかのカウント値ですね。

 

この上下限の閾値として、内部参照電圧か、FVR、DACを選べるようになっていました。

前のプログラムを見ると、FVRを使っていたようです。FVRは、ADCにも関係しているみたいなので、そのモジュールが必要になるのだろうと思いました。

 

でも、やっぱりわからないので、放置です。

 

しかし、Notificationsの状態が変わりました。ADCが必要とのWARNINGは消え、mTouchはADCを使用しているというINFOに変わりました。しかし、また別のWARNINGが表示されています。

mTouchは最低1個のハードウエアセンサが必要だと言っています。ハードウエアセンサを加えるには、最低1個ADC ANxピンを加えなさいとあります。

 

その指示に従い、Pin ModuleでRA2をアナログ入力にしてみます。

image

すると、上記のようにPin Managaer (画面下のウインドウ)にmTouchというのが現れました。

この時、まだ、Notificationは、WARNINGが出たままです。そこで、上記ではまだカギがかかっていないmTouchのRA2の所をクリックしてカギをかけてみました。

すると、以下のようになりました。

image

新たなマークに変わりましたね。ADC側とSensorがリンクしているようなイメージなのでしょうかね。

 

この時、Notificationsはさらに項目が増えましたが、WARNINGはなくなりました。

HINTというTypeのメッセージがあります。

1つは、上記のキャプチャで見えているTx Guardについてで、センサの感度向上のため、active guardとかいうものが使えるとのこと。それを使いたいなら、Tx Guardのピンを選択しなさいとあります。今は、なんのこっちゃという状況なので、何もしないでおきます。

 

もう1つのHINTは、何々、センサAN2は、まだボタンかセンサに割り当てられていません。Easy Setupでセンサとして入力するものを割り当てて下さいと書いてありますね。

どういうことでしょうかね。Easy Setupと言っても、それぞれのResource毎にあるので、最初、Pin Moduleの所にいましたから、訳がわかりませんでした。

 

でもmTouchを開いてみると、一番初めに書いたButton and Proximity Configurationがそれっぽい気がしてきました。右向き三角を押して開いてみると、Hardware Sensorsという所に、設定したRA2/AN2の表示は見えます。その上にButtonsとProximitiesという2つの項目が見えます。

よくわかりませんが、今回は、ボタンとして使うので、Buttonsの方を選んでクリックしてみました。

 

Create New Buttonというボタンがあるので、クリックすると、Button0というのができますが、まだ何も設定していないので、No sensor selectedとなっています。

そこをクリックして、右側に4項目の設定項目を編集していきます。

大事なのは、Sensorというところでしょう。プルダウンメニューを押すと、今回は1つしか設定していないので、Sensor_AN2しか出てきませんがそれを選択します。そして、名称を適当に変更しましょう。今回は、TouchButton0としてみました。

残りの2つは、今はよくわからないので、そのままにしておきます。ThresholdとDeviation Scalingとなっており、前者は触れたかどうかの判定基準みたいですね。後者は、後で調べます。

以下のキャプチャがその時の状態です。

image

 

そうしたら、Notificationsを見てみます。幸いなことに、WARNINGはありません。また、先ほどHINTとして出ていた項目は、センサのButton Settingsをしたので消えたようです。

 

あと、読んでいなかったHINTは、1つありました。mainループの中で、MTOUCH_Service_Mainloop()を呼び出すように書いてあります。これは自分で設定しないといけないということですね。

 

これで、タッチセンサが使える準備はよいでしょうかね。ADCの追加して、mTouchの設定をしましたから。もう1つのAFAの方は取り合えずそのままにしておきます。

 

では、Generateします。すでにv2で手を入れていますから、自動生成のファイルとは違っていますが、mergeされるので問題ないでしょう。

 

プログラム作成

main.cから見ていきます。何も変わっていません。

先の指示通り、MTOUCH_Service_Mainloop();をループ内に記述してみました。

 

今回、新たに加わったファイルはずいぶんいっぱいありますね。

mtouch.c, mtouch_button.c, mtouch_random.c, mtouch_sensor.c, mtouch_sensor_scan.cとそれぞれのヘッダファイル、それにmtouch_sensor_abstraction.hというファイルができていました。

最後のファイルから見るように思えますね。

 

まず、やりたいのは、単にタッチセンサが押されたかどうかをメインループでチェックするだけです。

 

さて、どうするのでしょうか。

abstraction (概要)という単語が含まれたヘッダファイルを見ると、Global Functionsとして、2つだけ記載がありました。

bool                   MTOUCH_Sensor_wasSampled     (enum mtouch_sensor_names sensor);
mtouch_sensor_sample_t MTOUCH_Sensor_RawSample_Get  (enum mtouch_sensor_names sensor);

最初の関数は、センサのサンプリングが終わったかを確認するものですかね。

次のがセンサの値を取得するものみたいですね。

両者とも、enum mtouch_sensor_namesで定義されたセンサ名を引数として指定するようです。

少し上の方に、その定義はあり、今回は1つだけです。Sensor_AN2という名称です。

これは、MCCで定義されたものですね。ここは自分で変更しなかったので、デフォルトのままになっていました。

 

他のファイルも見てみます。mtouch.hが良さそうです。

このファイルには、上記でmainに加えたMTOUCH_Service_Mainloop()がここで定義されていました。

 

使い方が書いてありますね。それによると、

  1. あなた自身のアプリケーションにコールバック関数を定義しなさい。
    それには、mtouch_button.hをインクルードして、次にコールバックAPIを使って、あなたのアプリケーションの関数に対してポインタを指示しなさい。ということで、2種類の関数が示されていました。

    void MTOUCH_Button_SetPressedCallback   (void (*callback)(enum mtouch_button_names button));
    void MTOUCH_Button_SetNotPressedCallback(void (*callback)(enum mtouch_button_names button));
    ポインタとか難しいことはおいておいて、上記のcallbackの所に自分が記述する処理ルーチン名を書けばよいでしょう。

  2. もう一つの方法として、適切なAPIを使って、mTouchのステートマシンの状態を直接みる方法をとることもできるとあります。
    この場合も、インクルードするのは1と同じで、状態を見るのに、以下の関数を使いなさいとありました。
    bool MTOUCH_Button_isPressed     (enum mtouch_button_names    button);
    こちらの方法は、分かりやすいかもしれません。

ここからは、必要な定義を順に追ってみていくしかないでしょう。

最初に追加したMTOUCH_Service_Mainloop()との関係もよくわかりませんね。

 

しばし、長考というか、ソースファイルの解読です。

 

まだ、ちゃんと全部を理解したわけではありませんが、

MTOUCH_Service_Mainloop()は、タッチセンサをリセットして、サンプリングというような全動作をしているもののようです。

 

そして、今回は、上記の手法の2を使いましたけど、MTOUCH_Button_isPressed (Sensor_AN2)で押されたかどうかをチェックするようです。

 

とりあえず、以下のような感じにしてみました。main.cです。

while (1)
{
    // Add your application code
    MTOUCH_Service_Mainloop();
    if (MTOUCH_Button_isPressed(Sensor_AN2)) {
        // タッチセンサが押された
        LED_b_Toggle();
    }
}

 

#include "mcc_generated_files/mtouch/mtouch_button.h"

も忘れずに。

 

どうなるかわかりませんけど、コンパイルしてみます。成功しました。

だめもとで、PICに書き込んでみます。

 

結果

SWの方は、従来と変えていませんので、ちゃんとOn/Off動作します。

さて、タッチボタンはどうでしょうか。。。

 

おおー、LEDが光った! ということは、タッチセンサが反応したということです。

 

チャタリング処理もなにもしていませんので、触っていると、Toggle処理により点滅してしまい、手を放した時、点灯したり、消灯したりといった状況だったり、手で触っても反応が鈍い感じだったりと、不安定な状況ですけど、動作することはわかりましたので、大前進です。

 

しきい値とかも設定しなおさないととか思っていましたけど、なんだかたまたまかもしれませんが、動いてくれて本当に助かりました。

 

ここから、次のステップに進めそうです。

次は、タッチセンサがどのような状態なのか、信号線1本でシリアル接続して、LCDに表示させるというところでしょうか。

まあ、その前に、もう少し中身を理解しておかないといけないかもしれません。

 

今日の所は、ここまで。 ではまた。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDランプをPIC 12F1822でコントロールする ステップ2

2016/06/26 14:17

TouchLED_v2

続いて、タイマを使って、自動消灯を行うようにしてみます。

プロジェクトは、v2として、前のものは保存しておきます。

 

前のv1をベースに作りますので、今回からは、新規作成ではなく、コピーして進めます。

左側のProjectsのウインドウ内で、TouchLED_v1を右クリックして、Copy...を選びます。

ポップアップが出るので、名称だけを変更します。

image

Copyボタンを押して、コピーします。

そうしたら、TouchLED_v2ができるので、それがmainプロジェクトになるようにマウスの右クリックで、Set as Main Projectを選びます。そうすると、TouchLED_v2が太字になります。

 

ここで、今回使うタイマのために、MCCウインドウを開きます。

Window→MPLAB Code Configurator v3→MCCですね。

 

ここで何を行おうとしているか先に決めておかないといけませんね。Timer2を使って10msごとの割り込みを行い、時間を計測することにしましょう。10msという間隔はチャッタリングの対応にもちょうどよいかもしれません。将来的には、LEDの明るさ制御にPWMをTimer2に使うことになると思われますけど、ここのステップではそれは封印しておきます。

 

左側中央部のDevice Resourcesの中から、Timer→TMR2をダブルクリックで選びます。

すると、中央にTMR2のウインドウが開きます。

ここで、10msになるようにPrescalerとPostscalerを設定します。

Postscaler=1:1、Prescaler=1:64、Timer Periodの入力ボックスに10msと入力しました。

これで、実際には、10.24msのタイマ設定ができたようです。

Registersのタブを開くと、普通は自分で設定すべきPR2が0x13となっていることがわかります。

 

image

 

そして、割り込み設定のため、左上のInterrupt Moduleをクリックして、Interrupt Moduleのウインドウを出します。TMR2の割り込みを有効化させるためEnabledにチェックを入れます。

image

 

これでGenerateします。もちろん、Force Updateで。

v1を引き継いでいるので、main()文は、すでに割り込み有効の設定がされています。

 

interrpt_manager.cを開いてみると、TMR2割り込みの記述が追加されていました。

処理は、TMR2_ISR()となっています。Navigateでどのようになっているか見ると、これは、tmr2.cというファイルにありました。

 

TMR2IFのフラグのクリアとTMR2_InterruptHandler()が呼び出されている処理がされていました。

 

実際の処理はどこに書くかというと、TMR2_DefaultInterruptHandler()の中のようです。

// add your TMR2 interrupt custom code

という記載がありました。

 

ここからは、ふつうにプログラミングです。

 

今のままでは、常に10msごとの割り込みが発生してしまうので、まずは、TMR2_StopTimer()でタイマを止めておきます。

ボタンを押されたら、タイマ開始TMR2_StartTimer()して、10ms割り込みを始めます。

10ms割り込み処理ルーチン内で、時間計測をします。

そして、設定時間になったら、LEDをオフ、タイマの停止というような処理にしてみます。

 

チャタリング防止もやります。

 

複数のファイルに渡りますが、1つずつ提示します。

まず、グローバル変数定義のため新規に作ったmain.h

 

#ifndef MAIN_H
#define    MAIN_H

#ifdef    __cplusplus
extern "C" {
#endif

//グローバル変数定義
    char BtnStatus; // ボタン押下なら1、10msごとにキャプチャしていく
    int IntCount; // タイマのカウント


#ifdef    __cplusplus
}
#endif

#endif    /* MAIN_H */

コメント入れて3行だけ自分で追加しています。黄色のマーカーで塗った部分ですね。以下同様。

 

次に、main.cです。

#include "main.h"

/*
                         Main application
*/
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR2_StopTimer();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    BtnStatus = 0;
   
    while (1)
    {
        // Add your application code
    }
}

 

あと、2つあります。pin_manager.cは以下の通り。

#include "../main.h"        //追加

 

(変更なしなので、途中省略)


void PIN_MANAGER_IOC(void)
{   
    // interrupt on change for group IOCAF
    if(IOCAFbits.IOCAF4 == 1)
    {
        IOCAFbits.IOCAF4 = 0;
        // Add handler code here for Pin - RA4
        if (BtnStatus == 0) {
            // 連続して割り込み発生しても2重に処理しないように
            BtnStatus = 1; // チャタリング防止のため、ボタンが押されてもすぐに処理しない
            IntCount = 0;
            TMR2_StartTimer();
        }
 
    }
}

SWが押されて、ここに来たら、BtnStatus (初期値0)を見て、1にセット、また、タイマをカウントするためのIntCount=0にリセットして、TMR2をスタートさせます。

 

最後に、tmr2.cです。

#include "../main.h"        //追加

/**
  Section: TMR2 APIs
*/

 

(変更なしなので、途中省略)


void TMR2_DefaultInterruptHandler(void){
    // add your TMR2 interrupt custom code
    // or set custom function using TMR2_SetInterruptHandler()
    IntCount++;
    BtnStatus = BtnStatus << 1;
    if (SW_b_GetValue() == 0) {
        BtnStatus = BtnStatus | 1;
        if (BtnStatus == 0b0111) {
            // 3回連続押下状態
            LED_b_Toggle(); // LED点灯?消灯切替
        }
    }
    if (IntCount > 1000) {
        //10s経過したら消灯
        TMR2_StopTimer();
        LED_b_SetHigh();
    }
   
}

 

ここでは、TMR2の10msごとの割り込みで、SWが継続的に押されているかを確認して、3回連続SWが押下されていたらボタンが押されたと判断して、LEDの点灯と消灯を切り替えています。

 

また、TMR2の割り込みの回数をカウントして、1000回、つまり10ms×1000回=10s経過したら、TMR2は停止させ、LEDは消灯という処理も行っています。ここは、自動消灯の処理ですね。

1000を増やせば、消灯までの時間を調整できます。

 

これをコンパイルして、PICに書き込んで動作を確認しました。

ボタンを短く押したり、長めに押したりして、反応を見ましたが、思ったような反応性になり、これでOKそうです。

 

これで、v2の完成とします。

次は、問題のタッチセンサ対応ですかね。 では、また。

記事へブログ気持玉 / トラックバック / コメント


5灯LEDランプをPIC 12F1822でコントロールする ステップ1

2016/06/26 11:42

最初、完全版を作ろうと、MCCで色々初めての設定もやろうとしていました。Timerとか、mTouchとか。そうしたら欲張ったせいか、エラーが出て、MCCのGenerateもできない状況に陥っています。

そうすると、プログラム作成も開始できず、前に進みませんので、方針を変えて、欲張らずに、一歩ずつやろうと思います。

 

とにかく、MCCが使えることがわかりましたので、MCCを使ってプログラムしてみようと思っています。

 

ハードは、すでに製作した8ピンのPIC 12F1822で、5灯のLEDランプ+タッチセンサというものになります。

 

回路図を示しておくと、以下となります。いつものようにEagleを使っています。

image_thumb[1]

 

まずは、単純にSWでLEDのOn/Offを。

 

System Moduleの設定

プロジェクト (TouchLED_v1としました) を作ったら、最初は、Project Resources→System ModuleでOSCの設定ですね。

電池の持ちも考慮して、500KHz_MFに設定します。

image_thumb[4]

Easy Setupしか使っていません。

Notificationsに1件出ていますが、INTOSCだとPLLは使えないという、INFOというTypeのものなので、問題ありません。

 

Pin Moduleの設定

次に、Pin Moduleです。RA4にSWがつながっているので、GPIOのinputと設定します。名称は、SW_bとしました。0=Lowになったら、SWがOnですから。WPUは使うようにチェックを入れました。

 

LEDのコントロールを行うRA5は、トランジスタを介して、LEDのOn/Offを制御しますので、GPIOのoutputと設定します。名称は、LED_bとしました。これも0でLEDオンと負論理なので。Start Highにチェックを入れて、はじめは点灯しないようにします。

image

image

 

Interrupt Module

SW_bが変化したら、LEDをOn/Offしたいので、入力変化割り込みIOCを設定しました。

image

 

これだけで、Generateしてみます。できました。

 

プログラム作成

main.cを開いてみます。

例によって、割り込みの設定は、コメント化されているので、コメントを外します。

割り込みの有効化IOCIEは、初期化ルーチンSYSTEM_Initialize()から、PIN_MANAGER_Initialize()が呼び出され、そこで、設定されていました。

マウスの右クリックから、Navigate→Go to Declaration/Definitionでたどれるので、手間はそれほどかかりません。

 

実際の割り込み処理は、interrupt_manager.cを開いてみてみます。

割り込みが発生すると、PIN_MANAGER_IOC()が呼び出されるようです。

このルーチンは、pin_manager.cにありますが、中身は空です。

ここに必要な処理を書けばよいようです。

 

で、ここで、設定を忘れていることに気づきました。

Pin Module設定で、画面キャプチャでは切れているODの右隣にIOCという項目があります。そこはデフォルトでは、noneになっていますが、今回は、SWは、押すとGNDレベルになるので、それを検知するため、negativeにします。

再度、強制Generateします。Force Update on Allをしてから、Generateボタンを押しました。

すでにmain.cの割り込みの所のコメントを外していたので、そこが違うと表示され、Outputウインドウには、Forcing diff/mergeと表示されました。差分が表示されているウインドウは、閉じました。閉じるとき、この情報は消えるというメッセージがでますが、気にせず閉じました。差分の情報を参照しながら作業したい時は、閉じない方がよいかもしれません。

 

さて、作業に戻ります。

設定を変更したので、pin_manager.cの初期化ルーチンPIN_MANAGER_Initialize()が変更され、RAポートがnegativeで割り込みが発生するように定義されました。

 

また、先ほどは、空だったPIN_MANAGER_IOC()の中に、フラグをクリアするような処理が追加さていました。

また、

// Add handler code here for Pin ? RA4

というコメントがあり、ここに必要な処理を書くように促されていました。

 

ここで、SWが押されたことに応じて、LEDのOn/Offを変更するように、LED_Flagというグローバル変数を作り、それを反転するように記述を追加します。

      LED_Flag ^= 1; //LEDの点灯/消灯を切替

という行を追加しました。と書いて、変数定義をどこに記述するか眺めていたら、pin_manager.hには色々な定義がされていることを見つけました。

 

その中で、LED_b_Toggle()というものがありました。以下のように定義されています。

#define LED_b_Toggle()   do { LATA5 = ~LATA5; } while(0)

これを使えばよさそうです。

 

上述の文の代わりに、これを記述し直しました。

 

これでコンパイル(エラーはありませんでした)して、PICに書き込みます。

SWを押すと、LED点灯、もう一度押すと、LED消灯ということが簡単にできました。

でも、チャッタリング回避もしていないので、点灯するはずが、一瞬光って消灯してしまうとか、その逆が起こったりはしますね。

 

でも、MCCって、本当に簡単にプログラムできますね。

プログラムコードは示しませんが、main.cで割り込みのEnableの所2か所のコメントを外したのと、pin_manager.cの上述のPIN_MANAGER_IOC()に、LED_b_Toggle();を追加しただけですから、問題ないでしょう。

 

次のステップに進みましょう。それは、次の記事で。

記事へブログ気持玉 / トラックバック / コメント


続きを見る

トップへ

月別リンク

KazHatブログ/BIGLOBEウェブリブログ
[ ]
文字サイズ:       閉じる