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

    #diy #arduinonano #humidity_air

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

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

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

    #diy #arduinonano #humidity_air

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

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

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

    #diy #arduinonano #humidity_air

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

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

    #diy #arduinonano #humidity_air

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

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

    #diy #arduinonano #humidity_air

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

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

    #diy #arduinonano #humidity_air

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

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

    Продолжаем!

    #diy #arduinonano #humidity_air

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

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

    Продолжаем!

    #diy #arduinonano #humidity_air

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

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

    Продолжаем!

    #diy #arduinonano #humidity_air

  11. #diy #arduinonano #humidity_air

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

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

  12. #diy #arduinonano #humidity_air

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

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

  13. #diy #arduinonano #humidity_air

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

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

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

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

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

    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.

    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

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

    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.

    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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.

    https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt

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

    These are the key tutorials for the main concepts used in this project:

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

    Parts list

    • Arduino Uno.
    • AY-3-8910 chip.
    • Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
    • 5V compatible MIDI interface.
    • Jumper wires.

    Direct Digital Synthesis on the AY-3-8910

    I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.

    But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.

    There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.

    So I’ll be working to the following properties:

    • 4-bit resolution for the output.
    • 8-bit wavetable.
    • 8.8 fixed point accumulator to index into the wavetable.
    • 8096 Hz sample rate.

    YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:

    • Frequency value for the channel should be set to the highest frequency possible.
    • All channels should be disabled.

    This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.

    Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.

    I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.

    Setting up for DDS

    I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:

    • Timer 2 is an 8-bit timer.
    • Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
    • Use CTC (clear timer on compare) mode.
    • Generate a compare match interrupt.
    • Do not enable any output pins.

    The appropriate ATMega328 registers to enable this are:

      // COM2A[1:0] = 00  No output
    // WGM2[2:0] = 010 CTC mode
    // CS2[2:0] = 011 Prescalar=32
    ASSR = 0;
    TCCR2A = _BV(WGM21);
    TCCR2B = _BV(CS21) | _BV(CS20);
    TCNT2 = 0;
    OCR2A = 60;
    TIMSK2 = _BV(OCIE2A);

    Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.

    pinMode(11, OUTPUT);
    TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggle

    And this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.

    The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.

    Analysis of Performance

    The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.

    No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.

    After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.

    Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:

    void AY3891x::write(byte regAddr, byte data) {
    latchAddressMode(regAddr);
    daPinsOutput(data);
    noInterrupts();
    mode010to110();
    mode110to010();
    interrupts();
    daPinsInput();
    }

    void AY3891x::latchAddressMode(byte regAddr) {
    mode010to000();
    daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
    mode000to001();
    mode001to000();
    mode000to010();
    }

    void AY3891x::daPinsOutput(byte data) {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
    }

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) {
    digitalWrite(_DA_pin[i], data & 0x01);
    data = data >> 1;
    }
    }
    }

    void AY3891x::daPinsInput() {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
    }
    }

    And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.

    One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.

    Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.

    Measuring the Basic Performance

    At this point I took a step back and created a free-running test sketch to really see what is going on.

    #include "AY3891x.h"

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

    #define AY_CLOCK 9 // D9
    void aySetup () {
    pinMode(AY_CLOCK, OUTPUT);
    digitalWrite(AY_CLOCK, LOW);

    TCCR1A = (1 << COM1A0);
    TCCR1B = (1 << WGM12) | (1 << CS10);
    TCCR1C = 0;
    TIMSK1 = 0;
    OCR1AH = 0;
    OCR1AL = 7; // 16MHz / 8 = 2MHz Counter

    psg.begin();

    // Output highest frequency on each channel, but set level to 0
    // Highest freq = 1000000 / (16 * 1) = 62500
    psg.write(AY3891x::ChA_Amplitude, 0);
    psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChB_Amplitude, 0);
    psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChC_Amplitude, 0);
    psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);

    // LOW = channel is in the mix.
    // Turn everything off..
    psg.write(AY3891x::Enable_Reg, 0xFF);
    }

    int toggle;
    void setup() {
    pinMode(11, OUTPUT);
    toggle = LOW;
    digitalWrite(11, toggle);
    aySetup();
    }

    void loop() {
    toggle = !toggle;
    digitalWrite(11, toggle);
    for (int i=0; i<16; i++) {
    psg.write(AY3891x::ChA_Amplitude, i);
    }
    }

    All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:

    This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.

    And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.

    Optimising Register Writes

    So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.

    The library I’m using aims for the most portable compatibility: “This library uses the generic digitalWrite() function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”

    It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.

    Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:

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

    Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.

    So to improve things whilst using this same PCB will require me to re-write the library myself.

    As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    digitalWrite(BC1, HIGH);
    digitalWrite(BDIR, HIGH);

    // Latch address
    // NB: Addresses are all in range 0..15 so don't need to
    // worry about writing out bits 6,7 - just ensure set to zero
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);

    delayMicroseconds(10);

    // Mode = Write
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, HIGH);

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

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);
    }

    I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:

    DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3

    To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.

      digitalWrite(BC2, HIGH);
    DDRD |= 0xFC;
    DDRC |= 0x04;
    DDRB |= 0x01;

    This now improves on that previous 280Hz and gives me 1600Hz performance.

    So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…

    #define BC1LOW  {PORTC &= 0xFE;} // A0 LOW
    #define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
    #define BC2LOW {PORTC &= 0xFD;} // A1 LOW
    #define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
    #define BDIRLOW {PORTC &= 0xF7;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

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

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

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

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

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

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

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

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

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

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

    This now gives me the following:

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

    The downside of this method:

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

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

    DDS Part 2

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

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

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

    Find it on GitHub here.

    Closing Thoughts

    I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.

    But hopefully filling in the gaps won’t take too long and will be the subject of a further post.

    Now that I have something that works, I’m actually quite surprised by how well it is working.

    Kevin

    #arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi

  37. Arduino and AY-3-8910 – Part 3

    I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.

    https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt

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

    These are the key tutorials for the main concepts used in this project:

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

    Parts list

    • Arduino Uno.
    • AY-3-8910 chip.
    • Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
    • 5V compatible MIDI interface.
    • Jumper wires.

    Direct Digital Synthesis on the AY-3-8910

    I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.

    But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.

    There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.

    So I’ll be working to the following properties:

    • 4-bit resolution for the output.
    • 8-bit wavetable.
    • 8.8 fixed point accumulator to index into the wavetable.
    • 8096 Hz sample rate.

    YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:

    • Frequency value for the channel should be set to the highest frequency possible.
    • All channels should be disabled.

    This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.

    Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.

    I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.

    Setting up for DDS

    I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:

    • Timer 2 is an 8-bit timer.
    • Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
    • Use CTC (clear timer on compare) mode.
    • Generate a compare match interrupt.
    • Do not enable any output pins.

    The appropriate ATMega328 registers to enable this are:

      // COM2A[1:0] = 00  No output
    // WGM2[2:0] = 010 CTC mode
    // CS2[2:0] = 011 Prescalar=32
    ASSR = 0;
    TCCR2A = _BV(WGM21);
    TCCR2B = _BV(CS21) | _BV(CS20);
    TCNT2 = 0;
    OCR2A = 60;
    TIMSK2 = _BV(OCIE2A);

    Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.

    pinMode(11, OUTPUT);
    TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggle

    And this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.

    The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.

    Analysis of Performance

    The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.

    No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.

    After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.

    Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:

    void AY3891x::write(byte regAddr, byte data) {
    latchAddressMode(regAddr);
    daPinsOutput(data);
    noInterrupts();
    mode010to110();
    mode110to010();
    interrupts();
    daPinsInput();
    }

    void AY3891x::latchAddressMode(byte regAddr) {
    mode010to000();
    daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
    mode000to001();
    mode001to000();
    mode000to010();
    }

    void AY3891x::daPinsOutput(byte data) {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
    }

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) {
    digitalWrite(_DA_pin[i], data & 0x01);
    data = data >> 1;
    }
    }
    }

    void AY3891x::daPinsInput() {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
    }
    }

    And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.

    One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.

    Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.

    Measuring the Basic Performance

    At this point I took a step back and created a free-running test sketch to really see what is going on.

    #include "AY3891x.h"

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

    #define AY_CLOCK 9 // D9
    void aySetup () {
    pinMode(AY_CLOCK, OUTPUT);
    digitalWrite(AY_CLOCK, LOW);

    TCCR1A = (1 << COM1A0);
    TCCR1B = (1 << WGM12) | (1 << CS10);
    TCCR1C = 0;
    TIMSK1 = 0;
    OCR1AH = 0;
    OCR1AL = 7; // 16MHz / 8 = 2MHz Counter

    psg.begin();

    // Output highest frequency on each channel, but set level to 0
    // Highest freq = 1000000 / (16 * 1) = 62500
    psg.write(AY3891x::ChA_Amplitude, 0);
    psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChB_Amplitude, 0);
    psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChC_Amplitude, 0);
    psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);

    // LOW = channel is in the mix.
    // Turn everything off..
    psg.write(AY3891x::Enable_Reg, 0xFF);
    }

    int toggle;
    void setup() {
    pinMode(11, OUTPUT);
    toggle = LOW;
    digitalWrite(11, toggle);
    aySetup();
    }

    void loop() {
    toggle = !toggle;
    digitalWrite(11, toggle);
    for (int i=0; i<16; i++) {
    psg.write(AY3891x::ChA_Amplitude, i);
    }
    }

    All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:

    This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.

    And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.

    Optimising Register Writes

    So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.

    The library I’m using aims for the most portable compatibility: “This library uses the generic digitalWrite() function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”

    It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.

    Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:

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

    Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.

    So to improve things whilst using this same PCB will require me to re-write the library myself.

    As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    digitalWrite(BC1, HIGH);
    digitalWrite(BDIR, HIGH);

    // Latch address
    // NB: Addresses are all in range 0..15 so don't need to
    // worry about writing out bits 6,7 - just ensure set to zero
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);

    delayMicroseconds(10);

    // Mode = Write
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, HIGH);

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

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);
    }

    I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:

    DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3

    To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.

      digitalWrite(BC2, HIGH);
    DDRD |= 0xFC;
    DDRC |= 0x04;
    DDRB |= 0x01;

    This now improves on that previous 280Hz and gives me 1600Hz performance.

    So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…

    #define BC1LOW  {PORTC &= 0xFE;} // A0 LOW
    #define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
    #define BC2LOW {PORTC &= 0xFD;} // A1 LOW
    #define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
    #define BDIRLOW {PORTC &= 0xF7;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

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

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

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

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

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

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

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

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

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

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

    This now gives me the following:

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

    The downside of this method:

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

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

    DDS Part 2

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

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

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

    Find it on GitHub here.

    Closing Thoughts

    I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.

    But hopefully filling in the gaps won’t take too long and will be the subject of a further post.

    Now that I have something that works, I’m actually quite surprised by how well it is working.

    Kevin

    #arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi