#ay38910 — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #ay38910, aggregated by home.social.
-
Arduino AY-3-8910 Shield Build Guide
Here are the build notes for my Arduino AY-3-8910 Shield Design.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to electronics and microcontrollers, see the Getting Started pages.
Bill of Materials
- Arduino AY-3-8910 Shield PCB (GitHub link below)
- 2x AY-3-8910 or YM2419 devices (see notes on sourcing here: Arduino and AY-3-8910)
- 8x 1KΩ resistors
- 2x 100nF ceramic capacitors
- 1x or 2x 1uF electrolytic capacitors
- 1x 3.5mm stereo TRS socket (see photos and PCB for footprint)
- Arduino headers: 1x 6-way; 2x 8-way; 1x 10-way pin or extended headers as required
- Optional: 2x 40 pin wide DIP sockets (highly recommended)
If both chips audio outputs are to be combined, using the solder bridges, then only one 1uF electrolytic capacitor should be used.
Build Steps
Taking a typical “low to high” soldering approach, this is the suggested order of assembly:
- Resistors.
- Disc capacitors (see notes below).
- DIP sockets (if used) and TRS socket.
- Electrolytic capacitors.
- Arduino headers.
There are two solder bridge jumpers which can be used for the following:
- To mix all channels from both chips onto the same output.
- To combine left and right channels for the TRS socket.
By default, one chip goes to the left audio output and one goes to the right, but it is possible to combine them into a single mono output. But then there is another choice: combine the left and right audio channels (tip and ring) for the TRS socket; or leave all outputs just to the tip of the socket.
If these options are being considered, then one of the output electrolytic capacitors should be omitted too. More details below.
Here are some build photos.
The ceramic capacitors are actually shown as being installed on the underside of the board, but depending on the 40 pin DIP socket used (or not) it may be possible to install them on the top side of the board as I’ve done below.
I’ve used “extended headers” which give me a breakout for the Arduino GPIO on the top of the board. If simple pin headers are used, then care should be taken about the height of the board and avoiding the possibility of the resistors shorting out on the USB socket of the Arduino.
Solder jumper options
For mono operation:
- Only install electrolytic capacitor highlighted in PURPLE. Do not install the capacitor with the YELLOW cross.
- Bridge the solder jumper highlighted in RED.
For mono socket operation, i.e. TIP and GROUND only, leave the solder bridge highlighted in ORANGE unbridged. This allows a mono jack lead to be used as RING is unconnected in the socket and can be ignored.
To take the mono signal into a stereo socket, i.e. TIP, RING and GROUND but with TIP and RING having the same mono output signal, solder the bridge highlighted in ORANGE. This allows a stereo jack lead to be used and both channels will received the same output signal.
Testing
I recommend performing the general tests described here: PCBs.
Once everything appears electrically good, a variation of the test application from my AY-3-8910 Experimenter PCB Build Guide can be used that will play a chord on both of the devices at a different octave.
Note: the GPIO usage of the Arduino is printed on the back of the PCB and listed in the Arduino AY-3-8910 Shield Design.
PCB Errata
There are no known issues with the PCB at present.
Enhancements:
- None
Closing Thoughts
This seems to work fine and is a lot simpler than my quad board if some simple experimentation is required.
I still haven’t gotten around to building some real applications for any of these boards yet though, so ought to get on to that.
Kevin
-
Arduino AY-3-8910 Shield Design
Having build my AY-3-8910 Experimenter PCB I thought a slightly simpler format board would be useful, so I’ve put together an Uno shield-format PCB that can support one or two AY-3-8910 chips.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to electronics and microcontrollers, see the Getting Started pages.
The Circuit
This is simply the two-chip version of my AY-3-8910 Experimenter PCB Design. I fixed the “reset on D13” thing though, so the connections are now as follows:
ArduinoAY-3-8910D2-D9D0-D7D10CLOCKD11/RESETA0/A1BC1/BDIR for device AA2/A3BC1/BDIR for device BThis leaves A4/A5 free for analog IO or I2C, D0/D1 free for the UART, and D12/D13 free for other uses including the on-board LED on D13.
I trimmed down the output audio stage, but arranged one chip on the L channel and one chip on the R channel, but left the solder jumpers in to allow the mixing of both devices onto single or dual channels too.
I’ve also added the two pull-downs, opting for the same arrangement as the patch on the previous board, and I’ve correctly the silkscreen capacitor values.
PCB Design
I’ve just managed to squeeze everything into the Uno shield format. I put the chips smoothing capacitors on the underside of the board to allow them to sit close to the chips’ power pins.
I’ve broken out the spare GPIO pins to additional headers, partly to make it clear which pins are spare.
I’ve also listed the GPIO usage for each chip on the underside of the board.
I was tempted to remove the mounting holes at the shaped end of the board, but left them in the end. One is a bit close to the additional breakout headers for A4/A5, but I have the option not to add those if I want to.
I have extended the board slightly though compared to the traditional Arduino shield shape just to accommodate the length of the 40-pin devices a bit more easily.
Closing Thoughts
I must admit I wasn’t sure if I could get two 40-pin wide DIP devices onto a shield, but it just about fits.
Fingers crossed, having a four-device version already, this will be a little easier to get going than the last one!
Kevin
-
Arduino and AY-3-8910 – Part 5
My next bit of messing around with Arduno and AY-3-8910 takes my AY-3-8910 Experimenter PCB Design and adds some simple MIDI reception to create a 12-channel AY-3-8910 tone module.
- Part 1 – Getting started and looking at playing YM files.
- Part 2 – Adding basic MIDI control.
- Part 3 – Basic experiments with direct digital synthesis.
- Part 4 – Using the AY-3-8910 as a 4-bit DAC for Mozzi.
- Part 5 – Driving four AY-3-8910s using my AY-3-8910 Experimenter PCB.
https://makertube.net/w/hLo4HLYcQkcGvf8N9XzgCS
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key tutorials for the main concepts used in this project:
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Nano.
- 4x AY-3-8910 chips.
- Built AY-3-8910 Experimenter PCB
The Code
This is taking a combination of the following previous projects:
- AY-3-8910 Experimenter PCB Build Guide – the sample code to drive the Quad AY-3-8910 board.
- Arduino Tones – A New, New Hope – driving 12 channels of Arduino tones.
I had the option of assigning unique MIDI channels to each of the 12 channels of the quad AY-3-8910s, but instead opted for a system that listens on all MIDI channels but assigns incoming notes to the next free channel.
If there are no spare channels, the notes are ignored.
I’ve included an option to respond to velocity, by translating a MIDI velocity value (0 to 127) into a AY-3-8910 amplitude level (0 to 15). But for now, I’m using it with a fixed velocity.
In order to map a polyphonic note index onto a chip and channel, I use the following:
void ayNoteOn (int chan, int pitch, int vel) {
int ay = chan / 3;
int ch = chan % 3;
aySetFreq (ay, ch, pitch, vel);
}The aySetFreq() function takes a MIDI nonte number and turns it into a course an fine frequency value for programming into the AY-3-8910.
void aySetFreq (int ay, int ch, int note, int vel) {
int vol = vel >> 3;
uint16_t freq = 0;
if (note != 0) {
freq = pgm_read_word(&Notes[note-NOTE_START]);
}
switch (ch) {
case 0:
ayFastWrite (ay, AY38910Regs::A_TONE_C, freq >> 8);
ayFastWrite (ay, AY38910Regs::A_TONE_F, freq & 0x0FF);
ayFastWrite (ay, AY38910Regs::A_AMP, vol);
break;
}
}Additional case statements are provided for channels 1 (B) and 2 (C). The Notes array is the list of frequencies calculated for a 1MHz clock using the equation provided in the data sheet:
- Freq (tone) = Freq (clock) / (16 TP)
Where TP is the 12-bit value placed in the course and fine frequency registers. So turning this around and plugging in the frequencies for MIDI notes, we can figure out the 12-bit values required to be programmed into the registers.
In the end, I cheated and used the table already provided here: https://github.com/Andy4495/AY3891x/blob/main/src/AY3891x_sounds.h
This covers all notes from C0 (MIDI 12) to B8 (MIDI 119).
I should also note that I’ve now removed all of the original AY3891x library and am using my own fast-access routines now tailored for supporting four devices.
As I’m using port IO though, this does mean there is a fair bit of hardcoded assumptions about Arduino PORT usage and GPIO pins.
Closing Thoughts
The video shows my, now, go-to test of anything linked to Arduinos and tones – a 12-channel arrangement of the end titles of Star Wars Episode IV – A New Hope.
As the code will select the next free channel for incoming notes, sometimes consecutive notes sound slightly different due, presumably, to differences in the output channels of the devices. Something to look at, at some point.
It would also be useful to have a “multi-track” version where each channel is an independent MIDI channel in its own right, but for now, using OMNI and “next free channel” is fine.
I have to say, when the theme really gets going with those vintage 8-bit tone sounds, I could be sitting back in that 80s Star Wars vector graphics video arcade machine… (although apparently that used several Atari POKEY chips, not AY-3-891x- shame. I wonder if you can get hold of those too…)
“The force will be with you. Always.”
Kevin
-
AY-3-8910 Experimenter PCB Build Guide
Here are the build notes for my AY-3-8910 Experimenter PCB Design.
https://makertube.net/w/fULfpG9LNwpb3iCfavVkAp
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to electronics and microcontrollers, see the Getting Started pages.
Bill of Materials
- AY-3-8910 Experimenter PCB (GitHub link below)
- Arduino Nano
- Up to 4x AY-3-8910 40-pin DIP devices (see notes here on obtaining devices: Arduino and AY-3-8910)
- 1x 6N138 optoisolator
- 1x 1N4148 or 1N914 signal diode
- Resistors: 4x 220Ω, 1x 4K7, 14x 1K
- 6x 100nF ceramic capacitors
- 2x 1uF electrolytic capacitors (the PCB has 220uF on the sinkscreen)
- 1x 100uF electrolytic capacitor
- Either: 3x 3.5mm TRS PCB mount sockets
- Or: 1x 3.5mm TRS PCB mount sockets and 2x 180 DIN PCB mount sockets
- 1x 2.1mm barrel jack socket
- 2x 15-way pin header sockets
- 4x 40-way wide DIP sockets
- Pin headers
- Optional: 1x SPDT, 1x DPDT both with 2.54mm pitch connectors
- Optional: 1x 8-way DIP socket
Build Steps
Taking a typical “low to high” soldering approach, this is the suggested order of assembly:
- All resistors and diode.
- DIP and TRS socket(s).
- Disc capacitors.
- Switches (if used).
- Electrolytic capacitors.
- 15-way pin header sockets.
- Barrel jack socket.
- DIN sockets (if used).
It is necessary to add two additional 1K resistors as patch-links on the underside of the board. Details below.
Here are some build photos.
The DIP sockets should go on next before the TRS sockets.
Pin headers and jumpers could be used for the MIDI on/off switch. The power switch could be bypassed with a wire link if not required.
There are a number of optional pin header breakouts: power, UART, additional IO and all the IO for the four AY-3-8910 chips. For this build I’m not populating those.
Errata Fixes
As mentioned in the design notes, two additional resistors must be added to pull the audio outputs to GND as part of the output/mixer circuit. I used two additional 1K resistors.
These can be added to the underside of the board as shown below.
Testing
I recommend performing the general tests described here: PCBs.
Once everything appears electrically good, here is a test application that will play a chord on each of the devices at a different octave. If this works it should be possible to hear all 12 notes in the four chords across four octaves sounding.
Find the code here: https://github.com/diyelectromusic/sdemp/tree/main/src/SDEMP/ArduinoAY38910QuadTest
PCB Errata
As already mentioned there are the following issues with this PCB:
- The two 220uF capacitors should be replaced with 1uF capacitors.
- Two additional resistors need to be patched into the audio output circuit.
Enhancements:
- None
Sample Applications
Here are some applications to get started with:
- (on their way)
Closing Thoughts
It took quite a long time to realise the issue with the output channels. For ages, it appeared that the interface to the chip just wasn’t functioning correctly. With hindsight, some kind of register read/write test would have confirmed that a lot earlier.
It was only when going back to the schematics of other designs and recognising that the output was always HIGH did the penny drop that the additional resistor was required. Then there was some experimentation to find something that would work with my board and not cause issues in use.
But it seems like I got there in the end. Now I can get on with doing something a little more interesting MIDI and music wise.
Kevin
-
Arduino and AY-3-8910 – Part 4
After Part 3 I started to go back and add MIDI, and changed the waveform on the touch of a button, and then started to wonder if I could add envelopes and so on.
And then it occurred to me, I didn’t really need to re-implement my own synthesis library, I could probably write a custom audio output function for Mozzi and get it to use the AY-3-8910 as a 4-bit DAC…
- Part 1 – Getting started and looking at playing YM files.
- Part 2 – Adding basic MIDI control.
- Part 3 – Basic experiments with direct digital synthesis.
- Part 4 – Using the AY-3-8910 as a 4-bit DAC for Mozzi.
- Part 5 – Driving four AY-3-8910s using my AY-3-8910 Experimenter PCB.
https://makertube.net/w/ast3HQ2a3fCanKy9Pr6qUc
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key tutorials for the main concepts used in this project:
- Arduino AY3891x Library: https://github.com/Andy4495/AY3891x
- Arduino Nano AY-3-8910 PCB: https://github.com/GadgetReboot/AY-3-8910
- AY-3-8910 on synth DIY wiki: https://sdiy.info/wiki/General_Instrument_AY-3-8910
- Mozzi: https://sensorium.github.io/Mozzi/learn/
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Uno.
- AY-3-8910 chip.
- Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
- 5V compatible MIDI interface.
- Jumper wires.
Mozzi Custom Audio Output
Mozzi supports a wide range of microcontrollers with a range of different output methods from PWM, built-in DACs, I2S, through to custom output options with DMA or something else.
I’m not going to go over how Mozzi works here, but here are details of how to run with the different audio output modes here: https://sensorium.github.io/Mozzi/learn/output/
The key option for me is MOZZI_OUTPUT_EXTERNAL_CUSTOM. There are a number of configuration options that must be set prior to include the main Mozzi file as follows:
#include "MozziConfigValues.h"
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_CUSTOM
#define MOZZI_AUDIO_BITS 8
#define MOZZI_CONTROL_RATE 64
#define MOZZI_AUDIO_RATE 16384
#define MOZZI_ANALOG_READ MOZZI_ANALOG_READ_NONE
#include <Mozzi.h>
#include <Oscil.h>
#include <tables/cos2048_int8.h>
#include <mozzi_midi.h>
#include <mozzi_fixmath.h>This sets up the audio synthesis parameters to 8 bit audio with a sample rate of 16384Hz.
Implementing a custom audio output this way requires two functions. One for the audio output and one to tell Mozzi when it is time to call the audio output function.
I would rather have used MOZZI_OUTPUT_EXTERNAL_TIMED which handles the calling at the correct AUDIO_RATE for me, but that relies on the use of the ATMega328’s Timer 1, but in this case Timer 1 is providing the 1MHz clock for the AY-3-3810.
But rather than implementing yet another timing routine, I just used the micros() counter to decide if it was time to generate audio or not.
void audioOutput(const AudioOutput f)
{
int out = MOZZI_AUDIO_BIAS + f.l();
ayOutput(0,out);
}
unsigned long lastmicros;
bool canBufferAudioOutput() {
unsigned long nowmicros = micros();
if (nowmicros > lastmicros+58) {
lastmicros=nowmicros;
return true;
}
return false;
}To get samples produced at the required 16384Hz sample rate means there needs to be one sample produced 16384 times a second. There thus needs to be a sample every 60uS. If I implement the above function checking for nowmicros > lastmicros + 60 then the resulting sound is slightly flat (in tuning). I’m guessing this is related to the overheads of the function call and logic, so I’ve gone with lastmicros+58 and that sounds pretty good to me.
My ayOutput() routine takes an 8-bit sample and cuts it down to the 4-bits required for a level on the AY-3-8910.
FM Synthesis on the AY-3-8910 (sort of)
I wanted to try the FM synth mode just to see what would happen and thought it would be interesting to switch between the carrier sine wave signal and the modulated signal by pressing the button.
Unfortunately, I just could not get the button logic to work, even though I could see the state of the pin (A5) changing.
Finally after an hour or so of puzzling why such an apparently simple test of logic wasn’t working, I realised what the issue must be. Mozzi, for the AVR microcontrollers, has its own fast ADC routines. It turns out that these were interferrng with using A5 as a digital input pin.
It is fairly easy to override the Mozzi fast ADC though by setting MOZZI_ANALOG_READ to NONE.
The Mozzi code has a carrier and modulator waveform running at audio rate and an index running at the control rate to bring the modulator in and out.
It is just about possible to see the FM modulation on the oscilloscope as shown below.
Of course, the AY-3-8910 isn’t actually doing FM synthesis itself. It is just acting as a 4-bit DAC, but it is still quite fun to see.
Closing Thoughts
This is all getting a little pointless really, as there is nothing being done that the Arduino Nano couldn’t do better on its own, but it is a bit of fun to see where this thread ends up.
There are a number of interesting angles now. One of which would be to utilise all three channels. This could provide a form of additive synthesis, it could perform some fixed interval additional oscillators, or it could be used for 3-note polyphony.
Now that Mozzi is running it is also possible to do anything Mozzi can do, and that includes implementing envelope generation.
Kevin
-
Arduino and AY-3-8910 – Part 3
I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.
- Part 1 – Getting started and looking at playing YM files.
- Part 2 – Adding basic MIDI control.
- Part 3 – Basic experiments with direct digital synthesis.
- Part 4 – Using the AY-3-8910 as a 4-bit DAC for Mozzi.
- Part 5 – Driving four AY-3-8910s using my AY-3-8910 Experimenter PCB.
https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key tutorials for the main concepts used in this project:
- Arduino AY3891x Library: https://github.com/Andy4495/AY3891x
- Arduino Nano AY-3-8910 PCB: https://github.com/GadgetReboot/AY-3-8910
- AY-3-8910 on synth DIY wiki: https://sdiy.info/wiki/General_Instrument_AY-3-8910
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Uno.
- AY-3-8910 chip.
- Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
- 5V compatible MIDI interface.
- Jumper wires.
Direct Digital Synthesis on the AY-3-8910
I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.
But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.
There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.
So I’ll be working to the following properties:
- 4-bit resolution for the output.
- 8-bit wavetable.
- 8.8 fixed point accumulator to index into the wavetable.
- 8096 Hz sample rate.
YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:
- Frequency value for the channel should be set to the highest frequency possible.
- All channels should be disabled.
This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.
Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.
I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.
Setting up for DDS
I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:
- Timer 2 is an 8-bit timer.
- Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
- Use CTC (clear timer on compare) mode.
- Generate a compare match interrupt.
- Do not enable any output pins.
The appropriate ATMega328 registers to enable this are:
// COM2A[1:0] = 00 No output
// WGM2[2:0] = 010 CTC mode
// CS2[2:0] = 011 Prescalar=32
ASSR = 0;
TCCR2A = _BV(WGM21);
TCCR2B = _BV(CS21) | _BV(CS20);
TCNT2 = 0;
OCR2A = 60;
TIMSK2 = _BV(OCIE2A);Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.
pinMode(11, OUTPUT);
TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggleAnd this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.
The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.
Analysis of Performance
The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.
No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.
After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.
Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:
void AY3891x::write(byte regAddr, byte data) {
latchAddressMode(regAddr);
daPinsOutput(data);
noInterrupts();
mode010to110();
mode110to010();
interrupts();
daPinsInput();
}
void AY3891x::latchAddressMode(byte regAddr) {
mode010to000();
daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
mode000to001();
mode001to000();
mode000to010();
}
void AY3891x::daPinsOutput(byte data) {
byte i;
for (i = 0; i < NUM_DA_LINES; i++) {
if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
}
for (i = 0; i < NUM_DA_LINES; i++) {
if (_DA_pin[i] != NO_PIN) {
digitalWrite(_DA_pin[i], data & 0x01);
data = data >> 1;
}
}
}
void AY3891x::daPinsInput() {
byte i;
for (i = 0; i < NUM_DA_LINES; i++) {
if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
}
}And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.
One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.
Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.
Measuring the Basic Performance
At this point I took a step back and created a free-running test sketch to really see what is going on.
#include "AY3891x.h"
AY3891x psg( 17, 8, 7, 6, 5, 4, 3, 2, 16, 15, 14);
#define AY_CLOCK 9 // D9
void aySetup () {
pinMode(AY_CLOCK, OUTPUT);
digitalWrite(AY_CLOCK, LOW);
TCCR1A = (1 << COM1A0);
TCCR1B = (1 << WGM12) | (1 << CS10);
TCCR1C = 0;
TIMSK1 = 0;
OCR1AH = 0;
OCR1AL = 7; // 16MHz / 8 = 2MHz Counter
psg.begin();
// Output highest frequency on each channel, but set level to 0
// Highest freq = 1000000 / (16 * 1) = 62500
psg.write(AY3891x::ChA_Amplitude, 0);
psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
psg.write(AY3891x::ChB_Amplitude, 0);
psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
psg.write(AY3891x::ChC_Amplitude, 0);
psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);
// LOW = channel is in the mix.
// Turn everything off..
psg.write(AY3891x::Enable_Reg, 0xFF);
}
int toggle;
void setup() {
pinMode(11, OUTPUT);
toggle = LOW;
digitalWrite(11, toggle);
aySetup();
}
void loop() {
toggle = !toggle;
digitalWrite(11, toggle);
for (int i=0; i<16; i++) {
psg.write(AY3891x::ChA_Amplitude, i);
}
}All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:
This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.
And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.
Optimising Register Writes
So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.
The library I’m using aims for the most portable compatibility: “This library uses the generic
digitalWrite()function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.
Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:
- https://github.com/53175ddd/AY-3-8910_Arduino – uses a mixture of PORT IO and digitalWrite(). Assumes use of D0-D7 for data channel.
The following are projects that have not used a library, but just done their own thing:
- https://github.com/internalregister/AY-3-8910 – uses a mixture of digitalWrite and PORT IO. Assumes use of D0-D7 for the data channel.
- https://github.com/GaryA/TB-AY-3_MIDI – uses direct PORT IO for D2-D9
Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.
So to improve things whilst using this same PCB will require me to re-write the library myself.
As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:
void ayFastWrite (byte reg, byte val) {
// Mode=Addr Latch
digitalWrite(BC1, HIGH);
digitalWrite(BDIR, HIGH);
// Latch address
// NB: Addresses are all in range 0..15 so don't need to
// worry about writing out bits 6,7 - just ensure set to zero
PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
PORTB = (PORTB & 0xFE);
PORTC = (PORTC & 0xF7);
// Mode = Inactive
digitalWrite(BC1, LOW);
digitalWrite(BDIR, LOW);
delayMicroseconds(10);
// Mode = Write
digitalWrite(BC1, LOW);
digitalWrite(BDIR, HIGH);
// Write data
PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3
// Mode = Inactive
digitalWrite(BC1, LOW);
digitalWrite(BDIR, LOW);
}I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:
DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.
digitalWrite(BC2, HIGH);
DDRD |= 0xFC;
DDRC |= 0x04;
DDRB |= 0x01;This now improves on that previous 280Hz and gives me 1600Hz performance.
So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…
#define BC1LOW {PORTC &= 0xFE;} // A0 LOW
#define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
#define BC2LOW {PORTC &= 0xFD;} // A1 LOW
#define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
#define BDIRLOW {PORTC &= 0xFB;} // A2 LOW
#define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH
void ayFastWrite (byte reg, byte val) {
// Mode=Addr Latch
BC1HIGH;
BDIRHIGH;
// Latch address
PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
PORTB = (PORTB & 0xFE);
PORTC = (PORTC & 0xF7);
// Need 400nS Min
delayMicroseconds(1);
// Mode = Inactive
BC1LOW;
BDIRLOW;
// Need 100nS settle then 50nS preamble
delayMicroseconds(1);
// Mode = Write
BC1LOW;
BDIRHIGH;
// Write data
PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3
// Need 500nS min
delayMicroseconds(1);
// Mode = Inactive
BC1LOW;
BDIRLOW;
// Need 100nS min
}The timings come from the AY-3-8910 datasheet:
The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.
There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.
This now gives me the following:
This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.
The downside of this method:
- It is ATMega328 specific.
- It is specific to the pin mappings and PORT usage of this PCB.
- It does not support reading or other chip operations between the writes.
It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.
DDS Part 2
Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.
Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.
Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.
Closing Thoughts
I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.
But hopefully filling in the gaps won’t take too long and will be the subject of a further post.
Now that I have something that works, I’m actually quite surprised by how well it is working.
Kevin
#arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi -
AY3-8910 tries to do SID voice. Syncing the internal modulator to the bass note periods for pretend pwm. Not bad at all for the humble AY without resorting to the intense cpu control of the volume registers, as might be found in much demoscene stuff.
#ay38910 #soundchip #chiptune #zxspectrum #amstradcpc #vectrex #sidchip #c64 #eurorack #eurorackmodule #thegoat
-
Arduino AY player: продолжение
Изучаем устройство OLED-экрана SSD1306 и дорабатываем звуковые индикаторы музыкального плеера PSG-файлов на чипе AY-3-8910.
-
Arduino AY player: продолжение
Изучаем устройство OLED-экрана SSD1306 и дорабатываем звуковые индикаторы музыкального плеера PSG-файлов на чипе AY-3-8910.