Sunday, March 1, 2026

Arduino Nano R4 Input Capture (Guitar Tuner)

    I bought some of the new Arduino Nano R4 boards to play with. These boards step up from AVR to ARM (RA4M1) microcontrollers while still running at 5V, a rarity these days. The complete change of architecture brings with it some growing pains. Nothing is the same under the hood, and few of the new features are meaningfully supported within the Arduino ecosystem.

    I'd like to implement some kind of autotune for a synth, but how do we measure frequency with the new R4 peripherals? Since Arduino S.r.l. (Qualcomm) isn't going to hold our hand, we have to dig into the manufacturer's datasheet.

RTFM

    There's a feature that some timers have, called "input capture". It doesn't get mentioned all that much, but it's very handy for measuring external pulses. It allows an incoming signal to trigger a "snapshot" (capture) of a timer's current count. When configured correctly, it can measure things like the timing/width of a pulse, or the period/frequency of a waveform. If we search the datasheet for this term, we can find Figure 22.17

P 445 of the Renesas RA4M1 Group 32 User’s Manual


    This is the process to set up a timer for input capture. Great, but how do we follow this, and what are these acronyms? GTCR, GTUDDTYC, GTICASR... These are memory-mapped registers that control the timers. Explanations of every timer (GPT) register exist in the datasheet starting with Table 22.4 on page 396.

P 396 of the Renesas RA4M1 Group 32 User’s Manual

    Ok, but how do we actually use these registers within the Arduino IDE? Buried in the Arduino files is a header that defines these: R7FA4M1AB.h

    Here's GTCR. We can see that it's the "General PWM Timer Control Register". The header defines this as a union. This is just a method of declaring multiple variables that live within one address. CST is a single bit of GTCR, the least significant, while MD spans bits 16 through 18 of the same address.

GTCR definition from R7FA4M1AB.h

    If we look higher in the header, we see that the GTCR union is within a struct named "R_GPT0". This serves to contain all the timer-related registers. If we want to refer to MD, like the datasheet instructs, we would use: R_GPT0->GTCR_b.MD

The Wind-up

    The following code will configure the timer using the syntax we've established. There is a "gotcha" though. The timer module has to be enabled before it will function.

  //Timer Setup------------------------------------------------------------------------------------
  R_MSTP->MSTPCRD_b.MSTPD5 = 0; //enable GPT0 module clock (General PWM Timer 321 to 320 Module Stop)
  delayMicroseconds(10);
  
  R_GPT0->GTCR_b.CST = 0; //stop timer
  R_GPT0->GTCR_b.MD = 0b000; //saw-wave PWM mode (000b)
  
  R_GPT0->GTUDDTYC = 0b11; //set 11b first (per datasheet Figure 22.17)
  R_GPT0->GTUDDTYC = 0b01; //01b for up-counting
  
  R_GPT0->GTPR = 0xFFFFFFFF; //max cycle
  R_GPT0->GTCNT = 0; //0 initial count

  //GTCCRA input capture enabled on the...
  R_GPT0->GTICASR_b.ASCARBL = 1; //rising edge of GTIOCA input when GTIOCB input is 0
  R_GPT0->GTICASR_b.ASCARBH = 1; //rising edge of GTIOCA input when GTIOCB input is 1
  
  R_GPT0->GTCR_b.CST = 1; //start count operation

    Some of these acronyms above aren't mentioned in Figure 22.17, but the datasheet can clarify them.

On Time    

    The timer is running, and is configured for input capture on the rising edge of input GTIOCA. Well, what is that? It's a pin of the microcontroller, but the Arduino board obscures the true names of the pins by holding to their naming convention of D0-D13.

    We can view the real pin names in the schematic of the Nano boardGTIOCA is P107_GPT0_A, and that maps to "D7" of the Arduino header.

Arduino Nano R4 schematic

    When this pin state changes to high, the counter's value will be dumped into register GTCCR and the TCFA flag will be set. We can wait for this flag, read the value from the register, then clear the flag.

   while (!R_GPT0->GTST_b.TCFA); //wait for input capture A

   uint32_t lastTS = R_GPT0->GTCCR[0];
   R_GPT0->GTST_b.TCFA = 0; //clear the flag

    We run the risk of not polling the flag quickly enough, and missing a captured value though. A better approach is to use interrupts.

