Waveforms, Frequency and Timing

How the signal is made

Both channels use direct digital synthesis. A 32-bit phase accumulator advances by a fixed increment on every timer tick; the top bits of the accumulator index a wavetable; the table value goes out to a 16-bit R-2R ladder DAC driven through daisy-chained 74HC595 shift registers over SPI at 8 MHz — one 16-bit word every 2 µs.

The DAC output is a staircase. A Sallen-Key low-pass filter after the DAC removes the sampling steps and leaves a continuous waveform.

Phase accumulator32-bit
Tick rate40 kHz single channel, 20 kHz per channel in Dual
Underlying frequency resolution9.31 µHz
Frequency setting resolution1 Hz (the command takes whole hertz)

The 9.31 µHz figure is what the accumulator can theoretically resolve at 40 kHz. You cannot ask for it — the frequency command takes an integer number of hertz — but it is why the synthesised frequency does not drift or quantise audibly across the range.

Frequency range

ModeRange
Single channel (Modes 0, 3, or Dual with one channel disabled)1 Hz - 20 kHz
Dual with both channels active1 Hz - 10 kHz per channel

The halving in Dual mode is not a policy decision. One timer interrupt services both synthesisers, so each channel is updated at 20 kSa/s instead of 40 kSa/s, and the usable output frequency scales with it.

20 kHz is the honest ceiling of this instrument. It is a 16 MHz 8-bit microcontroller computing every sample in software. If you need more bandwidth, this is not the tool.

The five built-in waveforms

IndexWaveform
0Sine
1Square
2Triangle
3Sawtooth
4Haversine
5Custom (user-uploaded arbitrary table)

Index 5 selects whichever arbitrary waveform you have uploaded to that channel. CH1 and CH2 hold separate tables and can each be running a different custom shape at the same time. See Arbitrary Waveforms.

Duty cycle

Range1 - 99%
Step1%

Duty cycle affects the square wave only. Every other shape ignores it entirely, so setting it while a sine is selected is a harmless no-op that takes effect if you later switch to square. Each channel has its own duty-cycle setting.

Phase

Range0 - 359°
Step

Phase is set per channel. It is most useful in Dual mode, where the two synthesisers share a timebase and a phase offset between them is therefore stable — set both channels to the same frequency and use phase to produce a fixed relationship between them.

Frequency modulation

Both channels support FM, each modulating its own carrier.

ParameterLimit
Carrier frequency≤ 10 kHz
Modulating frequency≤ 1 kHz
Peak deviation≤ 5 kHz

The modulating waveform can be sine, square, triangle or sawtooth. Square gives you frequency-shift keying between two discrete frequencies; sawtooth gives you a repeating linear chirp.

Note that the FM carrier ceiling of 10 kHz is lower than the plain carrier ceiling of 20 kHz. That is deliberate: deviation is added to the carrier on every sample, and the sum has to stay inside what the synthesiser can produce. The app clamps the carrier to 10 kHz before sending it.

FM can be toggled on and off without reconfiguring the carrier, modulating frequency and deviation, so you can compare modulated and unmodulated output without re-entering the parameters.

Channel-specific command restrictions

The firmware guards CH1’s own-waveform commands behind the active channel. If CH2 is the active channel and you send a CH1 frequency, waveform, phase or duty command, the reply is -3 — “that control is for CH1 only, select CH1 first”. CH2’s equivalents carry no such guard. The app handles this for you by selecting the channel before sending; it only matters if you are driving the board by hand over the protocol.

Updated · Full project report (PDF)