FT232HでI2C通信

# FT232HでI2C通信  

## 1.MM-FT232H

今回MM-FT232H(サンハヤト)という変換モジュールを使用しました。  
MM-FT232HはUART（RS232）とかSPIとかI2CとかJTAGとかパラレルインターフェイスとか色々な通信ができるみたいで何かと便利そうです。  
これを用いてI2C通信を行います。  

![MM-FT232H_01_L.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/f92da95f-46fb-ec90-f415-c42ef479466f.png)

今回実装するシステム概要は下図となります。  

![ブロック図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/508f56b9-a271-844c-d8ef-108302ff895d.png)

## 2.用意したもの

・MM-FT232H  
・[デバイスドライバ](https://www.ftdichip.com/Drivers/D2XX.htm)  
・[libMPSSE.zip](https://www.ftdichip.com/Support/SoftwareExamples/MPSSE/LibMPSSE-I2C.htm)←ライブラリ一式  
・PC(windows10)  
・VisualStudio2019  
・RL78G13 ←Slave用  

・[MM-FT232H 取扱説明書](https://www.sunhayato.co.jp/material2/ett09/item_759)  
・[AN_177_User_Guide_For_LibMPSSE-I2C.pdf](https://www.ftdichip.com/Support/Documents/AppNotes/AN_177_User_Guide_For_LibMPSSE-I2C.pdf)←ライブラリの詳細が記載されています。  

## 3.配線

取説に従って配線します。  
![取説.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/9f9a86d0-5626-7479-a6a6-45c8e1aafa54.png)

## 4.アプリの実装

MM-FT232HをI2C通信で使用する場合、アプリケーションの作成が必要となります。  
今回はVisualStudioでデスクトップアプリを作成します。  

VisualStudioを起動し、[新しいプロジェクト作成]からプロジェクトを作成します。  
今回はデスクトップウィザードを選択します。  
![新しいプロジェクト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/ccb501fb-76fd-ff6d-1e03-d30ba253589b.png)

アプリケーションの種類：デスクトップアプリケーション(.exe)、空のプロジェクトを選択し、プロジェクトを作成しました。  
![空のプロジェクト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/0f57d703-07b4-371c-6f36-ad6c0ac94056.png)

↓  

次にlibMPSSE.zipを展開し、プロジェクトフォルダにコピーします。  
libMPSSE__0.6\include\libMPSSE_i2c.h  
libMPSSE__0.6\include\windows\ftd2xx.h  
libMPSSE__0.6\lib\windows\visualstudio\win32  
![ライブラリ組込.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/d6df48ae-9736-2e27-7761-1a43e1728845.png)

↓  

プロジェクト＞プロパティ＞リンカー＞入力＞追加の依存ファイルからリンカーにlibMPSSE.libへのパスを追加します。
![依存ファイル追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/4a8456a7-72ac-e41d-5a34-d18372e0c3aa.png)

↓  

libMPSSE_i2c.h,ftd2xx.hをプロジェクトに追加、main.cppを作成して以下をコーディングしました。  
ライブラリの使用箇所にSTEP1~6とコメントを入れてあるのでI2Cに関してはそちらを確認して下さい。

```C
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
/* STEP1：FT232Hライブラリ関連インクルード */
#include "ftd2xx.h"         /* D2XX */
#include "libMPSSE_i2c.h"   /* libMPSSE */

/************************************************************************************
定数
************************************************************************************/
#define D_I2C_DATA_MAX      (255)

#define D_DEVICE_ADDR       (0x08)      /* RL78のSlaveアドレスを0x10で実装したが、FT232Hライブラリでは0x08となるので注意!! */

/************************************************************************************
列挙体
************************************************************************************/
/************************************************************************************
型
************************************************************************************/
typedef void (*FUNC)(void);

/************************************************************************************
構造体
************************************************************************************/
typedef struct {
    RECT rect;
    FUNC func;
    TCHAR* text;
} ST_TEXT_TBL;

/************************************************************************************
プロトタイプ
************************************************************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static void BasePaint(HWND hWnd);
static void LButtonDown(HWND hWnd, LONG  x, LONG  y);

static void LeftButtonTx(void);

static BOOL I2cPreprocess(void);
static BOOL I2c_WriteData(BYTE* data, DWORD size);
static BOOL I2c_ReadData(BYTE* data, DWORD size);
static BOOL I2cPostprocess(void);

/************************************************************************************
変数
************************************************************************************/
static TCHAR szWindowClass[] = _T("DesktopApp");
static TCHAR szTitle[] = _T("I2C demo");

HINSTANCE hInst;

static FT_HANDLE ftHandle;

static TCHAR text_tx[] = _T("□送信ボタン□");
static TCHAR text_title_tx[] = _T("送信：");
static TCHAR text_title_rx[] = _T("受信：");
static TCHAR text_title_msg[] = _T("メッセージ：");

static TCHAR text_output_tx[30] = _T("");
static TCHAR text_output_rx[30] = _T("");
static TCHAR text_output_msg[30] = _T("");

static BYTE g_i2cRxData[D_I2C_DATA_MAX];
static BYTE g_i2cTxData[D_I2C_DATA_MAX];

static ST_TEXT_TBL g_textTbl[] = {
    /*  left    top     right   bottom  func            text                */
    /* データ表示 */
    {   {0,     0,      150,    30},    LeftButtonTx,   text_tx             },
    {   {180,   30,     480,    60},    0,              text_output_tx      },
    {   {30,    30,     180,    60},    0,              text_title_tx       },
    {   {180,   60,     480,    90},    0,              text_output_rx      },
    {   {30,    60,     180,    90},    0,              text_title_rx       },
    {   {180,   90,     480,    120},   0,              text_output_msg     },
    {   {30,    90,     180,    120},   0,              text_title_msg      },
};

/************************************************************************************
Name:           WinMain
Parameters:
Returns:
Description:
************************************************************************************/
int CALLBACK WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR     lpCmdLine,
    _In_ int       nCmdShow
)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL,
            _T("Call to RegisterClassEx failed!"),
            _T("LoRa Module Controller"),
            NULL);

        return 1;
    }
    hInst = hInstance;

    /* STEP2：libMPSSEモード開始 */
    Init_libMPSSE();

    HWND hWnd = CreateWindow(
        szWindowClass,
        szTitle,
        (WS_OVERLAPPEDWINDOW ^ (WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)),
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        500,
        200,
        NULL,
        NULL,
        hInstance,
        NULL
    );

    if (!hWnd)
    {
        MessageBox(NULL,
            _T("Call to CreateWindow failed!"),
            _T("Windows Desktop Guided Tour"),
            NULL);
        return 1;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}
/************************************************************************************
Name:           WndProc
Parameters:
Returns:        LRESULT
Description:    Processes messages for the main window.
************************************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        BasePaint(hWnd);
        break;
    case WM_LBUTTONDOWN:
        LButtonDown(hWnd, LOWORD(lParam), HIWORD(lParam));
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }
    return 0;
}
/************************************************************************************
Name:           BasePaint
Parameters:     HWND hWnd
Returns:        none
Description:    Base表示
************************************************************************************/
static void BasePaint(HWND hWnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
    ST_TEXT_TBL* cur;
    ST_TEXT_TBL* end;

    hdc = BeginPaint(hWnd, &ps);
    end = g_textTbl + sizeof(g_textTbl) / sizeof(g_textTbl[0]);
    for (cur = g_textTbl; cur < end; cur++) {
        DrawText(hdc, cur->text, _tcslen(cur->text), &cur->rect, 0);
    }
    EndPaint(hWnd, &ps);
    return;
}
/************************************************************************************
Name:           LButtonDown
Parameters:     HWND hWnd
Returns:        none
Description:    左クリック時の処理
************************************************************************************/
static void LButtonDown(HWND hWnd, LONG  x, LONG  y)
{
    HDC hdc;
    ST_TEXT_TBL* cur;
    ST_TEXT_TBL* end;

    end = g_textTbl + sizeof(g_textTbl) / sizeof(g_textTbl[0]);

    for (cur = g_textTbl; cur < end; cur++) {
        if ((cur->rect.left < x) && (x < cur->rect.right)) {
            if ((cur->rect.top < y) && (y < cur->rect.bottom)) {
                if (cur->func != 0) {
                    cur->func();
                }
                break;
            }
        }
    }

    /* 表示の更新 */
    hdc = GetDC(hWnd);
    for (cur = g_textTbl; cur < end; cur++) {
        BitBlt(hdc, cur->rect.right, cur->rect.top, (cur->rect.left - cur->rect.right), (cur->rect.bottom - cur->rect.top), hdc, 0, 0, WHITENESS);
        DrawText(hdc, cur->text, _tcslen(cur->text), &cur->rect, 0);
    }
    ReleaseDC(hWnd, hdc);
    return;
}
/************************************************************************************
Name:           LeftButtonTx
Parameters:     none
Returns:        none
Description:0x12,0x34を送信して0x56,0x78を受信出来れば通信成功
************************************************************************************/
static void LeftButtonTx(void)
{
    BYTE data[] = { 0x12, 0x34 };
    DWORD size;
    BOOL ret;

    size = sizeof(data);
    memcpy(g_i2cTxData, data, size);
    wsprintf(text_output_msg, _T("通信失敗..."));
    wsprintf(text_output_tx, _T("-"));
    wsprintf(text_output_rx, _T("-"));
    ret = I2c_WriteData(g_i2cTxData, size);
    if (ret == TRUE) {
        wsprintf(text_output_tx, _T("%02x %02x"), g_i2cTxData[0], g_i2cTxData[1]);
        ret = I2c_ReadData(g_i2cRxData, size);
        if (ret == TRUE) {
            wsprintf(text_output_rx, _T("%02x %02x"), g_i2cRxData[0], g_i2cRxData[1]);
            if ((g_i2cRxData[0] == 0x56) && (g_i2cRxData[1] == 0x78)) {
                wsprintf(text_output_msg, _T("通信成功!!"));
            }
        }
    }
    return;
}
/************************************************************************************
Name:           I2cPreprocess
Parameters:
Returns:
Description:I2C前処理
************************************************************************************/
static BOOL I2cPreprocess(void)
{
    FT_STATUS status = FT_OK;
    ChannelConfig chConf;
    DWORD chNum = 0;

    /* STEP3：接続されているモジュール数を確認する */
    status = I2C_GetNumChannels(&chNum);
    if (status != FT_OK) {
        return FALSE;
    }
    if (chNum == 0) {
        return FALSE;
    }
    /* STEP4：モジュールをOPENする */
    status = I2C_OpenChannel(0, &ftHandle);
    if (status != FT_OK) {
        return FALSE;
    }
    memset(&chConf, 0x00, sizeof(chConf));
    chConf.ClockRate = I2C_CLOCK_STANDARD_MODE;
    chConf.LatencyTimer = 255;
    status = I2C_InitChannel(ftHandle, &chConf);
    if (status != FT_OK) {
        return FALSE;
    }
    return TRUE;
}
/************************************************************************************
Name:           I2c_WriteData
Parameters:
Returns:
Description:I2C書込み
************************************************************************************/
static BOOL I2c_WriteData(BYTE* data, DWORD size)
{
    FT_STATUS status = FT_OK;
    BOOL ret = TRUE;
    DWORD sizeTransfered = 0;
    DWORD options = 0;
    DWORD trials = 0;

    /* 前処理 */
    ret = I2cPreprocess();
    if (ret == FALSE) {
        return ret;
    }

    /* STEP5：書込処理 */
    options = I2C_TRANSFER_OPTIONS_START_BIT | I2C_TRANSFER_OPTIONS_STOP_BIT;
    status = I2C_DeviceWrite(ftHandle, D_DEVICE_ADDR, size, data, &sizeTransfered, options);
    trials = 0;
    while ((status != FT_OK) && (trials < 10)) {
        status = I2C_DeviceWrite(ftHandle, D_DEVICE_ADDR, size, data, &sizeTransfered, options);
        trials++;
    }
    if (status != FT_OK) {
        ret = FALSE;
    }

    /* 後処理 */
    I2cPostprocess();
    return ret;
}
/************************************************************************************
Name:           I2c_ReadData
Parameters:
Returns:
Description:I2C読込み
************************************************************************************/
static BOOL I2c_ReadData(BYTE* data, DWORD size)
{
    FT_STATUS status = FT_OK;
    BOOL ret = TRUE;
    DWORD sizeTransfered = 0;
    DWORD options = 0;
    DWORD trials = 0;

    /* 前処理 */
    ret = I2cPreprocess();
    if (ret == FALSE) {
        return ret;
    }

    /* STEP5：読込処理 */
    options = I2C_TRANSFER_OPTIONS_START_BIT | I2C_TRANSFER_OPTIONS_STOP_BIT;
    status = I2C_DeviceRead(ftHandle, D_DEVICE_ADDR, size, data, &sizeTransfered, options);
    trials = 0;
    while ((status != FT_OK) && (trials < 10)) {
        status = I2C_DeviceRead(ftHandle, D_DEVICE_ADDR, size, data, &sizeTransfered, options);
        trials++;
    }
    if (status != FT_OK) {
        ret = FALSE;
    }

    /* 後処理 */
    I2cPostprocess();
    return ret;
}
/************************************************************************************
Name:           I2cPostprocess
Parameters:
Returns:
Description:I2C後処理
************************************************************************************/
static BOOL I2cPostprocess(void)
{
    /* STEP6：モジュールをCLOSEする */
    I2C_CloseChannel(ftHandle);
    return TRUE;
}
```

## 5.RL78G13側の実装

動作確認用に以下の実装を行いました。

| スレーブアドレス | 挙動 |
|:--------------:|:----:|
| 0x10           | 0x12,0x34を受信すると、0x56,0x78を送信する。  |

コーディングの詳細は割愛。  
今後ネタが無くなったら別記事として紹介します。  

## 6.動作確認

送信ボタンをクリック  
![送信前.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/090fd856-93ea-6ce4-ede9-1284911ca931.png)

↓  

0x12,0x34を送信し、0x56,0x78を受信して通信成功!!  
![通信成功.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604076/b5069004-d85e-5f51-33a9-7f731348f55a.png)


## 7.まとめ

正直、最初アプリの作成が必要と分かったときは拒否反応が出ましたが、  
基本open⇒read/write⇒closeの順でライブラリの関数を呼ぶだけのとてもシンプルな作りだったので、使いやすかったです。  
むしろ既成のアプリを使用するより自由度が高くていいんじゃないかとも思えました。  
あとは日本語のドキュメントがあれば尚良いのになと感じました。

## 8.参照資料

サンハヤト公式HP(https://www.sunhayato.co.jp/material2/ett09/item_759)  
FTD公式HP(https://www.ftdichip.com/)