Excuse Me

    Interrupts are another poorly covered topic when it comes to the R4 within the Arduino IDE. The first snag to getting an input capture interrupt is the GPIO. Using the "Port mn Pin Function Select Register", the pin must be switched to work as a peripheral instead of standard IO. We then need to select GTIOC0A as the peripheral. The correct value for this can be found in Table 19.6

P 370 of the Renesas RA4M1 Group 32 User’s Manual

    Plugging in the value gives us this:

  //GPIO Pin D7 (P107) Setup
  R_PFS->PORT[1].PIN[7].PmnPFS_b.PMR = 1; //Used as an I/O port for peripheral functions
  R_PFS->PORT[1].PIN[7].PmnPFS_b.PSEL = 0b11; //GTIOC0A (GPT peripheral function)

    Then we need to set up the interrupt. This process isn't exactly straightforward, but we can crib from those who have gone before us. This post on the Arduino forums by pertomaslarsson is very helpful. Referencing it, I came up with this:

//Asynchronous General Purpose Timer interrupt (from vector_data.h)
static const IRQn_Type IRQn_CCMPA = AGT0_INT_IRQn;

  //Interrupt Setup
  //assign GPT0 Capture to IRQn_CCMPA (AGT0_INT_IRQn #17)
  R_ICU->IELSR_b[IRQn_CCMPA].IELS = ELC_EVENT_GPT0_CAPTURE_COMPARE_A;
  NVIC_SetVector(IRQn_CCMPA, (uint32_t)captureISR); //point to the ISR function
  NVIC_SetPriority(IRQn_CCMPA, 12);
  NVIC_EnableIRQ(IRQn_CCMPA);

    Much like the pins, there are a finite number of interrupt vectors, and we have to select what functionality we'd like them to have. Here we assign our input capture interrupt to the generic AGT0 vector. We also give it a function(Interrupt Service Routine) to call when the interrupt happens.

    Here's a simple example of an ISR function:

uint32_t lastTS, currentTS, delta = 0; //input capture readings

void captureISR() {
  lastTS = currentTS;
  currentTS = R_GPT0->GTCCR[0]; //read from input capture register
  delta = currentTS - lastTS;
  
  //clear interrupt flag for compare match A
  R_ICU->IELSR_b[IRQn_CCMPA].IR = 0;
}

    It stores the previous reading, grabs a new reading, and clears the flag, allowing a new interrupt to fire. We can use the readings elsewhere in our code.

Alright Stop

    So far, this has all been academic. Let's finish with a functional example. I've created a simple guitar tuner that displays the frequency in Hz, the name of the note, and how far the tuning is from concert pitch.

Guitar tuner proof of concept


    The source code is available on pastebin


Monday, October 27, 2025

Famicom PCB and Schematic Redraw

    There are several schematics for the Famicom, but they're all a little different. It's not clear if this is from different board revisions, mistakes, modifications, etc. So, I thought I'd trace a board that I had already depopulated for another project.

    The main goal was to make an accurate schematic, but I decided to recreate the PCB layout too. It's not perfect. I had to fudge some footprints, but they're in the ballpark. This would serve as a good starting point for making a replacement original board.

    There are some oddities in the schematic, but nothing too interesting. A tristate buffer is abused into working as an amplifier for the audio. A few passives have a dotted outline, indicating that they're not usually (ever?) populated. The oscillator circuit is little different from other schematics. Files are on my github here.

    Here are the new traces overlaid on a scan of the PCB. This highlights the accuracies and inaccuracies.
    
Overlay


New schematic

New PCB top

New PCB bottom








 

Saturday, July 19, 2025

Adding MIDI to the Electro Harmonix EH400 Mini Synthesizer

    Once again we return to the EH400. This time we're looking at adding MIDI.

    This mod is for a friend and needs to retain the use of the original controls. While it's tempting to build on the CV mod from last time, that's not compatible with the original touch keyboard. We need to perform the functions of the keyboard, but we have an uncommon keyboard. It works by shorting the top foil of the keyboard to one of the 25 contacts on the lower PCB. This presents one of 25 different resistances to the oscillator, and causes a note to play. How can we fake this?

