Monday, September 1, 2014

Arduino Timers

In taking my DDS code to the next level, I needed to dig into Arduino timers and gain a fuller understanding of how they work.  The ATMega328 has three timers known as Timer 0, Timer 1 and Timer 2.  Each timer has two outputs and corresponding output compare registers that determine when the output is toggled.

Each of the timers has a prescaler that generates the timer clock by dividing the system clock (16 MHz) by a selectable value 1, 8, 63, 256 or 1024.  Timer 2 has a different set of prescale values from the other timers.  Each timer output has a corresponding output compare register that can be used to generate interrupts.

Each timer has a select-able mode.  The PWM modes are "Fast PWM" and "Phase Correct PWM".  Each timer can run from 0 to 255 or from 0 to a fixed value.  Timer 1 is a 16 bit counter that has additional modes to support timer values up to 16 bits.  Each output may optionally be inverted.

A timer, once enabled will run and can generate interrupts on overflow and/or matches against either output compare register.  Each timer has a set of registers that control the behavior of that timer.

TCCRnA, TCCRnB: Timer/Counter Control Registers - Holds the main control bits for the timer.  It should be noted that the "A" and "B" on the end does not correspond to the outputs A and B.  These registers hold several groups of bits:

  • WGM: Waveform Generation Mode - These bits that are split between TCCRnA and TCCRnB control the overall mode of the timer.
  • CS: Clock Select - These bits control the clock prescaler.
  • COMnA, COMnB: Compare Match Output - Enable, disable, invert output A or B respectively.

OCRnA, OCRnB: Output Compare Register - Sets the levels at whch outputs A or B respectively will be affected.  When the timer value matches the register value, the corresponding output will be modified as specified by the mode.

Timer Pins
It is at best confusing which timer controls which pin, not to mention that it varies between different processors.  For the Mega328, the following table describes the Output Compare register, silk screened pin number on the Arduino board, pin number on the chip and name of the pin.

Timer OCR  Board pin Chip pin Name 
0  OC0A 6 12 PD6 
OC0B 5 11 PD5 
1 OC1A 9      15   PB1 
OC1B   10      16   PB2 
2 OC2A 11 17   PB3 
      OC2B 3 5   PD3

Timers are initialized by the Arduino to set the prescaler to divide the clock by 64.  Timer 0 is set to Fast PWM while Timer 1 and Timer 2 are initialized to Phase Correct PWM.

Internally Arduino uses Timer 0 to implement the millis() and delay() library functions.  Changing the frequency of this timer will affect these functions.

There are a couple of modes for each timer that will be discussed separately.

Fast PWM

This is the simplest PWM (pulse-width-modulated) mode.  The timer repeatedly counts from 0 to 255.  The timer output turns on when the timer is at 0 and turns off when the timer matches the output compare register value.  The higher the output compare register value, the higher the duty cycle.  Both timer outputs will have the same frequency but can have the different duty cycles as set by OCRnA or OCRnB.

The output frequency for an 8 bit timer is determined by the system clock (16 MHz) divided by the currently set prescaler value divided by 256.  The last division by 256 is because the timer runs from 0 to 255 before it overflows.  For example, assume a prescaler set to divide by 64:

Frequency = 16 MHz / 64 / 256 = 16000000 / 64 / 256 = 976.5625 Hz
Duty Cycle Output A = OCRnA+1 / 256.
Duty Cycle Output B = OCRnB+1 / 256

As can be seen from the duty cycle calculation, Fast PWM holds the output high one cycle longer than the value in the Compare Match Output register OCRnA/OCRnB.  The motivation behind this is that for Fast PWM counting to 255, the duty cycle can be from 0 to 256 cycles.  The output compare register however can only hold the values 0 to 255.  The solution is to keep the output high for OCR+1 cycles. so an OCR value of 255 is 100% duty cycle, but an OCR value of 0 is a 1/256% duty cycle.  This is in contrast to Phase Correct PWM where an OCR value of 0 is a 0% duty cycle and 255 is a 100% duty cycle.

Phase Correct PWM

In this mode the timer counts from 0 to 255 and then back down to 0.  The output turns off as the timer hits the OCR value on the way up and turns it back on at the OCR value on the way back down.  This results in a more symmetrical output, the frequency of which will be 1/2 the value for Fast PWM mode because the timer runs both directions.

Again assuming a prescaler value of 64:

Frequency = 16 MHz / 64 / 255 = 16000000 / 64 / 255 / 2 = 490.196 Hz
Duty Cycle Output A = OCRnA / 256.
Duty Cycle Output B = OCRnB / 256

