Kartu Suara USB pada YM3812

Saya suka game komputer lama. Saya suka besi tua, tetapi tidak cukup untuk mengumpulkannya di rumah. Hal lain adalah memilih chip lama dan mencoba mereproduksi sesuatu sendiri, gabungkan yang lama dengan yang baru. Pada artikel ini, ceritanya tentang bagaimana saya menghubungkan mikrokontroler AVR ke YM3812, yang digunakan dalam kartu suara seperti Adlib, Sound Blaster dan Pro AudioSpectrum. Saya tidak menciptakan sesuatu yang pada dasarnya baru, saya hanya menggabungkan berbagai ide. Mungkin seseorang akan tertarik dengan implementasi saya. Atau mungkin pengalaman saya akan mendorong seseorang untuk membuat proyek retro mereka sendiri.


Inti dari proyek ini


Berjalan di Internet, suatu hari saya menemukan proyek yang menarik Dewan Audio OPL2 untuk Arduino & Raspberry Pi . Singkatnya: sambungkan papan ke Arduino atau Raspberry Pi, muat sketsa atau perangkat lunak, masing-masing, dengarkan. Gagasan yang menggoda untuk memilih chip OPL2, mendengarkan bunyinya dan mencoba melakukan sesuatu sendiri tidak meninggalkan saya, dan saya memesan, mengumpulkan, dan mulai mencari tahu cara kerjanya.


Beberapa kata tentang manajemen chip YM3812


Agar musik dapat diputar, kita harus mengatur register. Beberapa bertanggung jawab untuk menyetel instrumen, beberapa untuk memainkan catatan, dll. Alamat register adalah 8 bit. Nilai register adalah 8 bit. Daftar register diberikan dalam spesifikasi .


Untuk mentransfer register, kita harus mengatur pembacaan dengan benar pada input kontrol CS, RD, WR dan A0 dan bus data D0..D7.


Input CS diperlukan untuk memblokir bus data selama instalasi. Set CS = 1 (matikan input), set D0..D7, set CS = 0 (nyalakan).


Input RD harus unit logis
Untuk menulis alamat register, atur WR = 0, A0 = 0
Untuk menulis nilai register, atur WR = 0, A0 = 1


Papan Audio OPL2 untuk Arduino & Raspberry Pi


Skema yang disederhanakan


Registrasi Prosedur Transfer:


  1. Selama inisialisasi, atur PB2 = 1 untuk memblokir input YM3812
  2. Kami melewati alamat register
    2.1 PB1 = 0 (A0 = 0)
    2.2 Kami mengirimkan byte alamat register melalui antarmuka SPI. Data disimpan dalam register geser 74595
    2.3 PB2 = 0 (WR = 0, CS = 0). Chip 7404 membalikkan sinyal dan memasok 1 ke input ST_CP 74595 , yang mengalihkan outputnya Q0..Q7. YM3812 menulis alamat register
    2.4 PB2 = 1 (WR = 1, CS = 1)
  3. Kami melewati nilai register
    3.1 PB1 = 1 (A0 = 1)
    3.2 Kami mentransfer byte data melalui antarmuka SPI yang serupa dengan hal.2.2
    3,3 PB2 = 0 (WR = 0, CS = 0). YM3812 menulis data
    3,4 PB2 = 1 (WR = 1, CS = 1)

Inverter 7404 dan kuarsa XTAL1 mengimplementasikan generator pulsa persegi panjang dengan frekuensi 3,579545 MHz, yang diperlukan untuk pengoperasian YM3812 .
YM3014B mengubah sinyal digital menjadi sinyal analog, yang diperkuat oleh penguat operasional LM358 .
Penguat audio LM386 diperlukan agar speaker atau headphone pasif dapat dihubungkan ke perangkat Daya LM358 tidak cukup.


Sekarang mari kita coba mengekstrak suara dari semua ini. Hal pertama yang saya (dan mungkin bukan hanya saya) pikirkan adalah bagaimana membuat semuanya berfungsi di DosBox. Sayangnya, bermain di luar kotak dengan perangkat keras Adlib tidak akan berhasil, karena DosBox tidak tahu apa-apa tentang perangkat kami, dan tidak tahu bagaimana mengirimkan perintah OPL2 di mana saja (sejauh ini tidak).


Penulis proyek menawarkan sketsa untuk Teensy, yang berfungsi sebagai perangkat MIDI. Secara alami, suara akan terdiri dari instrumen yang telah dikompilasi dan suara akan berbeda, kita akan mendapatkan emulasi perangkat MIDI pada chip OPL2. Saya tidak punya Teensy, dan saya tidak bisa mencoba opsi ini.


Operasi Port Serial


Ada SerialPassthrough sketsa. Dengan itu, kita dapat mengirimkan perintah melalui port serial. Tetap hanya menerapkan dukungan di DoxBox. Saya menggunakan versi dari SVN: svn://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk


