Tuesday, September 9, 2014

Minima Controller - OLED Display

After some initial head scratching, I have my adaptation of the Minima sketch that supports my OLED display up and running on my controller shield.


I was initially not getting any joy out of the OLED display and it remained black despite my efforts.  However, I had stupidly placed the initialization of the PCA9546 multiplexer after the initialization of the OLED display.  Since the OLED cannot be addressed until the mux is set to channel 1, no joy.

I did write a somewhat useful utility that enumerates all i2c device id's that can be seen on the Arduino i2c bus as well as on each of the four channels of the mux which others may find useful.

Debugging this did point out to me an error in my PCA9546 code and a couple of changes I would like to make.

1. I have mistakenly made the selectChannel() method private, when it should be public.
2. The channel argument to selectChannel() is currently a bit field.  The bottom 4 bits indicate which of the four channels should be enabled.  I believe I will change this to just take a channel number rather than a bit field.  I also need a way to deselect all channels.
3. I need to provide a constructor for the PCA9546 class that will allow initialization without selecting any channel.

I will provide an updated listing of PCA9546 class with these changes in a separate post.

Here is the code for scanning for devices connected to the i2c bus and all channels of the PCA9546.

// i2c_scanner
//

#include <Wire.h>
#include "PCA9546.h"

PCA9546 *mux;
char buf[66];

void setup()
{
  Wire.begin();

  Serial.begin(115200);
  Serial.println("i2c Scanner");
  mux = NULL;
}

void printAddress(byte address)
{
  if (address<16) Serial.print("0");
  Serial.println(address, HEX);
}

void loop()
{
  uint8_t nChannel;
  
  for (nChannel = 0; nChannel <= 4; nChannel++)
  {
    Serial.print("Channel ");
    Serial.println(nChannel, DEC);
    byte error, address;
    int nDevices;
  
    Serial.println("Scanning...");
  
    nDevices = 0;
    if (nChannel > 0)
    {
      if (mux == NULL)
      {
        mux = new PCA9546(0x70, 1 << (nChannel - 1));
      }
      else
      {
        bool fRc = mux->selectChannel(1 << (nChannel - 1));
      }
    }
    
    for(address = 1; address < 127; address++ ) 
    {
      Wire.beginTransmission(address);
      error = Wire.endTransmission();
      
      // Just for good measure, try again
      if (error != 0)
      {
        delay(10);
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
      }
  
      if (error == 0)
      {
        Serial.print("i2c device found at address 0x");
        printAddress(address);  
        nDevices++;
      }
      else if (error == 4) 
      {
        Serial.print("Unknown error at address 0x");
        printAddress(address);
      }    
    }
    if (nDevices == 0)
      Serial.println("No i2c devices found\n");
    else
      Serial.println("");
  }

  // Hang the script
  while (1==1);
}

Monday, September 8, 2014

Minima Controller Shield - Some measurements

Last evening I placed my second Si570 on my Minima Controller shield and am pleased to see it is functional as well.

The datasheet for the Si570 CMOS version says that supply current draw should typically be 90 mA with a maximum of 98 mA.  With both devices powered up, I am seeing a current draw on the 3V3 rail of 140 mA.

The Arduino UNO R3 board uses a TI LP2985-33DBVR regulator which according to its datasheet is rated for 150 mA.  There is nothing on the Arduino board that uses 3V3, but given the maximum current draw of two Si570 devices at 196 mA drove the decision to install a separate 3V3 800 mA regulator on the board.  At my current draw, I might be able to get away with using the Arduino 3V3 rail, but it is pushing things.  Another option might be to replace the regulator but again, I didn't want to modify the Arduino board.

Interestingly, the Arduino documentation says not to draw more than 50 mA from the 3V3 rail which makes me suspect that they may have trimmed the heat sink tab on the part.

I am seeing 13.5 dBm with no attenuation out of the Si570 devices so there is plenty of drive available to allow experimentation with different mixers.  The PI pad resistors can be populated to set any level desired.



I have Wayne, NB6M's Minima build at my disposal for a few weeks to be able to try out the new shield for both VFO and BFO signal sources and am looking forward to seeing how this works out in a practical sense.



Sunday, September 7, 2014

Minima Controller Shield programming

UPDATED:

