SSブログ

beagleboard を触ろう - UART 割り込みモード [組み込みソフト]


UART モジュールを割り込みモードで使ってみましょう。

UART は、データを受信した時、割り込みを上げることができます。
受信 FIFO に、あらかじめ設定した閾値以上のデータを受信すると、割り込みが上がります。

UART の受信割り込みのための設定は、閾値の値を決めることと、受信割り込みイネーブルビットを立てることの 2つだけです。

閾値の値は、SCR_REG, TLR_REG, FCR_REG によって決定します。

1. SCR_REG[7] RX_TRIG_GRANU1 ビットが 0 かつ TLR_REG[7:4] RX_FIFO_TRIG_DMA ビットフィールドが 0 の時
- FCR_REG[7:6] RX_FIFO_TRIG ビットフィールドの値で閾値が決定する
- 設定可能な値は 8, 16, 56, 60 の 4 通り

2. SCR_REG[7] RX_TRIG_GRANU1 ビットが 0 かつ TLR_REG[7:4] RX_FIFO_TRIG_DMA ビットフィールドが 0 以外の時
- TLR_REG[7:4] RX_FIFO_TRIG_DMA ビットフィールドの値で閾値が決定する
- 設定可能な値は 4, 8, 12,..., 60 の 15 通り

3. SCR_REG[7] RX_TRIG_GRANU1 ビットが 1 の時
- TLR_REG[7:4] RX_FIFO_TRIG_DMA を上位 4 ビット、FCR_REG[7:6] RX_FIFO_TRIG を下位 2 ビットとした 6 ビット値で閾値が決定する
- 設定可能な値は 1, 2, 3,..., 63 までの 63 通り


受信データが閾値バイト数以上、受信 FIFO にたまると、割り込みがアサートされます。
例えば、閾値を 1 に設定した場合、受信 FIFO に 1 バイト以上受信データが入れば、UART は割り込みをアサートします。
割り込み要因は、受信 FIFO から受信データを読み出すことによってクリアされます。

割り込み要因をクリアするには、受信 FIFO にたまっているデータが閾値未満になるまで取り出さなければなりません。
閾値を 1 に設定した場合、受信 FIFO に 2 バイトたまっていれば、1 バイト読み出しただけでは割り込み要因はクリアされず、UART は割り込みをアサートし続けます。

OMAP35x TRM に、この様子が図解されています。
赤丸で囲んだところが、受信 FIFO に閾値以上データがたまっている間です。
その間、割り込みがアサートされていることを表すのが、青線部分です。

RHR_intr.png


受信割り込みのイネーブルは、IER_REG にて行います。
IER_REG[0] RHR_IT ビットを立てると、受信割り込みがイネーブルされます。
IER_REG[0] RHR_IT ビットが落ちた状態だと、受信割り込みを上げる条件が満たされても、UART から割り込みがアサートされることはありません。

RHR_IT ビットは、受信割り込みとともにタイムアウト割り込みも有効にします。
受信 FIFO に受信データがあるのに、取り出さない場合は、タイムアウト割り込みが発生します。

タイムアウト割り込みを無効にすることは、多分できません。
また、タイムアウト値は固定値のような気がします(値不明)。
ここら辺、OMAP35x TRM には何も書かれていないと思うんですよねえ・・・。
それとも見落としているだけかな・・。。


閾値の設定、受信割り込みのイネーブルを含め、UART の初期化手順は OMAP35x TRM に載っています。
UART/IrDA/CIR 章の UART/IrDA/CIR Basic Programming Model 節です。

※実際のコード上の注意
UART のレジスタって、同じアドレスでもモードによって異なるレジスタが見えたり、また同じアドレスでも読み出しアクセスされる場合と、書き込みアクセスされる場合とで、異なるレジスタが見えたりと、ちょっと扱いづらいです。
特に、読み出しと書き込みでアクセスされるレジスタが違うというのは、厄介です。
例えば、アドレス 0x49020008 は、読み出しアクセスでは IIR レジスタが、書き込みアクセスでは FCR レジスタがアクセスされます。
FCR の RX_FIFO_TRIG ビットフィールドの値を変更しようとして、

FCR = (FCR & ~0xC0) | 0x40;