Notice that frequency is divided by 255 instead of 256 and that the duty cycle calculations do not add one as seen above.

This is important

Suppose that a timer is set to Fast PWM mode and is set up to count to an OCRnA value of 3.  In this case the timer will take the values 012301230123...  Note that there are 4 clock cycles in each timer cycle.  Thus, the frequency will be divided by 4.  The duty cycle will be a multiple of 25% (1/4) since the output can be high for 0, 1, 2, 3, or 4 cycles out of the four.  Similarly, if the timer counts up to 255, there will be 256 clock cycles in each timer cycle and the duty cycle will be a multiple of 1/256.  In other words, Fast PWM divides by N+1 where N is the maximum timer value (either OCRnA or 255).

In the case of Phase Correct PWM mode and the same OCRnA value of 3, the timer values will be 012321012321...  There are six clock cycles in each timer cycle (012321).  Therefore the frequency will be divided by 6 in this case and the duty cycle will be a multiple of 33% since the output can be high for 0, 2, 4, or 5 of the 6 clock cycles.  Again, if the timer instead counts up to 255 and back down, there will be 510 clock cycles in each timer cycle and the duty cycle will be a multiple of 1/255.  In other words, phase-correct PWM divides by 2N where N is the maximum timer value.

Tips

You need to both enable a pin for output and enable the PWM mode on the pin in order to get any output.

Different timers use the control bits and prescaler differently.  Be sure to check the datasheet for the processor in use to know the appropriate settings for the timer.

Some combination of bits do not work together.  For example, toggle mode does not work with Fast PWM to 255 or with output B.

Be sure you have correctly set the necessary bits in the correct control register.

Check that you are using the correct output pins for the given timer on a given processor.


Sunday, August 24, 2014

Corrections to DDS code

Many thanks to my friend Eldon, WA0UWH who pointed out the fact that my 0-255 amplitude table in my previous post has some 256 values in it.  Oh sigh...  I neglected to take into account that the range of sine values is -1 to +1 inclusive.  I added a generic mapping function to handle the mapping correctly.  I have updated my previous post with the changes.  This also fixed the gliches seen in my integrated output.  See the last scope trace screen shot.  I have updated this photograph as well, so all evidence of temporary insanity has been removed...

Thanks Eldon!

Saturday, August 23, 2014

Direct Digital Synthesis (DDS)

UPDATED

With all the recent work on software for the Si570 device, this got me thinking a bit about the process of direct digital synthesis.  I went back and read again a great tutorial on the topic by Analog Devices MT-085 Fundamentals of Direct Digital Synthesis and decided to experiment with a software DDS using the Arduino as a test platform.

Basically the architecture of a DDS system has a stable clock driving a look up of sinusoidal information where one or more cycles of a sine wave (or for that matter, any other arbitrary waveform) are fed in sequence to a digital to analog converter to produce the final output.  Each of these look ups can be stored in a read-only memory and the value fetched represents the corresponding digital amplitude of the signal at each clock tick.

For my experimentation, I will construct a sinusoidal waveform using 8 bit data representing the amplitude of the signal at each clock tick.  To construct the table of sinusoidal amplitude information, I will retrieve a value from 0-255 which will map to a signal where the sinusoidal waveform crosses zero at the middle of this range (e.g. 128) and 256 samples for one period of the waveform.

Each address in the look up table corresponds to a phase point on the sine wave from 0 to 360 degrees.  In reality only data for 90 degrees (one quadrant of a circle) would be required as the quadrant is indicated by the two most significant bits.  The look up table contains the corresponding digital amplitude information for one complete cycle of a sine wave used to drive a DAC (digital to analogue converter).

If the look up table contained an entry for each bit of a 32 bit index into the table, there would be 2^32 output values before the index overflowed.  A sine wave thus constructed would have a frequency equal to the input clock frequency divided by 2^32 as it would take that many data points to reconstruct the sine wave.  Let's call this the M=1 case (one increment per clock tick).

If we increment through the table a little faster, say M=2 then the table index will roll over twice as fast and the output frequency would be doubled.  For an n-bit index (in DDS systems this is called a phase accumulator) there are 2^n possible phase points (data points in our look up table).  Let's call M the amount that the phase accumulator (index) is incremented on each clock cycle.  If fc is the clock frequency, then the frequency of the output sine wave is determined by the following formulae, also known as the DDS tuning equation.

    fc = (M * fc) / (2^n)

