element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Benefits of Membership
    • Achievement Levels
    • Members Area
    • Personal Blogs
    • Feedback and Support
    • What's New on element14
  • Learn
    Learn
    • Learning Center
    • eBooks
    • 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
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Dev Tools
    • Manufacturers
    • Raspberry Pi
    • RoadTests & Reviews
    • Avnet Boards Community
    • Product Groups
  • 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
Personal Blogs
  • Members
  • More
Personal Blogs
Legacy Personal Blogs Arduino: R-2R: Setting the Signal Amplitude
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: jc2048
  • Date Created: 30 Apr 2018 2:33 PM Date Created
  • Views 1495 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 5 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 5 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 5 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.…
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to shabaz

    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 image

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

    Hehe yeah indeed, more G2M. I explored reusing the source code to one of the Linux shells (maybe csh, I can't recall), but in the end wrote something very cut-down, but with some features like tab-completion and help, otherwise it is a pain using command lines. It functions, but I'll have to tidy it up and finish the project sometime!

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

    Darn command prompts!

    There are some good parsers for those too, in particalar for unix style parameter parsing. But everyone knows that's a G2M (geek-machine) interface.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz over 5 years ago in reply to jc2048

    Hi Jon, Jan,

    Sorry I should have been clearer, I had no issue with SCPI for automation, just in my use-case I wasn't doing automation, the parser was for human-machine interface, i.e. a user interface like a command prompt.

    It is awesome that there is a SCPI parser for Arduino.

    Definitely for machine to machine (M2M) and controlling and co-ordinating multiple instruments a structured interface like SCPI is handy.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • jc2048
    jc2048 over 5 years ago in reply to shabaz

    I wouldn't have even tried this if Jan hadn't pointed us to the open-source SCPI library that we're using. The other thing that helped me was finding out, towards the end of last year, that you could program the Uno directly in C - somehow, up until then, I'd thought that it was limited to the commands in the reference.

     

    Did you have reasons for not wanting to use SCPI (just wondering about the pros and cons since I'm not very familiar with automated test equipment)?

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
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 © 2023 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

  • Facebook
  • Twitter
  • linkedin
  • YouTube