2011年1月29日土曜日

LPCXpresso LPC1768を使ってUARTを制御してみよう!

先日に引き続きLPCXpressoを使ってCortex-M3を体験する話題です。

前回までにCMSISを使った下地を整える作業を行いました。

今回はUARTをレジスタ経由で制御してみます。
資料は前回までと同様にUM10360を使用します。

今回はUART1を制御対象に選びました。
User manualにも基本的な設定がわかりやすく示されています。

  1. 電源:PCONPレジスタ(Table 46)のPCUART1ビットをセットする。
  2. ペリフェラルクロック:PCLKSEL0レジスタ(Table 40)のPCLK_UART1を選択する。
  3. ボーレート:U1LCRレジスタ(Table 298)のDLABを1にセットする。これによりボーレート設定の為のDLLレジスタ(Table 292)とDLMレジスタ(Table 293)へのアクセスが有効になる。また、必要があれば分数分周レジスタ(Table 305)の分数ボーレートを設定する。
  4. UART FIFO:FIFOを有効にするためにU0FCR(Table 297)にあるFIFO enableビット(ビット0)を使用する。
  5. ピン:UARTピンをPINSELレジスタを使って選択し、PINMODEレジスタを使ってピンモードを設定する。
  6. 割り込み:UART割り込みを有効にするためにU1LCR(Table 298)にあるDLABレジスタを0に設定する。これによりU1IER(Table 294)へのアクセスが有効になる。割り込みはNVICが使用する然るべき割り込み設定レジスタを設定することで有効になる。
  7. DMA:UART1転送と受信関数はGPDMAコントローラによって操作することができる。
と手順に沿って説明されています。
次のページにはピンの説明とレジスタの説明が続きます。


こちらはそのレジスタの説明。
詳細は次のページから更に続きます。


もう後は手順に従ってレジスタの値を操作するだけです。
今回は割り込みを使わずに何かを送ってみるという事で作業してみましょう。


それでは先ほどのドキュメントの記述に合わせてmain関数の中に実装を加えます。

試しに「LPC_SC->」と入力してみましょう。
IDEに候補が出てきます。あぁ、なんて贅沢なんだ。
ここで言うSCはSystem Controlの略です。



まずはUART1の電源とクロック制御を有効にします。
(そうです。ペリフェラル毎に電源を落とし、クロックの供給を止めることで省電力化できるようになっています。)


このビットはリセット時に1となっているので、あえて立てる必要は無いと言えば無いのですが、操作対象ペリフェラルがどのレジスタと依存関係があるのか知るのは重要です。


これでペリフェラルの電源が入りました。

次にペリフェラルに供給するクロックを選択します。
UART1に供給されるクロックはPCLKSEL0レジスタで選択します。


このビットは初期値は00です。


この値は以下から選択することができます。


CCLKはClock generation for the LPC17xxのブロックを見ると何の事だかわかります。


ペリフェラルに供給されるクロックはCCLK/4、CCLK、CCLK/2、CCLK/8の4つの中から選択できるようになっています。(但し、CAN1、CAN2、CANはCCLK/8ではなく、CCLK/6になっています。)

これでUART1に対するクロックの供給設定も完了です。


次にUART1のLCR(Line Control Register)を設定します。
このレジスタにはワード長やストップビット、パリティなどの設定が含まれます。


今回は「8ビット長、1ストップビット、パリティなし」で設定します。
また、ボーレートレジスタにアクセスするためにDLABビットも1にします。

では、「ボーレートはどうやって設定するの?」という話になります。
最初の説明で
  • U1LCRレジスタ(Table 298)のDLABを1にセットする。
  • これによりボーレート設定の為のDLLレジスタ(Table 292)とDLMレジスタ(Table 293)へのアクセスが有効になる。
  • また、必要があれば分数分周レジスタ(Table 305)の分数ボーレートを設定する。
とありました。まさにこの作業を行うことになります。

Fractional Divider RegisterはDLMとDLLで設定しきれない値(割り切れない数値)を設定するためのものです。所望のボーレートに対して1.1%以内にするためのものです。


LPC17xxのユーザマニュアル(UM10360)にはこの値を決定するフローが記されています。

  1. PCLKと設定したいボーレートを手元に用意します。
  2. DLestを求めます。 DLest = PCLK / (16 x ボーレート)
  3. 求めたDLestが整数であれば、DIVADDVAL=0, MULVAL=1を設定して完了です。
