マイクロコントローラMAXQ610のシリアルポートの使用法

要約

マイクロコントローラMAXQ610は、2つの標準的なUSARTシリアルポートを備えています。MAXQ610のシリアルポートがサポートしているモードの1つである非同期モード1を使用して、PCのCOMポートやその他の様々な種類のレガシー機器と通信を行うことができます。このアプリケーションノートでは、シリアルポートの送受信動作用にMAXQ610が提供する割込み処理サポートによって、新しいキャラクタを受信したときまたはキャラクタの送信が完了したときにアプリケーションが迅速にシリアルポートに応答することができることを説明します。

概要

マイクロコントローラMAXQ610は、外部のデバイスやシステムとの通信を可能にするペリフェラルを内蔵しています。それらのペリフェラルには、マスター/スレーブのシリアルペリフェラルインタフェース(SPI)、赤外線(IR)通信用のキャリア生成/変調システム、および一般的にシリアルポートとして知られる2つの独立した汎用同期/非同期レシーバ/トランスミッタ(USART)が含まれます。

このアプリケーションノートでは、MAXQ610のシリアルポートを10ビット非同期モードで使用する方法を示します。このモードは、PCの標準COMポートに対する通信および/またはデバッグ出力の送信に広く使用されています。このモードでは、通信チャネル双方の実際の動作周波数に関係なく、共通のビットフォーマットとボーレートが設定されている状態で、2つのデバイスまたはシステム間で通信を行うことができます。

このアプリケーションノートのデモ用コードは、アセンブリ言語ベースのMAX-IDE環境を使用して、MAXQ610およびMAXQ610のEV (評価)キット向けに記述されています。このアプリケーションノートで取り上げるデモアプリケーションのコードとプロジェクトファイルをダウンロードすることができます。

デモ用コードを実行するためには、以下のハードウェアとソフトウェアが必要です。

  • MAXQ610のEVキットのボード
  • MAXQJTAGボード(シリアル-JTAGインタフェース)
  • JTAGプログラミングケーブル(2 × 5、メス/メス0.100インチヘッダ、10コネクタリボンケーブル)
  • DB9、ストレート、オス-メスシリアルケーブル
  • センタープラスの5VDC安定化電源
  • 空きCOM (シリアル)ポートまたはUSB-シリアル変換アダプタを備えたPC
  • MAX-IDE環境
  • ターミナルエミュレータ(ダムターミナルモードで動作するMTK、ハイパーターミナル、TeraTermなど)

MAX-IDE環境の最新のインストールパッケージとドキュメントを、無償でダウンロードすることができます。


C言語バージョンのデモ用コードを実行するためには、IAR Embedded Workbench®をインストールする必要があります。このソフトウェアの評価用コピーがMAXQ610のEVキットのCDに含まれています。

MAXQ610のEVキットのセットアップ

このアプリケーションノートで取り上げるデモ用コードは、MAXQ610のEVキット上で動作します。しかし、コードを正しく動作させ、コード中の機能を有効化するためには、EVキットのボード上のジャンパを正しく設定する必要があります。キットのボード上のジャンパ設定は、デモを実行するための両方のステップ(すなわち、コードのロードとコードの実行)で同一の設定を使用します。

MAXQ610のEVキットのジャンパの設定

  • JH1:ピン2-3間を接続してください。
  • JH2:ピン2-3間を接続してください。
  • JH3:ピン2-3間を接続してください。
  • 以下のジャンパを接続してください(ピン1-2間):JH14、JH20、JH22、JH23、JH24、JH25、およびJH26。

MAXQ610のEVキットのボードを正しく設定した後、デモ用コードのコンパイルとロードのためのハードウェアのセットアップを行います。

  • シリアル-JTAGボード上のジャンパJH1、JH2、およびJH3を接続してください。
  • 5VDC電源をシリアル-JTAGボード上のプラグJ2に接続してください。
  • DB9ケーブルでPCのCOM1ポートとシリアル-JTAGボードのJ1コネクタの間を接続してください。
  • JTAGプログラミングケーブルでシリアル-JTAGボードのP2とMAXQ610のEVキットのボードのP5の間を接続してください。JTAGケーブルの赤い線が両方のコネクタのピン1 (TCK)側に来るようにしてください。
  • 5VDC電源へ給電してください。