As was intended, programming of the controller shield is trivial.  A single additional class was required and no changes to other code or libraries was necessary.  With the Si570 devices behind a multiplexer, the only change necessary to the main Minima sketch is to select the multiplexer channel containing the Si570 you wish to talk to.  For a controller with a single Si570, selecting that channel needs only happen at startup and existing code continues to work unchanged.

I created a driver class for the PCA9546 as follows:

File: PCA9546.h

#ifndef PCA9546_H
#define PCA9546_h

#ifndef P
#define PBUFSIZE (66)
extern char buf[PBUFSIZE];

#define  P(x) strcpy_P(buf, PSTR(x))
#endif

typedef enum 
{
  PCA9546_ERROR = 0,
  PCA9546_SUCCESS
} PCA9546_Status;

#define PCA9546_CHANNEL_1 (1) // Bit 1
#define PCA9546_CHANNEL_2 (2) // Bit 2
#define PCA9546_CHANNEL_3 (4) // Bit 3
#define PCA9546_CHANNEL_4 (8) // Bit 4

class PCA9546
{
public:
  PCA9546(uint8_t i2c_address, uint8_t channel);

  PCA9546_Status status;
  uint8_t channel;

private:
  uint8_t i2c_address;
  bool selectChannel(uint8_t channel);
  uint8_t i2c_read();
  void i2c_write(uint8_t data);
};

#endif

File: PCA9546.cpp

/*
 * PCA9546 Library for Arduino
 *
 * MIT License
 *
 * Copyright Jeff Whitlatch - ko7m - 2014
 */

#include <Arduino.h>
#include <Wire.h>
#include "PCA9546.h"
#include "debug.h"


#define DEBUG(x ...)  // Default to NO debug    
//#define DEBUG(x ...) debugUnique(x)    // UnComment for Debug

// Initialize the PCA9546 and enable the channel(s) indicated
PCA9546::PCA9546(uint8_t PCA9546_address, uint8_t channel)

  i2c_address = PCA9546_address;

  Wire.begin();
  selectChannel(channel);
}

// Send a channel selection word to the PCA9546
bool PCA9546::selectChannel(uint8_t channel)
{
  // Sanity check value passed.  Only least significant 4 bits valid
  if (channel <= 0xf)
  {
    i2c_write(channel);
    debug(P("Successfully selected PCA9546 channel"));
    status = PCA9546_SUCCESS;
  }
  else
  {
    debug(P("PCA9546 channel selection failed"));
    status = PCA9546_ERROR;
  }
  return (PCA9546_SUCCESS == status);
}

// Write a byte to I2C device.  There is only a single register.  If multiple bytes written, last one wins.
void PCA9546::i2c_write(uint8_t data)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(data);
  Wire.endTransmission();
}

// Read the one byte register from the I2C device
uint8_t PCA9546::i2c_read()
{
  uint8_t rdata = 0xFF;
  Wire.beginTransmission(i2c_address);
  Wire.requestFrom(i2c_address, (uint8_t)1);
  if (Wire.available()) rdata = Wire.read();
  return rdata;
}

So, this new class has been added to the Minima source files.  In the Minima main sketch, I add the following code right before including "Si570.h":

#include "PCA9546.h"

I then add the definition of the PCA9546 I2C address right before the one for the Si570:

#define PCA9546_I2C_ADDRESS 0x70

I then create a global variable to hold the PCA9546 object right before the one defined to hold the Si570 object:

PCA9546 *mux;

Ok, so the only thing required now is to initialize the multiplexer and select channel 1.  The rest of the Minima sketch can run without change.  I put the following code right before the initialization of the Si570:

  // Initialize the PCA9546 multiplexer and select channel 1
  mux = new PCA9546(PCA9546_I2C_ADDRESS, PCA9546_CHANNEL_1);
  if (mux->status == PCA9546_ERROR)
  {
    printLine2CEL(P("PCA9546 init error"));
    delay(3000);

  }

I have posted the PCA9546 driver source and integration with Eldon Brown's (WA0UWH) latest Minima sketch on my public branch of my Minima repository on Github.  Please check it out at https://github.com/ko7m/radiono_ko7m.

If integration of the files above provides any grief for anyone cloning my shield, please contact me for assistance and I will be happy to help.  Direct email is ko7m at arrl.net.

Minima Controller Shield checkout

UPDATED:

