I've made a bit more progress. I now have my test code connected to the SCPI parser. That was very easy to do: I simply copied their generator example, took out the lines specific to the chip they were using, and adapted it slightly to calculate the table step. Much to my amazement it works and I can now program the output frequency.

 

Here's the sketch. The two includes that the highlighting wipes out are:

 

#include <scpiparser.h>
#include <Arduino.h>

 

#include 
#include 
struct scpi_parser_context ctx;
float frequency;
scpi_error_t identify(struct scpi_parser_context* context, struct scpi_token* command);
scpi_error_t get_frequency(struct scpi_parser_context* context, struct scpi_token* command);
scpi_error_t set_frequency(struct scpi_parser_context* context, struct scpi_token* command);
unsigned int tableOffset = 0;             // output count
unsigned int tableStep = 5120;
//
//  --- Sine table - generated by sineTable.exe 
//
unsigned int sineTable[512] = {
   0x8000,0x8192,0x8324,0x84b6,0x8647,0x87d9,0x896a,0x8afb,0x8c8b,0x8e1b,0x8fab,0x9139,0x92c8,0x9455,0x95e2,0x976d,
   0x98f8,0x9a82,0x9c0b,0x9d93,0x9f19,0xa09f,0xa223,0xa3a6,0xa528,0xa6a8,0xa826,0xa9a3,0xab1f,0xac98,0xae11,0xaf87,
   0xb0fb,0xb26e,0xb3de,0xb54d,0xb6ba,0xb824,0xb98c,0xbaf2,0xbc56,0xbdb8,0xbf17,0xc073,0xc1ce,0xc325,0xc47a,0xc5cd,
   0xc71c,0xc869,0xc9b4,0xcafb,0xcc3f,0xcd81,0xcebf,0xcffb,0xd133,0xd269,0xd39b,0xd4ca,0xd5f5,0xd71d,0xd842,0xd964,
   0xda82,0xdb9d,0xdcb4,0xddc7,0xded7,0xdfe3,0xe0ec,0xe1f1,0xe2f2,0xe3ef,0xe4e8,0xe5dd,0xe6cf,0xe7bd,0xe8a6,0xe98c,
   0xea6d,0xeb4a,0xec24,0xecf9,0xedca,0xee96,0xef5f,0xf023,0xf0e2,0xf19e,0xf255,0xf307,0xf3b5,0xf45f,0xf504,0xf5a5,
   0xf641,0xf6d9,0xf76c,0xf7fa,0xf884,0xf909,0xf98a,0xfa05,0xfa7d,0xfaef,0xfb5d,0xfbc5,0xfc29,0xfc89,0xfce3,0xfd39,
   0xfd8a,0xfdd6,0xfe1d,0xfe5f,0xfe9d,0xfed5,0xff09,0xff38,0xff62,0xff87,0xffa7,0xffc2,0xffd8,0xffe9,0xfff6,0xfffd,
   0xffff,0xfffd,0xfff6,0xffe9,0xffd8,0xffc2,0xffa7,0xff87,0xff62,0xff38,0xff09,0xfed5,0xfe9d,0xfe5f,0xfe1d,0xfdd6,
   0xfd8a,0xfd39,0xfce3,0xfc89,0xfc29,0xfbc5,0xfb5d,0xfaef,0xfa7d,0xfa05,0xf98a,0xf909,0xf884,0xf7fa,0xf76c,0xf6d9,
   0xf641,0xf5a5,0xf504,0xf45f,0xf3b5,0xf307,0xf255,0xf19e,0xf0e2,0xf023,0xef5f,0xee96,0xedca,0xecf9,0xec24,0xeb4a,
   0xea6d,0xe98c,0xe8a6,0xe7bd,0xe6cf,0xe5dd,0xe4e8,0xe3ef,0xe2f2,0xe1f1,0xe0ec,0xdfe3,0xded7,0xddc7,0xdcb4,0xdb9d,
   0xda82,0xd964,0xd842,0xd71d,0xd5f5,0xd4ca,0xd39b,0xd269,0xd133,0xcffb,0xcebf,0xcd81,0xcc3f,0xcafb,0xc9b4,0xc869,
   0xc71c,0xc5cd,0xc47a,0xc325,0xc1ce,0xc073,0xbf17,0xbdb8,0xbc56,0xbaf2,0xb98c,0xb824,0xb6ba,0xb54d,0xb3de,0xb26e,
   0xb0fb,0xaf87,0xae11,0xac98,0xab1f,0xa9a3,0xa826,0xa6a8,0xa528,0xa3a6,0xa223,0xa09f,0x9f19,0x9d93,0x9c0b,0x9a82,
   0x98f8,0x976d,0x95e2,0x9455,0x92c8,0x9139,0x8fab,0x8e1b,0x8c8b,0x8afb,0x896a,0x87d9,0x8647,0x84b6,0x8324,0x8192,
   0x8000,0x7e6d,0x7cdb,0x7b49,0x79b8,0x7826,0x7695,0x7504,0x7374,0x71e4,0x7054,0x6ec6,0x6d37,0x6baa,0x6a1d,0x6892,
   0x6707,0x657d,0x63f4,0x626c,0x60e6,0x5f60,0x5ddc,0x5c59,0x5ad7,0x5957,0x57d9,0x565c,0x54e0,0x5367,0x51ee,0x5078,
   0x4f04,0x4d91,0x4c21,0x4ab2,0x4945,0x47db,0x4673,0x450d,0x43a9,0x4247,0x40e8,0x3f8c,0x3e31,0x3cda,0x3b85,0x3a32,
   0x38e3,0x3796,0x364b,0x3504,0x33c0,0x327e,0x3140,0x3004,0x2ecc,0x2d96,0x2c64,0x2b35,0x2a0a,0x28e2,0x27bd,0x269b,
   0x257d,0x2462,0x234b,0x2238,0x2128,0x201c,0x1f13,0x1e0f,0x1d0d,0x1c10,0x1b17,0x1a22,0x1930,0x1842,0x1759,0x1673,
   0x1592,0x14b5,0x13db,0x1306,0x1235,0x1169,0x10a0,0x0fdc,0x0f1d,0x0e61,0x0daa,0x0cf8,0x0c4a,0x0ba0,0x0afb,0x0a5a,
   0x09be,0x0926,0x0893,0x0805,0x077b,0x06f6,0x0675,0x05fa,0x0582,0x0510,0x04a2,0x043a,0x03d6,0x0376,0x031c,0x02c6,
   0x0275,0x0229,0x01e2,0x01a0,0x0162,0x012a,0x00f6,0x00c7,0x009d,0x0078,0x0058,0x003d,0x0027,0x0016,0x0009,0x0002,
   0x0000,0x0002,0x0009,0x0016,0x0027,0x003d,0x0058,0x0078,0x009d,0x00c7,0x00f6,0x012a,0x0162,0x01a0,0x01e2,0x0229,
   0x0275,0x02c6,0x031c,0x0376,0x03d6,0x043a,0x04a2,0x0510,0x0582,0x05fa,0x0675,0x06f6,0x077b,0x0805,0x0893,0x0926,
   0x09be,0x0a5a,0x0afb,0x0ba0,0x0c4a,0x0cf8,0x0daa,0x0e61,0x0f1d,0x0fdc,0x10a0,0x1169,0x1235,0x1306,0x13db,0x14b5,
   0x1592,0x1673,0x1759,0x1842,0x1930,0x1a22,0x1b17,0x1c10,0x1d0d,0x1e0e,0x1f13,0x201c,0x2128,0x2238,0x234b,0x2462,
   0x257d,0x269b,0x27bd,0x28e2,0x2a0a,0x2b35,0x2c64,0x2d96,0x2ecc,0x3004,0x3140,0x327e,0x33c0,0x3504,0x364b,0x3796,
   0x38e3,0x3a32,0x3b85,0x3cda,0x3e31,0x3f8c,0x40e8,0x4247,0x43a9,0x450d,0x4673,0x47db,0x4945,0x4ab2,0x4c21,0x4d91,
   0x4f04,0x5078,0x51ee,0x5367,0x54e0,0x565c,0x57d9,0x5957,0x5ad7,0x5c59,0x5ddc,0x5f60,0x60e6,0x626c,0x63f4,0x657d,
   0x6707,0x6892,0x6a1d,0x6baa,0x6d37,0x6ec6,0x7054,0x71e4,0x7374,0x7504,0x7695,0x7826,0x79b8,0x7b49,0x7cdb,0x7e6d};

