بطاقة صوت USB على YM3812

أنا أحب ألعاب الكمبيوتر القديمة. أنا أحب الحديد القديم ، ولكن ليس بما يكفي لجمعها في المنزل. شيء آخر هو اختيار بعض الشرائح القديمة ومحاولة إعادة إنتاج شيء بنفسك ، والجمع بين القديم والجديد. في هذه المقالة ، تدور القصة حول كيفية توصيل متحكم AVR بجهاز YM3812 ، والذي تم استخدامه في بطاقات الصوت مثل Adlib و Sound Blaster و Pro AudioSpectrum. لم أخلق شيئًا جديدًا بشكل أساسي ، لقد جمعت ببساطة أفكارًا مختلفة. ربما شخص ما سوف تكون مهتمة في تنفيذ بلدي. أو ربما ستدفع تجربتي شخصًا إلى إنشاء مشروع الرجعية الخاص به.


جوهر هذا المشروع


أثناء التجول في شبكة الإنترنت ، واجهت يومًا ما مشروعًا رائعًا OPL2 Audio Board لـ Arduino & Raspberry Pi . باختصار: قم بتوصيل لوحة Arduino أو Raspberry Pi ، قم بتحميل رسم أو برنامج ، على التوالي ، استمع. فكرة مغرية لاختيار شريحة OPL2 ، والاستماع إلى كيف يبدو ومحاولة القيام بشيء من جانبي ، لم تتركني ، وطلبت وتجميعت وبدأت في معرفة كيف تعمل.


بضع كلمات عن إدارة رقاقة YM3812


لتشغيل الموسيقى ، يجب علينا تعيين سجلات. بعضها مسؤول عن ضبط الأدوات ، وبعضها عن تشغيل الملاحظات ، إلخ. عنوان السجل هو 8 بت. قيمة السجل هي 8 بت. وترد قائمة السجلات في المواصفات .


لنقل السجلات ، يجب أن نضبط بشكل صحيح قراءات مدخلات التحكم CS و RD و WR و A0 وحافلة البيانات D0..D7.


هناك حاجة إلى إدخال CS لمنع ناقل البيانات أثناء تثبيته. اضبط CS = 1 (أوقف تشغيل الإدخال) ، اضبط D0..D7 ، اضبط CS = 0 (تشغيل).


يجب أن تكون مدخلات RD وحدة منطقية
لكتابة عنوان السجل ، اضبط WR = 0 ، A0 = 0
لكتابة قيمة السجل ، اضبط WR = 0 ، A0 = 1


OPL2 Audio Board for Arduino & Raspberry Pi


مخطط مبسط


إجراء نقل التسجيل:


  1. أثناء التهيئة ، اضبط PB2 = 1 لحظر إدخال YM3812
  2. نحن نعبر عنوان التسجيل
    2.1 PB1 = 0 (A0 = 0)
    2.2 ننقل وحدات بايت عنوان التسجيل عبر واجهة SPI. يتم تخزين البيانات في سجل التحول 74595
    2.3 PB2 = 0 (WR = 0 ، CS = 0). رقاقة 7404 تحول الإشارة وتزود 1 إلى المدخلات من ST_CP 74595 ، والتي تحول مخرجاتها Q0..Q7. YM3812 يكتب عنوان التسجيل
    2.4 PB2 = 1 (WR = 1 ، CS = 1)
  3. نمر بقيمة السجل
    3.1 PB1 = 1 (A0 = 1)
    2.3 نقوم بنقل وحدات بايت البيانات عبر واجهة SPI بشكل مشابه للصفحة 2.2
    3.3 PB2 = 0 (WR = 0 ، CS = 0). YM3812 يكتب البيانات
    3.4 PB2 = 1 (WR = 1 ، CS = 1)

ينفذ العاكس 7404 والكوارتز XTAL1 مولد نبض مستطيل بتردد قدره 3.579545 ميغاهرتز ، وهو أمر ضروري لتشغيل YM3812 .
يحول YM3014B إشارة رقمية إلى إشارة تمثيلية ، يتم تضخيمها بواسطة مكبر للصوت التشغيلي LM358 .
LM386 مضخم صوت LM386 بحيث يمكن توصيل مكبرات الصوت أو سماعات الرأس السلبية بالجهاز السلطة LM358 ليست كافية.


الآن دعونا نحاول استخراج الصوت من كل هذا. أول شيء فكرت به (وربما ليس أنا فقط) هو كيفية جعل كل شيء يعمل في DosBox. لسوء الحظ ، لن ينجح اللعب خارج الجهاز باستخدام Adlib ، لأن لا يعرف DosBox أي شيء عن الجهاز لدينا ، ولا يعرف كيفية نقل أوامر OPL2 في أي مكان (حتى الآن لا يعرف ذلك).


يقدم مؤلف المشروع رسم تخطيطي لـ Teensy ، يعمل كجهاز MIDI. وبطبيعة الحال ، سوف يتكون الصوت من أدوات تم تجميعها مسبقًا وسيكون الصوت مختلفًا ، وسنحصل على محاكاة لجهاز MIDI على رقاقة OPL2. ليس لدي Teensy ، ولم أستطع تجربة هذا الخيار.


تشغيل المنفذ التسلسلي


هناك رسم SerialPassthrough . مع ذلك ، يمكننا نقل الأوامر من خلال المنفذ التسلسلي. يبقى فقط لتنفيذ الدعم في DoxBox. لقد استخدمت الإصدار من SVN: svn://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk


في ملف src/hardware/adlib.cpp نقوم بتغيير تطبيق 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; }; } 

قبل التجميع ، استبدل رقم منفذ COM بالرقم الحالي.


