目次

 

はじめに

 
USBコントローラのないAVRマイコンとV-USBファームウェアを使うだけでLow-SpeedのUSBデバイスを作成することができます。
 
ここでは、AVRマイコンにV-USBファームウェアを組み込んで仮想USBデバイスを作成する方法を書いていきたいと思います。
サンプルとしてホストPCのコンソールアプリからの命令でLEDを点灯・消灯させるUSBデバイスを製作します。
 
「ハードウェアの製作」ではAVRマイコン(ATTiny2313)を使ったUSBデバイス作成方法を説明します。
 
「ソフトウェアの製作」ではAVRマイコン組込みソフトウェア開発環境作成から、USBデバイスを制御するPCアプリケーションの作成方法を説明します。
 
一通り読んでいただければ、USBデバイス(Low-Speedのコントロール転送)を作成できるように記述したいと思います。
 

応用例


 

ハードウェアの製作

USBデバイスの回路図

 
以下に回路図を示します。V-USB本家のホームページに回路図が掲載されていますが、回路図エディタで回路図をおこしました。
ISPのために6pinヘッダを取り付けています。リセットボタンも付いています。
作図ではKiCadを使用しました。
 
 
本家が示している回路図はこちらにあります。信号電圧を3.6V付近にするために、3端子レギュレータやダイオードを使用します。
 

回路の実装

 
ユニバーサル基板に上記の回路を実装しました。 
 
 
ユニバーサル基板に回路を実装するより、ブレッドボードに実装するほうが簡単でしょう。必要な部品は回路図から読み取ってください。
 

注意事項

 
V-USBファームウェアのヘッダファイルを書き換えることでD+とD-に接続するポートは任意のポートに変更できます。
ここで注意したいのですが、D+に接続するポートには必ずINT0ポートを接続する必要があります。
#私はこの説明を読み飛ばしたことではまりました。
 
以下がその記述です。

usbconfig.h

/* ---------------------------- Hardware Config ---------------------------- */
#define USB_CFG_IOPORTNAME      D
/* This is the port where the USB bus is connected. When you configure it to
 * "B", the registers PORTB, PINB and DDRB will be used.
 */
#define USB_CFG_DMINUS_BIT      4
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
 * This may be any bit in the port.
 */
#define USB_CFG_DPLUS_BIT       2
/* This is the bit number in USB_CFG_IOPORT where the USB D+ line is connected.
 * This may be any bit in the port. Please note that D+ must also be connected
 * to interrupt pin INT0! [You can also use other interrupts, see section
 * "Optional MCU Description" below, or you can connect D- to the interrupt, as
 * it is required if you use the USB_COUNT_SOF feature. If you use D- for the
 * interrupt, the USB interrupt will also be triggered at Start-Of-Frame
 * markers every millisecond.]
 */

 

ソフトウェアの製作

「ソフトウェアの製作」でlibusb-win32ドライバのAPIを使用するために必要なWindowsアプリケーションを作成する方法を示します。
 

開発環境のダウンロード

 

AVRマイコン開発環境

WindowsでV-USBのクロスコンパイルを行い、ATTiny2313へバイナリを書き込みます。
ライターは純正のものから自作のものまでいろいろあります。入手しておいてください。
 
C/C++コンパイラはWinAVRを使用します。
AVRマイコンの開発元であるAtmelのサイトで開発環境(AVR Studio 4)がダウンロードできます。
WinAVRからダウンロードしてインストールしてください。
その後にAVR Studio 4をインストールして、AVR Studio 4のプロジェクトオプション設定でWinAVRをデフォルトのコンパイラに設定します。
 

#External Toolsグループボックスの中でWinAVRへのパスを通しておく
 

Visual Studio 2010 Express Edition

 
ホストPCで動作するコンソールアプリケーションをVisual Studio 2010のVisual C++で作成します。ダウンロードしてインストールしてください。
 

開発環境の構築

 

