##AVRマイコンをUSB接続
PCにシリアルポートがほぼ無くなってから久しく、ちょっとした工作物をPCに繋ぐのにも色々苦労が伴うので、私が使ったAVRマイコンとPCをUSBで接続する方法の特徴をまとめてみました。
####USBシリアル変換モジュールを使う
FT232等のUSBシリアル変換モジュールを使い、PCからはシリアルポートとして扱います、マイコン側からは普通にUSART接続として使えるので簡単ですがパソコン側のドライバの関係で小さなデータを頻繁に送ったりするとバッファリングされたりするのでレイテンシが大きくなる場合があり(数ミリセカンド単位)リアルタイムに処理が必要な場合は低レベルのライブラリの使用が必要になる事が有ります、またMIDIやキーボドなどのデバイスを作る場合はシリアルとの変換ブリッジソフトが必要です。
MIDIブリッジHairless MIDI to Serial Bridge
V-USBはLow-SpeedのUSBデバイスをソフトウェアと簡単な回路で実現したもの。
USBトランシーバを持っていないATmega328やATTiny85等でもUSBデバイスなどを作る事が出来、規模が小さく速度を求められない場合は大変便利です。(写真は「Enter」のみのUSBキーボード外部クリスタル無し)
簡単に使えるインタラプト転送の場合帯域のほとんどを使わない為、レガシイMIDI-USB変換器などでもデータ量が増えるとデータの取りこぼしが発生するぐらいの速度しか出ないため、低速デバイス専用と思っておいて良いでしょう。
また、10mSごとにホストからのデータ転送要求のための割り込みが掛るので、途中で割り込まれると困る場合(他デバイスとの通信等)は工夫が必要になります。
WindowsでHIDデバイスを使う時にはLibUSBのデバイスドライバのインストールが必要になりWindows8以降のOSを使用している場合、非承認デバイスドライバのインストールと言う面倒くさい作業が必要になる場合があります。
少しテンプレートと違う事をする時はある程度のUSBデバイスに対する知識も必要となるのですが、ディスクリプタの実装が後述のLUFAと比べると分かりにくく複合デバイスなどを作るのには苦労します。
MIDIデバイスのディスクリプタの例
/* --------------------------- Device Descriptor --------------------------- */
#if USB_CFG_DESCR_PROPS_DEVICE == 0
#undef USB_CFG_DESCR_PROPS_DEVICE
#define USB_CFG_DESCR_PROPS_DEVICE sizeof(usbDescriptorDevice)
PROGMEM const char usbDescriptorDevice[] = { /* USB device descriptor */
18, /* sizeof(usbDescriptorDevice): length of descriptor in bytes */
USBDESCR_DEVICE, /* descriptor type */
0x10, 0x01, /* USB version supported */
USB_CFG_DEVICE_CLASS,
USB_CFG_DEVICE_SUBCLASS,
0, /* protocol */
8, /* max packet size */
/* the following two casts affect the first byte of the constant only, but
* that's sufficient to avoid a warning with the default values.
*/
(char)USB_CFG_VENDOR_ID,/* 2 bytes */
(char)USB_CFG_DEVICE_ID,/* 2 bytes */
USB_CFG_DEVICE_VERSION, /* 2 bytes */
USB_CFG_DESCR_PROPS_STRING_VENDOR != 0 ? 1 : 0, /* manufacturer string index */
USB_CFG_DESCR_PROPS_STRING_PRODUCT != 0 ? 2 : 0, /* product string index */
USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER != 0 ? 3 : 0, /* serial number string index */
1, /* number of configurations */
};
#endif
/* ----------------------- Configuration Descriptor ------------------------ */
#if USB_CFG_DESCR_PROPS_HID_REPORT != 0 && USB_CFG_DESCR_PROPS_HID == 0
#undef USB_CFG_DESCR_PROPS_HID
#define USB_CFG_DESCR_PROPS_HID 9 /* length of HID descriptor in config descriptor below */
#endif
#if USB_CFG_DESCR_PROPS_CONFIGURATION == 0
#undef USB_CFG_DESCR_PROPS_CONFIGURATION
#define USB_CFG_DESCR_PROPS_CONFIGURATION sizeof(usbDescriptorConfiguration)
PROGMEM const char usbDescriptorConfiguration[] = { /* USB configuration descriptor */
9, /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
USBDESCR_CONFIG, /* descriptor type */
18 + 7 * USB_CFG_HAVE_INTRIN_ENDPOINT + 7 * USB_CFG_HAVE_INTRIN_ENDPOINT3 +
(USB_CFG_DESCR_PROPS_HID & 0xff), 0,
/* total length of data returned (including inlined descriptors) */
1, /* number of interfaces in this configuration */
1, /* index of this configuration */
0, /* configuration name string index */
#if USB_CFG_IS_SELF_POWERED
(1 << 7) | USBATTR_SELFPOWER, /* attributes */
#else
(1 << 7), /* attributes */
#endif
USB_CFG_MAX_BUS_POWER/2, /* max USB current in 2mA units */
/* interface descriptor follows inline: */
9, /* sizeof(usbDescrInterface): length of descriptor in bytes */
USBDESCR_INTERFACE, /* descriptor type */
0, /* index of this interface */
0, /* alternate setting for this interface */
USB_CFG_HAVE_INTRIN_ENDPOINT + USB_CFG_HAVE_INTRIN_ENDPOINT3, /* endpoints excl 0: number of endpoint descriptors to follow */
USB_CFG_INTERFACE_CLASS,
USB_CFG_INTERFACE_SUBCLASS,
USB_CFG_INTERFACE_PROTOCOL,
0, /* string index for interface */
#if (USB_CFG_DESCR_PROPS_HID & 0xff) /* HID descriptor */
9, /* sizeof(usbDescrHID): length of descriptor in bytes */
USBDESCR_HID, /* descriptor type: HID */
0x01, 0x01, /* BCD representation of HID version */
0x00, /* target country code */
0x01, /* number of HID Report (or other HID class) Descriptor infos to follow */
0x22, /* descriptor type: report */
USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH, 0, /* total length of report descriptor */
#endif
#if USB_CFG_HAVE_INTRIN_ENDPOINT /* endpoint descriptor for endpoint 1 */
7, /* sizeof(usbDescrEndpoint) */
USBDESCR_ENDPOINT, /* descriptor type = endpoint */
(char)0x81, /* IN endpoint number 1 */
0x03, /* attrib: Interrupt endpoint */
8, 0, /* maximum packet size */
USB_CFG_INTR_POLL_INTERVAL, /* in ms */
#endif
#if USB_CFG_HAVE_INTRIN_ENDPOINT3 /* endpoint descriptor for endpoint 3 */
7, /* sizeof(usbDescrEndpoint) */
USBDESCR_ENDPOINT, /* descriptor type = endpoint */
(char)(0x80 | USB_CFG_EP3_NUMBER), /* IN endpoint number 3 */
0x03, /* attrib: Interrupt endpoint */
8, 0, /* maximum packet size */
USB_CFG_INTR_POLL_INTERVAL, /* in ms */
#endif
};
#endif
LUFAライブラリをAtmelStudioへインストールして使用します、テンプレートのUSBデバイスの必要な部分を変更すれば良くオンラインドキュメントのModules->USB Class Driver辺りを見ればなんとか使い方が分かります、先述のV-USBと比べるとディスクリプタの実装が分かり易いので頑張ればコピペ+αぐらいの労力で複合デバイスの作成も可能ですが、V-USB同様USBの知識なしではかなり苦労します。
流石に専用のUSBトランシーバーを使うので速度はV-USBとは比べ物にならないぐらい速く、作成出来るUSBデバイスの種類も豊富なので対応chipが使用できるのであれば柔軟にデバイスが作成出来ます、ATmega32U2を使ってコンパクトに纏めることもこの方法では可能ですし、動かしておいて後からアセンブリにゴリゴリ書き換えたりするならお勧めの方法です。
MIDIとCDCの複合デバイスのディスクリプタの例
const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =
{
.Config =
{
.Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration},
.TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t),
.TotalInterfaces = 4,
.ConfigurationNumber = 1,
.ConfigurationStrIndex = NO_DESCRIPTOR,
.ConfigAttributes = (USB_CONFIG_ATTR_RESERVED | USB_CONFIG_ATTR_SELFPOWERED),
.MaxPowerConsumption = USB_CONFIG_POWER_MA(100)
},
.MIDI_IAD =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_Association_t), .Type = DTYPE_InterfaceAssociation},
.FirstInterfaceIndex = INTERFACE_ID_AudioControl,
.TotalInterfaces = 2,
.Class = AUDIO_CSCP_AudioClass,
.SubClass = AUDIO_CSCP_ControlSubclass,
.Protocol = AUDIO_CSCP_ControlProtocol,
.IADStrIndex = NO_DESCRIPTOR
},
.Audio_ControlInterface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_AudioControl,
.AlternateSetting = 0,
.TotalEndpoints = 0,
.Class = AUDIO_CSCP_AudioClass,
.SubClass = AUDIO_CSCP_ControlSubclass,
.Protocol = AUDIO_CSCP_ControlProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.Audio_ControlInterface_SPC =
{
.Header = {.Size = sizeof(USB_Audio_Descriptor_Interface_AC_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_Header,
.ACSpecification = VERSION_BCD(1,0,0),
.TotalLength = sizeof(USB_Audio_Descriptor_Interface_AC_t),
.InCollection = 1,
.InterfaceNumber = INTERFACE_ID_AudioStream,
},
.Audio_StreamInterface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_AudioStream,
.AlternateSetting = 0,
.TotalEndpoints = 2,
.Class = AUDIO_CSCP_AudioClass,
.SubClass = AUDIO_CSCP_MIDIStreamingSubclass,
.Protocol = AUDIO_CSCP_StreamingProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.Audio_StreamInterface_SPC =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_AudioInterface_AS_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_General,
.AudioSpecification = VERSION_BCD(1,0,0),
.TotalLength = (sizeof(USB_Descriptor_Configuration_t) -
offsetof(USB_Descriptor_Configuration_t, Audio_StreamInterface_SPC))
},
.MIDI_In_Jack_Emb =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal,
.JackType = MIDI_JACKTYPE_Embedded,
.JackID = 0x01,
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_In_Jack_Emb2 =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal,
.JackType = MIDI_JACKTYPE_Embedded,
.JackID = 0x02,
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_In_Jack_Ext =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal,
.JackType = MIDI_JACKTYPE_External,
.JackID = 0x03,
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_In_Jack_Ext2 =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal,
.JackType = MIDI_JACKTYPE_External,
.JackID = 0x04,
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_Out_Jack_Emb =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal,
.JackType = MIDI_JACKTYPE_Embedded,
.JackID = 0x05,
.NumberOfPins = 1,
.SourceJackID = {0x03},
.SourcePinID = {0x01},
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_Out_Jack_Emb2 =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal,
.JackType = MIDI_JACKTYPE_Embedded,
.JackID = 0x06,
.NumberOfPins = 1,
.SourceJackID = {0x04},
.SourcePinID = {0x01},
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_Out_Jack_Ext =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal,
.JackType = MIDI_JACKTYPE_External,
.JackID = 0x07,
.NumberOfPins = 1,
.SourceJackID = {0x01},
.SourcePinID = {0x01},
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_Out_Jack_Ext2 =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface},
.Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal,
.JackType = MIDI_JACKTYPE_External,
.JackID = 0x08,
.NumberOfPins = 1,
.SourceJackID = {0x02},
.SourcePinID = {0x01},
.JackStrIndex = NO_DESCRIPTOR
},
.MIDI_In_Jack_Endpoint =
{
.Endpoint =
{
.Header = {.Size = sizeof(USB_Audio_Descriptor_StreamEndpoint_Std_t), .Type = DTYPE_Endpoint},
.EndpointAddress = MIDI_STREAM_OUT_EPADDR,
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = MIDI_STREAM_EPSIZE,
.PollingIntervalMS = 0x05
},
.Refresh = 0,
.SyncEndpointNumber = 0
},
.MIDI_In_Jack_Endpoint_SPC =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_DualJack_Endpoint_t), .Type = DTYPE_CSEndpoint},
.Subtype = AUDIO_DSUBTYPE_CSEndpoint_General,
.TotalEmbeddedJacks = 0x02,
.AssociatedJackID = {0x01, 0x02}
},
.MIDI_Out_Jack_Endpoint =
{
.Endpoint =
{
.Header = {.Size = sizeof(USB_Audio_Descriptor_StreamEndpoint_Std_t), .Type = DTYPE_Endpoint},
.EndpointAddress = MIDI_STREAM_IN_EPADDR,
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = MIDI_STREAM_EPSIZE,
.PollingIntervalMS = 0x05
},
.Refresh = 0,
.SyncEndpointNumber = 0
},
.MIDI_Out_Jack_Endpoint_SPC =
{
.Header = {.Size = sizeof(USB_MIDI_Descriptor_DualJack_Endpoint_t), .Type = DTYPE_CSEndpoint},
.Subtype = AUDIO_DSUBTYPE_CSEndpoint_General,
.TotalEmbeddedJacks = 0x02,
.AssociatedJackID = {0x05, 0x06}
},
.CDC_IAD =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_Association_t), .Type = DTYPE_InterfaceAssociation},
.FirstInterfaceIndex = INTERFACE_ID_CDC_CCI,
.TotalInterfaces = 2,
.Class = CDC_CSCP_CDCClass,
.SubClass = CDC_CSCP_ACMSubclass,
.Protocol = CDC_CSCP_ATCommandProtocol,
.IADStrIndex = NO_DESCRIPTOR
},
.CDC_CCI_Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_CDC_CCI,
.AlternateSetting = 0,
.TotalEndpoints = 1,
.Class = CDC_CSCP_CDCClass,
.SubClass = CDC_CSCP_ACMSubclass,
.Protocol = CDC_CSCP_ATCommandProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.CDC_Functional_Header =
{
.Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalHeader_t), .Type = DTYPE_CSInterface},
.Subtype = CDC_DSUBTYPE_CSInterface_Header,
.CDCSpecification = VERSION_BCD(1,1,0),
},
.CDC_Functional_ACM =
{
.Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalACM_t), .Type = DTYPE_CSInterface},
.Subtype = CDC_DSUBTYPE_CSInterface_ACM,
.Capabilities = 0x06,
},
.CDC_Functional_Union =
{
.Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalUnion_t), .Type = DTYPE_CSInterface},
.Subtype = CDC_DSUBTYPE_CSInterface_Union,
.MasterInterfaceNumber = INTERFACE_ID_CDC_CCI,
.SlaveInterfaceNumber = INTERFACE_ID_CDC_DCI,
},
.CDC_NotificationEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = CDC_NOTIFICATION_EPADDR,
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = CDC_NOTIFICATION_EPSIZE,
.PollingIntervalMS = 0xFF
},
.CDC_DCI_Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_CDC_DCI,
.AlternateSetting = 0,
.TotalEndpoints = 2,
.Class = CDC_CSCP_CDCDataClass,
.SubClass = CDC_CSCP_NoDataSubclass,
.Protocol = CDC_CSCP_NoDataProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.CDC_DataOutEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = CDC_RX_EPADDR,
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = CDC_TXRX_EPSIZE,
.PollingIntervalMS = 0x05
},
.CDC_DataInEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = CDC_TX_EPADDR,
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = CDC_TXRX_EPSIZE,
.PollingIntervalMS = 0x05
},
};
####Arduino LeonardoまたはMicroを使う
ATmega32U4を使用しているLeonardoやMicroは標準でkeyboardとmouse,ライブラリでMIDIデバイスが作れるので、ちょっとした実験や凝ったものを作るので無ければUSBのディスクリプタ等の知識がなくても簡単にUSBデバイスが作成できますが、コードサイズがやや大きくなる傾向があります。
USB-MIDI keyboardの例
#include <frequencyToNote.h>
#include <MIDIUSB.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>
#define ON 1
#define OFF 0
int note_no[12];
int stat[12];
midiEventPacket_t event;
void setup() {
// put your setup code here, to run once:
for (int i = 0; i < 12; i++) {
pinMode(i, INPUT_PULLUP);
stat[i]=OFF;
note_no[i] = 48 + i;
}
}
void loop() {
// put your main code here, to run repeatedly:
for (int i = 0; i < 12;i++) {
if ( digitalRead(i) == LOW) {
if (stat[i] == OFF) {
stat[i] = ON;
event = {9, 0x90, note_no[i], 127};
MidiUSB.sendMIDI(event);
}
} else {
if (stat[i] == ON) {
stat[i] = OFF;
event = {9, 0x90, note_no[i], 0};
MidiUSB.sendMIDI(event);
}
}
}
MidiUSB.flush();
}
まとめ
取りあえず実験とかはFT232等、小さく纏めたい場合はV-USB、自由度と速度重視のLUFA、これで出来るなら一番簡単Arduinoと言った所で使い分ければよいのではないでしょうか。