ホームに戻る
 PIC18F2455 でUSB機器を作る

0、はじめに

「PICで楽しむUSB機器 自作のすすめ」著:後閉哲也
という書籍を参考にLEDを点滅させるデバイスを作ってみました。
以下はその覚え書きです。

書籍内ではCOMポートのエミュレータを使う方法も紹介されていますが、
ここでは汎用USBクラスを用います。

1、特に用意するもの

PIC はUSB対応のものを用意する必要があります。
USB対応の PIC(PIC18F2455、PIC18F2550、PIC18F4455、PIC18F4550 のいずれか)。
また、MPLAB IDE、MPLAB C18コンパイラが必要になります。
あとは MicroChip のページからUSBフレームワークをダウンロードします。
注意点としては MPLAB C18コンパイラ に試用期間があり、
60日以降は実行バイナリ容量の最適化が行われなくなります。
USBフレームワークは C18コンパイラで書かれているため、
アセンブラや他のCコンパイラでは通らないようです。
その他、PCやUSBケーブル、電子部品はもちろん必要です。
注意として、クロック振動子は 20MHz など4の倍数でないといけないようです。

2、プログラミングの手順

MPLAB IDE、MPLAB C18コンパイラをインストール。
IDE のプロジェクトウィザードで該当の PIC と C18コンパイラを選択。
プロジェクトのオプションで各種ディレクトリの設定をします。
リンカースクリプトに各 PIC の *.lkr を登録します。
USBフレームワークを展開。

C:\MCHPFSUSB\fw\Demo

これ以降のファイルが必要になるので、
とりあえずすべてのCファイルとHファイルをプロジェクトに登録します。

usbcgf.h の以下2行をコメントアウトします。

//#define USE_SELF_POWER_SENSE_IO
//#define USE_USB_BUS_SENSE_IO

また、usbdsc.c の Vendor ID と Product ID を設定します。

これにPIC側のプログラムを書いてビルドが通ればOKです。

PC側のプログラムを作るにはDLLが必要になります。
必要なDLL(mpusbapi.dll、mpusbapi.h)はUSBフレームワークにあります。

C:\MCHPFSUSB\Pc\Mpusbapi\Dll

DLL とヘッダを使ってプログラムを作成します。
PCのプログラム内でもIDを記述します。
VC++でやりましたがビルドは通っています。

PIC のプログラムはライターで PIC へ転送し、
USB機器に取り付けてケーブルをPCにさすと、
ドライバーを要求されるのでドライバーの位置を指示します。

ドライバ(mchpusb.inf、mchpusb.sys)もUSBフレームワークに含まれます。

C:\MCHPFSUSB\Pc\MCHPUSB Driver\Release

ただし、IDが一致しないと認識しないため、
mchpusb.inf のIDに PIC のIDを追加してください。

, USB\VID_09B9&PID_0048

以上を追加。

3、回路の作り方

USBケーブルは端子の先を手前にして上から接続の金属が見える状態で、
左からGND、D+、D−、Vbusの順である。
Vbusから電源をとる方法をバスパワーを使うといい、
USB機器に独自の電源を用意する場合をセルフパワーといいます。

電源の供給方法は3通りあります。
バスパワーのみは供給できる最大電流に制限があります。
セルフパワーのみでは接続の認識をする場合はI/Oピンで認識する仕組みが要ります。
両対応ではセルフパワーが無ければバスパワーに自動で切り替える仕組みが必要です。

パスコンは電源側に 47μF の電解コンデンサ、
PIC 電源側と USB 電源レギュレータに 0.1μF のセラミックコンデンサを用います。
電源とGNDは電位差の乱れが無いようできるだけ1点分岐にします。
D+とD−はそのまま対応で PIC に繋ぎます。

クロック振動子は外部の 20MHz など4の倍数の振動数のものを使います。
その他の回路は作りたいものに応じて PIC の仕様に基づいて準備します。
LEDを点灯させるならLEDと 500Ω ほどの抵抗があればいいと思います。

4、プログラムの説明

PIC側は電源が入ると PORTC0 がオンになる。
メッセージ 0 で接続応答、1 で PORTC1 が反転し返答、2 で PORTC2 が反転し返答。
PORTC0-2 にはLEDが接続されており状態が確認できる。