Win32 コンソールアプリケーション

 
まず、Windowsアプリケーションの開発環境を構築していきます。
Visual Studio 2010 Express EditionのVisual C++でWin32 コンソール アプリケーションのプロジェクトを作成します。
 
 
プロジェクトを作成すると以下のようなフォルダとファイルが生成されます。 
 

上記のフォルダにfirmwareフォルダを作成します。そのフォルダにV-USBファームウェアをコピーします。 
 
 

各種ファイルの配置

 
V-USBファームウェアを格納している書庫ファイルを解凍すると、サンプルプログラムを格納したフォルダ(example)があります。今回製作するUSBデバイスはカスタムクラスデバイスです。コマンドラインアプリケーションで参考にするファイルは以下のフォルダにあります。

(root)\example\custom-class\commandline
 
上記のフォルダ内にある、opendevice.cとopendevice.hを開発環境のled_ctrlフォルダ直下へコピーしてください。
 
led_ctrl.cppにexample\custom-class\commandlineフォルダにあるset-led.cのコード全体をコピーして若干の修正を加えます。led_ctrl.cppをエディタで開いて#include <usb.h>を#include "usb.h"に変更してください。opendevice.hについても同様に変更してください。その他、コンパイルが通るように修正を行ってください。細かい修正は割愛します。
 
V-USBファームウェアで使用するファイルは以下のフォルダにあります。
 
(root)\example\custom-class\firamware
 
上記のフォルダ内にある、main.c, requests.h, usbconfig.hを開発環境のfirmwareフォルダ以下へコピーしてください。また、usbdrvフォルダを開発環境のfirmwareフォルダ直下へコピーしてください。
 
libusb-win32ドライバを格納している書庫ファイルを解凍すると、libファイルを格納したフォルダ(lib\msvc)にlibusb.libファイルがあります。また、includeフォルダにlusb0_usb.hファイルがあります。
libusb.libファイルとlusb0_usb.hファイルを開発環境のled_ctrlフォルダへコピーしてください。また、lusb0_usb.hファイルはusb.hにリネームしてください。
結果、以下の様なファイル配置になります。

開発環境

[led_ctrl]
 ├ [firmware]
 │  ├ [usbdrv]
 │  │  ├ asmcommon.inc
 │  │  ├ Changelog.txt
 │  │  ├ CommercialLicense.txt
 │  │  ├ License.txt
 │  │  ├ oddebug.c
 │  │  ├ oddebug.h
 │  │  ├ Readme.txt
 │  │  ├ usbconfig-prototype.h
 │  │  ├ usbdrv.c
 │  │  ├ usbdrv.h
 │  │  ├ usbdrvasm.asm
 │  │  ├ usbdrvasm.S
 │  │  ├ usbdrvasm12.inc
 │  │  ├ usbdrvasm128.inc
 │  │  ├ usbdrvasm15.inc
 │  │  ├ usbdrvasm16.inc
 │  │  ├ usbdrvasm165.inc
 │  │  ├ usbdrvasm18-crc.inc
 │  │  ├ usbdrvasm20.inc
 │  │  ├ USB-ID-FAQ.txt
 │  │  ├ USB-IDs-for-free.txt
 │  │  └ usbportability.h
 │  ├ main.c
 │  ├ requests.h
 │  └ usbconfig.h
 ├ [led_ctrl]
 │  ├ led_ctrl.cpp
 │  ├ led_ctrl.vcxproj
 │  ├ led_ctrl.vcxproj.filters
 │  ├ led_ctrl.vcxproj.user
 │  ├ libusb.lib
 │  ├ opendevice.c
 │  ├ opendevice.h
 │  ├ ReadMe.txt
 │  ├ stdafx.cpp
 │  ├ stdafx.h
 │  ├ targetver.h
 │  └ usb.h
 ├ led_ctrl.sdf
 ├ led_ctrl.sln
 └ led_ctrl.suo

 


次に、Visual C++で以下のようにプロジェクトにファイルを登録してください。

 
プロジェクトにファイルを登録したらopendevice.cのコンパイルオプションに/TPを加えてください。*.cファイルをC++ファイルとしてコンパイルするオプションです。また、プロジェクトのプロパティのリンカオプションでlibusb.libファイルをリンクするように設定してください。
 