となります。
で、求めたDLestが整数でない時は補正値を算出することになります。
  1. FRestという変数を持ち出します。最初にこの変数に1.5を代入します。
  2. DLestを求めます。DL=INT(PCLK/(16 x ボーレート x FRest))
  3. FRestを次の式で計算します。 FRest = PCLK/(16 x BR x DLest)
  4. もし1.1<FRest<1.9が成り立てば計算は終了です。(成り立たない場合はFRestの値を1.1から1.9選んで再度ステップ2に戻ります。)
  5. DIVADDVALとMULVALの値をドキュメントからピックアップします。
  6. 後はDLM=DLest[15:8], DLL=DLest[7:0]を代入して完了です。
テーブルは以下のようになっています。

後は上記に従って計算するだけです。
もちろん先ほどのフローをプログラム化しても良いでしょう。
ここでは手計算します。


まず、PCLKの元になるCCLKを調べる必要があります。
今回のプロジェクトではCMSISが使われています。
スタートアップで「CMSISを使うならSystemInit()」という実装があります。
この中でクロック分周期に対する設定が行われています。


CCLKCFGが0x00000003と設定されています。
今回はこの値をこのまま使う事にします。


CPUクロックはpllclkの4分周と設定されていることがわかります。

ここで再度確認しましょう。
CPUクロックは以下のパラメータによって決まります。

図中の右側にあるCCLKCFGは先ほど調べたパラメータです。
pllclkを知るために残りのCLKSRCSELとPLL0CFGとPLL0CONが必要になります。




ここで、PLL0の周波数を決めるパラメータを見てみます。


PLL0CFGを見るとPre-dividerとMultiplierの値がわかります。
  • N=0x0005 + 1
  • M=0x0063 + 1
ですから、Fcco = (2 x M x Fin) / N = (2 x 100 x 12M) / 6 となり、Fcco = 400,000,000[Hz]となります。

「うそ!」と思った方、慌てないでください。
これはあくまでPLLの出力です。
この出力を分周してCPUクロックを作り出します。

LPC1768の最大クロック周波数は100M[Hz]です。
分周するパラメータがCCLKSELです。


CCLKSELの設定状況より、今回は4分周が選択されていることがわかります。
CPUのクロック周波数CCLKは400,000,000 / 4 = 100,000,000[Hz]が選択されることになります。
結果的に最大クロックを設定していたのだと言う事がわかります。

ちなみに、CCLKCFGレジスタの説明の中にもある通り、PLL0を接続している場合には「分周なし」と「2分周」は最大クロック周波数を超えるので有効な選択肢ではありません。

以上でクロックの系統がわかりました。
これを図示したのが以下です。


長くなりましたが、先ほどのFig 51. Algorithm for setting UART dividersに戻りましょう。
フローに従って計算してみます。
  • PCLK = CCLK / 4 = 100 / 4 = 25M[Hz]
  • ボーレートBR = 9600を選択。
  • DLest = PCLK / (16 x BR) = 25M / (16 x 9600) = 162.760417
DLestは整数ではありませんので、補正する必要があります。
  • FRest = 1.5
  • DLest = INT(PCLK / (16 x BR x FRest)) = INT(25M / (16 x 9600 x 1.5)) = 108
  • FRest = PCLK / (16 x BR x DLest) = 25M / (16 x 9600 x 108) = 1.507041
算出したFRestは1.1<FRest<1.9を満たしていますので計算は終了です。
算出したFRestに従ってテーブルから必要な値を拾い出します。

  • DIVADDVAL = 1 (Table 306より)
  • MULVAL = 2 (Table 306より)
上記で補正値の設定は完了です。

最後にピンモードを設定します。

 

これで準備完了です。


それでは、最後にUART1のペリフェラルを設定するテストコードを示します。


DLABビットはDivisor Latch Access Bitと言ってDivisor Latchへのアクセスを行う時にのみ使用します。UART1のレジスタマップでwhen DLAB=0とかwhen DLAB=1とか書いてあるのはこのためです。

実際に実行して波形を見てみます。
永遠に0x55を送信するコードで見てみましょう。
0x55にするのは1ビット毎にトグルする波形が


オシロでUART1のTXをあたります。
今回はパリティビットがありませんので、
  • スタートビット
  • データ(8ビット)
  • ストップビット
の合計10ビットです。


1ビットが104[us]で送信されています。
1,000,000[us]=1[s]で9615.385ビット送信できることがわかります。
設定の狙いの値(9600bps)と実測データからエラーを計算すると約0.16%ですから、ほぼ狙い通りの値に設定できていることがわかります。

