element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Personal Blogs
  • Community Hub
  • More
Personal Blogs
Legacy Personal Blogs Arduino: R-2R: Setting the Signal Amplitude
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: jc2048
  • Date Created: 30 Apr 2018 2:33 PM Date Created
  • Views 3451 views
  • Likes 6 likes
  • Comments 7 comments
  • arduino uno project
Related
Recommended

Arduino: R-2R: Setting the Signal Amplitude

jc2048
jc2048
30 Apr 2018

I'm back again with the resistor DAC on an Arduino Uno as a simple audio generator. I'm not clear how I came to do so many blogs on it, but this is going to be the last one.

 

Here's the latest sketch with commands for setting the output level as well as the frequency. This sketch is for a Uno, so may need adaption for other boards. I've changed the command to be 'FUNCtion' rather than 'SOURce' simply because I can (though I probably ought to read the SCPI spec and see what you should use, in case they recommend command usage).

 

#include <scpiparser.h>

#include <Arduino.h>

 

#include 
#include 
struct scpi_parser_context ctx;
float frequency;
float voltage;
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);
scpi_error_t get_voltage(struct scpi_parser_context* context, struct scpi_token* command);
scpi_error_t set_voltage(struct scpi_parser_context* context, struct scpi_token* command);
unsigned int tableOffset = 0;             // output count
unsigned int tableStep = 5120;
unsigned char amplitudeSelect = 0;
//
//  --- 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* scpiCommand;
  /* First, initialise the parser. */
  scpi_init(&ctx);
  /*
   * After initialising the parser, we set up the command tree.  Ours is
   *
   *  *IDN?         -> identify
   *  :FUNC
   *    :FREQuency  -> set_frequency
   *    :FREQuency? -> get_frequency
   *    :VOLTage  -> set_amplitude
   *    :VOLTage? -> get_amplitude
   */
  scpi_register_command(ctx.command_tree, SCPI_CL_SAMELEVEL, "*IDN?", 5, "*IDN?", 5, identify);
  scpiCommand = scpi_register_command(ctx.command_tree, SCPI_CL_CHILD, "FUNCTION", 8, "FUNC", 4, NULL);
  scpi_register_command(scpiCommand, SCPI_CL_CHILD, "FREQUENCY", 9, "FREQ", 4, set_frequency);
  scpi_register_command(scpiCommand, SCPI_CL_CHILD, "FREQUENCY?", 10, "FREQ?", 5, get_frequency);
  scpi_register_command(scpiCommand, SCPI_CL_CHILD, "VOLTAGE", 7, "VOLT", 4, set_voltage);
  scpi_register_command(scpiCommand, SCPI_CL_CHILD, "VOLTAGE?", 8, "VOLT?", 5, get_voltage);
  
  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];
  char line_buffer[128];
  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)
      if(line_buffer[read_length-1]=='\r')
        read_length = read_length - 1;
    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;
}
/**
 * Read the current voltage setting.
 */
scpi_error_t get_voltage(struct scpi_parser_context* context, struct scpi_token* command)
{
  Serial.println(voltage,4);
  scpi_free_tokens(command);
  return SCPI_SUCCESS;
}
/**
 * Set the audio output amplitude.
 */
scpi_error_t set_voltage(struct scpi_parser_context* context, struct scpi_token* command)
{
  struct scpi_token* args;
  struct scpi_numeric output_numeric;
  float testLevel;
  args = command;
  while(args != NULL && args->type == 0)
  {
    args = args->next;
  }
  output_numeric = scpi_parse_numeric(args->value, args->length, 0.4, 0.050, 0.8);
  if(output_numeric.length == 0 ||
    (output_numeric.length == 1 && output_numeric.unit[0] == 'V'))
  {
 //   tableStep = (unsigned int)(65536.0 * output_numeric.value / 100000.0);
    voltage = constrain(output_numeric.value, 0.05, 0.8);
    amplitudeSelect = 0;
    testLevel = 0.05;
    while(amplitudeSelect < 16) {
      if(voltage <= testLevel)
        break;
      testLevel = testLevel + 0.05; 
      amplitudeSelect = amplitudeSelect + 1;
      }
    voltage = testLevel;
    amplitudeSelect = 15 - amplitudeSelect;
    
   
  }
  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;
  }
  
  digitalWrite(A0, (amplitudeSelect & 0x01));   
  digitalWrite(A1, (amplitudeSelect & 0x02)>>1);   
  digitalWrite(A2, (amplitudeSelect & 0x04)>>2);   
  digitalWrite(A3, (amplitudeSelect & 0x08)>>3);   
    
  scpi_free_tokens(command);
  return SCPI_SUCCESS;
  
}

 