以下にled_ctrl.cppのコードを示します。

led_ctrl.cpp

// led_ctrl.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

/* Name: led_ctrl.cpp
 * Project: custom-class, a basic USB example
 * Author: Christian Starkjohann
 * Modify: Shougo TODA
 * Creation Date: 2008-04-10
 * Tabsize: 4
 * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
 */

/*
General Description:
This is the host-side driver for the custom-class example device. It searches
the USB for the LEDControl device and sends the requests understood by this
device.
This program must be linked with libusb on Unix and libusb-win32 on Windows.
See
http://libusb.sourceforge.net/ or http://libusb-win32.sourceforge.net/
respectively.
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "usb.h"
#include "opendevice.h"

#include "../firmware/requests.h"
#include "../firmware/usbconfig.h"

static void usage(char *name)
{
    fprintf(stderr, "usage:\n");
    fprintf(stderr, "  %s on ....... turn on LED\n", name);
    fprintf(stderr, "  %s off ...... turn off LED\n", name);
    fprintf(stderr, "  %s status ... ask current status of LED\n", name);
#if ENABLE_TEST
    fprintf(stderr, "  %s test ..... run driver reliability test\n", name);
#endif /* ENABLE_TEST */
}

int main(int argc, char **argv)
{
usb_dev_handle      *handle = NULL;
const unsigned char rawVid[2] = {USB_CFG_VENDOR_ID}, rawPid[2] = {USB_CFG_DEVICE_ID};
char                vendor[] = {USB_CFG_VENDOR_NAME, 0}, product[] = {USB_CFG_DEVICE_NAME, 0};
char                buffer[4];
int                 cnt, vid, pid, isOn;

    usb_init();
    if(argc < 2){   /* we need at least one argument */
        usage(argv[0]);
        exit(1);
    }
    /* compute VID/PID from usbconfig.h so that there is a central source of information */
    vid = rawVid[1] * 256 + rawVid[0];
    pid = rawPid[1] * 256 + rawPid[0];
    /* The following function is in opendevice.c: */
    if(usbOpenDevice(&handle, vid, vendor, pid, product, NULL, NULL, NULL) != 0){
        fprintf(stderr, "Could not find USB device \"%s\" with vid=0x%x pid=0x%x\n", product, vid, pid);
        exit(1);
    }
    /* Since we use only control endpoint 0, we don't need to choose a
     * configuration and interface. Reading device descriptor and setting a
     * configuration and interface is done through endpoint 0 after all.
     * However, newer versions of Linux require that we claim an interface
     * even for endpoint 0. Enable the following code if your operating system
     * needs it: */
#if 0
    int retries = 1, usbConfiguration = 1, usbInterface = 0;
    if(usb_set_configuration(handle, usbConfiguration) && showWarnings){
        fprintf(stderr, "Warning: could not set configuration: %s\n", usb_strerror());
    }
    /* now try to claim the interface and detach the kernel HID driver on
     * Linux and other operating systems which support the call. */
    while((len = usb_claim_interface(handle, usbInterface)) != 0 && retries-- > 0){
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
        if(usb_detach_kernel_driver_np(handle, 0) < 0 && showWarnings){
            fprintf(stderr, "Warning: could not detach kernel driver: %s\n", usb_strerror());
        }
#endif
    }
#endif

    if(strcmp(argv[1], "status") == 0){
        cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0, buffer, sizeof(buffer), 5000);
        if(cnt < 1){
            if(cnt < 0){
                fprintf(stderr, "USB error: %s\n", usb_strerror());
            }else{
                fprintf(stderr, "only %d bytes received.\n", cnt);
            }
        }else{
            printf("LED is %s\n", buffer[0] ? "on" : "off");
        }
    }else if((isOn = (strcmp(argv[1], "on") == 0)) || strcmp(argv[1], "off") == 0){
        cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, isOn, 0, buffer, 0, 5000);
        if(cnt < 0){
            fprintf(stderr, "USB error: %s\n", usb_strerror());
        }