Resistance

    One way is to simulate this keyboard via some kind of variable resistance. It could be a digital potentiometer, or a DAC + a voltage controlled resistor. This problem is that this replaces the keyboard. This could be fixed by somehow switching between the original keyboard and the new resistance, or by reading the keyboard and using it to control the new resistance.

Push the Button

    A simpler solution is to control the keyboard directly, by pretending to press the "keys". We can short the foil to the pads via some kind of switch. BJTs, optocouplers, FETs, and analog switches are among the options. Considering we'll need 25 switches, we want something that's not too cumbersome. Analog switches are nice because you can get several of them in one package. Luckily, we don't need 25 completely separate switches; they'll all be sharing the foil as one of their terminals. This means we can use an analog multiplexer like the CD4051, as it has many switches with one common connection.

One of many

    It's important to pick the right multiplexer (mux) for the job. Ideally we'd find a 25:1 mux, but they really only come in powers of two, up to 16. To make things simple, we'd like it to run off of the 9V supply rail, and we'd like a low "on" resistance too. This keeps it from impacting the tuning too much. This narrows down our options to mainly derivatives of the CD4067. The 74HC4067 has a lower on resistance while still being able to tolerate the 9V supply. In practice, a normal CD4067 might be low enough though.

4067 multiplexer

Free range

    So, we can manage our 25 switches with two muxes. There's another component to controlling the pitch though, the octave switch. It shifts the keyboard by two octaves. We'll need digital control of this octave switch in order to have the full 4 octave range available over MIDI. How does this switch work though?

Front and back, and side to side

    There are two capacitors that dictate the range. One might assume that the switch selects between a larger one, and a smaller one, to change octaves. Turns out that they're both the same size though. It's their configuration that the switch changes. This is done because capacitors are somewhat low tolerance devices. Let's say they're 5%; one cap could be +5% while the other is -5%. That puts them pretty significantly out of tune with each other. Instead, the circuit uses the fact that caps in parallel sum together, and caps in series decrease in value. The math behind this turns out to work in our favor.

Series vs parallel from wiki.testguy.net

    Parallel is simply C1 + C2, but series is (C1 * C2)/(C1 + C2). Let's plug in some worst case scenario 5% 100nf capacitors: (105nf * 95nf)/(105nf + 95nf) = 9975/200 = 49.875nf series. 105nf + 95nf = 200nf parallel. 49.875nf vs 200nf. Wow, we're only a quarter of a percent out from a perfect four-to-one relationship (two octave shift). So, this change in configuration (parallel vs serial) gives a result that brings our octaves much closer to in tune than 5% capacitors would normally allow.

Another one

    We find that we need one more analog switch to control the parallel/serial capacitor configuration. This time we don't need 16 throws, but the on resistance is especially important. There are a number of candidates, but the DG409LEDY happens to be relatively cheap and available at the moment. It's a dual 4:1 mux that can handle the voltage and is rated at 17Ω on resistance.

    A simple solution would be to copy the connections of the original, mechanical switch. "On resistance" wasn't a consideration in its design though. It's important that we chain as few connections through the switch as possible, so that we don't keep stacking this 17Ω penalty. Here's one arrangement that limits this while still switching between serial and parallel.

Octave switch - Simulate here


The soft side

    As with any MIDI project, there's software and digital IO involved. This opens up new possibilities on a synth that was completely analog. We can control things remotely, generate new signals, or respond to new inputs. Given how spartan the synth is, there are only so many targets for modulation. The filter and the oscillator are the big ones.
Arduino Pro Mini

Lasting impact

    There's one more thing that the original keyboard offers, a form of velocity labeled "Sense". This is accomplished with a piezo that's stuck to the bottom of the keyboard, and routed into the envelope generator. We can take our MIDI velocity, turn it into a voltage, and pipe it into the same point. This is far from precise work, so we can generate our voltage in an imprecise way: pulse width modulation. Many microcontrollers have provisions to generate PWM and it can easily be filtered into a useable voltage.

