Unduh konfigurasi ke FPGA melalui USB atau bongkar FTDI MPSSE



Dalam kehidupan setiap laci, ada saatnya ketika Anda ingin menulis loader Anda sendiri dari file konfigurasi di FPGA. Saya harus berpartisipasi dalam pengembangan stan pelatihan untuk departemen universitas teknis. Dudukan dirancang untuk mempelajari pemrosesan sinyal digital, meskipun ini tidak terlalu penting dalam rangka artikel ini. Dan yang penting adalah bahwa FPGA (Altera Cyclone IV) adalah jantung dari stan, di mana siswa mengumpulkan semua jenis skema DSP, seperti yang dikandung oleh penulis stan. Dudukan terhubung ke komputer melalui USB. Anda perlu mengunduh FPGA dari komputer melalui USB.

Keputusan dibuat untuk terhubung ke PC menggunakan FTDI dalam inkarnasi dual-channel - FT2232H. Satu saluran akan digunakan untuk konfigurasi FPGA, yang lain dapat digunakan untuk pertukaran FIFO kecepatan tinggi.


FTDI memiliki papan debug MORPH-IC-II , di mana FPGA Cyclone II di- flash melalui USB. Konsep dalam domain publik. Kode sumber bootloader sebagian terbuka: bootloader itu sendiri tersedia, namun, semua logika bekerja dengan FTDI dipindahkan ke perpustakaan pribadi dan tidak dapat dimodifikasi. Sebenarnya, saya awalnya berencana untuk menggunakan bootloader ini di proyek saya, atau, dalam kasus ekstrim, membuat shell saya berdasarkan dll. Firmware dimuat ke dalam FPGA dalam mode serial pasif (pasif serial - PS), FTDI beroperasi dalam mode MPSSE. Di papan tempat memotong roti, kinerja solusi MORPH-IC-II sepenuhnya dikonfirmasi, tetapi masalahnya, seperti yang sering terjadi, tidak datang dari mana. Ternyata selama pengoperasian dll MORPH-IC-II, semua perangkat FTDI yang terhubung diblokir, dan sebagai bagian dari kompleks pelatihan ada dua perangkat lagi dengan konverter yang sama: generator dan penganalisa sinyal. Bekerja serentak dengan mereka tidak mungkin. Sangat aneh dan menyebalkan.


Kasus serupa diimplementasikan oleh orang-orang dari penjelajah Mars: programmer USB JTAG MBFTDI . FTDI juga digunakan di sana dalam mode MPSSE, tetapi tidak seperti MORPH-IC-II, operasi FPGA dilakukan dalam mode JTAG. Sumber tersedia secara bebas, tetapi saya tidak menemukan indikasi yang jelas tentang status mereka (lisensi). Karena itu, untuk menggunakannya dalam proyek komersial, tangan saya tidak naik.


Saya akan memperbaiki kesalahan semacam itu, semua yang akan disajikan dalam kerangka artikel ini diposting di repositori terbuka di bawah lisensi BSD.


Unduh file konfigurasi ke chip FPGA


Pertama-tama, Anda harus berurusan dengan mode boot FPGA. Bagi mereka yang baru mulai berkenalan dengan topik, saya akan memberikan perjalanan kecil. Meskipun Altera (Intel) FPGA dari keluarga Cyclone IV E dipasang di papan saya, metode pemuatan serupa untuk seluruh grup FPGA Cyclone, dan ada kecurigaan bahwa dalam satu atau lain bentuk mereka cocok untuk banyak keluarga lainnya.


Jenis FPGA ini menggunakan SRAM yang mudah menguap untuk menyimpan data konfigurasi. Data konfigurasi ini menentukan fungsionalitas perangkat yang dihasilkan. Dalam jargon profesional, data ini sering disebut "firmware." Dengan demikian, firmware disimpan dalam RAM khusus dan setiap kali perangkat dihidupkan, harus dimuat ke dalam chip FPGA. Ada beberapa cara (skema konfigurasi) dimana firmware dapat dimuat ke SRAM (daftar ini relevan untuk Cyclone IV E):


  1. Serial aktif (AS).
  2. Paralel aktif (AP)
  3. Serial pasif (PS)
  4. Paralel cepat pasif (FPP).
  5. JTAG.

Pilihan mode boot tertentu dilakukan menggunakan terminal eksternal FPGA (grup MSEL). Mode JTAG selalu tersedia. Mode aktif menyiratkan bahwa ketika daya diterapkan, FPGA secara mandiri membaca data dari memori eksternal (serial atau paralel). Dalam mode pasif, FPGA menunggu media eksternal untuk secara proaktif mentransfer data konfigurasi ke dalamnya. Skema ini sangat cocok dengan konsep master (master) - slave (Slave). Dalam mode aktif, FPGA bertindak sebagai master, dan dalam mode pasif sebagai budak.