#if ENABLE_TEST
    }else if(strcmp(argv[1], "test") == 0){
        int i;
        srandomdev();
        for(i = 0; i < 50000; i++){
            int value = random() & 0xffff, index = random() & 0xffff;
            int rxValue, rxIndex;
            if((i+1) % 100 == 0){
                fprintf(stderr, "\r%05d", i+1);
                fflush(stderr);
            }
            cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_ECHO, value, index, buffer, sizeof(buffer), 5000);
            if(cnt < 0){
                fprintf(stderr, "\nUSB error in iteration %d: %s\n", i, usb_strerror());
                break;
            }else if(cnt != 4){
                fprintf(stderr, "\nerror in iteration %d: %d bytes received instead of 4\n", i, cnt);
                break;
            }
            rxValue = ((int)buffer[0] & 0xff) | (((int)buffer[1] & 0xff) << 8);
            rxIndex = ((int)buffer[2] & 0xff) | (((int)buffer[3] & 0xff) << 8);
            if(rxValue != value || rxIndex != index){
                fprintf(stderr, "\ndata error in iteration %d:\n", i);
                fprintf(stderr, "rxValue = 0x%04x value = 0x%04x\n", rxValue, value);
                fprintf(stderr, "rxIndex = 0x%04x index = 0x%04x\n", rxIndex, index);
            }
        }
        fprintf(stderr, "\nTest completed.\n");
#endif /* ENABLE_TEST */
    }else{
        usage(argv[0]);
        exit(1);
    }
    usb_close(handle); 
    return 0;
}

 
 

V-USBファームウェア

 
次に、AVRマイコンに書きこむV-USBファームウェアの開発環境を構築します。
 
AVR Studio 4を起動して、プロジェクトウィザードからプロジェクトを作成してください。
ファイルは以下の様に登録します。


ビルドをしてhexファイルを作成してください。コンパイルエラーは各自修正してください。

次に、hexファイルをお手持ちのライターでダウンロードしてください。

開発環境ファイル群のダウンロード

 
ここまでの環境設定をこちらからダウンロードできます。
 
 

USBデバイスの動作確認

 

PCへの接続とlibusb-win32ドライバのインストール 

 

libusb-win32


libusb-win32は汎用のUSBドライバです。このドライバのAPIをコンソールアプリケーションから呼び出してUSBデバイスと通信をします。
ダウンロードして適当なフォルダに保存してください。
 

USBデバイスとPCの接続


まず、製作したUSBデバイスをPCのUSBポートに接続します。
以下のメッセージが表示されたらデバイスは正常に動作しています。
何も表示されなかった場合、回路かファームウェア設定に不具合があると思われます。調査して修正してください。
 
 

libusb-win32ドライバ INFファイルの作成

 
libusb-win32をダウンロードして解凍した後のフォルダ内にinf-wizardが収録されています。これを起動します。
このユーティリティを使って製作したUSBデバイスのドライバをインストールするためのINFファイルをウィザード形式で作成します。
 

[NEXT>]ボタンを押下していくとDevice Selection画面になります。
V-USBのusbconfig.hに書いたVendor IDとProduct IDとデバイス名がリストに表示されるので、
リスト項目を選択して[NEXT>]ボタンを押下してください。
INFファイルを保存する場所を聞いてきますので適当な場所にINFファイルを保存してください。
そのフォルダにドライバファイルが格納されます。
 

最後に以下の画面になります。
[Install Now..]ボタンを押下してください。ドライバのインストールが開始されます。
 

インストールが完了したらデバイスマネージャで確認してください。
以下のようにUSBデバイスが表示されたらドライバのインストールは成功です。
 
 

Win32コンソールアプリケーションの実行

 
コマンドプロンプトからled_ctrl.exeを実行してください。


左の写真がLED点灯、右の写真がLED消灯の写真です。 




応用例

 

応用例


PWMドライバを作成して、コントロール転送でサーボモータを動作させてみました。 ATmega168用のPWMドライバはこちら

参考資料・参考URL