home.social

#arduinonano — Public Fediverse posts

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

  1. Я думала, я закончила настройку еще вчера...
    Но утром случился неприятный сюрприз - ардуина ушла в бесконечный цикл чтения датчика... провозилась еще час - оказалось, питание 5V для датчика слишком велико - капризничает - переткнула на пин 3V3 - вроде бы стабильно работает.
    Жду пока остынет датчик.
    Настенный датчик показывает влажность 10% меньше. Не знаю какому верить!
    ​:blobcatgooglyshrug:​

    #diy #arduinonano #humidity_air

  2. Изменила десклет, добавив смайликов😉

    Датчик и плату убрала в пластиковую коробочку, в которой сделала два отверстия: датчик приклеила на термоклей ровно над ним -чтобы воздух свободно мог бы проходить через него.

    Завтра выложу куда-нибудь итоговые файлы: десклета для рабочего стола Cinnamon, скетча для ардуино, настройки usb порта.

    #diy #arduinonano #humidity_air

  3. Desklet готов!
    В постоянном режиме выводит данные о температуре и влажности на рабочий стол.

    Теперь надо упаковать это всё в какую-то коробочку или баночку (для защиты от котиков
    😸!)

    #diy #arduinonano #humidity_air

  4. Desklet готов!
    В постоянном режиме выводит данные о температуре и влажности на рабочий стол.

    Теперь надо упаковать это всё в какую-то коробочку или баночку (для защиты от котиков
    😸!)

    #diy #arduinonano #humidity_air

  5. Йеее ! 🎉
    скетч работает, данные* читает!

    *кстати, датчик показывает на 10 % больше чем китайский

    Продолжаем!

    #diy #arduinonano #humidity_air

  6. Йеее ! 🎉
    скетч работает, данные* читает!

    *кстати, датчик показывает на 10 % больше чем китайский

    Продолжаем!

    #diy #arduinonano #humidity_air

  7. #diy #arduinonano #humidity_air

    Из далекого Хуижоу
    ​:02laugh:​ приехал наконец-то датчик влажности, масюсенький совсем!​:blobcatprettyplease:​

    Осталось найти гайд, как его подключить к Ардуино и дописать десклет для вывода влажности на экран. Конечно это будет голимый вайбкодинг, но думаю какой-нибудь дипсик справится!)))

  8. #diy #arduinonano #humidity_air

    Из далекого Хуинжао
    ​:02laugh:​ приехал наконец-то датчик влажности, масюсенький совсем!​:blobcatprettyplease:​

    Осталось найти гайд, как его подключить к Ардуино и дописать десклет для вывода влажности на экран. Конечно это будет голимый вайбкодинг, но думаю какой-нибудь дипсик справится!)))

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

  10. 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 a 4×4 output grid be supported?
    • 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.

    The simulator can be found on GitHub here: https://github.com/diyelectromusic/TD4-CPU

    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.

    Complete Bill Of Materials

    ICs:

    • 1x 74HC10 Triple 3-input NAND
    • 1x 74HC14 Hex Schmitt trigger inverters
    • 1x 74HC32 Quad 2-input OR
    • 1x 74HC74 Dual D-Type Flip Flop
    • 2x 74HC153 Dual 4-to-1 selector/multiplexer
    • 4x 74HC161 4-bit binary counter
    • 1x 74HC283 4-bit binary full adder

    Semiconductors and Passive Components

    • 25x 3x2mm rectangular LED
    • Resistors: 2x100R; 33x 1K; 1x 3K3; 1x 10K; 1 x 33K; 3x 100K
    • Capacitors: 3x 10uF electrolytic

    Other components:

    • 2x SPDT slider switches (see PCB for footprint)
    • 1x micro USB socket (Molex, see PCB for footprint)
    • 2x tactile switches
    • 1x 4-way DIP switches
    • DIP sockets: 7x 16 way; 4x 14 way
    • 2x 15-way pin header sockets

    And 1 Arduino Nano of course.

    The PCB can be found on Github here: https://github.com/diyelectromusic/TD4-CPU. The video at the end of this post shows it in action.

    Nano Assembler Update

    I’ve updated my Nano assembler with a new command to change to 5-bit address mode if required.

    Help
    ----
    H: Help
    L: List
    G: Goto
    C: Clear
    R: Restore
    A: Addr Mode
    O: Opcodes
    OpCode
    OpCode im

    Current line: b0101 [15]

    Address Mode = 5 bit


    RAM Disassembly

    b00000 [0]: OUT b0001b1010 00010xA1b10000 [10]: OUT b0001b1010 00010xA1
    b00001 [1]: ADDA b0001b0000 00010x01b10001 [11]: OUT b0010b1010 00100xA2
    b00010 [2]: OUT b0010b1010 00100xA2b10010 [12]: OUT b0100b1010 01000xA4
    b00011 [3]: ADDB b0001b0101 00010x51b10011 [13]: OUT b1000b1010 10000xA8
    b00100 [4]: OUT b0100b1010 01000xA4b10100 [14]: OUT b0100b1010 01000xA4
    b00101 [5]: ADDA b0001b0000 00010x01b10101 [15]: OUT b0100b1010 01000xA4
    b00110 [6]: OUT b1000b1010 10000xA8b10110 [16]: OUT b0010b1010 00100xA2
    b00111 [7]: ADDB b0001b0101 00010x51b10111 [17]: OUT b0001b1010 00010xA1
    b01000 [8]: OUT b0100b1010 01000xA4b11000 [18]: OUT b0001b1010 00010xA1
    b01001 [9]: ADDA b0001b0000 00010x01b11001 [19]: OUT b0010b1010 00100xA2
    b01010 [A]: OUT b0010b1010 00100xA2b11010 [1A]: OUT b0100b1010 01000xA4
    b01011 [B]: ADDB b0001b0101 00010x51b11011 [1B]: OUT b1000b1010 10000xA8
    b01100 [C]: OUT b0001b1010 00010xA1b11100 [1C]: OUT b1000b1010 10000xA8
    b01101 [D]: ADDA b0000b0000 00000x00b11101 [1D]: OUT b0100b1010 01000xA4
    b01110 [E]: OUT b1111b1010 11110xAFb11110 [1E]: OUT b0010b1010 00100xA2
    b01111 [F]: ADDA b0000b0000 00000x00b11111 [1F]: OUT b0001b1010 00010xA1
    Current line: b10101 [15]

    When in 4-bit mode (the default) it will continue to act as previously, wrapping the address around between 0 and 15. But when it switches to 5-bit mode it will now wrap between 0 and 31 and the list function will show the whole 32 bytes of RAM/ROM side by side as show above.

    The updated sketch is available on GitHub here.

    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

    https://makertube.net/w/fAp8ZsbPLUYEKiStc34J9o

    #arduinoNano #pcb #TD4

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

    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

    The Code

    This is taking a combination of the following previous projects:

    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.

    Find it on GitHub here.

    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

    #arduinoNano #ay38910 #midi #tone

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

    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

    The Code

    This is taking a combination of the following previous projects:

    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.

    Find it on GitHub here.

    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

    #arduinoNano #ay38910 #midi #tone

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

    Find it on GitHub here.

    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

    #arduinoNano #ay38910 #pcb

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

    Find it on GitHub here.

    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

    #arduinoNano #ay38910 #pcb

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

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

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

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

  19. 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 &= 0xFB;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    BC1HIGH;
    BDIRHIGH;

    // Latch address
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS settle then 50nS preamble
    delayMicroseconds(1);

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

    The timings come from the AY-3-8910 datasheet:

    The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.

    There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.

    This now gives me the following:

    This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.

    The downside of this method:

    • It is ATMega328 specific.
    • It is specific to the pin mappings and PORT usage of this PCB.
    • It does not support reading or other chip operations between the writes.

    It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.

    DDS Part 2

    Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.

    Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.

    Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.

    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
  20. 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 &= 0xFB;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    BC1HIGH;
    BDIRHIGH;

    // Latch address
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS settle then 50nS preamble
    delayMicroseconds(1);

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

    The timings come from the AY-3-8910 datasheet:

    The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.

    There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.

    This now gives me the following:

    This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.

    The downside of this method:

    • It is ATMega328 specific.
    • It is specific to the pin mappings and PORT usage of this PCB.
    • It does not support reading or other chip operations between the writes.

    It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.

    DDS Part 2

    Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.

    Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.

    Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.

    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
  21. Arduino and AY-3-8910 – Part 2

    Following on from my initial experiments in Arduino and AY-3-8910 this post looks at the sound generation capabilities in a little more detail and adds some basic MIDI control.

    https://makertube.net/w/3CxNBDKu5Gm6MQzMcLZYz6

    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.

    AY-3-8910 Sound Generation

    The most basic means of sound generation is to use the three tone generators to generate square waves at a frequency set using the on-chip registers (note in the following data from the datasheet, the R numbers are in octal – so there are 16 registers in total):

    I don’t plan to get into the ins-and-outs of how to interface to the chip, instead I’ll link off to some excellent discussions here:

    The frequency registers have a 12-bit resolution, spread over two 8-bit registers as shown below.

    The datasheet tells us how to calculate the value to write to the register for a specific frequency:

    • Reg Value = System Clock / (16 * frequency)

    For a 1MHz clock, the register value is thus 62500 / frequency, so the higher the frequency, the lower the register value. This means that concert A at 440Hz requires the value 62500 / 440 = 142, so:

    • R1 = 142 >> 8;
    • R0 = 142 & 0xFF;

    For a 1MHz clock, the range of frequencies goes from 1MHz / 16 to 1 MHz / (16 * 4095) or ~62.5kHz to 15 Hz

    The AY3891x library has a set of definitions that already defines the frequencies for each MIDI note from C0 to B8.

    The volume for the note is set in another register:

    When Mode=0, the amplitude is set by the 4-bit fixed level. When Mode=1, the amplitude is controlled by the built-in envelope generator.

    The envelope generator is a “global” setting for all channels, so for finer control, people often wrote their own envelope generator, manipulating the volume levels directly.

    The datasheet describes the four parameters required to define an envelope:

    Envelopes have a cycle time, which is set by R13 and R14 in a similar way to the frequency. This time the formula is:

    • Reg value = System Clock / (256 * env frequency)

    Once again this is split over two registers, but this time supports a full 16-bit value.

    There are graphical representations of what the combinations of the envelope bits mean, but I must confess I’m not entirely sure I understand them all and some don’t seem to sound, at least at the frequency I’ve chosen.

    The Circuit

    I’m reusing the PCB from GadgetReboot from Part 1, but this time I’ve added the button in (it is connected to A5 and GND) and added headers to the UART connection.

    Unfortunately the UART only has GND, TX, RX – to use it with one of my Arduino MIDI Interfaces also requires a 5V connection, so I’ve taken that from the SD card header.

    The Code

    The note-playing code comes from the “AY3891x_EX3_Simple_Tone” example, including the ATmega328 specific code for the 1MHz clock. There is a table of note frequencies already provided for notes C0 through to B8, so it is just a case of mapping these onto MIDI notes C0 (12) through B8 (119).

    One thing I wanted was to support simple polyphony using all three channels. But that means deciding what to do when a fourth note comes in – i.e. to ignore it or to replace one of the existing playing notes. I’ve left options for both.

    I also wanted to make use of the channel volume too, so it is relatively trivial to map the MIDI 0..127 note velocity values onto the 0..15 levels for the sound generator. This is using the “fixed” level mode mentioned earlier.

    But I also wanted the option to play with the envelopes, so I’ve wired in the button and have an option to use that to change between the different envelopes. As I’m not attempting anything particularly complex right now, I just gone with a fixed 10Hz frequency for the envelope generator’s cycle.

    It won’t win any prizes for synthesis, but it does work.

    Find it on GitHub here.

    Closing Thoughts

    Fundamentally, without the envelope generation this is the same as a three-channel Arduino tone() function, but at the time that was pretty ground-breaking as it allowed a system to keep playing a tone without having to keep driving it from the CPU.

    Add in the noise channel, amplitude control and envelopes and you can start to see why this is also a step up musically too.

    But when you get to making custom, per-channel envelopes, or even manipulating the 4-bit level control as a simple DAC or PCM generator, then you can start to see how some of the outstanding chiptunes of the time could be generated.

    But even in this simple form there is still a fair bit more that could be done. Some examples might be:

    • Adding a potentiometer to control the envelope frequency. The when used with the triangle envelope this will act as a modulation control.
    • Add MIDI control values for the volume levels, modulation and choice of envelope.
    • Define some specific parameters to create “instruments” which can be selecting using MIDI program change messages.
    • Get the noise generator into the mix and define some percussion “instruments” too.

    But what I really want to do is start taking a look at some of the sound drivers that were written that allow some of the chip tunes to come out.

    I’m also getting to the point where I want my own PCB with things on it that I want to play with too.

    Kevin

    #arduinoNano #ay38910 #chiptunes #envelopeGenerator #midi

  22. @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.

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

  24. Arduino and AY-3-8910

    I’ve had some AY-3-8910 programmable sound generators for a while and planned to do something with them, but they’ve sat in the “to think about” box for a while. But recently getting hold of the WhyEm sound card for the RC2014 has re-awakened my interest.

    This is some “first looks” experiments with the AY-3-8910 and an Arduino.

    https://makertube.net/w/26yAj7wAzZwCsFoBkJpZs2

    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.

    The AY-3-8910 PSG

    The AY-3-8910 programmable sound generator is quite an iconic chip of its time. Not as popular maybe as the SID used in the Commodore 64, but still pretty widespread use in 8-bit computers of the time and apparently a number of arcade machines too.

    There are a number of “compatible” chips, including the Yamaha YM2149. There is also the AY-3-8912 which is the same as the 8910 but without one of the general purpose IO ports, which take no part in the sound generation.

    I’m not going to go into the details of the device itself, for that I’ll refer to the following resources:

    The summary of the specification though is as follows:

    • Three square wave output channels.
    • Global AD envelope generator, with a selection of fixed envelopes.
    • Noise generator an option on the channels.
    • Mixer.

    There is an 8-bit data register interface to the chip along with three control signals (BDIR, BC1 and BC2).

    Sourcing AY-3-8910 Chips

    I should point out that it isn’t possible to buy new AY-3-8910 or YM2419 chips these days, so any that can be found online are almost certainly reclaimed from older hardware. These may or may not work reliably, but it will be a bit of a lottery.

    There is a lot of discussion about the issue here: http://blog.tynemouthsoftware.co.uk/2023/01/testing-ay-3-8910-ym2149-sound-card-for-rc2014-and-minstrel-4D.html

    Some more here: https://maidavale.org/blog/remarked-chips-from-ebay/

    This is why RC2014 eventually went with the “Why Emulator” approach: https://rc2014.co.uk/modules/why-em-ulator-sound-module/

    My own experience is that you can get them on ebay pretty cheaply. They are often described as “new” but really they will be pulled from older hardware and be cleaned up to look new again.

    Usually this means re-tinning the pins with solder, sanding down the original markings, and re-etching the text and logos. I guess they do this to be able to claim “as new”, but personally I’d rather they just let them be sold as reclaimed/recycled. The prices always reflect a reclaimed level of quality to me anyway.

    Here is a photo of a recent batch of 10 bought as a pack from the same ebay seller.

    Notice how, whilst the text and logos seem to essentially be the same, there are many differences between the actual devices:

    • Depth, position, size of the molding marks.
    • Depth, position, size of the pin 1 marker.
    • Colour of the final finish.
    • They also all have very different under-side markings and appearance (not shown).

    I’ve also never seen one yet that doesn’t claim to be a Microchip device, whereas in reality most original devices I suspect would be either Yamaha YM2419 or GI AY-3-8910, so are being re-etched.

    It is possible to tell if the device is a YM or AY as they respond slightly differently when the 4-bit registers get written to and read. If the value 31 is written to one of the 4-bit registers then a YM will read back 31 but an AY device will read back 15.

    Full details here: http://blog.tynemouthsoftware.co.uk/2023/01/testing-ay-3-8910-ym2149-sound-card-for-rc2014-and-minstrel-4D.html

    The Arduino AY3891x library includes a sketch to check and print the result (see below).

    Hardware

    For once, I’m using someone else’s design for my initial messing about. There is a schematic and some hardware built on a protoboard shown in the Arduino YM3891x library here: https://github.com/Andy4495/AY3891x/tree/main/extras/hardware

    But there is a PCB that was designed by GadgetReboot here: https://github.com/GadgetReboot/AY-3-8910. This can be ordered directly from PCBWay and so that is what I’ve used.

    The only thing to watch out for, is that D2/D3 are reversed compared to the protoboard hardware. So whilst all other pin definitions in the library examples are fine, D2 and D3 need to be swapped to use them with the PCB.

    I also didn’t bother to add the power LED (and R5), the toggle switch or the additional header pins.

    The full pinout interface from the Arduino to the AY-3-8910 is as follows:

    AY-3-8910ArduinoDA0-DA7D2-D8, A3BC1A0BC2A1BDIRA2CLOCKD9/RESETA4 (not used in the library)

    The most critical one from a re-use point of view is the clock as the code uses the ATmega328P hardware Timer 1 to output a 50% duty cycle PWM signal on output OC1A (D9) to generate the 1MHz clock required by the AY-3-8910.

    Using a different microcontroller will mean re-writing this part of the code for a suitable replacement timer.

    The PCB also has the following Arduino GPIO pin use:

    • D0/D1 – UART pin header
    • D10-D13 – SD card pin header
    • A4 – /RESET of the AY-3-8910 but doesn’t appear to be required to use the library.

    The AY-3-8910 is powered from the +5V from the Arduino. VIN for the Arduino is not connected, so USB power is assumed.

    The Code

    Taking the “AY3891x_EX3_Simple_Tone” example as a starting point, the following line needs updating as shown below:

    // Original line
    AY3891x psg( 17, 8, 7, 6, 5, 4, 2, 3, 16, 15, 14);

    // Required for use with the PCB
    AY3891x psg( 17, 8, 7, 6, 5, 4, 3, 2, 16, 15, 14);

    This is using the “lesser number of pins” constructor, which assumes that the A8, A9, /RESET and CLOCK pins of the AY-3-8910 are not managed by the library. There is a more complete constructor to define the additional pins if required. There is a good description of how the AY-3-8910 interface works in the main source code: https://github.com/Andy4495/AY3891x/blob/main/src/AY3891x.cpp

    I also switched any Serial.begin() statements to use 9600 baud for preference too.

    With these changes, the simple tone example just works, as does the “AY3891x_EX8_Check_Orig_or_Clone”, which tells me I have an AY-3-8910 rather than a YM2149.

    But what I really wanted was to play one of the tunes from a game I remembered. My first introduction to AY-3-8910 music was the 128K Spectrum version of The Neverending Story. This was one of the two games that came bundled with the original machine, and for someone used to the 48K beeps and boops it was an amazing upgrade.

    Unfortunately I don’t seem able at present to find a “YM” file version of that tune to play, so instead I turned to a David Whittaker classic – “Glider Rider”. The “YM Jukebox” GitHub repository has a whole pile of YM files ready to use here: https://github.com/nguillaumin/ym-jukebox/tree/master/data/David%20Whittaker

    There are (I believe) two key ways to get “chiptunes” for the AY-3-8910. A YM file is a stream of the register values sent to the chip to direct the sound generation. With one of these files, it is simply a case of turning these values into a C array and it can be included in an Arduino sketch (there are several steps required, see below, but it is all doable).

    Another common way to get tunes is an “AY” file. As I understand things these are the actual Z80 assembler instructions required to drive the AY-3-8910 chip, so extracting the required data for use on another system is not so easy. These are really designed to be used on the original systems or via emulation.

    To produce an Arduino source code file from a YM file requires the following steps (described here: https://github.com/Andy4495/AY3891x/tree/main/extras/tools):

    python decoder.py GliderRider.ym force_interleaved
    python bin2c14.py outputfile > chiptunes.h
    • Take the resulting const PROGMEM structure into the “chiptunes.h” file of the “AY3891x_EX6_Chiptunes_Flash” example sketch.

    Note that due to the memory limitations of the Arduino Nano, the resulting C structure for the tune will have to be cut-off at around 2000 lines, but be sure to leave the termnating part of the structure:

    const byte PROGMEM psg_data[] = {
    0x6C,0x07,0x7C,0x07,0x7C,0x07,0x17,0x31,0x0F,0x00,0x00,0x00,0x00,0x20,
    0x6C,0x07,0x7C,0x07,0x7C,0x07,0x1F,0x31,0x0D,0x00,0x00,0x00,0x00,0x00,
    ....
    0x7C,0x07,0xBE,0x03,0x58,0x02,0x01,0x31,0x09,0x0E,0x00,0x00,0x00,0x00,
    };

    If there are build errors, then it might be because the structure is still too long. 2000 lines fits for me using around 97% of the programme memory of the Arduino Nano.

    If full (longer) tunes are required, then the PCB has the option to hook up to an SD card and longer files can be stored there.

    YM Files

    Here is a little more detail about the above process required to get a YM file into C code, mostly for my own reference (the above links all talk about it pretty well tbh).

    Step 1: A downloaded YM File is usually actually compressed using LHA. 7Zip can open these files and save the extracted version ready for step 2.

    Here is the difference of the start of the files shown using a hex editor. Notice the “-lh5-” header near the start showing that this is a LHA compressed file, and a filename associated with the uncompressed version, in this case glrider.ym.

    Uncompressed:

    We can now clearly see he header information including the file identifier “YM5!” along with title, author and copyright information in comment fields.

    The AU3891x library goes on to explain: “This uncompressed binary file has the audio programming registers stored in a non-interleaved format for more efficient compression. These values now need to be interleaved for more efficient playback.”

    We can find more details of the YM file format here, in the words of the creator of the format, Arnaud Carré, directly: https://www.lynn3686.com/ym3456_tidy.html

    A music-file is composed of YM2149 registers generated by the
    original play-routine for each 50th seconds. As the YM2149 has 14 registers 8 bits each, that means 14 bytes for 1/50 second, so 700 bytes for one second of soundchip.

    When I convert an ATARI music, I play the music on the ATARI, and I
    store YM2149 registers set each 1/50sec (Vertical Blank time, VBL) in a big file as follow:

    VBL1:
    store reg0,reg1,reg2,...,reg12,reg13 (14 regs)
    VBL2:
    store reg0,reg1,reg2,...,reg12,reg13 (14 regs)
    ...
    VBLn:
    store reg0,reg1,reg2,...,reg12,reg13 (14 regs)

    The problem is that is takes a lot of disk-space. Just count: A 10
    minutes song will take 420000 bytes on disk. But don't panic, the music are compressed with LHARC method (using LHA program from Haruyasu Yoshizaki). To reach best compression ratio, I store registers in a different order:

    VBL1 reg0, VBL2 reg0, VBL3 reg0 .... VBLn reg0
    VBL1 reg1, VBL2 reg1, VBL3 reg1 .... VBLn reg1
    ...
    VBL1 reg14,VBL2 reg14,VBL3 reg14.... VBLn reg14

    Hence the “interleaved” format discussed previously. So looking at the file itself, specifically for YM5 it has the following format:

    Offset Size Type Comment
    0 4 DWORD ID of YM5 format. ('YM5!')
    4 8 string[8] Check String ('LeOnArD!')
    12 4 DWORD Nb of valid VBL of the file.
    16 4 DWORD Song attributes (see below)
    20 2 WORD Nb of digi-drum sample (can be 0)
    22 4 DWORD YM2149 External frequency in Hz (ex:2000000 for ATARI-ST version, 1000000 for AMSTRAD CPC)
    26 2 WORD Player frequency in Hz (Ex: 50Hz for almost player)
    28 4 DWORD Vbl number to loop the song. (0 is default)
    32 2 WORD Size (in bytes) of futur additinal data. (must be 0 for the moment)

    So we can see this playing out from the above file:

    00000: 59 4D 35 21 = "YM5!"
    00004: 4C 65 4F 6E 41 72 44 21 = "LeOnArD!"
    0000C: 00 00 13 60 = Number of frames: 4960
    00010: 00 00 00 01 = Attributes:
    b0: Set if Interleaved data block.
    b1: Set if the digi-drum samples are signed data.
    b2: Set if the digidrum is already in ST 4 bits format.
    00014: 00 00 = No digidrum samples
    00016: 00 0F 42 40 = External frequency 1MHz
    0001A: 00 32 = Player frequency 50Hz
    0001C: 00 00 00 00 = Number of loops for the song: 0
    00020: 00 00

    This header is then followed by:

    For each digidrum sample:
    4 DWORD sample size
    nnnn BYTES sample data (8bits per sample)

    NT-String Name of the song.
    NT-String Name of the author.
    NT-String Comments (YM file converter ?!)

    All YM2149 registers.

    4 DWORD End-File check. ('End!')

    Which again we can now see in the uncompressed file:

    (no digidrum samples)
    00022: 476C6964657220526964657200 = "Glider Rider"
    0002E: 4461766964205768697474616B657200 = "David Whittaker"
    0003E: 2863293139383620517569636B73696C766100 = "(c)1986 Quicksilva"

    00052: 6C6C7C8C... = 4960 values for reg0
    013B2: 07070707... = 4960 values for reg1
    ...
    0FC32: 02000000... = 4960 values for reg13
    10F92: 00000000... = 2x4960 additional sets of values
    13652: 45 6E 64 21 = "End!"

    Curiously there seems to be data for 16 registers, which I guess would allow the inclusion of the two GPIO ports as well as the sound generation registers.

    So to turn this into a stream of register values to send to the AY-3-8910 at a rate of 50Hz requires take the same “VBL” value for each register in turn and de-interleaving it.

    This is the output from the decoder.py file as it processes the above:

    Attempting to read file at glrider.ym...
    YM5! format file detected based on first four bytes of file...
    4960
    File is interleaved...
    Song title: Glider Rider
    Author: David Whittaker
    Comments: (c)1986 Quicksilva
    Data length is 79360 bytes...
    Song length is 4960 frames...
    Deinterleaving...
    Register 0: 0x6c 01101100
    Register 1: 0x07 00000111
    Register 2: 0x7c 01111100
    Register 3: 0x07 00000111
    Register 4: 0x7c 01111100
    Register 5: 0x07 00000111
    Register 6: 0x17 00010111
    Register 7: 0x31 00110001
    Register 8: 0x0f 00001111
    Register 9: 0x00 00000000
    Register 10: 0x00 00000000
    Register 11: 0x00 00000000
    Register 12: 0x00 00000000
    Register 13: 0x20 00100000
    ==Frame#00000/04959======
    Register 0: 0x6c 01101100
    Register 1: 0x07 00000111
    Register 2: 0x7c 01111100
    Register 3: 0x07 00000111
    Register 4: 0x7c 01111100
    Register 5: 0x07 00000111
    Register 6: 0x1f 00011111
    Register 7: 0x31 00110001
    Register 8: 0x0d 00001101
    Register 9: 0x00 00000000
    Register 10: 0x00 00000000
    Register 11: 0x00 00000000
    Register 12: 0x00 00000000
    Register 13: 0x00 00000000
    ==Frame#00001/04959======

    ...

    Register 0: 0x7c 01111100
    Register 1: 0x07 00000111
    Register 2: 0x54 01010100
    Register 3: 0x00 00000000
    Register 4: 0x0f 00001111
    Register 5: 0x03 00000011
    Register 6: 0x01 00000001
    Register 7: 0x31 00110001
    Register 8: 0x07 00000111
    Register 9: 0x0c 00001100
    Register 10: 0x0d 00001101
    Register 11: 0x00 00000000
    Register 12: 0x00 00000000
    Register 13: 0x00 00000000
    ==Frame#04959/04959======

    Notice that this is only pulling out the 14 sound generating registers. We can see this in the resultant de-interleaved file.

    We can see tha tthe first two values correspond to the first items for reg0 and reg1: 6C and 07 and finish the 14 values with 20 before starting on the new set of 14 register values (again starting with 6C and 07 in this case).

    The final step turns the above data stream into a C header file mentioned above.

    To play the file thus requires the following algorithm:

    Every 50Hz:
    Read the next 14 register values from the data
    Write all 14 register values out to the AY-3-8910

    Closing Thoughts

    I’ve really not done very much myself this time. The PCB was from GadgetReboot. The library from Andy4495. The tune data provided by the YM jukebox and all other scripts and bits of the process were available online.

    Now I need to decide what I’d like to do with all this.

    Kevin

    #arduinoNano #ay38910 #chiptunes

  25. Adding Automatic Emergency Braking To An RC Car - Modern RC cars can be pretty darn fast. That’s fun and all, but it also makes it e... - hackaday.com/2025/07/02/adding #transportationhacks #radiocontrol #arduinonano #toyhacks #r/ccar

  26. Adding Automatic Emergency Braking To An RC Car - Modern RC cars can be pretty darn fast. That’s fun and all, but it also makes it e... - hackaday.com/2025/07/02/adding #transportationhacks #radiocontrol #arduinonano #toyhacks #r/ccar

  27. Duppa I2C MIDI Controller – Part 3

    This is a follow up post to Part 2 expanding on the code and adding in some of the alternative control options I mentioned.

    • Part 1 – getting to know the devices and testing them out.
    • Part 2 – adding MIDI to the LED ring and I2C encoder.
    • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
    • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

    https://makertube.net/w/ncLFMqBwCUcrrM4r3mHJwd

    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 Arduino, see the Getting Started pages.

    Parts list

    • Arduino Uno/Nano.
    • DuPPa small RGB LED Ring.
    • 10K potentiometer.
    • KY-040 rotary encoder module.
    • Bespoke hook-up wires (available from duppa.net).
    • Breadboard and jumper wires.

    The Circuit

    This circuit shows all three control options on the same diagram, but naturally use whichever one you require. There are the following three options:

    • Duppa I2C Encoder (from the previous project) on A4, A5 (SDA, SCL).
    • Plain potentiometer on A0.
    • Rotary encoder on D10, D11, D12.

    In addition to the I2C LED Ring on A4, A5 (SDA, SCL) of course.

    Here is a separate diagram for the endless potentiometer. I can be used alongside the above, but I’ve drawn it on its own for clarity.

    Mine has the pins in the following order: Wiper A, GND, Wiper B, VCC.

    The Code

    This uses the code from Part 2 as a starting point and adds in some additional features.

    The main aim is to add additional control options and then make them selectable in code. There are several definitions at the top of the file. One of them should be uncommented to enable that control option.

    //#define CC_ENCODER
    #define CC_POTENTIOMETER
    //#define CC_I2CENCODER
    //#define CC_ENDLESSPOT

    Each control method will have some associated functions, but the interface will be hidden away behind these definitions and called from the main code, something like the following.

    #ifdef CC_ENCODER
    #include <librarycode.h>
    ... control specific definitions ....

    void encSetup() {
    // Control specific setup code
    }

    void encLoop() {
    // Control specific loop code
    }
    #else
    void encSetup() {}
    void encLoop() {}
    #endif

    If that control option is not enabled, then it will just end up with the two empty functions.

    Normal Potentiometer

    This is fairly straightforward. I’m using the averaging technique I’ve mentioned before (details here) and include a counter so that the pot isn’t read on every scan, otherwise it slows down all other processing significantly.

    The pot reading is scaled down to 7-bits from the default 10-bit value with a bitshift.

    I’ve opted to have a jump if the CC value is updated over MIDI rather than anything more sophisticated, as that is probably the simplest.

    All the same display options are available as used previously: one LED per CC; scaled to multiples of the ring size; or scaled to a single ring.

    This works quite well with all of them, but probably makes most sense when the MIDI CC range is scaled to a single revolution of the LED ring.

    CC_WRAP has no effect when used with a normal potentiometer, as the pot itself does not wrap around.

    Rotary Encoder

    This is using the same encoder library I’ve used before in my Arduino MIDI Rotary Encoder Controller. This makes the scanning code relatively straight forward.

    I’ve refactored out the increment/decrement functions from the I2C encoder code into midiCCIncrement and midiCCDecrement, so these can now be used by both encoder options.

    These encoder modules are often switched, but I’m not making use of the switch here.

    Once again, all the same display options are available: one LED per CC; scaled to multiples of the ring size; or scaled to a single ring. CC_WRAP can be on or off.

    Endless Potentiometer

    There is a detailed discussion of how these work here: https://hackaday.io/project/171841-driveralgorithm-for-360-deg-endless-potentiometer
    My initial thought was that I could just use one of the wipers, assuming it would go from 0 to full resistance and immediately back to zero, but they don’t – they gradually go from 0 to full resistance and then gradually back to 0 again. See the diagram in the above link.

    This means that some processing is required to get a single reading out of them, so I ended up using a library from here:

    Well actually, I ended up using the slight variant of the library as used on the “Ottopot” MIDI controller, which can be found here:

    In my case I just dropping in the endlesspotentiometer.cpp/h files into my Arduino sketch (swapping any includes from <> to “” in the process). There was one reference to main.h that needed removing, and it required a definition of MAX_POT_VALUE which is 1023 for an Arduino Uno.

    Then the code is fairly straight forward as the library is able to give an indication of direction and how much the pot has moved.

    One thing to watch out for – I wanted this to be able to act on midiCC in a relative manner, replication the benefits of an encoder, but with a potentiometer, so I needed to know how much the pot had changed and then add that to the current midiCC value, rather than set it directly.

    To do this I allowed midiCCIncrement/Decrement to take a parameter – how far to increase or decrease midiCC.

    The core code for handling the endless pot was thus:

    endpot.updateValues(epot1.avgeAnalogRead(PIN_EALG_1),
    epot2.avgeAnalogRead(PIN_EALG_2));
    if (endpot.isMoving) {
    if (endpot.direction == endpot.CLOCKWISE) {
    midiCCIncrement(endpot.valueChanged);
    } else if (endpot.direction == endpot.COUNTER_CLOCKWISE) {
    midiCCDecrement(endpot.valueChanged);
    }
    }

    I also turned the potentiometer averaging code into a class of its own so I could also use it here for both analog readings of the endless pot.

    It took a bit of experimentation with the sensitivity and threshold settings and how they interacted with the frequency of reading, but I’ve ended up with something that seems to work ok for me.

    Summary

    Although at the start I said that one of the options should be commented out to select it, in reality, if the devices are all on separate IO pins, then actually they can all be enabled at once.

    And it does seem to work pretty well, with all four methods: I2C encoder, plain encoder, potentiometer – all interacting as you might expect they would.

    Find it on GitHub here.

    Closing Thoughts

    I was quite surprised how usable everything was with all four input methods enabled. I probably wouldn’t recommend it for typical use, but it was a bit of fun.

    It is particularly satisfying to sweep through the entire range using the pot with “one LED per CC” enabled, even though scaling a single ring to the potentiometers range makes more sense (to me).

    At some point I will try to get several controls up and running.

    Kevin

    #arduinoNano #duppa #endlessPotentiometer #i2c #ifdef #midi #potentiometer #rgbLed #rotaryEncoder

  28. Duppa I2C MIDI Controller – Part 2

    This is a follow up post to Part 1 where I’m starting to look at MIDI applications and a range of control options.

    • Part 1 – getting to know the devices and testing them out.
    • Part 2 – adding MIDI to the LED ring and I2C encoder.
    • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
    • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

    Since posting the first part, I’ve stumbled across a rather excellent DIY MIDI controller that uses 8 of these Duppa LED rings and 8 endless potentiometers (which I hadn’t realised was even a thing!). It is a fantastic build, based on a Teensy and PlatformIO and I totally recommend going and taking a look. Read about it here: https://gerotakke.de/ottopot/.

    https://makertube.net/w/2oKgvpZ29L2oExanSaUXjE

    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 Arduino, see the Getting Started pages.

    Parts list

    • Arduino Uno/Nano.
    • DuPPa small RGB LED Ring.
    • Mini I2C Encoder.
    • Bespoke hook-up wires (available from duppa.net).
    • Breadboard and jumper wires.

    The Circuit

    I eventually want to include various options for controlling the ring and MIDI:

    • Duppa I2C rotary encoder.
    • Plain rotary encoder.
    • Simple potentiometer.
    • Endless potentiometer.

    I don’t have any endless potentiometers yet, they are something I’ve only recently found exist, but I’ll post again when I get a chance to try them!

    The required connections, naturally, are quite different for each case:

    • Duppa I2C encoder: connects to SDA/SCL.
    • Plain rotary encoder: requires two digital IO pins.
    • Simple potentiometer: requires a single analog input.
    • Endless potentiometer: requires two analog inputs.

    In this first post, I’m just looking at the same Duppa I2C Encoder and LED Ring that I used in Part 1 but am adding a 5V compatible MIDI module.

    The Code

    Once again, this is using the Duppa Arduino library: https://github.com/Fattoresaimon/ArduinoDuPPaLib/

    CC, Pots, Encoders…

    The biggest issue I find with attempting to develop a MIDI CC controller is where is the authoritative definition of what the CC value actually is? What do I mean by that? Well we have the value in the synthesizer, defined on power up or via the on-board controls. And then we have the setting in the external MIDI controller. Until the MIDI controller sends an updated value to the synth, these will be different. And if the value on the synth changes locally, e.g. using knobs or controls on the synth, then they will be out of sync.

    I’ve not found an easy answer to this, but what I’m planning is having the CC controller receive MIDI CC messages as well as send them. This means that if the CC value is changed directly on the synth, if the synth transmits that over MIDI, then it will be received by the external controller which can update its own value accordingly.

    One problem with this is that there are two types of hardware controller: relative or absolute.

    A rotary encoder is a relative controller – it turns in a direction and the value will increase or decrease accordingly. If the internal knowledge of the CC value changes, the encoder will just continue to increase of decrease from that new value instead.

    A potentiometer is (usually) an absolute controller – it turns and has a value. If the internal knowledge of the CC value changes, then unless you have a motorised potentiometer, it will still be in the same place so on turning there will be a jump in value from the stored value to the new setting of the potentiometer.

    One option to deal with absolute values is to have the new position value only become relevant once the turning “catches up” with the internal value and the starts adjusting it from that point onwards. But this can create a disjoint between the user experience of turning the knob and it actually changing anything. But on the plus side, absolute values are “remembered” when powered off – providing the knobs are left in the same place.

    I’m hoping to use the encoders as a pseudo potentiometer. But it isn’t going to be possible to have a complete rotation considered the same as a full sweep of a potentiometer, as that will be down to the number of “detents” per rotation and that won’t be anything like enough to support 128 MIDI CC values. But I do plan to indicate the value by LEDS, and use those to indicate the position in the full sweep. This will allow the starting point to change if a CC message is received.

    One solution to this problem, and that used by the Ottopot controller mentioned at the start, is to use an endless potentiometer. This not-only allows a variable starting position, but it also represents a full-sweep of values with a single turn as per a simple potentiometer.

    So when I get hold of some of those I’ll come back to revisit this code.

    For this first version there is code for the I2C encoder implemented using the following functions:

    • i2cEncSetup()
    • i2cEncLoop()
    • i2cEncIncrement()
    • i2cEncDecrement()

    These are based on the code I used in Part 1. The increment and decrement functions act on a global “midiCC” directly, which stores the CC value to use using the range of a single MIDI “byte” 0 to 127. There is a compilation option to allow wrapping around (between 0 and 127) or not.

    MIDI Handler

    The Arduino MIDI library is used for both send and receive and all MIDI functionality is wrapped up in the following functions:

    • midiSetup()
    • midiLoop()
    • midiControlChange()

    There are a few additional functions to give an optional LED indication of MIDI activity. Within the MIDI loop the midiCC value is checked and if it has changed then a MIDI control change message is transmitted:

    void midiLoop() {
    MIDI.read();
    if (lastMidiCC != midiCC) {
    MIDI.sendControlChange(MIDI_CC, midiCC, MIDI_CHANNEL);
    }
    }

    There is an option to have software MIDI THRU enabled and this is handled as part of the MIDI.read() call. On setup midiControlChange() is installed as a handler function for MIDI CC messages and if the correct CC message is received on the correct MIDI channel then the midiCC value is updated directly.

    One consequence of using midiCC directly and it being changed by either the encoder or from MIDI is that any change will also trigger the sending of the CC value out too.

    This means that if MIDI THRU is enabled and a MIDI CC value is sent to the Arduino then it will almost certainly be sent back over MIDI twice – once as part of the THRU handling, and once as a consequence of it having changed the Arduino’s stored midiCC value.

    LED Ring Indicator

    The simplest implementation will be to scale the 24 LEDs of the ring to the 128 MIDI values and light up the LED that best represents the chosen value.

    An alternative is to use one LED per MIDI CC value and allow the ring to loop round, possibly in a different colour. For 128 values across 24 LEDs this means there will be five full circles of the ring plus 8 more.

    I’ve also provided the option to scale the MIDI CC values to a multiple of the LED ring so that the full MIDI 0..127 range wraps around back to 0 back at the start of the ring.

    In the following, scalemax is the largest multiple of NUM_LEDS that will fit in 128, then the midiCC value is scaled to that new range and then used in the rest of the LED calculation.

    int scalemax = 128 - 128 % NUM_LEDS;
    int midiCC_scaled = (midiCC * scalemax / 128);
    nextLed = midiCC_scaled % NUM_LEDS;
    uint32_t col=0;
    if (midiCC_scaled < NUM_LEDS) {
    col = 0x00003F;
    } else if (midiCC_scaled < NUM_LEDS*2) {
    col = 0x003F3F;
    } else if (midiCC_scaled < NUM_LEDS*3) {
    col = 0x003F00;
    } else if (midiCC_scaled < NUM_LEDS*4) {
    col = 0x3F3F00;
    } else if (midiCC_scaled < NUM_LEDS*5) {
    col = 0x3F0000;
    } else {
    col = 0x3F003F;
    }

    One quirk to note is that the LEDs are numbered anti-clockwise, so at some point I’ll have to reverse the LED number when it comes to presenting an increasing CC value as a clockwise changing display.

    I’d also like to have a bit of a lazy, dragging LED change, so I want to implement something that fades illuminated LEDs out as the value changes, leaving some kind of “tail” as the LED moves.

    To do this, I’ve used an array to store the colour value used for any illuminated LEDs and then at regular intervals that colour is updated to fade back to OFF.

    I’ve implemented a relatively simple fade – basically each of the R, G and B components of the colour is bit-shifted to the right by 1 on each “scan”. This has the effect of continually dividing the colour value by 2 until it reaches 0. The only thing to watch out for is that I don’t do this for the current illuminated LED.

    Also note that I only pull out the most significant 7 bits of each 8 bit value (by & 0xFE) so that the shift doesn’t map the least significant bit of one value down into the next colour.

    for (int i=0; i<NUM_LEDS; i++) {
    if (i != nextLed && ledMap[i] > 0) {
    uint32_t newcol = ((ledMap[i] & 0xFE0000) >> 1)
    + ((ledMap[i] & 0x00FE00) >> 1)
    + ((ledMap[i] & 0x0000FE) >> 1);
    LEDRingSmall.LEDRingSmall_Set_RGB(i, newcol);
    ledMap[i] = newcol;
    }
    }

    All the LED handling is wrapped up in the following functions:

    • ledSetup()
    • ledLoop()
    • ledIndex()

    The last function is responsible for swapping the LED numbers around to make them go clockwise. It isn’t as simple as doing NUMLEDS – led as I still want the first LED to be at “12 o’clock”, hence the function to return the correct index.

    Find it on GitHub here.

    Closing Thoughts

    I am so pleased with that lazy LED change effect.

    Having so many turns of the encoder isn’t particularly practical at the moment, but it does work. It is certainly good to have a few configuration options – especially the option to wrap around, as it takes quite a few turns to get from 0 to 127.

    In a follow up post I’ll take a look at some of the other options, and when I get my endless encoders in the post that will definitely get a mention too.

    I also want to wrap up the existing code somehow to allow for several LED CC controls if required and some kind of box might also be nice.

    Kevin

    #arduinoNano #duppa #i2c #midi #rgbLed #rotaryEncoder

  29. EuroRack 6HP MCU Experimenter Module

    I’ve been following the work of HAGIWO for some time and always been really impressed with the breadth and range of modules they produce. I have noticed some patterns in their microcontroller based modules and so wondered if it was possible to build some kind of generic template PCB that would allow me to do some experimenting whilst reducing the number of links and connections required between components compared to using protoboard.

    • Note: HAGIWO now has a SMT general purpose 4HP Arduino EuroRack module of their own – the “MOD1” now available via Patreon, and a lot neater than mine – full details here: https://note.com/solder_state/n/nc05d8e8fd311

    This is the result.

    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 materials for the main concepts used in this project:

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

    The Circuit

    I wanted to include the following elements:

    • Microcontroller interface – ideally an Arduino Nano or a Raspberry Pi Pico.
    • PWM audio output stage.
    • Gate/Trigger input and output stage.
    • CV input and output stage.
    • EuroRack compatible power connector.

    I wanted suitable protection on the various stages, so have essentially correlated some of the circuits put up by HAGIWO and pulled out a range of elements into my schematic. I’ve been looking through the designs for the following:

    Although the basic template for many of HAGIWO’s designs are very similar, often specific component values might be tweaked, so I wanted to ensure there was nothing too prescriptive in the design here to prevent tailoring.

    In the end I used some specific component values from certain circuits in the schematic itself, but really they are just an example starting point. The values required will depend on the expected input and output levels and whether converting to/from 5V or 3V3 operation.

    For reference, common EuroRack voltages are (from the Doepfer Technical Specification):

    • Audio signals: 10Vpp i.e. -5V to +5V.
    • Modulating Control Voltages: 5Vpp i.e. -2.5V to +2.5V (LFOs); 8Vpp i.e. 0V to +8V (EGs).
    • Trigger/Gate: 0 to 5V, although up to 12V should be accepted.
    • Pitch Control Voltages: Typically 1V/octave.

    Key design principles for the control board:

    • Will allow for a 5V Arduino Nano or a 3V3 Raspberry Pi Pico, with the circuits VCC taken from the power output of the microcontroller (so 5V or 3V3 respectively).
    • Powered from the EuroRack +12V either into VIN of the Arduino Nano or via a L7805 generating 5V into VSYS of a Raspberry Pi Pico.
    • Anticipating that not everything will fit on a single board, there is a header interface between the microcontroller and everything else.
    • The connections to each of the circuit stages is via headers or blank solder pads, allowing patching across to any microcontroller pins as required.
    • There is no support for controls, pots or jacks on the board, it is assumed those will be added to a panel and patched across to the control board using headers.
    • Some mounting holes will be provided.
    • Only the components and circuit blocks required for a specific build will be used. If using an Arduino Nano, this includes being able to omit the L7805 and associated power circuitry as it can be powered from +12V into VIN.

    A future post of usage notes will go into the detail of each circuit element a little more.

    EuroRack Power

    I was pretty keen to include some protection for the EuroRack power side of things, following some of the advice from here: https://www.youtube.com/watch?v=cBGyEBQnIws.

    The problem is that although it is possible to protect the 10-pin version of the EuroRack power connector from reverse connection, it is a lot harder to do with the 16-pin version.

    Pinout from the Doepfler Technical Specification (note -12V is “pin 1” and usually denoted by the stripe on the cable – but not always!):

    For the 10-pin version we essentially just have to cope with +12V and -12V being the wrong way round. With the 16-pin version it isn’t that simple and there are several problems if a cable is plugged in the wrong way round:

    • The CV/Gate signals will overlap with -12V and GND.
    • The 5V connection will overlap with GND.
    • The +12V connection will overlap with GND.
    • As there are three sets of GND pins, typically all connected together, the +12V and +5V of the power supply and any other modules in the system will be connected together!

    That last one is very likely to cause damage to any module or power supply plugged into the rest of the rack. The worst-case scenario is that it could damage the PSU and ALL other modules in the rack… none are going to like receiving 12V when 5V is expected… err…

    As this is a PCB designed for DIY experimentation, I wanted to ensure that the chances of a serious problem with any other kit it might be hooked up to are minimised.

    But I should repeat at this point: do NOT use this module alongside any other expensive equipment or rack or power supply. It is meant for DIY experiments only and should only be connected to other DIY or otherwise sacrificial equipment.

    Still, I’ve only “wired up” the following of the EuroRack power support connector:

    EuroRack StandardPCB ConnectionInternal GATEN/CInternal CVN/C+5VN/C+12V+12VGNDGNDGNDN/CGNDN/C-12VN/C

    In particular note that I’m only using one of the GND connections – the others are left not connected or linked at all, and especially not to each other. This means that should a cable to plugged in the wrong way round, there is nothing on my PCB that will bridge +5V and +12V via GND; it also means I can include some basic protection against +12V and GND being the wrong way round.

    I’m also planning to use shrouded connectors, but of course that won’t guard against incorrectly wired cables or other weirdness.

    I was in two minds about attempting to use the +5V rail, but as I couldn’t find any advice online about how to protect that, I just opted for the use of a L7805 and the +12V rail, which I was planning to use anyway as it seemed more “universal” than expecting a 5V too.

    The final power related part of the schematic is thus:

    PCB Design

    I’ve deliberately designed this onto two boards – a MCU board and an IO board, with headers between them. The power circuit is part of the MCU board. The idea is that all of the components on the MCU board are on the underside; all the components on the IO board are on the top side; that they will link together; and that the power connector will be on the underside and all IO connections (jacks, pots, etc) on the top.

    I’ve also overlapped the footprints of the Nano and Pico and linked up GND and VCC (5V for the Nano and 3V3 for the Pico), so either could be used.

    I’ve deliberately kept the size of the PCBs below 30x100mm, but will get these built as two individual designs. But keeping it within 6HP and 100mm has made for a very packed board!

    In terms of silkscreen labels, each section of the schematic has a specific numbering scheme – for example, all components associated with the first PWM circuit are numbered between 100-199. Similarly for all the other sections.

    But this is not a PCB that is meant to be populated without referral to the schematic and some bespoke design on a per-board basis.

    Closing Thoughts

    This is not a board to be soldered up and just used. It is a template, meant to remove the need for some connections, with some commonly used “circuit patterns” allowing easy connection to a microcontroller and EuroRack power supply.

    But I’m quite pleased with how I’ve done my best to minimise the chances of this board causing a problem with other DIY modules in a rack.

    But for this more than any other boards – the usual warning applies. Do not use with expensive equipment. Test thoroughly, double check cable wiring, and do not use alongside other expensive modules in an expensive rack of kit.

    In a future article, I’ll go through some notes on how I expect to be using it.

    Kevin

    #arduinoNano #EuroRack #HAGIWO #pcb #raspberryPiPico

  30. EuroRack 6HP MCU Experimenter Module

    I’ve been following the work of HAGIWO for some time and always been really impressed with the breadth and range of modules they produce. I have noticed some patterns in their microcontroller based modules and so wondered if it was possible to build some kind of generic template PCB that would allow me to do some experimenting whilst reducing the number of links and connections required between components compared to using protoboard.

    This is the result.

    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 materials for the main concepts used in this project:

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

    The Circuit

    I wanted to include the following elements:

    • Microcontroller interface – ideally an Arduino Nano or a Raspberry Pi Pico.
    • PWM audio output stage.
    • Gate/Trigger input and output stage.
    • CV input and output stage.
    • EuroRack compatible power connector.

    I wanted suitable protection on the various stages, so have essentially correlated some of the circuits put up by HAGIWO and pulled out a range of elements into my schematic. I’ve been looking through the designs for the following:

    Although the basic template for many of HAGIWO’s designs are very similar, often specific component values might be tweaked, so I wanted to ensure there was nothing too prescriptive in the design here to prevent tailoring.

    In the end I used some specific component values from certain circuits in the schematic itself, but really they are just an example starting point. The values required will depend on the expected input and output levels and whether converting to/from 5V or 3V3 operation.

    For reference, common EuroRack voltages are (from the Doepfer Technical Specification):

    • Audio signals: 10Vpp i.e. -5V to +5V.
    • Modulating Control Voltages: 5Vpp i.e. -2.5V to +2.5V (LFOs); 8Vpp i.e. 0V to +8V (EGs).
    • Trigger/Gate: 0 to 5V, although up to 12V should be accepted.
    • Pitch Control Voltages: Typically 1V/octave.

    Key design principles for the control board:

    • Will allow for a 5V Arduino Nano or a 3V3 Raspberry Pi Pico, with the circuits VCC taken from the power output of the microcontroller (so 5V or 3V3 respectively).
    • Powered from the EuroRack +12V either into VIN of the Arduino Nano or via a L7805 generating 5V into VSYS of a Raspberry Pi Pico.
    • Anticipating that not everything will fit on a single board, there is a header interface between the microcontroller and everything else.
    • The connections to each of the circuit stages is via headers or blank solder pads, allowing patching across to any microcontroller pins as required.
    • There is no support for controls, pots or jacks on the board, it is assumed those will be added to a panel and patched across to the control board using headers.
    • Some mounting holes will be provided.
    • Only the components and circuit blocks required for a specific build will be used. If using an Arduino Nano, this includes being able to omit the L7805 and associated power circuitry as it can be powered from +12V into VIN.

    A future post of usage notes will go into the detail of each circuit element a little more.

    EuroRack Power

    I was pretty keen to include some protection for the EuroRack power side of things, following some of the advice from here: https://www.youtube.com/watch?v=cBGyEBQnIws.

    The problem is that although it is possible to protect the 10-pin version of the EuroRack power connector from reverse connection, it is a lot harder to do with the 16-pin version.

    Pinout from the Doepfler Technical Specification (note -12V is “pin 1” and usually denoted by the stripe on the cable – but not always!):

    For the 10-pin version we essentially just have to cope with +12V and -12V being the wrong way round. With the 16-pin version it isn’t that simple and there are several problems if a cable is plugged in the wrong way round:

    • The CV/Gate signals will overlap with -12V and GND.
    • The 5V connection will overlap with GND.
    • The +12V connection will overlap with GND.
    • As there are three sets of GND pins, typically all connected together, the +12V and +5V of the power supply and any other modules in the system will be connected together!

    That last one is very likely to cause damage to any module or power supply plugged into the rest of the rack. The worst-case scenario is that it could damage the PSU and ALL other modules in the rack… none are going to like receiving 12V when 5V is expected… err…

    As this is a PCB designed for DIY experimentation, I wanted to ensure that the chances of a serious problem with any other kit it might be hooked up to are minimised.

    But I should repeat at this point: do NOT use this module alongside any other expensive equipment or rack or power supply. It is meant for DIY experiments only and should only be connected to other DIY or otherwise sacrificial equipment.

    Still, I’ve only “wired up” the following of the EuroRack power support connector:

    EuroRack StandardPCB ConnectionInternal GATEN/CInternal CVN/C+5VN/C+12V+12VGNDGNDGNDN/CGNDN/C-12VN/C

    In particular note that I’m only using one of the GND connections – the others are left not connected or linked at all, and especially not to each other. This means that should a cable to plugged in the wrong way round, there is nothing on my PCB that will bridge +5V and +12V via GND; it also means I can include some basic protection against +12V and GND being the wrong way round.

    I’m also planning to use shrouded connectors, but of course that won’t guard against incorrectly wired cables or other weirdness.

    I was in two minds about attempting to use the +5V rail, but as I couldn’t find any advice online about how to protect that, I just opted for the use of a L7805 and the +12V rail, which I was planning to use anyway as it seemed more “universal” than expecting a 5V too.

    The final power related part of the schematic is thus:

    PCB Design

    I’ve deliberately designed this onto two boards – a MCU board and an IO board, with headers between them. The power circuit is part of the MCU board. The idea is that all of the components on the MCU board are on the underside; all the components on the IO board are on the top side; that they will link together; and that the power connector will be on the underside and all IO connections (jacks, pots, etc) on the top.

    I’ve also overlapped the footprints of the Nano and Pico and linked up GND and VCC (5V for the Nano and 3V3 for the Pico), so either could be used.

    I’ve deliberately kept the size of the PCBs below 30x100mm, but will get these built as two individual designs. But keeping it within 6HP and 100mm has made for a very packed board!

    In terms of silkscreen labels, each section of the schematic has a specific numbering scheme – for example, all components associated with the first PWM circuit are numbered between 100-199. Similarly for all the other sections.

    But this is not a PCB that is meant to be populated without referral to the schematic and some bespoke design on a per-board basis.

    Closing Thoughts

    This is not a board to be soldered up and just used. It is a template, meant to remove the need for some connections, with some commonly used “circuit patterns” allowing easy connection to a microcontroller and EuroRack power supply.

    But I’m quite pleased with how I’ve done my best to minimise the chances of this board causing a problem with other DIY modules in a rack.

    But for this more than any other boards – the usual warning applies. Do not use with expensive equipment. Test thoroughly, double check cable wiring, and do not use alongside other expensive modules in an expensive rack of kit.

    In a future article, I’ll go through some notes on how I expect to be using it.

    Kevin

    #arduinoNano #EuroRack #HAGIWO #pcb #raspberryPiPico

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

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

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

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

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