The frequency resolution of a DDS system is equal to fc/2^n.  If n = 32, this represents a resolution greater than one part in four billion. (2^32 = 4,294,967,296).  This is rather impractical and in general unnecessary.  In most DDS systems not all of the bits of the phase accumulator are used in the look up table and only the upper (most significant) bits are used.  This can significantly reduce the size of the look up table without affecting the frequency resolution.  This truncation of the phase does however add a small amount of phase noise to the final output.

There is a lot more information in the reference I mention above (MT-085) and well worth a read by anyone interested in DDS technology.

The first task at hand is to construct a table of 256 sine values representing the amplitude of the sinusoid for one sine period.  I wrote a little C# application to generate the necessary data structure text:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenSineData
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("PROGMEM prog_uchar sine256[] = ");
      Console.WriteLine("{");
      Console.Write("  ");

      for (int i = 0; i < 256; i++)
      {
        double angle = 360.0 / 256.0 * Convert.ToDouble(i);
        double radians = angle * Math.PI / 180;
        double sin = Math.Sin(radians);
        
        // Map range from -1 to +1 to 0 to 255
        int amplitude = Convert.ToInt32(map(sin, -1, 1, 0, 255));
        Console.Write(amplitude.ToString() + (i != 255 ? "," : ""));
        if (i > 0 && (i+1) % 16 == 0)
        {
          Console.WriteLine();
          Console.Write("  ");
        }
      }
      Console.WriteLine("};");
    }

    // Map from one range to another
    static double map(double x, double in_min,
                      double in_max, double out_min, double out_max)
    {
      return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
    }
  }
}

The output of this application can be pasted into my Arduino code producing a program memory-based lookup table.

PROGMEM prog_uchar sine256[] =
{
  128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,
  176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
  218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,
  245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
  255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246,
  245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
  218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179,
  176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
  128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82,
  79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40,
  37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11,
  10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,
  0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,
  10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35,
  37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76,
  79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124
  };

Importing this data into Excel and graphing it, we find a nice sinusoidal waveform with zero crossings at the value 128 and all amplitude values between 0 and 255 using only 256 data points and 8 bit amplitude values.



So far, so good...

To compute the necessary tuning word for the DDS for the desired output frequency:

  M = (2^32) * desired_frequency / clock_frequency

For my purposes, I intend to use a clock_frequency of approximately 32kHz by dividing down the Arduino master clock of 16MHz.  I will use a timer interrupt at 32kHz and a 32 bit phase accumulator.  The upper 8 bits will be used as the index into the above sine wave table used to drive the Arduino PWM DAC.  Of course a low pass filter will be required to remove high frequency components of the audio output.

Ok, so now we put together a little Arduino test application to pull all these concepts together.  I am using an ATMega2560 for my testing but any Arduino would work so long as the pin differences are taken into account between the platforms.  More on this later.


Here is my DDSTest sketch:

// Sine wave generator using DDS techniques
// Jeff Whitlatch - ko7m

#include "avr/pgmspace.h"

// Single period sine wave table.
// Amplitudes are 0-255 with 128 as zero crossing.  256 samples per period.
PROGMEM prog_uchar sine256[] =
{
  128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,
  176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
  218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,
  245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
  255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246,
  245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
  218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179,
  176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
  128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82,
  79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40,
  37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11,
  10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,
  0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,
  10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35,
  37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76,
  79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124
  };

// Useful macros for setting and resetting bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

// DDS frequency and reference clock
double dds_frequency = 0.0;
const double ref_frequency = (16000000/500);

// These must all be marked as volatile as they are used in an interrupt service routine
volatile byte sine_table_index;
volatile uint32_t phase_accumulator;
volatile uint32_t tuning_word;

void setup()
{
  // PWM output for timer2 is pin 10 on the ATMega2560
  // If you use an ATMega328 (such as the UNO) you need to make this pin 11
  // See spreadsheet here
  pinMode(10, OUTPUT);      // Timer 2 PWM output on mega256 is pin 10
  
  // Set up timer2 to a phase correct 32kHz clock
  timer2Setup();

  // disable interrupts to avoid timing distortion
  cbi (TIMSK0,TOIE0);    // Disable timer 0.  Breaks the delay() function
  sbi (TIMSK2,TOIE2);    // Enable timer 2.

  // Set up initial DDS frequency and calculate the timing word
  dds_frequency = 1000;
  tuning_word = pow(2,32) * dds_frequency / ref_frequency;
}

// Nothing to do here.  Everything is interrupt driven
void loop()
{
}

