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
Top Comments