Wiblocks --- NB1A DAC

NB1A Numerically Controlled Oscillator (NCO)

Using a counter timer interrupt the NB1A can be programmed to function as a four-channel numerically controlled oscillator. This example describes how to generate four independent sine waves using the NB1A. The DAC codes for a single cycle of a sine wave are stored in an array. An interrupt routine is used to trigger the periodic update of the DAC channels.

Wavetable

To discretely generate a waveform samples of the waveform are periodically output using a DAC. Each sample represents the value of the waveform at a specific phase of a single cycle. To determine which sample is to be output a phase accumulator is used. The phase accumulator is incremented periodically and a wavetable address is calculated. Increasing the value of the phase increment increases the frequency of the output waveform. Decreasing the value decreases the output waveform frequency.

The frequency is determined by the formula --
frequency = sample rate / number of samples per cycle
The size of the wavetable and the number of bits per sample is determined by the acceptable signal-to-error noise ratio (SNR). In this example the wavetable contains 64 bytes (one byte for each sample). The DAC on the NB1A is an 8 bit DAC. The SNR is 30dB. If a table size of 2048 bytes is used then the SNR would be 54.7 (Moore 1988).

For this example a sample rate of 3750Hz was chosen. If all 64 samples are output at the sample rate the frequency will be ≈ 60Hz (3750/64). This corresponds to a phase increment of one. Every 266μS the phase accumulator is incremented by one and a sample is output. For higher output frequencies the phase increment would be greater than one and samples would be skipped. For lower frequencies the phase increment would be less than one and samples would be repeated. To determine the phase increment required to generate a specific frequency --
phase increment = frequency * sine table length / sample rate

Wavetable Optimization for Sinusoidal Waveforms

For this example the waveform is sinusoidal and the wavetable contains one full cycle of the waveform. Using the half-wave symmetry of the sinewave the size of the wavetable could be reduced by a factor of two. When the phase-accumulator is on the second half of the waveform the values output would be negative (with respect to the mid-scale of the DAC). Using the quarter-wave symmetry of the sinewave the size of the wavetable could be reduced by a factor of four. This would require decrementing the phase-accumulator during the second and fourth quarters of the cycle (and changing signs for the third and fourth quarter). Exploiting the sine-wave symmetries would make the code more difficult to modify for other waveforms. The trade-off is memory size for code extensibility.

NCO Data Structure

The heart of this NCO implementation is the structure that holds the phase accumulator, the phase increment and the control byte for the DAC (below).
struct nco_struct {
  union {
    struct {
      unsigned UNUSED1 : 16;
      unsigned UNUSED2 : 9;
      unsigned round   : 1;
      unsigned address : 6;
    } acc_bits;
    unsigned long acc_long;
  } acc;
  unsigned long inc;
  unsigned char control; // control byte for the TLV5620
};
The phase accumulator determines the current memory location in the wavetable. In this example the wavetable is 64 bytes so only the top six bits are required for the address (acc.acc_bits.address). The seventh bit (acc.acc_bits.round) is used to round up the address value (use of the rounding bit in the phase accumulator improves the SNR by ≈ 6dB (Moore 1988) . The lower 23 bits are used in the phase calculation but are ignored in the address calculation. If the wavetable size is changed then the size of acc.acc_bits.address, acc.acc_bits.UNUSED2 and acc.acc_bits.UNUSED1 can be changed.

Phase Accumulator and DAC Updates

Outputting waveforms consists of periodically updating the phase-accumulators and outputting the DAC codes (below). The phase-accumulator value is updated by adding the phase-increment. The address of the DAC code to be output is contained in the address bits. If the rounding bit is set then the address is incremented by one. If the address is incremented then it needs to be masked to prevent generating an address to a non-existent wavetable location.
.
.
#define nco_addr(i)  (nco[i].acc.acc_bits.address)
#define nco_round(i) (nco[i].acc.acc_bits.round)
.
.
unsigned char addr;
.
.
for (i = 0; i < NCO_NUM_CHS; i++) {
  nco_inc_phase_acc(i);
  addr = nco_addr(i);
  if (nco_round(i)) {
    addr += 1;
    addr &= NCO_ADDR_MASK;
  }
  // 
  // update DAC channel i
  // 
  .
  .
}

NCO Output

The oscilloscope picture (right) shows the output of two channels of the NCO. Channel one is set to a frequency of 100Hz, channel two is set to 60Hz. Although only two channels could be shown on the oscilloscope all four channels are running. No filtering was added to the DAC outputs.

With the sample rate set to 3750Hz and a wavetable size of 64 samples a 100Hz output contains 37 samples per cycle. The 60Hz output contains 58 samples per cycle.

NB1A NCO Example

File ../../docs/app-files/nb1a_nco_r0.pde could not be opened for input

References

Snell, John 1988 "Design of a Digital Oscillator That Will Generate up to 256 Low-Distortion Sine Waves in Real Time" Foundations of Computer Music. Cambridge, Mass.: MIT Press

Moore, F. Richard 1988 "Table Lookup Noise for Sinusoidal Digital Oscillators" Foundations of Computer Music. Cambridge, Mass.: MIT Press