PCは3つのボタンと文字表示用のエディットを持つ。
ボタンは「接続」と「LED 1」、「LED 2」の3つで、
それぞれ送信後すぐに受信し、PIC 側の返答をエディットに表示する。

5、PIC 側のプログラム

/*
*   PIC 18F2455 用 USB接続テスト
*    クロック振動子 20MHz
*    バスパワーで駆動
*    PORTC 0-2 で LED 0-2 に出力
*/

#include <p18cxxx.h>
#include "system\typedefs.h" 
#include "system\usb\usb.h"
#include "io_cfg.h"

// コンフィグ
#pragma config PLLDIV = 5         // 20MHz / 5 = 4MHz * 24 = 96MHz
#pragma config CPUDIV = OSC1_PLL2 // 内部クロック 96MHz / 2 = 48MHz
#pragma config FOSC = HSPLL_HS    // 内部クロック = 48MHz
#pragma config WDT = OFF          // ウオッチドッグタイマーをオフ
#pragma config USBDIV = 2         // Full Speed 
#pragma config PWRT = ON          // パワーアップタイマーをオン
#pragma config BOR = ON           // ブラウンアウトリセットをオン
#pragma config LVP = OFF          // 低電圧プログラミングをオフ
#pragma config VREGEN = ON        // 3.3 V レギュレータをオン
#pragma config MCLRE = ON         // MCLR をオン
#pragma config PBADEN = OFF       // PORT B をデジタル入出力に

// 定数
#pragma udata
char input_buffer[64];  // 入力バッファ
char output_buffer[64]; // 出力バッファ
unsigned int RcvSize;   // 受信データサイズ

char MsgOK[] ="OK";     // 接続時の返答メッセージ
char MsgState[] = " ";  // LED

// main
#pragma code
void main(void)
{
  TRISC = 0x00;

  /// USB初期化
  mInitializeUSBDriver();

  // LED 0 を点灯
  LATCbits.LATC0 = 1;

  /// メインループ
  while(1){
    // B接続チェック
    USBCheckBusStatus();
                    
    // アイパターンモードオフか  
    if(UCFGbits.UTEYE != 1){                  
      USBDriverService();
    }
    // データ受信
    if((usb_device_state >= CONFIGURED_STATE) && (UCONbits.SUSPND==0)){
      // 受信チェック
      RcvSize = USBGenRead((byte*)&input_buffer, sizeof(input_buffer));
      if(RcvSize){
        switch(input_buffer[0]){
          case '0':
            // 接続
            if(!mUSBGenTxIsBusy()){
              USBGenWrite((byte*)MsgOK, 3);
            }
            break;                
          case '1':
            // LED 1 の点灯を反転
            LATCbits.LATC1 = !LATCbits.LATC1;
            if(LATCbits.LATC1 == 1){
              MsgState[0] = '1';
            }
            else{
              MsgState[0] = '0';
            }
            if(!mUSBGenTxIsBusy()){
              USBGenWrite((byte*)MsgState, 1);
            }
            break;
          case '2':
            // LED 2 の点灯を反転
            LATCbits.LATC2 = !LATCbits.LATC2;
            if(LATCbits.LATC2 == 1){
              MsgState[0] = '1';
            }
            else{
              MsgState[0] = '0';
            }
            if(!mUSBGenTxIsBusy()){
              USBGenWrite((byte*)MsgState, 1);
            }
            break;
          default:
            break;
        }
      }
    }
  }
}

6、PC側のプログラム

/*
*   USB機器コントロール用
*     ID: 09b9 0048
*/

#include <windows.h>
#include "mpusbapi.h"

#define ID_B1 1000
#define ID_B2 1010
#define ID_B3 1020
#define ID_EDIT1 2000

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// ボタンとエディットの定義
HWND hButton1, hButton2, hButton3;
HWND hEdit1;

//文字列の保管用
static char txt0[57] = "接続成功";
static char txt1[57] = "LED 1 が点灯";
static char txt2[57] = "LED 1 が消灯";
static char txt3[57] = "LED 2 が点灯";
static char txt4[57] = "LED 2 が消灯";

// USB通信用変数
char vid_pid[] = "vid_09b9&pid_0048"; // 注意:IDを変更!
char out_pipe[] = "\\MCHP_EP1";
char in_pipe[] = "\\MCHP_EP1";