إذا قمت بإزالة التعليق في السطر //adlib_write(reg,val); ، ثم سيتم تشغيل الصوت في وقت واحد من خلال المحاكي والجهاز.


في إعداد DosBox ، ستحتاج إلى تحديد استخدام OPL2:


 [sblaster] oplemu=compat oplmode=opl2 

إليك كيف حصلت عليها:



يبدو ضخم جدا. حتى إذا كنت تستخدم Arduino بدلاً من اللوح ، فأنت بحاجة إلى توصيل الأسلاك. قد يتغير رقم المنفذ على النظام وسوف تضطر إلى إعادة إنشاء DosBox. أردت حقًا أن أحضر كل شيء لإلقاء نظرة موجزة ، وأزل الأجزاء غير الضرورية ، وقم بتجميع كل شيء على لوحة واحدة.


OPL2-USB


ظهرت فكرة ، ولماذا لا تصنع جهازًا مستقلًا بحد أدنى من المكونات والمشاكل عند الاتصال. أولاً ، يمكنك إزالة 74595 واستخدام منافذ atmega. هنا يتم استخدامه فقط لتقليل عدد الأسلاك. ثانياً ، يمكنك استخدام مذبذب بلوري جاهز والتخلص من شريحة 7404 . لا يلزم أيضًا مضخم الصوت إذا قمت بتوصيل الجهاز بالسماعات. وأخيراً ، يمكنك التخلص من USB-UART إذا قمت بتوصيل atmega بـ USB مباشرةً ، على سبيل المثال باستخدام مكتبة V-USB: https://www.obdev.at/products/vusb/index.html . لكي لا تهتم بكتابة برامج التشغيل وتثبيتها ، يمكنك جعل متحكم جهاز HID مخصص.


USB-OPL2 دائرة مبسطة


المنافذ B و C مشغولة جزئيًا بالاتصال بمبرمج ISP والكوارتز. ظل المنفذ D مجانيًا تمامًا ، ونحن نستخدمه لنقل البيانات. قمت بتعيين المنافذ المتبقية في عملية تصميم PCB.


يمكن دراسة المخطط الكامل هنا: https://easyeda.com/marchukov.ivan/opl2usb


LED1 مع المقاوم لها هو اختياري وخلال التجمع لم أكن تثبيتها. مطلوب فتيل U4 حتى لا يحرق منفذ USB عن طريق الخطأ. لا يمكن أيضًا تعيينها ، ولكن يتم استبدالها بلوفر.


لجعل الجهاز مضغوطًا ، قررت محاولة تجميعه على مكونات SMD.


لوحات الدوائر المطبوعة والجهاز النهائي



خيار "آمن" في الحرارة يتقلص 50 / 25mm


الجزء الرقمي على اليسار ، التناظرية على اليمين.


بالنسبة لي ، كانت هذه أول تجربة في تصميم وتجميع الجهاز النهائي ولا يمكن الاستغناء عن عضادة. على سبيل المثال ، يجب أن يكون قطر الثقوب الموجودة في زوايا اللوح 3 مم للرفوف ، لكن تبين أن 1.5 مم.


يمكن الاطلاع على البرامج الثابتة على جيثب . في الإصدار السابق ، تم إرسال أمر واحد في حزمة USB واحدة. ثم اتضح أنه في المسارات الديناميكية يبدأ DosBox في التباطؤ بسبب الحمل الكبير والسرعة المنخفضة لـ USB 1.0 ، يتم تعليق DosBox على إرسال حزمة وتلقي استجابة. اضطررت إلى إنشاء قائمة انتظار غير متزامنة وإرسال الأوامر على دفعات. أضاف هذا تأخيرًا بسيطًا ، لكنه ليس ملحوظًا.


إعداد V-USB


إذا اكتشفنا بالفعل إرسال البيانات إلى YM3812 في وقت سابق ، فسيتعين على USB العبث بها.


إعادة تسمية usbconfig-prototype.h إلى usbconfig.h وإضافته (فيما يلي التعديلات فقط):


 //   .   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 

في ملف main.c ، نحدد هياكل بيانات الطرود


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

قم بتعريف مؤشر 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 }; 

معالجات الأحداث:


 //    .         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(); } 

أوصي بمقالات باللغة الروسية حول 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


دعم DosBox


يمكن عرض رمز DosBox في نفس المستودع .


للعمل مع الجهاز على جانب الكمبيوتر الشخصي ، استخدمت مكتبة hidlibrary.h (لسوء الحظ ، لم أجد روابط إلى الأصل) ، والتي كان لا بد من تعديلها قليلاً.


قررت عدم لمس محاكي OPL ، ولكن لتطبيق صفي منفصلة. يبدو التحول إلى USB في التكوينات الآن كما يلي:


 [sblaster] oplemu=usb 

في مُنشئ وحدة Adlib في adlib.cpp أضف الشرط:


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

وفي dosbox.cpp خيار التكوين الجديد:


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

يمكن جمع إكس المترجمة هنا: https://github.com/deadman2000/usb_opl2/releases/tag/0.1


فيديو


جهاز جاهز في العمل

إتصال:



الصوت المسجل من خلال بطاقة الصوت:





النتائج والخطط


كنت راضيا عن النتيجة. من السهل توصيل الجهاز دون أي مشاكل. بالطبع ، لن تدخل تعديلات DosBox في الإصدار الرسمي والفروع الشائعة ، مثل هذا هو حل محدد للغاية.


التالي في السطر هو اختيار OPL3. لا تزال هناك فكرة لبناء تعقب على رقائق OPL


مشاريع مماثلة


مشغل VGM


بطاقة الصوت OPL2 على ISA حافلة

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


All Articles