home.social

#arduinonano — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #arduinonano, aggregated by home.social.

  1. TD4 4-bit DIY CPU – Part 8

    Now that I’ve shown I could support more ROM if required using a microcontroller (see Part 6) I can start to ponder how that might be possible.

    • Part 1 – Introduction, Discussion and Analysis
    • Part 2 – Building and Hardware
    • Part 3 – Programming and Simple Programs
    • Part 4 – Some hardware enhancements
    • Part 5 – My own PCB version
    • Part 6 – Replacing the ROM with a microcontroller
    • Part 7 – Creating an Arduino “assembler” for the TD4
    • Part 8 – Extending the address space to 5-bits and an Arduino ROM PCB

    There are several other expansions to consider too. Other things I’m pondering are:

    • Can I find a way to add the two registers together?
    • Are there options to add another register?
    • Is 4-bit data still enough?
    • Could any extensions be added in a way that is backwards compatible with the existing instructions and behaviours?

    And probably a few other odds and ends as I go back and reconsider the schematic as it stands, but they can wait for a future post.

    TD4 Simulation

    Before I get stuck into the updates, I thought it would be useful to be able to simulate the TD4 to allow for quick turn-around experiments.

    I’ve used the “Digital” logic simulator which can be found here: https://github.com/hneemann/Digital

    I could have build the simulator from basic logic gates and that would perhaps have been more useful in helping to understand how the design works. But I wanted something that would be easy to fiddle about with to test enhancements, so I build it using the actual 74xx logic chips instead. This doesn’t make for such a readable simulation, as I’ve had to go with actual pinouts for chips rather than logical groupings of signals. But it does map more closely onto the final hardware which is handy for thinking in actual chip-usage rather than abstract logic.

    I’ve not bothered simulating the clock circuit, I’ve just wired in a clock source. I’ve also not added the ROM DIP switches, instead adding a ROM element and wiring it into the address and data lines. By right-clicking and viewing the attributes, it is possible to define a 16-byte ROM (4 address, 8 data lines) and edit the contents.

    The ROM element takes a multiplexed source and produces a multiplexed output, so I use a splitter/mixer function to turn that into D0-D7 as shown above. Similarly the output of the 74HC161 acting as the program counter (PC) has A0-A3 mixed into a single ADDR bus line.

    I’ve added outputs to the two registers to show their contents during execution. I’ve also added a DIP switch on the /RESET line to allow me to start and stop the simulation.

    The video below shows it running the above ROM contents, which is the same demo program I used in Part 6 with the microcontroller ROM.

    https://makertube.net/w/5njzGmYvqXiU3DLCMMtwqp

    Now I have an easier way of experimenting, onto the enhancements.

    Increasing the Address Space

    The address space is currently implemented as follows:

    • A 4-bit counter register based on a HC161 4-bit synchronous binary counter.
    • A HC154 4 to 16 line decoder/multiplexer for DIP switch selection.
    • A HC540 octal buffer/line driver to buffer (and invert) the data outputs.

    The counter auto increments on each clock pulse, thus moving through the address space, but it can also be a destination for the adder, allowing absolute jumps to specific addresses, thus implementing a JMP instruction.

    To increase the address space, there are a few considerations:

    • With more than 4 bits how should JMPs work? They will have remain 4-bits unless the data width is increased.
    • Each additional bit of address space will double the number of DIP switches required.
    • The next size of binary counter above 4-bits is typically 8-bits.

    One idea is to use the RCO pin of the 161. This is the “ripple carry out” and can be used to cascade counters for greater than 4-bit counting. As I understand things, RCO will be HIGH once all outputs are also HIGH, for a single clock pulse. This can be used to enable a following counter for that pulse. This is shown below (taken from the datasheet).

    And this is the sample application, again from the datasheet, showing how it would work, with extensions on to additional stages.

    A simple way to add an additional bit of address space might be to feed RCO into a flip-flop acting as a toggle in the configuration shown below.

    This can then be used to select between two HC154 4 to 16 decoders. As I already have an unused flip flop as part of the HC74 used for the CARRY, this could be quite an appealing solution and in simulation it does appear to work.

    There is one slight complication. As show above, A5 will toggle with A0-A3 = 1111 not as they change back to 0000. This is because the flip-flop toggles on the rising edge of the provided clock signal, which in this case is RCO from the 74HC161. Adding a NOT gate means that the rising edge happens as the 161’s RCO signal drops when it resets back to 0000.

    Whilst this solves the sequencing problem it does have the unfortunately side effect that the RESET state means that A5 is 1 on power up. That too could be solved with another NOT gate if required, or simply hanging A5 off the /Q output of the flip-flop rather than the Q output.

    Here is the additional wiring, in simulator form, to allow this to work.

    Note the addition of A4 which now comes from the spare flip-flop /1Q output, and the linking of RCO via a NOT gate to the flip-flop 1CP clock input. The rest of flip-flop 1 is configured in toggle mode, with /1RD and /1SD both tied high (inactive) and 1D linked to /1Q for the feedback. The non-inverting output 1Q is not used.

    Whilst this seems to require an additional logic gate (for the NOT) it turns out that there is a spare Schmidt trigger inverter on the 74HC14 that supports the clock circuit, so that is pretty convenient.

    The ROM has also been reconfigured for 5 address inputs with the same 8 data bits, creating a 32 x 8 bit ROM.

    There are a few issues with this though:

    • JMP/JNC only work within the same half of the memory, so JMP 4 in the first 16 locations will jump to location address 0x04, but JMP 4 in the second 16 locations will jump to location address 0x14.
    • A JMP 0 in the last location of each half will carry forward into the next half, as the counter ticks over at the same time as the load happens. So JMP 0 in address 0x0F will jump to address 0x10 and JMP 0 in address 0x1F will jump to address 0x00.

    But if one can program around those constraints this is quite a simple solution.

    An Alternative Solution

    There is a neat solution to adding a 5th address bit here: https://xyama.sakura.ne.jp/hp/4bitCPU_TD4.html#memory

    This uses the duplicate JMP/JNC instructions to encode a JMP2/JNC2 that results in the 5th address bit being set, this enabling a jump to the second half of the memory.

    In order to create the additional address line, there is a second PC register added – i.e. a 5th HC161 counter. As far as I can see the operation is as follows:

    • When the first PC register carries over, the second PC register counts up.
    • As only the first output of the second PC register is used, as it counts that output will simply alternate between 0 and 1.
    • The second PC register can take 1 as an input when the decoded instructions match JMP2 or JNC2 (D5 low, D6 and D7 high, with either D4 or CARRY), forcing A4 on when the first PC register is loaded with the 4-bit jump value, creating a JMP to the second half of the address space.
    • There is an A4 and /A4 signal which alternatively enable the two address decoders for the ROM.
    • This specific circuit uses four HC138 chips rather than two HC154, but the principle of operation is the same – generate one of 32 signals for the ROM from 5 bits of address line.

    The modifications to support this are fairly simple and it is neat how it uses redundancy in the instruction set to work, but it does require an additional 74HC161 chip.

    Combine the two?

    If additional logic can be used to address the second PC in the second solution above, then I’m wondering if that could also be used to deliberately set or reset the flip-flop in the first solution too.

    The key will be overriding the flip-flop state to preset A4 if the logic sequence for the spare JNC/JMP instructions turn up. If the /1SD input is active (LOW) then the output will be HIGH. If the /1RD input is active (LOW) then the output will be LOW.

    Here is the additional instruction decoding logic – I’m using NAND gates as the NOTs here, so I can just use a single quad NAND gate chip.

    So, the truth table for this is as follows:

    D4D5D6D7/C/LDPCENA400111011011X0101111001111X00XX00X10XX10X10XX01X10

    This corresponds to D7+D6 and either D4 or CARRY and NOT D5 causing the ENA4 signal to be true thus implementing the second JNC and JMP instructions (b1100 and b1101).

    Unfortunately, so far, I’ve not been able to figure out an option for driving the flip-flop where the logic pans out to correctly set A0-A3 and A4 to successfully load the PC + flip-flop as required by the new instruction, so I might have to leave that for now.

    TD4 Arduino 5-bit Address PCB

    At this point I thought I had enough to warrant building a new PCB for a microcontroller memory version of the TD4 with the option to support a 5-bit address bus with the limitations described above.

    I took the PCB from Part 5 as the starting point and replaced the ROM logic with an Arduino Nano and added in the flip-flop to create the 5-bit address bus.

    The ROM section is replaced with the Arduino as shown below.

    The CPU section now uses the spare NOT gate from the PWRCLK section and the spare flip-flop from the CPU section as shown below.

    I believe these were the only parts to change. I have included the option to disable the RESET button by cutting a solder jumper and replacing it with a link to an Arduino IO pin.

    I’ve also added headers to breakout the unused Arduino IO pins just in case that becomes useful at some point.

    The complete Arduino Nano pinout is as follows:

    TD4 SignalArduino Nano IOA0-A4A0-A4 (A4 optional)D0-D3D8-D11D4-D7D4-D7/RESETD12 (optional)

    The board can be powered either via the Arduinos USB port or via the PCB micro USB port.

    The PCB will be found on Github here once I know it all works.

    Conclusion

    I was hopeful I could add a 5th address line just using the spare components in the circuit and not adding to the chip count, and that is kind of possible as long as I’m ok with the limitations of the JMPs.

    Building all this onto a PCB will make further programming experiments quite a lot easier.

    But the next step is to see if the instruction set can be expanded. I am still in search of that illusive two-register add.

    Kevin

    #arduinoNano #pcb #td4

  2. 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…

    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:

    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.

    Find it on GitHub here.

    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

    #arduinoNano #ay38910 #include #mozzi

  3. 2025 One Hertz Challenge: Valvano Clock Makes the Seconds Count - A man named [Jim Valvano] once said “There are 86,400 seconds in a day. It’s up to... - hackaday.com/2025/07/14/2025-o #arduinonano #oleddisplay #clockhacks #ds3231rtc #contests

  4. 2025 One Hertz Challenge: Valvano Clock Makes the Seconds Count - A man named [Jim Valvano] once said “There are 86,400 seconds in a day. It’s up to... - hackaday.com/2025/07/14/2025-o #arduinonano #oleddisplay #clockhacks #ds3231rtc #contests

  5. 2025 One Hertz Challenge: Valvano Clock Makes the Seconds Count - A man named [Jim Valvano] once said “There are 86,400 seconds in a day. It’s up to... - hackaday.com/2025/07/14/2025-o #arduinonano #oleddisplay #clockhacks #ds3231rtc #contests

  6. 2025 One Hertz Challenge: Valvano Clock Makes the Seconds Count - A man named [Jim Valvano] once said “There are 86,400 seconds in a day. It’s up to... - hackaday.com/2025/07/14/2025-o #arduinonano #oleddisplay #clockhacks #ds3231rtc #contests

  7. 2025 One Hertz Challenge: Valvano Clock Makes the Seconds Count - A man named [Jim Valvano] once said “There are 86,400 seconds in a day. It’s up to... - hackaday.com/2025/07/14/2025-o #arduinonano #oleddisplay #clockhacks #ds3231rtc #contests

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

    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:

    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 toggle

    And 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:

    The following are projects that have not used a library, but just done their own thing:

    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 3

    To 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 &= 0xF7;} // 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.

    Find it on GitHub here.

    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

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

    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:

    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 toggle

    And 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:

    The following are projects that have not used a library, but just done their own thing:

    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 3

    To 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 &= 0xF7;} // 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.

    Find it on GitHub here.

    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

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

    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:

    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 toggle

    And 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:

    The following are projects that have not used a library, but just done their own thing:

    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 3

    To 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 &= 0xF7;} // 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.

    Find it on GitHub here.

    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

  11. @fast_code_r_us I build this with (more for) my nephew with #arduinonano clone and an #SSD1306 128x32 #oled display.

    I used the #U8g2 to display the text and the u8g2_font_unifont_t_weather to display the thermometer icon.

  12. И снова я сломал атмегу через все ту же #progisp + #usbisp :blobcatjoy: играя настройками фьюзов - Ext.CrystalOsc, но эта "игра" неспроста, после того как я заново оживил контроллер, решил прошить нормально через #usbasp и #avrdudeprog но тут же получил ошибку, даже калибровочные ячейки генератора считать не удалось. Пришлось искать методом тыка ту самую настройку внешнего кристалла в PROGISP :blobcatgrimacing: кажется теперь все серьезно, контроллер не откликается, хотя на выводах #rx_tx есть потенциал... Придется опять отложить контроллер в сторону или уже не суетиться и заказать новую #arduinonano или #arduinoUno или просто закупить #atmega328p и перепаять
    #embedded
    #troubleshootingfail
    #electronics

  13. 2024 Tiny Games Contest: Blind Maze Is Fun For All - If you think about it, even difficult mazes on paper are pretty easy. You can see ... - hackaday.com/2024/08/29/2024-t #2024tinygameschallenge #arduinonano #contests #games

  14. ШИР - Широтно-импульсный регулятор. Реализован не в #arduino среде, тут только #c #avrgcc #avrdude и #atmega328p Хотел проверить сервопривод, но он кажется мертв... Но, я надеюсь что со специальным устройством проверки он вдруг оживёт :ablobcatnod: AMEN
    #embedded
    #circuitry
    #avr
    #atmelstudio6
    #servo
    #pwm
    #sourcecode
    #oscilloscope
    #testproject
    #breadboard
    #arduinonano

  15. ШИР - Широтно-импульсный регулятор. Реализован не в #arduino среде, тут только #c #avrgcc #avrdude и #atmega328p Хотел проверить сервопривод, но он кажется мертв... Но, я надеюсь что со специальным устройством проверки он вдруг оживёт :ablobcatnod: AMEN
    #embedded
    #circuitry
    #avr
    #atmelstudio6
    #servo
    #pwm
    #sourcecode
    #oscilloscope
    #testproject
    #breadboard
    #arduinonano

  16. ШИР - Широтно-импульсный регулятор. Реализован не в #arduino среде, тут только #c #avrgcc #avrdude и #atmega328p Хотел проверить сервопривод, но он кажется мертв... Но, я надеюсь что со специальным устройством проверки он вдруг оживёт :ablobcatnod: AMEN
    #embedded
    #circuitry
    #avr
    #atmelstudio6
    #servo
    #pwm
    #sourcecode
    #oscilloscope
    #testproject
    #breadboard
    #arduinonano

  17. ШИР - Широтно-импульсный регулятор. Реализован не в #arduino среде, тут только #c #avrgcc #avrdude и #atmega328p Хотел проверить сервопривод, но он кажется мертв... Но, я надеюсь что со специальным устройством проверки он вдруг оживёт :ablobcatnod: AMEN
    #embedded
    #circuitry
    #avr
    #atmelstudio6
    #servo
    #pwm
    #sourcecode
    #oscilloscope
    #testproject
    #breadboard
    #arduinonano

  18. Finished building the DrawBridge.
    It seems to work, but I need to connect an external 5V power supply, since my laptop doesn't provide enough voltage to power the drive.
    Also, this #floppy drive was old, and makes squeaky noises when trying to write. The inserted floppy shows scratch markings, too.
    So, I'll need a proper floppy drive.
    #Amiga #floppydrive #soldering #perfboard #ArduinoNano #Arduino

  19. I decided to build a DrawBridge, an #Arduino #Amiga #Floppy Disk Reader/Writer, see here: amiga.robsmithdev.co.uk/
    For this, I bought a genuine #ArduinoNano and will use one of my old PC floppy drives.
    First step was #soldering the CTS pin of the #SMD #FTDI chip to pin A2. This was a challenge because I didn't want to unsolder the pin headers which might've made it easier.
    So, I used an enameled copper wire and fiddled it through a via.
    No shorts, and least the diagnostic shows it works.

  20. Well, I couldn't leave it alone...

    The Arduino code now allows triangle waves, up to 1.0MHz (same as square waves), and sine waves up to 12.5MHz.

    Also managed to push my Owon VDS1022i to its limits... the sine wave started to look a bit ropey after 5.0MHz, but not bad until then.

    The design, updated code and even more pictures are all shared on GitHub too:

    github.com/ilneill/DFG-ArdAD98

    #FrequencyGenerator
    #Arduino
    #ArduinoUno
    #ArduinoNano
    #AD9833
    #MCP41010
    #RotaryEncoder
    #KY040
    #HD44780lcd

  21. Hurrah! My latest project, an Arduino/AD9833 based digital frequency generator, is complete... And it works too 🎉

    Impressed with rotary encoders and the AD9833 module. Very pleased with my Owon VDS1022i USB oscilloscope performance!

    Design drawn, built. Done ✅
    Code written, tested. Done ✅

    The design, code and more pictures are all shared on GitHub too:

    github.com/ilneill/DFG-ArdAD98

    #FrequencyGenerator
    #Arduino
    #ArduinoUno
    #ArduinoNano
    #AD9833
    #MCP41010
    #RotaryEncoder
    #KY-040
    #HD44780lcd

  22. Following my previous project... A rather disappointing analogue waveform generator, may I introduce you to my latest project - a digital waveform generator.

    Working on the overall design, some Arduino code and a Fritzing drawing to accompany it.

    This 3.3v/5v module has an AD9833 frequency generator, an MCP41010 digital potentiometer and an AD8051 opamp.

    I really enjoy joining hardware and software to make a system.

    #FrequencyGenerator
    #Arduino
    #ArduinoUno
    #ArduinoNano
    #AD9833
    #MCP41010

  23. 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:

    If you are new to Arduino, see the Getting Started pages.

    Parts list

    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 pots

    Each 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;

    Find it on GitHub here.

    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

  24. I've had some luck with that #ATMega4808 board as far as getting an #ArduinoNano wired up as programmer and using the #JTAG2UPDI code to push a 4808-friendly bootloader onto the device.

    A problem though has brought me back into the nuances of the #CH340 serial chip world, which I had hoped I was done with yrs ago.

    Seems maybe older MacOS versions have probs with CH340K vs G vs other variants.

    I'm still on BigSur though, so perhaps this is reason to increment up to Ventura.

  25. This is a bit of an odd one, but I was asked to put something musically related together as a game to get in the spirit of the season, so I thought something along the lines of an Arduino adjustable tone generator with the aim being the player has to tune it by ear to as close to concert A – 440Hz – as possible.

    This shows how I used my Nano Audio Experimenter Sheild PCB to do it. Note, as with most of my builds, this isn’t particularly mechanically satisfying, but it did the job 🙂

    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:

    If you are new to Arduino, see the Getting Started pages.

    Parts list

    • Nano Audio Experimenter Sheild PCB.
    • Arduino Nano.
    • SSD1306 OLED display.
    • 1x temporary make, toggle switch.
    • Amplification and audio connections.
    • Power – either (micro) USB or a 7-9V barrel jack.

    The Circuit

    This uses the Nano Audio Experimenter Sheild PCB with the following configuration:

    • No DAC fitted.
    • Audio tone output via D3 through the audio output stage.
    • Audio GND jumpered across to digital GND via DAC header (see later).
    • SSD1306 OLED display fitted.
    • Switch connected to a free IO pin (recommend D4-D6).
    • No MIDI circuitry required.
    • Powered either by 7-12V barrel jack or 5V USB direct to the Nano.

    The photo shows the key configuration options. To get an audio output whilst using PWM, the analog GND portion of the board needs connecting to the main GND. This can be done by adding a link between the two holes highlighted below:

    In my case, as I’m using a pre-build PCB I’ve added a separate single-pin header socket which then can be used to jumper across these two connectors without removing the future ability to use a DAC again.

    The frequency is set using all three pots. This gives a lot more variability as a game than using a single pot. The OLED display is used to show the frequency once it has been chosen. This happens while the switch is pushed.

    The Arduino tone function will be used, which provides a 0-5V DC biased square wave output. Sending this through the audio filter and output stage means that the voltage is reduced (via the resistor potential divider) and the DC bias is removed (via the coupling capacitor) so it can be used as a more typical audio line output and connected to an old amp or similar.

    There are a number of options where a GPIO pin is available via a header which can then be used to connect to a simple push switch:

    • Top: D9 is exposed in the PWM output jumper (which is set to select D3).
    • Far Right: D4-6 are exposed for use with an analog multiplexer.
    • Right: D7 is also exposed both in the jumper that can be used to set the behaviour of S4 and (assuming the jumper is actually set to make the link) D7 becomes exposed on the far right header too.
    • Far Right: A3 is also exposed on the far right header and so could also be used if required.

    The downside of all of these is that my ready-made board requires header pin sockets which isn’t particularly robust for a game.

    Advanced Option

    But then I realised there is one additional pin exposed in a way that could still be used as an input but with a far more robust connector. D1 (TX) is linked to the MIDI OUT 5-pin DIN socket too, albeit via a 220Ω resistor. This means that I also have the option of connecting a switch across a 5-pin DIN Socket’s pins 2 (GND) and 5 (TX) and using that instead. The 5 pin DIN socket makes a much stronger and robust connection to the board, even if it is a little unconventional.

    WARNING: The switch must NOT be connected between pins 4 and 5, which are the normal MIDI connections or from pin 4 to GND. Depending on the configuration used, there is a risk of connecting 5V to GND in the Arduino Nano and shorting something out. You might get away with it, depending on which resistors you end up having in the circuit (5V / 220Ω is just under 30mA), but it is really important to double checking the wiring before use!

    Here is a “from the front/plug side” and “from the back/wiring side” view of a 5-pin DIN plug showing which pins are 5 and 2.

    Using D1 this way means that the serial port cannot be used (even though this project isn’t using MIDI) as TX is now being reconfigured as an INPUT.

    RX isn’t an option as a populated MIDI circuit has an optoisolator between the external DIN socket and the GPIO pin.

    Of course if a new PCB is being used, the MIDI section can be left unpopulated and it would then be possible to directly connect one of the MIDI sockets to either RX or TX. But for this project I’m just planning on reusing my generic, fully populated board, hence RX is not an option.

    The Code

    The basic tone generation is simply the Arduino tone function (more here). The frequency is a simple sum of the three potentiometers giving a range from 0 up to 3069. In musical terms this spans the range of notes from A0 (and lower) almost up to G7 (just over 3kHz).

    Once the choice of frequency has been made, by ear alone, the button can be pressed and the display used to show both the frequency chosen and the numerical difference from 440Hz. The winner is the person closest to 440Hz.

    To add an additional feeling of skill, but in reality to add a dose of luck, I’ve added two decimal points in the display which are actually randomly chosen each time the button is pressed. The idea being that if two players both get a pretty close 440Hz then the winner is essentially selected by random!

    In order to avoid having to do decimal arithmetic in the code however, when calculating the difference I’m multiplying the frequency by 100, adding in the (random) decimal as if it was just a number between 0 and 99 and then comparing the result to 44000.

    D3 is selected as the audio output and D4 is selected as the trigger button for the display using the following two lines:

    int SPEAKER = 3;
    int TRIGGER = 4;

    Additional notes from the code:

    • I’ve implemented a software smoothing algorithm for the reading of the potentiometers in an attempt to remove some jitter. Although again a small amount of jitter brings another element of luck into the game.
    • When printing out, I have to take care of blank spaces in the frequency to keep the decimal point aligned for anything from 1 to 4 digits; but also have to allow for an extra leading zero in the case of randomly selecting a decimal in the range 0.01 to 0.09.
    • I’m using the Adafruit GFX and SSD1306 libraries for the Arduino.
    • When the button is pressed, the display changes to show the frequency. When it is released the frequency is hidden again.

    Find it on GitHub here.

    Closing Thoughts

    This was a bit of a diversion and I could have easily knocked something up from solderless breadboard or stripboard with some pots and a display. But being able to just grab the PCB and experiment with the code made putting it together pretty easy.

    Kevin

    https://diyelectromusic.wordpress.com/2023/12/01/arduino-guess-the-frequency-game/

    #arduinoNano #potentiometer #ssd1306 #tone

  26. This takes the Arduino Nano Mozzi Experimenter Shield PCB and swaps out three of the pots for the optional use of the following:

    • MCP4725 DAC.
    • SSD1306 display.
    • IO links for an analog multiplexer.

    This is in addition to the remaining three potentiometers, the MIDI interface and the audio filter output.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    The Build Guide is available here: Nano Audio Experimenter Sheild PCB Build Guide.

    If you are new to Arduino, see the Getting Started pages.

    The Circuit

    The board includes a 5V MIDI IN and unbuffered MIDI OUT. It includes a barrel jack for power directly into the Arduino Nano’s raw input.

    It includes pin headers for an SSD1306 display and MCP4725 DAC and headers to allow connection to an analog multiplexer such as a 4051 (8-way) or 4067 (16 way). This is connected to A3.

    There is also an audio output filter stage that can be switched between D3 or D9 for PWM output or the DAC.

    The following Arduino pins are used in total:

    • A0, A1, A2: potentiometers.
    • A3: optional analog multiplexer input.
    • A4, A5: SCL, SDA I2C interface.
    • D0, D1: UART MIDI RX and TX.
    • D3 or D9: optional PWM output.
    • D4, D5, D6 and optionally D7: multiplexer “address” outputs.

    PCB Design

    Key aspects of the design:

    • It follows quite closely the format of the Arduino Nano Mozzi Experimenter Shield PCB on the power, MIDI and Nano side.
    • A jumper allows the audio output to be connected to D3, D9 or neither if a DAC is plugged in.
    • The multiplexer’s S4 pin can be hard-jumpered to ground if required or left unconnected (or both) if not using a 4067 16-way device.
    • There are header pins which match the footprint of a DPDT switch to disable the MIDI link to D0/D1 to allow uploading of sketches.
    • The audio section of the DAC has a separate GND plane for the analog output section. If the DAC is not used this has to be connected to the GND plane of the rest of the board, so an additional connecting, plated hole is provided near to the DAC header footprint.

    Closing Thoughts

    I believe it is possible to use both the SSD1306 display and the MCP4725 DAC whilst servicing the other IO on the board, but I can’t imagine it would yield any practical or useful function to do so!

    This is a board with options that aren’t necessarily meant to all be used at the same time. It is the latest in my line of “experimenter” boards, essentially designed for messing about with audio on the Arduino Nano.

    The only thing I wished I’d added was some test points for an oscilloscope probe.

    Kevin

    https://diyelectromusic.wordpress.com/2023/11/12/nano-audio-experimenter-sheild-pcb/

    #74hc4067 #arduinoNano #cd4051 #mcp4725 #midi #multiplexer #mux #pcb #pwm #ssd1306

  27. Converting A B&W Exposer Head For Colour Analog Photo Printing - [Koraks tinkers] was gifted a gargantuan photographic enlarger, a Durst Laborator ... - hackaday.com/2023/05/08/conver #analogphotography #arduinonano #ledhacks #mcp23016 #pca9685 #cobled #rgbled #pwm #ra4

  28. Rather than a "Hello world!" first post, here are some of the microcontrollers, hubs, etc. that I'm making blink as I learn MicroPython, PyBricks, and NXC.

    I'm excited to get back to being creative after a lot of change in my life.

    #pybricks #micropython #NXC #minisamm4 #attiny85 #attiny88 #raspberrypipicow #arduinonano #d1tiny #raspberrypi4 #raspberrypibuildhat #NXT #EV3 #technichub #burningmanmikeminifig

  29. heise-Angebot: Arduino-Projekt: Kellerlüfter mit Taupunktsteuerung

    In der Make 1/22 zeigen wir, wie man clever den Keller trocken bekommt: ganz ohne energiefressenden Verdampfer, mit reiner Physik und einem Arduino Nano.
    Arduino-Projekt: Kellerlüfter mit Taupunktsteuerung
  30. Arduino Nano Floppy Emulator For When Your Disk Is Not Accessible - Among the plethora of obsolete removable media there are some which are lamented, ... - hackaday.com/2021/10/17/arduin #floppyemulator #arduinohacks #arduinonano #floppydisk

  31. Arduino Nano Floppy Emulator For When Your Disk Is Not Accessible - Among the plethora of obsolete removable media there are some which are lamented, ... - hackaday.com/2021/10/17/arduin #floppyemulator #arduinohacks #arduinonano #floppydisk