シリアルポートのモードと初期化

MAXQ610の2つのUSARTペリフェラルのそれぞれが、同期モードまたは非同期モードで動作することができます。同期モード(モード0)で動作する場合、すべてのトランザクションについてMAXQ610がバスマスターの役割を果たします。TXDラインを送信と受信の両方についてシフトクロックとして動作するように設定して、RXDは双方向のデータラインとして使用します。この構成では、データは1度に片方向にのみ(半二重)、そしてマスターが要求したときにのみ送信されます。

非同期動作モード(モード1、2、および3)の場合、TXDとRXDの2つの単方向データラインが存在します。TXDラインはMAXQ610のシリアルポートから外部デバイスへの非同期データを搬送し、RXDラインは外部デバイスからMAXQ610に返される非同期データを搬送します。通信を行うためには、MAXQ610と外部デバイスとが、共通のフォーマット(データビットの数、パリティビット、およびストップビット)と共通のボーレート(バス周波数)について合意する必要があります。TXDラインとRXDラインの転送は独立して行われ、相互に同期させる必要はないため、これらのモードでは全二重方式でデータを送信することができます。MAXQ610はTXDライン上の外部デバイスに対して、その外部デバイスが現在RXD上で送信を行っているかどうかに関係なく、任意の時点でデータを送信することが可能で、逆もまた同様です。

このデモでは、以下の特性を持つモード1を選択します。

  • シリアルデータの非同期送信(TXD)および受信(RXD)
  • ボークロックは専用のボークロックジェネレータによって供給(PRレジスタを使用してプログラム可能)
  • 8データビット、1スタートビット、1ストップビット
  • パリティなし