Dalam masalah ini, ini bukan FPGA, tetapi pengguna harus memutuskan kapan firmware harus diperbarui, sehingga mode boot harus pasif. Dan untuk menghemat kaki chip, kami memilih antarmuka serial. Mode Passive Serial (PS) dan JTAG cocok di sini. Logika JTAG agak lebih rumit, jadi mari kita fokus pada opsi pertama.
Gambar di bawah ini menunjukkan skema koneksi FPGA ke pengontrol eksternal untuk mengunduh dalam mode PS.



Untuk memulai konfigurasi, master eksternal harus menghasilkan transisi rendah ke tinggi pada baris nCONFIG . Segera setelah FPGA siap menerima data, FPGA itu akan membentuk level tinggi di jalur nSTATUS . Setelah itu, master dapat mulai mentransmisikan data pada garis DATA [0] , dan clock yang sesuai berdenyut pada jalur DCLK . Data harus ditransmisikan ke perangkat target hingga level tinggi ditetapkan pada garis CONF_DONE (atau data tidak berakhir), dan FPGA beralih ke status inisialisasi. Perlu dicatat bahwa setelah CONF_DONE diatur ke satu, dua pulsa clock harus diterapkan sehingga inisialisasi FPGA dimulai.


Data ditransmisikan oleh bit paling signifikan ( LSB ) ke depan, yaitu, jika file konfigurasi berisi urutan 02 1B EE 01 FA (ambil contoh seperti dari Buku Pegangan), urutan harus dibentuk pada baris data:


0100-0000 1101-1000 0111-0111 1000-0000 0101-1111 

Dengan demikian, hanya lima jalur yang digunakan: jalur DATA [0] dan DCLK untuk transmisi serial, nCONFIG , nSTATUS , jalur CONF_DONE untuk kontrol.
Pada intinya, mode PS tidak lebih dari SPI dengan manipulasi flag tambahan.
Kecepatan transfer data harus lebih rendah daripada frekuensi maksimum yang ditunjukkan dalam dokumentasi, untuk seri Cyclone IV E yang digunakan dalam proyek, itu adalah 66 MHz.


Frekuensi transmisi minimum tidak ada, secara teori dimungkinkan untuk menangguhkan konfigurasi untuk waktu yang tidak terbatas. Ini memberikan peluang bagus untuk debugging langkah demi langkah dengan partisipasi osiloskop, yang tentunya akan kita gunakan.


Gambar di bawah ini menunjukkan diagram waktu antarmuka dengan timing yang paling signifikan.



Sly Beast MPSSE


Pertimbangkan pengoperasian FTDI dalam mode MPSSE. Mode MPSSE (Multi-Protocol Synchronous Serial Engine), menurut pendapat saya, adalah upaya yang kurang lebih berhasil untuk membuat desainer antarmuka serial tertentu, untuk memberikan pengembang kesempatan untuk mengimplementasikan protokol transfer data yang tersebar luas, seperti SPI, I2C, JTAG, 1-kawat dan banyak yang lain berdasarkan pada mereka.


Saat ini, mode tersedia untuk sirkuit mikro: FT232H, FT2232D, FT2232H, FT4232H. Dalam proyek saya, saya menggunakan FT2232H, jadi sebagian besar kita membicarakannya. Untuk mode MPSSE, 16 kaki dialokasikan, dibagi menjadi dua byte: L lebih rendah dan H. tertinggi. Setiap byte dapat dibaca atau diatur. Empat kaki bagian bawah byte L memiliki fungsi khusus - transmisi data serial dapat terjadi melalui mereka. Setiap leg dapat dikonfigurasi sebagai input atau output, nilai default dapat diatur untuk output. Untuk transmisi berurutan, urutan bit ( MSB / LSB ), panjang kata yang ditransmisikan, frekuensi pulsa clock, front-depan sinkronisasi (Naik) atau belakang (Jatuh), Anda dapat memilih untuk mengirimkan pulsa clock saja tanpa data, atau memilih clocking 3 fase (relevan untuk I2C) dan banyak lagi.