BYTE send_buf[64];    // 送信バッファ
BYTE receive_buf[64]; // 受信バッファ
DWORD RecvLength = 1; // 受信データ数
DWORD temp;
DWORD DevID = 0;      // 選択デバイス番号

HINSTANCE libHandle;  // 実行中のハンドル番号
HANDLE myOutPipe;     // 使用中のOUTパイプのハンドル
HANDLE myInPipe;      // 使用中のINパイプハンドル

int OpenPipe(void){
  DWORD selection;
  DWORD state = 0;

  selection = DevID;
  // パイプのオープン
  myOutPipe = MPUSBOpen(selection, vid_pid, out_pipe, MP_WRITE, 0);
  myInPipe = MPUSBOpen(selection, vid_pid, out_pipe, MP_READ, 0);
  if(myOutPipe == INVALID_HANDLE_VALUE || myInPipe == INVALID_HANDLE_VALUE){
    return 0;
  }

  return 1;
}

void ClosePipe(void){
  // パイプのクローズ
  MPUSBClose(myOutPipe);
  MPUSBClose(myInPipe);
  myOutPipe = INVALID_HANDLE_VALUE;
  myInPipe = INVALID_HANDLE_VALUE;
}

void CheckInvalidHandle(void){
  if(GetLastError() == ERROR_INVALID_HANDLE){
    MPUSBClose(myOutPipe);
    MPUSBClose(myInPipe);
    myOutPipe = INVALID_HANDLE_VALUE;
    myInPipe = INVALID_HANDLE_VALUE;
  }
}

DWORD SendReceivePacket(BYTE *SendData, DWORD SendLength, BYTE *ReceiveData, DWORD *ReceiveLength, UINT SendDelay, UINT ReceiveDelay){
  DWORD SentDataLength;
  DWORD ExpectedReceiveLength = *ReceiveLength;

  if(myOutPipe != INVALID_HANDLE_VALUE && myInPipe != INVALID_HANDLE_VALUE){
    if(MPUSBWrite(myOutPipe, SendData, SendLength, &SentDataLength, SendDelay)){
      // 送信したらすぐに受信
      if(MPUSBRead(myInPipe, ReceiveData, ExpectedReceiveLength, ReceiveLength,ReceiveDelay)){
        // 受信データ確認
        if(*ReceiveLength == ExpectedReceiveLength){
          // 受信成功
          return 1;
        }
        else if(*ReceiveLength < ExpectedReceiveLength){
          // 受信データサイズが合わない
          return 0;
        }
      }
      else{
        CheckInvalidHandle();
      }
    }
    else{
      CheckInvalidHandle();
    }
  }

  return 0;
}

int initUSB(){
  libHandle = NULL;
  libHandle = LoadLibrary("mpusbapi");

  if(libHandle == NULL){
    return 0;
  }
  else{
    // DLL から関数の呼び出し
    MPUSBGetDLLVersion = (DWORD(*)(void))GetProcAddress(libHandle, "_MPUSBGetDLLVersion");
    MPUSBGetDeviceCount = (DWORD(*)(PCHAR))GetProcAddress(libHandle, "_MPUSBGetDeviceCount");
    MPUSBOpen = (HANDLE(*)(DWORD,PCHAR,PCHAR,DWORD,DWORD))GetProcAddress(libHandle, "_MPUSBOpen");
    MPUSBWrite = (DWORD(*)(HANDLE,PVOID,DWORD,PDWORD,DWORD))GetProcAddress(libHandle, "_MPUSBWrite");
    MPUSBRead = (DWORD(*)(HANDLE,PVOID,DWORD,PDWORD,DWORD))GetProcAddress(libHandle, "_MPUSBRead");
    MPUSBReadInt = (DWORD(*)(HANDLE,PVOID,DWORD,PDWORD,DWORD))GetProcAddress(libHandle, "_MPUSBReadInt");
    MPUSBClose = (BOOL(*)(HANDLE))GetProcAddress(libHandle, "_MPUSBClose");

    if((MPUSBGetDeviceCount == NULL) || (MPUSBOpen == NULL) ||
        (MPUSBWrite == NULL) || (MPUSBRead == NULL) ||
        (MPUSBClose == NULL) || (MPUSBGetDLLVersion == NULL) ||
        (MPUSBReadInt == NULL)){
      return 0;
    }   
  }

  return 1;
}