Notes between notes

    We can fairly easily add a feature that that original instrument lacked: portamento. This is a smooth sliding effect between notes. Just one problem, we're stuck with the rigid notes we have. But, glissando done fast enough approximates the sound of portamento. This means playing every note that falls between the previous and current note. This could be as little as zero notes, or all four octaves of notes. The effect is most convincing when done quickly and over a large range. It doesn't require any extra hardware though, just code to change notes.

Other thoughts

    Key tracking could be added without too much trouble, as could other software modulation sources like LFOs, or envelopes. The only obvious destinations are the filter and the pulse width setting. These already have an envelope and an LFO respectively though.

    Key retriggering could be forced on by resetting the envelope during every note on. This would disable the legato behavior of the original keyboard.

    Some cross modulation would be possible with the addition of VCAs or digipots. The filter could be FM'd by the oscillator, or the oscillator could be AM'd by the LFO. At some point you lose the spirit of the original instrument though.

Closing circuit

A one-off MIDI board

EH400 MIDI schematic

Files

    The source code and schematics for this MIDI retrofit are available on Git Hub, here. It's intended to be built with an Arduino Pro Mini, but small changes will allow it to run on many AVRs.



Saturday, March 15, 2025

A lightweight ADSR for AVRs

    I've been doing more projects that involve digital control of analog circuits, and I found myself needing to generate ADSR envelopes in software, on an Arduino. I found some approaches that implement a digital filter to simulate the RC curve you get from a capacitor charging. This usually involves slow floating-point calculations that the Arduino isn't well-suited to crunching.

RC curve ADSR - AudioMulch

Look up, it's a bird, it's a table

    A classic way to avoid slow math is to precompute it, and store the results in a table. Then you just look up the result for your given input. Storing the result for every possible input can result in an excessively large table though, so maybe you want to store fewer results and estimate values between them. This is called interpolation, and there are multiple ways of doing it. We could use the continuous curves of splines to connect our points, but this would be slower than the actual calculation we're trying to avoid. Instead we'll use fast linear interpolation, that just draws straight lines.

An interpolated attack curve

    Usually you would store a series of x and y coordinates, and then calculate the line between each pair. We really only care about the lines though, so we might as well precompute those also. Thinking back to algebra 1, we just need to store the slope and y-intercept to define them.

It's not a phase, period

    Now that we have a curve, we need to progress through it over time. A naive approach might be to march through every possible value, and simply do this as fast as needed. This is likely a waste of processing time, and we can instead strategically skip over some values to progress through the table more quickly. The Prophet 600 reportedly updates its envelopes at just 200Hz. It simply takes larger or smaller "steps" each update to change the length(period) of the stages.

Ain't no half‐steppin'

    Another beginner trap is to only think in terms of whole numbers, since we're avoiding floating points. Let's say we have 10 possible output values, and our smallest step is 1. It then takes 10 steps to get through all outputs. If our next smallest step is 2, it only takes 5, and that's twice as fast. It'd be nice to have 1.5 as an option. We can do this by using fixed point numbers. We can use steps of 15, and count up to 100 to get the same effect as steps of 1.5 counting up to 10. We just need to divide by 10 at the end to get the correct output. If we use powers of 2, instead of 10, the division becomes bit-shifting, and that's very fast.

Bit shifting to divide by 2 - Wikipedia

Non-canonical sections

    We just saw that a step size of 2 is twice as fast as 1. This hints at the funny relationship between the step size and the period. It's just period = max count / step, but this results in a very uneven response. When the step size is small, a slight change results in a very different period length. When the step is large, even significant changes have very little impact on the period length.

period = max count / step size

    Algebra 1 strikes again; This curve is a conic section called a hyperbola. It comes from us having a function in the form of 1/x. We can't really help that we're dividing a constant by a variable, but we can control the variable. If we replace x with 1/x (the reciprocal), we effectively multiply by x instead of dividing by it. 10/(1/x) = 10 * x. Now we have a straight line.

period = max count / (1 / step size)

    This looks better, but it doesn't feel right when mapped to a control. This is because the period is being adjusted with the same granularity on slow attacks vs fast attacks. That ends up being too coarse at one end, and too fine at the other. We need a curve after all.