void setup()
{
  struct scpi_command* source;
  struct scpi_command* measure;

  /* First, initialise the parser. */
  scpi_init(&ctx);
  /*
   * After initialising the parser, we set up the command tree.  Ours is
   *
   *  *IDN?         -> identify
   *  :SOURCE
   *    :FREQuency  -> set_frequency
   *    :FREQuency? -> get_frequency
   */
  scpi_register_command(ctx.command_tree, SCPI_CL_SAMELEVEL, "*IDN?", 5, "*IDN?", 5, identify);
  source = scpi_register_command(ctx.command_tree, SCPI_CL_CHILD, "SOURCE", 6, "SOUR", 4, NULL);
  scpi_register_command(source, SCPI_CL_CHILD, "FREQUENCY", 9, "FREQ", 4, set_frequency);
  scpi_register_command(source, SCPI_CL_CHILD, "FREQUENCY?", 10, "FREQ?", 5, get_frequency);
  
  frequency = 1e3;
  // set the pins used as outputs:
  
  PORTD = 0;
  
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);
  digitalWrite(A0, HIGH);   
  digitalWrite(A1, HIGH);   
  digitalWrite(A2, HIGH);   
  digitalWrite(A3, LOW);   
  // timer 2 set up
  cli();                  // disable interrupts
  TCCR2A = 0;             // control register all 0
  TCCR2B = 0;             // control register all 0
  TCNT2 = 0;              // set count to 0
  OCR2A = 159;            // period = 160 x 1/16MHz = 10uS
  TCCR2A |= (1 << WGM21); // mode is clear on match
  TCCR2B |= (1 << CS20);  // no prescaler   
  TIMSK2 |= (1 << OCIE2A);  // enable interrupt on match
  sei();                  // enable interrupts
  Serial.begin(9600);
}
// timer 2 interrupt routine
ISR(TIMER2_COMPA_vect) {
  tableOffset = tableOffset + tableStep;
  PORTD = (sineTable[tableOffset >> 7]) >> 8;
}
// main loop
void loop()
{
  char line_buffer[256];
  unsigned char read_length;
  
  while(1)
  {
    /* Read in a line and execute it. */
    read_length = Serial.readBytesUntil('\n', line_buffer, 256);
    if(read_length > 0)
    {
      scpi_execute_command(&ctx, line_buffer, read_length);
    }
  }
}
/*
 * Respond to *IDN?
 */
