#arduinouno — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #arduinouno, aggregated by home.social.
-
KernelUNO, An OS For The Arduino Uno
https://fed.brid.gy/r/https://hackaday.com/2026/04/23/kerneluno-an-os-for-the-arduino-uno/
-
KernelUNO, An OS For The Arduino Uno
https://fed.brid.gy/r/https://hackaday.com/2026/04/23/kerneluno-an-os-for-the-arduino-uno/
-
Theremin-Style MIDI Controller Does It With Lasers https://hackaday.com/2025/09/07/theremin-style-midi-controller-does-it-with-lasers/ #opticaltheremin #midicontroller #ArduinoHacks #MusicalHacks #ArduinoUno #tofsensor
-
Theremin-Style MIDI Controller Does It With Lasers - Strictly speaking, a Theremin uses a pair of antennae that act as capacitors in a ... - https://hackaday.com/2025/09/07/theremin-style-midi-controller-does-it-with-lasers/ #opticaltheremin #midicontroller #arduinohacks #musicalhacks #arduinouno #tofsensor
-
Arduino and SP0256A-AL2 – Part 4
Having now tried both an Arduino and Raspberry Pi Pico as programmable clocks for the SP0256A-AL2, this post starts to look at some alternative solutions, starting with a HC4046 phase-locked loop, voltage-controlled oscillator clock.
- Part 1 – Basic introduction and getting started
- Part 2 – Arduino programmable clock
- Part 3 – Using a Raspberry Pi Pico as a programmable clock
- Part 4 – Using a HC4046 PLL as the clock
- Part 5 – Using an I2C SI5351 programmable clock
- Part 6 – Adding MIDI
https://makertube.net/w/8eRTaCvqb3bsmZEJFy94E3
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 microcontrollers, see the Getting Started pages.
Some Ideas
Whilst trawling around the Internet (and remembering a comment from my earlier posts), I found two existing projects where changing the frequency was used as a way to add pitch to the SP0256A-AL2:
- The previously mentioned MIDI Narrator: https://rarewaves.net/products/midi-narrator/
- A great blog post on SP0256A-AL2 pitch control: https://www.polaxis.be/2011/11/sp0256-al2-pitch-control/
Both use a VCO approach. The latter using a LTC6903 programmable oscillator; and the former using a CD4046 PLL device.
The LTC6903 looks like it would be a lot more complicated for me to use (especially being SMT), but I have some DIP 4046 devices already in the parts drawer so that is definitely an option for me.
I’ve also found (and now ordered) some breakout boards based on the SI5351 based programmable clock module. They all seem to follow Adafruit’s lead: https://learn.adafruit.com/adafruit-si5351-clock-generator-breakout/overview
One last thought – use a 555. No really! There are some versions that can go up o 3, 4 or even 5MHz apparently, see: https://en.wikipedia.org/wiki/555_timer_IC#Derivatives Of course having that in any way programmable might be a challenge, but it isn’t out of the question.
4046 VCO/PLL
A voltage-controlled oscillator is just what it sounds like. An oscillator whose frequency is related to the level of a control voltage on its input.
Here is a pinout and functional view of the 4046 from the TI 74HC4046/74HCT4046 datasheet. This provides several phase comparators and a voltage-controlled oscillator to create a phased-locked loop controller.
A key statement is the following:
“The VCO requires one external capacitor C1 (between C1A and C1B) and one external resistor R1 (between R1 and GND) or two external resistors R1 and R2 (between R1 and GND, and R2 and GND). Resistor R1 and capacitor C1 determine the frequency range of the VCO. Resistor R2 enables the VCO to have a frequency offset if required.”
The “application information” section describes the “with offset” operation:
There are a lot of graphs of characteristics in the datasheet for the various combinations of R1, R2 and C1 – these are the “figure 27-32” mentioned above. Actually, to cover all options, there are figures 10 through to 43, but most of those cover other situations.
The criteria for selecting R1, R2 and C are:
- R1 Between 3kΩ and 300kΩ
- R2 Between 3kΩ and 300kΩ
- R1 + R2 Parallel Value > 2.7kΩ
- C1 Greater Than 40pF
For the MIDI Narrator, the values being used are as follows:
- R1 = 1.5K to 2.5K (adjustable)
- R2 = 12K
- C1 = 1nF
Which I believe puts it in the following areas of the performance curves, although they aren’t strictly within the state required ranges…:
So without detailed calculations and measurements, I think this puts the (centre) offset frequency at a low-number of MHz (extrapolating for 12K at 5V) and the Max/Min ratio (R2/R1) at something around 4 to 8.
I believe, from looking at the other charts, that a higher R1 or C value gives a lower frequency. There are graphs for the following with the stated approx frequency ranges at 4.5V:
- 1M5, 100nF: 0 < Freq <100Hz
- 150K, 100nF: 0 < Freq < 1kHz
- 5K6, 100nF: 0 < Freq < 20kHz
- 150K, 50pF: 0 < Freq < 1.5MHz
One last thing to note from figure 32 – it states that a Vin of 0.95 VCC will generate the maximum frequency and 0V for the minimum.
It turns out I have some CD4046 in my parts box, not a HC or HCT 4046. On initial inspection, there doesn’t seem to be a lot of difference that would significantly affects my messing around. Some of the pins (that I’m not using) have different functions:
But as I’m not using those pins, that probably won’t matter. But then on closer inspection, I can see a key difference that definitely will affect my messing about – the operating frequency ranges:
- HC/HCT4096: Up to 18MHz at VCC=5V.
- 4096: Up to 1.4MHz at VDD=10V.
And digging into the detailed spec for the CD4046, when running at 5V the maximum is down to just 0.8MHz typical.
So I could use them to test out some general principles but need a HC/HCT version to really achieve what I’d like to do.
At this point my understanding was starting to struggle, and those graphs weren’t helping much. So I just went ahead and built the version of the circuit from the Rarewaves MIDI Narrator. I really wasn’t having much luck with it as is however, so started fiddling about with different components and an oscilloscope to see if I could make any sense from it.
There is a good article about the basics of a 4046 based VCO-driven PLL oscillator output here: https://hackaday.com/2015/08/07/logic-noise-4046-voltage-controlled-oscillator-part-one/
I was finding that the output wasn’t a particularly clean square wave, and at higher frequencies was pretty much triangular. At this stage I don’t know how much of that is my measuring equipment. I have a scope probe with a 10pF capacitance apparently, so a 10K resistor and 10pF capacitor do create a low-pass filter that significantly rounds off the signal.
But more significantly, the peak to peak voltage was greatly reduced to around 1.5Vpp with a significant BC bias. That wasn’t much use as a clock signal for me – from Part 3 I know the SP0256A-AL2 requires <0.6V for a LOW and >2.5V for a HIGH. Basically scope or on scope the output was pretty unreliable for me.
I’d included the 10K resistor between the 4046 and SPA256A-AL2 as per the circuit for the MIDI narrator. But without that resistor, the clock is much cleaner and has much more useful levels, so I removed it. Later experiments show that a 1K resistor seems to work fine too.
I also found that the suggested 1nF capacitor wasn’t giving me a particularly useful range. In the end, 4.7nF seemed to work well for me. Eventually after testing a range of different resistor values, I went back to 12K and 1.5K + 1K pot from the original circuit. This gives me:
- The frequency range set by a combination of 4.7nF and 12K.
- A frequency offset of between 1K and 2.5K is configurable.
I believe this will pull the frequency offset down slightly from the previous values to something between 100kHz and 600kHz, according to the charts, but it gives me a very useful range between around 1MHz and 5MHz once tuned slightly with the trimpot.
When going through the LM358 the usable voltage range for the VCO seems to be between ~2V and 3V for the 1MHz to 5MHz range. 0V is currently giving me 120kHz, tweaking of the offset resistor (R2 = 12K) helps here – 3K3 makes that around 500kHz, making the useful input voltage now around 1.5V to 3V. The issue is that reducing R2 starts to get to the point where the condition that R1||R2 > 2K7 no longer holds true. But either seems fine for some experimenting for now.
My final circuit used for now is as follows:
The CLK output was plugged directly into the SP0256A-AL2 OSC 1 input, with OSC 2 connected to GND.
The control voltage pot goes through a LM358 which might not be strictly necessary in this case, but helps to at least stabilise the input voltage to the VCO. It might also be useful when I switch to programmatically driving the control voltage from a microcontroller.
The usable clock frequency range is adjusted using the trimpot. Eventually, as mentioned, I settled on a range of around 1MHz to 5MHz. It will go lower, but that isn’t so useful for driving the SP0256A-AL2.
Arduino PWM Voltage Control
To get the Arduino to set the control voltage, requires generating a PWM signal which can be pretty simply done with analogWrite().
The default PWM frequency is around 400kHz without messing about directly with Arduino timers. This will need filtering though, but a simple low-pass filter should easily work here, especially as the frequency won’t be changing very often.
I just went back to the MIDI narrator circuit and could see that a 100K/220nF filter was used. Putting these into a low-pass filter calculator gives a cut-off frequency of just under 8Hz. This generates a pretty smooth output.
There is a balance between the frequency range and offset chosen for the 4046 by the selection of R1, R2 and C, and the voltage range sent out of the Arduino. After a fair bit of messing around, I settled on the following:
- R1 = 3K3
- R2 = 10K
- C = 560pF
Then used analogWrite() with values between 60 and 200 to generate voltages and frequencies as follows:
- analogWrite() value range: 60 to 200
- PWM smoothed voltage range: 1.3V to 3.6V
- LM258 output voltage range: 1.3V to 3.3V
- Clock frequency range: 2.2MHz to 5MHz
Setting up the clock via PWM voltage control is done as follows:
#define SP0_CLOCK 10 // D10
void clockSetup () {
pinMode(SP0_CLOCK, OUTPUT);
analogWrite(SP0_CLOCK, 128); // Approx 2.5V output
}
void setClock (int clock) {
analogWrite(SP0_CLOCK, clock);
}Closing Thoughts
This seems to work really well. The control using a pot isn’t particularly reliable, but that is quite different if generating a control voltage from a microcontroller.
I still need to do some calibrations to work out what voltages create which frequencies and then what pitch of voice reproduction that seems to produce, but that can come later. For now it is enough to know it appears to work albeit in still quite a hand-wavy kind of way.
As an aside, whilst trawling around the Internet for this one I stumbled across the CTS256A-AL2 device. This is a microcontroller (I think it is 8051 based, based on a PIC7041) that performs text to allophone conversion for pairing with a SP0256A-AL2 for a complete text to speech system.
There are some details, and an interesting DIY board, here: https://www.smbaker.com/cts256a-al2-text-to-speech-board and an older project here: https://8051bits.com/misc-projects/speech%20-%20music%20synthesizers/speech-mus-syn.html
Unfortunately these seem very hard to come by these days, but there is some interesting emulation code here: https://github.com/GmEsoft/SP0256_CTS256A-AL2
And a pretty thorough discussion about the device and its running code here: https://www.retrobrewcomputers.org/forum/index.php?t=msg&th=816&goto=10847
Kevin
-
Arduino and SP0256A-AL2 – Part 3
Following on from using an Arduino as a variable clock in Arduino and SP0256A-AL2 – Part 2, I have some ideas for a few options, but this post looks in detail at using a Raspberry Pi Pico as the clock source.
Spoilers: it kind of works, but isn’t quite the answer I need yet…
- Part 1 – Basic introduction and getting started
- Part 2 – Arduino programmable clock
- Part 3 – Using a Raspberry Pi Pico as a programmable clock
- Part 4 – Using a HC4046 PLL as the clock
- Part 5 – Using an I2C SI5351 programmable clock
- Part 6 – Adding MIDI
https://makertube.net/w/bxBYCqHrZvQLwLuwYa5Z9r
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 microcontrollers, see the Getting Started pages.
Using a RPi Pico
The RP2040 can be overclocked quite a bit, so generating a variable square wave up in the few MHz range should presumably be relatively straight forward. Using the built-in PIO state machines for a square wave is fairly simple and it can be done from Circuitpython or Micropython too.
This is a complete square wave generator for GP2 that steps down from 4MHz to 2MHz in steps of 100kHz. It can optionally overclock the RPi to 250 MHz too if required.
import time
import microcontroller
import board
import rp2pio
import adafruit_pioasm
square = adafruit_pioasm.assemble ("""
.program square
set pins, 1
set pins, 0
""")
RP2040Freq = 125_000_000
#RP2040Freq = 250_000_000
print ("RP2040 Frequency = ", microcontroller.cpu.frequency)
microcontroller.cpu.frequency = RP2040Freq
time.sleep(1)
print ("New RP2040 Frequency = ", microcontroller.cpu.frequency)
while True:
for freq in range (4000000, 2000000, -100000):
print("\nReqd frequency = ", freq*2)
print("Sq frequency = ", freq)
sm = rp2pio.StateMachine(
square,
frequency=freq*2,
first_set_pin=board.GP2,
)
print("Actual freq = {:d}".format(sm.frequency))
print("Actual sq freq = {:d}".format(int(sm.frequency/2)))
time.sleep(5)
sm.deinit()The PIO program itself has two instruction steps, so takes two cycles to complete, so the running frequency has to be twice the desired frequency of the square wave. It automatically keeps looping, so no additional instructions are required there.
The state machine will run at the system speed with a 16.8 fixed point fractional clock divider. Full details can be found in section 3.5.5 “Clock Dividers” in the RP2040 datasheet.
For certain values there might be some jitter:
If the system clock is faster though, the amount of jitter will be less I suspect, so it is advantageous to overclock the Pico for more accurate frequencies.
The problem with this approach is that whilst I get a nice accurate clock source with quite a good resolution across its range, every time the state machine is recreated to change the frequency, there is a “blip” in the audio from the SP0256A-AL2 whilst its clock temporarily disappears!
An alternative approach is to use a fixed state machine frequency but include a counter in the PIO program to allow for a configurable number of steps per scan of the PIO without having to stop and restart the clock.
The problem with this is that I am limited to a unit of the instruction time for the PIO state machine which gives a fixed overhead, in terms of the instructions required for a minimal loop, and a flexible overhead, in terms of the counter I can pass in.
The upshot of this is that I’m tied to a certain resolution of frequency change.
I have the following PIO code:
.program blink
.side_set 1
.wrap_target
pull noblock
mov x, osr
mov y, x
set pins, 1
lp1:
jmp y-- lp1
nop
nop
mov y, x
set pins, 0
lp2:
jmp y-- lp2
.wrapThe “pull” will update the output shift register (OSR) either with any new value written to the state machine or the last value of the X register. This value gets copied to Y to use as a counter. This happens twice, once for when the pin is set at 1 and once for when the pin is set at 0.
There are two nops whilst the pin is set at 1 to balance for the original pull and mov instructions at the end of the pin set to 0 cycle.
As the Y counter value is used twice, the flexible overhead of the timing is essentially proportional to count * 2. It counts for the pin being HIGH and then for the pin being LOW.
The fixed overhead is the cost of the original pull, two moves, the pin sets, and a single jmp per pin state – so by using the two nops to ensure the HIGH and LOW times are the same, that is 10 instruction cycles.
I was hoping to use the “side” instruction to eliminate the two set instructions, but so far I’ve not managed to get that to work. I still don’t understand PIO…
So for now the timing of the PIO routine is = 10 + 2 * count and the unit is the time for a single instruction, which is 1 / frequency of the PIO execution, up to a maximum frequency of the Pico’s system clock frequency.
Using an overclocked Pico at 250MHz, the frequency range would start at the following:
- Execution freq = Pico Sys Clock / (10 + 2 * count)
- So when count = 0; execution freq = 250MHZ / 10 = 25 MHz
That is far too fast for the SP0256A-AL5. In fact, I’ve found that anything over around 5MHz causes the chip problems.
For this reason, I’m using a minimum count of 20:
- Max execution freq = 250MHz / (10 + 2 * 20) = 5 MHz
Plotting execution frequency per “count” value (starting from 20) gives the following:
We can see the limits of the resolution at the top-end, and in fact, the first few equivalent frequencies in that range are as follows:
CountEquivalent Frequency205,000,000214,807,692224,629,629234,464,285244,310,344That is giving me something like a 150-200kHz jump each time, which isn’t great, but is probably the best I can do. I would be larger if I wasn’t overclocking the Pico. It does get smaller as the count increases, but it is only really worth going down to a count value of around 120, which is around 1MHz for the resulting clock. Anything lower than that and the SP0256A-AL2 isn’t particularly useful.
Here is the full Circuitpython code which attaches a pot to GP26 to control the frequency in the range of around 900kHz up to 5MHz. Note the scaling of the pot value (0 to 65535) by 600 prior to its use to add to the count.
import array
import time
import board
import rp2pio
import microcontroller
import adafruit_pioasm
from analogio import AnalogIn
algin = AnalogIn(board.GP26) # ADC0
blink = adafruit_pioasm.assemble(
"""
.program blink
.side_set 1
.wrap_target
pull noblock
mov x, osr
mov y, x
set pins, 1
lp1:
jmp y-- lp1
nop
nop
mov y, x
set pins, 0
lp2:
jmp y-- lp2
.wrap
"""
)
RP2040Freq = 250_000_000
microcontroller.cpu.frequency = RP2040Freq
time.sleep(1)
oldalgval = 0
sm = rp2pio.StateMachine(
blink,
frequency=RP2040Freq,
first_set_pin=board.GP2
)
sm.write(bytes(16))
while True:
algval = algin.value
if (algval != oldalgval):
oldalgval = algval
count = 20 + int(algval / 600)
freq = int (RP2040Freq / (10 + count*2))
data = array.array("I", [count])
sm.write(data)
time.sleep(0.2)One problem will be the 3V3 operating levels of the Pico. The SP0256A-AL2 datasheet states the following:
So whilst a “high logic” value for the oscillator has a minimum level of 2.5V, it also states that a minimum of 3.9V is required if driven from an external source.
If required, something like a 74HCT14, powered by 5V, can be used to level shift the 3V3 output of the Pico to a 5V signal for use with the SP0256A-AL2.
But in practice, I was finding the Pico worked fine as is. It is important to ensure both the Pico, Arduino and SP0256A-AL2 all have their grounds connected.
A this point I’m just using the Pico as a programmable clock, but if I was to go this route, then it would make sense to have the Pico drive the SP0256A-AL2 too and forgo the Arduino.
Closing Thoughts
So I have two choices if I want to use a Raspberry Pi Pico:
- Go for smooth changes of frequency, but with less resolution, especially at the higher frequencies.
- Go for more accurate resolution across the range but accept there will be blips when the clock changes which will be heard in the audio.
Neither is a perfect solution, but it shows the principles are valid. Also, using two microcontrollers is a bit over the top, so if I was to move to using a Pico, I’d probably want to find a way to drive the SP0256A from the Pico directly too and skip using an Arduino.
One benefit of that would be that I can time the frequency changes to coincide with silence in the speaking should I wish to, avoiding the possibility of major audio blips.
But I also have a few other options to try, which I’ll come back to in a future post.
Kevin
-
Arduino with Multiple Displays – Part 3
Whilst messing around a little more with my Arduino with Multiple Displays – Part 2, I’ve optimised the code a little and found out a bit more about these displays!
In this part, I’m actually using a PCB that can hold four displays, powered by a Waveshare Zero device. More on that here: Waveshare Zero Multi Display PCB Design.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key Arduino tutorials for the main concepts used in this project:
- Arduino with Multiple Displays
- https://emalliab.wordpress.com/2025/07/19/small-microcontroller-displays/
If you are new to microcontrollers, see the Getting Started pages.
Parts list
- A Waveshare Zero format board or similar
- 4x 0.96″ ST7735 60×180 SPI TFT displays.
- Built Waveshare Zero Multi Display PCB
- Breadboard and jumper wires.
Recall that I’m using displays that look like this – note the order of the pins.
Although even with displays that look exactly the same, it appears there can be differences in how they are used software wise. More on that later.
The Circuit
For two displays, I can reuse the circuit from Arduino with Multiple Displays – Part 2. For more displays, it is possible to cascade more displays using jumper wires, but I’ve used my PCB.
The pins to be used for various Waveshare Zero boards is covered in part 2.
The Code
Whilst using these displays, I found that the colours can be inverted in some of them compared to others. Typically, I’ve found that I might have to use either of the following two options to drive them correctly:
tft.initR(INITR_MINI160x80);
tft.initR(INITR_MINI160x80_PLUGIN);These represent different Adafruit displays as before, but they generally work for me.
However there is another thing to watch out for. These displays are 16-bit colour displays, which means each colour value is a 16-bit word with red, green and blue elements represented by 5, 6 and 5 bits. This means two of the colours have a resolution of 0 to 31, and one has 0 to 63.
But the ordering seems different for different displays. The default Adafruit library appears to assume RGB ordering, but my displays seem to be BGR. This means that if I use the provided short-cuts for colours, the red and blue elements are swapped.
Consequently, I defined my own colours along with a macro to allow me to provide RGB values and turn it into the device-specific 16-bit value as required.
In the following, I define the bit-shift number for each of red, green and blue and the use that in a macro “ST_COL” shifting the value to the correct place in the 5-6-5 format. Red and blue are the 5-bit colours and green is the 6-bit colour, so in each case I take the most significant bits which means each colour can still be defined in terms of 0..255 RGB values.
// Format is 16-bit 5-6-5 B-G-R
// Allow 0..255 in component values, by only taking
// most significant bits (5 or 6) from each value.
// bbbbbggggggrrrrr
#define ST_COL(r,g,b) (((r&0xF8)>>3)|((g&0xFC)<<3)|((b&0xF8)<<8))
#define ST_BLACK ST_COL(0,0,0)
#define ST_GREY ST_COL(64,64,64)
#define ST_WHITE ST_COL(255,255,255)
#define ST_BLUE ST_COL(0,0,255)
#define ST_GREEN ST_COL(0,255,0)
#define ST_RED ST_COL(255,0,0)
#define ST_YELLOW ST_COL(255,255,0)
#define ST_MAGENTA ST_COL(255,0,255)
#define ST_CYAN ST_COL(0,255,255)I’m also building up to seeing if I can drive more than four displays, so I’ve also changed the code to allow me to iterate across a number of displays.
#define NUM_TFTS 4
int tftTypes[NUM_TFTS] = {
INITR_MINI160x80, INITR_MINI160x80,
INITR_MINI160x80, INITR_MINI160x80,
};
int tftCS[NUM_TFTS] = {SPI_SS, 6, 5, 4};
#define TFT_RST 7
#define TFT_DC 11
Adafruit_ST7735 *tft[NUM_TFTS];
void setup() {
int rstPin = TFT_RST;0
for (int i=0; i<NUM_TFTS; i++) {
tft[i] = new Adafruit_ST7735(&MySPI, tftCS[i], TFT_DC, rstPin);
rstPin = -1;
tft[i]->initR(tftTypes[i]);
tft[i]->setRotation(3);
tft[i]->fillScreen(ST_BLACK);
}
}
void loop() {
for (int i=0; i<NUM_TFTS; i++) {
unsigned long time = millis();
tft[i]->fillRect(10, 20, tft[i]->width(), 20, ST_BLACK);
tft[i]->setTextColor(ST_GREEN);
tft[i]->setCursor(10, 20);
tft[i]->print(i);
tft[i]->print(":");
tft[i]->print(time, DEC);
}
}Each instance of the display code is now created dynamically and stored in an array which can then be iterated over when it comes to putting things on each display.
Notice how the reset pin definition is set to -1 after the first initialisation. This ensures that subsequent instantiations won’t reset displays that have already been set up.
The final code actually allows up to eight displays to be included by setting NUM_TFTS at the top to two or four.
The GPIO usage being assumed is described here: Waveshare Zero Multi Display PCB Build Guide.
Closing Thoughts
Approaching the code in this way allows me to experiment more easily with more than four displays.
If my PCB works as I’m hoping I should be able to cascade them to get eight displays – assuming the Waveshare Zero is up to driving eight of course.
Kevin
#arduinoUno #define #esp32c3 #ESP32s3 #rp2040 #st7735 #tftDisplay #WaveshareZero
-
Arduino with Multiple Displays – Part 2
As I mentioned in my last post on Arduino with Multiple Displays I’m going to look at other microcontrollers too. This post takes a wander through my Waveshare Zero and similar format boards that each support one of the RP2040, ESP32-C3 or ESP32-S3.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key Arduino tutorials for the main concepts used in this project:
- Arduino with Multiple Displays
- https://emalliab.wordpress.com/2025/07/19/small-microcontroller-displays/
If you are new to microcontrollers, see the Getting Started pages.
Parts list
- A Waveshare Zero format board or similar
- 2x 0.96″ ST7735 60×180 SPI TFT displays.
- Breadboard and jumper wires.
Once again I’m using displays that look like this – note the order of the pins.
The Circuit
All circuits are a variation on the above, requiring the following ideal connections:
DisplayFunctionRP2040ESP32-C3ESP32-S3BLKBacklight control
(not required)N/CN/CN/CCSChip select
One per display.5 or any SPI0 CS1010DCData/Command888RESReset1499SDAData (MOSI)3 or any SPI0 MOSI6 or 711SCLClock (SCLK)2 or any SPI0 SCLK4 or 612VCCPower3V33V33V3GNDGroundGNDGNDGNDFor the explanations of the pin choices, and what it means for the code, see the following sections.
ESP32-S3 Zero
In the Arduino IDE, using board ESP32-> Waveshare ESP32-S3-Zero.
There are several SPI buses on the ESP32-S3, but they have fixed uses as follows (see the ESP32-S3 Technical Reference Manual Chapter 30 “SPI Controller”):
- SPI 0: Reserved for internal use.
- SPI 1: Reserved for internal use.
- SPI 2: General purpose use – often called FSPI in the documentation.
- SPI 3: General purpose use – often called SPI or SPI3.
Sometimes the two SPI buses are called VSPI and HSPI but I think that is really terminology from the original ESP32 rather than the ESP32-S3.
The ESP32 Arduino core for the Waveshare ESP32-S3 Zero variant defines the following:
// Mapping based on the ESP32S3 data sheet - alternate for SPI2
static const uint8_t SS = 34; // FSPICS0
static const uint8_t MOSI = 35; // FSPID
static const uint8_t MISO = 37; // FSPIQ
static const uint8_t SCK = 36; // FSPICLKBy default the Adafruit libraries will use the boards default SPI interface, as defined in the variants.h file – i.e. the above.
When it comes to assigning SPI devices to GPIO there are a few considerations (see the “ESP32-S3 Technical Reference Manual, Chapter 6 “IO MUX and GPIO Matrix”):
- In general, any GPIO can be mapped onto any SPI function. However…
- Some GPIO have special “strapping” functions so are best avoided.
- Some GPIOs have a default SPI function that bypasses the GPIO MUX routing, so allows for better performance (see section 6.6 “Direct Input and Output via IO MUX”).
From my reading of the reference manual I believe the following are default “non-MUX” SPI connections:
In the previous table, where SPI3 is mentioned, then the entry for “Direct IO via IO MUX” is set to “no”, so I’m guessing that isn’t available.
But now we can see why the Arduino core is using GPIO 34-37, but we can also see that GPIO 10-13 would be an alternative (fast) option too.
The problem is that not all of GPIO 34-37 are broken out on a Waveshare ESP32-S3 Zero, so I need to use the alternative pinouts. Aside: this makes no sense to me that these are the defaults in the Waveshare ESP32-S3 Zero’s “variant.h” file, but anyway…
To use a different SPI interface requires using a constructor that passes in an initialised SPI instance. There is an example in the ESP32 core for setting up multiple SPI buses here: https://github.com/espressif/arduino-esp32/blob/master/libraries/SPI/examples/SPI_Multiple_Buses/SPI_Multiple_Buses.ino
This leads to the pins as defined in the previous table, and the code below to setup one of the displays.
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
#define SPI_SS 10
#define SPI_MOSI 11
#define SPI_SCLK 12
#define SPI_MISO 13
SPIClass MySPI(FSPI);
#define TFT_CS SPI_SS
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7735 tft = Adafruit_ST7735(&MySPI, TFT_CS, TFT_DC, TFT_RST);
void setup() {
MySPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_SS);
pinMode(SPI_SS, OUTPUT);
tft.initR(INITR_MINI160x80_PLUGIN);
}ESP32-C3 Zero
In the Arduino IDE, using board ESP32-> ESP32C3 Dev Module.
Again there are several SPI buses on the ESP32-C3, with the same fixed uses as follows (see the ESP32-C3 Technical Reference Manual Chapter 30 “SPI Controller”):
- SPI 0: Reserved for internal use.
- SPI 1: Reserved for internal use.
- SPI 2: General purpose use – sometimes called GP-SPI in the documentation.
The ESP32-C3 also has a very similar SPI arrangement to the ESP32-S3, in that whilst any pin can be configured for SPI usage, there are certain hard-wired optional arrangements that bypass the GPIO routing matrix.
The faster (direct to IO MUX) pins are as follows (more here):
- CS0 – 10
- SCLK – 6
- MISO – 2
- MOSI – 7
Curiously, the general ESP32-C3 Arduino variant defines them as follows:
static const uint8_t SS = 7;
static const uint8_t MOSI = 6;
static const uint8_t MISO = 5;
static const uint8_t SCK = 4;From the Technical Reference manual, we can see that the default Arduino definitions above, do not support the non-routed, direct-to-IO MUX pin mappings, which from the table below do indeed map onto GPIO 2, 6, 7, 10.
In terms of using a Waveshare ESP32-C3 Zero, both combinations would be supported on the broken out GPIO, so from a software point of view, the Adafruit libraries could be used “as is” with the default mapping, or with a custom SPI definition (as shown above) with the more bespoke, but faster, mapping.
RP2040 Zero
This is using the (unofficial) RP2040 core from here: https://github.com/earlephilhower/arduino-pico, where this is an entry: RP2040 -> Waveshare RP2040 Zero.
The RP2040 has two SPI peripherals and the SPI functions are mapped onto specific sets of GPIO pins. This gives a range of flexibility, but not arbitrary flexibility. The board definition file for the Waveshare RP2040 Zero provides this as a default:
// SPI
#define PIN_SPI0_MISO (4u)
#define PIN_SPI0_MOSI (3u)
#define PIN_SPI0_SCK (2u)
#define PIN_SPI0_SS (5u)
#define PIN_SPI1_MISO (12u)
#define PIN_SPI1_MOSI (15u)
#define PIN_SPI1_SCK (14u)
#define PIN_SPI1_SS (13u)Note that the SPI1 pins for the Waveshare RP2040 Zero are not all on the standard header connections, some are on the additional pin headers across the bottom.
Using a bespoke configuration is possible using a series of calls to set the SPI pins as shown below.
SPI.setRX(SPI_MISO);
SPI.setCS(SPI_SS);
SPI.setSCK(SPI_SCLK);
SPI.setTX(SPI_MOSI);
SPI.begin(true);To use pins for SPI1, replace SPI above with SPI1. As long as this happens prior to the call to the Adafruit libraries, everything works fine.
A Common Option
It would be nice to find a set of physical pin connections that I know would always work regardless of the board in use: RP2040, ESP32-S3 or ESP32-C3.
With careful noting of the RP2040 limitations, I think that is largely possible with the following. Even though the GPIO numbers are different, the physical pins are common on all three boards.
DisplayFunctionWS PinRP2040ESP32-C3ESP32-S3BLKBacklight control
(not required)N/CN/CN/CCS1Chip select
Display 1H2 P6GP5GP9GP10DCData/CommandH2 P5GP4GP10GP11RESResetH2 P9GP8GP6GP7SDAData (MOSI)H2 P8GP7GP7GP8SCLClock (SCLK)H2 P7GP6GP8GP9VCCPowerH1 P33V33V33V3GNDGroundH1 P2GNDGNDGNDCS2CS Display 2H1 P9GP14GP5GP6CS3CS Display 3H1 P8GP15GP4GP5CS4CS Display 4H1 P7GP26GP3GP4A couple of notes:
- I’ve avoided pins 1-4 on header 2, as the ESP32-C3 can’t use them for SPI and they support either the UART or USB.
- I’ve had to include a MISO (SPI RX) pin in each configuration too, so I’ve just picked something that can be ignored. For RP2040 that has to be one of GP0, GP4 or GP16 however, which could clash with either the UART, the above configuration for DC pin, or the onboard WS2812 LED, but there isn’t much that can be done.
- I’ve allowed three consecutive pins on the first header for optional additional CS pins for displays 2 to 4.
Here is the full set of configurable code for the above:
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
//#define WS_RP2040_ZERO
//#define WS_ESP32C3_ZERO
#define WS_ESP32S3_ZERO
#ifdef WS_RP2040_ZERO
#define SPI_SS 5
#define SPI_MOSI 7
#define SPI_SCLK 6
#define SPI_MISO 4 // Not used
#define SPI_BUS SPI
#define TFT_CS1 SPI_SS
#define TFT_CS2 14
#define TFT_CS3 15
#define TFT_CS4 26
#define TFT_RST 8
#define TFT_DC 4
#endif
#ifdef WS_ESP32C3_ZERO
#define SPI_SS 9
#define SPI_MOSI 7
#define SPI_SCLK 8
#define SPI_MISO 0 // Not used
SPIClass MySPI(FSPI);
#define TFT_CS1 SPI_SS
#define TFT_CS2 5
#define TFT_CS3 4
#define TFT_CS4 3
#define TFT_RST 6
#define TFT_DC 10
#endif
#ifdef WS_ESP32S3_ZERO
#define SPI_SS 10
#define SPI_MOSI 8
#define SPI_SCLK 9
#define SPI_MISO 1 // Not used
SPIClass MySPI(FSPI);
#define TFT_CS1 SPI_SS
#define TFT_CS2 6
#define TFT_CS3 5
#define TFT_CS4 4
#define TFT_RST 7
#define TFT_DC 11
#endif
#ifdef WS_RP2040_ZERO
Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT_CS1, TFT_DC, TFT_RST);
Adafruit_ST7735 tft2 = Adafruit_ST7735(TFT_CS2, TFT_DC, -1);
#else
Adafruit_ST7735 tft1 = Adafruit_ST7735(&MySPI, TFT_CS1, TFT_DC, TFT_RST);
Adafruit_ST7735 tft2 = Adafruit_ST7735(&MySPI, TFT_CS2, TFT_DC, -1);
#endif
void setup() {
#ifdef WS_RP2040_ZERO
SPI_BUS.setRX(SPI_MISO);
SPI_BUS.setCS(SPI_SS);
SPI_BUS.setSCK(SPI_SCLK);
SPI_BUS.setTX(SPI_MOSI);
SPI_BUS.begin(true);
#else
MySPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_SS);
pinMode(SPI_SS, OUTPUT);
#endif
tft1.initR(INITR_MINI160x80_PLUGIN);
tft2.initR(INITR_MINI160x80_PLUGIN);
tft1.setRotation(3);
tft1.fillScreen(ST77XX_BLACK);
tft2.setRotation(3);
tft2.fillScreen(ST77XX_BLACK);
}
void loop() {
unsigned long time = millis();
tft1.fillRect(10, 20, tft1.width(), 20, ST77XX_BLACK);
tft1.setTextColor(ST77XX_GREEN);
tft1.setCursor(10, 20);
tft1.print(time, DEC);
delay(100);
time = millis();
tft2.fillRect(10, 20, tft2.width(), 20, ST77XX_BLACK);
tft2.setTextColor(ST77XX_MAGENTA);
tft2.setCursor(10, 20);
tft2.print(time, DEC);
delay(400);
}Closing Thoughts
It is a little annoying that these great boards don’t share a re-usable, common pinout in terms of naming and positions, but I guess that isn’t the main focus for these systems.
Still, it seems that a common hardware pinout can be made that supports many displays, which is great, as I’d really like to get a number of them onto a PCB!
Kevin
#arduinoUno #esp32c3 #ESP32s3 #rp2040 #st7735 #tftDisplay #WaveshareZero
-
Arduino with Multiple Displays
A while back I tried to use several SSD1306 displays with an Arduino (see OLED MIDI Display – Part 2) and got into trouble with memory. I have another need for multiple displays coming up, so thought I’d revisit the idea to see what could be done.
This shows use to use several cheap SPI-based displays together with an Arduino.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key Arduino tutorials for the main concepts used in this project:
- Adafruit Mini TFT 0.96″ 160×80
- Adafruit ST7735 GFX Library
- https://emalliab.wordpress.com/2025/07/19/small-microcontroller-displays/
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Uno.
- 2x 0.96″ ST7735 60×180 SPI TFT displays.
- Breadboard and jumper wires.
I’m using displays that look like this – note the order of the pins.
The Circuit
The display pins are connected to the Uno as follows:
BLKN/CBacklight control – not requiredCSD10/D7Chip select – one for each display.DCD8Data/CommandRESD9ResetSDAD11Data (MOSI)SCLD13Clock (SCLK)VCC5VPowerGNDGNDGroundNotes:
- I’m using the Arduino Uno’s hardware SPI peripheral so the use of D11/D13 is fixed and can’t be changed.
- All signals apart from CS are shared between both boards.
- However – need to ensure that the boards are only reset once!
The Code
There are several software libraries that could be used for these kinds of displays. I’m using the Adafruit libraries:
- Adafruit_GFX
- Adafruit_ST7735_Library
These aren’t always ideal for generic no-name displays as they are geared up to support Adafruit products directly.
A case in point: there is no generic initialisation for a ST7735 display, but rather a number of bespoke initialisations that relate to Adafruit’s range of displays only. I was looking for some means of setting the display size (160×80) but that isn’t possible.
But as it happens, there are two initalisation options that map onto the above boards:
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
#define TFT_CS1 10
#define TFT_CS2 7
#define TFT_RST 9
#define TFT_DC 8
//#define TFT_TYPE INITR_MINI160x80
#define TFT_TYPE INITR_MINI160x80_PLUGIN // Inverted display
Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT_CS1, TFT_DC, TFT_RST);
Adafruit_ST7735 tft2 = Adafruit_ST7735(TFT_CS2, TFT_DC, -1);
void setup() {
tft1.initR(TFT_TYPE);
tft2.initR(TFT_TYPE);
}Notes for this initialisation code:
- The use of pins D11 and D13 for SPI is assumed, and RST and DC are common.
- I’m using the “INITR_MINI160x80_PLUGIN” initialisation option which is the same as “INITR_MINI160x80” but with the colours inverted. Without this, the built-in colour options aren’t correct for my boards.
- Notice how in the second initialisation I’m using “-1” as the RESET pin so that the first display isn’t reset again after being initialised.
- Both instances have their own CS pin defined.
- The resolution and display size is fixed at 160×80.
In my case I also used setRotation(3) to rotate the displays by 180 degrees.
Here is my complete test code that prints the current millis() count to each display in a different colour.
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
#define TFT_CS1 10
#define TFT_CS2 7
#define TFT_RST 9
#define TFT_DC 8
//#define TFT_TYPE INITR_MINI160x80
#define TFT_TYPE INITR_MINI160x80_PLUGIN // Inverted display
Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT_CS1, TFT_DC, TFT_RST);
Adafruit_ST7735 tft2 = Adafruit_ST7735(TFT_CS2, TFT_DC, -1);
void setup() {
tft1.initR(TFT_TYPE);
tft2.initR(TFT_TYPE);
tft1.setRotation(3);
tft1.fillScreen(ST77XX_BLACK);
tft2.setRotation(3);
tft2.fillScreen(ST77XX_BLACK);
}
void loop() {
unsigned long time = millis();
tft1.fillRect(10, 20, tft1.width(), 20, ST77XX_BLACK);
tft1.setTextColor(ST77XX_GREEN);
tft1.setCursor(10, 20);
tft1.print(time, DEC);
delay(100);
time = millis();
tft2.fillRect(10, 20, tft2.width(), 20, ST77XX_BLACK);
tft2.setTextColor(ST77XX_MAGENTA);
tft2.setCursor(10, 20);
tft2.print(time, DEC);
delay(400);
}Closing Thoughts
This is a really good start. What I’d like to do is see how far I can push it, see how many displays it is possible to hook up, and what the overheads of updating them might be.
Then depending on where that leaves me, I can have a think about if I need an alternative microcontroller, like a RP2040 or ESP32 – something with several cores might be particularly useful for this – and see how things go.
Then I can start to think about the musical uses I have in mind.
Update: It seems to work with four displays too. I’ve attached two 1.8″ ST7735 displays just to try it (these are “INITR_18BLACKTAB” displays). See photo below!
Kevin
-
Atari 2600 Controller Shield PCB Revisited – Part 3
Following on from Atari 2600 Controller Shield PCB Revisited – Part 2 someone on Mastodon made the point that the reason they tended to use RC circuits to read paddles “back in the day” was due to the expense of ADCs.
Which triggered a bit of an “oh yeah” moment.
The whole point was not to worry about the analog levels at all, and just measure the time it takes for the pin to read HIGH again.
So this looks back at removing the whole ADC thing with a simple “if (digitalRead(pin))” condition!
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to Arduino, see the Getting Started pages.
The Code
The overarching principles are the same as for Atari 2600 Controller Shield PCB Revisited – Part 2 but instead of all the bespoke code to read the analog to digital converter, I’m relying on the following:
- A digital input pin has a threshold for which the input is considered HIGH.
- We can wait for the input reading to register as HIGH instead of looking for absolute thresholds of an analog value.
- For an ATMega328P the threshold is 0.6 x VCC or around 3V. This is equivalent to just over 610 on a 0 to 1023 scale of an equivalent analog reading.
Taking this into account and using largely the same ideas as before, I can reuse most of the code but with the following timing and threshold values instead:
- Start scaling (the 0 point): 10
- End scaling (the 1023 point): 350
The timer TICK is still 100uS and the “breakout” point is still 1000.
When it comes to reading the digital INPUT, I’m using PORT IO once again for speed and expediency.
for (int i=0; i<4; i++) {
if ((PINC & (1<<i)) == 0) {
// Still not HIGH yet
}
}Here is the complete, now greatly simplified, basic code:
#include <TimerOne.h>
#define RAW_START 10
#define RAW_END 350
#define RAW_BREAK 1000
#define RAW_TICK 100
unsigned padState;
unsigned padCount[4];
unsigned atariValue[4];
void atariAnalogSetup() {
Timer1.initialize(RAW_TICK);
Timer1.attachInterrupt(atariAnalogScan);
padState = 0;
}
void atariAnalogScan (void) {
if (padState == 0) {
DDRC = DDRC | 0x0F; // A0-A3 set to OUTPUT
PORTC = PORTC & ~(0x0F); // A0-A3 set to LOW (0)
padState++;
} else if (padState == 1) {
DDRC = DDRC & ~(0x0F); // A0-A3 set to INPUT
for (int i=0; i<4; i++) {
padCount[i] = 0;
}
padState++;
} else if (padState > RAW_BREAK) {
for (int i=0; i<4; i++) {
atariValue[i] = 1023 - map(constrain(padCount[i],RAW_START,RAW_END),RAW_START,RAW_END,0,1023);
}
padState = 0;
} else {
for (int i=0; i<4; i++) {
if ((PINC & (1<<i)) == 0) {
padCount[i]++;
}
}
padState++;
}
}
int atariAnalogRead (int pin) {
return atariValue[pin-A0];
}
void setup() {
Serial.begin(9600);
atariAnalogSetup();
}
void loop() {
Serial.print(padState);
Serial.print("\t[ ");
for (int i=0; i<4; i++) {
Serial.print(atariAnalogRead(A0+i));
Serial.print("\t");
Serial.print(padCount[i]);
Serial.print("\t][ ");
}
Serial.print("\n");
}Closing Thoughts
Sometimes one really can’t see the “wood for the trees” and this was one of those occasions. I was so took up with thinking about how a modern system might think about a problem without thinking about the original reason for the particular solution.
It makes so much more sense thinking about it in these terms now. All it took was an observation from another, namely:
“So I know the RC timer is the classic way to sense analog paddles but they also didn’t have cheap ADCs back then.”
Many thanks “Chip” for that observation 🙂
Kevin
#arduinoUno #atari #atari2600 #include #potentiometer #TICKs
-
Continued with the second #PCB, the #synthesizer #shield.
The only things I changed were adding a socket for the #OpAmp and JST connector for the speaker.
I oversaw that this caused problems with the cardboard case, so I had to cut some holes. But I'm no fan of cardboard anyways, so I don't think I'll use it.
#Arduino #ArduinoUno#soldering #solderingkit #audio -
This is actually a collection of previous projects with the code tidied up a little and combined to show a single sketch that can be configured for either PWM, an R2R ladder or the MCP4725 I2C DAC.
There isn’t really anything here that hasn’t been talked about before somewhere, but hopefully this can act as a single reference point for a range of direct digital synthesis techniques from now on.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
These are the key Arduino tutorials for the main concepts used in this project:
- Arduino PWM Sound Output.
- Arduino PWM Output Filter Circuit.
- Arduino R2R Digital Audio – Part 3.
- MCP4725 Digital to Analog Converter.
- Arduino Mozzi Additive Synthesis.
If you are new to Arduino, see the Getting Started pages.
Parts list
- Arduino Uno or Nano.
- Cheap or disposable amplification and speaker.
- Several 10K potentiometers.
- One of:
- Breadboard and jumper wires
The Circuit
There is no specific circuit dedicated to this post, but it works well with the two audio experimenter PCBs listed above, which each contain options for the following:
- PWM audio output on either D3 or D9.
- R2R resistor DAC (in the case of the Uno experimenter PCB).
- MPC4725 I2C DAC.
Alternatively, the main elements for PWM operation can be put together on a solderless breadboard as follows.
The Code
As already mentioned the core elements of the code have largely been met before, but the main sections are described below.
The general theory of direct digital synthesis operation is described fully in Arduino R2R Digital Audio – Part 3 so I won’t go over that again.
This code implements simple additive synthesis by using potentiometers to set the amplitudes for a set of sine wave harmonics. The performance of the Arduino largely limits this to being a maximum of six sine waves to be added up, but that is enough for some simple experimentation.
The code configures the first six harmonics: fundamental (f), f*2, f*3, f*4, f*5, f*6. There are some default ranges that can be used for testing without pots:
#define SC 32
int sine[MAXPOTS] = {SC*2,0,0,0,0,0};
int saw[MAXPOTS] = {SC,SC/2,SC/4,SC/8,SC/16,SC/32};
int squ[MAXPOTS] = {SC,0,SC/2,0,SC/4,0};There is no way to set the fundamental frequency – it is fixed at 440Hz. Making that controllable is left as an exercise for another day! The natural options are MIDI triggering or another pot.
The basic properties of the synthesis code are as follows:
- The sample rate depends on the technique, but in principle it could support 4096Hz, 8192Hz, 16384Hz or 32768Hz.
- It uses a 256 entry, 8-bit wavetable to define the basic sine wave.
- It uses 8.8 fixed point accumulators with the top 8-bits as the index into the wave table.
- It uses a 16-bit sample value which is scaled down as required by the audio output method.
The general pattern used in this code is as follows:
// Audio output specific functions:
dacSetup ()
dacWrite (value)
dacScan ()
// Generic audio functions:
dacPlayer ()
Call dacWrite (last calculated sample value)
For each potentiometer:
Update accumulator for the DDS
Add potval * sinetable[accumulator>>8] to the total
setup ()
Call dacSetup ()
loop ()
Call dacScan ()
Every 10 loops update the potsEach audio output option will implement the three functions dacSetup, dacWrite and dacScan, but not all need to be used. Conditional compilation is used to select between audio output options by defining one of PWM_OUTPUT, DAC_OUTPUT or R2R_OUTPUT.
Here are some notes for each option.
PWM:
- Output scaled to 8-bits for use with PWM.
- PWM is configured to run at 65536Hz.
- The TIMERn_OVF_vect interrupt is used to trigger sample updating via dacPlayer().
- All four sample rates are possible so samples are not written out on every interrupt. For example, for a sample rate of 16384Hz a sample is written out on every fourth interrupt.
- Can support either D9 (Timer 1) or D3 (Timer 2).
- As updates are interrupt driven, dacScan () is empty.
R2R:
- Uses D8-D9, D2-D7 as bits 0 to 7 for the DAC output.
- Output scaled to 8-bits for use.
- PORT I/O is used to write to the data lines.
- Code takes into account the fact that D0/D1 might be in use as the UART.
- Updates are interrupt driven using the TimerOne library, calling dacPlayer() directly.
- As updates are interrupt driven, dacScan () is empty.
MCP4725:
- The I2C address for the DAC is configured by defining MCP4725ADDR. It defaults to 0x60.
- As the DAC can’t be written to from an interrupt routine, the output is set during dacScan() so the loop() has to run as fast as possible.
- The sample rate is set by monitoring the micros() tick (note on an Arduino the resolution is 4uS at best).
- Uses the non-blocking I2C library and fast analog read from Mozzi.
- Uses the MCP4725 fast write mode, which only requires two bytes to be sent to the DAC.
- The maximum sample rate is 8192Hz and even then it runs a little slow (i.e. the 440Hz tone is flat by around a semitone).
General comments:
- There is an optional timing pin that is configured by defining TIMING_TEST. This is toggled in dacPlayer().
- There is an optional fixed set of amplitudes that can be used instead of potentiometers. These are set up in setDefaultAmplitudes() when DAC_TEST is defined.
- The maximum number of pots supported is 6. The code skips using A4/A5 as these map onto I2C if the DAC is used. The number of pots to scan (and hence sine waves to add up) can be reduced by setting NUMPOTS to a number less than MAXPOTS (which is 6).
- If the number of pots is reduced, then the scaling factors used to calculating the totals can be adjusted by changing SC and PSC accordingly. For 6 pots/waves they are set to 32 and 4 respectively. This means that analogReads have a maximum range of 0..31 which is set by
val = analogRead(pot) >> PSC;
Closing Thoughts
This has been interesting to revisit. After all my experiments this is starting to finally make some sense. It has been interesting to contrast the three output methods both in terms of their computational performance and in terms of output waveform quality.
The photo at the start shows the R2R output of the test sine wave. The photo below is the PWM output for a potentiometer-driven saw.
This has also prompted me to revisit my Arduino PWM Output Filter Circuit and finally work out how to properly combine a low-pass filter and potential divider and still get something approximating the filter characteristics I wanted. I feel I understand quite a bit more about what is going on now.
Now if I could just get a bit of a handle on impedance I might actually start to feel like I know a little about what I’d be talking about….
Kevin
https://diyelectromusic.wordpress.com/2024/03/06/arduino-direct-digital-additive-synthesis/
#additiveSynthesis #arduinoNano #arduinoUno #dac #dds #mcp4725 #pwm #r2r
-
Peek Behind the Curtain of this Robotic Mouse - At first glance, this little animatronic mouse might seem like a fairly simple aff... - https://hackaday.com/2022/01/09/peek-behind-the-curtain-of-this-robotic-mouse/ #robotshacks #animatronic #arduinouno #leadscrew #nema17 #puppet #tb6600 #sg90 #gt2
-
Working on the WAvrVM firmware, today I finally got around to implement an op code parser for the serial protocol between the #raspberrypi3 and the #arduinonano via a HC-05 bluetooth module. The program already takes more than 50% of the available flash and SRAM.But I think everything will fit nicely.
#rgbled #ws2812 #linux #diy #maker #programming #foss #floss #coding #arduino #arduinouno #electronics #opensource #creativecommons #kicad #diyelectronics #electronicsprojects #electronicsproject
-
After removing the adhesive side of the LED strips the other day, I put some solder on the pads. All in all I repeated this step for 270 pads.
#3dprint #3dprinting #rgbled #ws2812 #linux #diy #maker #programming #foss #floss #rustlang #rustprogramming #coding #raspberrypi2 #raspberrypi3 #raspberrypi #arduino #arduinouno #arduinonano #electronics #opensource #creativecommons #kicad #diyelectronics #electronicsprojects #electronicsproject
-
I took the time to document the operations I implemented for my control software WAvrVM. That is a little VM that runs in an #arduino with an instruction set to calculate and set the pixel colors in an #neopixel #ws2812 RGB LED strip. Joined with the bluetooth module HC-05 a #raspberrypi3 or #raspberrypi3 can then upload new animations or just change the colors.
#rgbled #diy #maker #programming #arduino #arduinouno #arduinonano #electronics #electronicsprojects #electronicsproject #opensource
-
For the big LED panel project I finally found the time to cut the strip into 45 parts. Each 5 LEDs long. Also removed the adhesive pads of them, because it does not stick to 3D printed parts well. Going to use hot glue for this.
#3dprint #3dprinting #rgbled #ws2812 #linux #diy #maker #programming #coding #raspberrypi2 #raspberrypi3 #raspberrypi #arduino #arduinouno #arduinonano #electronics #opensource #creativecommons #kicad #diyelectronics #electronicsprojects #electronicsproject
-
Soldered the LED panel circuit now. And it works like a charm! Next up on my list is working on the 30-40 LED panel version and finishing the "firmware".
#3dprint #3dprinting #rgbled #ws2812 #linux #diy #maker #programming #foss #floss #rustlang #rustprogramming #coding #raspberrypi2 #raspberrypi3 #raspberrypi #arduino #arduinouno #arduinonano #electronics #opensource #creativecommons #kicad #diyelectronics #electronicsprojects #electronicsproject
-
Cakeday Countdown Clock is a Sweet Little Scroller - If you want strangers to give you well wishes on your birthday out in the real world, you have cal... more: https://hackaday.com/2020/07/08/cakeday-countdown-clock-is-a-sweet-little-scroller/ #countdowntimer #arduinohacks #arduinouno #countdown #ds1307rtc #ledmatrix #arduino #cake #rtc
-
Simon Says, But With Servos - How much easier would life be if you could just grab hold of whatever mechanism you wanted to mani... more: https://hackaday.com/2020/04/07/simon-says-but-with-servos/ #choreographyforservos #potentiometer #arduinohacks #arduinouno #simonsays #arduino #servo
-
Watchman Watches You Watching Him Watch You - At this point, society has had over three decades to get used to the Blue Man Group. Maybe that’s wh... more: https://hackaday.com/2020/03/15/watchman-watches-you-watching-him-watch-you/ #raspberrypizero #arduinohacks #facetracking #raspberrypi #animatronic #arduinouno #googleaiy