My Minima controller Arduino shield checks out as functional.  With two Si570 devices on the board, powering them from the Arduino regulator is pushing things, so I have isolated the 3V3 rail from the Arduino and provided my own regulator.  However, with only a single Si570 on the board, I am within limits on the Arduino regulator.  Therefore, I jumpered the power input pin back to the Vin pin on the Arduino to supply power to the shield.  This is the brown jumper wire seen below.  The yellow wire is the ground to my scope probe.



Measuring the voltage off the regulator and the Si570 look good.  Probing the output of the Si570 as seen above, we have the following default frequency of 56.32 MHz output seen below.



For those following along, the schematic of the shield looks like this.  There really isn't anything to it.  Just a multiplexer in front of two Si570's, voltage regulation and breaking out some of the I/O pins to separate cable connections for the Minima.




Now to go figure out how to talk to the multiplexer and the Si570.

I removed the shield from the Arduino Uno and connected up a bus pirate to see if I can talk to the PCA9546.  When searching the I2C address space for devices, initially I got a "response" from all possible addresses.  Clearly this is wrong...

Probing the PCA9546 I found that the voltages were correct, but that the reset pin was held low.  As seen from the schematic above, this is connected to the reset button and the ICSP reset pin.  However, this pin on the shield is not pulled up to any voltage rail and currently relies on the connected UNO to supply this voltage.  So, rather than power the shield from the bus pirate, I plugged it back into the UNO and reinstalled my brown power jumper and just monitored SCL and SDA pins with the bus pirate.



Now interrogating the bus yielded the following results:

Bus Pirate v3
Firmware v4.2 Bootloader v4.2
DEVID:0x0447 REVID:0x3043 (B5)
http://dangerousprototypes.com


I2C>(1)
Searching 7bit I2C address space.
   Found devices at:
0xE0(0x70 W) 0xE1(0x70 R) 0xFE(0x7F W) 0xFF(0x7F R)

Checking the PCA9546 datasheet, we see the following:


If you look at the schematic above, you will notice that I connect A0-A2 to ground, so the device address should be 0x70 (this value is the 0xE0 shifted right one bit to remove the read/write bit) and the read/write addresses should be 0xE0 for write and 0xE1 for read.

So, it looks like the PCA9546 can be seen on the bus and so now it might be time to write some actual code.

Saturday, September 6, 2014

Minima Controller Shield Build

I have completed (as far as I went) the build of my Minima Controller Shield, but have yet to complete testing of the board.  I populated only a single Si570, the edge and ICSP stackable headers and the header for my OLED display.



The prototype board needs a little work.  I need to move the VFO SMA edge connector away from the USB connector.  This will work for a prototype, but needs to move in the final version for sure.



The OLED display is a 3V3 device and I have it sharing the same I2C bus as the first Si570 for the VFO.  There is also a 5V set of connections for an LCD display with an I2C backpack for example.  This shares the same I2C bus as the PCA9546 multiplexer input, so it should allow 5V displays to work without driver modifications.   I will need to customize the 3V3 OLED driver to take into account that it is connected to the PCA9546 multiplexer.  I used this device because it allows voltage level conversion with a 5V input side and four 1.8 - 5V or more output side I2C buses.  It also allows me to address multiple devices that do not allow you to change their I2C address such as the Si570.  Without it, one would be limited to a single Si570 on a bus.



Ok, off to do some voltage testing and then to see if I can address the PCA9546.  If so, I will get busy on my display driver changes and testing the Si570.  I plan to roll my changes back into my Si570 driver to allow multiple instances behind a multiplexer as a compile time option.

Thursday, September 4, 2014

Small Progress on the Minimal Controller Shield

I had a little time this evening to start stuffing my prototype Minima controller board with parts.  Got the Si570, voltage regulator, reset button and PCA9546 I2C multiplexer installed.  The rest is connectors and a few R and C 0805 parts.  Getting there...



Progress on laying out my RF board for the Minima has been stalled for a couple weeks.  I hope to get back on that before too much more time has passed.

Tuesday, September 2, 2014

Direct Digital Synthesis (DDS) Part 2

Continuing on the theme of DDS in software, my previous post illustrated a simple audio sinusoidal generator using DDS techniques.  I promised to turn this baseline code into something more useful and here I will illustrate using this technique to generate a WSPR beacon message.

Here we will not be building a complete WSPR beacon, but only the components that deal with generating the WSPR audio signal in software.

