YS電子工作ラボ

<V.3版>

PIC - PC(VC#)間
    UART 文字列送受信

(Harmony コールバック関数編)

(→プロジェクトファイル(Harmony Ver.3版 MHC v3.6.0) ダウンロード
(→プロジェクトファイル(Harmony Ver.3版 MHC v3.8.0) ダウンロード


 PICとPC(VC#)間の UART文字列送受信の例を紹介します。 ここではHarmonyのコールバック関数を使用しています。



<仕様>
 ・PCとPIC32MZ間でUART接続による文字列の送受信をおこなう。
 ・PC側から文字列に対して PIC側からそれぞれに対応した文字列を返信すること
  送受信の文字列は 以下とする

      PCからの送信文字列 PICからの返信文字列
Japan\r Tokyo 
America\r Washington
U.K.\r  London
France\r  Paris
How are you?\ Pardon?

 ・PICのキャラくタ液晶はI2Cインターフェースのキャラクタ液晶とし、以下を表示のこと。
   1行目 …… PCからの受信文字列
   2行目 …… PCへの送信文字列

 ・PICは起動後すみやかに PC側に 下記文字列を送信のこと
    Hellow World !!\r\n"

 ・ Harmonyのコールバック関数を使用のこと。 

 ・PC側のアプリケーションは マイクロソフトのVC# で作成のこと。  実行ファイルを以下からダウンロードできます。

 ・PC側のアプリケーションは以下のようなダイアログウィンドウとする。(PC側のアプリケーションについは( →URL ) 参照願います。
  すなわち、
   1. コンボボックスから送信文字列を選択して 送信ボタンをクリックするとPIC側に送信をおこなう。
   2. 受信文字列は リッチテキストボックスに順次追加表示する。


<回路図> (→PDFファイル
        (参考)mikroBUS 評価ボード DB004(→購入方法のU4TX,U4RXを使用した場合 → URL


<外観> PIC32MZ評価ボード(→購入方法)を使った実験品の外観です。  





<動作結果> (→ 動画: 1080pのHD動画を見ることができます。)

 









<解説> 記載してある内容は要点だけです。 詳細はプロジェクトファイルを精読願います。

  ステップ1:   MHC作成

項目 1. [Clock Diagram]タブで、クロックの構成を外付け24MHz水晶発振器用、システムクロック200MHzに設定します。
 POSCMOD: → EC
 FPLLICLK: → POSC
 FPLLIDIV: → DIV_3
2. [Pin Settings]タブでの設定
■ピン7のRC2ポート
 Function: → U6RX
■ピン16のRG9ポート
 Function: → U6TX
3. [Prpject Graph]タブでの設定
■追加コンポーネント
 CORE, UART6, UART
■ コンポーネント間の接続
 UART6 -  UART
■コンポーネント設定
 UART6: 9600bps
MHC



 ■ app.cで、追加、修正により青字部分のように変更します。

    (1) 液晶のライブラリ関数などをインクルードします。
  #include "definitions.h"
#include "1lcd_lib_XC32.h"
   
   
   (2) 所要の変数を定義、宣言します。
  APP_DATA appData;

char Buf[16];
char Buf1[32];
char buf[16];

int ix = 0;
char* str;
char data;

char Japan[] = "Japan";
char America[] = "America";
char UK[] = "U.K.";
char France[] = "France";
char Tokyo[] = "Tokyo";
char Washinton[] = "Washinton";
char London[] = "London";
char Paris[] = "Paris";
char Pardon[] = "Pardon ?";

static const char Hellow_World[] = "Hellow World !!\r\n";

int delay_Clock = 200000000; //システムクロック:200MHz
   
   
   (3) 遅延関数delay_us()、delay_ms()を定義します。
 
void delay_us(volatile unsigned int usec)        //1μsec遅延
{
        volatile  int count;

        count = (int)(delay_Clock/20000000)*usec;


        do      //実測 at 200MH (Clock=200000000)
        {       //delay_us(1000):1000.4μsec  delay_us(100):100.6μsec  delay_us(10):10.5μsec  delay_us(1):1.5μsec
                asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");asm("NOP");
                asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");

                count--;
        }while(count != 0);
}



void delay_ms(volatile unsigned int msec)        //1msec遅延
{
        volatile unsigned int i;         //実測:at200MH (Clock=200000000)//delay_ms(1): 1.0006msec   delay_ms(100):100.04msec

        for(i=0; i<msec; i++)
        delay_us(1000);
}
      
   
   
   (4) 液晶の初期化などの関数を定義します。
 
void my_Initialize ( void )
{     
    lcd_init();				// LCD初期化
    lcd_cmd(0b00001100);    // カーソル:OFF  ブリンク:OFF    
        
    lcd_clear(); 
    
    lcd_cmd(0x80);          //1目の先頭へ
    sprintf(Buf1,"UART Harmony        ");//
    lcd_str(Buf1);                   //液晶表示
    

    lcd_cmd(0xC0);          //2行目の先頭へ
    sprintf(Buf1," Harmony V3                        ");	//
    lcd_str(Buf1);           // 開始メッセージ1行目表示
    
    delay_ms(2000);
    
}   
   
   
   (5) 送受信バッファのデータ処理に係るコールバック関数を定義します。
  このコールバック関数は処理が完了した場合とエラーが発生した場合に呼ばれます。
  処理完了に係る呼び出しには以下の2つがあります。
   1. 送信バッファにあるデータがすべて排出(送信)されて送信バッファが空になった場合
   2. 受信バッファが受信を完了した場合
     これらを告知すべく appData.transferStatus を trueにセットします。
 
static void APP_USARTBufferEventHandler(    //送受信バッファーのデータ処理完了に係るコールバック関数
    DRV_USART_BUFFER_EVENT bufferEvent,         
    DRV_USART_BUFFER_HANDLE bufferHandle,
    uintptr_t context
)
{
    switch(bufferEvent)
    {
        case DRV_USART_BUFFER_EVENT_COMPLETE: //処理が完了すると呼ばれる(イベントが発生する)。 以下の2つの場合がある            
                                              //1. 送信バッファにあるデータがすべて排出され送信バッファが空になった場合
                                              //2. 受信バッファが受信を完了した場合
            appData.transferStatus = true; 
            break;

        case DRV_USART_BUFFER_EVENT_ERROR:
            appData.state = APP_STATE_ERROR;
            break;

        default:
            break;
    }
}

   
   
   (6) APP_Initialize( )で パラメータを初期化しておきます。
 
void APP_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    appData.state           = APP_STATE_INIT;
    appData.transferStatus  = false;
    appData.usartHandle     = DRV_HANDLE_INVALID;
    appData.bufferHandle    = DRV_USART_BUFFER_HANDLE_INVALID;
 
}

   
   
   (7) APP_Tasks ( )の APP_STATE_INITステートで 初期化をおこないます。
  1. my_IniTialize( )関数で キャラクタ液晶の初期化 および起動直後の表示をおこないます。
  2. DRV_USART_Open( )関数で UARTのドライバインスタンスを定義してドライバハンドルを生成します。
    ハンドルが2重定義などの問題がなければハンドルが正常に生成されるので
  3. DRV_USART_BufferEventHandlerSet( )でコールバック関数を設定します。
     コールバック関数が正常に生成されたら次のAPP_STATE_TRANSMIT_MESSAGEステートに移動します。
 
void APP_Tasks ( void )
{
    
    /* Check the application's current state. */
    switch ( appData.state )
    { 
        /* Application's initial state. */
        case APP_STATE_INIT:    //初期化ステート
            my_Initialize();    //

            appData.usartHandle = DRV_USART_Open(DRV_USART_INDEX_0, DRV_IO_INTENT_READWRITE);   //
                                    //UARTのドライバーインスタンスを定義し、ドライバーハンドルを生成
            if (appData.usartHandle != DRV_HANDLE_INVALID)      //ハンドルが正常に生成された場合
            {
                DRV_USART_BufferEventHandlerSet(appData.usartHandle, APP_USARTBufferEventHandler, 0);
                                                                //コールバック関数APP_USARTBufferEventHandlerを設定
                appData.state = APP_STATE_TRANSMIT_MESSAGE;
            }
            else    //ハンドルが正常に生成されなかった場合
            {
                appData.state = APP_STATE_ERROR;
            }
            break;


   
   
   (8) APP_STATE_TRANSMIT_MESSAGEステートでは、PIC起動時の文字列送信を行います。
    DRV_USART_WriteBufferAdd( )関数で 文字列のポインタ、文字列長さなどを指定して文字列送信を行います。 ハンドルの
    競合などがなくハンドルが生成できたなら APP_STATE_WAIT_MESSAGE_TRANSFER_COMPLETEステートに移動します。
 
        case APP_STATE_TRANSMIT_MESSAGE:    //PIC起動時に文字列送信
    
            DRV_USART_WriteBufferAdd(appData.usartHandle, (void*)Hellow_World, sizeof(Hellow_World), &appData.bufferHandle);    //文字列送信
            if (appData.bufferHandle != DRV_USART_BUFFER_HANDLE_INVALID)    //ハンドルの競合などがなくハンドル生成成功の場合
            {
                appData.state = APP_STATE_WAIT_MESSAGE_TRANSFER_COMPLETE;
            }
            else
            {
                appData.state = APP_STATE_ERROR;
            }
            break;

   
   
   (9) APP_STATE_WAIT_MESSAGE_TRANSFER_COMPLETEステートでは、送信バッファが空になった時にでるイベントを待ちます。
   このイベントが発出されると appData.transferStatus が trueになります。 イベントを検出したら
   次のイベントを検出できるように appData.transferStatus = false;  で フラグをfalseにセットします。 そして
   次のステート APP_STATE_RECEIVE_DATAステートに移動します。
 
        case APP_STATE_WAIT_MESSAGE_TRANSFER_COMPLETE:
            if(appData.transferStatus == true)  //送信完了(送信バッファが空になるの)を待つ  
            {
                appData.transferStatus = false; //フラグをfalseにセット
                appData.state = APP_STATE_RECEIVE_DATA;
            }
            break;

   
   
   (10) APP_STATE_RECEIVE_DATAステートでは、受信準備をセットします。
   受信の準備ができたら 次のAPP_STATE_WAIT_RECEIVE_COMPLETEステートに移動します。
   受信が完了するまで待つのは次のステートです。
 
        case APP_STATE_RECEIVE_DATA:    //受信
        
            DRV_USART_ReadBufferAdd(appData.usartHandle, &data, sizeof(data), &appData.bufferHandle); //受信開始
                                                                                    //受信モードへ、受信バッファ受信待ち状態へ
           
            if (appData.bufferHandle != DRV_USART_BUFFER_HANDLE_INVALID)    //ハンドルの競合などがなくハンドル生成成功の場合
            {
                appData.state = APP_STATE_WAIT_RECEIVE_COMPLETE;  
            }
            else
            {
                appData.state = APP_STATE_ERROR;
            }
            break;

 
   
    (11) このAPP_STATE_WAIT_RECEIVE_COMPLETEステートで 受信バッファーへのデータ受信が完了するのを待ちます。
    すなわち appData.transferStatus が trueになるのを待ちます。
    1. このステートを \r を受信するまで繰り返し実行して PC側から送られてくる文字列を全部配列として受信しします。
    2. 終端が\rの受信データ配列を受信したら、終端\rを\0に置き換えて文字列にします。
    3. 受信文字列を判定して 返信文字列を指定します。
    4. あらかじめ想定している文字列以外の文字列の場合は Pardon ? を返信文字列に指定します。
    5. PC側のリッチテキストボックスに返信データがデータ毎に改行されるように改行コードを返信文字列に追加します。
    6. キャラクタ液晶上段に受信文字列を、下段に返信文字列を表示します。

    (備考)この部分を別関数にすると 処理に時間がかかり誤送信が発生することがあります。  原因はコンパイラの最適化
        (Compiler optimization)の問題と思われるので 有料のX32コンパイラを使用すると発生しなくなるかもしれません。
    
 
        case APP_STATE_WAIT_RECEIVE_COMPLETE:   //受信完了待ちステート

            if(appData.transferStatus == true)  //受信バッファへのデータ受信が完了するのを待つ
            {
                appData.transferStatus = false; //フラグをfalseにセット
   
                   
                buf[ix] = data;
    

                if(buf[ix] == '\r')         // Enter キーが押された場合
                {
                    buf[ix] = '\0';

                    if(strcmp(buf,Japan) == 0)str = Tokyo;
                    else if(strcmp(buf,America) == 0) str = Washinton;
                    else if(strcmp(buf,UK) == 0) str = London;
                    else if(strcmp(buf,France) == 0) str = Paris;
                    else str = Pardon;
        
                    ix = 0;
        
                    sprintf(Buf,"%s\r\n",str);  //改行追加
                    lcd_cmd(0x80);          //1目の先頭へ
                    sprintf(Buf1,"%s               ",buf);
                    lcd_str(Buf1);                   //液晶表示

                    lcd_cmd(0xC0);          //2行目の先頭へ
                    sprintf(Buf1,"%s               ",str);
                    lcd_str(Buf1);           // 開始メッセージ1行目表示
                    appData.state = APP_STATE_TRANSMIT_DATA; 
                }
                else            //Enter キー以外が押された場合
                {
                    ix++;
                    appData.state = APP_STATE_RECEIVE_DATA;    //受信
                }
                
            }
            break;

   
   
   (12) このAPP_STATE_TRANSMIT_DATAステートでは、文字列が送信されます。 DRV_USART_WriteBufferAdd( )関数に文字列のポインタ、サイズなどを
   引数にセットすると送信が実行されます。 送信ハンドルが2重になったりしていなければハンドルは正常に生成されるので
   次のAPP_STATE_WAIT_TRANSMIT_COMPLETEに移動します。
 
       case APP_STATE_TRANSMIT_DATA:   //送信データ作成、データ送信ステート
            DRV_USART_WriteBufferAdd(appData.usartHandle, Buf, strlen(Buf), &appData.bufferHandle); //送信バッファーにデータに書き込み  
        //  DRV_USART_WriteBufferAdd(appData.usartHandle, Buf, sizeof(Buf), &appData.bufferHandle); // ←NG sizeof()だと時々 文字列の長さを誤認する
            if (appData.bufferHandle != DRV_USART_BUFFER_HANDLE_INVALID) //ハンドル生成成功の場合   
            {
                appData.state = APP_STATE_WAIT_TRANSMIT_COMPLETE;   
            }
            else
            {
                appData.state = APP_STATE_ERROR;
            }
            break; 

   
   
  (13)  このAPP_STATE_WAIT_TRANSMIT_COMPLETE ステートで 送信バッファが空になったイベントを待ちます。
    すなわち appData.transferStatus が trueになるのを待ちます。 イベントを受信したら
    次のイベントを受信できるように appData.transferStatusを falseにセットしてから 次のデータを受信すべく
    APP_STATE_RECEIVE_DATAステートに移動します。
 
       case APP_STATE_WAIT_TRANSMIT_COMPLETE:  // 送信完了待ち

            if(appData.transferStatus == true)  //送信完了(送信バッファが空になるの)を待つ
            {
                appData.transferStatus = false; //フラグをfalseにセット
                appData.state = APP_STATE_RECEIVE_DATA; 
            }
            break;

   
   
  (14) APP_STATE_ERROR、APP_STATE_IDLE、default のステートは 正常に動作している場合は使用されません。
 
    case APP_STATE_ERROR:   //正常動作の場合このステートは使用されない
          
            appData.state = APP_STATE_IDLE;
            break;

        case APP_STATE_IDLE:    //正常動作の場合このステートは使用されない
            break;
            
        default:                //正常動作の場合このステートは使用されない
            break


   
   
   
   
  以下、app.c 全文