今回はUARTを題材にどのようにペリフェラルの設定を行っていくのかを具体的な例で示しました。
ここで挙げたコードが重要なのではなく、1つのペリフェラルを設定するのに、順々に手順をおっていく過程が重要です。

ぶ厚いドキュメントを見るとそれだけで嫌になるかもしれませんが、読むべきところは関係している数ページ分くらいです。
今回はそれを体感頂くことができればと思い、少し長くなりましたが実例を挙げてご紹介しました。

ペリフェラルが思った通りに動作しない時にはレジスタの設定順序などに依存関係がある場合もあります。そのような記述がないかどうか見てみるのも良いでしょう。
また、ドキュメントに書いてある通りに初期化をするのがコツです。
結果的に同じだからと判断して最初から最適化する事を狙うのではなく、試しにドキュメント通りに実装して見る事をお勧めします。


int main(void) {

// TODO: insert code here

// 電源:PCONPレジスタ(Table 46)のPCUART1ビットをセットする。
LPC_SC->PCONP |= (1 << 4);

// ペリフェラルクロック:PCLKSEL0レジスタ(Table 40)のPCLK_UART1を選択する。
LPC_SC->PCLKSEL0 &= ~(0x3 << 8);
LPC_SC->PCLKSEL0 |= (0x0 << 8);

// ボーレート:U1LCRレジスタ(Table 298)のDLABを1にセットする。
//         これによりボーレート設定の為のDLLレジスタ(Table 292)とDLMレジスタ(Table 293)へのアクセスが有効になる。
//         また、必要があれば分数分周レジスタ(Table 305)の分数ボーレートを設定する。
LPC_UART1->LCR = 0;
LPC_UART1->LCR = (0x1 << 7); // 1: Enable access to Divisor Latches.
const uint32_t BR = 9600;
const uint32_t DLest = 25000000 / (16 * BR * 1.5);
const uint32_t DIVADDVAL = 1;
const uint32_t MULVAL = 2;
LPC_UART1->FDR = (DIVADDVAL << 0) | (MULVAL << 4);
LPC_UART1->DLL = (DLest >> 0) & 0xFF;
LPC_UART1->DLM = (DLest >> 8) & 0xFF;
LPC_UART1->LCR =
(0x3 << 0) // 11: 8-bit character length.
| (0 << 2) // 0: 1 stop bit.
| (0 << 3) // 0: Disable parity generation and checking.
| (0x0 << 4) // 00: Odd parity. (Inactive.)
| (0x0 << 6) // 0: Disable break transmission.
| (0x0 << 7); // 0: Disable access to Divisor Latches.

// UART FIFO:FIFOを有効にするためにU0FCR(Table 297)にあるFIFO enableビット(ビット0)を使用する。
LPC_UART1->FCR |= (1 << 0);

// 割り込み:UART割り込みを有効にするためにU1LCR(Table 298)にあるDLABレジスタを0に設定する。
//        これによりU1IER(Table 294)へのアクセスが有効になる。
//        割り込みはNVICが使用する然るべき割り込み設定レジスタを設定することで有効になる。

// Note: 今回は使わない。

// DMA:UART1転送と受信関数はGPDMAコントローラによって操作することができる。

// Note: 今回は使わない。

// ピン:UARTピンをPINSELレジスタを使って選択し、PINMODEレジスタを使ってピンモードを設定する。
LPC_PINCON->PINSEL0 |= (0x1 << 30);
LPC_PINCON->PINSEL1 |= (0x1 <<  0);

// 送信有効化レジスタ
LPC_UART1->TER |= (1 << 7);

// Enter an infinite loop.
volatile int i = 0;
volatile int j;
while (1) {
while((LPC_UART1->LSR & (1 << 5)) == 0) {}
LPC_UART1->THR = '0';// + (i % 10);
i++;

for (j = 0; j < 10000; j++) {}
}
return 0;
}

2 件のコメント:

  1. 日本語の資料が少ないので、大変参考になりました

    以下のコードですが、
      LPC_UART1->DLL = (DLest >> 0) & 0x0F;
     LPC_UART1->DLM = (DLest >> 8) & 0x0F;
    DLM=DLest[15:8], DLL=DLest[7:0]ですので、
    0xFFでマスクする必要があるのではないでしょうか

    返信削除
  2. Akihiro様

    ありがとうございます。

    おっしゃる通り
    LPC_UART1->DLL = (DLest >> 0) & 0xFF;
    LPC_UART1->DLM = (DLest >> 8) & 0xFF;
    が正しいです。

    大変助かりました。

    返信削除