PSoC UARTメモ
by K.I
2007/12/06
Index
- タクトスイッチで操作するコントローラは良く作るんだけど、PCからもコントロールするようにしたい。
- タクトスイッチは、GPIO割込みで、設定、操作。
- シリアルは、受信割込みで、設定、操作が出来るようにする。
- それ以外では、SLEEPで消費電力を抑える。
- って感じにしようと思ったんだけど、
- PSoCのUARTを真面目に使ったことがなかったので、いろいろ引っ掛かってしまった。。
[top]
- デバッグしながら書いたものなので纏まりが無いし、間違いもありそうだけど、記録として残しておこう。
- 最初、標準のUART_1_bCmdCheckとか使ってプログラムを書いてみる。
- シリアルインターフェースに全く反応しない。
- ちゃんと割込みが掛ってないのかなぁ。割込みハンドラを書かないとダメなのかなぁ。
- ということで、受信割込みハンドラを書いてみることにしました1。
- UART割込みルーチンのuart_1int.asmに、ljmp _rx_handlerとして、
で割込み許可されてるはずなのに、割込みハンドラに飛んでこない。
- GPIO割込みは掛ってるし、割込みハンドラはLEDを点滅させるだけなので、こんなの動かないわけが無いんだけど。。
- 試しにboot.asmのジャンプテーブルに直接書いてみてもダメ
org 2Ch ;PSoC Block DCB03 Interrupt Vector
ljmp _rx_handler
reti
まるで割込み許可されていないような感じだ。
試しに直接割込みフラグをEnableにしてみたが、ダメ。
INT_MSK1 |= INT_MSK1_DCB03; //RX Interrupt enable
- 実際に波形が来てないんじゃないだろうか?ということで、ロジックアナライザのDV1-100でシリアルインターフェースの信号をみてみる。
- 今回は、FT232Rを初めて使ったので、結構疑っていたんだけど。
- 操作アプリのDigiView2で波形確認。TX,RXともに問題なし
UART_1_IntCntl(UART_1_ENABLE_RX_INT); // Enable RX interrupts
INT_MSK1 |= INT_MSK1_DCB03; // RX Interrupt enable →これは試しにやっただけで本来は不要
M8C_EnableGInt; // Global Interrupt enable
#pragma interrupt_handler rx_handler
boot.asm上の割込みテーブルはOK
org 2Ch ;PSoC Block DCB03 Interrupt Vector
ljmp _UART_1_RX_ISR
reti
uart_1int.asm上の_UART_1_RX_ISRルーチンでは、_rx_handlerにljmpしてる
ljmp _rx_handler
- ここまで、UARTのRXが設定されているDCB03の割込み処理には問題が無いように見える。
- RXにデータが入力されていないのでは?
- TX,RXともに、ロジアナで波形を確認した。ボーレートも問題なし。
- Port1_6→GIO6→SysClkSyncでRI0[2]→DCB03のRX端子に接続
- Port1_6のI/O設定は、Hi-Z状態(入力)、InterruptはDisable。。。
- でも、UART割込みは別に許可してるんだから、関係ないよなぁ。
- とりあえず、FallingEdgeに変更してみよう
- そうか!Sleep状態からはデジタルモジュールからの割込みは掛らないということか。
- SLEEPからのWakeUpはGPIO割込みじゃないとダメなのかな。
- Sleepなしに設定してみたと思ったけど、気のせいかなぁ。。
- GPIO割込みでWakeするようにしてみると、キー入力から20ms弱でLEDが反転し、さらに20ms弱でまた反転する
- RX割込みでキーコード+CRLFを返すようにしてみた。
- しかし今度は、RX割込みが掛りっぱなしになった。何故だろう。
* NULL,CR,LFを繰返している→つまりNULLコードを受信している?
- 本当は、RX割込みが終わったら、またスリープ状態に戻って欲しいんだけど。。。
- そもそも、キー入力から何故2回RX割込みが入るんだろう。
- それに、ループバックした時にRX割込みが掛りっぱなしになるのは何故か。
- 20ms弱の時間はかなり長いけど、いったい何だろう?
- 2回目の割込みは何故発生するんだろう?
- キーコードとCRLFを返すようにすると、RX割込みが掛りっぱなしになるのは何故か。
- RX自体は変化していないから、RX割込みは掛からないはず。
- NULL,CR,LFのデータ出力は、rx_handlerが実行されているためだろう。
- rx-handlerの出力したデータをターミナルソフトがループバックしてるのでは?
- しかしターミナルソフトを終了しても、割込み掛りっぱなしは終わらない。
- それにキーコードCRLFを出力しない時はRX割込みが掛りっぱなしになることは無いのは何故か
- やはりキーコードが戻っているのでは?しかしRXは全く変化していないし、
- それともLEDが変化してるからか?でもPort2_7は割込みDisableになっている
- リピートで連続してキー入力すると35ms間隔ぐらいになるが、
- その場合、2回目のLED反転の前に、次のキー入力がされる。
- そうすると、最初のキー入力の2回目のLED反転はキャンセルされるように見える。
- 最後のキー入力から、20ms弱でLEDが反転し、さらに20ms弱後にLEDが反転する。
- 反転前に、次のキー入力があった場合、最初のキー入力はキャンセルされる。
- スリープ状態から、Port1_6の立下りで割込みが掛ってシステムが起動する。
- 次にRX割込みで、rx_handlerが起動する。
- あれ、スリープの起動時間はどれくらいだろう。
- 起動してからRXはちゃんと受信出来るんだろうか?
- スリープに戻るのにまた時間が掛かるはず。
- でも、スリープ状態になってもLEDの状態は保持されるので、変化する理由にはならないな〜。
- 試しにスリープしないでやってみようか。SLEEPの代わりに100msのWaitを入れてみる。
- 結果は、あまり変わらなかった。。あれ?
- そもそも、1秒ぐらいにしてみるとどうなるのかな?それもあまり変わらない。
- 結局SLEEPはほとんど関係ないみたいだ。
- とすれば、2回目の割込みはいったい何だ??
- 2回目の割込みを掛らないようにする方法は無いかなぁ。
UART_1_IntCntl(UART_1_DISABLE_RX_INT ); // Enable RX interrupts
- で割込みを禁止して、1秒後に割込み許可してみる。→変わらないぞ???
- 割込み禁止したっきりにしても同じ。
- 結局、UARTのRX割込みということではなく、単にGPIO割込みに見える。
- しかしそれなら何故、rx_handlerが実行されるんだろう?
- うーん。これだったら、むしろGPIO割込みでRX読んだ方が良いんじゃないだろうか?
- gpio_handlerで、RX読むとするとPort1_6が0の場合は、RXとすれば出来るかなぁ。
- あっ、つまりRX割込みの前にGPIO割込みがあるのか。。
- GPIO割込みでもLEDを反転してるから、2回反転するのは当然なんだ!
- GPIO割込みでは、タクトスイッチ以外は無視しなきゃダメだな〜(考えれば当たり前なんだけど、追加したので忘れてた。。)
- 20ms掛っていたのは、GPIO割込みのチャタ処理の所為か〜。
- じゃ、キーコードNULL、CRLFが無限に出力されるのは何故だろう。
- まさか出力すると、入力が無くてもRXで割込みが掛っちゃうんだろうか?
- 仮にそうだとすると、データがNULLで出力されるのも分かる。
- ということで、以下のようにしたらとりあえず考えていたように動いた。
- gpio割込みで、RXが0ならば何もしない。
- RX割込みでRXデータがNULLの時は何もしない。
- mainでは、処理が終わったらSLEEP状態にする。
- しかし、これだとNULは受け取れないのでバイナリデータで支障がでるかな。
- それにスリープからの起動に時間が掛かるためか反応が鈍い
- 文字によって変わる。a,c,e,..が遅く、b,d,f,...が速い。
- 多分コードの問題。0x41,0x43,0x45は遅く、0x40,0x42,0x44は速い。つまり最初のビット0が0だと速い。
- スタートビット0で、SLEEPから目覚めて、最初のビット0が1だと認識出来ない?のかな??
- もしかすると、GPIO割込みでRXが1だとタクトスイッチ処理で喰われちゃうのかな?
それならばとgpio割込みで、RXが0、或はタクトスイッチが変化しなければ何もしない、としてみた。
- 文字によって反応が鈍くなることもなくなった。→やっぱりGPIO処理で喰われてたんだなぁ。
- TX出力時にRX割込みが掛っちゃうというのは、なんか勘違いしてるのかもしれないけどとりあえず良しとしよう。
- ここまで来ると、今回はバイナリデータを扱わないんだから、標準のルーチンで良さそうに思えてきた。
- 標準ルーチンは、projectmcxさんが 分析されてるようにRXCmdBufferをEnableにした時だけ実行されるようになってるらしい。
- 試しに、_rx_handlerへのljmpを、lcallに変更してみる。
- キー入力があると、そのまま暴走状態になる。
- あぁ、そうだった。Cコンパイラは割込みルーチンの最後に自動的にretiを入れちゃう3んだった。。
- そうするとC言語で書く場合、標準のルーチンに戻る方法ってないのかな?
- Insert your custom codeってあるけど、これはアセンブラで書けってことか。。
- でも何故かReturnを2回送らないとコマンドを認識しない。
- SLEEPすると何が起こるんだろう。
- スリープから目覚める(GPIO割込み)
- コマンド文字列受信→RXバッファに入れる(RX割込み)
- CmdCheck
- スリープ
- スリープから目覚める(GPIO割込み)
- CRコード受信→RXバッファに入れる(RX割込み)
- CmdCheck→コマンド処理(文字列送信、CRLF送信)
- スリープ
- これなら、問題ないはずだ。
- しかし考えてみるとSLEEPから目覚めた後、CRコード受信の後にCmdCheckが行われる保障はない。
- むしろRX受信処理の方が時間が掛かるのかな。そうだとすると、
- スリープから目覚める(GPIO割込み)
- CmdCheck
- コマンド文字列受信→RXバッファに入れる(RX割込み)
- スリープ
- スリープから目覚める(GPIO割込み)
- CmdCheck
- CRコード受信→RXバッファに入れる(RX割込み)
- スリープ
- スリープから目覚める(GPIO割込み)
- CmdCheck→コマンド処理(文字列送信、CRLF送信)
- CRコード受信→RXバッファに入れる(RX割込み)
- スリープ
- ということで、2回目のCRでコマンド処理されているのかも。
- スリープが無い時は、CmdCheckは無限ループなので、次のループで処理されることになる。
- ならば、単にCmdCheckをメインループの最後に持ってきてみる。
- とりあえず動いたが、コマンド文字列送信時に表示にへんなゴミが出る。
- コマンド文字列送信時には何も処理されないはずなので、ゴミが出るわけはないんだけど。。
- ロジアナで見てみると、CR受信時に文字列の後にCRLFが送信されずに、RXがLになったままで、
- 次のコマンド文字列受信時に、CRLFが送信されている。
- コマンド文字列送信とCRLF送信後に、スリープしてるはずなのに、CRLF送信前にスリープしている感じだ。
- つまり、 UART_CPutString とか UART_PutCRLF っていうのは、送信用バッファにデータを入れるだけで、実際の送信はすぐに行われないのかもしれない。
- そうだとすると送信完了を確認してからSLEEPすれば良いことになる。
- あれ、戻ってこない。。。うーん。
- では、TXバッファが空になるまで待つというのはどうだろう。
while (!(UART_1_bReadTxStatus()&UART_1_TX_BUFFER_EMPTY)); // wait TX Complete
うーん、文字化けの仕方は変わったけど、最初と大差ないなぁ。
- ロジアナで見てみると、CR受信時に文字列の後にCRが出力されるようになったけどLFは出力されず、RXがLのままで、
- 次のコマンド文字列受信時に、LFが出力されている。
- TXバッファが空になるのを、もう1回待ってみる。→変わらない。TXバッファのチェックってどうなってるんだ?
- スリープ前に、数10msの待ち時間を入れてみるとちゃんと動いた。→でもカッコ悪〜い。。。
- TX Bufferが空になったのを確認しても、TX Shift Registorに残ってるってことなのかな。
- UART_TX_COMPLETEが1になることは無いってことなのかな?
- DigitalCommのUARTのDataSheetのTX,Conrol/Status Registorの記述を確認する。
Tx Complete is a flag that indicates if a data byte is in the process of being transmitted.
This bit is reset when the register is read.
あぁ、読むとリセットされるんだ。じゃ、単にこれで良いのか。
if (UART_1_bReadTxStatus()&UART_1_TX_COMPLETE)
CPU_SCR0 |= CPU_SCR0_SLEEP_MASK; //sleep
- うーん。直ったかな?
- LCDを付けて気が付いたけど、これだとコマンド入力後、CR入力までスリープしてないな〜。
- だから、この場合は以下のようになる。
- スリープから目覚める(GPIO割込み)
- CmdCheck
- コマンド文字列受信→RXバッファに入れる(RX割込み)
- CRが来るまで、CmdCheckを繰返す
- CRコード受信→RXバッファに入れる(RX割込み)
- 応答を返す
- TX完了したらスリープ
- COMPLETEフラグはクリアされちゃうから、こうなるのか。
- 何かTXから出力しないとSLEEPしないことになる。。あぁ、ちょっとマズイなぁ。
- COMPLETEフラグがクリアされなければ、簡単なのになぁ。
- 問題点としては、
- データ送信後にSLEEPすると、CRLF送信前にスリープしてしまう。
- TXバッファが空になるまで待って、SLEEPするようにしてもLF送信前にスリープに入ってしまう。
- TX_COMPLETEでSLEEPすれば良いが、TX_COMPLETEはクリアされてしまうのでデータ送信時以外では不都合がある。
- ちゃんとスリープさせる方法として、以下のような3つの手段が考えられる。
- 常に数文字送信分のディレイの後にスリープする →毎回ディレイがあるので冗長
- TXバッファが空になるまで待って、1文字送信分のディレイ後にスリープ →上記と同様だが、最低限のディレイとなる
- データ送信後のみ、TX_COMPLETEを待ってSLEEP、それ以外は常にSLEEPする →送信フラグと条件分けが必要
- どれも、いまいちスッキリしない感じだけど、これぐらいしか思いつかない。。。
- タクトスイッチGPIO割込み、UART割込み、スリープを併用する場合、
- UARTは、RXCmdBufferを有効にしておく→標準ルーチンを使う時
- SLEEPさせる時は、受信割込みで起動出来ないので、GPIO割り込みを併用する。
- その場合、RXの変化によりWakeUpさせるだけが目的なので、RX変化でGPIO割込みが発生した場合は何も処理しないようにする。
- データ送信したら送信完了までSLEEPしないようにする。→送信完了の確認方法は要注意
- このプログラムではタクトスイッチをGPIO割込みで読み込んでいるけど、チャタリング処理とか考えるとタイマ割込みで読み込んだ方が良いと思う。
- でも、スリープ中はタイマが止まるので単純には行かないため、今回は諦めた。
- BUFFER_EMPTY,COMPLETEプラグについては、もうちょっとうまくやる方法があるかもしれない。
- 作成したプログラムの例(骨組みだけ)
- この例では、SLEEP前にTX_BUFFER_EMPTYをチェックして、さらにTX Shift Registorが空じゃない場合のために1文字送信分ディレイをとるようにしている。
- あと、キーDown,Upで、それぞれチャタリング処理が必要になる
#pragma interrupt_handler gpio_handler
void gpio_handler()
{
if (~PRT1DR&0x40 || !(~PRT1DR&0x03F)) return; // RX変化なら何もしない
: // bit6=RX,bit0〜5=タクトスイッチ
タクトスイッチ処理、モード変更や設定等
:
}
void main()
{
//Initialize
LCD_1_Start();
UART_1_CmdReset();
UART_1_EnableInt();
UART_1_Start(UART_1_PARITY_NONE);
LED_1_Start();
INT_MSK0 |= INT_MSK0_GPIO;
M8C_EnableGInt;
while(1) {
:
// 設定モード、キー入力等のローカル処理
:
// 以下、UARTによるPCリモート処理
if (UART_1_bCmdCheck()) { // Wait for command
if (cmd = UART_1_szGetParam()) { // get command
switch (*cmd) {
case 'R': // Read
UART_1_CPutString(">Read \r\n");
UART_1_PutCRLF();
break;
case 'W': // Write
UART_1_CPutString(">Write \r\n");
UART_1_PutCRLF();
break;
}
}
UART_1_CmdReset(); // Reset command buffer
}
while (~UART_1_bReadTxStatus()&UART_1_TX_BUFFER_EMPTY);
wait_ms(1); // about 1ms delay
CPU_SCR0 |= CPU_SCR0_SLEEP_MASK; // sleep
}
1後から考えると変なんだけど、この時は分かってなかったので。
2しかし、DigiView4のアプリは使い辛い。イライラするので3に戻した。
3これ、callで呼んで自分でretiを入れる仕様の方が自由度あるんじゃないのかなぁ。余計なお世話のような気がするけど。。
[top]
- PSoCのUARTに関して参考にさせて頂いたサイト。
- 特にバイナリデータを扱う場合は、参考になると思う。
[top]
[電子工作関連に戻る]
⇒ Disqusの広告がうるさすぎるので基本は非表示にしました