I have done a similar beacon previously for the Parallax Propeller processor.  This will be a similar initial implementation that sends a pre-encoded message using Joe Taylor's wsprcode.exe application.  I encoded the message "KO7M CN87 7" and here is that message:

C:\Users\Jeff>wsprcode "KO7M CN87 7"
Message: KO7M CN87 7

Source-encoded message (50 bits, hex): 8B CC 46 9D 56 B1 C0

Data symbols:
      1 1 0 1 0 1 0 1 1 1 1 0 1 1 1 0 0 1 1 0 0 1 0 1 1 1 0 0 0 1
      1 1 1 0 0 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1 1 0 0 0
      0 1 0 1 0 0 0 0 0 1 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 1 0 0 0 1
      0 1 0 1 1 0 1 0 0 0 1 1 0 1 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1
      0 1 0 1 0 0 0 1 1 0 0 1 1 1 0 1 0 1 1 1 0 0 0 1 0 1 0 1 0 0
      1 0 1 1 1 0 1 1 0 0 0 1

Sync symbols:
      1 1 0 0 0 0 0 0 1 0 0 0 1 1 1 0 0 0 1 0 0 1 0 1 1 1 1 0 0 0
      0 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 1 0 1 1 0 0 1 1 0 1 0 0 0 1
      1 0 1 0 0 0 0 1 1 0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 1
      1 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 1 1 0 1 1 0 0 1 1
      0 1 0 0 0 1 1 1 0 0 0 0 0 1 0 1 0 0 1 1 0 0 0 0 0 0 0 1 1 0
      1 0 1 1 0 0 0 1 1 0 0 0

Channel symbols:
      3 3 0 2 0 2 0 2 3 2 2 0 3 3 3 0 0 2 3 0 0 3 0 3 3 3 1 0 0 2
      2 2 2 0 1 0 2 3 0 1 2 0 2 0 0 2 3 0 3 3 2 2 1 3 2 3 2 0 0 1
      1 2 1 2 0 0 0 1 1 2 3 0 3 2 1 0 3 0 0 3 0 0 3 0 3 3 0 0 0 3
      1 2 1 2 3 0 2 0 1 0 2 2 0 2 1 2 0 3 0 2 3 3 1 2 1 3 2 0 1 3
      0 3 0 2 0 1 1 3 2 0 0 2 2 3 0 3 0 2 3 3 0 0 0 2 0 2 0 3 1 0
      3 0 3 3 2 0 2 3 1 0 0 2

Decoded message: KO7M CN87 7              ntype:  7

I grabbed the channel symbols and turned them into a byte array, thus:

uint8_t symbols[] = 
{
  3,3,0,2,0,2,0,2,3,2,2,0,3,3,3,0,0,2,3,0,0,3,0,3,3,3,1,0,0,2,
  2,2,2,0,1,0,2,3,0,1,2,0,2,0,0,2,3,0,3,3,2,2,1,3,2,3,2,0,0,1,
  1,2,1,2,0,0,0,1,1,2,3,0,3,2,1,0,3,0,0,3,0,0,3,0,3,3,0,0,0,3,
  1,2,1,2,3,0,2,0,1,0,2,2,0,2,1,2,0,3,0,2,3,3,1,2,1,3,2,0,1,3,
  0,3,0,2,0,1,1,3,2,0,0,2,2,3,0,3,0,2,3,3,0,0,0,2,0,2,0,3,1,0,
  3,0,3,3,2,0,2,3,1,0,0,2
};

// Maximum number of symbols in a WSPR message

const uint32_t iSymbolMax = (sizeof(symbols) / sizeof(uint8_t));

I then set up a couple of constants and a table of DDS tuning words for the WSPR channel symbols.  Remember that for a SSB transmitter, the audio tones that are used to modulate it for WSPR transmission are in the range of 1400-1600 Hz.  The WSPR band is 200 Hz wide and so I have chosen to put my beacon signal in the middle of the band.  Any frequency can be used in this 200 Hz range, but consideration should be made for the width of the signal when choosing to be near the band edge.

// Tuning word is set to the following multiplier times the tone frequency
const double multiplier = pow(2,32) / ref_frequency / 256;

// Tuning word delta for 1.4648 Hz delta between symbols
const double delta = 1.4648 * multiplier;