int connectUSB(){
  DWORD max_count;

  max_count = MPUSBGetDeviceCount(vid_pid);

  for(int DevID = 0; DevID < max_count; DevID++){
    if(OpenPipe()){
      send_buf[0] = 0x30;
      RecvLength = 3;
      if(SendReceivePacket(send_buf, 1, receive_buf, &RecvLength, 1000, 1000) == 1){
        if((receive_buf[0] == 'O') && (receive_buf[1] == 'K')){
          ClosePipe();
          return 1;
        }
      }
    }
  }

  return 0;
}

void turn_on_led1(){
  if(OpenPipe()){
    send_buf[0] = 0x31;
    RecvLength = 1;
    if(SendReceivePacket(send_buf, 1, receive_buf, &RecvLength, 1000, 1000) == 1){
      if(receive_buf[0] == '0'){
        SetWindowText(hEdit1, (LPTSTR)txt2);
      }
      else{
        SetWindowText(hEdit1, (LPTSTR)txt1);
      }
    }
  }

  ClosePipe();
}

void turn_on_led2(){
  if(OpenPipe()){
    send_buf[0] = 0x32;
    RecvLength = 1;
    if(SendReceivePacket(send_buf, 1, receive_buf, &RecvLength, 1000, 1000) == 1){
      if(receive_buf[0] == '0'){
        SetWindowText(hEdit1, (LPTSTR)txt4);
      }
      else{
        SetWindowText(hEdit1, (LPTSTR)txt3);
      }
    }
  }

  ClosePipe();
}

//メイン関数
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR cmdLine, int cmdShow){
  WNDCLASS wd;

  char *wdName = "window00";

  if(!hPreInstance) {
    wd.style = CS_HREDRAW | CS_VREDRAW;
    wd.lpfnWndProc = (WNDPROC)WndProc;
    wd.cbClsExtra = 0;
    wd.cbWndExtra = 0;
    wd.hInstance = hInstance;
    wd.hIcon = NULL;
    wd.hCursor = LoadCursor(NULL, IDC_ARROW);
    wd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wd.lpszMenuName = NULL;
    wd.lpszClassName = wdName;

    if (!RegisterClass(&wd))
      return FALSE;
  }

  HWND form00 = CreateWindowEx(
    NULL, wdName, "usb_test",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 100,
    NULL, NULL, hInstance, NULL);

  // USB 通信のための初期設定
  initUSB();

  //ウィンドウの表示
  ShowWindow(form00, cmdShow);

  //ウィンドウの更新
  UpdateWindow(form00);

  //イベントのループ
  MSG msg;
  while (GetMessage(&msg, NULL, NULL, NULL)){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
  HINSTANCE hInst;
  hInst = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE);

  switch (message) {
    case WM_CREATE:
      hButton1 = CreateWindow(
        "BUTTON", "接続",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        10, 10, 50, 20,
        hwnd, (HMENU)ID_B1, hInst ,NULL);
      hButton2 = CreateWindow(
        "BUTTON", "LED 1",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        70, 10, 50, 20,
        hwnd, (HMENU)ID_B2, hInst, NULL);
      hButton3 = CreateWindow(
        "BUTTON", "LED 2",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        130, 10, 50, 20,
        hwnd, (HMENU)ID_B3, hInst, NULL);
      hEdit1 = CreateWindow(
        "EDIT", "",
        WS_CHILD | WS_VISIBLE,
        10, 40, 170, 20,
        hwnd, (HMENU)ID_EDIT1, hInst, NULL);
      break;
    case WM_COMMAND:
      switch(LOWORD(wParam)){
        case ID_B1:
          // 接続
          if(connectUSB()){
            SetWindowText(hEdit1, (LPTSTR)txt0);
          }
          break;
        case ID_B2:
          // LED 1 点灯切り替え
          turn_on_led1();
          break;
        case ID_B3:
          // LED 2 点灯切り替え
          turn_on_led2();
          break;
        default:
          return(DefWindowProc(hwnd, message, wParam, lParam));
      }
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return DefWindowProc(hwnd, message, wParam, lParam);
  }
  return NULL;
}

inserted by FC2 system