// Setup timer2 with prescaler = 1, PWM mode to phase correct PWM
// See the ATMega2560 datasheet for all the gory details
void timer2Setup()
{
  // Clock prescaler = 1
  sbi (TCCR2B, CS20);      // 001 = no prescaling
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Phase Correct PWM
  cbi (TCCR2A, COM2A0);    // 10 = clear OC2A compare match
  sbi (TCCR2A, COM2A1);

  // Mode 1
  sbi (TCCR2A, WGM20);     // See table 20-8 in datasheet
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
}

// Timer 2 interrupt service routine (ISR) is used to generate
// the timebase reference clock for the DDS generator at 32kHz.
ISR(TIMER2_OVF_vect)
{
  // Update phase accumulator and extract the sine table index from it
  phase_accumulator += tuning_word;
  sine_table_index = phase_accumulator >> 24;  // Use upper 8 bits as index
  
  // Set current amplitude value for the sine wave being constructed.
  OCR2A = pgm_read_byte_near(sine256 + sine_table_index); 

}

Connecting a powered speaker to pin 10 allows me to hear the 1kHz tone.  The PWM signal on pin 10 looks like this, though when this picture was taken, a 1.5kHz tone was being generated.





So, far so good...  A low pass filter will be required or at least some level of integration to turn this into a sine wave.  Since we are sampling at 32kHz, Nyquist says that the highest frequency we can produce is 16kHz.  So by setting a simple RC low pass filter cutoff frequency near this frequency should be adequate.  I will choose 15kHz for simplicity.

  cutoff frequency = 1/ (2 * pi * R * C)

Using a 1000 ohm resistor for R:

  15000 = 1/(2000 * pi * C)
  C = 1/(30000000 * pi)
  C = 1.06103295e-8 farad or 0.0106103295 microfarads.  (.01 uF will do thanks...)

The following is the result using this simple integrator:





Fun!  Ok, so next installment, I hope to do something useful with all this...

More to come...

Monday, August 11, 2014

Si570 Arduino Shield PCB is off to be manufactured

I finished up the Si570 shield for the Arduino today and sent the gerber files off the the fab house today.  They will be on the panel to be produced on 13 Aug, so hopefully I will have some prototype PCBs back soon.



Update: My prototype boards shipped yesterday (Friday) so hopefully they will be in hand by Monday.

Sunday, August 10, 2014

Si570 shield progress

I have spent some time today working on my Si570 Arduino shield.  A few changes:
  1. Added a 3.3V regulator on the board with a separate power input (9-12v) as with dual Si570 devices on the board, I don't want to draw that current from the Arduino regulator.
  2. Increased the trace size for the 3V3 power rail off the regulator.
  3. Changed the SMA connector footprint to be appropriate for a much more narrow connector.
  4. I2C multiplexor chip used to drive multiple (up to 4) I2C buses optionally at different logic levels.  I drive the multiplexor at 5V from the Arduino.  Each Si570 is on its own 3V3 bus and two extra busses are available.  I included the 3V3 OLED display on the first Si570 bus.
  5. Hand routing of the mess made by the Eagle auto-router.
To be done:
  1. Replace reset button with one that is actually available.
  2. Rework the SMA connector footprint a little more as it is still marginally too wide.
A lot of discussion was passed back and forth regarding the use of separate connections for the various periphery that will connect this shield to the Minima board.  Most folks suggested that I just use the normal Arduino connectors along the edges for all off-board connections.  I have provided those connections for those that choose to connect in this way or decide to stack another shield on top of the Si570 shield.

I however also included individual connections for individual periphery (encoder, paddles, buttons, PTT, etc.) so that a cable with a ground and signal lines can be connected as a single connector without having to worry about multiplexing ground lines, etc.

Using the I2C multiplexor will require a change to my Si570 driver and OLED driver.  5V I2C displays will not require any code changes as they will be connected directly to the 5V Arduino I2C bus.

Here is a quick screen shot of the board.  It is pretty close to being ready to send off the be manufactured.



I am hoping to get these off to be created this week.

Monday, August 4, 2014

Bug found in Si570 code for Minima

A problem has been discovered in my Si570 code that implements 1Hz tuning.  The code was unnecessarily resetting the DCO on every update when tuning down in frequency.  While setting the frequency correctly, the error was causing the DCO to restart on every increment in frequency producing an audible click in the Minima.  The frequency was being set correctly, just clicking the receiver when it was not necessary.