The epitome of hyperbola

    The hyperbola we started with was too severe, and didn't pass through any particular points. What if we could fix those problems? Then a hyperbola might be a suitable curve.

    1 / (x + 1) / (10 - x) will approach points 0,10 and 10,0. That's helpful. We can change the severity of the curve by multiplying x by a coefficient in the numerator. Here's 1 / (0.25x + 1) / (10 - x)

period = max count / ((0.25 * step size + 1) / (10 - step size))

    By continuing to manipulate this formula, I landed on a new one that lets you adjust the maximum period without distorting the curve. I'm using 10 bits for the step size, so the number 1024 comes from the maximum step size. c sets the curve, and m sets the maximum period


    I'm using a sampling rate of 4kHz, and a maximum count of 2^20 = 1048576. This gives us the whole calculation to find the length of a period in seconds:

period length in seconds

    c, m and the constants are all known at compile time, so this can be simplified in our program prior to execution. A c of 4976 and an m of 3 simplify to roughly:

c = 4976, m = 3

    You can graph this function and adjust it in real time at this desmos link

Starting over

    There's another snag, restarting the envelope. It's tempting to begin counting from 0, but that's not how analog envelopes typically work. They start the attack stage from whatever their output level currently is. That would be simple, but we progress through our stages linearly, while the output is the result of our lookup table. So we have to convert from an output level, back to a position in the attack stage (that yields the same output). I chose to use a second lookup table to convert outputs back to attack stage positions. 

Release

    The C++ source code is on my github here. The focus is really the ADSR library, but the project is a functional example. It uses an Arduino nano, four potentiometers plus a trigger for input, and an MPC4728 for 12-bit analog output.

    Here's an incomplete prototype that I wrote in JavaScript. The gate is controlled in real time by mouse click. The red dots indicate the progress through each stage, and the black line is the actual output.




Thursday, January 23, 2025

Timex Sinclair 1000 New Case and Keyboard

    The Timex Sinclair 1000 (ZX81) is a charmingly limited computer from 1982. It has one of the worst keyboards of all time, and it begs for you to DIY something better. Many people have have already created improved keyboards and cases, but I wanted to try my hand. I figured there are enough chunky, old computers out there though; I want to make something thin and sleek.

Video

RF modulator

    The RF modulator is the tallest part of the board, and needed to go. It can be replaced with a simple transistor buffer to get composite video. Some ULAs require a more advanced mod to get usable video. One option is this Ginger Electronic board. It has the bonus feature of allowing you to invert the screen "colors".

Channel switch

    The channel switch on the bottom of the PCB also adds to the height, and needed to be removed.

Power

Heatsink

    The heatsink isn't thick, but it is quite large. It's meant to extend under the keyboard, but this would add to the thickness of our new keyboard. Out it went. The regulator can't run without it though, so that had to be replaced. I found these generic buck converters that are drop in replacements, and don't require heatsinks.

Keyboard

Redragon K603

    The original keyboard is as thin as it is unpleasant to use. To keep the new one thin, I looked for low-profile switches and especially short keycaps. I found the Redragon K603 keyboard to be a cheap source of both. The switches are even socketed, so they can be easily removed. The switches can be found separately though, and there are many options for keycaps.

Keycap stickers (image from 4keyboard.com)

    The next issue was the key legends. They're unique to this computer. Luckily I found these stickers from 4keyboard.com. They have all the information of the original keys, and they fit on a standard keycap.

Keyboard membrane connector

    It's easy enough to design a mounting plate and board for a custom keyboard, but it has to connect to the motherboard's strange connector. It's expecting the mylar sheets of the original button matrix. I could have bypassed or replaced the connector, but that felt like cheating. Instead, I designed a flat flex cable that fits into the original connector, and a standard flat flex connector on the new keyboard PCB.

New flex cable

Case

    I don't have any experience with 3D printing, and I'm not always impressed with the results I see. I stuck with 2D and designed a laser cut case. To keep things simple, I used all right angles, and dovetail joints. I settled for using super glue to join the majority of pieces (these nozzles helped immensely). The rest are held with screws and m2 nylon standoffs. Many of these are recessed into the bottom of the case as a way of achieving non-standard heights.
    A few standoffs go through the case lid also. This requires a screw with a head larger than the standoff to secure the lid. I found this pack that includes "large head" m2 screws.


