Question about timing
Closed this issue · 11 comments
Hi @AidanHockey5! Thank you for open-sourcing this. I have a question and hopefully you can provide some insight.
I'm currently trying to get some basic interfacing between a Teensy 3.5 and the YM2612, but I'm seeing some weirdness in the timing of the data bus.
In your code, you use a 110nS pause (via wait10nS(11)
) between toggling the data bus pins which by all appearances in your YouTube videos, seems to work quite well!
However, when I attempt to us a bus speed that fast I get lots of weird artifacts. Sometimes the chip doesn't work at all and sometimes the audio is weird indicating that the bus is being misread. I had to bump mine up to 1000nS pauses (roughly 10x your bus speed!) to get stable operation. That seems to work totally fine but I'm confused as to why.
There are two significant differences between your setup and mine:
- I use pin 4 to generate an 8Mhz PWM signal for the clock instead of a programmable clock or crystal (for now).
- I'm using different pins on the Teensy for the control pins.
Any ideas? Do you happen to know what the actual bus speed is for the YM2612?
Thanks in advance!
Thinking somewhat theoretically here, if the YM2612's bus clocks at the same rate as its input clock (which I don't think is likely) then a 7.6Mhz clock would mean the bus period is 132ns. That would (maybe?) explain why your timing seems to work, but I'm still baffled why it's not working for me as well- my clock is slightly faster which means it should be more tolerant of the lower pauses and doesn't explain at all why I need roughly 10x(!) as long.
Dug some some more info by reading the datasheet for the predecessor, then YM2203. Based on it's datasheet, the bus is decoupled from the clock (which is much more in line with my expectations) and the timing goes like this:
- Set A0/A1 lines and wait at least 10ns
- Pull CS AND WR low
- Set data lines and wait at least 100ns
- At least 200ns since pulling CS and WR low, pull them high.
- Hold the data lines stable for at least 10ns
10ns + 200ns + 10ns = 210ns total.
Working from that and some trial and error this seems pretty stable, but I still don't entirely understand why I seem to need significantly higher pause times:
void ym_set_reg(byte address, byte data) {
digitalWriteFast(YM_A1, LOW);
digitalWriteFast(YM_A0, LOW);
SendYMByte(address);
wait10nS(1);
digitalWriteFast(YM_CS, LOW);
digitalWriteFast(YM_WR, LOW);
wait10nS(200); // data lines must settle for at least 100ns, but we shouldn't raise the WR and CS until at least 200ns have passed.
digitalWriteFast(YM_WR, HIGH);
digitalWriteFast(YM_CS, HIGH);
wait10nS(50);
digitalWriteFast(YM_A0, HIGH);
SendYMByte(data);
wait10nS(1);
digitalWriteFast(YM_CS, LOW);
digitalWriteFast(YM_WR, LOW);
wait10nS(200);
digitalWriteFast(YM_WR, HIGH);
digitalWriteFast(YM_CS, HIGH);
wait10nS(50);
}
Hello!
I've taken some time to translate the Japanese YM3438 application manual's section on timings. The YM3438 is the CMOS equivalent to the YM2612 and should have near-identical timings.
http://www.aidanlawrence.com/wp-content/uploads/2018/06/YM3438_translated_timings.png
[ I'm still learning Japanese, so appologies if some of the translations are loose! :) ]
You can view the entire YM3438 application manual here, but it is in Japanese.
http://www.aidanlawrence.com/wp-content/uploads/2018/05/YM3438_APL.pdf
As for that wait10nS delay function, it was kind of a tune-and-check hack to try and get my chip to perform with the shortest timings possible. Lots of my timings were just trial-and-error plugged-in to see what worked. This was before I discovered the application manual linked above.
Hopefully that sheet should give you some insight into how the YM2612 is timed.
BTW, other than the delays required to push data to the sound chips, are you accounting for the VGM specific wait times? I.E. 0x61, 0x62, 0x63, 0x7x, single sample delays after 0x50, 0x52, 0x53, etc.?
I remember having lots of speed issues before I figured out how to properly delay VGM stream commands.
Code like:
//Found above the VGM command switch
unsigned long timeInMicros = micros();
if( timeInMicros - startTime <= pauseTime)
{
return;
}
and
//Found near the bottom of almost every VGM command switch statement
startTime = timeInMicros;
pauseTime = singleSampleWait;
Thank you so much for all this super helpful info.
I hadn't yet implemented a VGM player as my ultimate goal was to use this under midi control. I went ahead and make one just to check stuff out. It seems when playing back VGM data I'm able to drop the delays significantly. Perhaps it's the VGM sample rate that gives the chip enough time between writes, as my test program just wrote data without doing so at 44.1kHz.
There are some interesting slow-downs during VGM playback, probably due to unbuffered SD card reads.
Thanks again, I really appreciate you going out of you way to help.
No problem! I'd be very interested in seeing a MIDI implementation.
I believe this iteration of the project actually does have a buffer system, but it is set to one byte. I found that buffering data actually slowed things down more than just reading directly from the SD card.
The latest and greatest version of my Genesis player actually removes the buffer feature in favor of directly reading from the SD card.
https://github.com/AidanHockey5/STM32_VGM_Player_YM2612_SN76489
When it comes to slowdown, it is usually the result of lengthy or frequent PCM samples being pushed to the chip. FM-synth commands are usually sent over in an instant, but PCM samples need to be written extremely quickly to the chip in order to keep up with the 44.1 KHz sample rate. PCM sample write time is actually what spurred the creation of the weird wait10nS()
function. Timing is definitely the part of this project that I spent the most time on.
Since this project wasn't made with MIDI in mind, the chip-interfacing delays will probably have to be a little more precise.
Thanks again. I'll keep you posted as I work through the MIDI part of things. I got the vgm stuff in a place where I'm fine with it as a way of testing changes elsewhere. :)
Hey @AidanHockey5 - I wanted to let you know that I more or less finished my midi-controlled YM2612 synthesizer: https://blog.thea.codes/genesynth-a-sega-genesis-inspired-synthesizer/
Ahhh very cool! This is great! As it turns out, I am also building a MIDI controlled Genesis Synth around the YM2612 and SN76489! The breadboard prototype is just about done and I'm soon going to move towards producing PCBs. Your project is super awesome! Great job!