モード1でシリアルポートが使用するボーレート(1秒間に送受信されるビット数)は、(「MAXQ Family User's Guide」で解説しているように)以下の2つの式によって決まります。

ボークロック周波数(BAUD) = システムクロック周波数 × PR/217

ボーレート = BAUD × 2(SMOD × 2)/26

式1は、ボークロックジェネレータの出力を表しています。生成される出力の周波数は、16ビットのPR (位相)レジスタの内容によって制御されます。PRレジスタにロードする値が大きいほど、ボークロックの周波数が高くなります。

式2では、SMD (シリアルポートモード)レジスタ中の制御ビット(SMOD)の効果が計算に入っています。このビットに1をセットすると、最終的なボーレートが4倍に増加します。すなわち式2は、SMODの値に応じて次のいずれかの形で表すことができます。

SMOD = 0のとき、ボーレート = BAUD/64

SMOD = 1のとき、ボーレート = BAUD/16

水晶の速度とボーレートのほとんどの組み合わせについて、どちらのSMODの設定でも使用することができます。SMODの設定は、PRレジスタにロードする値に影響することに注意してください。以下のアプリケーションではSMOD = 1を選択するものとし、したがってボーレートの式は次の形に還元されます。

ボーレート = (システムクロック周波数 × PR/217)/16

すなわち、

ボーレート = (システムクロック周波数 × PR)/221

これをPRの値について解くと、我々にとって最も関心のある式が得られます。通常は、ボーレートとシステムクロック周波数は決まっているため、目的のボーレートを生成するために必要なPRレジスタの設定を求めることができれば良いわけです。したがって、

PR = ボーレート × 221/(システムクロック周波数)

たとえば、MAXQ610のEVキットに搭載されている標準的な12MHzの水晶を使用して、ボーレートを9600ボーに設定するとします。必要なPRレジスタの設定は(9600 x 221/12000000)であり、これは約1677または068Dhになります。

PRとSMODの値を計算した後は、USARTを適切なモードに設定して、PRとSMODの値を書き込むことによってシリアルポートの初期化が完了します。ここではシリアルポート0を使用するため、以下のコードでそのシリアルポートに該当するレジスタのセット(SCON0、SMD0、PR0、およびSBUF0)に対して書込みを行います。

;==========================================================================

====
;=
;=  InitSerial0
;=
;=  Set up serial port 0 to run in 10-bit asynchronous mode at
;=  9600 baud.
;=

InitSerial0:
   move    SCON0.6, #1       ; Set to mode 1 (10-bit asynchronous).
   move    SCON0.4, #1       ; Enable receiver. 
   move    SMD0.1,  #1       ; Baud rate = 1/16 of baud clock
   move    PR0, #0068Dh      ; P = 2^21 × 9600/12.000MHz
                             
   move    SCON0.0, #0       ; Clear received character flag.
   move    SCON0.1, #0       ; Clear transmit character flag.
   move    SMD0.2,  #1       ; Enable receive/transmit interrupt.
   ret

USARTペリフェラルの動作モードの詳細については、「MAXQ610 User's Guide」および「MAXQ Family User's Guide」の第10項(「Serial I/O Module」)を参照してください。

キャラクタの送信と受信

シリアルポートを適切に設定した後は、キャラクタの送信と受信を行うことができます。モード1の場合、1スタートビット、8データビット、および1ストップビットでキャラクタが構成されます。スタートビットとストップビットは同期のために使用され、USARTハードウェアによって処理されます。残りの8ビットが実際のデータを転送するため、8ビットのバイトを1度に1つシリアルポートで送信または受信することができます。

シリアルポートのキャラクタ送信は、以下の3ステップで行います。

  1. 送信するバイト値をSBUFレジスタに書き込みます。
  2. SCONレジスタのTI (送信割込み)ビットがハイになるまで(1がセットされるまで)待ちます。これは、ハードウェアがシリアルポートのキャラクタ送信を完了したことを示します。
  3. TIビットを0でクリアします。

常に1つの送信の完了を待ってから新しい送信を開始する必要があります。命令実行のサイクルタイムと比較して、シリアルポートの送信には長い時間が必要です。したがって、ステップ2でシリアルポートの送信完了を待つ間に、他の操作を実行することが可能です。たとえば、12MHzの水晶を使用して115200ボーのボーレートで送信を行う場合、送信に必要な合計時間は約10 x (1/115200)すなわち87µsになります。これと同じ時間の間に、MAXQ610は最大1041もの命令を実行することができます(87µs/(1/12MHz))。

シリアルポートによるキャラクタの受信も、同様の手順で行います。

  1. RI (受信割込み)ビットがハイになるのを待ちます。これは、シリアルポートが新しいキャラクタを受信したことを示します。
  2. SBUFレジスタから読取りを行ってデータバイトを取得します。
  3. RIビットを0でクリアします。

受信したキャラクタは1度に1つのみシリアルポートのハードウェアに格納(バッファ)されます。したがって、ハードウェアによってRIビットに1がセットされた場合は(キャラクタを受信していることを示します)、シリアルポートが次のキャラクタを受信する前に、SBUFレジスタを読み取ってキャラクタを取得して、RIビットをクリアする必要があります。シリアルポートが新しいキャラクタを受信した時点でRIビットがまだハイだった場合は、新しいキャラクタが失われることになります。

シリアルポート割込みの処理

シリアルポートを操作する簡単な方法としては、個々の送信または受信操作について、必要に応じてRIおよびTIビットの値を単にポーリング(繰り返してチェック)します。たとえば、キャラクタの書込みを行う場合、アプリケーションのコードはデータバイトをSBUFに書き込んだ後、ハードウェアによって1がセットされるまでTIビットをポーリングすることになります。送信が完了するまで、他の操作は行われません。同様に、新しいバイトを受信する場合は、アプリケーションはハードウェアによってハイにセットされるまで単にRIビットをポーリングして、受信されたバイトをSBUFの読取りによって取り出します。

RIおよびTIビットをポーリングするこの方法は、キャラクタの送受信待ちに相当の時間が費やされるため、簡単さと引き換えに性能が犠牲になります。さらに、いつ外部デバイスから新しいキャラクタが来るかをあらかじめアプリケーションが把握している必要があり、アプリケーションがキャラクタの送信と受信を同時に行うこともできません。

より柔軟な(ただし若干複雑な)方法は、TIとRIが単なるステータスビットではなく同期割込みソースにもなっているという事実を利用します。TIおよびRIビットの状態を継続的にポーリングして送信または受信動作の完了を判断する代わりに、MAXQ610ではTIまたはRIが0から1に変化したときに割込みを発生させることが可能です。この方法の場合、アプリケーションは必要なときにのみシリアルポートに応答すれば良いため、より生産的なことに時間を費やすことができます。

このプロセスの最初のステップは、IGEビット(IC.0)を1にセットすることによって、すべての割込みが許可されます。これによって、MAXQ610のすべての割込み処理を許可することが可能になります。

次に、割込み処理ルーチンを組み込む必要があります。MAXQ610は、複数の固定割込みベクタシステムを使用しており、個々の割込みソースまたは割込みソースのグループに対してそれぞれ異なる(プログラム不可)ベクタアドレスが割り当てられます。ここでは、ワードアドレス0040hに割り当てられたシリアルポート割込みベクタが対象になります。

次に示す割込みハンドラルーチンは、以下の機能を実行します。

  1. 一時的にIC.0に0をセットして、(優先順位の高い)他の割込みの発生を防ぎます。
  2. PSFおよびAccレジスタをスタックにプッシュして値を保存します。割込みはいつ発生するか分からないため、割込みハンドラでは常に他のアプリケーションコードで使用しているレジスタの保存と復元を行う必要があります。
  3. シリアルポート割込みはTI (送信)またはRI (受信)いずれかのフラグによってトリガされる可能性があるため、なぜ割込みが起動されたのかをハンドラのコードで調べる必要があります。
  4. 送信割込みが起動された場合は、TIビットをクリアして、フラグ値(A[15]に格納)をセットして送信が完了したことをアプリケーションに通知します。これは単にデモを目的としています。割込みハンドラはこの状況への応答として、SBUFに新しい値をロードして次のキャラクタを送信することもできます。
  5. 受信割込みがトリガされた場合は、RIビットをクリアして、受信したキャラクタをSBUFから読み取り、受信したキャラクタに応じて適切な動作を行います。
  6. AccおよびPSFレジスタの値をスタックからポップして復元します。
  7. IC.0に1をセットして再び割込みを許可します。
  8. RETI命令を使用して割込みハンドラを終了します。

org 0040h

serialInt:
   move    IC.0, #0          ; Block any other interrupts from triggering.
   push    PSF
   push    Acc
   move    C, SCON0.0        ; Check for receive character interrupt.
   jump    C, serialInt_Rx

serialInt_Tx:
   move    SCON0.1, #0       ; Clear transmit complete interrupt flag.
   move    A[15], #1         ; Set flag to indicate transmit complete.
serialInt_done:
   move    IC.0, #1          ; Re-enable interrupts.
   pop     Acc
   pop     PSF
   reti

serialInt_Rx:
   move    SCON0.0, #0       ; Clear receive character interrupt flag.
   move    Acc, SBUF0        ; Get character from serial port.
   cmp     #'0'
   jump    E, serialInt_Rx0
   cmp     #'1'
   jump    E, serialInt_Rx1
   cmp     #'2'
   jump    E, serialInt_Rx2
   cmp     #'3'
   jump    E, serialInt_Rx3
   cmp     #'4'
   jump    E, serialInt_Rx4
   jump    serialInt_done

serialInt_Rx0:
   move    Acc, PO3
   or      #0Fh              ; Turn all LEDs off.
   move    PO3, Acc
   jump    serialInt_done


....


serialInt_Rx3:
   move    Acc, PO3
   xor     #04h              ; Toggle P3.2 state. 
   move    PO3, Acc
   jump    serialInt_done

serialInt_Rx4:
   move    Acc, PO3
   xor     #08h              ; Toggle P3.3 state.
   move    PO3, Acc
   jump    serialInt_done

デモアプリケーションのビルド

このシリアルデモアプリケーションの全体的な枠組みは、以下の通りです。

  1. ポート端子およびシリアルポートを初期化します。
  2. 割込みを許可します。
  3. 以下のバナーテキストをシリアルポート経由で送信します。

    	MAXQ610 Serial Port Demo
    	Type characters "1"-"4" to toggle LEDs or '0' to turn all LEDs off.
    
  4. 「1」、「2」、「3」、「4」、または「0」の各キャラクタを受信した場合、それぞれに応じてLEDの状態を変更します。

上で示した割込みハンドラのコードから分かるように、受信したキャラクタへの応答として、アプリケーションはポート端子P3.0、P3.1、P3.2、およびP3.3の出力の状態を変化させます。MAXQ610のEVキットのボードでは、(ジャンパJH22、JH23、JH24、およびJH25が短絡されている場合)これらのポート端子は4つのLED、DS1、DS2、DS3、およびDS4を駆動します。ポート端子がロー状態(0)でLEDがオンになり(点灯)、ポート端子がハイ状態でLEDがオフになります。

main:
   move    WDCN,  #0 
   move    PD3.0, #1         ; Set P3.0 to output mode.
   move    PO3.0, #1         ; Drive P3.0 high (LED off).

   move    PD3.1, #1         ; Set P3.1 to output mode.
   move    PO3.1, #0         ; Drive P3.1 low (LED on).

   move    PD3.2, #1         ; Set P3.2 to output mode.
   move    PO3.2, #1         ; Drive P3.2 high (LED off).

   move    PD3.3, #1         ; Set P3.3 to output mode.
   move    PO3.3, #0         ; Drive P3.3 low (LED on).

   move    PD0.2, #1         ; Set P0.2 (TXD) to output mode.
   move    PO0.2, #1         ; Idle high when not transmitting.

   call    InitSerial0
   move    IC.0, #1          ; Enable interrupts.

   move    LC[0], #12000     ; Give transceiver time to power on.
   djnz    LC[0], $      

   ; Print string to serial port.

   move    CP, #0800h
printLoop:
   move    Acc, @CP++
   nop
   jump    Z, printLoop_done
   move    GR, Acc
   move    Acc, GRL
   call    TxChar0
   move    Acc, GRH
   call    TxChar0
   jump    printLoop
printLoop_done:
   nop

mainLoop:
   nop
   jump    mainLoop


;==========================================================================

====
;=
;=  TxChar0
;=
;=  Outputs a character to serial port 0.
;=
;=  Inputs  : Acc - Character to send.
;=

TxChar0:
   push    Acc
   move    SBUF0, Acc        ; Send character.
   move    A[15], #0         ; Clear interrupt service routine flag.
TxChar0_Loop:
   nop
   nop
   nop
   move    Acc, A[15]        ; Check flag.
   jump    Z, TxChar0_Loop
   move    SCON0.1, #0       ; Clear the transmit flag.
   pop     Acc
   ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;

org 800h

   db 0Dh, 0Ah, 0Dh, 0Ah
   db "MAXQ610 Serial Port Demo", 0Dh, 0Ah, 0Dh, 0Ah
   db "Type characters '1'-'4' to toggle LEDs or '0' to turn all LEDs off"
   db 0Dh, 0Ah, 0Dh, 0Ah
   dw 0000h

バナーテキストはDBステートメントを使用してワードアドレス0800h以降に格納されます。この定数データは、プログラムコードの他の部分とともにMAXQ610のフラッシュメモリにロードされ、上のコード(printLoopの部分)が示すように、コードポインタCPを使用して取り出すことができます。CPを使用したプログラムメモリからのフェッチでは1度に1ワードのデータが取り出されるため、各ワードの読み取り後に2つのキャラクタをシリアルポートから送信しています。

デモの実行

MAX-IDEを使用してデモ用コードをコンパイルして、MAXQ610のEVキットにロードした後は、以下のようにして実行することができます。

  1. 電源をオフにして、JTAG、電源、およびシリアルの各ケーブルを抜いてください。
  2. シリアルケーブルでPCのCOM1とMAXQ610のEVキットのボードのプラグJ1を接続してください。
  3. 電源ケーブルをMAXQ610のEVキットのボードのプラグJ3に接続してください。
  4. 電源をオンにしてください。
  5. PC上でターミナルエミュレータプログラムを起動してください。COM1上で、9600ボー、8データビット、1ストップビット、パリティなしの通信を行うように設定してください。
  6. MAXQ610のEVキットのボード上のRESET (SW1)を押下して、放してください。ターミナルエミュレータの画面にバナーテキスト(MAXQ610 Serial Port Demo...)が表示されるはずです。表示されない場合は、接続およびジャンパ設定をチェックしてください。
  7. LEDの状態をトグルさせるキャラクタを次のようにタイプしてください。「1」とタイプするとDS1がトグルします。「2」とタイプするとDS2がトグルします。「3」とタイプするとDS3がトグルします。「4」とタイプするとDS4がトグルします。「0」とタイプするとすべてのLEDがオフになります。キャラクタのエコーは行われません。

C言語によるデモ用コード

T以下のデモ用コードは、同じアプリケーションをIARのEmbedded Workbench IDE向けにC言語で実装したものを示しています。簡単にするため、割込み処理コードは除去してあります。シリアルポート0からキャラクタを出力するようにputchar関数を実装していることに注意してください。これによって、puts()printf()などの標準入出力ライブラリの関数を使用することが可能にな ります。

int putchar(int c)
{
   SBUF0 = c;
   while ((SCON0 & 0x02) == 0);
   SCON0 = (SCON0 ^ 0x02);
   return c;
}

void initUSART0(void)
{
   int i2;
   PD0_bit.bit2 = 1;   // Hold Tx0 line High.
   PO0_bit.bit2 = 1;   // Hold Tx0 line High.
   SMD0 = 2;           // Set baud rate select bit.
   SCON0 = 0x50;       // Set mode 1 and receive enable for UART 0.
   PR0 = 0x068D;       // 9600 baud: PR0 = 2^21 * 9600 / 12.000MHz
   for (i2 = 1; i2 < 10000; i2++);   // Give transceiver time to power 

on.
   SCON0 = 0x50;       
}

void main( void )
{
   int c;
   IC_bit.IGE = 0;
   WDCN       = 0;    
   PD3 = 0x0F;
   PO3 = 0x05;    // Default - DS2 and DS4 on, DS1 and DS3 off

   initUSART0();
   puts("MAXQ610 Serial Port Demo");
   puts("Type characters "1"-"4" to toggle LEDs or "0" to turn all LEDs 

off\n");

   while (1) {
      while ((SCON0 & 0x01) == 0);        // Wait for RI flag to go high.
      c = SBUF0;                          // Receive character.
      SCON0 = (SCON0 ^ 0x01);             // Clear RI flag.
            
      switch (c) {
         case '0' : PO3 = 0x0F;
                    break;
         case '1' : PO3 = (PO3 ^ 0x01);   // Toggle P3.0
                    break;
         case '2' : PO3 = (PO3 ^ 0x02);   // Toggle P3.1
                    break;
         case '3' : PO3 = (PO3 ^ 0x04);   // Toggle P3.2
                    break;
         case '4' : PO3 = (PO3 ^ 0x08);   // Toggle P3.3
                    break;
         default  : break;
      }
   }
}

結論

MAXQ610は多くのMAXQマイクロコントローラが備えている標準的なUSARTシリアルポートのインスタンスを2つ提供します。MAXQ610のシリアルポートがサポートしているモードの1つである非同期モード1を使用して、PCのCOMポートやその他の様々な種類のレガシー機器と通信を行うことができます。シリアルポートの送受信動作用にMAXQ610が提供する割込み処理サポートによって、新しいキャラクタを受信したときまたはキャラクタの送信が完了したときにアプリケーションが迅速にシリアルポートに応答することができます。

MAXQ610のUSARTシリアルポートは、このアプリケーションノートの対象範囲を超える幅広い設定オプションと通信モードをサポートしています。詳細については、前述の「MAXQ610 User's Guide」および「MAXQ Family User's Guide」の第10項を参照してください。