Dalam file src/hardware/adlib.cpp kami mengubah implementasi OPL2:


 #include "serialport/libserial.h" namespace OPL2 { #include "opl.cpp" struct Handler : public Adlib::Handler { virtual void WriteReg( Bit32u reg, Bit8u val ) { //adlib_write(reg,val); if (comport) { SERIAL_sendchar(comport, reg); SERIAL_sendchar(comport, val); } } virtual Bit32u WriteAddr( Bit32u port, Bit8u val ) { return val; } virtual void Generate( MixerChannel* chan, Bitu samples ) { Bit16s buf[1024]; while( samples > 0 ) { Bitu todo = samples > 1024 ? 1024 : samples; samples -= todo; adlib_getsample(buf, todo); chan->AddSamples_m16( todo, buf ); } } virtual void Init( Bitu rate ) { adlib_init(rate); LOG_MSG("Init OPL2"); if (!SERIAL_open("COM4", &comport)) { char errorbuffer[256]; SERIAL_getErrorString(errorbuffer, sizeof(errorbuffer)); LOG_MSG("Serial Port could not be opened."); LOG_MSG("%s", errorbuffer); return; } if (!SERIAL_setCommParameters(comport, 115200, 'n', SERIAL_1STOP, 8)) { LOG_MSG("Error serial set parameters"); SERIAL_close(comport); return; } } ~Handler() { if (comport) SERIAL_close(comport); } private: COMPORT comport; }; } 

Sebelum perakitan, ganti nomor port COM dengan yang sekarang.


Jika Anda menghapus komentar di baris //adlib_write(reg,val); , maka suara akan diputar secara simultan melalui emulator dan perangkat.


Di pengaturan DosBox, Anda perlu menentukan penggunaan OPL2:


 [sblaster] oplemu=compat oplmode=opl2 

Begini cara saya mendapatkannya:



Itu terlihat cukup besar. Bahkan jika Anda menggunakan Arduino alih-alih papan tempat memotong roti, Anda perlu menghubungkan kabel. Nomor port pada sistem dapat berubah dan Anda harus membangun kembali DosBox. Saya benar-benar ingin membawa semuanya ke tampilan yang ringkas, menghapus bagian yang tidak perlu dan mengumpulkan semuanya di satu papan.


OPL2-USB


Sebuah ide muncul, dan mengapa tidak membuat perangkat independen dengan minimum komponen dan masalah saat terhubung. Pertama, Anda dapat menghapus 74595 dan menggunakan port atmega. Di sini digunakan hanya untuk mengurangi jumlah kabel. Kedua, Anda dapat menggunakan osilator kristal yang sudah jadi dan menyingkirkan chip 7404 . Penguat audio juga tidak diperlukan jika Anda menghubungkan perangkat ke speaker. Dan akhirnya, Anda dapat menyingkirkan USB-UART jika Anda menghubungkan atmega ke USB secara langsung, misalnya menggunakan perpustakaan V-USB: https://www.obdev.at/products/vusb/index.html . Agar tidak perlu repot menulis driver dan menginstalnya, Anda dapat menjadikan mikrokontroler sebagai perangkat HID khusus.


Sirkuit sederhana USB-OPL2


Port B dan C sebagian sibuk menghubungkan ke programmer ISP dan kuarsa. Port D tetap sepenuhnya gratis, kami menggunakannya untuk transfer data. Saya menetapkan port yang tersisa dalam proses desain PCB.


Skema lengkap dapat dipelajari di sini: https://easyeda.com/marchukov.ivan/opl2usb


LED1 dengan resistornya adalah opsional dan selama perakitan saya tidak menginstalnya. Sekering U4 diperlukan agar tidak secara sengaja membakar port USB. Itu juga tidak bisa diatur, tetapi diganti dengan jumper.


Untuk membuat perangkat kompak, saya memutuskan untuk mencoba merakitnya pada komponen SMD.


Papan sirkuit tercetak dan perangkat jadi



"Aman" pilihan dalam panas menyusut 50 / 25mm


Bagian digital di sebelah kiri, analog di sebelah kanan.


Bagi saya, ini adalah pengalaman pertama dalam mendesain dan merakit perangkat jadi dan tidak bisa melakukannya tanpa tiang tembok. Misalnya, lubang di sudut papan harus berdiameter 3 mm untuk rak, tetapi ternyata berukuran 1,5 mm.


Firmware dapat dilihat di github . Dalam versi sebelumnya, satu perintah dikirim dalam satu paket USB. Kemudian ternyata pada trek dinamis DosBox mulai melambat karena overhead yang besar dan kecepatan rendah USB 1.0, DosBox hang mengirim pengiriman paket dan menerima respons. Saya harus membuat antrian asinkron dan mengirim perintah dalam batch. Ini menambahkan sedikit keterlambatan, tetapi tidak terlihat.


Pengaturan V-USB


Jika kita sudah tahu mengirim data ke YM3812 sebelumnya, maka USB harus mengotak-atik.