というコードを書くのはダメです。
これ、FCR = (IIR & ~0xC0) | 0x40 という意味になってしまいますから。
要注意です。


beagleboard に載っている RS-232 コネクタは、UART3 に接続されています。
UART3 に割り当てられている IRQ 番号は、74 です。
割り込みコントローラのマスクレジスタ (MIR) の IRQ_74 に対応するビットは、落としておく必要があります。
ここを落としておかないと、UART が割り込みを上げても割り込みコントローラがブロックしてしまいます。

また、CPU の割り込みマスクである CPSR の I ビットも、同じように落とす必要があります。
これも、落としておかないと、CPU でブロックされてしまいますね。

・UART の設定(閾値設定、受信割り込みイネーブル設定)
・割り込みコントローラの設定(MIR の設定)
・CPU の設定(CPSR の設定)

これらが設定できれば、受信割り込みを CPU で拾うことができます。


それではちょっとテストしてみましょう。

事前に、UART3 を割り込みモードで初期化しておきます。
受信 FIFO の閾値は 1 にして、IER_REG には RHR_BIT ビットを立てて、受信割り込みをイネーブルしておきます。
また、CPU の CPSR の I ビットも落としておきます。

初期化が終わったら、下のようなコードを動かしておきます。
これは、割り込みとは関係のない動作をします。

64: volatile char serial_ch;
65: static int uart_interrupt_test(void)
66: {
67:	int i;
68:
69:	serial_ch = 0;
70:	intc_enable_irq(UART3_IRQ);
71:
72:	while (serial_ch != 'q') {
73:		for (i = 0; i < WAIT_LOOP; i++)
74:			__asm__ volatile("nop");
75:		serial_printf("%s\n", __FUNCTION__);
76:	}
77:
78:	intc_disable_irq(UART3_IRQ);
79: }

WAIT_LOOP を適当に調節して、シリアルコンソール上に 1秒おきくらいに "uart_interrupt_test" が表示されるようにしておきます。
70 行目の intc_enable_irq() は、INTC (割り込みコントローラ) の MIR レジスタの IRQ_74 に対応するビットを落とします。
78 行目の intc_disable_irq() は、逆に対応するビットを立てます。
serial_ch は、割り込みハンドラーによって設定されます。
割り込みハンドラーが serial_ch に 'q' を設定したら、この関数はループから抜けて終了します。


割り込みハンドラーは、次のようなものを用意します。

29: extern volatile char serial_ch;
30: void uart_handler(int irq)
31: {
32:	int it_type;
33:
34:	it_type = (__raw_readb(UART3_IIR) & 0x3E) >> 1;
35:	if (it_type != 2)
36:		return;
37:
38:	uart3_int_cnt++;
39:	serial_ch = serial_getc();
40:	serial_printf("OK\n");
41: }

IIR レジスタは、[5:1] IT_TYPE ビットフィールドに、割り込みタイプを保持します。
RHR 割り込み(受信割り込み)が発生した場合は、割り込みタイプは 0x2 になります。
前述したように、RHR 割り込みをイネーブルした場合は、タイムアウト割り込みも有効になります。
タイムアウト割り込みが発生した場合は、割り込みタイプは 0x6 になります。
RHR 割り込みのみを扱うように、35 行目で判別を行っています。

39 行目での serial_getc() が、割り込み要因のクリアになります。
受信 FIFO からデータを読み出して、閾値以下にすることが割り込み要因クリアの条件ですので。
serial_getc() は、RHR レジスタから 1バイト読み出します。


この uart_handler() を、次のような register_handler() 関数を使って登録しておきます。

12: static void (*irq_handler[96])(int);
    :
33:
34: void register_handler(void (*handler)(int), int irq)
35: {
36:	irq_handler[irq] = handler;
37: }


register_handler(uart_handler, UART3_IRQ) 呼び出しによって登録します。



UART3 受信割り込みが発生すると、登録した uart_hander() は do_irq() の 26 行目で呼び出されます。

