#arduinonano — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #arduinonano, aggregated by home.social.
-
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
-
This is actually a collection of previous projects with the code tidied up a little and combined to show a single sketch that can be configured for either PWM, an R2R ladder or the MCP4725 I2C DAC.
There isn’t really anything here that hasn’t been talked about before somewhere, but hopefully this can act as a single reference point for a range of direct digital synthesis techniques from now on.
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 Arduino tutorials for the main concepts used in this project:
- Arduino PWM Sound Output.
- Arduino PWM Output Filter Circuit.
- Arduino R2R Digital Audio – Part 3.
- MCP4725 Digital to Analog Converter.
- Arduino Mozzi Additive Synthesis.
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Uno or Nano.
- Cheap or disposable amplification and speaker.
- Several 10K potentiometers.
- One of:
- Breadboard and jumper wires
The Circuit
There is no specific circuit dedicated to this post, but it works well with the two audio experimenter PCBs listed above, which each contain options for the following:
- PWM audio output on either D3 or D9.
- R2R resistor DAC (in the case of the Uno experimenter PCB).
- MPC4725 I2C DAC.
Alternatively, the main elements for PWM operation can be put together on a solderless breadboard as follows.
The Code
As already mentioned the core elements of the code have largely been met before, but the main sections are described below.
The general theory of direct digital synthesis operation is described fully in Arduino R2R Digital Audio – Part 3 so I won’t go over that again.
This code implements simple additive synthesis by using potentiometers to set the amplitudes for a set of sine wave harmonics. The performance of the Arduino largely limits this to being a maximum of six sine waves to be added up, but that is enough for some simple experimentation.
The code configures the first six harmonics: fundamental (f), f*2, f*3, f*4, f*5, f*6. There are some default ranges that can be used for testing without pots:
#define SC 32
int sine[MAXPOTS] = {SC*2,0,0,0,0,0};
int saw[MAXPOTS] = {SC,SC/2,SC/4,SC/8,SC/16,SC/32};
int squ[MAXPOTS] = {SC,0,SC/2,0,SC/4,0};There is no way to set the fundamental frequency – it is fixed at 440Hz. Making that controllable is left as an exercise for another day! The natural options are MIDI triggering or another pot.
The basic properties of the synthesis code are as follows:
- The sample rate depends on the technique, but in principle it could support 4096Hz, 8192Hz, 16384Hz or 32768Hz.
- It uses a 256 entry, 8-bit wavetable to define the basic sine wave.
- It uses 8.8 fixed point accumulators with the top 8-bits as the index into the wave table.
- It uses a 16-bit sample value which is scaled down as required by the audio output method.
The general pattern used in this code is as follows:
// Audio output specific functions:
dacSetup ()
dacWrite (value)
dacScan ()
// Generic audio functions:
dacPlayer ()
Call dacWrite (last calculated sample value)
For each potentiometer:
Update accumulator for the DDS
Add potval * sinetable[accumulator>>8] to the total
setup ()
Call dacSetup ()
loop ()
Call dacScan ()
Every 10 loops update the potsEach audio output option will implement the three functions dacSetup, dacWrite and dacScan, but not all need to be used. Conditional compilation is used to select between audio output options by defining one of PWM_OUTPUT, DAC_OUTPUT or R2R_OUTPUT.
Here are some notes for each option.
PWM:
- Output scaled to 8-bits for use with PWM.
- PWM is configured to run at 65536Hz.
- The TIMERn_OVF_vect interrupt is used to trigger sample updating via dacPlayer().
- All four sample rates are possible so samples are not written out on every interrupt. For example, for a sample rate of 16384Hz a sample is written out on every fourth interrupt.
- Can support either D9 (Timer 1) or D3 (Timer 2).
- As updates are interrupt driven, dacScan () is empty.
R2R:
- Uses D8-D9, D2-D7 as bits 0 to 7 for the DAC output.
- Output scaled to 8-bits for use.
- PORT I/O is used to write to the data lines.
- Code takes into account the fact that D0/D1 might be in use as the UART.
- Updates are interrupt driven using the TimerOne library, calling dacPlayer() directly.
- As updates are interrupt driven, dacScan () is empty.
MCP4725:
- The I2C address for the DAC is configured by defining MCP4725ADDR. It defaults to 0x60.
- As the DAC can’t be written to from an interrupt routine, the output is set during dacScan() so the loop() has to run as fast as possible.
- The sample rate is set by monitoring the micros() tick (note on an Arduino the resolution is 4uS at best).
- Uses the non-blocking I2C library and fast analog read from Mozzi.
- Uses the MCP4725 fast write mode, which only requires two bytes to be sent to the DAC.
- The maximum sample rate is 8192Hz and even then it runs a little slow (i.e. the 440Hz tone is flat by around a semitone).
General comments:
- There is an optional timing pin that is configured by defining TIMING_TEST. This is toggled in dacPlayer().
- There is an optional fixed set of amplitudes that can be used instead of potentiometers. These are set up in setDefaultAmplitudes() when DAC_TEST is defined.
- The maximum number of pots supported is 6. The code skips using A4/A5 as these map onto I2C if the DAC is used. The number of pots to scan (and hence sine waves to add up) can be reduced by setting NUMPOTS to a number less than MAXPOTS (which is 6).
- If the number of pots is reduced, then the scaling factors used to calculating the totals can be adjusted by changing SC and PSC accordingly. For 6 pots/waves they are set to 32 and 4 respectively. This means that analogReads have a maximum range of 0..31 which is set by
val = analogRead(pot) >> PSC;
Closing Thoughts
This has been interesting to revisit. After all my experiments this is starting to finally make some sense. It has been interesting to contrast the three output methods both in terms of their computational performance and in terms of output waveform quality.
The photo at the start shows the R2R output of the test sine wave. The photo below is the PWM output for a potentiometer-driven saw.
This has also prompted me to revisit my Arduino PWM Output Filter Circuit and finally work out how to properly combine a low-pass filter and potential divider and still get something approximating the filter characteristics I wanted. I feel I understand quite a bit more about what is going on now.
Now if I could just get a bit of a handle on impedance I might actually start to feel like I know a little about what I’d be talking about….
Kevin
https://diyelectromusic.wordpress.com/2024/03/06/arduino-direct-digital-additive-synthesis/
#additiveSynthesis #arduinoNano #arduinoUno #dac #dds #mcp4725 #pwm #r2r
-
Arduino Nano Adds USB Interface to the Psion Organiser II - Introduced in 1984, the Psion Organiser series defined the first generation of ele... - https://hackaday.com/2022/02/07/arduino-nano-adds-usb-interface-to-the-psion-organiser-ii/ #diyusbinterface #handheldshacks #psionorganiser #arduinohacks #classichacks #arduinonano