Ganti nama usbconfig-prototype.h menjadi usbconfig.h dan tambahkan (di bawah ini hanya suntingan):


 //   .   define       #define F_CPU 12000000UL //    #define USB_CFG_IOPORTNAME B #define USB_CFG_DMINUS_BIT 0 #define USB_CFG_DPLUS_BIT 1 #define USB_CFG_HAVE_INTRIN_ENDPOINT 1 //    20  #define USB_CFG_MAX_BUS_POWER 20 // ,      usbFunctionWrite #define USB_CFG_IMPLEMENT_FN_WRITE 1 //     (    OPL2) #define USB_RESET_HOOK(resetStarts) if(!resetStarts){hadUsbReset();} //  .         #define USB_CFG_DEVICE_ID 0xdf, 0x05 /* VOTI's lab use PID */ #define USB_CFG_VENDOR_NAME 'd', 'e', 'a', 'd', '_', 'm', 'a', 'n' #define USB_CFG_VENDOR_NAME_LEN 8 #define USB_CFG_DEVICE_NAME 'O', 'P', 'L', '2' #define USB_CFG_DEVICE_NAME_LEN 4 // ,    HID- #define USB_CFG_DEVICE_CLASS 0 #define USB_CFG_INTERFACE_CLASS 3 //   usbHidReportDescriptor #define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 22 //      INT0,      PCINT0 #define USB_INTR_CFG PCICR #define USB_INTR_CFG_SET (1 << PCIE0) #define USB_INTR_CFG_CLR 0 #define USB_INTR_ENABLE PCMSK0 #define USB_INTR_ENABLE_BIT PCINT0 #define USB_INTR_VECTOR PCINT0_vect 

Dalam file main.c , kami mendefinisikan struktur data paket


 //      #define BUFF_SIZE 16 //  -   struct command_t { uchar address; uchar data; }; //   struct dataexchange_t { uchar size; struct command_t commands[BUFF_SIZE]; } pdata; 

Nyatakan pegangan untuk HID


 PROGMEM const char usbHidReportDescriptor[] = { // USB report descriptor 0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page) 0x09, 0x01, // USAGE (Vendor Usage 1) 0xa1, 0x01, // COLLECTION (Application) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, sizeof(struct dataexchange_t), // REPORT_COUNT 0x09, 0x00, // USAGE (Undefined) 0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf) 0xc0 // END_COLLECTION }; 

Penangan acara:


 //    .         static uchar currentAddress; static uchar bytesRemaining; //   uchar usbFunctionWrite(uchar *data, uchar len) { if (bytesRemaining == 0) return 1; if (len > bytesRemaining) len = bytesRemaining; uchar *buffer = (uchar*)&pdata; memcpy(buffer + currentAddress, data, len); currentAddress += len; bytesRemaining -= len; if (bytesRemaining == 0) { for (int i = 0; i < pdata.size; ++i) { struct command_t cmd = pdata.commands[i]; if (cmd.address == 0xff && cmd.data == 0xff) //    OPL2      FFFF opl_reset(); else opl_write(cmd.address, cmd.data); } } return bytesRemaining == 0; } //    USBRQ_HID_SET_REPORT       usbMsgLen_t usbFunctionSetup(uchar data[8]) { usbRequest_t *rq = (void*)data; if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) { if (rq->bRequest == USBRQ_HID_SET_REPORT) { bytesRemaining = sizeof(struct dataexchange_t); currentAddress = 0; return USB_NO_MSG; } } return 0; /* default for not implemented requests: return no data back to host */ } //      extern void hadUsbReset(void) { opl_reset(); } 

Saya merekomendasikan artikel berbahasa Rusia ini tentang V-USB:
http://microsin.net/programming/avr-working-with-usb/avr-v-usb-tutorial.html
http://we.easyelectronics.ru/electro-and-pc/usb-dlya-avr-chast-2-hid-class-na-v-usb.html


Dukungan DosBox


Kode untuk DosBox dapat dilihat di repositori yang sama .


Untuk bekerja dengan perangkat di sisi PC, saya menggunakan perpustakaan hidlibrary.h (sayangnya, saya tidak menemukan tautan ke aslinya), yang harus diubah sedikit.


Saya memutuskan untuk tidak menyentuh emulator OPL, tetapi untuk mengimplementasikan kelas terpisah saya sendiri. Beralih ke USB dalam konfigurasi sekarang terlihat seperti ini:


 [sblaster] oplemu=usb 

Dalam konstruktor modul Adlib di adlib.cpp tambahkan ketentuan:


  else if (oplemu == "usb") { handler = new OPL2USB::Handler(); } else { 

Dan di dosbox.cpp opsi konfigurasi baru:


 const char* oplemus[]={ "default", "compat", "fast", "mame", "usb", 0}; 

Exe yang dikompilasi dapat diambil di sini: https://github.com/deadman2000/usb_opl2/releases/tag/0.1


Video


Perangkat siap beraksi

Koneksi:



Suara yang direkam melalui kartu suara:





Hasil dan rencana


Saya puas dengan hasilnya. Menghubungkan perangkat itu mudah, tidak ada masalah. Tentu saja, modifikasi DosBox saya tidak akan pernah masuk ke versi resmi dan cabang populer, seperti Ini adalah solusi yang sangat spesifik.


Baris berikutnya adalah memilih OPL3. Masih ada ide untuk membangun pelacak pada chip OPL


Proyek serupa


VGM Player


Kartu Suara OPL2 di ISA Bus

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


All Articles