言っとくが修復不能な欠陥があってマトモには動かない.音は出るけど、数秒ごとにFIFOアンダーフローが生じてプツッとノイズが混じる. →想定原因はこちらを参照
動作確認?が済んでいる条件は、、、
・windows10
・デバドラはusbaudio2.sys
・USB audio class 2.0
・32bit 384kHz ステレオ (他モードでは音出ししてない)
・DCCであるから、USB→I2S の変換回路である
・DACはPCM5102のI2S接続で音出ししている
それでは以下はど長文のレポートである.
なにせ途中で投げ出した設計なので、コメントがインチキだったりするのであまり信用しないでくれよな.
なお、いかなる間違いにもわたしは何の責任も負わないので素直に死んでくれ.
ーーーーーー
【全体写真】
①EZ-USB
②8bit audio data → I2S 変換 FPGA
③DAC PCM5102
④USB setup request表示器 Arduino
①と②の関係.
EZ-USBに積まれた音声データは、FPGAに読みだしてもらう必要がある.FPGAがmaster clockを発生させるからだ.音声データは8bitで伝送する.
ゆえにEZ-USBがhostから指示されたbit情報やサンプリング周波数情報をFPGAへ渡してやる必要がある.そのための配線がごちゃっとある.
②と③の関係.
FPGAが音声データをI2Sに変換してDACへ音声データを供給する.
①と④の関係.
hostからのsetup requestをモニタするためにarduinoを設けてある.EZ-USBはI2Cでsetupdataを出力する.それを受けたArduinoはsetupdataを解析してCOM portへ文字出力する.
ソースコードは3つの部分から成る.
①EZ-USB firmware 開発環境はKeil uVision4
②XILINX Spartan6 開発環境はISE13 verilog
④Arduino nano 開発環境はArduinoIDE
以下では、①②④のsource codeを解説する.
【①EZ-USB firmware】
source fileの構成はこのようになっている.こちらのフォルダにヒラサカが追記したファイルを入れておく.赤線のファイルについて以下で触れる.
【USB descriptor dscr.a51】
まずはdevice descriptorから.
VID/PIDはゼロにしてある.そんなので動くの?と思われるかもしれないが、動いている.動く理由は、windowsのUAC2.0 driverにはVID/PIDに紐づけされた部分が無いらしいのだ.その代わり、UAC2.0 classでぇす、というUSB deviceからの申告に素直に対応するみたいよ.
; --------- Device Descriptor ------------
;bLength : 0x12 (18 bytes)
;bDescriptorType : 0x01 (Device Descriptor)
;bcdUSB : 0x200 (USB Version 2.00)
;bDeviceClass : 0xEF (Miscellaneous)
;bDeviceSubClass : 0x02
;bDeviceProtocol : 0x01 (IAD - Interface Association Descriptor)
;bMaxPacketSize0 : 0x40 (64 bytes)
;idVendor : 0x0000
;idProduct : 0x0000
;bcdDevice : 0x0208
;iManufacturer : 0x01 (String Descriptor 1)
; Language 0x0409 : "bangflat"
;iProduct : 0x02 (String Descriptor 2)
; Language 0x0409 : "EZ-USB DCC"
;iSerialNumber : 0x00 (No String Descriptor)
;bNumConfigurations : 0x01 (1 Configuration)
DeviceDscr:
次はDevice Qualifier Descriptorである.
特に言うべきことはない.
;----------------- Device Qualifier Descriptor -----------------
;bLength : 0x0A (10 bytes)
;bDescriptorType : 0x06 (Device_qualifier Descriptor)
;bcdUSB : 0x200 (USB Version 2.00)
;bDeviceClass : 0xEF (Miscellaneous)
;bDeviceSubClass : 0x02
;bDeviceProtocol : 0x01 (IAD - Interface Association Descriptor)
;bMaxPacketSize0 : 0x40 (64 Bytes)
;bNumConfigurations : 0x01 (1 other-speed configuration)
;bReserved : 0x00
DeviceQualDscr:
Configuration Descriptorについてはいくつか指摘しておきたい.
265bytesという巨大数値があるが、これはHOST PCがdecriptorの下の方まで一気読みを要求してくるのに対応している.
2 interfacesという文字がある.2つのinterfaceがありますと言っているのだが、1つはAudio control IFで、もう一つはAudio streaming IFだ.Audio controlはサンプリング周波数やvolumeなどの機能.Audio streamingはPCM dataを流す機能.
HighSpeedConfigDscr: という文字がある.後ろの方にはFullSpeedConfigDscr:というのがあって、HighSpeedは480Mbpsモードの意味である.FullSpeedは12Mbpsの意味である.480/12Mbpsそれぞれに独立したdescriptorが存在する.なお、32bit384kHzPCMを再生するには12Mbpsではまるで足りない.なので専ら使うのは480Mbpsである.
; ------------------ Configuration Descriptor -------------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x02 (Configuration Descriptor)
;wTotalLength : 0x0109 (265 bytes)
;bNumInterfaces : 0x02 (2 Interfaces)
;bConfigurationValue : 0x01 (Configuration 1)
;iConfiguration : 0x00 (No String Descriptor)
;bmAttributes : 0xC0
; D7: Reserved, set 1 : 0x01
; D6: Self Powered : 0x01 (yes)
; D5: Remote Wakeup : 0x00 (no)
; D4..0: Reserved, set 0 : 0x00
;MaxPower : 0x32 (100 mA)
HighSpeedConfigDscr:
IADのことはよく知らない.
; ------------------- IAD Descriptor --------------------
;bLength : 0x08 (8 bytes)
;bDescriptorType : 0x0B
;bFirstInterface : 0x00
;bInterfaceCount : 0x02
;bFunctionClass : 0x01 (Audio)
;bFunctionSubClass : 0x00 (undefined)
;bFunctionProtocol : 0x20 (AF 2.0)
;iFunction : 0x00 (No String Descriptor)
ここからはAudio control IFである.
幾つかの機能moduleから成り、それぞれにID番号がつけられ、ID番号で結線を追えるようになっている.
記述されているのはこんなことである.
input terminalはID1であり、clockはID18から供給される.
control feature unitはID13であり、ID1から入力される.
output terminalはID7であり、ID13から入力され、clockはID18から供給される.
volume等の音質制御機能を全部殺してある.
しかしここで重要なのは、サンプリング周波数の設定である.ID18のAudio Control Clock Source UnitへめがけてHOST PCがサンプリング周波数を投げて寄こすのだ.
それ以外の機能は何も使っていない.
; ---------------- Interface Descriptor -----------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x04 (Interface Descriptor)
;bInterfaceNumber : 0x00
;bAlternateSetting : 0x00
;bNumEndpoints : 0x01 (1 Endpoint)
;bInterfaceClass : 0x01 (Audio)
;bInterfaceSubClass : 0x01 (Audio Control)
;bInterfaceProtocol : 0x20 (Device Protocol Version 2.0)
;iInterface : 0x00 (No String Descriptor)
中略
; ----- Audio Control Input Terminal Descriptor 2.0 -----
;bLength : 0x11 (17 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x02 (Input Terminal 2.0)
;bTerminalID : 0x01
;wTerminalType : 0x0101 (USB streaming)
;bAssocTerminal : 0x00
;bCSourceID : 0x12 (18)
;bNrChannels : 0x00 (0 Channels)
;bmChannelConfig : 0x00000000 (-)
;iChannelNames : 0x00 (No String Descriptor)
;bmControls : 0x0040
; D1..0 : Copy Protect : 0x00 (not present)
; D3..2 : Connector : 0x00 (not present)
; D5..4 : Overload : 0x00 (not present)
; D7..6 : Cluster : 0x01 (read only)
; D9..8 : Underflow : 0x00 (not present)
; D11..10: Overflow : 0x00 (not present)
; D15..12: Reserved : 0x00
;iTerminal : 0x00 (No String Descriptor)
; ----- Audio Control Output Terminal Descriptor 2.0 ----
;bLength : 0x0C (12 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x03 (Output Terminal 2.0)
;bTerminalID : 0x07
;wTerminalType : 0x0301 (Speaker)
;bAssocTerminal : 0x00 (0)
;bSourceID : 0x0D (13)
;bCSourceID : 0x12 (18)
;iTerminal : 0x00 (No String Descriptor)
; ------ Audio Control Feature Unit Descriptor 2.0 ------
;bLength : 0x12 (18 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x06 (Feature Unit 2.0)
;bUnitID : 0x0D (13)
;bSourceID : 0x01 (1)
;bmaControls[0] : 0x00, 0x00, 0x00, 0x00 ←全部殺し
;iFeature : 0x00 (No String Descriptor)
; --- Audio Control Clock Source Unit Descriptor 2.0 ----
;bLength : 0x08 (8 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x0A (Clock Source 2.0)
;bClockID : 0x12
;bmAttributes : 0x03
; D1..0: Clock Type : 0x03
; D2 : Sync to SOF : 0x00
; D7..3: Reserved : 0x00
;bmControls : 0x07
; D1..0: Clock Frequency : 0x03 (host programmable)
; D3..2: Clock Validity : 0x01 (read only)
; D7..4: Reserved : 0x00
;bAssocTerminal : 0x00
;iClockSource : 0x00 (No String Descriptor)
; ----------------- Endpoint Descriptor -----------------
;bLength : 0x07 (7 bytes)
;bDescriptorType : 0x05 (Endpoint Descriptor)
;bEndpointAddress : 0x86 (Direction=IN EndpointID=6)
;bmAttributes : 0x03 (TransferType=Interrupt)
;wMaxPacketSize : 0x0006
; Bits 15..13 : 0x00 (reserved, must be zero)
; Bits 12..11 : 0x00 (0 additional transactions per microframe -> allows 1..1024 bytes per packet)
; Bits 10..0 : 0x06 (6 bytes per packet)
;bInterval : 0x04 (4 ms)
ここは短く終わってしまう.interface1のalternate0である.
alternate0がどのように活用されているかというと、hardwareをsleepにするスイッチのように使われる.後述するalternate1,2,3になったら再生ONの意味である.
; ---------------- Interface Descriptor -----------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x04 (Interface Descriptor)
;bInterfaceNumber : 0x01
;bAlternateSetting : 0x00
;bNumEndpoints : 0x00 (Default Control Pipe only)
;bInterfaceClass : 0x01 (Audio)
;bInterfaceSubClass : 0x02 (Audio Streaming)
;bInterfaceProtocol : 0x20 (Device Protocol Version 2.0)
;iInterface : 0x04 (String Descriptor 4)
; Language 0x0409 : "Speaker"
以下はinterface1のalternate0である.16bitモードのPCM streamだ.
EPを2つ持っている.EP4 OUTと、EP2 INだ.
EP4 OUTはもちろんPCM streamだ.
EP2 INは何かというと、転送レート調節機能のためのfeedbackである.
EZ-USBを放棄した原因がこのfeedbackだった.EP4 OUTとEP4 INのように同じ番号じゃないとfeedbackは動かないというのがUSB規格書に書いてある.EZ-USBで同番号IN/OUTは実装できない.そのせいでfeedbackが正しく動作せず、転送レート調節機能が動作せず、FIFO emptyが生じていると考えている.
2chでFront LとFront Rであると書かれている.
16bit 4bytesと書かれているのに戸惑いがあるだろう.無駄であるが、32bit枠で16bit伝送してくれ、という要求だ.下位16bitはゼロ埋めで.理由は、I2S DACがそういう仕様だからなのだ.
400 bytes per packetという文字がある.これはEZ-USBのFIFOサイズ512bytesを超えない範囲でという意味で400にしてある.なので500でもいいんだろう.
末尾のEP82がfeedbackであり、4 bytes per packetという文字がある.これはfeedback valueが4bytesという意味である.
; ---------------- Interface Descriptor -----------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x04 (Interface Descriptor)
;bInterfaceNumber : 0x01
;bAlternateSetting : 0x01
;bNumEndpoints : 0x02 (2 Endpoints)
;bInterfaceClass : 0x01 (Audio)
;bInterfaceSubClass : 0x02 (Audio Streaming)
;bInterfaceProtocol : 0x20 (Device Protocol Version 2.0)
;iInterface : 0x00 (No String Descriptor)
; ------ Audio Streaming Interface Descriptor 2.0 -------
;bLength : 0x10 (16 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x01 (AS General)
;bTerminalLink : 0x01 (1)
;bmControls : 0x05
; D1..0: Active Alt Settng: 0x01 (read only)
; D3..2: Valid Alt Settng : 0x01 (read only)
; D7..4: Reserved : 0x00
;bFormatType : 0x01 (FORMAT_TYPE_I)
;bmFormats : 0x00000001 (PCM)
;bNrChannels : 0x02 (2 channels)
;bmChannelConfig : 0x00000003 (FL, FR)
;iChannelNames : 0x00 (No String Descriptor)
; ----- Audio Streaming Format Type Descriptor 2.0 ------
;bLength : 0x06 (6 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x02 (Format Type)
;bFormatType : 0x01 (FORMAT_TYPE_I)
;bSubslotSize : 0x04 (4 bytes)
;bBitResolution : 0x10 (16 bits)
; ----------------- Endpoint Descriptor -----------------
;bLength : 0x07 (7 bytes)
;bDescriptorType : 0x05 (Endpoint Descriptor)
;bEndpointAddress : 0x04 (Direction=OUT EndpointID=4)
;bmAttributes : 0x05 (TransferType=Isochronous SyncType=Asynchronous EndpointType=Data)
;wMaxPacketSize : 0x0190
; Bits 15..13 : 0x00 (reserved, must be zero)
; Bits 12..11 : 0x00 (0 additional transactions per microframe -> allows 1..1024 bytes per packet)
; Bits 10..0 : 0x190 (400 bytes per packet)
;bInterval : 0x01 (1 ms)
; ----------- Audio Data Endpoint Descriptor ------------
;bLength : 0x08 (8 bytes)
;bDescriptorType : 0x25 (Audio Endpoint Descriptor)
;bDescriptorSubtype : 0x01 (General)
;bmAttributes : 0x00
;bLockDelayUnits : 0x00
;wLockDelay : 0x0000
; ----------------- Endpoint Descriptor -----------------
;bLength : 0x07 (7 bytes)
;bDescriptorType : 0x05 (Endpoint Descriptor)
;bEndpointAddress : 0x82 (Direction=IN EndpointID=2)
;bmAttributes : 0x11 (TransferType=Isochronous SyncType=None EndpointType=Feedback)
;wMaxPacketSize : 0x0004
; Bits 15..13 : 0x00 (reserved, must be zero)
; Bits 12..11 : 0x00 (0 additional transactions per microframe -> allows 1..1024 bytes per packet)
; Bits 10..0 : 0x04 (4 bytes per packet)
;bInterval : 0x04 (4 ms)
以下はinterface1のalternate2である.24bitモードだ.
16bitモードとほとんど同じである.
24bit 4bytesと指定してあるので、24bit+8bitのゼロがPCM dataとして伝送される.
; ---------------- Interface Descriptor -----------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x04 (Interface Descriptor)
;bInterfaceNumber : 0x01
;bAlternateSetting : 0x02
;bNumEndpoints : 0x02 (2 Endpoints)
;bInterfaceClass : 0x01 (Audio)
;bInterfaceSubClass : 0x02 (Audio Streaming)
;bInterfaceProtocol : 0x20 (Device Protocol Version 2.0)
;iInterface : 0x00 (No String Descriptor)
中略
; ----- Audio Streaming Format Type Descriptor 2.0 ------
;bLength : 0x06 (6 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x02 (Format Type)
;bFormatType : 0x01 (FORMAT_TYPE_I)
;bSubslotSize : 0x04 (4 bytes)
;bBitResolution : 0x18 (24 bits)
以下略
以下はinterface1のalternate3である.32bitモードだ.
16bitモードとほとんど同じである.
32bit 4bytesと指定してある.
; ---------------- Interface Descriptor -----------------
;bLength : 0x09 (9 bytes)
;bDescriptorType : 0x04 (Interface Descriptor)
;bInterfaceNumber : 0x01
;bAlternateSetting : 0x03
;bNumEndpoints : 0x02 (2 Endpoints)
;bInterfaceClass : 0x01 (Audio)
;bInterfaceSubClass : 0x02 (Audio Streaming)
;bInterfaceProtocol : 0x20 (Device Protocol Version 2.0)
;iInterface : 0x00 (No String Descriptor)
中略
; ----- Audio Streaming Format Type Descriptor 2.0 ------
;bLength : 0x06 (6 bytes)
;bDescriptorType : 0x24 (Audio Interface Descriptor)
;bDescriptorSubtype : 0x02 (Format Type)
;bFormatType : 0x01 (FORMAT_TYPE_I)
;bSubslotSize : 0x04 (4 bytes)
;bBitResolution : 0x20 (32 bits)
以下略
HighSpeedのdescriptorはここまででおしまい.
HighSpeedConfigDscrEnd:
FullSpeedのdescriptorがここから始まる.
FullSpeedについてはほとんどメンテナンスしてないし動作確認もしてないので説明を省く.
FullSpeedConfigDscr:
中略
FullSpeedConfigDscrEnd:
以下のstring descriptorはword境界に配置されないと動かない.現状はコメントアウトしてあるが、word境界をkeepするために適宜コメントを外すこともある.
// db 00H ;; needed for word arign
// db 00H ;; needed for word arign
string descriptorは説明を省く.
StringDscr:
中略
StringDscrEnd:
サンプリング周波数の一覧である.Audio controlのclock source unitへのリクエストで、対応しているサンプリング周波数の一覧を要求されるのでここに記してある.
最初の2bytesは、8種類のサンプリング周波数があるという意味.
それ以降はリトルエンディアンの32bit整数である.最初の3つは数値はこうだ.
44H, 0acH, 00H, 00H, →0x0000ac44 = 44100Hz MAX
44H, 0acH, 00H, 00H, →0x0000ac44 = 44100Hz MIN
00H, 000H, 00H, 00H, →0x00000000 = 0Hz RES
44.1kHzを示しているのはわかるが、MAX/MIN/RESとは何なのか?
MAX~MINの間をRES分解能で設定できますという意味なのだが、RESOLUTIONがゼロなので44.1kHz決め打ちになっているのである.
それ以降のサンプリング周波数はこうだ.全部合わせて8種類である.
0x0000bb80 48kHz
0x00015888 88.2kHz
0x00017700 96kHz
0x0002b110 176.4kHz
0x0002ee00 192kHz
0x00056220 352.8kHz
0x0005dc00 384kHz
FsRangeDscr:
db 08H,00H,44H,acH,00H,00H,44H,acH,00H,00H,00H,00H,00H,00H,80H,bbH
db 00H,00H,80H,bbH,00H,00H,00H,00H,00H,00H,88H,58H,01H,00H,88H,58H
db 01H,00H,00H,00H,00H,00H,00H,77H,01H,00H,00H,77H,01H,00H,00H,00H
db 00H,00H,10H,b1H,02H,00H,10H,b1H,02H,00H,00H,00H,00H,00H,00H,eeH
db 02H,00H,00H,eeH,02H,00H,00H,00H,00H,00H,20H,62H,05H,00H,20H,62H
db 05H,00H,00H,00H,00H,00H,00H,dcH,05H,00H,00H,dcH,05H,00H,00H,00H
db 00H,00H
FsRangeDscrEnd:
【fw.c】
main()があるファイル.キモのところだけを抜粋.
void main(void) {
// Initialize user device
TD_Init(); EPの初期化
FPGAに渡すべき情報がある.16/24/32bitの切り替えと、サンプリング周波数の切り替えなどだ.PD[7:0]を使う.
OED = 0x7F; // PD7 input
IOD = 0;
LEDインジケータとしてPAを使う.
// SOF timer flash LED IO
OEA |= 0x80; // PA7 output
OEA |= 0x08; // PA3 output
OEA |= 0x40; // PA6 output 0100_0000
PA3=1;
PA6=1;
何をやっているのかは知らない.初期化.
EZUSB_InitI2C(); // Initialize EZ-USB I2C controller
USB系割り込みをオンにする.
USBIE |= bmSUDAV | bmSOF | bmSUTOK | bmSUSP | bmURES | bmHSGRANT;
feedbackのためSOF周期をFPGAでカウントする.FPGAはカウント完了割り込みしてくる.EZ-USBの割り込みポートはINT0.INT0のピンはPA0.negative edgeで割り込み.
// INT0, SOF measurement interrupt
PORTACFG |= 0x01; // PA0 for INT0
SYNCDELAY;
TCON |= 0x01; // INT0 negative Edge triggered
SYNCDELAY;
EX0 = 1; // enable INT0
SYNCDELAY;
FIFO emptyで自分自身のINT1に割り込みする.ピンはPA1.FIFO empyの個数をカウントする実験のため.
// INT1, FIFO empty interrupt
PORTACFG |= 0x02; // PA1 for INT1
SYNCDELAY;
TCON |= 0x04; // INT1 negative Edge triggered
SYNCDELAY;
EX1 = 0; // disable INT1 あとでONにする
SYNCDELAY;
EndPoint割り込みは全部OFF.
// EPx INTERRUPT
// EPIE[7:0]={EP8, EP6, EP4, EP2, EP1OUT, EP1IN, EP0OUT, EP0IN}
EPIE = 0x00;
SYNCDELAY;
EZUSB_xxxx()はCypressが作ったルーチンなので中でなにをやってるのか知らない.
EZUSB_IRQ_ENABLE(); // Enable USB interrupt (INT2)
EZUSB_ENABLE_RSMIRQ(); // Wake-up interrupt
最終的なinterrupt ON
EA = 1; // Enable 8051 interrupts
main()のwhile loopここから.
while(TRUE)
{
TD_Poll(); ポーリング処理はこのルーチンで行う
SUDというのはSetUpDataのことで、setup data割り込みでGotSUD=1にされる.setup dataがhostから届いたらSetupCommand()がコールされるようになっている.
if(GotSUD) // GotSUD is set by ISR_Sudav()
{
SetupCommand(); // Implement setup command
GotSUD = FALSE; // Clear SETUP flag
}
main()はここまで.
main()から飛んでくるsetup data処理ルーチン.
Cypressのsample codeをベースにしている.ただしそれは標準USBリクエストには対応しているものの、UAC2.0のリクエストには全く対応していないのでヒラサカが追記した.
Windowsのデバドラがどのようなリクエストを発行するのかをUSB protocol analyzerなどで調査して、必要な処理を追加した.
void SetupCommand(void)
{
setup dataはSETUPDAT[0]~[7]の8bytesである.この8bytesをUSB規格書から探すとどの様な処理が要求されているのかを理解することができる.
ここでは、SETUPDAT={2,1,0,0,0x86,0}となっている.意味はEP86をリセットしろだ.
// Clear feature endpoint 86
if(SETUPDAT[0]==2 && SETUPDAT[1]==1 && SETUPDAT[2]==0 && SETUPDAT[3]==0 && SETUPDAT[4]==0x86 && SETUPDAT[5]==0) {
// RESET FIFO
FIFORESET = 0x80;
SYNCDELAY;
FIFORESET = 0x86; // reset EP6
SYNCDELAY;
FIFORESET = 0x00;
SYNCDELAY;
INPKTEND = 0x86; // ARM FIFO
SYNCDELAY;
INPKTEND = 0x86;
SYNCDELAY;
}
SETUPDAT={0x21,1,0,1,0,0x12,4} これはサンプリング周波数をXXXにセットしろというリクエストである.0x12がdescriptorに記されたclock source IDを指定している.末尾の4はサンプリング周波数が4bytesの32bitの整数であるという意味になっている.4bytesはSETUPDAT[0-7]に含まれない.別に届くので明示的に読まなければならない.
// Set smpl freq
else if(SETUPDAT[0]==0x21 && SETUPDAT[1]==0x01 && SETUPDAT[2]==0 && SETUPDAT[3]==1 && SETUPDAT[4]==0 && SETUPDAT[5]==0x12 && SETUPDAT[6]==4) {
4bytesを読む.
EP0BCH = 0;
SYNCDELAY;
EP0BCL = 0; // Clear bytecount to allow new data recieve
SYNCDELAY;
while(EP0CS & bmEPBUSY);
4bytesを読み終わった.
32bit整数に変換してサンプリング周波数を得る.
FS = EP0BUF[0] + EP0BUF[1]*256 + EP0BUF[2]*65536 + EP0BUF[3]*16777216;
PD[4:3]にサンプリング周波数を出力する.PD[4:3]はFPGAへ配線されている.FPGAはclock分周比を切り替える.44.1kHz系はまだ実装してない.
if (FS==48000) { PD4=0; PD3=0; }
else if(FS==96000) { PD4=0; PD3=1; }
else if(FS==192000) { PD4=1; PD3=0; }
else if(FS==384000) { PD4=1; PD3=1; }
サンプリング周波数が変更されたのに伴い、念のためPCM streamを扱うEP4をクリアしておく.
FIFORESET = 0x80; // activate NAK-ALL
SYNCDELAY;
EP4FIFOCFG = 0x00; //switching to manual mode
SYNCDELAY;
FIFORESET = 0x84; // Reset FIFO 4
SYNCDELAY;
OUTPKTEND = 0X84; //OUTPKTEND done twice as EP4 is double buffered by default
SYNCDELAY;
OUTPKTEND = 0X84;
SYNCDELAY;
EP4FIFOCFG = 0x10; //switching to auto mode
SYNCDELAY;
FIFORESET = 0x00; //Release NAKALL
SYNCDELAY;
}
SETUPDAT={0x21,0x0a,0,0,2,0} これはHIDをidle状態にしとけの意味である.でもdescriptorからHIDを削除しちゃったので、今ではこのリクエストは来なくなっている.それで中身は何も無い.
// Set HID IDLE
else if(SETUPDAT[0]==0x21 && SETUPDAT[1]==0x0A && SETUPDAT[2]==0 && SETUPDAT[3]==0 && SETUPDAT[4]==2 && SETUPDAT[5]==0) {}
SETUPDAT={0x80,0,0,0,0,0,2,0} get statusの意味である.何のステータスだよと思うのだが、つつがなく過ごしていますの意味をこめてゼロを返しとく.末尾の{2,0}はリトルエンディアン16bitで2を表す.返信は2bytesでという意味である.
ここで返信のやり方がわかるだろう.すなわち、EP0BUFに返信データを積む.そして返信バイト数EP0BCH/EP0BCLにセットする.あとはEZ-USBのhardwareにお任せでよい.
// GET STATUS
else if(SETUPDAT[0]==0x80 && SETUPDAT[1]==0 && SETUPDAT[2]==0 && SETUPDAT[3]==0 && SETUPDAT[4]==0 && SETUPDAT[5]==0 && SETUPDAT[6]==2 && SETUPDAT[7]==0) {
EP0BUF[0] = 0; 返信するゼロの1byte目
EP0BUF[1] = 0; 返信するゼロの2byte目
SYNCDELAY;
EP0BCH = SETUPDAT[7]; 返信バイト数上位=0
SYNCDELAY;
EP0BCL = SETUPDAT[6]; 返信バイト数下位=2
SYNCDELAY;
}
SETUPDAT={0xa1,1,0,2,0,7,6} descriptorに記載されたoutput terminal ID=7へのリクエストである.質問は、出力端子に機器が接続されているか? である.そんな質問してきて誰得なんだろうと思うのだが、このリクエストは何度も飛んでくるんだ.不思議だなぁ.末尾の6は6bytesの返信を要求されている.返信内容は、下記に示されるように2chでFrontL,FrontRだよである.
// Get TE_CONNECTOR_CONTROL of output terminal in the interface0 --> UAC2.0 5.2.5.4.2
else if(SETUPDAT[0]==0xA1 && SETUPDAT[1]==1 && SETUPDAT[2]==0 && SETUPDAT[3]==2 && SETUPDAT[4]==0 && SETUPDAT[5]==7 && SETUPDAT[6]==6) {
EP0BUF[0] = 2; // channel number
EP0BUF[1] = 3; // front L and front R
EP0BUF[2] = 0;
EP0BUF[3] = 0;
EP0BUF[4] = 0;
EP0BUF[5] = 0;
SYNCDELAY;
EP0BCH = SETUPDAT[7]; 返信バイト数上位=0
SYNCDELAY;
EP0BCL = SETUPDAT[6]; 返信バイト数下位=6
SYNCDELAY;
}
SETUPDAT={0xa1,1,0,2,0,0x12,1} clock source ID=0x12のサンプリング周波数が適切なのかどうかが問われている.1byteで返信することが求められている.TRUEを返しとく.
// Get smpl freq valid
else if(SETUPDAT[0]==0xA1 && SETUPDAT[1]==0x01 && SETUPDAT[2]==0 && SETUPDAT[3]==2 && SETUPDAT[4]==0 && SETUPDAT[5]==0x12 && SETUPDAT[6]==1) {
EP0BUF[0] = TRUE; // TRUE
EP0BUF[1] = 0x00;
SYNCDELAY;
EP0BCH = SETUPDAT[7]; 返信バイト数上位=0
SYNCDELAY;
EP0BCL = SETUPDAT[6]; 返信バイト数下位=1
SYNCDELAY;
}
SETUPDAT={0xa1,1,0,1,0,0x12,4} 現状のサンプリング周波数を返信せよと要求されている.返信は32bit整数で.0x12はclock source IDを表す.4は返信4bytesを表す.
// Get current smpl freq
else if(SETUPDAT[0]==0xA1 && SETUPDAT[1]==0x01 && SETUPDAT[2]==0 && SETUPDAT[3]==1 && SETUPDAT[4]==0 && SETUPDAT[5]==0x12 && SETUPDAT[6]==4) {
EP0BUF[0] = FS % 256;
EP0BUF[1] = FS / 256;
EP0BUF[2] = FS / 65536;
EP0BUF[3] = FS / 16777216;
SYNCDELAY;
EP0BCH = SETUPDAT[7];
SYNCDELAY;
EP0BCL = SETUPDAT[6];
SYNCDELAY;
}
SETUPDAT={0xa1,1,0,1,0,0x0d,1} MUTEせよ要求.descriptorでmute機能を殺してしまったのでもうこのリクエストは来ないけどね.1bytes返信を求められているのでゼロを返しておく.ゼロを返すのが正しいのかどうかは知らない.
// Audio class MUTE ctl
else if(SETUPDAT[0]==0xA1 && SETUPDAT[1]==0x01 && SETUPDAT[2]==0 && SETUPDAT[3]==1 && SETUPDAT[4]==0 && SETUPDAT[5]==0x0D && SETUPDAT[6]==1) {
EP0BUF[0]=0;
SYNCDELAY;
EP0BCH=0;
SYNCDELAY;
EP0BCL=1;
SYNCDELAY;
}
SETUPDAT={0xa1,2,0,1,0,0x12} サンプル周波数の全リストを返信せよという要求.descriptorファイルの末尾にあるFsRangeDscrのデータを返信する.フォーマットはdescriptorを参照のこと.
返信のやり方その2がここにある.このやり方ではEP0BUFを操作していない.その代わりに、SUDPTRH/SUPPTRLに送信データの所在するpointerをセットするのだ.返信バイト数はSETUPDAT[7:6]が自動的に参照される.
// Get smpl freq range
else if(SETUPDAT[0]==0xA1 && SETUPDAT[1]==0x02 && SETUPDAT[2]==0 && SETUPDAT[3]==1 && SETUPDAT[4]==0 && SETUPDAT[5]==0x12 ) {
dscr_ptr = (void*)pFsRangeDscr;
SUDPTRH = MSB(dscr_ptr);
SYNCDELAY;
SUDPTRL = LSB(dscr_ptr);
SYNCDELAY;
}
なお、hostからのリクエストを注意深く観察すると、このリクエストが連続して2度発行されるケースがある.その意図を知っておくとよい.
1度目:返信2byteが要求される.FsRangeDscrの先頭2bytesには、何種類のサンプル周波数に対応しているかの数値が記載されている.ここでは8種類である.
2度目:hostは8種類と知ると全部で2+8*4*3=98bytesのサンプル周波数テーブルが在るのだと知る.そこで2度目は98bytesの返信を要求する.
1度目も2度目も、EZ-USBの操作は上の通りで構わないのである.返信バイト数は自動的にセットされるからだ.
さらにいうと、hostのdriverによっては、1度目でイキナリ256bytes返信をしろとアバウトな要求で済まされてしまい、2度目が無いケースもある.windows10のusbaudio2.dllはそういう挙動に見える.(version upで変わるかもだが)
ここから下にもcodeはあるが、cypressのsample codeなので説明は割愛する.
【bulkloop.c】
初期化ルーチン、ポーリングルーチン、割り込みルーチン、などが記載されている.
EPの初期設定はここで行われている.
void TD_Init(void) // Called once at startup
{
FIFO clockなどの設定.
// set the slave FIFO mode & interface to external clock
// [7] 1:IFCLK internal 0:IFCLK external
// [6] 1:48MHz 0:30MHz
// [5] 1:IFCLK output 0:not output
// [4] IFCLK polality
// [3] 0:synchronous 1:asynchronous
// [2] 0 GSTATE off
// [1:0] 11 slave FIFO
IFCONFIG = 0x0b; // 0000_1011
SYNCDELAY;
EPの設定.
EZ-USB FX2LPには、EP0,1,2,4,6,8がある.EP0,1はFIFOサイズが64byteしかないし、リクエスト制御に頻繁に使われるのでuserには使い勝手が悪い.userが使えるのは2,4,6,8だと思っていい.
このDCCでは次のようにEPをアサインしている.
EP2 feedback
EP4 PCM stream
EP6 audio control
EP8 未使用
各EPのFIFOサイズは、512bytesを2本である.
// EPx configuration
// [7]ON
// [6]DIR 0:out 1:in
// [5:4]TYPE 01:ISO 10:BULK 11:INT
// [3]SIZE 0:512 1:1024
// [2]
// [1:0]FIFO個数 00:4 10:2 11:3
EP1OUTCFG = 0xB0; // 1011_0000 interrupt
SYNCDELAY;
EP1INCFG = 0xB0; // 1011_0000 interrupt
SYNCDELAY;
EP2CFG = 0xD2; // 1101_0x10 in Isochronous 512byte double, feedback
SYNCDELAY;
EP4CFG = 0x92; // 1001_0x10 out Isochronous 512BYTE double, audio stream
SYNCDELAY;
EP6CFG = 0xF2; // 1111_0x10 in interrupt 512byte double, control
SYNCDELAY;
EP8CFG = 0xF2; // 1111_0x10 in interrupt 512byte double, HID
SYNCDELAY;
EP2,4,6,8をリセットする.
// RESET FIFO
FIFORESET = 0x80;
SYNCDELAY;
FIFORESET = 0x82; // reset EP2
SYNCDELAY;
FIFORESET = 0x84; // reset EP4
SYNCDELAY;
FIFORESET = 0x86; // reset EP6
SYNCDELAY;
FIFORESET = 0x88; // reset EP8
SYNCDELAY;
FIFORESET = 0x00;
SYNCDELAY;
EP2,6,8はIN方向である.512byte満杯になったら自動的にhostへ送信する機能をONする.このDCCではIN方向のデータ量が少ないのでこの機能が動く場面は無い.
// EPx AUTOLENGTH Auto-commit 512-byte packets
EP2AUTOINLENH = 0x02;
SYNCDELAY;
EP2AUTOINLENL = 0x00;
SYNCDELAY;
EP6AUTOINLENH = 0x02;
SYNCDELAY;
EP6AUTOINLENL = 0x00;
SYNCDELAY;
EP8AUTOINLENH = 0x02;
SYNCDELAY;
EP8AUTOINLENL = 0x00;
SYNCDELAY;
EP4はOUT方向である.FIFOを空っぽにする.
// ARM FIFO EP4
OUTPKTEND = 0x84;
SYNCDELAY;
OUTPKTEND = 0x84;
SYNCDELAY;
EP2,6,8を空っぽにする.
// ARM FIFO
INPKTEND = 0x82;
SYNCDELAY;
INPKTEND = 0x82;
SYNCDELAY;
INPKTEND = 0x86;
SYNCDELAY;
INPKTEND = 0x86;
SYNCDELAY;
INPKTEND = 0x88;
SYNCDELAY;
INPKTEND = 0x88;
SYNCDELAY;
EP1,4を動かす.
// arm EPOUT
EP1OUTBC = 0x40; // arm EP1 OUT by writing to the byte count
SYNCDELAY;
EP4BCL = 0x80; // arm EP2 OUT by writing byte count w/skip.
SYNCDELAY;
EP4をAUTOOUTにする.AUTOOUTとは、hostから届いたデータがFIFOに積まれたらCPUの介在なしに外部へデータ出力される機能である.
EP2,6,8はAUTOINにしないので、FIFOにデータを積むのはCPUの仕事になる.
// EPx FIFO CONFIG
// [7]
// [6] FULL FLAG
// [5] EMPTY FLAG
// [4] AUTOOUT 1:No CPU involvement
// [3] AUTOIN
// [2] ZEROLENIN
// [1]
// [0] WORDWIDE 0:8bit PB[7:0]
EP2FIFOCFG = 0x00; // [3]AUTOIN=0, [2]ZEROLEN=0
SYNCDELAY;
EP4FIFOCFG = 0x10; // [4]AUTOOUT=1, [2]ZEROLEN=0
SYNCDELAY;
EP6FIFOCFG = 0x00; // [3]AUTOIN=0, [2]ZEROLEN=0
SYNCDELAY;
EP8FIFOCFG = 0x00; // [3]AUTOIN=0, [2]ZEROLEN=0
SYNCDELAY;
}
EP初期化はここまで.
ポーリング処理のルーチンここから.
void TD_Poll(void){
feedback機能.FPGAに1024Fsで8つのSOF周期をカウントさせるように別途作ってある.0xC000ぐらいの値になる.
(49.152MHz x 125uSec = 6144、6144x8=49152=0xC000)
それをEZ-USBのPD7,PD5を通してFPGAからシリアルで読む.SOFvalueに格納する.
SOFcmplは、FPGAが発するINT0割り込み処理ルーチンで1にセットされる.
BOOL x;
if(SOFcmpl==1){
// read SOF value from FPGA
SOFvalue=0;
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 32768 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 16384 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 8192 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 4096 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 2048 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 1024 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 512 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 256 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 128 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 64 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 32 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 16 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 8 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 4 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 2 : 0);
PD5=1; x=PD7; PD5=0; SOFvalue += (x ? 1 : 0);
SOFvalueをEP2 feedbackを通してhostへ送る.4bytesである.
しかし4bytesの位取りがよくわからない.
CM6631やXMOSでは0x00180000が送信されているのは判っている.
そこで、C000を1800にするために以下のbit shiftにしてある.でもこれが正しいのかどうかはわかっていない.
if( (EP2CS & 4) == 4 ){ // EP2 empty
EP2FIFOBUF[0] = (BYTE)( (SOFvalue << 5 ) & 0xFF); // SOFvalue[2:0]
EP2FIFOBUF[1] = (BYTE)( (SOFvalue >> 3 ) & 0xFF); // SOFvalue[10:3]
EP2FIFOBUF[2] = (BYTE)( (SOFvalue >> 11) & 0xFF); // SOFvalue[15:11]
EP2FIFOBUF[3] =0;
SYNCDELAY;
EP2BCH = 0;
SYNCDELAY;
EP2BCL = 4;
SYNCDELAY;
}
SOFcmpl=0;
}
SOFvalueをI2C経由でArduinoに送信してモニタする機能.ArduinoはI2CをCOM port経由でPCのterminalに表示する.DCCに必須の機能ではない.
if(sof_print_flag){
PA6=~PA6;
I2C_printnum2(12, SOFvalue>>8);
I2C_printnum2(12, SOFvalue&0xff);
EZUSB_WriteI2C_str2(12, (BYTE xdata*)" FIFOempty= ");
I2C_printnum2(12, FIFOempty>>8);
I2C_printnum2(12, FIFOempty&0xff);
if(FIFOempty==0) EZUSB_WriteI2C_str2(12, (BYTE xdata*)" @@@@");
EZUSB_WriteI2C_str2(12, (BYTE xdata*)"\n");
FIFOempty=0;
sof_print_flag=FALSE;
//SOFvalue++;
}
}
SetupCommand()からこれがコールされる.set interfaceリクエスト.
やっていることは、まずalternateのセット{0,1,2,3}.
alternate0は音声再生しないで止まっている状態.
alternate1は16bit.
alternate2は24bit.
alternate3は32bit.
PD[2:1]に{0,1,2,3}を出力する.PD[2:1]はFPGAへ与えられ、FPGAは32,24,16bitによって処理内容を変える.
EX1はINT1のenable.
bit数を変えたので念のためEP4をリセットする.
BOOL DR_SetInterface(void){
AlternateSetting = SETUPDAT[2];
switch(AlternateSetting){
case(0): PD2=0; PD1=0; EX1=0; SYNCDELAY; break;
case(1): PD2=0; PD1=1; EX1=1; SYNCDELAY; break;
case(2): PD2=1; PD1=0; EX1=1; SYNCDELAY; break;
case(3): PD2=1; PD1=1; EX1=1; SYNCDELAY; break;
default: PD2=0; PD1=0; EX1=0; SYNCDELAY;
}
FIFORESET = 0x80; // activate NAK-ALL
SYNCDELAY;
EP4FIFOCFG = 0x00; //switching to manual mode
SYNCDELAY;
FIFORESET = 0x84; // Reset FIFO 4
SYNCDELAY;
OUTPKTEND = 0X84; //OUTPKTEND
SYNCDELAY;
OUTPKTEND = 0X84;
SYNCDELAY;
EP4FIFOCFG = 0x10; //switching to auto mode
SYNCDELAY;
FIFORESET = 0x00; //Release NAKALL
SYNCDELAY;
return(TRUE); // Handled by user code
}
ここから下は割り込み処理ルーチン.
EZ-USB FX2LPの割り込みにはこの13種類がある.
なのだが、8番と10番はさらに多種に別れていて多様な割り込みに対応する.
//--------------------------------------------------
// Interrupt Handlers
#define INT0_VECT 0 // 0x03 INT0
#define TMR0_VECT 1 // 0x0B
#define INT1_VECT 2 // 0x13 INT1
#define TMR1_VECT 3 // 0x1B
#define COM0_VECT 4 // 0x23
#define TMR2_VECT 5 // 0x2B
#define WKUP_VECT 6 // 0x33
#define COM1_VECT 7 // 0x3B
#define USB_VECT 8 // 0x43 INT2 USB
#define I2C_VECT 9 // 0x4B INT3
#define INT4_VECT 10 // 0x53 INT4 GPIF
#define INT5_VECT 11 // 0x5B INT5
#define INT6_VECT 12 // 0x63 INT6
//--------------------------------------------------
INT0割り込み処理.FPGAが8つのSOF周期をカウント完了したらINT0に割り込んでくるように作った.
void ISR_INT0(void) interrupt 0{ // when SOF count complete, comes here
SOFcmpl=1; // completed SOF measurement
TCON &= 0xFD; // Clear INT0 TCON.1 Flag
INT0cnt++;
if(INT0cnt==200) {PA7=~PA7; INT0cnt=0; } LEDチカチカ
}
INT1割り込み処理.FIFO emptyで割り込んでくる.empty発生個数をカウント.
void ISR_INT1(void) interrupt 2{ // when FIFOempty, comes here
FIFOempty++; // count up FIFO empty
TCON &= 0xF7; // Clear INT1 TCON.3 Flag
FIFOemptyLEDcnt=0;
PA3=1; // LED on LEDチカチカ
}
setupdataが来たら割り込み.
void ISR_Sudav(void) interrupt USB_VECT{
GotSUD = TRUE; // Set flag
EZUSB_IRQ_CLEAR();
USBIRQ = bmSUDAV; // Clear SUDAV IRQ
}
SOF割り込み.SOF周期(125uSec)は別名micro frameとも呼ばれ、host PCが周期的に発するpacketの周期である.
DCC機能的に重要なのは、PD6をtoggleしているところで、PD6をFPGAに与え、FPGAはPD6周期をカウントしている.その値をfeedbackを介してhost PCに送信する.
それ以外にはLEDチカチカやI2C送信などのタイマーとして使っている.
void ISR_Sof(void) interrupt USB_VECT{
PD6 = ~PD6; // SOF indicator
if(AlternateSetting==0) {
sof_print_flag=FALSE;
sof_timer=0;
}
else if(sof_timer==40000) { // 5Sec timer
//PA7=~PA7;
sof_print_flag = TRUE;
sof_timer=0;
}
else sof_timer++;
FIFOemptyLEDcnt++;
if(FIFOemptyLEDcnt==8000){ FIFOemptyLEDcnt=0; PA3=0; } // LED off
EZUSB_IRQ_CLEAR();
USBIRQ = bmSOF; // Clear SOF IRQ
}
以下の割り込みルーチンはCypressのsample codeなので説明を略す.
【USBJmpTb.a51】
これは割り込みvectorが記述されている.
冒頭のこれらの宣言は、割り込み処理ルーチンの全リストである.bulkloop.cに本体が記述されている.
ほとんどがCypressによるsample codeなのだが、ISR_INT0とISR_INT1はヒラサカが追加したルーチンである.
extrn code (ISR_INT0, ISR_INT1)
extrn code (ISR_Sudav, ISR_Sof, ISR_Sutok, ISR_Susp, ISR_Ures, ISR_Highspeed, ISR_Ep0ack, ISR_Stub, ISR_Ep0in, ISR_Ep0out, ISR_Ep1in, ISR_Ep1out, ISR_Ep2inout, ISR_Ep4inout, ISR_Ep6inout, ISR_Ep8inout, ISR_Ibn)
extrn code (ISR_Ep0pingnak, ISR_Ep1pingnak, ISR_Ep2pingnak, ISR_Ep4pingnak, ISR_Ep6pingnak, ISR_Ep8pingnak, ISR_Errorlimit, ISR_Ep2piderror, ISR_Ep4piderror, ISR_Ep6piderror, ISR_Ep8piderror, ISR_Ep2pflag)
extrn code (ISR_Ep4pflag, ISR_Ep6pflag, ISR_Ep8pflag, ISR_Ep2eflag, ISR_Ep4eflag, ISR_Ep6eflag, ISR_Ep8eflag, ISR_Ep2fflag, ISR_Ep4fflag, ISR_Ep6fflag, ISR_Ep8fflag, ISR_GpifComplete, ISR_GpifWaveform)
ISR_INT0とISR_INT1はヒラサカが追加したルーチンなのだから、vectorにも追加する必要があるだろう.
どこにどのように追加したらよいのだろうか?
fx2.hの中に定義されているこの部分がわかりやすい.
INT0のvectorは3番地、INT1のvectorは0x13番地である.
#define INT0_VECT 0 // 0x03 INT0
#define TMR0_VECT 1 // 0x0B
#define INT1_VECT 2 // 0x13 INT1
#define TMR1_VECT 3 // 0x1B
#define COM0_VECT 4 // 0x23
#define TMR2_VECT 5 // 0x2B
#define WKUP_VECT 6 // 0x33
#define COM1_VECT 7 // 0x3B
#define USB_VECT 8 // 0x43 INT2 USB
#define I2C_VECT 9 // 0x4B INT3
#define INT4_VECT 10 // 0x53 INT4 GPIF
#define INT5_VECT 11 // 0x5B INT5
#define INT6_VECT 12 // 0x63 INT6
これがINT0,INT1のために追加したところである.割り込みvectorに記述されているのはLJMP命令そのものだ.すぐ下に43番地と53番地とあるのはUSB関連のたくさんある割り込みルーチンに対応するものだ.
CSEG AT 03H
ljmp ISR_INT0 ; INT0 goes to ISR_INT0
CSEG AT 13H
ljmp ISR_INT1 ; INT1 goes to ISR_INT1
CSEG AT 43H
USB_Int2AutoVector equ $ + 2 ; 43+2=45
ljmp USB_Jump_Table ; INT2 goes to USB_jump_table
CSEG AT 53H
USB_Int4AutoVector equ $ + 2
ljmp USB_Jump_Table ; INT4 goes to USB_jump_table
以下は省略.
【i2c_rw.c】
setupdataをI2Cへ出力するルーチン.基本的にCypressのsample codeだが、一部ヒラサカが追加した.
BYTE hex2char( BYTE x ){
if(x<10) return('0'+x);
else return('A'+x-10);
}
void I2C_printnum(BYTE addr, BYTE x){
BYTE xdata c[3];
c[0] = hex2char(x>>4);
c[1] = hex2char(x&0xf);
c[2] = ' ';
EZUSB_WriteI2C(addr, 3, c);
}
void I2C_printnum2(BYTE addr, BYTE x){
BYTE xdata c[2];
c[0] = hex2char(x>>4);
c[1] = hex2char(x&0xf);
EZUSB_WriteI2C(addr, 2, c);
}
void I2C_printSUD(BYTE addr, BYTE n){
int i;
EZUSB_WriteI2C_str2(addr, (BYTE xdata*)"#");
for(i=0;i<8;i++) I2C_printnum(addr, SETUPDAT[i]);
for(i=0;i<n;i++) I2C_printnum(addr, EP0BUF[i]);
EZUSB_WriteI2C_str2(addr, (BYTE xdata*)"\n");
}
【②FPGA verilog code】
top.vというファイルひとつだけである.
主な仕事は、EZ-USBのFIFOから読みだしてI2Sに変換することである.FIFOに出てくる音声データはリトルエンディアンなのだが、I2Sはビッグエンディアンなので並び替えもやらなくちゃいけない.
音声モードは、16,24,32bit、44.1~384kHzに対応するので、clock切り替えなどの操作が必要になる.
ただし、実機動作確認したのは32bit 384kHzだけである.
もう一つの仕事として、SOF間隔を1024Fsでカウントする仕事もさせている.
途中で投げ出したcodeなので汚い.シーケンサで書き換えたいとも思うが放置した.
現物で使ったFPGAはXILINX Spartan6であるが、そんな巨大なデバイスは不要だ.
まずはclock系の理解のため、このチャートで分周比を考える.
源発信はxtalの49.152MHzをつかう.48kHz系の1024Fsが49.152MHzだ.(44.1kHz系は45.1584MHzのxtalを使う)
49.152MHzを10bit分周する.
MCKはDACへ渡すclockである.[0]を使って24.576MHz固定.
FIFOCKはEZ-USBのIFCLKへ接続する.[0]を使って24.576MHz固定.
BCKはI2Sのbit clockであり、64Fsである.LchRch各々32bitなので合計64bitという理由だ.それゆえ、サンプリング周波数に応じて切り替える必要がある.384kHzのときは[0]24.576MHz、192kHzのときは[1]12.288MHzなどという具合に.
SLRDはFIFOのread enableである.FIFOは8bit busで運用する.4bytesの2ch分を毎度読みだすのが仕事なので8Fsとする.384kHzのときは[3]3.072MHz、192kHzのときは[4]1.536MHzなどとなる.
LRCKはI2Sのサンプリング周波数である.384kHzのときは[6]384kHz、192kHzのときは[7]192kHzなどとする.
// 49152000
//[0] 24576000 oBCK MCLK FIFOCK
//[1] 12288000 *
//[2] 6144000 x
//[3] 3072000 o oSLRD
//[4] 1536000 *
//[5] 768000 x
//[6] 384000 o oLRCK
//[7] 192000 *
//[8] 96000 x
//[9] 48000 o
IO定義
module top(
input xtal_i, // xtal 49MHz
EZ-USBから受け取る制御信号
input [1:0] bitsel_i, // 0:off 1:16 2:24 3:32
input [1:0] fssel_i, // 0:48k 1:96k 2:192k 3:384k
input [7:0] fifodata_i, // FIFO data bus
input xfifoempty_i, // FIFO empty flag
SOFカウントのための信号
input sof_i, // from ez-usb, sof cycle 4kHz
input sof_ck, // from ez-usb, read clock of sof measurement
output sof_int_o, // to ez-usb, interrupt, sof measurement value ready
output sof_o, // to ez-usb, SOF measure value serial output
output sof_timeout, // SOF serial timeout
音声データのためのclockやI2S信号
output mck_o, // 24MHz fix, musked by fifoempty
output bck_o, // not musked
output fifock_o, // 24MHz fix, not musked
output xslrd_o, // FIFO read enable BCK/8
output lrck_o, // LRCK BCK/64
output data_o,
input xrst,
input xslrd_i, // loopback of xslrd_o
インジケータ類
output led_1,
output led_2,
input key_1, key_2, sw1,
output fs384_o, fs192_o, fs96_o, fs48_o, // LED
output bit32_o, bit24_o, bit16_o // LED
);
assign led_1 = 0;
assign led_2 = 0;
wire fifoempty = ~xfifoempty_i;
EZ-USBから受け取るFsセレクト及びbitセレクト.
sleepについてはひと手間かけてある.bitが変わったら一瞬sleepに入れる、Fsが変わったら一瞬sleepに入れる、ようにしている.リセットのためだ.
assign fs384 = fssel_r==3;
assign fs192 = fssel_r==2;
assign fs96 = fssel_r==1;
assign fs48 = fssel_r==0;
assign bit32 = bitsel_r==3; // alt3
assign bit24 = bitsel_r==2; // alt2
assign bit16 = bitsel_r==1; // alt1
assign sleep = bitsel_r==0 || // alt0
(bitsel_rr!=bitsel_r) || // change alt
(fssel_rr!=fssel_r); // change fs
モニタのためにLEDを切り替えてるだけなので無視してかまわない.
assign fs384_o = sw1 ? 0 : fs384;
assign fs192_o = sw1 ? 0 : fs192;
assign fs96_o = sw1 ? fssel_i[1] : fs96;
assign fs48_o = sw1 ? fssel_i[0] : fs48;
assign bit32_o = sw1 ? 0 : bit32; // alt3
assign bit24_o = sw1 ? bitsel_i[1] : bit24; // alt2
assign bit16_o = sw1 ? bitsel_i[0] : bit16; // alt1
fsselとbitselの取り込み.
reg [1:0] fssel_r, fssel_rr; // 48k 96k 192k 384k
always@(posedge xtal_i or negedge xrst)
if(!xrst) {fssel_rr,fssel_r} <= 3;
else {fssel_rr,fssel_r} <= {fssel_r,fssel_i};
reg [1:0] bitsel_r, bitsel_rr; // 32bir 24bit 16bit, sleep
always@(posedge xtal_i or negedge xrst)
if(!xrst) {bitsel_rr,bitsel_r} <= 0;
else {bitsel_rr,bitsel_r} <= {bitsel_r,bitsel_i};
I2SとFIFOの信号を発生するキモのところ.
基本的にはFsで切り替えているだけである.
ただし、10分周カウンタにsleepでリセットをかけている.また、FIFO emptyでpauseさせている.これらは、byteズレを防止するために必須である.
reg [9:0] div1; // 0-1023 sequencer
always@(posedge xtal_i or negedge xrst)
if(!xrst) div1 <= 0;
else if(sleep) div1<=10'b11_1111_1111; // restart
else if(fifoempty) ; // paused
else div1 <= div1+1;
assign bck= fs48 ? div1[3] :
fs96 ? div1[2] :
fs192 ? div1[1] : div1[0];
assign bck_en= fs48 ? div1[3:0]==1 :
fs96 ? div1[2:0]==1 :
fs192 ? div1[1:0]==1 : div1[0];
assign slrd= fs48 ? div1[6:1]==0 :
fs96 ? div1[5:1]==0 :
fs192 ? div1[4:1]==0 : div1[3:1]==0;
assign lrck = fs48 ? div1[9] :
fs96 ? div1[8] :
fs192 ? div1[7] : div1[6] ;
音声PCMデータが、USBはリトルエンディアン、I2Sはビッグエンディアンなので並び替えのための信号を作っておく.後で使う.
wire [1:0] byte_cnt;
assign byte_cnt= fs48 ? div1[8:7] :
fs96 ? div1[7:6] :
fs192 ? div1[6:5] : div1[5:4];
AK4495のmckを作る.
24.576MHz固定でよいのだが、sleepで止める.AK4495はmckが停まるとパワーセーブになるのだ.
ただし、mckをFIFO emptyでpauseさせるべきではない.ノイズを出させないため.
なおAK4495 datsheetによるとbckとmckの位相に縛りは無いので、別途此処でmckを生成させても不都合は生じない.
reg mck_r;
always@(posedge xtal_i or negedge xrst)
if(!xrst) mck_r <= 0;
else if(sleep) mck_r <= 0;
else mck_r <= ~mck_r;
FIFO clockをつくる.
24.576MHz固定でよいのだが、これは止めてはだめ.止めるとFIFOが狂う.
なお、FIFO clockはEZ-USB内部の48MHzでも構わないはずである.ここでは同期読み出しはやってないからだ.(=非同期読み出しでFIFOアクセスしている)
reg fifock_r;
always@(posedge xtal_i or negedge xrst)
if(!xrst) fifock_r <= 0;
else fifock_r <= ~fifock_r;
bckのタイミング合わせ.2つ遅れにさせているのはFIFO emptyから復帰したときのグリッジを防ぐため.
reg bck_r, bck_rr;
always@(posedge xtal_i) {bck_rr,bck_r} <= {bck_r,bck};
FIFO read strobe作成
reg xslrd_r;
always@(posedge xtal_i)
if(!xrst) xslrd_r <= 0;
else if(bck_en) xslrd_r <= ~slrd;
LRCK作成.リトルエンディアン→ビッグエンディアンの変換とタイミングを合わせてもいる.
reg lrck_r,lrck_rr;
always@(posedge xtal_i)
if(!xrst) {lrck_rr,lrck_r} <= 0;
else if(bck_en) {lrck_rr,lrck_r} <= {lrck_r,~lrck};
リトルエンディアン→ビッグエンディアン変換
16,24,32bit modeに関わらず4bytesで音声streamが来るようdescriptorに記述した.
なので、bitに関わらず4bytesのエンディアン変換をすればよい.
reg [31:0] fifodataL_r, fifodataR_r;
always@(posedge xtal_i)
if(!xrst) {fifodataL_r,fifodataR_r}<=0;
else if(bck_en) begin
if(xslrd_i==0 && lrck==0) begin
if (byte_cnt==0) fifodataL_r[7:0] <= fifodata_i;
else if(byte_cnt==1) fifodataL_r[15:8] <= fifodata_i;
else if(byte_cnt==2) fifodataL_r[23:16] <= fifodata_i;
else if(byte_cnt==3) fifodataL_r[31:24] <= fifodata_i;
end
if(xslrd_i==0 && lrck==1) begin
if (byte_cnt==0) fifodataR_r[7:0] <= fifodata_i;
else if(byte_cnt==1) fifodataR_r[15:8] <= fifodata_i;
else if(byte_cnt==2) fifodataR_r[23:16] <= fifodata_i;
else if(byte_cnt==3) fifodataR_r[31:24] <= fifodata_i;
end
if(lrck_rr==0) fifodataL_r <= {fifodataL_r[30:0],1'b0};
if(lrck_rr==1) fifodataR_r <= {fifodataR_r[30:0],1'b0};
end
出力データアサイン
assign data_o = lrck_rr ? fifodataR_r[31] : fifodataL_r[31];
assign fifock_o = fifock_r; // FIFO synchronization clock
assign xslrd_o = xslrd_r; // FIFO RD
assign mck_o = mck_r; // 24MHz fix
assign bck_o = bck_rr; // I2S
assign lrck_o = lrck_r; // I2S
ここから下はSOF間隔を49MHzでカウントする回路.
測定結果をEZ-USBへ渡すには、sof_int_oでEZ-USBに割り込みをかける.その後にsof_ckとsof_oでシリアル出力する.
説明は略す.
【④Arduino】
I2C_EZUSB.ino である.
EZ-USBから、8bytesのsetup dataと、4bytesのEP0 dataがASCII形式でI2C経由で到来する.その番号を解析して文字列をCOM PORTへ出力するのが役割.
ここから下はs[]を解析するcodeが続く.
void analyze() {
if(s[0]==0 && s[1]==9 && s[2]==1 && s[3]==0 && s[4]==0) Serial.println("standard SET_CONFIG");
中略
I2CとCOMの初期化.I2Cアドレスは12番.
void setup() {
Wire.begin(12); // join i2c bus
Wire.onReceive(isr_i2c_slave);
Serial.begin(115200); // start serial for output
Serial.print("starting USB setup monitor\n");
n=0;
}
I2Cを受信したら割り込みがかかる.
void isr_i2c_slave() {
while (Wire.available())
{
char t;
t = Wire.read(); // receive a byte as character
asc[n++]=t;
if(n<MAXBUF){ ; }
else { n=0; t=0; }
以下略.
EZ-USB DCC起動時のsetup log.
起動した時点ではalternative0になっているのでFPGAやDACはsleep状態となっている.
先頭8bytesがsetup dataである.後続の4bytesはEP0BUFの先頭4bytesである.
80 06 00 01 00 00 40 00 |02 03 00 00 standard GET_DESCRIPTOR DEVICE
80 06 00 01 00 00 12 00 |02 03 00 00 standard GET_DESCRIPTOR DEVICE
80 06 00 02 00 00 FF 00 |02 03 00 00 standard GET_DESCRIPTOR CONFIG
80 06 00 02 00 00 09 01 |02 03 00 00 standard GET_DESCRIPTOR CONFIG
80 06 00 03 00 00 FF 00 |02 03 00 00 standard GET_DESCRIPTOR STRING(0)
80 06 02 03 09 04 FF 00 |02 03 00 00 standard GET_DESCRIPTOR STRING(2)
80 06 00 01 00 00 12 00 |02 03 00 00 standard GET_DESCRIPTOR DEVICE
80 06 00 02 00 00 09 00 |02 03 00 00 standard GET_DESCRIPTOR CONFIG
80 06 00 02 00 00 09 01 |02 03 00 00 standard GET_DESCRIPTOR CONFIG
00 09 01 00 00 00 00 00 |02 03 00 00 standard SET_CONFIG
01 0B 00 00 01 00 00 00 |02 03 00 00 standard SET_INTERFACE interface(1) alt(0)
80 06 02 03 09 04 04 00 |02 03 00 00 standard GET_DESCRIPTOR STRING(2)
80 06 02 03 09 04 16 00 |02 03 00 00 standard GET_DESCRIPTOR STRING(2)
80 00 00 00 00 00 02 00 |00 00 00 00 standard GET_STATUS
80 06 00 03 00 00 04 00 |00 00 00 00 standard GET_DESCRIPTOR STRING(0)
80 06 02 03 09 04 02 00 |00 00 00 00 standard GET_DESCRIPTOR STRING(2)
80 06 02 03 09 04 16 00 |00 00 00 00 standard GET_DESCRIPTOR STRING(2)
A1 02 00 01 00 12 00 01 |00 00 00 00 Audio class GET_RANGE interface(0) clock source(18)
80 06 04 03 09 04 02 00 |00 00 00 00 standard GET_DESCRIPTOR STRING(4)
80 06 04 03 09 04 10 00 |00 00 00 00 standard GET_DESCRIPTOR STRING(4)
02 01 00 00 86 00 00 00 |00 00 00 00 standard CLEAR_FEATURE EP86
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
A1 01 00 02 00 07 06 00 |02 03 00 00 Get TE_CONNECTOR_CONTROL of output terminal
PCで音声再生アプリをPLAYにしたときのlog.
サンプリング周波数が0x05dc00=384000にセットされている.
aiternative3にセットされている.=32bit
01 0B 03 00 01 00 00 00 |02 03 00 00 standard SET_INTERFACE interface(1) alt(3)
01 0B 00 00 01 00 00 00 |02 03 00 00 standard SET_INTERFACE interface(1) alt(0)
21 01 00 01 00 12 04 00 |00 DC 05 00 Audio class SET_CURRENT Fs ID(18)
01 0B 03 00 01 00 00 00 |00 DC 05 00 standard SET_INTERFACE interface(1) alt(3)
PCで音声再生アプリを停止したときのlog.
alternative0にセットされている.
01 0B 00 00 01 00 00 00 |00 DC 05 00 standard SET_INTERFACE interface(1) alt(0)
【EZ-USBを中心にした結線図】
あー書き疲れたびー
かしこ
内容はさっぱり理解できませんが^^;)、なんかお疲れ様です。
返信削除鍛冶屋さんこんにちは。
削除分割すればよかった。
自粛厨は暇なのでした。