16: void do_irq(void)
17: {
18: 	int irq;
19:	void (*handler)(int);
20:
21:	irq_cnt++;
22:
23:	irq = __raw_readl(INTCPS_SIR_IRQ) & 0x7F;
24:	handler = irq_handler[irq];
25:	if (handler)
26:		handler(irq);
27:	sr32(INTCPS_CONTROL, 0, 1, 1);
28:	
29:	/* Data synchronization barrier */
30:	__asm__ volatile("mov r0, #0\n\t"
31:			 "mcr p15, 0, r0, c7, c10, 4\n");
32: }

受信 FIFO の閾値を 1 に設定しているので、受信割り込みはシリアルコンソールにキー入力が行われる度に発生します。
よって、uart_handler() は、シリアルコンソールにキー入力が行われる度に呼び出されることになり、その度に "OK" を表示します。

uart_interrupt_test() を実行させて、適当にキー入力をしてみると、こんな感じになります↓

interrupt_test.png

キー入力がある度に、"OK" が表示されています。
'q' を入力すると、uart_interrupt_test() は終了します。

・・・

今回は、受信 FIFO の閾値を 1 にしてみました。
閾値を 2 にすると、どうなるでしょうか。

閾値を 2 に設定した場合は、キー入力が 2 回行われないと、受信割り込みは上がってこないです。
その代わり、キー入力を 1回行った後、タイムアウト割り込みが上がってきます。
キー入力 1回では受信割り込みが発生せず、uart_handler() が呼び出されないので、受信 FIFO のデータが取り出されないからですね。

uart_handler() の 34-36 行目は、このタイムアウト割り込みを排除しています。

34:	it_type = (__raw_readb(UART3_IIR) & 0x3E) >> 1;
35:	if (it_type != 2)
36:		return;


タイムアウト割り込みが発生した場合は、uart_handler() で何も処理しないので、割り込み要因がクリアされません。
クリアされないので、uart_handler() が終わると、またタイムアウト割り込みが上がってしまいます。
タイムアウト割り込み発生 ⇒ ハンドラーで何もせず ⇒ タイムアウト割り込み発生・・・というループを延々繰り返すので、uart_interrupt_test() はまったく動けなくなります。

あと、閾値を 2 にした場合は、uart_handler() で serial_getc() を 2回呼び出して、受信 FIFO を空にしてあげる必要があります。
serial_getc() を 1回しか呼び出さなかったら、FIFO に 1バイト残ってしまって、またタイムアウト割り込みが発生してしまいますね。

キーを 1回入力する ⇒ uart_interrup_test() が動けなくなる ⇒ キーをもう 1回入力する ⇒ 受信割り込みが上がって割り込み要因がクリアされる ⇒ uart_interrupt_test() が動けるようになる

というような感じになります。

・・・

まあ、とりあえずこれで UART の割り込みモードが確認できました。

今まで受信割り込みについて見てきましたが、送信割り込みを発生させることも可能です。
送信割り込みの場合も、受信割り込みの場合と同様に、閾値を設定し、送信割り込みイネーブルビットを立てます。

送信割り込みが発生する条件は、送信 FIFO が閾値以下になることです。
逆に、送信 FIFO にデータが閾値より多くたまると、送信割り込みのアサートは止まります。
割り込み要因のクリア方法は、送信データを書きこむことだけです。
送信データはないけど割り込みを発生させたくない場合、割り込み要因はクリアできないので、この場合は割り込みコントローラの MIR レジスタでマスクするしかないですね。

THR_intr.png

送信割り込みの使い方は、

・普段は MIR レジスタでマスクして送信割り込みが上がらないようにしておく
・データ送信時に、送信 FIFO が一杯になって書き込めないとき、MIR のマスクを解除して送信割り込みが上がるようにする
・送信割り込みが上がってきたとき、保留していた送信データを送信 FIFO に書き込む
・保留していたデータをすべて送信したら、MIR レジスタをマスクして、元の状態に戻す

というような感じになると思います。

nice!(1)  コメント(1)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 1

コメント 1

BrandyErage

Спасибо большое автору этого проекта за труд и большой Респект.
Поделюсь опытом, который может пригодиться в любой момент.


Урок 1.Если непонимаем Django, хочется? <a href=https://www.youtube.com/watch?v=-qm9woFOfR0&feature=emb_logo>Как быть</a>? ?
by BrandyErage (2019-12-02 21:12) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。