UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

このページは、「 MPLAB Harmony v2 動作確認」で作成した「test_ume」プロジェクトの 』の続きです。
ここでは、[UMEHOSHI ITA]に実装したUSB CDC(Universal Serial Bus Communications Device Class)を使ってシリアル通信(エコー動作) する動作確認まで紹介しています。

[UMEHOSHI ITA]で[[Harmony Integrated Software Framework v2.06]を使い、USB CDCのプログラミングする

前回の作業で作成した「test_ume」のプロジェクトを開きます。

app.cとapp.hへの追加コード


上記で見えるapp.cのAPP_Tasks()関数が、main.cのループから呼び出されるメインの処理です。
appData.stateの状態遷移でアプリの処理が進行する動作です。
appData.stateは、app.hの中で定義されるenumで列挙されるAPP_STATES型です。
また、appDataは APP_DATAの名前の構造体で、app.hで定義されます。
Harmonyのプログラミングでは、アプリに合わせて状態名を、るAPP_STATES型に追加し、 APP_DATAの構造体に必要な情報を記憶するメンバを追加することから始めます。
それに従い、app.hへの追加を行います。

app.h ファイルへのコード追加 その1

typedef enum { と } APP_STATES; の中への追加コード(app.cのAPP_Tasks関数内のswitch分岐用)を示します。
APP_STATESをワードで探して、次の列挙を追加します。
    ////////////////////////////////////////////////////////////////////////////
 
    /* Application waits for device configuration*/
    APP_STATE_WAIT_FOR_CONFIGURATION,

    /* The application checks if a switch was pressed */
    APP_STATE_CHECK_SWITCH_PRESSED,

    /* Wait for a character receive */
    APP_STATE_SCHEDULE_READ,

    /* A character is received from host */
    APP_STATE_WAIT_FOR_READ_COMPLETE,

    /* Wait for the TX to get completed */
    APP_STATE_SCHEDULE_WRITE,

    /* Wait for the write to complete */
    APP_STATE_WAIT_FOR_WRITE_COMPLETE,

    /* Application Error state*/
    APP_STATE_ERROR
            
    ////////////////////////////////////////////////////////////////////////////
ここでビルド( [Production]の[Build Main Project]を選んで)で、エラーがないことを確認します。


app.h ファイルへのコード追加 その2

typedef struct { と } APP_DATA; の中への追加コードを示します。(Harmonyアプリの状態管理変数用)
APP_DATAをワードで探して、次の構造体メンバを追加します。
    ////////////////////////////////////////////////////////////////////////////
     /* Device layer handle returned by device layer open function */
    USB_DEVICE_HANDLE deviceHandle;

    /* Set Line Coding Data */
    USB_CDC_LINE_CODING setLineCodingData;

    /* Device configured state */
    bool isConfigured;

    /* Get Line Coding Data */
    USB_CDC_LINE_CODING getLineCodingData;//USB CDC ?

    /* Control Line State */
    USB_CDC_CONTROL_LINE_STATE controlLineStateData;

    /* Read transfer handle */
    USB_DEVICE_CDC_TRANSFER_HANDLE readTransferHandle;

    /* Write transfer handle */
    USB_DEVICE_CDC_TRANSFER_HANDLE writeTransferHandle;

    /* True if a character was read */
    bool isReadComplete;

    /* True if a character was written*/
    bool isWriteComplete;

    /* True is switch was pressed */
    bool isSwitchPressed;

    /* True if the switch press needs to be ignored*/
    bool ignoreSwitchPress;

    /* Flag determines SOF event occurrence */
    bool sofEventHasOccurred;

    /* Break data */
    uint16_t breakData;

    /* Switch debounce timer */
    unsigned int switchDebounceTimer;

    unsigned int debounceCount;

    /* Application CDC read buffer */
    uint8_t * readBuffer;

    /* Number of bytes read from Host */ 
    uint32_t numBytesRead; 
    
    ////////////////////////////////////////////////////////////////////////////
ここでビルド( [Production]の[Build Main Project]を選んで)で、エラーがないことを確認します。

app.c ファイルへのコード追加 その1

void APP_Tasks ( void )関数内の「switch ( appData.state )」で、次のコード部分があります。
これは、このアプリの初期化の後に呼び出されるコード部分です。
	case APP_STATE_SERVICE_TASKS:
	{
break; }
この白の空欄に、次のUSBに関するオープンとイベントハンドラの登録処理です。
            ////////////////////////////////////////////////////////////////////
            appData.deviceHandle = USB_DEVICE_Open( USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE );

            if(appData.deviceHandle != USB_DEVICE_HANDLE_INVALID)
            {
                /* Register a callback with device layer to get event notification (for end point 0) */
                USB_DEVICE_EventHandlerSet(appData.deviceHandle, APP_USBDeviceEventHandler, 0);

                appData.state = APP_STATE_WAIT_FOR_CONFIGURATION;
	      _RB15 = 1; // 確認のために LEDを点灯 (確認後にコメント化)
            }                      
            ////////////////////////////////////////////////////////////////////
ここでビルド( [Production]の[Build Main Project]を選んで)すると、次のエラーが生じるでしょう。
../src/app.c:???:??: error: 'APP_USBDeviceEventHandler' undeclared (first use in this function)
USB_DEVICE_EventHandlerSet(appData.deviceHandle, APP_USBDeviceEventHandler, 0);
これは、上記で追加したイベントハンドラを用意していないためです。
これを定義して、他の必要な処理用に、my_app.cと、my_app.hを次で作ります。

my_app.cと、my_app.h ファイルの追加

追加するファイルは、Harmonyの自動生成ファイルの変更を少なくする目的で別ファイルにしました。
この考えは本来のHarmonyの思想から外れるかもしれませんが、個人的なファイル管理の観点でそうしています。
前述で示した、USB_DEVICE_EventHandlerSetのイベントハンドラ定義を、ここで行います。

my_app.h ファイルの追加

まず次の操作で、my_app.hを作ります。

「my_app」の名前を付けます。

このmy_app.hのコードは次のようになります。

#ifndef _MY_APP_H    /* Guard against multiple inclusion */
#define _MY_APP_H

#include "app.h"

#define APP_READ_BUFFER_SIZE 64

/* Macro defines USB internal DMA Buffer criteria */
#define APP_MAKE_BUFFER_DMA_READY 

/* USB read buffer (USBの受信バッファです。)*/
extern uint8_t APP_MAKE_BUFFER_DMA_READY readBuffer[APP_READ_BUFFER_SIZE]
        __attribute__((coherent, aligned(16)));

extern APP_DATA appData; // Reference appData in app.c
//(appDatasはHarmonyのアプリ用データの核となる利用者が変更できる構造体)

// USBのイベントハンドラ
void APP_USBDeviceEventHandler 
        ( USB_DEVICE_EVENT event, void * eventData, uintptr_t context );

bool APP_State_Reset(void); // My_APP_Tasks内のリセット時処理

void My_APP_Tasks ( void ); // USBの状態遷移に対する分岐処理関数

void Init_Usb( void ); // USBの初期設定

#endif /* _MY_APP_H */

app.cを開いて、「#include "app.h"」と使っていいる箇所を、代わりに
#include "my_app.h"」へ変更します。

この作品では、今後のソース(.c)で「#include "my_app.h"」を使います。




my_app.c ファイルの追加

次の操作で、my_app.cを作ります。

「my_app」の名前を付けます。

このmy_app.cのコードは次のようになります。

#include "my_app.h"


/*******************************************************
 * USB CDC Device Events - Application Event Handler
 * ( callback functions)
 *******************************************************/

USB_DEVICE_CDC_EVENT_RESPONSE APP_USBDeviceCDCEventHandler
(
    USB_DEVICE_CDC_INDEX index ,
    USB_DEVICE_CDC_EVENT event ,
    void * pData,
    uintptr_t userData
)
{
    APP_DATA * appDataObject;
    appDataObject = (APP_DATA *)userData;
    USB_CDC_CONTROL_LINE_STATE * controlLineStateData;
    USB_DEVICE_CDC_EVENT_DATA_READ_COMPLETE * eventDataRead; 

    switch ( event )
    {
        case USB_DEVICE_CDC_EVENT_GET_LINE_CODING:

            /* This means the host wants to know the current line
             * coding. This is a control transfer request. Use the
             * USB_DEVICE_ControlSend() function to send the data to
             * host.  */

            USB_DEVICE_ControlSend(appDataObject->deviceHandle,
                    &appDataObject->getLineCodingData, sizeof(USB_CDC_LINE_CODING));

            break;

        case USB_DEVICE_CDC_EVENT_SET_LINE_CODING:

            /* This means the host wants to set the line coding.
             * This is a control transfer request. Use the
             * USB_DEVICE_ControlReceive() function to receive the
             * data from the host */

            USB_DEVICE_ControlReceive(appDataObject->deviceHandle,
                    &appDataObject->setLineCodingData, sizeof(USB_CDC_LINE_CODING));

            break;

        case USB_DEVICE_CDC_EVENT_SET_CONTROL_LINE_STATE:

            /* This means the host is setting the control line state.
             * Read the control line state. We will accept this request
             * for now. */

            controlLineStateData = (USB_CDC_CONTROL_LINE_STATE *)pData;
            appDataObject->controlLineStateData.dtr = controlLineStateData->dtr;
            appDataObject->controlLineStateData.carrier = controlLineStateData->carrier;

            USB_DEVICE_ControlStatus(appDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);

            break;

        case USB_DEVICE_CDC_EVENT_SEND_BREAK:

            /* This means that the host is requesting that a break of the
             * specified duration be sent. Read the break duration */

            appDataObject->breakData = ((USB_DEVICE_CDC_EVENT_DATA_SEND_BREAK *)pData)->breakDuration;
            
            /* Complete the control transfer by sending a ZLP  */
            USB_DEVICE_ControlStatus(appDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
            
            break;

        case USB_DEVICE_CDC_EVENT_READ_COMPLETE:

            /* This means that the host has sent some data ★ */
            eventDataRead = (USB_DEVICE_CDC_EVENT_DATA_READ_COMPLETE *)pData;
            appDataObject->isReadComplete = true;
            appDataObject->numBytesRead = eventDataRead->length; 
            break;

        case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_RECEIVED:

            /* The data stage of the last control transfer is
             * complete. For now we accept all the data */

            USB_DEVICE_ControlStatus(appDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
            break;

        case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_SENT:

            /* This means the GET LINE CODING function data is valid. We dont
             * do much with this data in this demo. */
            break;

        case USB_DEVICE_CDC_EVENT_WRITE_COMPLETE:

            /* This means that the data write got completed. We can schedule
             * the next read. */

            appDataObject->isWriteComplete = true;
            break;

        default:
            break;
    }

    return USB_DEVICE_CDC_EVENT_RESPONSE_NONE;
}

/***********************************************
 * Application USB Device Layer Event Handler.
 * ( callback functions)
 ***********************************************/
void APP_USBDeviceEventHandler ( USB_DEVICE_EVENT event, void * eventData, uintptr_t context )
{
    USB_DEVICE_EVENT_DATA_CONFIGURED *configuredEventData;
    switch ( event )
    {
        case USB_DEVICE_EVENT_SOF:
            /* This event is used for switch debounce. This flag is reset
             * by the switch process routine. */
            appData.sofEventHasOccurred = true;
            break;

        case USB_DEVICE_EVENT_RESET:
            appData.isConfigured = false;
            break;

        case USB_DEVICE_EVENT_CONFIGURED:            
            /* Check the configuratio. We only support configuration 1 */
            configuredEventData = (USB_DEVICE_EVENT_DATA_CONFIGURED*)eventData;
            if ( configuredEventData->configurationValue == 1)
            {
                /* Register the CDC Device application event handler here.
                 * Note how the appData object pointer is passed as the
                 * user data */
                USB_DEVICE_CDC_EventHandlerSet(USB_DEVICE_CDC_INDEX_0, APP_USBDeviceCDCEventHandler, (uintptr_t)&appData);
                /* Mark that the device is now configured */
                appData.isConfigured = true;
            }
            break;

        case USB_DEVICE_EVENT_POWER_DETECTED:
            /* VBUS was detected. We can attach the device */
            USB_DEVICE_Attach(appData.deviceHandle);
            break;

        case USB_DEVICE_EVENT_POWER_REMOVED:
            /* VBUS is not available any more. Detach the device. */
            USB_DEVICE_Detach(appData.deviceHandle);
            break;

        case USB_DEVICE_EVENT_SUSPENDED:
            break;

        case USB_DEVICE_EVENT_RESUMED:
        case USB_DEVICE_EVENT_ERROR:
        default:
            break;
    }
}

上記では、APP_USBDeviceEventHandler定義が目的でしたが、この定義内でAPP_USBDeviceCDCEventHandlerを利用するため 2つ関数を定義しています。
ここでビルド( [Production]の[Build Main Project]を選んで)で、エラーがないことを確認します。
また、PICKit3(などの書き込みツール)とPCを接続し、PICKit3と「Umehoshi ITA」を繋げ、プログラムを書き込みして動作を 確認しましょう。
書き込んだ後、「Umehoshi ITA」基板とPICKit3の接続を外します。

そして、WindowsPCと「Umehoshi ITA」基板を接続します。
ここで、「デバイス マネージャー」表示内の「ポート(COMとLPT)」に新たな「通信ポート(COMn)」が出現すればOKです。


ビックリマークが付いていますが、認識はできるようになりました。
次で、実際にCDCとしてのシリアル通信を可能とするコードの追加を行います。





my_app.c ファイルへのコード追加

USBの受信データを記憶するバッファreadBufferの配列定義を関数定義より前の位置に追加します。
それは、次の内容です。

/* USB read buffer */
uint8_t APP_MAKE_BUFFER_DMA_READY readBuffer[APP_READ_BUFFER_SIZE]
        __attribute__((coherent, aligned(16)));

アプリ状態を列挙したAPP_STATES型の分岐処理として My_APP_Tasks ( void )の定義を追加します。
また、リセット時の処理(APP_State_Reset)、USBの初期化処理(Init_Usb)も定義する。位置は、前回作成時の最後に追加すればよいでしょう。
なお、各関数の宣言は、上記でmy_app.hに宣言済みです。


/******************************************************************************
 * This function returns true if the device was reset
 * This function is called in every step of the application state machine.
 * called by My_APP_Tasks (Local Functions)  
 *****************************************************************************/
bool APP_State_Reset(void)
{
    bool retVal;
    if(appData.isConfigured == false) {
        appData.state = APP_STATE_WAIT_FOR_CONFIGURATION;
        appData.readTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
        appData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
        appData.isReadComplete = true;
        appData.isWriteComplete = true;
        retVal = true;
    } else  {
        retVal = false;
    }
    return(retVal);
}

/******************************************************************************
 * Event branch processing
 * called by APP_Tasks ( app.c )  
 *****************************************************************************/
void My_APP_Tasks ( void ){
    /* Check the application's current state. */
    switch ( appData.state )
    {
        case APP_STATE_WAIT_FOR_CONFIGURATION:
            /* Check if the device was configured */
            if(appData.isConfigured) {// デバイス設定か?
		/* If the device is configured then lets start reading */
                	appData.state = APP_STATE_SCHEDULE_READ;
                    _RB15 = 0; //  確認のために LEDを消灯 (確認後にコメント化)
            }
            break;
 
        case APP_STATE_SCHEDULE_READ:
            if(APP_State_Reset()) {
                break;
            }
            
            /* If a read is complete, then schedule a read
             * else wait for the current read to complete */
            if(appData.isReadComplete == true)  {      
                appData.isReadComplete = false;
                appData.readTransferHandle =  USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
                appData.state = APP_STATE_WAIT_FOR_READ_COMPLETE;

                USB_DEVICE_CDC_Read (USB_DEVICE_CDC_INDEX_0,
                        &appData.readTransferHandle, appData.readBuffer,
                        APP_READ_BUFFER_SIZE);// USB のシリアル受信
                
                if(appData.readTransferHandle == USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID)
                {
                    appData.state = APP_STATE_ERROR;
                    break;
                }
            }
            break;

        case APP_STATE_WAIT_FOR_READ_COMPLETE:
            if(APP_State_Reset()) {
                break;
            }
            /* Check if a character was received or a switch was pressed.
             * The isReadComplete flag gets updated in the CDC event handler. */
            if(appData.isReadComplete ){
                if(appData.numBytesRead > 0) {// appData.readBufferに受信データがある。
                    appData.state = APP_STATE_SCHEDULE_WRITE;// 受信したデータを送信するため、出力状態にする
                    _RB15 = 1; //  確認のために LEDを点灯 (確認後にコメント化)
                }
            }
            break;

        case APP_STATE_SCHEDULE_WRITE:
            if(APP_State_Reset()){
                break;
            }
            /* Setup the write */
            appData.writeTransferHandle= USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
            appData.isWriteComplete = false;
            appData.state = APP_STATE_WAIT_FOR_WRITE_COMPLETE;
            USB_DEVICE_CDC_Write(USB_DEVICE_CDC_INDEX_0,
                    &appData.writeTransferHandle,
                    appData.readBuffer, appData.numBytesRead,
                    USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE);// 受信情報を出力する。
            break;

        case APP_STATE_WAIT_FOR_WRITE_COMPLETE:
            if(APP_State_Reset())  {
                break;
            }
            appData.state = APP_STATE_SCHEDULE_READ;
            break;
    }
}

void Init_Usb( void )
{
    /* Device Layer Handle  */
    appData.deviceHandle = USB_DEVICE_HANDLE_INVALID ;

    /* Device configured status */
    appData.isConfigured = false;

    /* Initial get line coding state */
    //appData.getLineCodingData.dwDTERate = 9600;
    appData.getLineCodingData.dwDTERate = 115200;
    appData.getLineCodingData.bParityType =  0;
    appData.getLineCodingData.bParityType = 0;
    appData.getLineCodingData.bDataBits = 8;
  
    /* Read Transfer Handle */
    appData.readTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;

    /* Write Transfer Handle */
    appData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;

    /* Intialize the read complete flag */
    appData.isReadComplete = true;

    /*Initialize the write complete flag*/
    appData.isWriteComplete = true;

    /* Initialize Ignore switch flag */
    appData.ignoreSwitchPress = false;

    /* Reset the switch debounce counter */
    appData.switchDebounceTimer = 0;

    /* Reset other flags */
    appData.sofEventHasOccurred = false;
    appData.isSwitchPressed = false;

    /* Set up the read buffer */
    appData.readBuffer = &readBuffer[0];
}

上記の黄色の部分が注目箇所で、これでエコー動作を実現させています。

app.c ファイルでのコード追加

前述でmy_app.cに追加した関数を、app.cで呼び出す処理です。
	Init_Usb();

上記コードは、app.cの中の APP_Initialize()の関数定義内の最後に追加します。

また、My_APP_Task関数は、app.cの中の APP_Task関数定義の中に追加します。
次の白の部分が追加コードです。switchのdefalut部分です。

	default:
	{
           	/* TODO: Handle error in application's state machine. */
My_APP_Tasks ( );
break; }

動作確認

USBのCDC(Communication Device Class)プロトコルは、シリアル通信に使われる規格です。
以下では、PC側で「Tera Term」というターミナルエミュレータを別途にインストールしてエコー実験を行っています。
(「Tera Term」の入手は使い方の情報はネットなどから探して得てください。)
以下は、上記プログラムを書き込んだ基板[UMEHOSHI ITA]とPCを接続して、PCのデバイスマネージャで接続ポートを確認し、 そのCOMポートに「Tera Term」で接続しようとしているPCの操作画面です。 ([File]メニューから[New Connection...]を選択した画面)

上記は、接続時のデバイスマネージャに現れた [USB Serial Port(COM3)」に接続する場合の例です。赤マルのCOM番号を合わせる必要があります。
なお「Tera Term」では、この接続前で、[Setup]メニューの[Serial Port ..]の設定で、次のように設定する必要があります。

以下は、接続した後の「Tera Term」で、『hello』+Enterのキー入力をした画面です。


なお、「UMEHOSHI ITA」のテスト用プログラムは、この[USB CDC]を利用しています。