Pindah ke pemrograman dengan mulus. Ada dua cara alternatif interaksi perangkat lunak dengan chip FTDI: yang pertama, sebut saja klasik, dalam hal ini, ketika terhubung ke port USB, chip dalam sistem didefinisikan sebagai port serial virtual (COM), sistem operasi menggunakan driver VCP (Virtual COM Port). Semua pemrograman lebih lanjut tidak berbeda dari pemrograman port COM klasik: dibuka - dikirim / dihitung - ditutup. Dan ini berlaku untuk berbagai sistem operasi, termasuk Linux dan Mac OS. Namun, dengan pendekatan ini, tidak mungkin untuk mewujudkan semua fitur pengontrol FTDI - chip tersebut akan berfungsi sebagai adaptor USB-UART. Metode kedua disediakan oleh perpustakaan eksklusif FTD2XX, antarmuka ini menyediakan fungsi-fungsi khusus yang tidak tersedia di API port COM standar, khususnya, dimungkinkan untuk mengkonfigurasi dan menggunakan mode operasi khusus, seperti MPSSE, 245 FIFO, Bit-bang. Pustaka FTD2XX API didokumentasikan dengan baik oleh Panduan Pengembangan Aplikasi Perangkat Lunak D2XX Programmer , dikenal luas untuk waktu yang lama dalam lingkaran sempit. Dan ya, FTD2XX juga tersedia untuk berbagai sistem operasi.


Pengembang FTDI dihadapkan dengan tugas mengintegrasikan MPSSE yang relatif baru ke dalam model interaksi perangkat lunak D2XX yang ada. Dan mereka berhasil, untuk bekerja dalam mode MPSSE serangkaian fungsi yang sama digunakan untuk mode "klasik" lainnya, library yang sama API FTD2XX digunakan.


Singkatnya, algoritma untuk beroperasi dalam mode MPSSE dapat dijelaskan sebagai berikut:


  1. Temukan perangkat di sistem dan buka.
  2. Inisialisasi chip dan masukkan ke mode MPSSE.
  3. Atur mode operasi MPSEE.
  4. Bekerja langsung dengan data: mengirim, menerima, mengelola GPIO - kami menerapkan protokol pertukaran target.
  5. Tutup perangkat.

Menulis bootloader


Mari kita turun ke bagian praktis. Dalam percobaan saya, saya akan menggunakan versi Eclipse dari Oxygen.3a Release (4.7.3a) sebagai IDE, dan mingw32-gcc (6.3.0) sebagai kompiler. Sistem operasi Win7.


Dari situs web FTDI kami mengunduh versi terbaru driver untuk sistem operasi kami. Dalam arsip kami menemukan file header ftd2xx.h dengan deskripsi semua fungsi API. API itu sendiri diimplementasikan sebagai ftd2xx.dll, tetapi kami akan meninggalkan impor dinamis untuk nanti, dan menggunakan tautan statis: kita memerlukan file library ftd2xx.lib. Untuk kasus saya, ftd2xx.lib ada di direktori i386.


Di Eclipse, buat proyek C baru. Membuat makefile dapat dipercaya dengan IDE. Dalam pengaturan tautan, tentukan jalur dan nama perpustakaan ftd2xx (saya mentransfer file yang diperlukan ke direktori proyek di folder ftdi). Saya tidak akan fokus pada fitur pengaturan proyek untuk Eclipse, karena saya menduga sebagian besar dari mereka menggunakan lingkungan dan kompiler lain untuk pemrograman Win.


Poin satu. Temukan perangkat dan buka


API FTD2XX memungkinkan Anda untuk membuka chip menggunakan satu atau beberapa informasi yang diketahui tentangnya. Ini mungkin nomor seri dalam sistem: chip FTDI pertama yang terhubung akan mengambil angka 0, 1 berikutnya dan seterusnya. Jumlah dalam sistem ditentukan oleh urutan di mana sirkuit mikro terhubung, dengan kata lain, ini tidak selalu nyaman. Untuk membuka chip dengan nomor, fungsi FT_Open . Anda dapat membuka chip dengan nomor seri ( FT_OPEN_BY_SERIAL_NUMBER ), deskripsi ( FT_OPEN_BY_DESCRIPTION ) atau dengan lokasi ( FT_OPEN_BY_LOCATION ), untuk ini, fungsi FT_OpenEx . Nomor seri dan deskripsi disimpan dalam memori internal chip dan dapat direkam di sana selama pembuatan perangkat dengan FTDI diinstal. Deskripsi, sebagai suatu peraturan, mencirikan jenis perangkat atau keluarga, dan nomor seri harus unik untuk setiap produk. Oleh karena itu, cara paling mudah untuk mengidentifikasi perangkat yang didukung oleh program yang sedang dikembangkan adalah deskripsinya. Kami akan membuka chip FTDI sesuai dengan deskripsi (deskriptor). Faktanya, jika kami awalnya mengetahui string deskriptor chip, maka kami tidak perlu mencari perangkat di sistem, namun, sebagai percobaan, kami akan menampilkan semua perangkat yang terhubung ke komputer dengan FTDI. Menggunakan fungsi FT_CreateDeviceInfoList , FT_CreateDeviceInfoList akan membuat daftar terperinci dari chip yang terhubung, dan menggunakan fungsi FT_GetDeviceInfoList , FT_GetDeviceInfoList mempertimbangkannya.