Here it is working with PuTTY:

 

 

image

 

The output level is in volts and is constrained to the range 50mV to 800mV in steps of 50mV. That's the peak-to-peak voltage; here's what 2kHz and 800mV looks like on the 'scope:

 

 

image

 

The firmware will take the next highest level - you can see that in the line where I try to set it to 23mV and it returns 50mV for the current level.

 

I had one problem with doing all this. Initially, when I put in the two new commands, it didn't work and the firmware locked up - it didn't even respond to the IDN request. That turned out to be because the SCPI library uses a fair amount of memory when the program runs; with my sine table (which uses up half of the processor's RAM) in there it was running out of space. I've temporarily cured that by reducing the line buffer from 256 to 128 characters. The sine table doesn't have to be ints (I made it int originally because I was thinking I might be scaling the values) and so reducing that to unsigned chars would be a better solution, but I didn't want to have to redo the table at this stage. If you use the library keep an eye on the dynamic memory allocated - it's a bit confusing to debug if you don't realise what's going on.

 

I'm going to leave this here, rather than finish it off as a full project. If you wanted to take it further, issues to think about include:

 

a) Power rails - using the +5V rail from the Arduino Uno leaves the resulting waveforms somewhat noisy.

b) There is some frequency variation evident on the higher frequency waveforms. I haven't investigated further, but it probably means either that there's something with a higher priority than the timer 2 interrupt or something is disabling all interrupts for short periods.

c) Trying to use an LM324 wan't very inspired.

 

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..."

  • Sign in to reply

Top Comments

  • Jan Cumps
    Jan Cumps over 7 years ago in reply to shabaz +3
    In the c++ libraries, there is a nice tokeniser for the command line attributes. It's been since the late 90's that I used it though. I moved on
  • shabaz
    shabaz over 7 years ago +2
    Hi Jon, It is very neat you got all that to function on an Uno, including amplitude adjustments and parsing the interace in real-time. It is fairly complete already! Nice feature/performance mix. I too…
  • Jan Cumps
    Jan Cumps over 7 years ago in reply to shabaz +2
    shabaz wrote: ... I still found it hard to get the parsing all sorted (I'm not using SCPI, but the concept was similar). The advantage of SCPI is that there are capable open sourced parsers for grabs.…
Parents
  • shabaz
    shabaz over 7 years ago

    Hi Jon,

     

    It is very neat you got all that to function on an Uno, including amplitude adjustments and parsing the interace in real-time. It is fairly complete already! Nice feature/performance mix.

     

    I too have been working on a frequency generator (a work in progress for an awfully long time), but the coding was easier since I used an external IC to do the actual signal generation, and I still found it hard to get the parsing all sorted (I'm not using SCPI, but the concept was similar).

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 7 years ago in reply to shabaz

    shabaz  wrote:

     

    ... I still found it hard to get the parsing all sorted (I'm not using SCPI, but the concept was similar).

    The advantage of SCPI is that there are capable open sourced parsers for grabs.
    The one that's used on the Arduino is basic (given the memory) but smart enough to serve an application like Jon's.
    The parser we used for the electronic load (on a MSP432 microcontroller and that I'm now trying on a Pi) is magnificent. SCPI compliance out of the box, including the error handling. Easy to add new SCPI commands.
    Someone managed to get that compiled for the UNO, but not with using the bootloader and Arduino environment. And with virtually no place for the instrument code image.

     

    Anyways, what I wanted to say is that if you use an existing standard, the parser is often already built and debuged for you.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • Jan Cumps
    Jan Cumps over 7 years ago in reply to shabaz

    shabaz  wrote:

     

    ... I still found it hard to get the parsing all sorted (I'm not using SCPI, but the concept was similar).

    The advantage of SCPI is that there are capable open sourced parsers for grabs.
    The one that's used on the Arduino is basic (given the memory) but smart enough to serve an application like Jon's.
    The parser we used for the electronic load (on a MSP432 microcontroller and that I'm now trying on a Pi) is magnificent. SCPI compliance out of the box, including the error handling. Easy to add new SCPI commands.
    Someone managed to get that compiled for the UNO, but not with using the bootloader and Arduino environment. And with virtually no place for the instrument code image.

     

    Anyways, what I wanted to say is that if you use an existing standard, the parser is often already built and debuged for you.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube