Pendahuluan
Setelah membaca judul, pertanyaan logis mungkin muncul: mengapa saat ini mempelajari implementasi perangkat lunak USB kecepatan rendah ketika ada sekelompok pengendali murah dengan modul perangkat keras? Faktanya adalah bahwa modul perangkat keras, menyembunyikan tingkat pertukaran tingkat logis, mengubah protokol USB menjadi semacam keajaiban. Untuk merasakan bagaimana "sihir" ini bekerja, tidak ada yang lebih baik daripada mereproduksi dari awal, mulai dari level terendah.
Untuk tujuan ini, kami akan mencoba membuat perangkat yang berpura-pura menjadi USB-HID berdasarkan ATmega8 controller. Berbeda dengan literatur yang tersebar luas, kita tidak akan beralih dari teori ke praktik, dari level terendah ke level tertinggi, dari voltase logika ke kesimpulan, dan diakhiri dengan "penemuan" vusb yang sama, setelah setiap langkah memeriksa apakah kode berfungsi seperti yang diharapkan. Secara terpisah, saya perhatikan bahwa saya tidak menciptakan alternatif untuk perpustakaan ini, melainkan, saya secara konsisten mereproduksi kode sumbernya, melestarikan sebanyak mungkin struktur dan nama, menjelaskan mengapa bagian ini atau itu berfungsi. Namun, gaya penulisan kode yang saya kenal berbeda dengan gaya penulis vusb. Segera, saya jujur ββmengakui bahwa selain minat altruistik (untuk menceritakan topik yang sulit kepada orang lain), saya juga memiliki minat yang egois - untuk mempelajari topik sendiri dan untuk menangkap poin maksimum yang halus untuk diri saya sendiri. Ini juga mengikuti bahwa beberapa poin penting mungkin terlewatkan, atau beberapa topik tidak diungkapkan sepenuhnya.
Untuk persepsi kode yang lebih baik, saya mencoba menyoroti bagian yang diubah dengan komentar dan menghapusnya dari bagian yang dibahas sebelumnya. Sebenarnya, kode sumber akan menjadi sumber utama informasi, dan teks akan menjelaskan apa yang dilakukan dan mengapa, serta hasil apa yang diharapkan.
Saya juga mencatat bahwa hanya USB berkecepatan rendah yang dipertimbangkan, bahkan tanpa menyebutkan, yang membedakan lebih banyak varietas berkecepatan tinggi.
Langkah 0. Setrika dan persiapan lainnya
Sebagai tes, mari kita gunakan papan debugging buatan sendiri berbasis ATmega8 dengan kuarsa 12 MHz. Saya tidak akan memberikan skema, itu cukup standar (lihat situs web vusb resmi), satu-satunya hal yang layak disebutkan adalah kesimpulan yang digunakan. Dalam kasus saya, output D + sesuai dengan PD2, output D-PD3, dan suspensi tergantung pada PD4. Pada prinsipnya, resistor pull-up dapat dihubungkan ke daya, tetapi kontrol manual tampaknya sedikit lebih konsisten dengan standar.
Daya 5 V dipasok dari konektor USB, namun, tidak lebih dari 3,6 V diharapkan pada saluran sinyal (mengapa ini menjadi misteri bagi saya). Jadi Anda perlu menurunkan kekuatan controller, atau meletakkan dioda zener pada garis sinyal. Saya memilih opsi kedua, tetapi pada umumnya itu tidak masalah.
Karena kita "menciptakan" implementasi, akan menyenangkan untuk melihat apa yang terjadi pada otak controller, yaitu, setidaknya diperlukan beberapa jenis informasi debug. Dalam kasus saya, ini adalah dua LED pada PD6, PD7 dan, yang paling penting, UART pada PD0, PD1, dikonfigurasi pada 115200, sehingga Anda dapat mendengarkan obrolan controller melalui layar biasa atau program lain untuk bekerja dengan port COM:
$ screen /dev/ttyUSB0 115200
Juga, wireshark dengan modul yang sesuai akan berubah menjadi utilitas yang berguna untuk debugging USB (itu tidak selalu dimulai dari kotak, tetapi menyelesaikan masalah seperti itu cukup berhasil terletak di Internet dan bukan tugas artikel ini).
Di sini akan mungkin untuk menghabiskan satu kilobyte teks pada deskripsi programmer, makefile, dan hal-hal lain, tetapi ini hampir tidak masuk akal. Demikian pula, saya tidak akan fokus pada pengaturan periferal yang tidak terkait dengan USB. Jika seseorang bahkan tidak bisa mengetahuinya, apakah terlalu dini untuk masuk ke dalam usus perangkat lunak USB?
Kode sumber untuk semua langkah tersedia di Github.
Langkah 1. Terima setidaknya sesuatu
Menurut dokumentasi, USB mendukung beberapa kecepatan tetap, di mana AVR hanya akan menarik yang terendah: 1,5 megabit per detik. Ini ditentukan oleh resistor pull-up dan komunikasi selanjutnya. Untuk frekuensi yang kami pilih, resistor harus menghubungkan D- dengan catu daya 3,3 V dan memiliki nilai nominal 1,5 kOhm, tetapi dalam praktiknya dapat dihubungkan dengan +5 V dan nilai nominal dapat sedikit bervariasi. Dengan frekuensi pengontrol 12 MHz, hanya 8 clock cycle per bit. Jelas bahwa keakuratan dan kecepatan tersebut hanya dapat dicapai di assembler, jadi kami akan membuka file drvasm.S. Ini juga menyiratkan kebutuhan untuk menggunakan interupsi untuk menangkap awal byte. Saya senang bahwa byte pertama yang dikirimkan melalui USB selalu sama, SYNC, jadi jika Anda memulai, tidak apa-apa. Akibatnya, dari awal byte hingga akhir, hanya 64 siklus pengontrol yang lulus (pada kenyataannya, marginnya bahkan lebih kecil), jadi Anda sebaiknya tidak menggunakan interupsi non-USB lainnya.
Segera letakkan konfigurasi ke file usbconfig.h terpisah. Di sinilah pin yang bertanggung jawab untuk USB akan diatur, serta bit, konstanta dan register yang digunakan.
Masukkan teoretis
Transfer melalui USB dilakukan dalam paket beberapa byte di masing-masing. Byte pertama selalu byte sinkronisasi SYNC, sama dengan 0b10000000, byte kedua adalah pengidentifikasi byte dari paket PID. Transfer setiap byte berjalan dari bit yang paling signifikan ke yang paling signifikan (ini tidak sepenuhnya benar, tetapi dalam vusb kehalusan ini diabaikan, diberikan di tempat lain) menggunakan pengkodean NRZI. Metode ini terdiri dari fakta bahwa nol logis ditransmisikan dengan mengubah level logis ke kebalikannya, dan unit logis ditransmisikan oleh non-perubahan. Selain itu, perlindungan diperkenalkan dari desync (yang tidak akan kami gunakan, tetapi harus diperhitungkan) dari sumber sinyal dan penerima: jika ada enam unit berturut-turut dalam urutan yang ditransmisikan, yaitu, keadaan terminal tidak berubah selama enam periode berturut-turut, inversi paksa ditambahkan ke transmisi, seolah-olah nol ditransmisikan. Jadi, ukuran byte bisa 8 atau 9 bit.
Perlu juga disebutkan bahwa jalur data dalam USB adalah diferensial, yaitu ketika D + tinggi, D- rendah (ini disebut K-state) dan sebaliknya (J-state). Ini dilakukan untuk kekebalan kebisingan yang lebih baik pada frekuensi tinggi. Benar, ada pengecualian: sinyal di akhir paket (disebut SE0) ditransmisikan dengan menarik kedua garis sinyal ke ground (D + = D- = 0). Ada dua sinyal lagi yang ditransmisikan dengan menahan tegangan rendah pada garis D + dan tegangan tinggi pada saluran D + untuk waktu yang berbeda. Jika waktunya kecil (satu byte atau sedikit lebih lama), maka ini adalah Idle, jeda di antara paket, dan jika besar, sinyal reset.
Jadi, transmisi berada pada pasangan diferensial, belum termasuk kasus eksotis SE0, tapi kami belum akan mempertimbangkannya. Jadi untuk menentukan status bus USB, kita hanya perlu satu jalur, D + atau D-. Secara umum, tidak ada perbedaan yang harus dipilih, tetapi untuk kepastian biarkan D-.
Awal paket dapat ditentukan dengan menerima byte SYNC setelah Idle lama. Status Idle sesuai dengan log.1 pada D-line (ini juga merupakan J-state), dan byte SYNC adalah 0b100000, tetapi ditransmisikan dari bit paling tidak signifikan ke yang paling signifikan, apalagi, itu dikodekan dalam NRZI, yaitu, setiap nol berarti pembalikan sinyal, dan satu cara mempertahankan level yang sama. Jadi urutan status D- adalah sebagai berikut:
Awal paket paling mudah dideteksi pada sisi jatuh, dan kami akan mengonfigurasi interupsi di atasnya. Tetapi bagaimana jika controller sibuk selama awal penerimaan dan tidak dapat langsung memasuki interupsi? Untuk menghindari kehilangan jumlah lintasan dalam situasi seperti itu, kami menggunakan byte SYNC untuk tujuan yang dimaksud. Seluruhnya terdiri dari front di batas bit, sehingga kita bisa menunggu salah satunya, lalu setengah bit lagi, dan langsung menuju ke tengah bit berikutnya. Namun, menunggu bagian depan "beberapa" bukanlah ide yang baik, karena kita tidak hanya perlu masuk ke bagian tengah, tetapi juga untuk mengetahui bagian apa yang kita peroleh dalam skor. Dan untuk SYNC ini juga cocok: ia memiliki dua bit nol berturut-turut di akhir (mereka adalah K-state). Di sini kita akan menangkap mereka. Jadi, dalam file drvasm.S, sepotong kode muncul dari entri interupsi ke foundK. Selain itu, karena waktu untuk memeriksa status port, untuk transisi tanpa syarat dan seterusnya, kita mendapatkan tanda bukan pada awal bit, tetapi hanya di tengah. Tapi tidak ada gunanya memeriksa bit yang sama, karena kita sudah tahu artinya. Oleh karena itu, kami menunggu 8 siklus clock (sejauh ini kosong nop'ami) dan periksa bit berikutnya. Jika juga nol, maka kami telah menemukan akhir SYNC dan dapat melanjutkan ke penerimaan bit yang signifikan.
Sebenarnya, semua kode lebih lanjut dimaksudkan untuk membaca dua byte lagi dengan output berikutnya ke UART. Nah, menunggu keadaan SE0 agar tidak sengaja masuk ke paket selanjutnya.
Sekarang Anda dapat mengkompilasi kode yang dihasilkan dan melihat byte apa yang diterima perangkat kami. Secara pribadi, saya memiliki urutan berikut:
4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00
Ingat, kami mengeluarkan data mentah, tidak termasuk nol tambahan dan pengodean NRZI. Mari kita coba decode secara manual, dimulai dengan bit rendah:
Tidak masuk akal untuk mendekode nol, karena 16 nilai yang identik dalam satu baris tidak dapat dimasukkan dalam paket.
Dengan demikian, kami dapat menulis firmware yang menerima dua byte pertama dari paket, meskipun sejauh ini tanpa decoding.
Langkah 2. Versi demo NRZI
Agar tidak melakukan pengodean ulang secara manual, Anda bisa mempercayakan ini ke controller itu sendiri: operasi XOR melakukan apa yang Anda butuhkan, meskipun hasilnya terbalik, jadi tambahkan inversi lain setelahnya:
mov temp, shift lsl shift eor temp, shift com temp rcall uart_hex
Hasilnya cukup diharapkan:
2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF
Langkah 3. Singkirkan siklus penerimaan byte
Mari kita ambil satu langkah kecil lagi dan perluas siklus penerimaan byte pertama dalam kode linier. Jadi ternyata banyak nops, hanya perlu menunggu dimulainya bit berikutnya. Alih-alih beberapa dari mereka, Anda dapat menggunakan decoder NRZI, yang lain akan berguna nanti.
Hasil dari opsi sebelumnya tidak berbeda.
Langkah 4. Baca ke buffer
Membaca dalam register terpisah tentu saja cepat dan indah, tetapi ketika ada terlalu banyak data, lebih baik menggunakan entri buffer yang terletak di suatu tempat di RAM. Untuk melakukan ini, kita akan mendeklarasikan array dengan ukuran yang cukup di main, dan dalam interrupt kita akan menulis di sana.
Masukkan teoretis
Struktur paket di USB terstandarisasi dan terdiri dari bagian-bagian berikut: SYNC byte, PID + CHECK byte (masing-masing 2 bidang 4 bit), bidang data (kadang-kadang 11 bit, tetapi lebih sering jumlah sewenang-wenang dari 8-bit byte) dan jumlah pemeriksaan CRC sebesar 5 ( untuk bidang data 11-bit), atau 16 (untuk sisanya) bit. Akhirnya, akhir indikasi paket (EOP) adalah dua jeda bit, tetapi ini bukan lagi data.
Sebelum bekerja dengan array, Anda masih perlu mengkonfigurasi register, dan bebas nop sebelum bit pertama tidak cukup untuk ini. Oleh karena itu, Anda harus meletakkan pembacaan dua bit pertama ke dalam bagian linier kode, di antara perintah yang akan kami sisipkan kode inisialisasi, dan kemudian melompat ke tengah siklus pembacaan, ke label rxbit2. Berbicara tentang ukuran buffer. Menurut dokumentasi, dalam satu paket tidak mungkin untuk mentransfer lebih dari 8 byte data. Kami menambahkan layanan byte PID dan CRC16, kami mendapatkan ukuran buffer 11 byte. SYNC byte dan status EOP tidak akan ditulis. Kami tidak akan dapat mengontrol interval permintaan dari tuan rumah, tetapi kami juga tidak ingin kehilangannya, jadi kami akan mengambil margin ganda untuk membaca. Untuk saat ini, kami tidak akan menggunakan seluruh buffer, tetapi agar tidak kembali di masa mendatang, lebih baik segera mengalokasikan volume yang diperlukan.
Langkah 5. Bekerja dengan buffer secara manusiawi
Alih-alih membaca secara langsung byte pertama dari array, kita menulis sepotong kode yang membaca byte persis seperti yang sebenarnya ditulis ke array. Dan pada saat yang sama menambahkan pemisah antar paket.
Sekarang hasilnya terlihat seperti ini:
>03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF
Langkah 6. Menambahkan Aditif Nol Aditif
Akhirnya saatnya untuk selesai membaca bitstream ke standar. Item terakhir yang berhasil kami kelola adalah nol palsu, ditambahkan setiap enam unit berturut-turut. Karena kita memiliki penerimaan byte yang dikerahkan ke tubuh linier loop, Anda harus memeriksa setiap bit, di delapan tempat. Pertimbangkan dua bit pertama sebagai contoh:
unstuff0: ;1 ( breq) andi x3, ~(1<<0) ;1 [15] 0- . mov x1, x2 ;1 [16] () in x2, USBIN ;1 [17] <-- 1- . ori shift, (1<<0) ;1 [18] 0- .1 rjmp didUnstuff0 ;2 [20] ;<---//---> rxLoop: eor shift, x3 ;1 [0] in x1, USBIN ;1 [1] st y+, shift ;2 [3] ldi x3, 0xFF ;1 [4] nop ;1 [5] eor x2, x1 ;1 [6] bst x2, USBMINUS ;1 [7] 0- shift bld shift, 0 ;1 [8] in x2, USBIN ;1 [9] <-- 1- (, ) andi x2, USBMASK ;1 [10] breq se0 ;1 [11] andi shift, 0xF9 ;1 [12] didUnstuff0: breq unstuff0 ;1 [13] eor x1, x2 ;1 [14]; bst x1, USBMINUS ;1 [15] 1- shift bld shift, 1 ;1 [16] rxbit2: in x1, USBIN ;1 [17] <-- 2- (, ) andi shift, 0xF3 ;1 [18] breq unstuff1 ;1 [19] didUnstuff1:
Untuk kenyamanan navigasi, alamat perintah yang dijelaskan akan dihitung oleh label di sebelah kanan. Harap dicatat bahwa mereka diperkenalkan untuk menghitung siklus jam pengontrol, sehingga mereka tidak berurutan. Byte berikutnya dibaca pada label rxLoop, byte sebelumnya dibalik dan ditulis ke buffer [0, 3]. Selanjutnya, pada label [1], status D-line dibaca, menurut XOR dengan keadaan yang diterima sebelumnya, kami mendekode NRZI (Saya ingat bahwa XOR biasa menambahkan inversinya, untuk memperbaiki yang kita masukkan register topeng x3, diinisialisasi dengan unit 0xFF) dan menulis ke 0 dengan sedikit register geser [7,8]. Kemudian kesenangan dimulai - kami memeriksa apakah bit yang diterima adalah keenam yang tidak berubah. Bit konstan yang diterima dengan D- sesuai dengan penulisan nol (bukan satu! Kami akan berubah menjadi satu di akhir, XOR) dalam register. Oleh karena itu, Anda perlu memeriksa apakah bit 0, 7, 6, 5, 4, 3 adalah nol. Dua bit yang tersisa tidak masalah, mereka tetap dari byte sebelumnya dan diperiksa sebelumnya. Untuk menghilangkannya, kita memotong register dengan mask [12], di mana semua bit yang menarik bagi kita diatur ke 1: 0b11111001 = 0xF9. Jika setelah menerapkan mask semua bit ternyata nol, situasi menambahkan bit sudah diperbaiki dan ada transisi ke label unstuff0. Satu bit lagi [17] dibaca di sana daripada apa yang sebelumnya dibaca, dalam interval antara operasi lain, dari kelebihan [9]. Kami juga menukar register nilai saat ini dan sebelumnya x1, x2. Faktanya adalah bahwa pada setiap bit nilainya dibaca dalam satu register, dan kemudian XOR dengan yang lain, setelah itu register ditukar. Karena itu, ketika membaca register tambahan, operasi ini juga perlu dilakukan. Tetapi hal yang paling menarik adalah bahwa dalam register data shift kami menulis bukan nol, yang kami terima dengan jujur, tetapi unit yang tuan rumah coba untuk transfer [18]. Ini disebabkan oleh fakta bahwa ketika menerima bit berikutnya, nilai nol juga harus diperhitungkan, dan jika kami mencatat nol, pemeriksaan topeng tidak dapat mengetahui bahwa bit tambahan telah diperhitungkan. Dengan demikian, dalam register geser, semua bit terbalik (relatif terhadap yang ditransmisikan oleh tuan rumah), dan nol tidak. Untuk mencegah kekacauan seperti itu di buffer, kami akan melakukan inversi terbalik menurut XOR bukan dengan 0xFF [0], tetapi dengan 0xFE, yaitu, register di mana bit yang sesuai akan diatur ulang ke 0 dan, karenanya, tidak akan mengarah pada inversi. Untuk melakukan ini, pada sampel [15] dan reset bit nol.
Situasi serupa terjadi dengan bit 1-5. Katakanlah, bit 1 berhubungan dengan memeriksa 1, 0, 7, 6, 5, 4, sedangkan bit 2, 3 diabaikan. Ini sesuai dengan mask 0xF3.
Tetapi pemrosesan 6 dan 7 bit berbeda:
didUnstuff5: andi shift, 0x3F ;1 [45] 5-0 breq unstuff5 ;1 [46] ;<---//---> bld shift, 6 ;1 [52] didUnstuff6: cpi shift, 0x02 ;1 [53] 6-1 brlo unstuff6 ;1 [54] ;<---//---> bld shift, 7 ;1 [60] didUnstuff7: cpi shift, 0x04 ;1 [61] 7-2 brsh rxLoop ;3 [63] unstuff7:
Topeng untuk bit ke-6 adalah angka 0b01111110 (0x7E), tetapi Anda tidak dapat menempatkannya di register geser, karena itu akan mengatur ulang bit ke-0, yang harus ditulis ke array. Selain itu, pada hitungan mundur [45], topeng sudah ditumpangkan, mereset 7 bit. Oleh karena itu, perlu untuk memproses bit tambahan jika bit 1-6 sama dengan nol, dan bit ke 0 tidak masalah. Artinya, nilai register harus 0 atau 1, yang diperiksa dengan sempurna dengan membandingkan "kurang dari 2" [53, 54].
Prinsip yang sama digunakan untuk bit ke-7: alih-alih menerapkan masker 0xFC, pemeriksaan dilakukan untuk "kurang dari 4" [61, 63].
Langkah 7. Urutkan paket
Karena kami dapat menerima paket dengan byte pertama (PID) sama dengan 0x2D (SETUP), kami akan mencoba mengurutkan paket yang diterima. Ngomong-ngomong, mengapa saya memanggil paket 0x2D SETUP ketika tampaknya ACK? Faktanya adalah bahwa transmisi USB dari bit yang paling signifikan ke yang paling signifikan dilakukan dalam setiap bidang, dan bukan byte, sementara kami menerima byte-by-byte. Bidang signifikan pertama, PID, hanya membutuhkan 4 bit, diikuti oleh 4 bit PERIKSA lainnya, mewakili inversi bitwise dari bidang PID. Dengan demikian, byte pertama yang diterima tidak akan menjadi PID + PERIKSA, melainkan PERIKSA + PID. Namun, tidak ada banyak perbedaan, karena semua nilai diketahui sebelumnya, dan mudah untuk mengatur ulang camilan di tempat. Segera, kami akan menulis kode utama yang mungkin berguna bagi kami di file usbconfig.h.
Kami belum mulai menambahkan kode pemrosesan PID, perhatikan bahwa itu harus cepat (yaitu, dalam assembler), tetapi penyelarasan dengan jam tidak diperlukan, karena kami telah menerima paket. Oleh karena itu, selanjutnya bagian ini akan ditransfer ke file asmcommon.inc, yang akan berisi kode assembler yang tidak terikat ke frekuensi. Sementara itu, sorot saja komentarnya.
Sekarang mari kita lanjutkan untuk menyortir paket yang diterima.
Masukkan teoretis
Paket data pada bus USB digabungkan menjadi transaksi. Setiap transaksi dimulai dengan pengiriman oleh tuan rumah dari paket penanda khusus yang membawa informasi tentang apa yang tuan rumah ingin lakukan dengan perangkat: konfigurasikan (SETUP), kirim data (OUT) atau terima (IN). Setelah paket marker ditransmisikan, jeda dua bit mengikuti. Ini diikuti oleh paket data (DATA0 atau DATA1), yang dapat dikirim baik oleh tuan rumah dan perangkat, tergantung pada paket penanda. Selanjutnya, jeda panjang dua bit lainnya dan jawabannya adalah HANDSHAKE, paket konfirmasi (ACK, NAK, STALL, kami akan mempertimbangkannya lain kali).
Karena pertukaran berlangsung pada saluran yang sama, tuan rumah dan perangkat harus terus-menerus beralih antara transmisi dan penerimaan. Jelas, penundaan dua-bit tepat untuk tujuan ini dan dibuat agar mereka tidak mulai bermain push-push, saat mencoba mentransfer beberapa data secara bersamaan ke bus.
Jadi, kita tahu semua jenis paket yang dibutuhkan untuk pertukaran. Kami menambahkan cek byte PID yang diterima untuk kesesuaiannya. Saat ini, perangkat belum dapat menulis paket primitif seperti ACK ke bus, yang berarti tidak dapat memberi tahu tuan rumah apa itu. Oleh karena itu, perintah seperti IN tidak dapat diharapkan. Jadi kami hanya akan memeriksa penerimaan perintah SETUP dan OUT, yang kami akan menunjukkan dimasukkannya LED yang sesuai di cabang yang sesuai.
Selain itu, perlu mengambil pengiriman log di luar interupsi, di suatu tempat di utama.
Kami mem-flash perangkat dengan apa yang terjadi setelah melakukan perubahan ini dan mengamati urutan byte yang diterima berikut ini:
2D|80|06|00|01|00|00|40|00 C3|80|06|00|01|00|00|40|00 2D|80|06|00|01|00|00|40|00 C3|80|06|00|01|00|00|40|00
Dan selain itu - keduanya membakar LED. Jadi, kami menangkap SETUP dan OUT.
Langkah 8. Baca alamat di amplop
Masukkan teoretis
Paket penanda (SETUP, IN, OUT) berfungsi tidak hanya untuk menunjukkan perangkat apa yang mereka inginkan darinya, tetapi juga untuk mengatasi perangkat tertentu di bus dan ke titik akhir tertentu di dalamnya. Titik akhir diperlukan untuk secara fungsional menyorot subfungsi perangkat tertentu. Mereka dapat bervariasi dalam frekuensi pemungutan suara, nilai tukar, dan parameter lainnya. Katakanlah, jika perangkat tampaknya berupa adaptor USB-COM, tugas utamanya adalah menerima data dari bus dan mentransfernya ke port (titik akhir pertama) dan menerima data dari port dan mengirimkannya ke bus (kedua). Dari segi makna, titik-titik ini dimaksudkan untuk aliran besar data yang tidak terstruktur. Tapi selain itu, dari waktu ke waktu perangkat harus bertukar dengan tuan rumah status jalur kontrol (semua jenis RTS, DTR, dan lainnya) dan pengaturan pertukaran (kecepatan, paritas). Dan di sini, sejumlah besar data tidak diharapkan. Selain itu, nyaman ketika informasi layanan tidak dicampur dengan data. Jadi ternyata nyaman untuk menggunakan setidaknya 3 titik akhir untuk adaptor USB-COM. Dalam praktiknya, tentu saja, itu terjadi dengan cara yang berbeda ...
Pertanyaan yang sama menariknya adalah mengapa perangkat mengirim alamatnya, karena selain itu, Anda masih tidak bisa memasukkan apa pun ke port khusus ini. Hal ini dilakukan untuk mempermudah pengembangan hub USB. Mereka bisa sangat "bodoh" dan hanya menyiarkan sinyal dari host ke semua perangkat tanpa khawatir tentang penyortiran. Dan perangkat itu sendiri akan mencari tahu, memproses paket atau mengabaikannya.
Jadi, baik alamat perangkat dan alamat titik akhir terkandung dalam paket penanda. Struktur paket tersebut diberikan di bawah ini:
lapangan
, - ( - PID = SETUP OUT) (IN) , .
, (-) (Handshake) :
- : , , NAK
- -: SETUP OUT, , IN β ,
- . , , ,
Β« β Β» . PID', , . Β«PIDΒ» . usbCurrentTok. PID' (DATA0, DATA1) , . , ? : , ( 0 usbCurrentTok ), , . ( SE0) , - , D+, D- . , SYNC, . , , . «» , . .
, . x3, (, , , ).
, USB , , . , , , CRC ( ). , [21]. 0- . , [26]. , CRC, .
9.
, , Β« Β», ACK. NAK', ( cnt β ). USB , , SYNC PID. Y, cnt ( ). , β ACK. x3 β 1 , . x3 ( r20) 20.
( SETUP, ), ACK' , , , . , .
, D+, D- ( ), β . XOR , , , , - .
, , , , . , , , . . vusb : txBitloop 2 ([00], [08]). 3 , 6 . , . 1 3 : 171. ( 171, 11 , ), β , . cnt=4:
4 β 171 = -167 = ( ) 89 (+ )
89 β 171 = -82 = ( ) 174 (+ )
174 β 171 = 3. ,
, .
, 3 , 1. 6 , , x4. D+, D- , . .
:
2D|80|06|00|01|00|00|40|00 69|00|10|00|01|00|00|40|00
C3 . , , UART . , , IN , . , .
10. NAK
NAK , . , . , - .
, . , , - , . usbRxBuf, . , β , USB_BUFSIZE. usbInputBufOffset, . .
NAK handleData , [22]. (usbRxLen), - . ( β ), usbRxLen, , β usbRxToken, SETUP OUT - . : , , ACK .
. , , - , -, . ? , , , , - .
,
2D|80|06|00|01|00|00|40|00
, NAK`, , .
11.
, , . β . , , , , , . . . , USB, usbPoll. β , . β . SETUP , PID CRC, SETUP 5- , 16-. 3 «» . «» PID usbRxToken, CRC , , . usbProcessRx, , .
, , β , SE0. , USB .
. SETUP, . . SETUP usbRequest_t 8 . : ( USB-) , - . , . .
, , , .
12. SETUP'
, , . . usbDriverSetup, . , . , ( , , ) . , : ACK NAK, .
13.
, SETUP + DATAx, DATAx 8 . IN DATAx, . , . , ACK NAK. , . β usbTxBuf, , usbTxLen . low-speed USB 8 ( PID, CRC), usbTxLen 11. PID, , . , 16, , 0x0F, . PID , . IN, , (handshake , ).
:
SETUP + DATAx, ACK NAK . , , usbPoll, , ( PID=DATA1 ( DATA0 DATA1 , , DATA1). CRC . , , - . β 4 . , 3 , 4. , SYNC . Β« IN NAK?Β» NAK. , , DATA1 .
, β USBRQ_SET_ADDRESS ( , ). . (drvsdm.S, make SE0). , , , DATA1 , , . , , , , , . , , .
14.
, . , USBRQ_GET_DESCRIPTOR USBRQ_SET_ADDRESS, , . usbDriverDescriptor, . , USBRQ_GET_DESCRIPTOR. , , :
USBDESCR_DEVICE β : USB (1.1 ), , , . .
USBDESCR_CONFIG β , , . .
USBDESCR_STRING β , .
, , USBDESCR_DEVICE, , .
15.
. -, . , - - , , HID, , . Vendor ID Product ID, USB, . , vusb .
, , - . , , , (, ) usbMsgPtr, β len, usbMsgLen. ( ) 18 , 8. , , 3 . - , STALL.
usbDeviceRead. , memcpy_P, , , .
, , , . , , .
, , .
PID' DATA0 DATA1 . PID' , , - .
, DATA0 / DATA1 ( ), , , 3 , . XOR PID', . , , XOR' . PID DATA1, XOR PID , XOR DATA0 .
, , USBDESCR_CONFIG.
16. - !
USBDESCR_CONFIG USBDESCR_DEVICE. ( , ) . , - USB-, , D+, D-.
, : , , . , ( , ). , UTF-16, . USB UTF-8 .
vusb , lsusb . VID, PID , . , VID, PID, β .
, , ( ). SETUP: , , . 0, , β . , , , .
.
17. (HID)
HID β human interface device, , , . HID , . , , , , , . «» . HID ( low-speed 800 ), .
HID , USBDESCR_HID_REPORT. vusb, . , usbDriverSetup ( ) usbFunctionSetup ( ). , SETUP, OUT. , , , usbFunctionWrite.
, usbDeviceRead usbFunctionRead, . , , usbFunctionSetup ( , ) USB_FLG_USE_USER_RW, usbDriverSetup .
β β usbFunctionWrite usbFunctionRead. . β , .
usbDriverSetup.
18.
, , . HID, , , ( udev - ). , , . , , , .
UPD: ramzes2 , HIDAPI
.
19. vusb
vusb , .
drvasm.S - usbdrvasm.S asmcommon.inc, -, , usbdrvasm12.inc β usbdrvasm20.inc.
main.c main.c ( ) usbdrv.c ( vusb)
usbconfig.h ( ), , , usbconfig.h.
Kesimpulan
vusb, , , . , , . . , , , USB-HID. , , , vusb, , , , .
https://www.obdev.at/products/vusb/index.html ( vusb)
http://microsin.net/programming/arm-working-with-usb/usb-in-a-nutshell-part1.html
.. USB:
https://radiohlam.ru/tag/usb/
http://we.easyelectronics.ru/electro-and-pc/usb-dlya-avr-chast-1-vvodnaya.html
http://usb.fober.net/cat/teoriya/
PS - (, ) ,