Daftar perangkat yang terhubung. Daftar:
 ftStatus = FT_CreateDeviceInfoList(&numDevs); if (ftStatus == FT_OK) { printf("Number of devices is %d\n",numDevs); } if (numDevs == 0) return -1; // allocate storage for list based on numDevs devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs); ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs); if (ftStatus == FT_OK) for (int i = 0; i < numDevs; i++) { printf("Dev %d:\n",i); printf(" Flags=0x%x\n",devInfo[i].Flags); printf(" Type=0x%x\n",devInfo[i].Type); printf(" ID=0x%x\n",devInfo[i].ID); printf(" LocId=0x%x\n",devInfo[i].LocId); printf(" SerialNumber=%s\n",devInfo[i].SerialNumber); printf(" Description=%s\n",devInfo[i].Description); } 

Selamat datang di kebun binatang saya
 D:\workspace\ftdi-mpsse-ps\Debug>ftdi-mpsse-ps.exe Number of devices is 4 Dev 0: Flags = 0x0 Type = 0x5 ID = 0x4036001 LocId = 0x214 SerialNumber = AI043NNV Description = FT232R USB UART Dev 1: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2121 SerialNumber = L731T70OA Description = LESO7 A Dev 2: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2122 SerialNumber = L731T70OB Description = LESO7 B Dev 3: Flags = 0x2 Type = 0x8 ID = 0x4036014 LocId = 0x213 SerialNumber = FTYZ92L6 Description = LESO4.1_ER 

Tiga perangkat dengan chip FTDI terhubung ke PC saya: FT232RL (tipe 0x5), FT2232H (tipe 0x6) dan FT232H (tepe 0x8). Chip FT2232H dalam sistem ditampilkan sebagai dua perangkat independen (Dev 1 dan Dev 2). Antarmuka FPGA PS terhubung ke Dev 2, deskriptornya adalah "LESO7 B". Buka itu:


 //Open a device with device description "LESO7 B" ftStatus = FT_OpenEx("LESO7 B", FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("pen failure\r\n"); return -1; } 

Sebagian besar fungsi API mengembalikan status panggilan jenisnya FT_STATUS , semua nilai yang mungkin dideskripsikan sebagai enum dalam file header. Ada banyak dari mereka, tetapi cukup untuk mengetahui bahwa nilai FT_OK adalah tidak adanya kesalahan, semua nilai lainnya adalah kode kesalahan. Gaya pemrograman yang baik adalah memeriksa nilai status setelah setiap panggilan ke fungsi API.


Jika perangkat berhasil dibuka, maka dalam variabel ftHandle muncul beberapa nilai selain nol, beberapa deskriptor file yang setara, yang digunakan saat bekerja dengan file. Pegangan yang dihasilkan membuat koneksi dengan antarmuka perangkat keras dan harus digunakan saat memanggil semua fungsi pustaka yang memerlukan akses ke chip.
Untuk mengkonfirmasi dalam prakteknya pengoperasian sistem untuk tahap saat ini, kita harus segera melanjutkan ke langkah lima dari algoritma kami.


Setelah Anda selesai bekerja dengan chip, Anda harus menutupnya. Untuk melakukan ini, gunakan fungsi FT_Close :


 FT_Close(ftHandle); 

Poin 2. Inisialisasi chip dan nyalakan MPSSE


Pengaturan ini khas untuk sebagian besar mode dan dijelaskan dengan baik dalam dokumentasi AN_135 FTDI MPSSE Basics .


  1. Kami melakukan reset (rezet) chip. Fungsi FT_ResetDevice .
  2. Jika ada sampah di buffer terima, kami membersihkannya. Fungsi FT_Purge .
  3. Sesuaikan ukuran buffer untuk membaca dan menulis. Fungsi FT_SetUSBParameters .
  4. Matikan paritas. FT_SetChars .
  5. Kami menetapkan batas waktu untuk membaca dan menulis. Secara default, batas waktu dinonaktifkan, aktifkan batas waktu transmisi. FT_SetTimeouts .
  6. Kami mengonfigurasi waktu tunggu untuk mengirim paket dari chip ke host. Secara default, 16 ms, akselerasi ke 1 ms. FT_SetLatencyTimer .
  7. Aktifkan kontrol aliran untuk menyinkronkan permintaan yang masuk. FT_SetFlowControl .
  8. Semuanya siap untuk mengaktifkan mode MPSSE. Setel ulang pengontrol MPSSE. Kami menggunakan fungsi FT_SetBitMode , mengatur mode ke 0 (mode = 0, mask = 0).
  9. Aktifkan mode MPSSE. Fungsi FT_SetBitMode - mode = 2, mask = 0.

Kami menyatukan dan mengkonfigurasi chip dalam fungsi MPSSE_open , sebagai parameter kami melewati garis dengan pegangan perangkat yang akan dibuka:


Cantumkan MPSSE_open
 static FT_STATUS MPSSE_open (char *description) { FT_STATUS ftStatus; ftStatus = FT_OpenEx(description, FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("open failure\r\n"); return FT_DEVICE_NOT_OPENED; } printf ("open OK, %d\r\n", ftHandle); printf("\nConfiguring port for MPSSE use...\n"); ftStatus |= FT_ResetDevice(ftHandle); //Purge USB receive buffer first by reading out all old data from FT2232H receive buff: ftStatus |= FT_Purge(ftHandle, FT_PURGE_RX); //Set USB request transfer sizes to 64K: ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65536); //Disable event and error characters: ftStatus |= FT_SetChars(ftHandle, 0, 0, 0, 0); //Sets the read and write timeouts in milliseconds: ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000); //Set the latency timer to 1mS (default is 16mS): ftStatus |= FT_SetLatencyTimer(ftHandle, 1); //Turn on flow control to synchronize IN requests: ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x00, 0x00); //Reset controller: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_RESET); //Enable MPSSE mode: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_MPSSE); if (ftStatus != FT_OK) { printf("Error in initializing the MPSSE %d\n", ftStatus); return FT_OTHER_ERROR; } Sleep(50); // Wait for all the USB stuff to complete and work return FT_OK; } 

Butir 3. Mengkonfigurasi mode operasi MPSEE


Sebenarnya, pada tahap ini prosesor MPSSE diaktifkan dan siap menerima perintah. Perintah adalah urutan byte, byte pertama di antaranya adalah "op-code", diikuti oleh parameter perintah. Perintah ini mungkin tidak memiliki parameter dan terdiri dari satu "op-code". Perintah ditransmisikan menggunakan fungsi FT_Write , respons dari prosesor MPSSE dapat diperoleh dengan menggunakan fungsi FT_Read .


Setelah setiap pengiriman perintah, akan bermanfaat untuk membaca respons prosesor, karena jika ada perintah yang salah, respons tersebut mungkin berisi pesan kesalahan - karakter 0xFA. Mekanisme "perintah buruk - respons 0xFA" dapat digunakan untuk menyinkronkan program aplikasi dengan prosesor MPSSE. Jika semuanya OK, maka chip akan mengembalikan karakter 0xFA pada perintah yang sengaja salah. Kode-op dijelaskan dalam Command Processor untuk Mode Emulasi Bus Host MPSSE dan MCU .
Mengkonfigurasi MPSSE dilakukan untuk menetapkan kecepatan data, arah, dan status awal dari garis I / O.
Pertimbangkan untuk mengatur kecepatan data prosesor MPSSE. Pengaturan untuk chip dengan dukungan untuk hanya mode kecepatan penuh (FT2232 D ) dan chip dengan kecepatan tinggi (FT2232 H , FT232H, FT4232H) agak berbeda. FT2232D lama menggunakan jam 12MHz, sedangkan yang modern menggunakan 60MHz. Maka rumus untuk menghitung laju transfer data:


KecepatanData= fracfcore(1+Pembagi) cdot2


di mana f core adalah frekuensi inti FTDI, Divisor adalah pembagi dua byte, yang, pada kenyataannya, mengatur frekuensi jam data.
Akibatnya, jika pembagi sama dengan nol, maka kecepatan transfer data maksimum adalah 30 Mbps, dan kecepatan transfer data minimum adalah pada pembagi 65535 - 458 bit / s.
Kami akan mempercayakan perhitungan pembagi ke preprosesor. Makro mengembalikan pembagi:


 #define FCORE 60000000ul #define MPSSE_DATA_SPEED_DIV(data_speed) ((FCORE/(2*data_speed)) -1) 

Dan kedua makro ini mengembalikan byte tinggi dan rendah dari pembagi, masing-masing:


 #define MPSSE_DATA_SPEED_DIV_H(data_speed) ((MPSSE_DATA_SPEED_DIV(data_speed)) >> 8) #define MPSSE_DATA_SPEED_DIV_L(data_speed) \ (MPSSE_DATA_SPEED_DIV(data_speed) - (MPSSE_DATA_SPEED_DIV_H(data_speed)<< 8)) 

Selain itu, perlu dicatat bahwa dalam chip modern untuk kompatibilitas dengan FT2232D lama ada tambahan 5 pembagi, yang mengubah 60 MHz menjadi 12 MHz. Pembagi ini diaktifkan secara default, dalam kasus kami pembagi ini harus dimatikan.
Kami menemukan kode-op yang sesuai (0x8A) dan perintah helm ke prosesor:


Daftar pengiriman tim
 BYTE byOutputBuffer[8], byInputBuffer[8]; DWORD dwNumBytesToRead, dwNumBytesSent = 0, dwNumBytesRead = 0; byOutputBuffer[0] = 0x8A; ftStatus = FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Error\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("dwNumBytesToRead = %d:", dwNumBytesToRead); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; 

Sebagai percobaan, alih-alih perintah 0x8A yang sebenarnya, kami akan mengirimkan nilai 0xFE, yang tidak sesuai dengan kode op apa pun, keluaran konsol:


 dwNumBytesToRead = 2: FAh FEh 

Prosesor mengembalikan dua byte, byte perintah buruk adalah 0xFA dan nilai perintah buruk ini. Jadi, dengan mengirim beberapa perintah sekaligus, kita tidak hanya bisa melacak fakta kesalahan itu sendiri, tetapi juga memahami tim mana kesalahan ini terjadi.
Agar tidak berurusan dengan "angka ajaib" di masa mendatang, kami akan memformat semua kode-op dalam bentuk konstanta dan menempatkannya dalam file header yang terpisah.
Untuk sepenuhnya mengkonfigurasi mode, Anda perlu menentukan arah garis I / O dan nilai defaultnya. Mari kita beralih ke diagram koneksi. Agar tidak mengacaukan artikel yang sudah membengkak, saya telah menggambar sebuah fragmen menarik dari skema ini:



Baris DCLK , DATA [0] , nCONFIG harus dikonfigurasi sebagai output, baris nSTATUS , CONF_DONE sebagai input. Dengan menggunakan diagram, kami menentukan status awal yang harus dimiliki garis. Untuk kejelasan, pinout dari rangkaian dirangkum dalam tabel:


Pin FPGANama pinPinMPSSEArahanstandar
DCLKBDBUS038TCK / SKKeluar0
DATA [0]BDBUS139TDI / DOKeluar1
nCONFIGBDBUS240TDO / DIKeluar1
nSTATUSBDBUS341TMS / CSMasuk1
CONF_DONEBDBUS443GPIOL0Masuk1

Semua baris yang digunakan terletak pada byte rendah port MPSSE. Untuk mengatur nilainya, gunakan op-code 0x80. Perintah ini mengasumsikan dua argumen: byte pertama yang mengikuti op-code adalah nilai bit-by-bit, dan yang kedua adalah arah (satu adalah port output, nol adalah port input).
Sebagai bagian dari pertarungan melawan "angka ajaib", semua nomor baris seri dan nilai defaultnya akan diformat sebagai konstanta:


Tentukan port
 #define PORT_DIRECTION (0x07) #define DCLK (0) #define DATA0 (1) #define N_CONFIG (2) #define N_STATUS (3) #define CONF_DONE (4) // initial states of the MPSSE interface #define DCLK_DEF (1) #define DATA0_DEF (0) #define N_CONFIG_DEF (1) #define N_STATUS_DEF (1) #define CONF_DONE_DEF (1) 

Tetap hanya untuk memastikan bahwa loop TDI - TDO dinonaktifkan (dapat diaktifkan untuk pengujian) dan memasukkannya ke dalam fungsi terpisah:


Mendaftarkan Fungsi MPSSE_setup
 static FT_STATUS MPSSE_setup () { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8], byInputBuffer[8]; FT_STATUS ftStatus; // Multple commands can be sent to the MPSSE with one FT_Write dwNumBytesToSend = 0; // Start with a fresh index byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_DIVIDER_5; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_ADAPTIVE_CLK; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_3PHASE_CLOCKING; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set TCK frequency // Command to set clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_TCK_DIVISION; // Set ValueL of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_L(DATA_SPEED); // Set 0xValueH of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_H(DATA_SPEED); ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - low byte, both pin directions and output values /* | FPGA pin | Pin Name | Pin | MPSSE | Dir | def | | --------- | -------- | --- | ------ | --- | --- | | DCLK | BDBUS0 | 38 | TCK/SK | Out | 0 | | DATA[0] | BDBUS1 | 39 | TDI/DO | Out | 1 | | nCONFIG | BDBUS2 | 40 | TDO/DI | Out | 1 | | nSTATUS | BDBUS3 | 41 | TMS/CS | In | 1 | | CONF_DONE | BDBUS4 | 43 | GPIOL0 | In | 1 | */ // Configure data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; // Initial state config above: byOutputBuffer[dwNumBytesToSend++] = (DCLK_DEF << DCLK) | (DATA0_DEF << DATA0) | (N_CONFIG_DEF << N_CONFIG) | (N_STATUS_DEF << N_STATUS) | (CONF_DONE_DEF << CONF_DONE); // Direction config above: byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the low GPIO config commands dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - high byte, all input, Initial State -- 0. // Send off the high GPIO config commands: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_HIGHBYTE; byOutputBuffer[dwNumBytesToSend++] = 0x00; byOutputBuffer[dwNumBytesToSend++] = 0x00; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Disable loopback: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_LOOP_TDI_TDO; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Unknown error in initializing the MPSSE\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("Error in initializing the MPSSE, bad code:\r\n"); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; } 

Poin 4. Kami menerapkan protokol pemuatan


Segalanya tampak siap untuk eksperimen praktis. Pertama, periksa apakah inisialisasi dilakukan dengan benar, di bagian utama program, panggil MPSSE_open() dan MPSSE_setup() , dan sebelum menutup perangkat ( FT_Close ), kami meletakkan getchar() kosong getchar() . Jalankan program dan gunakan osiloskop untuk memastikan bahwa semua saluran PS diatur ke level default. Mengubah nilai level-level ini dalam inisialisasi (tidak ada hal buruk yang akan terjadi dengan FPGA), kami memastikan bahwa prosesor MPSSE memberikan hasil yang diinginkan valid - semuanya bekerja dengan baik dan Anda dapat melanjutkan untuk mentransfer data.
Pengiriman dan penerimaan data secara berurutan dilakukan dalam mode perintah menggunakan op-code yang sama. Byte pertama dari perintah ini adalah op-code, yang menentukan jenis operasi, diikuti oleh panjang urutan yang dikirim atau diterima dan, jika itu adalah transmisi, data aktual. Prosesor MPSSE dapat mengirim dan menerima data, juga melakukannya secara bersamaan. Transmisi dapat berupa bit forward paling tidak signifikan (LSB) atau yang paling signifikan (MSB). Transmisi data dapat terjadi di tepi pulsa clock yang memimpin atau tertinggal. Setiap kombinasi opsi memiliki op-code sendiri, masing-masing bit op-code menjelaskan mode operasi:


SedikitFungsi
0Sinkronisasi penulisan-depan: 0 - positif, 1 - negatif
11 - bekerja dengan byte, 0 - bekerja dengan bit
2Tepi depan untuk membaca: 0 - positif, 1 - negatif
3Mode Transmisi: 1 - LSB, 0 - MSB terlebih dahulu
4Pengiriman data TDI
5Membaca Data dari Garis TDO
6Transmisi data TMS
7Harus 0, kalau tidak, ini adalah kelompok perintah lain

Ketika mengkonfigurasi FPGA sesuai dengan skema PS, data ditransmisikan pada keunggulan dalam mode LSB. , , op-code 0001_1000b 0x18 . ( , ), . : . , , 0, 65536, 65535. , . MPSSE_send .


MPSSE_send
 static BYTE byBuffer[65536 + 3]; static FT_STATUS MPSSE_send(BYTE * buff, DWORD dwBytesToWrite) { DWORD dwNumBytesToSend = 0, dwNumBytesSent, bytes; FT_STATUS ftStatus; // Output on rising clock, no input // MSB first, clock a number of bytes out byBuffer[dwNumBytesToSend++] = MPSSE_CMD_LSB_DATA_OUT_BYTES_POS_EDGE; // 0x18 bytes = dwBytesToWrite -1; byBuffer[dwNumBytesToSend++] = (bytes) & 0xFF; // Length L byBuffer[dwNumBytesToSend++] = (bytes >> 8) & 0xFF; // Length H memcpy(&byBuffer[dwNumBytesToSend], buff, dwBytesToWrite); dwNumBytesToSend += dwBytesToWrite; ftStatus = FT_Write(ftHandle, byBuffer, dwNumBytesToSend, &dwNumBytesSent); if (ftStatus != FT_OK ) { printf ("ERROR send data\r\n"); return ftStatus; } else if (dwNumBytesSent != dwNumBytesToSend) { printf ("ERROR send data, %d %d\r\n", dwNumBytesSent, dwNumBytesToSend); } return FT_OK; } 

β€” 65 , - , op-code . byBuffer , buff , , op-code . , , .
, "" , 25 , , , 1 ( , #define DATA_SPEED 1000000ul ). :


 BYTE byOutputBuffer[] = {0x02, 0x1B, 0xEE, 0x01, 0xFA}; MPSSE_send(byOutputBuffer, sizeof(byOutputBuffer)); 

( ):


β€” DATA[0] , β€” DCLK . . , , .


, SPI ( ). , PS, . nCONFIG , nSTATUS , CONF_DONE . β€” , , β€” , .


MPSSE_get_lbyte , , .


MPSSE_get_lbyte
 static FT_STATUS MPSSE_get_lbyte(BYTE *lbyte) { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8]; FT_STATUS ftStatus; dwNumBytesToSend = 0; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_GET_DATA_BITS_LOWBYTE; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, lbyte, dwNumBytesToRead, &dwNumBytesRead); if ((ftStatus != FT_OK) & (dwNumBytesToRead != 1)) { printf("Error read Lbyte\r\n"); return FT_OTHER_ERROR; // Exit with error } return FT_OK; } 

, op-code , . , - , , . , . MPSSE_set_lbyte :


MPSSE_set_lbyte
 static FT_STATUS MPSSE_set_lbyte(BYTE lb, BYTE mask) { DWORD dwNumBytesToSend, dwNumBytesSent; BYTE byOutputBuffer[8], lbyte; FT_STATUS ftStatus; ftStatus = MPSSE_get_lbyte(&lbyte); if ( ftStatus != FT_OK) return ftStatus; // Set to zero the bits selected by the mask: lbyte &= ~mask; // Setting zero is not selected by the mask bits: lb &= mask; lbyte |= lb; dwNumBytesToSend = 0; // Set data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; byOutputBuffer[dwNumBytesToSend++] = lbyte; byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if ((ftStatus != FT_OK) & (dwNumBytesSent != 1)) { printf("Error set Lbyte\r\n"); return FT_OTHER_ERROR; } return FT_OK; } 

, . : FTDI; MPSSE; rbf- , nCONFIG , N_STATUS ; rbf- ; , , CONF_DONE . , MPSSE FTDI . , nCONFIG "" , , , .


main
 int main(int argc, char *argv[]) { FT_STATUS ftStatus; BYTE lowByte; DWORD numDevs; // create the device information list if ( argv[1] == NULL) { printf ("NO file\r\n"); return -1; } frbf = fopen(argv[1],"rb"); if (frbf == NULL) { printf ("Error open rbf\r\n"); return -1; } ftStatus = FT_CreateDeviceInfoList(&numDevs); if ((numDevs == 0) || (ftStatus != FT_OK)) { printf("Error. FTDI devices not found in the system\r\n"); return -1; } ftStatus = MPSSE_open ("LESO7 B"); if (ftStatus != FT_OK) { printf("Error in MPSSE_open %d\n", ftStatus); EXIT(-1); } MPSSE_setup(); if (ftStatus != FT_OK) { printf("Error in MPSSE_setup %d\n", ftStatus); EXIT(-1); } printf ("nConfig -> 0\r\n"); MPSSE_set_lbyte(0, 1 << N_CONFIG); printf ("nConfig -> 1\r\n"); MPSSE_set_lbyte(1 << N_CONFIG, 1 << N_CONFIG); if (MPSSE_get_lbyte(&lowByte) != FT_OK) { EXIT(-1); } if (((lowByte >> N_STATUS) & 1) == 0) { printf("Error. FPGA is not responding\r\n"); EXIT(-1); } int i = 0; size_t readBytes = 0; // Send the configuration file: do { readBytes = fread(buff, 1, MPSSE_PCK_SEND_SIZE, frbf); if (MPSSE_send(buff, readBytes) != FT_OK) EXIT(-1); putchar('*'); if (!((++i)%16)) printf("\r\n"); } while (readBytes == MPSSE_PCK_SEND_SIZE); printf("\r\n"); memset(buff, 0x00, sizeof(buff)); MPSSE_send(buff, 1); //        ? printf("Load complete\r\n"); // wait CONF_DONE set // A low-to-high transition on the CONF_DONE pin indicates that the configuration is // complete and initialization of the device can begin. i = 0; do { if (MPSSE_get_lbyte(&lowByte) != FT_OK) { printf ("Error read CONF_DONE\r\n"); EXIT(-1); } if (i++ > TIMEOUT_CONF_DONE) { printf ("Error CONF_DONE\r\n"); EXIT(-1); } Sleep(2); } while (((lowByte >> CONF_DONE) & 1) == 0); printf("Configuration complete\r\n"); FT_Close(ftHandle); fclose(frbf); } 

:


 pen "LESO7 B" OK nConfig -> 0 nConfig -> 1 ** Load complete Configuration complete 

rbf- . . 30 / .
, - JTAG.



  1. FTDI-MPSSE-Altera PS . .
  2. . . .
  3. Software Application Development D2XX Programmer's Guide . FTDI. API D2XX.
  4. FTDI MPSSE Basics. Application Note AN_135 . . FTDI MPSSE. .
  5. Command Processor untuk Mode Emulasi Bus Host MPSSE dan MCU. Catatan Aplikasi AN_108 . Referensi untuk kode-op. Tidak mungkin tanpanya.
  6. Driver D2XX . Pengemudi FTDI.

Source: https://habr.com/ru/post/id426131/


All Articles