scpi_error_t identify(struct scpi_parser_context* context, struct scpi_token* command)
{
  scpi_free_tokens(command);
  Serial.println("E14,Arduino Audio Signal Generator,1,10");
  return SCPI_SUCCESS;
}
/**
 * Read the current frequency setting.
 */
scpi_error_t get_frequency(struct scpi_parser_context* context, struct scpi_token* command)
{
//  float voltage;
  Serial.println(frequency,4);
  scpi_free_tokens(command);
  return SCPI_SUCCESS;
}
/**
 * Set the audio output frequency.
 */
scpi_error_t set_frequency(struct scpi_parser_context* context, struct scpi_token* command)
{
  struct scpi_token* args;
  struct scpi_numeric output_numeric;
//  unsigned char output_value;
  args = command;
  while(args != NULL && args->type == 0)
  {
    args = args->next;
  }
  output_numeric = scpi_parse_numeric(args->value, args->length, 1e3, 20, 2e4);
  if(output_numeric.length == 0 ||
    (output_numeric.length == 2 && output_numeric.unit[0] == 'H' && output_numeric.unit[1] == 'z'))
  {
    tableStep = (unsigned int)(65536.0 * output_numeric.value / 100000.0);
    frequency = (unsigned long)constrain(output_numeric.value, 20, 2e4);
  }
  else
  {
    scpi_error error;
    error.id = -200;
    error.description = "Command error;Invalid unit";
    error.length = 26;
    
    scpi_queue_error(&ctx, error);
    scpi_free_tokens(command);
    return SCPI_SUCCESS;
  }
  scpi_free_tokens(command);
  return SCPI_SUCCESS;
}

 

Here are the results for the following SCPI messages

 

:SOUR:FREQ 20Hz

 

 

:SOUR:FREQ 1234Hz

 

 

:SOUR:FREQ 2e4Hz

 

 

The frequencies are fairly accurate, being within a few Hertz of those requested.

 

This is the line that does the real work (line 158)

 

    tableStep = (unsigned int)(65536.0 * output_numeric.value / 100000.0);

 

it takes the frequency from the parser (a float in Hertz) and calculates the table step necessary to generate it. The 100k is the sample rate, the 65536 is the number of intervals one complete cycle is divided into.

 

There are a couple of issues I've identified from doing this:

 

1/ The amplitude dimishes at high frequencies. That means that either I've got my filter values wrong or that the variable gain section is a mess. To look at that I'm going to go back to my test code and rework it to do a frequency sweep - then I'll be able to see directly the frequency response on the 'scope. It also looks like there might be other problems with the 20kHz one.

 

2/ The serial side seems a bit fragile. I can't seem to talk to it with PuTTY (probably just me not being able to get the settings right), but have also once got it into a state where it wouldn't talk to the Arduino serial tool either, so maybe the error recovery isn't very good.

 

However, it's looking quite promising as a very low cost way to generate waveforms.

 

The original blog by Jan, where he introduced us to the SCPI library that he'd found, is here: Arduino in Test Instrumentation - Part 1: SCPI Lib

 

This is the SCPI parser library on GitHub: https://github.com/LachlanGunn/oic

 

 

Part one: Arduino: R-2R Experiment

Part two: Arduino: R-2R: Sine On You Crazy Diamond

Part three: Arduino: R-2R: Buffer, Attenuate, and Filter

Part four: Arduino: R-2R: "We Interrupt This Programme..."

Part five: Arduino: R-2R: "Resistance is..."?

Part six: Arduino: R-2R: Setting the Output Frequency

Part seven: Arduino: R-2R; "A Sweep is as Lucky, as Lucky Can Be..."

Part eight: Arduino: R-2R: Setting the Signal Amplitude