// Array of pre-calculated tuning words for each WSPR symbol spaced
// 1.4648 Hz apart. Arbitrarily chose symbol 2 for 1500 Hz as W1JT does not
// define it in his documentation.
volatile const uint32_t tuning_words[] = 
{
  (dds_frequency * multiplier - (delta * 2)),
  (dds_frequency * multiplier - delta),
  (dds_frequency * multiplier),
  (dds_frequency * multiplier + delta) 
};

I am going to use timer 2 to generate a 32 kHz clock (reference frequency) for the DDS and generate a 1500 Hz tone.

// DDS frequency is chosen to be in the middle of the 1400-1600 Hz band
// Any value in this range is appropriate, but should be chosen to stay within
// the band while allowing for 1.4648 Hz above and 2.9296 Hz below plus
// sidebands.
int dds_frequency = 1500;

// The reference frequency is subject to the accuracy of the 16 MHz
// crystal oscillator.
const double ref_frequency = (16000000/510);

So, once we enable interrupts, a 1500 Hz tone will be generated.  The four WSPR channel symbols are 1.4648 Hz apart and are of 683 ms duration.  Since we are executing an interrupt service routine every 32 us, it will take 21427 interrupts to generate one WSPR channel symbol.  Once we have seen this many interrupts, we move on to the next symbol in the WSPR message until all 162 symbols have been generated.  At this point, we disable interrupts and the tone generation stops.

// Timer 2 interrupt service routine (ISR) is used to generate
// the timebase reference clock for the DDS generator at 32kHz.
ISR(TIMER2_OVF_vect)
{
  // Keep track of how long this symbol has been playing and when to move
  // to next symbol. WSPR symbol length = 8192 / 12000 * 1000 ms = 682.6666 or
  // 683 ms.  This function is called about every 32 us.  By keeping track of
  // how many times this function is called we can calculate the length of
  // each symbol.
  //
  // Clock is 16000000 / 510 = 31372.5490 Hz = 31.875 us per tick
  // .683 / 3.1875e-5 = 21427 counts.  This may need be adjusted based on
  // accuracy of main 16 MHz clock.
  if (cTone++ >= 21427)
  {
    cTone = 0;  // Reset tone length counter
    iSymbol++;  // Carry on with next symbol
  }
  
  // Update 24 bit phase accumulator and extract the sine table index from
  // top 8 bits of phase accumulator.
  phase_accumulator += tuning_word;
  sine_table_index = phase_accumulator >> 16;  // 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);
  
  // If we have sent the entire 162 symbol WSPR msg, kill the timer
  // and reset to first symbol.
  if (iSymbol >= iSymbolMax)
  {
    TIMSK2 = 0;  // Disable timer 2 to stop tone
    iSymbol = 0; // Reset back to beginning of symbol set
  }
  
  // Get the next tuning word based on the current symbol
  tuning_word = tuning_words[symbols[iSymbol]];
}

So, putting all this together, I have a functional WSPR beacon using software DDS.  This is not a complete beacon solution as WSPR messages have to be started within a couple seconds of an even minute.  But, the code if started on an even minute is easily decoded by Joe Taylor's fine software.  Here is a screen shot of an Argo waterfall display of the signal:

Here is a screen shot of the signal decode:


Where am I going with all this?  Tying all this back to recent work on the Minima transceiver, I am proposing that the CW sidetone hardware be replaced by a software DDS sidetone generator.  In doing so, not only can we provide CW sidetone services, but the audio tone can be modulated in interesting ways to easily integrate digital modes such as WSPR into the Minima project.  On transmit, the CW sidetone is used to generate CW signals by feeding the audio signal into the transmitter audio chain.  The same technique is used for digital modes.

In my next post, I will flesh out the WSPR beacon to allow specification of the message without requiring the message be pre-encoded using wsprcode.exe.  Stay tuned...

Here is the complete source code listing thus far.  If you wish to just compile it and use it, please change the symbols[] array to contain your own WSPR message.

// Sine wave generator using DDS techniques implementing WSPR beacon
// 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
};