The error was two-fold.  Firstly, I failed to reserve sufficient bits for the 3500 ppm calculation.  Secondarily, the Arduino absolute value function (abs()) is very quirky and it was just simpler to remove the function from my code.

Lastly, I modified the Si570 code to only calculate the 3500 ppm offset whenever the DCO centre frequency was changed rather than on every frequency change in order to gain a slight performance improvement.

The changes will be posted to my GitHub Si570 repository as soon as I am able.  Meanwhile, here are the changes for the adventurous among us that want to take on the change manually.

In Si570.h, I added a member variable to the Si570 class to hold the calculated 3500 ppm value for the current center frequency.

class Si570
{
public:
  Si570(uint8_t i2c_address, uint32_t calibration_frequency);
  Si570_Status setFrequency(uint32_t newfreq);
  void debugSi570();

  Si570_Status status;

private:
  uint8_t i2c_address;
  uint8_t dco_reg[13];
  uint32_t f_center;
  uint32_t frequency;
  uint16_t hs, n1;
  uint32_t freq_xtal;
  uint64_t fdco;
  uint64_t rfreq;
  uint32_t max_delta;

  uint8_t i2c_read(uint8_t reg_address);
  int i2c_read(uint8_t reg_address, uint8_t *output, uint8_t length);

  void i2c_write(uint8_t reg_address, uint8_t data);
  int i2c_write(uint8_t reg_address, uint8_t *data, uint8_t length);

  bool read_si570();
  void write_si570();
  void qwrite_si570();

  uint8_t getHSDIV();
  uint8_t getN1();
  uint64_t getRFREQ();

  void setRFREQ(uint32_t fnew);
  int findDivisors(uint32_t f);

};

Secondarily, I modified Si570.cpp to initialize max_delta when the Si570 object is constructed in Si570::Si570.

  // We are about the reset the Si570, so set the current and center frequency to the calibration frequency.
  f_center = frequency = calibration_frequency;

  max_delta = ((uint64_t) f_center * 10035LL / 10000LL) - f_center;

Lastly,  I modified the setFrequency function to remove the quirky Arduino abs() function and to set the max_delta value only when it changes.

// Set the Si570 frequency
Si570_Status Si570::setFrequency(uint32_t newfreq) 
{
  // If the current frequency has not changed, we are done
  if (frequency == newfreq)
    return status;

  // Check how far we have moved the frequency
  uint32_t delta_freq = newfreq < f_center ? f_center - newfreq : newfreq - f_center;

  // If the jump is small enough, we don't have to fiddle with the dividers
  if (delta_freq < max_delta) 
  {
    setRFREQ(newfreq);
    frequency = newfreq;
    qwrite_si570();
  }
  else 
  {
    // otherwise it is a big jump and we need a new set of divisors and reset center frequency
    int err = findDivisors(newfreq);
    setRFREQ(newfreq);
    // Set the new center frequency
    f_center = frequency = newfreq;
    // Calculate the new 3500 ppm delta
    max_delta = ((uint64_t) f_center * 10035LL / 10000LL) - f_center;
    write_si570();
  }
  
  return status;

}

My apologies for not catching this bug before the code was made available.  Many thanks to one of the many Minima users "John - MI0DFG" for finding and reporting this issue so that I can get it fixed.

Tuesday, July 29, 2014

Added 60 metre support to Minima tuning

I have updated my Minima code to support channelized 60 metre frequency allocations.  If you select 60 metres through the band up/down buttons, the rotary encoder will now select the next/previous channel frequency and ensure that USB is set.

If on the other hand you tune the VFO to the 5Mhz frequency range without selecting 60 metres with the band switches, you may set any frequency you like or either sideband.

I do not yet support the CW+PSK 1.5 kHz offset from the normal SSB frequencies but plan to add this support soon.

I am pleased with how this is coming together.  I am diverging a bit from the original Minima code, but intend to keep backwards compatibility as long as possible for those that are interested in that.  I use conditional compile options to enable or disable functionality as desired.  For example I can build for the original 6 wire LCD displays (16x2 or 20x4), for I2C displays of the same geometry as the 6 wire displays or for OLED displays.  I can build for the original pot tuning or for a rotary encoder.  I have a special build for Freetronics versions of the Arduino LCD shield. I can also build to support displays that have no notion of a display cursor.

Eventually however, my build will fork completely and become specialized to my needs/desires.  I will maintain the last compatible build as a separate fork from Eldon, WA0UWH's fine work.

My code will soon be available from my github to anyone that would benefit from these changes.