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 に閾値以上データがたまっている間です。
その間、割り込みがアサートされていることを表すのが、青線部分です。
受信割り込みのイネーブルは、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() を実行させて、適当にキー入力をしてみると、こんな感じになります↓
キー入力がある度に、"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 レジスタでマスクするしかないですね。
送信割り込みの使い方は、
・普段は MIR レジスタでマスクして送信割り込みが上がらないようにしておく
・データ送信時に、送信 FIFO が一杯になって書き込めないとき、MIR のマスクを解除して送信割り込みが上がるようにする
・送信割り込みが上がってきたとき、保留していた送信データを送信 FIFO に書き込む
・保留していたデータをすべて送信したら、MIR レジスタをマスクして、元の状態に戻す
というような感じになると思います。
Спасибо большое автору этого проекта за труд и большой Респект.
Поделюсь опытом, который может пригодиться в любой момент.
Урок 1.Если непонимаем Django, хочется? <a href=https://www.youtube.com/watch?v=-qm9woFOfR0&feature=emb_logo>Как быть</a>? ?
by BrandyErage (2019-12-02 21:12)