uint8_t symbols[] = 
{
  3,3,0,2,0,2,0,2,3,2,2,0,3,3,3,0,0,2,3,0,0,3,0,3,3,3,1,0,0,2,
  2,2,2,0,1,0,2,3,0,1,2,0,2,0,0,2,3,0,3,3,2,2,1,3,2,3,2,0,0,1,
  1,2,1,2,0,0,0,1,1,2,3,0,3,2,1,0,3,0,0,3,0,0,3,0,3,3,0,0,0,3,
  1,2,1,2,3,0,2,0,1,0,2,2,0,2,1,2,0,3,0,2,3,3,1,2,1,3,2,0,1,3,
  0,3,0,2,0,1,1,3,2,0,0,2,2,3,0,3,0,2,3,3,0,0,0,2,0,2,0,3,1,0,
  3,0,3,3,2,0,2,3,1,0,0,2
};

// Maximum number of symbols in a WSPR message
const uint32_t iSymbolMax = (sizeof(symbols) / sizeof(uint8_t));

// 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 is chosen to be in the middle of the 1400-1600 Hz band  Any value
// in this range is appropriate, but should be chosen to stay within the band while
// allowing for 1.4648 Hz above and 2.9296 Hz below plus sidebands.
int dds_frequency = 1500;

// The reference frequency is subject to the accuracy of the 16 MHz crystal oscillator.
const double ref_frequency = (16000000/510);

// The following formula will calculate the necessary tuning word for a given output freq
// tuning_word = pow(2,32) * dds_frequency / ref_frequency;

// 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;
volatile uint16_t iSymbol;
volatile uint16_t cTone;

// Tuning word is set to the following multiplier times the tone frequency
const double multiplier = pow(2,32) / ref_frequency / 256;

// Tuning word delta for 1.4648 Hz delta between symbols
const double delta = 1.4648 * multiplier;

// Array of pre-calculated tuning words for each WSPR symbol spaced 1.4648 Hz apart.  
// Arbitrarily chose symbol 2 for 1500 Hz as W1JT does not define it in his documentation.
volatile const uint32_t tuning_words[] = 
{
  (dds_frequency * multiplier - (delta * 2)),
  (dds_frequency * multiplier - delta),
  (dds_frequency * multiplier),
  (dds_frequency * multiplier + delta) 
};

// Setup
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 https://spreadsheets.google.com/pub?key=rtHw_R6eVL140KS9_G8GPkA&gid=0
  pinMode(10, OUTPUT);      // Timer 2 PWM output on mega256 is pin 10
  
  // Set up timer2 to a phase correct 32kHz clock
  timer2Setup();

  iSymbol = cTone = 0;     // Set initial symbol and symbol length
  
  // Calculate the tuning word for the first symbol before we enable interrupts
  tuning_word = tuning_words[symbols[iSymbol]];
  
  // disable interrupts to avoid timing distortion
  //cbi (TIMSK0, TOIE0);    // Disable timer 0.  Breaks the delay() function
  sbi (TIMSK2, TOIE2);      // Enable timer 2.
}

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

// Setup timer2 with prescaler = 1, PWM mode to phase correct PWM
// See th ATMega datasheet for all the gory details
void timer2Setup()
  TIMSK2 = 0;
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);
}

// Timer 2 interrupt service routine (ISR) is used to generate
// the timebase reference clock for the DDS generator at 32kHz.
ISR(TIMER2_OVF_vect)
{
  // Keep track of how long this symbol has been playing and when to move to next symbol
  // WSPR symbol length = 8192 / 12000 * 1000 ms = 682.6666 or 683 ms.  This function is
  // called about every 32 us.  By keeping track of how many times this function is called
  // we can calculate the length of each symbol
  //
  // Clock is 16000000 / 510 = 31372.5490 Hz = 31.875 us per tick
  // .683 / 3.1875e-5 = 21427 counts.  This may need be adjusted based on accuracy of
  // main 16 MHz clock.
  if (cTone++ >= 21427)
  {
    cTone = 0;  // Reset tone length counter
    iSymbol++;  // Carry on with next symbol
  }
  
  // Update 24 bit phase accumulator and extract the sine table index from top 8 bits
  phase_accumulator += tuning_word;
  sine_table_index = phase_accumulator >> 16;  // 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);
  
  // If we have sent the entire 162 symbol WSPR msg, kill the timer
  // and reset to first symbol.
  if (iSymbol >= iSymbolMax)
  {
    TIMSK2 = 0;  // Disable timer 2 to stop tone
    iSymbol = 0; // Reset back to beginning of symbol set
  }
  
  // Get the next tuning word based on the current symbol
  tuning_word = tuning_words[symbols[iSymbol]];

}

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.