Disclaimer: I posted this project hoping that it would be interesting and useful. However, it is provided "as is" with NO WARRANTY of any kind and if you connect it to an actual organ you must know what you're doing to avoid damage to the organ or to the tuner. The user assumes all risk using this project with actual hardware.
As electronics gets smaller and higher density it becomes more difficult and often impossible to repair and/or modify electronic products. In too many cases you just have throw it away, which is sad. I recently took a step in the opposite direction by acquiring a Rodgers Trio 321C home theatre organ built around 1978. In the USA there are many old organs you can buy for cheap or pick up for free. It’s always nice to be able to save a beautiful old organ from ending up in a landfill.
Here’s my Trio: it has three full-size 61-key manuals and a 32-note pedalboard (not shown). The pedals are actually a bit narrower than the AGO (American Guild of Organists) standard, but they work OK for me. The narrower pedals allow the Trio to be smaller than a full-size console which made it possible to move it down my basement stairs.
This is an early picture when the Trio was in the garage, before I replaced some burnt-out light bulbs.
The Trio is an analog instrument mostly built from discrete resistors, capacitors, diodes, and transistors. They’re mostly standard through-hole components that are still available. So if something goes wrong, you can get in there and diagnose what’s going on with an oscilloscope, and replace individual components. There are some ICs, but most are standard operational amplifiers and CMOS logic ICs. There are a few standard and one custom IC that are no longer available, but the organ works well now and the parts most likely to fail are replaceable.
My long-term goal is to add “virtual pipe organ” (VPO) capability. Modern organs use samples of actual pipe organs and play those samples back to produce the sound of a real pipe organ. You can fairly easily wire up an old organ’s keyboard contacts to a microcontroller and use it to produce a MIDI data stream. You then use the MIDI stream to drive a music synthesizer, which may be a general-purpose PC. I’m planning on using a Raspberry Pi 2 model B for this purpose some day.
In the meantime, I really like the sound of the Trio as is. It was a little out of tune when I got it, which is not surprising. I tolerated it for a while: being a little out-of-tune is actually better for some theatre music. But Bach wasn’t happy and once you notice something’s wrong it becomes increasingly obvious. So I needed to tune the Trio.
Modern organs don’t need tuning -- they generate notes from a nice, stable crystal oscillator. Many organs of my Trio’s vintage had 12 oscillators -- one for each note of the standard chromatic scale -- and had divider chains to produce the rest of the notes from the “top octave” oscillators.
The Trio does it differently: it has an oscillator for each note. In fact, it has two oscillators for most notes: a sine wave oscillator for producing Tibia (flute) sounds and a square wave “main” oscillator for producing everything else (reeds, strings, diapason, piano, chimes). There are 73 square wave oscillators (6 octaves plus one extra C). These cover the 5-octave manuals (61 notes) plus an extra octave of bass notes for the pedal board. There are 85 sine wave oscillators (7 octaves plus an extra C). The extra octaves are for “4 foot” and “2 foot” stops that add higher Tibia octaves to the note you are playing.
You need to tune each of these 158 oscillators individually by taking off the back of the Trio so you can get at the circuit boards, which are mounted on two huge wooden frames. Each oscillator has a fixed capacitor and a variable inductor to set the frequency. Here are some oscillators:
The inductors are the large gray cylinders. The top part of each inductor can be turned clockwise or counter-clockwise to adjust the frequency.
An FPGA-Based Tuner
While it is possible to tune the Trio by ear, it’s much easier to use an electronic tuning device. You can buy these, and I’ve heard that you can get an electronic tuner as a smart phone app. But it’s much more fun to build one. I figured such a device would make a fun FPGA project and would be a good test design for my XXICC FPGA design tools. Plus, most purchased tuners use audio input. With my FPGA-based tuner I can simply attach a probe to the Trio oscillator being tuned and give my ears a rest.
The rest of this ’blog describes my design in detail. Here's the FPGA source code and the spreadsheet I used to compute the frequencies.
My design uses a Nandland Go Board which has a Lattice iCE40 HX1K FPGA. The Go Board has a 2-digit 7-segment display which display the current note being tuned, buttons to select the current note, a VGA output connector which I use to output a sine and square wave, and a Pmod connector to input a Trio oscillator signal.
Basically, my tuner accurately synthesizes a note from the Go Board’s 25 MHz oscillator and compares it to a Trio’s oscillator output, displaying the frequency difference using the Go Board’s row of 4 LEDs. Alternatively, you can use an oscilloscope to compare the two notes. I’ll describe this in more detail later.
Synthesizing Notes
The standard chromatic scale has 12 notes: C, C# (C sharp), D, D#, E, F, F#, G, G#, A, A#, and B. Each note also has an octave number. I like to use Yamaha’s convention of C3 as Middle C, so the octave from Middle C up to the next higher C is C3, C3#, D, D3#, ... A3, A3#, B3. Middle C is approximately 261.6 Hz. A3 is usually 440 Hz. The Trio’s square wave oscillators are from C0 (32.7 Hz) through C6 (2093 Hz). The Trio’s sine wave oscillators are from C1 (65.4 Hz) through C8 (8372 Hz). Here’s a spreadsheet for all 97 notes.
For my tuner I decided to use equal temperament. This is the usual temperament since the mid-19th Century so it’s appropriate for theatre organ music. Also, the Trio has a “Glockenspiel” -- a mechanical xylophone with magnetic strikers. The Glockenspiel has fixed tuning, so the rest of the Trio needs to match it.
In equal temperament, the frequency ratio between each adjacent pair of notes is the same. This interval is called a semitone -- there are 12 semitones per octave. The ratio between a note and one octave above is 2, which makes the semitone ratio equal to the 12th root of 2, approximately 1.059,463. That is, each note is 5.946% higher than the one below it. Unfortunately, the 12th root of 2 is irrational, so the only pure intervals are octaves. All other intervals are irrational, so they sound slightly out of tune.
For example, the frequency ratio from C3 to G3 (a perfect fifth) is approximately 1.4983 in equal temperament. The pure interval is 1.5 which is rational, and if an instrument is tuned that way you hear a nice pure interval. With equal temperament it’s not pure and you hear the mismatch as “beats”. There’s a whole science and art behind temperament and there is no practical way to make all intervals pure on a keyboard instrument. Equal temperament is a compromise that makes all key signatures sound equally bad. There are other temperaments -- such as for Bach’s “Well-Tempered Clavier” -- that have different compromises. In general, it makes sense to match the temperament of your instrument to what the composer used when creating each piece of music -- if that’s known. Many modern organs and virtual pipe organs let you change temperament instantaneously. Obviously you can’t do that with the Trio’s 158 hand-tuned oscillators.
Let’s get back to my organ tuner. I generate the “top octave” frequencies using Direct Digital Synthesis (DDS), which is described in Building a Frequency Synthesiser by shabaz With DDS, you add a phase increment (PI) to a phase accumulator (PA), which is just a binary adder plus a register. The PA overflows (and wraps) at a frequency determined by the PI.
In my tuner, I add PI at an update rate of 25 MHz / 95 = 128,205 Hz. To get C7 = 4186 Hz, I need a phase increment of 4186 / 128,205 = 0.032,651. That is, if I add 0.032,651 to PA at 128,205 Hz for one second, PA will overflow past the decimal point 4186 times. I’ve shown these in decimal, but the arithmetic is really in binary and PA is a 20-bit binary fraction with the binary point at the left end of the number. PI is a 16-bit number 0.032651 * 2^20 = 34,237. If you add 34,237 to PA at 128,205 Hz for one second, the 20-bit PA overflows 4186 times.
If you look at my spreadsheet, I chose the 128,205 update rate so that the 16-bit PI values for the top octave are between 32,768 and 65,635 to maximize the use of PI bits. The error of using 16-bit numbers is very small, at most 9.3 PPM (parts per million). This is much less than your typical 100 PPM crystal oscillator accuracy.
In tuning theory, one subdivides a semitone into 100 steps which like semitones are equal ratios. This tiny ratio is called a cent and is the 100th root of the semitone ratio 1.059,463 or the 1200th root of 2, approximately 1.000,577,790. Each cent is an increase of 577.8 PPM. Note that my tuner’s worst case error of 9.3 PPM is much less than a cent, so you won’t be able to hear it.
With a hand-tuned oscillator like my Trio’s it’s pretty much impossible to get the tuning exactly right. In fact, it’s better to be a little off. If notes are too perfectly tuned, you can’t hear individual notes when they are played together. Instead you hear a single note with a different timbre. There’s a whole art to this which I don’t know much about yet, but I don’t need to worry with hand-tuned oscillators.
Push-Button Debounce Logic
Now that I’ve taken care of tuning theory, let’s take a look at my FPGA starting with the user interface. The Go Board has four push-buttons arranged in a 2 x 2 array. I use the left buttons to increment (top) and decrement (bottom) the current note one semitone at a time. I use the right buttons to increment and decrement by octaves.
Since these are buttons, they have to be debounced. Here’s an excellent ’blog about debouncing by Jack Gannsle. Basically, when you push a button, it bounces between Off and On for a while before settling at On. Similarly, when you release a button it bounces between On and Off for a while before settling at Off. If you don’t debounce the button it’s as if you pressed a button a random number of times instead of once.
So you need some kind of debouncing logic. In theory you need this logic for each button, but if you know only one button is going to be pressed at a time you can share it between all your buttons. Here’s the logic I use for my tuner. It’s written in GCHD (GalaxC for Hardware Design), part of my XXICC project and documented in The XXICC Anthology.
// Debounce one or more push-buttons, generating a single pulse when a button is pressed.
// Assume only one button is pressed at a time.
clock input clk, // Master clock, at least 1 KHz.
input update, // Single cycle update pulse approx 1 KHz.
input pressed, // A button is pressed (OR of multiple buttons).
output event, // Single cycle pulse indicating button was just pressed.
module {event = Debounce(clk, update, pressed)} =
{
reg k(4); // 4-bit counter, use MSb to detect that button was just pressed.
// On each update pulse, increment or decrement k, saturating at 15 or 0.
if clk rises then
if update then k =
k + (pressed & k != 15? 1: !pressed & k != 0? -1: 0);
// Button-press event occurs when MSb of k changes from 0 to 1.
event = update & pressed & k == 7;
},
I’ve been using this switch debounce technique design since 1977. It’s is basically a digital low-pass filter. A 4-bit counter k increments when a button is pressed (saturating at 15) and decrements when all buttons are released (saturating at 0). Like C and Verilog, GCHD has a conditional expression a? b: c which acts like a multiplexer: if a is TRUE, the expression’s value is b, otherwise it’s c.
When a button is first pressed, contact bounce causes pressed to alternate between 0 and 1, causing k to alternate between incrementing and decrementing, so k stays near 0. When the bouncing settles, k increments continuously up to 15. When k increments from 7 to 8 the logic reports the button press event. When the button is released you have similar bouncing, which causes the counter to stay near 15 until the bouncing settles.
This technique is quite versatile. For example, you can use time-multiplexing to share the logic across hundreds or thousands of switches. I’ve also used it to filter I2C inputs, which is required by the standard so that you don’t need to terminate the SDA and SCL lines. You can also implement the technique in software, using microcontroller pins to sample several switches or a switch matrix.
Counter k needs to be updated at approximately 1 KHz, which is the typical debounce rate. For my tuner, the master clk is 128,205 Hz. The update pulse goes high for one clk cycle approximately once per msec, and updates k to a new value depending on whether pressed is 1 (a button is pressed) or 0 (all buttons are released).
The event output goes high when the a button is pressed and k updates from 7 to 8. It’s the debounced button press event and pulses high for a single clk cycle.
Here’s a GCHD module that divides the 128,205 Hz clk to approximately 1 KHz:
output carry, // Carry out if K = 127.
module {carry = KHzCounter(clk)} =
{
reg K(7), // 7 bit divide-by-128 counter.
carry = K == 127;
if clk rises then K = K+1;
};
The carry output goes high for a clk cycle once per 128 clk cycles, giving us a little more than 1 KHz. KHzCounter shares the “clock input clk” declaration with module Debounce.
Now that I have reliable debounce logic, I use it with the four push-buttons: up, dn, up8, and dn8. This logic is in the “main program” of my tuner, which begins:
synthesize hardware (target = GoBoard, clk = 128205)
{
clock input clk; // 128.205 KHz Flavia clock.
...
The synthesize hardware statement create logic from GCHD code, with options for the target FPGA (in this case the Lattice iCE40 on the Nandland Go Board) and the master clock frequency.
Here’s the GCHD code that declares the 4 push-buttons and creates an instance of the Debounce module:
net KHz1; // 1 KHz pulse stream: single-cycle pulse each msec.
KHz1 = KHzCounter(clk); // Convert 128.205 KHz Flavia clock to 1 KHz pulse stream.
input {up, dn, up8, dn8}; // Push buttons.
net any = up | dn | up8 | dn8; // Any button is pressed.
net event; // Button-press event.
// Use single debouncer for all 4 buttons: assume only one is pressed at a time.
event = Debounce(clk, KHz1, any);
The update input of Debounce is set to KHz1, the 1 KHz pulse output of KHzCounter. The pressed input of Debounce is connected to any, which is 1 if any button is pressed. The output of Debounce is event, which pulses high for a single clk cycle when the button press is debounced.
Push-Button Auto-Repeat
The next part of the tuner code is for automatically repeating a button press if you hold down a button for 1.5 seconds. This is useful if you want to increment or decrement a note or octave many times. First, we convert the 1 KHz pulse stream to 16 Hz (which we’ll also use later in the tuner logic):
// Divide by 63 counter to convert 1 KHz to 16 Hz (approx).
reg J(6), // 6-bit divide-by-63 counter.
net Hz16 = KHz1 & (J & 62) == 62;
if clk rises then J = Hz16? 0: J + KHz1;
J is a 6-bit register that updates when KHz1 goes high for a single clk cycle once per msec. J counts from 0 through 62, so the logic divides by 63 which is the closest approximation to 16 Hz since we can’t divide by 62.5. The expression “(J & 62) == 62” saves a little bit of logic by only checking the top 5 bits of J. It’s equivalent to “J >= 62”, but the current version of GCHD doesn’t support “>=”.
The statement “J = Hz16? 0: J + KHz1” resets J if Hz16 pulses high, otherwise it sets J to J + KHz1, incrementing J each msec.
Next we divide 16 Hz by 8 to get 2 Hz, which we’ll use for 2 Hz auto-repeat and to detect that a button has been pressed for 1.5 sec:
// Divide by 8 counter to convert 16 Hz to 2 Hz. Reset if no buttons pressed.
reg K(3), // 3-bit divide-by-8 counter.
net Hz2 = Hz16 & K == 7;
if ~any then K = 0 else
if clk rises then K = K + Hz16;
If a button is pressed, 3-bit counter K increments when Hz16 pulses each 1/16th second. If no button is pressed, K asynchronously resets to 0. This way when a button is first pressed there is approximately a full 1/2 second before K first wraps from 7 to 0, pulsing Hz2. It could be as short as 7/16 second, but that’s close enough.
Asynchronous reset has priority over clocked updates.
Here’s the auto-repeat logic:
// Automatically increment or decrement at 2 Hz if hold button for 1.5 sec.
reg {auto1, auto2};
if ~any then auto1 = auto2 = 0 else
if clk rises then if Hz2 then {auto1 = TRUE; auto2 = auto1};
net auto = auto2 & Hz2; // Auto-inc/sec 2 Hz pulses.
If no button is pressed, flip-flops auto1 and auto2 asynchronously reset to 0. If a button is pressed and held, after 1/2 second auto1 is set and after 1 second auto2 is set. After 1.5 seconds signal auto pulses for one clock cycle every 1/2 second.
In summary, when you press a button the event signal pulses once about 10 msec later. If you hold down the button for 1.5 seconds, there will be an auto pulse every 1/2 second. The signals up, dn, up8, and dn8 indicate which button is pressed.
Updating the Current Note and Octave
Here is the code that declares registers for the current note and octave. 4-bit register n is the current note of the scale and has values 0 (C) through 11 (B). 4-bit register v is the current octave. I want the tuner to reset to middle C (C3), so n should reset to 0 and v should reset to 3.
// Note and octave counters.
reg n(4); // Up/down note counter: C, C#, D, ... B.
reg v(4); // Up/down octave counter: 0 - 8.
In the current version of XXICC, the power-on value of all iCE40 registers is 0. That’s fine for n, but wrong for v. As a work-around, I’ve added an enable flip-flop that initializes to 0 and goes high after one clk cycle. enable is then used as an asynchronous reset.
reg enable; // Enable goes high after one clk cycle.
if clk rises then enable = 1;
Here is the code that updates the current note n and octave v. If enable is 0, n resets asynchronously to 0 (C) and v resets to 3.
if !enable then {n = 0; v = 3} else // Async reset to Middle C.
if clk rises then
if event | auto then
{ // Increment note number if up is pressed: wrap from B (11) to C (0) if octave < 8.
// Decrement note number if down is pressed: wrap from C to B if octave > 0.
n = n + (up? (n != 11? +1: !v[3]? -11: 0):
dn? (n != 0? -1: v != 0? +11: 0): 0);
// Increment octave up to 8 if up8 is pressed or up is pressed and wrapping.
// Decrement octave down to 0 if down8 is pressed or down is pressed and wrapping.
v = v + ((up8 | up & n == 11) & !v[3]? +1:
(dn8 | dn & n == 0) & v != 0? -1: 0);
};
The logic updates n and/or v if a button has just been pressed (event pulses high) or a button has been held for 1.5 seconds or more (auto pulses high every 1/2 second).
Register n increments if button up is pressed, wrapping from B (11) to C (0). It stops incrementing at B8. Register n decrements if button dn is pressed, wrapping from C (0) to B (11). It stops decrementing at C0.
Octave register v increments if button up8 is pressed, stopping at 8. It also increments if button up is pressed and n wraps from B to C. v decrements if button dn8 is pressed, stopping at 0. It also decrements if button dn is pressed and n wraps from B to C.
Displaying the Current Note and Octave
The Go Board has a 2-digit non-multiplexed 7-segment display. The second digit is the octave number 0-8. Here’s my GCHD module for converting a 4-bit BCD digit into a 7-segment code. By convention, the segments are named A-G which have nothing to do with notes A-G.
// _A_
// F| |B
// |_G_|
// E| |C
// |_D_|
input X(4), // BCD digit.
output Y(7), // Seven-segment code “ABCDEFG”.
module {Y = SevenSeg(X)} =
{
Y = X == 0? 0b1111110: X == 1? 0b0110000:
X == 2? 0b1101101: X == 3? 0b1111001:
X == 4? 0b0110011: X == 5? 0b1011011:
X == 6? 0b1011111: X == 7? 0b1110000:
X == 8? 0b1111111: X == 9? 0b1111011: 0;
},
GCHD uses C-like notation 0b1101101 to represent a binary number.
I display the note letter in the first 7-segment digit, using upper case for A, C, E, F, G and lower case for B and D. I added a red LED to display “sharp”: for example, C with the LED On is C#. I was planning on using a decimal point, but they’re wired Off on the Go Board.
Here’s my GCHD module for converting a note number 0-11 into a 7-segment code, plus an extra bit for setting the “sharp” LED.
output Y(8), // Seven-segment code “#ABCDEFG”.
module {Y = SevenSegNote(X)} =
{
Y = X == 0? 0b0_1001110: X == 1? 0b1_1001110: // C, C#
X == 2? 0b0_0111101: X == 3? 0b1_0111101: // D, D#
X == 4? 0b0_1001111: X == 5? 0b0_1000111: // E, F
X == 6? 0b1_1000111: X == 7? 0b0_1011110: // F#, G
X == 8? 0b1_1011110: X == 9? 0b0_1110111: // G#, A
X == 10? 0b1_1110111: X == 11? 0b0_0011111: 0; // A#, B
},
GCHD follows the Verilog convention of allowing ‘_’ in numbers as separators, which are ignored.
The main program calls SevenSeg and SevenSegNote to display registers n and v. Outputs note and oct are the LED output signals.
ubyte output note; // 7-segment note LEDs (MSb = sharp).
note = SevenSegNote(n);
ubyte output oct; // 7-segment octave LEDs.
oct = SevenSeg(v);
We are now done with the tuner’s user interface. The rest is easy
Frequency Synthesis Logic
We described using DDS to generate notes earlier. The frequency synthesizer logic itself is pretty simple. First, we need a look-up table for the Top Octave phase increment of each note:
ushort output Y,
module {Y = TopOctave(X)} =
{ // All phase increments are between 32,768 and 65,535 inclusive.
Y = X == 0? 34_237: X == 1? 36_273: // C, C#
X == 2? 38_430: X == 3? 40_715: // D, D#
X == 4? 43_136: X == 5? 45_701: // E, F
X == 6? 48_418: X == 7? 51_297: // F#, G
X == 8? 54_348: X == 9? 57_579: // G#, A
X == 10? 61_003: /*X == 11?*/ 64_631; // A#, B
},
These are the values from my spreadsheet.
The main program calls the TopOctave module and sets its output to 16-bit net PI. Every clk cycle it adds PI to a 27-bit phase accumulator PA:
net PI(16); // 16-bit top-octave phase increment.
reg PA(27); // 27-bit phase accumulator.
PI = TopOctave(n);
if clk rises then PA = PA + PI;
Why 27 bits? Well, as described earlier the top-octave DDS uses a 20-bit PA: the 16-bit PI is actually divided by 16. The top octave is octave 7. To get octaves 0-6 we simply extend PA to 27 bits, and select one or more bits from PA for octave v. Octave 8 is special and I’ll get to it in a moment.
Here is the logic that selects the PA bits for octave v:
reg sel(4); // Octave v selects 4 MSbs of phase accumulator.
if clk rises then sel =
v == 0? PA[26:23]: v == 1? PA[25:22]: v == 2? PA[24:21]:
v == 3? PA[23:20]: v == 4? PA[22:19]: v == 5? PA[21:18]:
v == 6? PA[20:17]: v == 7? PA[19:16]: PA[18:15];
It actually selects 4 adjacent PA bits, which we use to produce a rudimentary sine wave. If v = 7, we select the top bits of the top octave, which are PA[19:16], i.e., the top 4 bits of a 20-bit PA. If v = 6, we go down an octave and select PA[20:17], cutting the synthesized frequency in half. Each lower octave cuts the frequency in half again. Octave 0 selects PA[26:23], which is 1/128th the frequency of octave 7.
Octave 8 selects PA[18:15], which is twice the frequency of octave 7. This works, but produces a signal with a more phase jitter. However, the Trio only goes up to C8 which has the least jitter in octave 8.
Now that we have selected the top 4 bits for octave v, we can generate a square wave and a sine wave. The square wave is simply the MSb of sel:
output square = sel[3]; // Square wave output.
output sin(3); // 3-bit sine wave output.
sin = Sine(sel);
For the sine wave I use a small look-up table:
output Y(3),
module {Y = Sine(X)} =
{
Y = X == 0? 4: X == 1? 5: X == 2? 6: X == 3? 7:
X == 4? 7: X == 5? 7: X == 6? 6: X == 7? 5:
X == 8? 4: X == 9? 3: X == 10? 2: X == 11? 1:
X == 12? 1: X == 13? 1: X == 14? 2: 3;
};
I output the sine wave over the Go Board’s VGA green output signal, which combines 3 bits using resistors to make a rudimentary digital-to-analog converter. I only use values 1-7 because I use a simple NPN transistor amplifier to drive a small speaker. I need 0.65 volts to turn on the NPN amplifier. Sine value 2 is the lowest value that turns on the NPN transistor: neither 0 nor 1 produces a high enough voltage to turn on the transistor, so they’re equivalent as far as the speaker is concerned. Using 0-7 would produce a distorted sine wave. Values 1-7 produce a nice symmetric wave.
Tuning Using an Oscilloscope
The tuner is now able to produce an accurate sine or square wave across the organ’s entire pitch range, with the note and octave selected by the Go Board’s push-buttons and displayed in the 7-segment LEDs. If you have an oscilloscope handy, you can now do the tuning.
To tune using a dual-trace ’scope, connect the lower trace to the tuner’s square wave output and trigger off it. You should get a nice clean, stable square wave which changes frequency as you select different notes and octaves.
Connect the upper trace to one of the organ’s oscillators. The ’scope probe should be high enough impedance that its presence doesn’t affect the oscillator frequency. The upper trace of the ’scope should have a nice square or sine wave, depending on the oscillator.
If the organ’s oscillator is exactly the same frequency as the selected note and octave the upper trace will be stationary. If it’s not the same frequency the upper trace will travel to the left or right at a speed determined by the mismatch. If the organ’s oscillator is sharp, the trace will travel left. If flat, it will travel right.
On the Trio, you can now turn the inductor clockwise or counter-clockwise to adjust the oscillator frequency until it matches the tuner’s square wave. Do this for all 158 Trio oscillators and you’re done
You can also do this with a single-trace ’scope if it has an external trigger input. You simply connect the tuner square wave output to that external trigger and use the single trace for the organ’s oscillator output. As with the dual-trace ’scope, the trace travels left if the oscillator is sharp and right if the oscillator is flat.
Tuning Using the Difference Display
The tuner also has a built-in display that shows the difference in frequency between the selected reference pitch and an organ oscillator output which is connected to an FPGA input. The difference display uses the Go Board’s four discrete LEDs below the 7-segment display. If the organ oscillator is sharp, the LEDs travel to the right telling you to turn the inductor clockwise. If the organ oscillator is flat, the LEDs travel left telling you to turn the inductor counter-clockwise. If the oscillator is tuned perfectly, you will see a single LED or a pair of adjacent LEDs turned on. If the tuning is close, that single LED moves slowly to the left or right, turning on two adjacent LEDs as it moves from one position to another.
This sort of moving LED display is common with electronic tuners. It’s actually based on the old electro-mechanical stroboscopic tuners that have 12 spinning disks with binary patterns on them. Each wheel spins at a rotational speed that matches one of the 12 notes. An audio input causes a strobe light to flash. If it matches one of the 12 pitches, the disk appears to stop. If the audio input is near a pitch, one of the wheels appears to turn slowly clockwise or counter-clockwise -- the rate depends on how sharp or flat the input is.
The FPGA logic for my tuner is in the main program. First, we sample input x from the organ using clk and use three flip-flops to prevent metastability and to filter x using a majority filter:
// Input signal and 4-bit difference counter are clocked at 128.205 KHz.
input x;
reg {x1, x2, x3, xprev}; // Synchronization flip-flops and majority filtering.
net xmaj = x1 & x2 | x2 & x3 | x1 & x3;
if clk rises then {x1 = x; x2 = x1; x3 = x2; xprev = xmaj};
net dx = xmaj != xprev; // Filtered x changed.
Signal dx indicates that filtered x has changed from the previous clk cycle, which occurs twice per input cycle. We also have signal dr which indicates that the reference square wave has changed from the previous clk:
reg prev; // Previous value of reference signal square.
if clk rises then prev = square;
net dr = square != prev; // Reference signal changed.
Our basic approach is to have a 2-bit display register disp which increments when dx occurs and decrements when dr occurs. disp is unchanged if neither occurs or both occur simultaneously.
disp turns on one of the four LEDs. If dx and dr have the same frequency, disp alternates between two values and only two adjacent LEDs will light. (The tuner considers the left-most and right-most LEDs to be adjacent.) If dx and dr are close, disp will slowly change to lower or higher values and you’ll see the LEDs move left or right.
If dx and dr are far apart, disp will change so quickly that all the LEDs will appear to be lit and you won’t be able to tell whether the oscillator is sharp or flat.
To fix this, we limit the speed that disp can change by only updating it every 16th of a second. You need four of these updates to move the LED four positions, so the LED display cycles at a maximum rate of 4 Hz. This is slow enough to see which way the LED is moving.
The tuner has a 4-bit register diff that records the difference between dx and dr over 1/16 second. The logic is in the main program:
reg diff(4); // 4-bit difference counter, cleared every 1/16 sec.
if clk rises then diff = // Saturates at +7 and -8.
Hz16? (dx & ~dr? +1: ~dx & dr? -1: 0):
diff + (dx & ~dr & diff != 7? +1: ~dx & dr & diff != 8? -1: 0);
Every clk cycle diff is updated to a new value. If Hz16 = 1, we have reached the end of a 1/16 sec interval and we “clear” diff by setting it to +1 if dx = 1 and dr = 0, -1 if dr = 1 and dx = 0, and to 0 in all other cases.
If Hz16 is 0, we increment diff if dx = 1 and dr = 0 and decrement diff if dr = 1 and dx = 0. We do not increment past 7 or decrement below 8. diff is a 2’s complement number, so 8-15 are negative values. In all other cases diff keeps its current value. This includes clk cycles when dx and dr occur simultaneously.
At the end of each 1/16 interval, diff is positive if there have been more dx than dr. diff is negative (MSb is set) if there have been more dr than dx. diff is 0 if there have been the same number of dx and dr over the interval.
Here’s the logic that updates the display register disp described earlier:
reg disp(2); // 2-bit display counter.
if clk rises then
if Hz16 then disp = disp + (diff == 0? 0: !diff[3]? +1: -1);
disp is updated every 1/16 second, i.e., when the Hz16 pulses high. If diff = 0, disp keeps its current value. Otherwise disp increments or decrements based on the sign bit of diff. Note that all registers update simultaneously when clk rises, so we update disp using the current value of diff and simultaneously “clear” diff as described earlier.
All that’s left is to turn on one of LED1 through LED4, based on the current value of disp:
output {LED1 = disp == 0, LED2 = disp == 1,
LED3 = disp == 2, LED4 = disp == 3};
If you’d rather have the LEDs move in the opposite direction, replace this code with:
output {LED1 = disp == 3, LED2 = disp == 2,
LED3 = disp == 1, LED4 = disp == 0};
Analog Input Circuitry
The FPGA’s oscillator input x needs to be a square wave between 0 and 3.3V. Since each organ is different, I won’t show the circuits I use, but I’ll describe them in general terms. The Trio’s square wave oscillators vary between about 1V and 10 V, so obviously I cannot connect one directly to an FPGA input. Plus, the drive is asymetric: the oscillator pulls down through a transistor and pulls up through a large resistor. I made a small adapter circuit using a PNP transistor amplifier pulled up to 3.3V. It worked quite well except for one thing: my pull-up was a little too strong and the circuit affected the oscillator frequency. I should have used a comparator IC with a high input impedance. I ended up re-tuning with an oscilloscope, which worked fine.
The Trio’s sine wave oscillators are very analog and I didn’t want to connect any circuits to them. Instead, there’s a nice op amp on the Tibia preamplifier board with a nice low-impedance output. Unfortunately, it’s a pretty low amplitide signal that’s between +0.25V and -0.25V. I ended up using my PNP amplifier with some diodes and a trim pot to set the bias. This amplified the 500 mV sine wave into a nice +3V square wave.
Another difference between the Trio sine and square wave oscillators is that the square wave oscillators operate all the time, whereas the since wave oscillators only operate when a key is pressed. I used a jumper and a resistor to mimic pressing a key to turn on each oscillator. Some of the notes didn’t have a good place to connect the jumper, so for those I had to go around to the front of the Trio and press a key, using a toothpick to wedge it on.
Results
I’m quite happy with the way the Trio tuned up. I think it’s as good as can be expected since it’s tricky to adjust those inductors, and with equal temperament you’re always somewhat out of tune.
I’m also quite happy with my FPGA software. This was by far the most complex FPGA design I’ve done using XXICC. I only found one bug, which was trivial and easily patched. GCHD synthesis could be better and I plan to work on that in the future.
This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit creativecommons.org/licenses/by-sa/3.0/. No warranty is expressed or implied. XXICC, GalaxC, GCHD, and Flavia are trademarks of John F. Beetem.
Top Comments