M2 case screws

Jacks

    The power, video, ear & mic jacks need to be brought to the outside of the new case. One option is to butt the motherboard against the left side, and expose the original jacks. This offsets things strangely due to the width of the new keyboard. The expansion edge connector ends up in the middle of the case, and the mounting holes off to the left. So, I placed it at the far right, and used new panel mount connectors instead.


Motherboard placement

    I took the opportunity to remove the headphone-style connector that's misused for power, and replace it with a normal 2.1mm barrel jack. I also added the conspicuously missing power switch. Lastly, I included a small toggle switch that can be used to invert the video signal.

Lettering

    The case needed some lettering to differentiate the "Ear" jack from the "Mic" jack, and to add some character. These letters can be etched in, but you get very little contrast with acrylic. Paint is pretty much required. Luckily, laser etching usually gets you a free stencil because they etch through the protective film that comes on the acrylic. This makes it very easy to paint the parts, and then remove the film. I found that model spray paint works well for this. 

Lettering on the new case

Putting it all together

    After all the gluing, painting, soldering, and desoldering, I had a finished case.


    

    The design files are on my github.




Sunday, November 24, 2024

Synsonics Pro Dual Kit Drum schematic

    I'm always looking for drum synths to document, and I recently found a "Synsonics Pro Dual Kit" from Japan. Not to be confused with any other drum by a toy manufacturer, or guitar for that matter.

    It consists of two identical drum voices, so I've drawn one of them. Sadly, there's nothing too novel here. We have a decay-only envelope, a triangle VCO, and an OTA-based VCA.

Synsonics Pro Dual schematic

    The VCO design pops up a lot when you look for simple, DIY designs. Despite this, I realized that I had never taken the time to learn how it works. As always, it has an integrator and a schmitt trigger; How different could it be?

Basic Oscillator

    Let's start by looking at a non-voltage controlled version of the circuit. Rt sets the frequency of the oscillator by limiting the current flowing between the schmitt's output and the integrator's input.


Voltage Control

    We could maybe put a voltage controlled current sink in place of Rt to achieve voltage control. The trouble is that we don't only need to sink current; Half the time we need to source current instead. 

    Here's one solution to that, courtesy of the LM13700 datasheet. The righthand OTA is again just a schmitt trigger. The lefthand OTA is used as a voltage controlled current sink/source. You can specify the direction (sink/source) via its non-inverting input. The addition of the capacitor turns it into an integrator of sorts.


    Here's a closer look at this funny integrator. It has two controls: one for rate (amount of current), and one for the direction (inversion).

OTA integrator

    Really, the "rate" control is a normal integrator input, except it can't go negative. The "direction" input is what specifies that the input is negative (or positive).


The Synsonics Version

    We can see that the Synsonics version doesn't use an OTA, but it still has an integrator that can be inverted. How do they pull it off? The integrator works very much like a summing amp, and they're making it do arithmetic.

    The transistor is acting as a switch that can connect R17 to ground (0V). R15 and R16 form a voltage divider that puts half of our "Rate" voltage at the non-inverting input of the op-amp. We can substitute those in the simulator to simplify things. 

simplified circuit

The Math

    The voltage at the non-inverting input (non-inv) is subtracted from each of the inputs. Each difference drops across its respective resistor, and the resulting currents are summed.

We can represent a single input's current like this: (input - non-inv)/resistor

    In our circuit, the non-inv input will always be half the rate voltage, so we can put that in the formula. Now we can calculate the current for the regular rate input: (rate - rate/2)/100kΩ = (rate/2)/100kΩ

If we plug in 10V, we get this: (10V/2)/100kΩ = 5V/100kΩ = 50uA

Rate current


    Here's the second input when it's connected to ground: (0V - rate/2)/50kΩ = (-rate/2)/50kΩ

We plug in our 10V again to get: (-10V/2)/50kΩ = -5V/50kΩ = -100uA

0V current


    So, the second input's current is twice the magnitude of the first, but negative. Remember these currents get summed together, giving us this: current - 2*current = -current

Inverted current

That's how the integrator is inverted, by subtracting twice the normal current. The rest of the oscillator is standard, so we'll leave off with a Falstad simulator link.


Falstad simulation