├── .gitignore ├── LICENSE ├── README.md ├── examples ├── Calibration │ ├── Calibration.ino │ └── Readme.txt ├── FrequencyGenerator │ └── FrequencyGenerator.ino ├── SimpleFrequencyGenerator │ └── SimpleFrequencyGenerator.ino ├── SineOutput │ └── SineOutput.ino ├── TapeOutput │ └── TapeOutput.ino ├── WaveformMeasurement │ └── WaveformMeasurement.ino └── selftest │ └── selftest.ino ├── keywords.txt ├── library.properties ├── production.hex ├── src ├── ad983x │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── ad983x.c │ └── ad983x.h ├── mcp49xx │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── mcp49xx.c │ └── mcp49xx.h ├── tsunami.cpp └── tsunami.h └── test_and_program.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Nick Johnson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of tsunami-arduino nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tsunami-arduino 2 | =============== 3 | 4 | Arduino library for the Tsunami signal generator and frequency counter 5 | -------------------------------------------------------------------------------- /examples/Calibration/Calibration.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Calibration.ino 3 | * Interactive calibration of input and output voltages of the Tsunami. 4 | * Requires a generic digital multimeter for the output calibration phase 5 | * and an output-to-input loopback connection for input calibration phase. 6 | * 7 | * Accepts commands via the serial port at 115200 baud. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define BUFFER_SIZE 32 15 | #define SAMPLE_SIZE 16 16 | #define NUM_MEASURE 32 17 | 18 | void setup() { 19 | // Initialize the serial port 20 | Serial.begin(115200); 21 | 22 | // Initialize the Tsunami 23 | Tsunami.begin(); 24 | } 25 | 26 | // Waits until a newline arrives from the serial port 27 | void waitLine() { 28 | char key; 29 | 30 | do { 31 | while(!Serial.available()); 32 | key = Serial.read(); 33 | } while(key != '\n'); 34 | } 35 | 36 | // Reads a line from the serial port, without timeout 37 | int readLine(char *buf, int len) { 38 | int pos = 0; 39 | 40 | do { 41 | while(!Serial.available()); 42 | buf[pos++] = Serial.read(); 43 | } while (pos < len && buf[pos-1] != '\n'); 44 | 45 | buf[--pos] = 0; 46 | 47 | return pos; 48 | } 49 | 50 | int32_t roundToInt32(float value) { 51 | if(value > 0) { 52 | return (int32_t)(value + 0.5); 53 | } else { 54 | return (int32_t)(value - 0.5); 55 | } 56 | } 57 | 58 | // Calculates the linear regression coefficients that satisfy y=a*x+b 59 | // with least amount of total error (sum of error squares is minimal) 60 | // Note: it is assumed that x and y values are all within +/-3300 and 61 | // n is less than 98 otherwise the int32 sums could possibly overflow 62 | void regress(int32_t *x, int32_t *y, int n, float *a, float *b) { 63 | int32_t avgx = 0; 64 | int32_t avgy = 0; 65 | int32_t ssxx = 0; 66 | int32_t ssxy = 0; 67 | 68 | // These are called averages but they're just added up sums for now 69 | for(int i = 0; i < n; i++) { 70 | avgx += x[i]; 71 | avgy += y[i]; 72 | } 73 | 74 | // Now these become actual averages, rounded to the nearest integer 75 | avgx = roundToInt32((float)avgx / n); 76 | avgy = roundToInt32((float)avgy / n); 77 | 78 | // These are called "variance of x" and "covariance of x and y"; we 79 | // should actually divide both sums by n in the end to get the real 80 | // values but since all we want is their proportion there's no need 81 | for(int i = 0; i < n; i++) { 82 | ssxx += (x[i] - avgx) * (x[i] - avgx); 83 | ssxy += (x[i] - avgx) * (y[i] - avgy); 84 | } 85 | 86 | // The slope of the regression a is covariance divided by variance; 87 | // the intercept b is not hard to get as soon as the slope is known 88 | *a = (float)ssxy / (float)ssxx; 89 | *b = (float)avgy - *a * (float)avgx; 90 | } 91 | 92 | void loop() { 93 | char buf[BUFFER_SIZE]; 94 | uint8_t value; 95 | float scale, shift; 96 | float slope, intercept; 97 | float cal_scale[CAL_DATA_ALL]; 98 | float cal_shift[CAL_DATA_ALL]; 99 | int32_t target_ofs[SAMPLE_SIZE]; 100 | int32_t target_amp[SAMPLE_SIZE]; 101 | int32_t target_val[SAMPLE_SIZE]; 102 | int32_t actual_ofs[SAMPLE_SIZE]; 103 | int32_t actual_amp[SAMPLE_SIZE]; 104 | int32_t actual_val[SAMPLE_SIZE]; 105 | int32_t actual_zero_offset; 106 | uint8_t number_ofs; 107 | uint8_t number_amp; 108 | 109 | // We sure don't want to calibrate on the back of a previous calibration so let's not 110 | Tsunami.useCalibrationData(CAL_DATA_NONE); 111 | 112 | // Wait until someone actually opens the serial port to avoid losing initial greeting 113 | while(!Serial); 114 | 115 | Serial.println(F("--==< Welcome to Tsunami Calibration Tool 1.0 >==--")); 116 | Serial.println(); 117 | 118 | // Retrieve (without applying) and list the currently stored calibration coefficients 119 | Serial.println(F("Your currently stored calibration coefficients are:")); 120 | Serial.println(); 121 | 122 | if(Tsunami.getCalibrationData(CAL_DATA_OFFSET, &scale, &shift)) { 123 | Serial.print(F("Output offset: scale = ")); 124 | Serial.print(scale, 6); 125 | Serial.print(F(", shift = ")); 126 | Serial.print(shift/1000, 3); 127 | Serial.println(F(" V.")); 128 | } else { 129 | Serial.println(F("Output offset: no calibration data found...")); 130 | } 131 | 132 | if(Tsunami.getCalibrationData(CAL_DATA_AMPLITUDE, &scale, &shift)) { 133 | Serial.print(F("Output amplitude: scale = ")); 134 | Serial.print(scale, 6); 135 | Serial.print(F(", shift = ")); 136 | Serial.print(shift/1000, 3); 137 | Serial.println(F(" V.")); 138 | } else { 139 | Serial.println(F("Output amplitude: no calibration data found...")); 140 | } 141 | 142 | if(Tsunami.getCalibrationData(CAL_DATA_MEAN_VALUE, &scale, &shift)) { 143 | Serial.print(F("Input mean: scale = ")); 144 | Serial.print(scale, 6); 145 | Serial.print(F(", shift = ")); 146 | Serial.print(shift/1000, 3); 147 | Serial.println(F(" V.")); 148 | } else { 149 | Serial.println(F("Input mean: no calibration data found...")); 150 | } 151 | 152 | if(Tsunami.getCalibrationData(CAL_DATA_PEAK_VALUE, &scale, &shift)) { 153 | Serial.print(F("Input peak: scale = ")); 154 | Serial.print(scale, 6); 155 | Serial.print(F(", shift = ")); 156 | Serial.print(shift/1000, 3); 157 | Serial.println(F(" V.")); 158 | } else { 159 | Serial.println(F("Input peak: no calibration data found...")); 160 | } 161 | 162 | if(Tsunami.getCalibrationData(CAL_DATA_CURRENT_VALUE, &scale, &shift)) { 163 | Serial.print(F("Input instant: scale = ")); 164 | Serial.print(scale, 6); 165 | Serial.print(F(", shift = ")); 166 | Serial.print(shift/1000, 3); 167 | Serial.println(F(" V.")); 168 | } else { 169 | Serial.println(F("Input instant: no calibration data found...")); 170 | } 171 | 172 | Serial.println(); 173 | Serial.print(F("Reset Coefficients, Edit Coefficients or start Calibration Wizard? (r/e/w) ")); 174 | 175 | readLine(buf, BUFFER_SIZE); 176 | 177 | Serial.println(); 178 | 179 | if(tolower(buf[0]) == 'r') { 180 | Serial.print(F("Do you really wish to reset all calibration coefficients? (y/n) ")); 181 | 182 | readLine(buf, BUFFER_SIZE); 183 | 184 | Serial.println(); 185 | 186 | if(tolower(buf[0]) == 'y') { 187 | Tsunami.setCalibrationData(CAL_DATA_OFFSET, 1.0, 0.0); 188 | Tsunami.setCalibrationData(CAL_DATA_AMPLITUDE, 1.0, 0.0); 189 | Tsunami.setCalibrationData(CAL_DATA_MEAN_VALUE, 1.0, 0.0); 190 | Tsunami.setCalibrationData(CAL_DATA_PEAK_VALUE, 1.0, 0.0); 191 | Tsunami.setCalibrationData(CAL_DATA_CURRENT_VALUE, 1.0, 0.0); 192 | Serial.println(F("Calibration coefficients were reset.")); 193 | } else { 194 | Serial.println(F("Calibration coefficients were NOT reset.")); 195 | } 196 | 197 | Serial.println(); 198 | Serial.println(F("Press Enter if you wish to start again.")); 199 | Serial.println(); 200 | 201 | waitLine(); 202 | 203 | return; 204 | } 205 | 206 | if(tolower(buf[0]) == 'e') { 207 | do { 208 | do { 209 | Serial.println(F("List of calibration coefficients:")); 210 | Serial.println(F("1 : Output offset")); 211 | Serial.println(F("2 : Output amplitude")); 212 | Serial.println(F("3 : Input mean value")); 213 | Serial.println(F("4 : Input peak value")); 214 | Serial.println(F("5 : Input instant value")); 215 | Serial.print(F("Please select a coefficient to edit: (1-5) ")); 216 | 217 | readLine(buf, BUFFER_SIZE); 218 | 219 | Serial.println(); 220 | 221 | value = atoi(buf) - 1; 222 | 223 | if(value >= CAL_DATA_ALL) { 224 | Serial.println(F("Sorry, that is not a value from this list; please try again.")); 225 | Serial.println(); 226 | } 227 | 228 | } while(value >= CAL_DATA_ALL); 229 | 230 | Serial.print(F("Your choice is ")); 231 | Serial.print(value + 1); 232 | Serial.print(F("; is that correct? (y/n) ")); 233 | 234 | readLine(buf, BUFFER_SIZE); 235 | 236 | Serial.println(); 237 | } while(tolower(buf[0]) != 'y'); 238 | 239 | do { 240 | Serial.print(F("Please input the new scale factor: ")); 241 | 242 | readLine(buf, BUFFER_SIZE); 243 | 244 | Serial.println(); 245 | 246 | scale = atof(buf); 247 | 248 | Serial.print(F("Your choice is ")); 249 | Serial.print(scale, 6); 250 | Serial.print(F("; is that correct? (y/n) ")); 251 | 252 | readLine(buf, BUFFER_SIZE); 253 | 254 | Serial.println(); 255 | } while(tolower(buf[0]) != 'y'); 256 | 257 | do { 258 | Serial.print(F("Please input the new offset (in V): ")); 259 | 260 | readLine(buf, BUFFER_SIZE); 261 | 262 | Serial.println(); 263 | 264 | shift = atof(buf); 265 | 266 | Serial.print(F("Your choice is ")); 267 | Serial.print(shift, 3); 268 | Serial.print(F(" V; is that correct? (y/n) ")); 269 | 270 | readLine(buf, BUFFER_SIZE); 271 | 272 | Serial.println(); 273 | } while(tolower(buf[0]) != 'y'); 274 | 275 | // Shift is actually in the units of the calibrated entity (mV), so correct for that 276 | Tsunami.setCalibrationData((CalibratedValue)value, scale, shift*1000); 277 | 278 | Serial.print(F("Updated item ")); 279 | Serial.print(value + 1); 280 | Serial.print(F(" to scale = ")); 281 | Serial.print(scale, 6); 282 | Serial.print(F(" , shift = ")); 283 | Serial.print(shift, 3); 284 | Serial.println(F(" V.")); 285 | 286 | Serial.println(); 287 | Serial.println(F("Press Enter if you wish to start again.")); 288 | Serial.println(); 289 | 290 | waitLine(); 291 | 292 | return; 293 | } 294 | 295 | /* 296 | * Start of Calibration Wizard 297 | */ 298 | 299 | Serial.println(F("Please connect a digital multimeter to the output of your Tsunami,")); 300 | Serial.println(F("switched to 'DC voltage' on the lowest range that can measure 3V.")); 301 | Serial.println(); 302 | Serial.println(F("Calibration consists of 5 phases: 2 are manual and 3 are automatic.")); 303 | Serial.println(F("First, you will be prompted for your reading on a series of values;")); 304 | Serial.println(F("Please press Enter to start phase 1.")); 305 | 306 | waitLine(); 307 | 308 | /* 309 | * PHASE 1 310 | */ 311 | 312 | Serial.println(); 313 | Serial.println(F("Starting phase 1...")); 314 | Serial.println(); 315 | 316 | // Hold the DDS chip in reset for DC offset measurements 317 | Tsunami.reset(true); 318 | 319 | number_ofs = 0; 320 | 321 | actual_zero_offset = 0; 322 | 323 | for(int value = -2500; value <= 2500; value += 500 ) { 324 | Tsunami.setOffset(value); 325 | 326 | target_ofs[number_ofs] = value; 327 | 328 | do { 329 | Serial.print(F("Testing offset at ")); 330 | Serial.print((float)value/1000, 3); 331 | Serial.println(F(" V.")); 332 | Serial.print(F("Please input your reading (in V): ")); 333 | 334 | readLine(buf, BUFFER_SIZE); 335 | 336 | Serial.println(); 337 | 338 | actual_ofs[number_ofs] = roundToInt32(atof(buf) * 1000); 339 | 340 | Serial.print(F("Your reading is ")); 341 | Serial.print((float)actual_ofs[number_ofs]/1000, 3); 342 | Serial.print(F(" V; is that correct? (y/n) ")); 343 | 344 | readLine(buf, BUFFER_SIZE); 345 | 346 | Serial.println(); 347 | } while(tolower(buf[0]) != 'y'); 348 | 349 | // Store the measured offset corresponding to a setting of zero offset for later use 350 | if(target_ofs[number_ofs] == 0) { 351 | actual_zero_offset = actual_ofs[number_ofs]; 352 | } 353 | 354 | number_ofs++; 355 | } 356 | 357 | // Release the DDS chip from reset 358 | Tsunami.reset(false); 359 | 360 | // Calculate best fitting line based on a list of output offsets we try to set and the 361 | // output offsets actually produced by the Tsunami as measured and entered by the user 362 | regress(target_ofs, actual_ofs, number_ofs, &slope, &intercept); 363 | 364 | // Calculate the calibration scale and shift that would cancel out such a linear error 365 | cal_scale[CAL_DATA_OFFSET] = 1.0 / slope; 366 | cal_shift[CAL_DATA_OFFSET] = - intercept / slope; 367 | 368 | Serial.println(); 369 | Serial.println(F("Phase 1 complete.")); 370 | Serial.println(); 371 | Serial.println(F("Next, please switch your DMM to 'AC voltage', lowest range for 2.5V.")); 372 | Serial.println(); 373 | Serial.println(F("Again, you will be prompted for your reading on a series of values;")); 374 | Serial.println(F("Please press Enter to start phase 2.")); 375 | 376 | waitLine(); 377 | 378 | /* 379 | * PHASE 2 380 | */ 381 | 382 | Serial.println(); 383 | Serial.println(F("Starting phase 2...")); 384 | Serial.println(); 385 | 386 | // Reset the offset and select a frequency of 1kHz 387 | Tsunami.setOffset(0); 388 | Tsunami.setFrequency(0, 1000.0); 389 | Tsunami.selectFrequency(0); 390 | 391 | number_amp = 0; 392 | 393 | for(int value = 1000; value <= 6000; value += 1000 ) { 394 | Tsunami.setAmplitude(value); 395 | 396 | target_amp[number_amp] = value; 397 | 398 | do { 399 | Serial.print(F("Testing amplitude at ")); 400 | Serial.print((float)value/1000, 3); 401 | Serial.print(F(" Vpp (")); 402 | Serial.print((float)value/2000 / sqrt(2), 3); 403 | Serial.println(F(" Vrms).")); 404 | Serial.print(F("Please input your reading (in V): ")); 405 | 406 | readLine(buf, BUFFER_SIZE); 407 | 408 | Serial.println(); 409 | 410 | actual_amp[number_amp] = roundToInt32(atof(buf) * 1000); 411 | 412 | Serial.print(F("Your reading is ")); 413 | Serial.print((float)actual_amp[number_amp]/1000, 3); 414 | Serial.print(F(" V; is that correct? (y/n) ")); 415 | 416 | readLine(buf, BUFFER_SIZE); 417 | 418 | Serial.println(); 419 | } while(tolower(buf[0]) != 'y'); 420 | 421 | // Correct for settings being "peak-to-peak" versus readings being "AC RMS" voltage 422 | actual_amp[number_amp] *= (2 * sqrt(2)); 423 | 424 | number_amp++; 425 | } 426 | 427 | // Calculate best fitting line based on output amplitudes we are trying to set and the 428 | // output amplitudes actually produced by the Tsunami measured and entered by the user 429 | regress(target_amp, actual_amp, number_amp, &slope, &intercept); 430 | 431 | // Calculate the calibration scale and shift that would cancel out such a linear error 432 | cal_scale[CAL_DATA_AMPLITUDE] = 1.0 / slope; 433 | cal_shift[CAL_DATA_AMPLITUDE] = - intercept / slope; 434 | 435 | Serial.println(); 436 | Serial.println(F("Phase 2 complete.")); 437 | Serial.println(); 438 | Serial.println(F("Next, please detach your DMM and loop Tsunami's output to its input.")); 439 | Serial.println(); 440 | Serial.println(F("The hard part is over - the remaining three phases are automatic;")); 441 | Serial.println(F("Please press Enter to start phase 3.")); 442 | 443 | waitLine(); 444 | 445 | /* 446 | * PHASE 3 447 | */ 448 | 449 | Serial.println(); 450 | Serial.println(F("Starting phase 3...")); 451 | Serial.println(); 452 | 453 | // Set a small signal to avoid distortions at high offsets 454 | Tsunami.setAmplitude(1000); 455 | 456 | // Note - Yes, we measure DC offset (average) with an AC signal instead of a stable DC 457 | // offset because the measuring circuit has hysteresis and would return different data 458 | // for the same fixed DC input depending on the direction of our scan - so AC it is... 459 | 460 | for(int point = 0; point < number_ofs; point++ ) { 461 | int value = target_ofs[point]; 462 | 463 | Tsunami.setOffset(value); 464 | 465 | Serial.print(F("Measuring offset at ")); 466 | Serial.print((float)value/1000, 3); 467 | Serial.print(F(" V.")); 468 | 469 | delay(1000); 470 | 471 | actual_val[point] = 0; 472 | 473 | for (int count = 0; count < NUM_MEASURE; count++) { 474 | actual_val[point] += Tsunami.measureMeanVoltage(); 475 | 476 | delay(100); 477 | } 478 | 479 | actual_val[point] /= NUM_MEASURE; 480 | 481 | Serial.print(F(" Actual reading: ")); 482 | Serial.print((float)actual_val[point]/1000, 3); 483 | Serial.println(F(" V.")); 484 | } 485 | 486 | // Calculate best fitting line based on actually produced output offsets as previously 487 | // measured and reported by the user and "mean input voltage" measurements we just did 488 | regress(actual_ofs, actual_val, number_ofs, &slope, &intercept); 489 | 490 | // Calculate the calibration scale and shift that would cancel out such a linear error 491 | cal_scale[CAL_DATA_MEAN_VALUE] = 1.0 / slope; 492 | cal_shift[CAL_DATA_MEAN_VALUE] = - intercept / slope; 493 | 494 | Serial.println(); 495 | Serial.println(F("Phase 3 complete.")); 496 | Serial.println(); 497 | Serial.println(F("Please leave the Tsunami's output connected to its input.")); 498 | Serial.println(); 499 | Serial.println(F("Please press Enter to start phase 4.")); 500 | 501 | waitLine(); 502 | 503 | /* 504 | * PHASE 4 505 | */ 506 | 507 | Serial.println(); 508 | Serial.println(F("Starting phase 4...")); 509 | Serial.println(); 510 | 511 | // Reset the offset and select a frequency of 1kHz 512 | Tsunami.setOffset(0); 513 | Tsunami.setFrequency(0, 1000.0); 514 | Tsunami.selectFrequency(0); 515 | 516 | for(int point = 0; point < number_amp; point++ ) { 517 | int value = target_amp[point]; 518 | 519 | // Correct for the actual, measured output offset at zero offset setting and for the 520 | // discrepancy between the "peak-to-peak" setting versus "positive peak" measurement 521 | target_val[point] = actual_amp[point] / 2 + actual_zero_offset; 522 | 523 | Tsunami.setAmplitude(value); 524 | 525 | Serial.print(F("Measuring amplitude at ")); 526 | Serial.print((float)value/1000, 3); 527 | Serial.print(F(" Vpp (")); 528 | Serial.print((float)value/2000, 3); 529 | Serial.print(F(" Vpeak).")); 530 | 531 | delay(1000); 532 | 533 | actual_val[point] = 0; 534 | 535 | for (int count = 0; count < NUM_MEASURE; count++) { 536 | actual_val[point] += Tsunami.measurePeakVoltage(); 537 | 538 | delay(100); 539 | } 540 | 541 | actual_val[point] /= NUM_MEASURE; 542 | 543 | Serial.print(" Actual reading: "); 544 | Serial.print((float)actual_val[point]/1000, 3); 545 | Serial.println(F(" Vpeak.")); 546 | } 547 | 548 | // Calculate best fitting line based on actually produced output amplitudes previously 549 | // measured and reported by the user and "peak input voltage" measurements we just did 550 | regress(target_val, actual_val, number_amp, &slope, &intercept); 551 | 552 | // Calculate the calibration scale and shift that would cancel out such a linear error 553 | cal_scale[CAL_DATA_PEAK_VALUE] = 1.0 / slope; 554 | cal_shift[CAL_DATA_PEAK_VALUE] = - intercept / slope; 555 | 556 | Serial.println(); 557 | Serial.println(F("Phase 4 complete.")); 558 | Serial.println(); 559 | Serial.println(F("Please leave the Tsunami's output connected to its input.")); 560 | Serial.println(); 561 | Serial.println(F("Please press Enter to start phase 5.")); 562 | 563 | waitLine(); 564 | 565 | /* 566 | * PHASE 5 567 | */ 568 | 569 | Serial.println(); 570 | Serial.println(F("Starting phase 5...")); 571 | Serial.println(); 572 | 573 | // Hey, look! Deja-vu! Hold the DDS chip in reset, again... 574 | Tsunami.reset(true); 575 | 576 | for(int point = 0; point < number_ofs; point++ ) { 577 | int value = target_ofs[point]; 578 | 579 | Tsunami.setOffset(value); 580 | 581 | Serial.print(F("Measuring instant value at ")); 582 | Serial.print((float)value/1000, 3); 583 | Serial.print(F(" V.")); 584 | 585 | delay(1000); 586 | 587 | actual_val[point] = 0; 588 | 589 | for (int count = 0; count < NUM_MEASURE; count++) { 590 | actual_val[point] += Tsunami.measureCurrentVoltage(); 591 | 592 | delay(100); 593 | } 594 | 595 | actual_val[point] /= NUM_MEASURE; 596 | 597 | Serial.print(F(" Actual reading: ")); 598 | Serial.print((float)actual_val[point]/1000, 3); 599 | Serial.println(F(" V.")); 600 | } 601 | 602 | // Release the DDS chip from reset (again...) 603 | Tsunami.reset(false); 604 | 605 | // Calculate best fitting line based on actually produced output offsets as previously 606 | // measured and reported by the user and "current input voltage" data we just measured 607 | regress(actual_ofs, actual_val, number_ofs, &slope, &intercept); 608 | 609 | // Calculate the calibration scale and shift that would cancel out such a linear error 610 | cal_scale[CAL_DATA_CURRENT_VALUE] = 1.0 / slope; 611 | cal_shift[CAL_DATA_CURRENT_VALUE] = - intercept / slope; 612 | 613 | Serial.println(); 614 | Serial.println(F("Phase 5 complete.")); 615 | Serial.println(); 616 | Serial.println(F("Calibration complete. The recommended calibration coefficients are:")); 617 | Serial.println(); 618 | Serial.print(F("Output offset: scale = ")); 619 | Serial.print(cal_scale[CAL_DATA_OFFSET], 6); 620 | Serial.print(F(", shift = ")); 621 | Serial.print(cal_shift[CAL_DATA_OFFSET]/1000, 3); 622 | Serial.println(F(" V.")); 623 | Serial.print(F("Output amplitude: scale = ")); 624 | Serial.print(cal_scale[CAL_DATA_AMPLITUDE], 6); 625 | Serial.print(F(", shift = ")); 626 | Serial.print(cal_shift[CAL_DATA_AMPLITUDE]/1000, 3); 627 | Serial.println(F(" V.")); 628 | Serial.print(F("Input mean: scale = ")); 629 | Serial.print(cal_scale[CAL_DATA_MEAN_VALUE], 6); 630 | Serial.print(F(", shift = ")); 631 | Serial.print(cal_shift[CAL_DATA_MEAN_VALUE]/1000, 3); 632 | Serial.println(F(" V.")); 633 | Serial.print(F("Input peak: scale = ")); 634 | Serial.print(cal_scale[CAL_DATA_PEAK_VALUE], 6); 635 | Serial.print(F(", shift = ")); 636 | Serial.print(cal_shift[CAL_DATA_PEAK_VALUE]/1000, 3); 637 | Serial.println(F(" V.")); 638 | Serial.print(F("Input instant: scale = ")); 639 | Serial.print(cal_scale[CAL_DATA_CURRENT_VALUE], 6); 640 | Serial.print(F(", shift = ")); 641 | Serial.print(cal_shift[CAL_DATA_CURRENT_VALUE]/1000, 3); 642 | Serial.println(F(" V.")); 643 | Serial.println(); 644 | Serial.print(F("Would you like to apply and store these calibration coefficients? (y/n) ")); 645 | 646 | readLine(buf, BUFFER_SIZE); 647 | 648 | Serial.println(); 649 | 650 | if(tolower(buf[0]) == 'y') { 651 | Tsunami.setCalibrationData(CAL_DATA_OFFSET, cal_scale[CAL_DATA_OFFSET], cal_shift[CAL_DATA_OFFSET]); 652 | Tsunami.setCalibrationData(CAL_DATA_AMPLITUDE, cal_scale[CAL_DATA_AMPLITUDE], cal_shift[CAL_DATA_AMPLITUDE]); 653 | Tsunami.setCalibrationData(CAL_DATA_MEAN_VALUE, cal_scale[CAL_DATA_MEAN_VALUE], cal_shift[CAL_DATA_MEAN_VALUE]); 654 | Tsunami.setCalibrationData(CAL_DATA_PEAK_VALUE, cal_scale[CAL_DATA_PEAK_VALUE], cal_shift[CAL_DATA_PEAK_VALUE]); 655 | Tsunami.setCalibrationData(CAL_DATA_CURRENT_VALUE, cal_scale[CAL_DATA_CURRENT_VALUE], cal_shift[CAL_DATA_CURRENT_VALUE]); 656 | Serial.println(F("Calibration coefficients were applied and stored.")); 657 | } else { 658 | Serial.println(F("Calibration coefficients were NOT applied / stored.")); 659 | } 660 | 661 | Serial.println(); 662 | Serial.println(F("Press Enter if you wish to start again.")); 663 | Serial.println(); 664 | 665 | waitLine(); 666 | } 667 | -------------------------------------------------------------------------------- /examples/Calibration/Readme.txt: -------------------------------------------------------------------------------- 1 | The purpose of the Calibration sketch in the Tsunami library is to interactively 2 | generate - and persistently store - corrective calibration coefficients for two 3 | of Tsunami's output settings (the generated waveform's offset and amplitude) and 4 | three of its input measurements (the measured mean value, peak value and current 5 | value of the input). Accordingly, this is accomplished in five distinct phases, 6 | the first two (related to the output) requiring a generic digital multimeter to 7 | measure and enter actually generated output values; for the final three phases 8 | the input needs to be connected to the output with a short loop cable or wire, 9 | while the sketch automatically takes readings of the previously measured output 10 | values to calibrate the input as well. Finally, a list of suggested calibration 11 | coefficients is produced offering the chance to store them in the non-volatile 12 | EEPROM memory of the Tsunami making the calibration available to other sketches. 13 | Beyond its main purpose as an interactive calibration tool, the sketch can also 14 | be used to inspect, manually edit or reset calibration coefficients. 15 | 16 | It is important to note that in its current form calibration is NOT effective 17 | automatically, but needs to be explicitly loaded in the user sketch via one or 18 | more "useCalibrationData()" calls; the choices are to selectively load any of 19 | the individual calibration channels (offset, amplitude, mean value, peak value 20 | and current value), or all of them or none at all (unload all calibration data) 21 | Loading calibration data is risk free in the sense that absence of calibration 22 | values is detected by the library, resulting in an error being returned and no 23 | correction being applied. 24 | 25 | The calibration model assumes and corrects for a strictly linear error, such as 26 | a gain and / or offset error applied to the ideal value, and therefore corrects 27 | neither errors of non-linearity (if any exist) nor errors caused by degradation 28 | of performance at higher frequencies; the latter remains a task for the user if 29 | precise high-frequency output / input is important for a specific application. 30 | 31 | How exactly the corrective gain and offset values are obtained is of no concern 32 | to the Tsunami library - it merely accepts them and corrects its input / output 33 | accordingly; that said, the Calibration sketch does attempt to minimize errors 34 | caused by potential non-linearity by measuring the calibrated values in several 35 | points, calculating a linear regression (the line that fits the measured values 36 | with the least possible amount of total error) and proposing coefficients that 37 | correct for the deviation of that line from the ideal. 38 | 39 | The actual calibration API consists of only the following three functions: 40 | 41 | ---------------------------------------------------------------------------- 42 | 43 | uint8_t setCalibrationData(CalibratedValue value, float scale, float shift); 44 | 45 | Persistently stores and immediately applies calibration coefficients to a single 46 | value where "CalibratedValue" selects the value for which the coefficients are 47 | to be applied, with the following possible values: 48 | 49 | CAL_DATA_OFFSET 50 | CAL_DATA_AMPLITUDE 51 | CAL_DATA_MEAN_VALUE 52 | CAL_DATA_PEAK_VALUE 53 | CAL_DATA_CURRENT_VALUE 54 | 55 | while "scale" and "shift" are the corrective gain and offset to be applied to 56 | the nominal value, "scale" being dimensionless and "shift" being expressed in 57 | the units of the corrected value: in this case, millivolts for all five values. 58 | 59 | Returns "true" if values were successfully stored, "false" otherwise. 60 | 61 | Note: the actual generated output waveform will only be affected after the next 62 | offset or amplitude adjustment, NOT as soon as the calibration data is changed. 63 | 64 | ---------------------------------------------------------------------------- 65 | 66 | uint8_t getCalibrationData(CalibratedValue value, float *scale, float *shift); 67 | 68 | Retrieves (without applying) the current calibration coefficients from EEPROM 69 | persistent storage for a single value selected by "CalibratedValue" which may 70 | take the same values as above; "scale" and "shift" are also defined as above. 71 | 72 | Returns "true" if values were actually present and returned, "false" otherwise. 73 | 74 | Note: the values returned are the ones currently stored in Tsunami's EEPROM and 75 | NOT whatever is actually being currently loaded / in use. 76 | 77 | ---------------------------------------------------------------------------- 78 | 79 | uint8_t useCalibrationData(CalibratedValue value); 80 | 81 | Retrieves & applies the current calibration coefficients from EEPROM persistent 82 | storage for a single value or all values as selected by "CalibratedValue" which 83 | may take the same values as above or one of the following additional values: 84 | 85 | CAL_DATA_ALL 86 | CAL_DATA_NONE 87 | 88 | where the first loads & applies all calibration coefficients at once while the 89 | second unloads them all, returning to an "uncalibrated" state. Multiple calls 90 | for different individual values are perfectly possible, loading any single one 91 | does not unload the others: after loading a value unloading it is only possible 92 | with the collective "none" or by setting "scale" to "1.0" and "shift" to "0.0". 93 | 94 | Returns "true" if values were actually present and applied, "false" otherwise. 95 | -------------------------------------------------------------------------------- /examples/FrequencyGenerator/FrequencyGenerator.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * FrequencyGenerator.ino 3 | * Demonstration of frequency generation and measurement using the Tsunami. 4 | * 5 | * Accepts commands via the serial port at 115200 baud. 6 | * 7 | * Valid commands: 8 | * freq x 9 | * Sets the frequency to the specified value in hz. 10 | * amp x 11 | * Sets the peak-to-peak amplitude to the specified voltage between roughly 12 | * 0.32 and 6.15Vp-p. Note that the accuracy increases significantly for 13 | * smaller voltages, so large voltages may be approximate. 14 | * off x 15 | * Sets the DC offset to the specified voltage between roughly +2 and -2 16 | * volts. Note that the accuracy around 0 is significantly better than for 17 | * larger offsets. 18 | * wf (sine|triangle) 19 | * Sets the waveform output to sine or triangle. 20 | * measure 21 | * Measures the waveform on the input connector, returning "measured freq phase amp" 22 | * where freq is the frequency in hz, phase is the floating point phase offset in degrees, and amp is the 23 | * peak-peak amplitude in volts. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | void setup() { 31 | // Initialize the serial port 32 | Serial.begin(115200); 33 | 34 | // Initialize the Tsunami 35 | Tsunami.begin(); 36 | 37 | // Start with a default frequency of 1khz 38 | Tsunami.setFrequency(0, 1000.0); 39 | } 40 | 41 | // Sets the output frequency to the specified value 42 | void setFrequency(float freq) { 43 | // We alternate which register we use to avoid glitches in the output. 44 | static uint8_t frequency_register = 1; 45 | 46 | Tsunami.setFrequency(frequency_register, freq); 47 | Tsunami.selectFrequency(frequency_register); 48 | frequency_register = 1 - frequency_register; 49 | } 50 | 51 | // Reads a line from the serial port, without timeout 52 | int readLine(char *buf, int len) { 53 | int pos = 0; 54 | while(1) { 55 | if(Serial.available()) { 56 | buf[pos++] = Serial.read(); 57 | 58 | if(pos == len || buf[pos - 1] == '\n') 59 | return pos; 60 | } 61 | } 62 | } 63 | 64 | // Handles 'freq' commands 65 | void commandFreq(char *name, char *args) { 66 | setFrequency(atof(args)); 67 | Serial.println("ok"); 68 | } 69 | 70 | // Handles 'amp' commands 71 | void commandAmp(char *name, char *args) { 72 | int value = (int)(atof(args) * 1000); 73 | Tsunami.setAmplitude(value); 74 | 75 | Serial.println("ok"); 76 | } 77 | 78 | // Handles 'off' commands 79 | void commandOff(char *name, char *args) { 80 | int value = (int)(atof(args) * 1000); 81 | Tsunami.setOffset(value); 82 | 83 | Serial.println("ok"); 84 | } 85 | 86 | // Handles 'wf' commands 87 | void commandWf(char *name, char *args) { 88 | if(args[0] == 't' || args[0] == 'T') { 89 | Tsunami.setOutputMode(OUTPUT_MODE_TRIANGLE); 90 | } else { 91 | Tsunami.setOutputMode(OUTPUT_MODE_SINE); 92 | } 93 | 94 | Serial.println("ok"); 95 | } 96 | 97 | void commandMeasure(char *name, char *args) { 98 | double voltage = Tsunami.measurePeakVoltage(); 99 | double phase = Tsunami.measurePhase(); 100 | double frequency = Tsunami.measureFrequency(); 101 | 102 | Serial.print("measured "); 103 | Serial.print(frequency); 104 | Serial.print(" "); 105 | Serial.print(phase); 106 | Serial.print(" "); 107 | Serial.println(voltage); 108 | } 109 | 110 | // This is a shorthand name for a pointer to a function that handles a command 111 | typedef void (*command_handler)(char *, char *); 112 | 113 | // A command_t structure holds the name of a command and the function that 114 | // should handle it 115 | typedef struct { 116 | char *name; 117 | command_handler handler; 118 | } command_t; 119 | 120 | // An array of all the valid command handlers. An empty entry denotes the 121 | // end of the list. 122 | const command_t commands[] = { 123 | {"freq", &commandFreq}, 124 | {"amp", &commandAmp}, 125 | {"off", &commandOff}, 126 | {"wf", &commandWf}, 127 | {"measure", &commandMeasure}, 128 | {NULL, NULL} 129 | }; 130 | 131 | // Interprets a command and executes the relevant function 132 | void handleCommand(char *buf) { 133 | // Separate the command from its arguments 134 | char *name = strsep(&buf, " \n\r"); 135 | 136 | // Iterate over all the defined commands 137 | for(const command_t *command = commands; command->name != NULL; command++) { 138 | // Check if the current command matches 139 | if(strcasecmp(command->name, name) == 0) { 140 | // Execute the command handler 141 | command->handler(name, buf); 142 | return; 143 | } 144 | } 145 | 146 | // If we reach this point, the command requested doesn't match anything. 147 | Serial.print("err Unknown command "); 148 | Serial.println(name); 149 | } 150 | 151 | void loop() { 152 | char buf[40]; 153 | 154 | while(1) { 155 | readLine(buf, 40); 156 | handleCommand(buf); 157 | } 158 | } -------------------------------------------------------------------------------- /examples/SimpleFrequencyGenerator/SimpleFrequencyGenerator.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * SimpleFrequencyGenerator.ino 3 | * Demonstration of simple frequency generation using the Tsunami. 4 | * 5 | * Accepts frequencies as floating point numbers on the serial console at 6 | * 115200 baud, and sets the output frequency accordingly. Amplitude is 7 | * fixed at maximum and DC offset is fixed at 0. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | void setup() { 15 | // Initialize the serial port 16 | Serial.begin(115200); 17 | 18 | // Initialize the Tsunami 19 | Tsunami.begin(); 20 | 21 | // Start with a default frequency of 1khz 22 | Tsunami.setFrequency(0, 1000.0); 23 | } 24 | 25 | int readLine(char *buf, int len) { 26 | int pos = 0; 27 | while(1) { 28 | if(Serial.available()) { 29 | buf[pos++] = Serial.read(); 30 | 31 | if(pos == len || buf[pos - 1] == '\n') 32 | return pos; 33 | } 34 | } 35 | } 36 | 37 | void setFrequency(float freq) { 38 | // We alternate which register we use to avoid glitches in the output. 39 | static uint8_t frequency_register = 1; 40 | 41 | Tsunami.setFrequency(frequency_register, freq); 42 | Tsunami.selectFrequency(frequency_register); 43 | frequency_register = 1 - frequency_register; 44 | } 45 | 46 | void loop() { 47 | char buf[20]; 48 | 49 | while(1) { 50 | readLine(buf, 20); 51 | setFrequency(atof(buf)); 52 | } 53 | } -------------------------------------------------------------------------------- /examples/SineOutput/SineOutput.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * SineOutput.ino 3 | * Generates a 400Hz sine wave using the Tsunami. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | void setup() { 11 | // Initialize the Tsunami 12 | Tsunami.begin(); 13 | } 14 | 15 | void loop() { 16 | float frequency; 17 | while(1) { 18 | frequency = 110.0; 19 | while(frequency < 8000.0) { 20 | Tsunami.setFrequency(frequency); 21 | frequency *= 1.05946309; // 12th root of 2 22 | delay(500); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/TapeOutput/TapeOutput.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * TapeOutput.ino 3 | * Demonstration of encoding data for storage on tape using a modification of 4 | * the Kansas City standard (http://en.wikipedia.org/wiki/Kansas_City_standard). 5 | * 6 | * This example uses the DDS's support for toggling between two frequencies 7 | * using a hardware pin by pointing a SoftwareSerial port at it and writing 8 | * data, which then gets translated directly into frequency-shift keyed data, 9 | * just like 8-bit home computers used to store and retrieve data on tape. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // Create a SoftwareSerial instance that writes to the DDS's frequency 18 | // select pin 19 | SoftwareSerial out(TSUNAMI_DDS_FSEL, TSUNAMI_DDS_FSEL); 20 | 21 | void setup() { 22 | // Initialize the Tsunami 23 | Tsunami.begin(); 24 | 25 | // Turn output off 26 | Tsunami.sleep(true); 27 | 28 | // Set the frequencies for 0 and 1 bits 29 | Tsunami.setFrequency(0, 1200l); 30 | Tsunami.setFrequency(1, 2400l); 31 | 32 | // Initialize the serial output at 300 baud 33 | out.begin(300); 34 | } 35 | 36 | void loop() { 37 | while(1) { 38 | // Wait until there's data 39 | while(!Serial.available()); 40 | 41 | // Turn the output on and emit a short leader of 1 bits 42 | Tsunami.sleep(false); 43 | delay(3); 44 | 45 | // While there's data, write it out on the serial interface 46 | while(Serial.available()) { 47 | out.write(Serial.read()); 48 | } 49 | 50 | // Include a short trailer of 1 bits before disabling the output again 51 | delay(3); 52 | Tsunami.sleep(true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/WaveformMeasurement/WaveformMeasurement.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * WaveformMeasurement.ino 3 | * Reports basic waveform measurements over USB serial. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | void setup() { 11 | Serial.begin(115200); 12 | 13 | // Initialize the Tsunami 14 | Tsunami.begin(); 15 | } 16 | 17 | void loop() { 18 | while(1) { 19 | Serial.print(Tsunami.measureFrequency()); 20 | Serial.print(" "); 21 | Serial.println(Tsunami.measurePeakVoltage()); 22 | delay(1000); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/selftest/selftest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define LED_PIN 13 7 | #define RUN_REPEATEDLY false 8 | 9 | char err[255]; 10 | 11 | typedef bool (*testfunction)(void); 12 | 13 | typedef struct { 14 | char *name; 15 | testfunction func; 16 | } test_t; 17 | 18 | void log_value(char *name, int32_t value) { 19 | char buf[32]; 20 | sprintf(buf, "# %s = %ld", name, value); 21 | Serial.println(buf); 22 | } 23 | 24 | void log_value(char *name, double value) { 25 | char buf[32]; 26 | sprintf(buf, "# %s = %f", name, value); 27 | Serial.println(buf); 28 | } 29 | 30 | bool _assert_nearly_equal(char *name, double value, double target, double error) { 31 | log_value(name, value); 32 | if(value < target - error || value > target + error) { 33 | sprintf(err, "%s: %f is not between %f and %f", name, value, target - error, target + error); 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | bool _assert_nearly_equal(char *name, int32_t value, int32_t target, int32_t error) { 40 | log_value(name, value); 41 | if(value < target - error || value > target + error) { 42 | sprintf(err, "%s: %ld is not between %ld and %ld", name, value, target - error, target + error); 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | bool _assert_nearly_equal(char *name, int16_t value, int16_t target, int16_t error) { 49 | return _assert_nearly_equal(name, (int32_t)value, (int32_t)target, (int32_t)error); 50 | } 51 | 52 | #define assert_nearly_equal(value, min, max, errmsg) if(!_assert_nearly_equal(value, min, max, errmsg)) return false 53 | 54 | bool read_usb_id() { 55 | Serial.print("# usb_id = "); 56 | for(int i = 0; i < 10; i++) 57 | Serial.print(boot_signature_byte_get((0x07 * 2) + i), HEX); 58 | Serial.println(); 59 | return true; 60 | } 61 | 62 | bool test_zero_offset() { 63 | Tsunami.measureCurrentVoltage(); 64 | delay(200); 65 | assert_nearly_equal("input_offset", Tsunami.measureCurrentVoltage(), 25, 200); 66 | return true; 67 | } 68 | 69 | bool test_offset_set() { 70 | delay(200); 71 | int offset = Tsunami.measureCurrentVoltage(); 72 | Tsunami.setOffset(-1000); 73 | delay(200); 74 | assert_nearly_equal("output_offset_neg", Tsunami.measureCurrentVoltage(), offset - 1340, 100); 75 | Tsunami.setOffset(1000); 76 | delay(200); 77 | assert_nearly_equal("output_offset_pos", Tsunami.measureCurrentVoltage(), offset + 1265, 250); 78 | return true; 79 | } 80 | 81 | bool test_amplitude_set() { 82 | Tsunami.setFrequency(32768); 83 | Tsunami.setAmplitude(6000); 84 | delay(500); 85 | assert_nearly_equal("input_amp_6", Tsunami.measurePeakVoltage(), 3170, 150); 86 | 87 | Tsunami.setAmplitude(1000); 88 | pinMode(TSUNAMI_PEAK, OUTPUT); 89 | pinMode(TSUNAMI_PEAK, INPUT); 90 | delay(1000); 91 | assert_nearly_equal("input_amp_1", Tsunami.measurePeakVoltage(), 580, 200); 92 | 93 | Tsunami.setAmplitude(0); 94 | delay(500); 95 | assert_nearly_equal("input_amp_0", Tsunami.measurePeakVoltage(), 70, 200); 96 | 97 | return true; 98 | } 99 | 100 | bool test_freq_phase() { 101 | Tsunami.setAmplitude(6000); 102 | Tsunami.setFrequency(1000.0); 103 | delay(100); 104 | assert_nearly_equal("input_freq_1k", (int32_t)Tsunami.measureFrequency(), 1000l, 2l); 105 | assert_nearly_equal("input_phase_1k", (int16_t)(1000 * Tsunami.measurePhase()), 30, 30); 106 | 107 | Tsunami.setFrequency(100000.0); 108 | delay(100); 109 | assert_nearly_equal("input_freq_100k", (int32_t)Tsunami.measureFrequency(), 100000l, 2l); 110 | assert_nearly_equal("input_phase_100k", (int16_t)(1000 * Tsunami.measurePhase()), 210, 25); 111 | 112 | Tsunami.setFrequency(1000000.0); 113 | // Reset phase counter for sensitive measurement 114 | Tsunami.reset(true); Tsunami.reset(false); 115 | delay(100); 116 | assert_nearly_equal("input_freq_1m", (int32_t)Tsunami.measureFrequency(), 1000000l, 2l); 117 | assert_nearly_equal("input_phase_1m", (int16_t)(1000 * Tsunami.measurePhase()), 999, 2); 118 | } 119 | 120 | test_t tests[] = { 121 | {"Read USB ID", &read_usb_id}, 122 | {"Test zero offset", &test_zero_offset}, 123 | {"Test offset adjustment", &test_offset_set}, 124 | {"Test amplitude adjustment", &test_amplitude_set}, 125 | {"Test frequency and phase", &test_freq_phase}, 126 | {NULL, NULL} 127 | }; 128 | 129 | void setup() { 130 | pinMode(LED_PIN, OUTPUT); 131 | Serial.begin(115200); 132 | delay(2000); 133 | } 134 | 135 | int run_tests() { 136 | #if RUN_REPEATEDLY 137 | // Persist any errors we see between runs 138 | static int first_failure = 0; 139 | #else 140 | int first_failure = 0; 141 | #endif 142 | 143 | for(int i = 0; tests[i].func != NULL; i++) { 144 | Tsunami.begin(); 145 | 146 | bool result = tests[i].func(); 147 | Serial.print(tests[i].name); 148 | Serial.print("... "); 149 | if(!result) { 150 | Serial.println("FAIL"); 151 | Serial.println(err); 152 | if(first_failure == 0) 153 | first_failure = i + 1; 154 | } else { 155 | Serial.println("OK"); 156 | } 157 | } 158 | return first_failure; 159 | } 160 | 161 | void error_code(int code) { 162 | while(1) { 163 | unsigned long when; 164 | for(int i = 0; i < code * 2; i++) { 165 | digitalWrite(LED_PIN, i & 1); 166 | when = millis() + 500; 167 | while(millis() < when) { 168 | if(Serial.read() != -1) 169 | return; 170 | } 171 | } 172 | 173 | // Longer delay at the end 174 | when = millis() + 3000; 175 | while(millis() < when) { 176 | if(Serial.read() != -1) 177 | return; 178 | } 179 | 180 | #if RUN_REPEATEDLY 181 | return; 182 | #endif 183 | } 184 | } 185 | 186 | void loop() { 187 | delay(1000); 188 | Serial.println("Executing test suite..."); 189 | int result = run_tests(); 190 | if(result == 0) { 191 | Serial.println("All tests passed."); 192 | digitalWrite(LED_PIN, HIGH); 193 | } else { 194 | Serial.println("Selftest FAIL."); 195 | } 196 | 197 | Serial.println("Press enter to rerun test suite."); 198 | // Flash error code while waiting for input 199 | error_code(result); 200 | Serial.println(); 201 | } 202 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Tsunami KEYWORD1 2 | tsunami KEYWORD1 3 | setOutputMode KEYWORD2 4 | setFrequency KEYWORD2 5 | reset KEYWORD2 6 | sleep KEYWORD2 7 | selectFrequency KEYWORD2 8 | selectPhase KEYWORD2 9 | setAmplitude KEYWORD2 10 | measurePeakVoltage KEYWORD2 11 | measureMeanVoltage KEYWORD2 12 | measureCurrentVoltage KEYWORD2 13 | measureFrequency KEYWORD2 14 | measureAverageFrequency KEYWORD2 15 | measurePhase KEYWORD2 16 | setOffset KEYWORD2 17 | enableSignOutput KEYWORD2 18 | disableSignOutput KEYWORD2 19 | enableAuxiliaryFiltering KEYWORD2 20 | disableAuxiliaryFiltering KEYWORD2 21 | setCalibrationData KEYWORD2 22 | getCalibrationData KEYWORD2 23 | useCalibrationData KEYWORD2 24 | OUTPUT_MODE_SINE LITERAL1 25 | OUTPUT_MODE_TRIANGLE LITERAL1 26 | CAL_DATA_OFFSET LITERAL1 27 | CAL_DATA_AMPLITUDE LITERAL1 28 | CAL_DATA_MEAN_VALUE LITERAL1 29 | CAL_DATA_PEAK_VALUE LITERAL1 30 | CAL_DATA_CURRENT_VALUE LITERAL1 31 | CAL_DATA_ALL LITERAL1 32 | CAL_DATA_NONE LITERAL1 33 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Tsunami 2 | version=1.0.0 3 | author=Nick Johnson 4 | maintainer=Nick Johnson 5 | sentence=Library for interfacing with the Tsunami's hardware peripherals. 6 | paragraph=Provides an easy to use API for programming the Tsunami. 7 | category=Signal Input/Output 8 | url=https://github.com/arachnidlabs/tsunami-arduino 9 | architectures=avr 10 | -------------------------------------------------------------------------------- /production.hex: -------------------------------------------------------------------------------- 1 | :020000040000FA 2 | :200000000C946E010C9496010C9496010C9496010C9496010C9496010C9496010C94960150 3 | :200020000C9496010C9496010C94850F0C94510E0C9496010C9496010C9496010C94960143 4 | :200040000C94FA050C9496010C9496010C9496010C945C060C9496010C9496010C94A9089B 5 | :200060000C9496010C9496010C9496010C9496010C9496010C9496010C9496010C949601C8 6 | :200080000C9496010C9496010C9496010C9496010C9496010C9496010C9496010C949601A8 7 | :2000A0000C9496010C9496010C949601F609F909E809EC09F2091A0A1A0A1A0AFD09010A3C 8 | :2000C000050A0B0A0F0A1A0A150A070605040100080A0B0C0D090000000200090F0000032D 9 | :2000E0000D01000C0000000000000000000000000000000000000408020110408040102097 10 | :200100004080408008020401804020100201108010204040200104040404040304050202D8 11 | :200120000202040302020202060606060606040402020204040200000000250028002B00F8 12 | :200140002E00310000000000240027002A002D003000080B00020202010009040000010244 13 | :200160000200000524001001052401010104240206052406000107058103100040090401C9 14 | :2001800000020A000000070502024000000705830240000000C18081C11201000202000098 15 | :2001A000400912B51A00010102000112010002000000400912B51A000101020001417261B8 16 | :2001C00063686E6964204C616273005473756E616D69000403090400000000000000002A58 17 | :2001E0002B280000000000000000000000000000000000000000002C9EB4A0A1A2A434A6CD 18 | :20020000A7A5AE362D3738271E1F20212223242526B333B62EB7B89F8485868788898A8BA5 19 | :200220008C8D8E8F909192939495969798999A9B9C9D2F3130A3AD350405060708090A0BFC 20 | :200240000C0D0E0F101112131415161718191A1B1C1DAFB1B0B50009040200010300000055 21 | :200260000921010100012265000705840340000105010902A1010901A100850105091901EA 22 | :2002800029031500250195037501810295017505810305010930093109381581257F750861 23 | :2002A00095038106C0C005010906A1018502050719E029E7150025017501950881029501E5 24 | :2002C0007508810395067508150025650507190029658100C00098074D0B261111241FBE32 25 | :2002E000CFEFDAE0DEBFCDBF13E0A0E0B1E0E4EBFBE202C005900D92A033B107D9F724E058 26 | :20030000A0E3B3E001C01D92A03BB207E1F712E0CCEDD2E004C02297FE010E94E112C63D7A 27 | :20032000D107C9F70E946B0B0C94D8150C9400001F93CF93DF936FE271E08AE494E00E9434 28 | :20034000BC0BCEE0D0E011E2FE0110935700649140E150E08AE494E00E94510C2196C831B5 29 | :20036000D10591F78AE494E00E94C50B81E0DF91CF911F91089587E10E94AD09DC0120E0B0 30 | :2003800032E746E050E00E94E512845E9C40089586E10E94AD09DC0120E032E746E050E0EF 31 | :2003A0000E94E512845E9C40089583E10E94AD09BC01882777FD8095982F0E94CE1120E050 32 | :2003C00030E040E85AE30E945A1208951F93CF93DF93EC01CB01BA0111E02E85121B1E878D 33 | :2003E00020E030E040E85DE40E945A1220E034E244E75BE40E9433110E94A0119B01AC0174 34 | :20040000612FCE0108960E94C0076E8585E0DF91CF911F910C94540A0F931F93CF93DF9378 35 | :20042000CDB7DEB7A0970FB6F894DEBF0FBECDBF7F936F935F934F939F938F938BE391E00A 36 | :200440009F938F938E010F5F1F4F1F930F930E940713B8018AE494E00E94D90B0FB6F8945B 37 | :20046000DEBF0FBECDBFA0960FB6F894DEBF0FBECDBFDF91CF911F910F9108954F925F927A 38 | :200480006F927F928F929F92AF92BF92CF92DF92EF92FF920F931F93CF93DF93EC014A0122 39 | :2004A0005B01280139010E940C02B301A2014C195D096E097F094C0C5D1C6E1C7F1C84161C 40 | :2004C0009506A606B7062CF0481459046A047B0414F57F926F925F924F927F936F935F9363 41 | :2004E0004F93BF92AF929F928F92DF93CF9380E791E09F938F9380E393E09F938F930E946A 42 | :2005000007138DB79EB742960FB6F8949EBF0FBE8DBF80E001C081E0DF91CF911F910F91E7 43 | :20052000FF90EF90DF90CF90BF90AF909F908F907F906F905F904F900895CF92DF92EF9237 44 | :20054000FF920F931F930E94C80168EC70E080E090E00E9422090E94C801AC01662757FD11 45 | :200560006095762F88ECC82ED12CE12CF12C09E110E020E030E083E991E00E943E021F91F7 46 | :200580000F91FF90EF90DF90CF900895CF92DF92EF92FF920F931F93CF93DF9368EC70E092 47 | :2005A00080E090E00E9422090E94C801EC014BED55E061E080E394E00E94990868EC70E0DA 48 | :2005C00080E090E00E9422090E94C8018E010C531540222717FD2095322FAC01662757FDCF 49 | :2005E0006095762FE4E6CE2ED12CE12CF12C80EA91E00E943E02882321F144E25AE061E059 50 | :2006000080E394E00E94990868EC70E080E090E00E9422090E94C8018E010F501B4F222773 51 | :2006200017FD2095322FAC01662757FD6095762F9AEFC92ED12CE12CF12C82EB91E00E9441 52 | :200640003E02DF91CF911F910F91FF90EF90DF90CF900895CF92DF92EF92FF920F931F9389 53 | :2006600061E080913E04681B60933E0420E033E648E050E088E394E00E94C00760913E0442 54 | :2006800085E00E94540A47E751E060E080E394E00E94990864EF71E080E090E00E942209FB 55 | :2006A0000E94BB01AC01662757FD6095762FA6E9CA2ED12CE12CF12C02E61CE020E030E012 56 | :2006C00084EC91E00E943E02882309F447C043E95DE060E080E394E00E94990861E087E13C 57 | :2006E0000E941B0A60E087E10E941B0A68EE73E080E090E00E9422090E94BB01AC016627E6 58 | :2007000057FD6095762FF8ECCF2ED12CE12CF12C04E412E020E030E080ED91E00E943E0239 59 | :200720008823E1F04FEF5FE060E080E394E00E94990864EF71E080E090E00E9422090E9483 60 | :20074000BB01AC01662757FD6095762F06E410E020E030E08CED91E00E943E021F910F91AF 61 | :20076000FF90EF90DF90CF900895CF92DF92EF92FF920F931F9347E751E060E080E394E052 62 | :200780000E94990840E050E06AE774E480E394E00E94E60164E670E080E090E00E94220986 63 | :2007A00080E394E00E9453070E949B11AB01BC0192E0C92ED12CE12CF12C08EE13E020E036 64 | :2007C00030E088EE91E00E943E02882309F4AAC00E94D50120E030E04AE754E40E945A122F 65 | :2007E0000E949B11AB01662757FD6095762F8EE1C82ED12CE12CF12C0EE110E020E030E009 66 | :2008000086EF91E00E943E02882309F48BC040E050E563EC77E480E394E00E94E60164E674 67 | :2008200070E080E090E00E94220980E394E00E9453070E949B11AB01BC0102E0C02ED12C74 68 | :20084000E12CF12C00EA16E821E030E085E092E00E943E02882309F465C00E94D50120E077 69 | :2008600030E04AE754E40E945A120E949B11AB01662757FD6095762F19E1C12ED12CE12C89 70 | :20088000F12C02ED10E020E030E085E192E00E943E02882309F446C040E054E264E779E4E6 71 | :2008A00080E394E00E94E60161E08BE00E94540A60E08BE00E94540A64E670E080E090E017 72 | :2008C0000E94220980E394E00E9453070E949B11AB01BC01B2E0CB2ED12CE12CF12C00E42B 73 | :2008E00012E42FE030E086E292E00E943E028823C9F00E94D50120E030E04AE754E40E9430 74 | :200900005A120E949B11AB01662757FD6095762F07EE13E020E030E084E392E00E943E0243 75 | :20092000811101C080E01F910F91FF90EF90DF90CF90089561E08DE00E941B0A40E052EC68 76 | :2009400061E070E08AE494E00E94470B60ED77E080E090E00C942209DF92EF92FF920F93CC 77 | :200960001F93CF93DF93C0E0D1E0EE24E394F12C00E010E08A819B81892B91F180E394E0F6 78 | :200980000E94A106EA81FB810995D82E688179818AE494E00E94BC0B63E472E08AE494E0DA 79 | :2009A0000E94BC0BD11011C068E472E08AE494E00E94D90B60E373E08AE494E00E94D90B18 80 | :2009C0000115110541F4870106C06DE472E08AE494E00E94D90B8FEFE81AF80A2496CACF88 81 | :2009E000C801DF91CF911F910F91FF90EF90DF900895CF92DF92EF92FF920F931F93CF935A 82 | :200A0000DF93EC01CC0FDD1F00E010E00C171D0704F5602F61708DE00E94540A0E94F30826 83 | :200A20006B017C0124EFC20E21E0D21EE11CF11C0E94F3086C157D058E059F0538F48AE47E 84 | :200A400094E00E94C40A019699F31AC00F5F1F4FDDCF0E94F3086B017C0188EBC80E8BE0F3 85 | :200A6000D81EE11CF11C0E94F3086C157D058E059F0550F68AE494E00E94C40A019699F3E4 86 | :200A8000DF91CF911F910F91FF90EF90DF90CF900895CF93DF9368EE73E080E090E00E94CE 87 | :200AA000220960E572E08AE494E00E94D90B0E94AC04EC01009759F468E672E08AE494E067 88 | :200AC0000E94D90B61E08DE00E94540A06C06AE772E08AE494E00E94D90B69E872E08AE400 89 | :200AE00094E00E94D90BCE010E94F9048AE494E0DF91CF910C94C50BCF93CFB7F8948091E7 90 | :200B00002F04811127C0E7E2F1E08491E7E0F1E09491E82FF0E0EE0FFF1FEC5BFE4FA591F1 91 | :200B2000B4918C91892321F461E081E10E94540A61E081E10E941B0A8CB580618CBD8CB5D9 92 | :200B400080648CBD61E08FE00E941B0A61E080E10E941B0A80912F048F5F80932F04CFBF82 93 | :200B6000CF9108956091400464708FE10E94540A6091400468708EE10C94540AEF92FF9273 94 | :200B80000F931F93CF93DF93EC018B017A018981811103C08CB5806202C08CB58F7D8CBD5F 95 | :200BA0008CB5837F9A81892B8CBD60E088810E94540A81E090E00E944A09F801CF01801B67 96 | :200BC000910B8E159F0544F481918EBD00000DB407FEFDCF8EB5F2CF81E090E00E944A0941 97 | :200BE00061E08881DF91CF911F910F91FF90EF900C94540A1F920F920FB60F9211242F93D0 98 | :200C00003F934F935F936F937F938F939F93AF93BF93EF93FF93809186009091870020912E 99 | :200C2000220130912301821B930B909321018093200180918600909187009093230180935F 100 | :200C4000220180912001909121018115944080F4809140048C30F8F4809140048C5F8093CE 101 | :200C600040040E94B20510921F0110921E0113C080911E0190911F01009739F097FD0BC0F1 102 | :200C800004974CF481E090E002C083E090E090931F0180931E01FF91EF91BF91AF919F91CE 103 | :200CA0008F917F916F915F914F913F912F910F900FBE0F901F9018951F920F920FB60F9285 104 | :200CC00011242F933F934F935F936F937F938F939F93AF93BF93EF93FF9380911E01909116 105 | :200CE0001F01019739F482E090E090931F0180931E010FC080914004843058F08091400453 106 | :200D00008450809340040E94B20510921F0110921E01FF91EF91BF91AF919F918F917F91CC 107 | :200D20006F915F914F913F912F910F900FBE0F901F9018954BE151E06EEB75E008960C94A3 108 | :200D4000B6070F931F93CF93DF93EC0161E089E00E941B0A60E089E00E94540A61E085E001 109 | :200D60000E941B0A60E085E00E94540A61E086E00E941B0A60E086E00E94540A61E087E04B 110 | :200D80000E941B0A61E087E00E94540A61E088E00E941B0A61E088E00E94540A61E084E125 111 | :200DA0000E941B0A60E084E10E94540A61E08FE10E941B0A61E08EE10E941B0A0E94B2057F 112 | :200DC0000E947C058CB58C7F8CBD8DB581608DBD61E08BE00E941B0A61E08BE00E94540ACF 113 | :200DE0008E01085F1F4FC8010E94010868E270E0C8010E94220860E08BE00E94540A48E118 114 | :200E000051E06EEB75E0CE010E944E0841E060E0CE010E94650840E050E060E0CE010E94EC 115 | :200E2000760840E060E0CE010E94880840E050E060E0CE010E94990841E061E0CE010E945E 116 | :200E4000650841E050E061E0CE010E94760840E061E0CE010E94880840E058E061E0CE01DA 117 | :200E60000E94990883E00E94AA091E861F86E0E8F0E080818C7F8083E1E8F0E08081827EED 118 | :200E80008083808182608083EFE6F0E0808181628083E0ECF0E0808181608083DF91CF918C 119 | :200EA0001F910F910895CF92DF92EF92FF92CF93F89480911E0190911F01019789F08091E0 120 | :200EC0001E0190911F01029759F060E070E080EC9FE77894CF91FF90EF90DF90CF90089569 121 | :200EE0006091200170912101C091400480E090E00E94CC119B01AC0160E074E284EF99E40A 122 | :200F00000E9433116B017C0161E070E002C0660F771FCA95E2F7882777FD8095982F0E94CB 123 | :200F2000CE119B01AC01C701B6010E945A12D1CF80E394E00C949A06CF93DF9300D0CDB71D 124 | :200F4000DEB779836A83DC01ED91FC91119742E050E0BE016F5F7F4F12968D919C91139739 125 | :200F600009950F900F90DF91CF910895FC01718360835383428380E092E295838483089529 126 | :200F8000EF92FF921F93CF93DF9300D000D0CDB7DEB77C01162F662319F0E0E0F0E802C052 127 | :200FA000E0E0F0E4B9017F736E2B7F2BC70129833A834B835C830E949C0729813A814B815A 128 | :200FC0005C81112319F080E090E802C080E090E4BA01A9012EE076956795579547952A9588 129 | :200FE000D1F7BA017F73682B792BC7010F900F900F900F90DF91CF911F91FF90EF900C94D3 130 | :201000009C07CF93DF93EC016C817D810E949C0720E030E0A90160E0CE010E94C00720E00A 131 | :2010200030E0A90161E0CE010E94C00760E070ECCE010E949C0760E070EECE01DF91CF9190 132 | :201040000C949C07FC0124813581257C622B732B758364830C949C07CF93DF9300D0CDB7DF 133 | :20106000DEB7FC01E60FF11DE60FF11D34962081318139832A83DC01ED91FC91119742E0A0 134 | :2010800050E0BE016F5F7F4F12968D919C91139709950F900F90DF91CF910895CF93DF930B 135 | :2010A000EC01798368835B834A831D821C8280E090E89F838E8360E0CE010E942C0861E043 136 | :2010C000CE01DF91CF910C942C08E62FF0E0EE0FFF1FE80FF91F24813581442311F0306437 137 | :2010E00001C03F7B358324830C942C08E62FF0E0EE0FFF1FE80FF91F248135814130510511 138 | :2011000011F43F7D01C03062358324830C942C08E62FF0E0EE0FFF1FE80FF91F248135811E 139 | :20112000442311F03F7E01C03061358324830C942C08FC01E60FF11DE60FF11D24813581A7 140 | :201140002227307F5F70242B352B358324830C942C081F920F920FB60F9211242F933F9365 141 | :201160008F939F93AF93BF938091420490914304A0914404B09145043091410423E0230F8A 142 | :201180002D3720F40196A11DB11D05C026E8230F0296A11DB11D2093410480934204909317 143 | :2011A0004304A0934404B09345048091460490914704A0914804B09149040196A11DB11DEC 144 | :2011C0008093460490934704A0934804B0934904BF91AF919F918F913F912F910F900FBE59 145 | :2011E0000F901F9018952FB7F894609142047091430480914404909145042FBF08953FB7BF 146 | :20120000F8948091460490914704A0914804B091490426B5A89B05C02F3F19F00196A11DC1 147 | :20122000B11D3FBF6627782F892F9A2F620F711D811D911D42E0660F771F881F991F4A9577 148 | :20124000D1F70895CF92DF92EF92FF92CF93DF936B017C010E94FF08EB01C114D104E10464 149 | :20126000F10489F00E94EF0F0E94FF086C1B7D0B683E734090F381E0C81AD108E108F108CE 150 | :20128000C851DC4FEACFDF91CF91FF90EF90DF90CF900895019739F0880F991F880F991FA9 151 | :2012A00002970197F1F70895789484B5826084BD84B5816084BD85B5826085BD85B581603C 152 | :2012C00085BDEEE6F0E0808181608083E1E8F0E01082808182608083808181608083E0E885 153 | :2012E000F0E0808181608083E1E9F0E0808182608083808181608083E0E9F0E08081816059 154 | :201300008083E1ECF0E0808184608083808182608083808181608083E3ECF0E080818160F9 155 | :201320008083E0ECF0E0808182608083E2ECF0E0808181608083EAE7F0E08081846080831C 156 | :201340008081826080838081816080838081806880830895809324010895823108F08251E6 157 | :20136000E82FF0E0E653FF4FE49180917B00E3FB222720F930E095E0220F331F9A95E1F7AF 158 | :201380008F7D822B80937B00E7708091240120E4829FC0011124E82BE0937C0080917A00D1 159 | :2013A000806480937A0080917A0086FDFCCF809178002091790090E0922B089590E0FC01F9 160 | :2013C0003197EF30F105B0F5EA5AFF4F0C94DF12809180008F7703C0809180008F7D80935E 161 | :2013E0008000089580918000877FF9CF84B58F7702C084B58F7D84BD0895809190008F77A6 162 | :2014000007C0809190008F7D03C080919000877F8093900008958091C0008F7703C0809103 163 | :20142000C0008F7D8093C00008958091C200877F8093C2000895CF93DF9390E0FC01EA50AA 164 | :20144000FF4F2491FC01EA5EFE4F8491882349F190E0880F991FFC01EC5BFE4FA591B49132 165 | :201460008A5C9E4FFC01C591D4919FB7611108C0F8948C91209582238C93888182230AC0B7 166 | :20148000623051F4F8948C91322F309583238C938881822B888304C0F8948C91822B8C93E7 167 | :2014A0009FBFDF91CF9108950F931F93CF93DF931F92CDB7DEB7282F30E0F901EA52FF4F84 168 | :2014C0008491F901EA50FF4F1491F901EA5EFE4F04910023C9F0882321F069830E94DE099F 169 | :2014E0006981E02FF0E0EE0FFF1FEA5CFE4FA591B4919FB7F8948C91611103C0109581237D 170 | :2015000001C0812B8C939FBF0F90DF91CF911F910F910895CF93DF931F92CDB7DEB76983FB 171 | :20152000DC01ED91FC910280F381E02D41E050E0BE016F5F7F4F09950F90DF91CF9108956A 172 | :20154000CF93DF93EC018C859D8597FF05C082E00E94270D9D878C878C859D85DF91CF91CA 173 | :20156000089583E00C947B0FFC018485958597FD06C082E00E94D00C90E00196089582E0E1 174 | :201580000E94D00C90E00895FC018485958597FD05C02FEF3FEF35872487089582E00C94F5 175 | :2015A000270DCF93DF93EC0180912C01882331F083E00E94580D1816190634F081E090E080 176 | :2015C0009B838A8380E090E0DF91CF910895FC0120812E5F208342E450E062E571E080E87F 177 | :2015E0000C94F20DFC0181819081913A59F4813209F03CC047E050E065E271E080E00E948B 178 | :20160000F20D43C0913291F5803239F467E070E085E291E00E94230E06C0823209F035C0F6 179 | :20162000828180932C018091250190912601A0912701B0912801803B9440A105B105C1F485 180 | :2016400080912C0180FD14C087E797E790930108809300082BE088E190E00FB6F894A89556 181 | :20166000809360000FBE209360000FC080E0089588E10FB6F89480936000109260000FBE4F 182 | :20168000A895109201081092000881E008952FEF3FEFFC0135872487089510924D041092D8 183 | :2016A0004C0488EE93E0A0E0B0E080934E0490934F04A0935004B093510485E193E09093EB 184 | :2016C0004B0480934A048FEF9FEF9093570480935604089508950E9454090E946A0B8AE9A0 185 | :2016E00094E00E94D30F0E949A04C0E0D0E00E9449052097E1F30E940000F9CFCF92DF92AB 186 | :20170000EF92FF920F931F93CF93DF936C017A01EB01E60EF71E00E010E0CE15DF0561F0CA 187 | :201720006991D601ED91FC910190F081E02DC6010995080F191FF1CFC801DF91CF911F9101 188 | :201740000F91FF90EF90DF90CF9008956115710581F0DB010D900020E9F7AD014150510901 189 | :20176000461B570BDC01ED91FC910280F381E02D099480E090E008950C94A60BDC01ED9105 190 | :20178000FC910190F081E02D09940F931F93CF93DF93EC016DE00E94BE0B8C016AE0CE019D 191 | :2017A0000E94BE0B800F911FDF91CF911F910F9108950F931F93CF93DF93EC010E94A60B5A 192 | :2017C0008C01CE010E94C50B800F911FDF91CF911F910F9108958F929F92AF92BF92CF92FA 193 | :2017E000DF92EF92FF920F931F93CF93DF93CDB7DEB7A1970FB6F894DEBF0FBECDBF7C0129 194 | :20180000C42EE52FCB01D22E19A221E02D1510F02AE0D22E8E010F5D1F4F8D2C912CA12C42 195 | :20182000B12C6C2D7E2FA50194010E94BD128C2DD29E80191124015011098A3014F4805DD8 196 | :2018400001C0895CF8018083211531054105510521F0C22EE32FCA01E4CFB801C7010E942A 197 | :20186000A60BA1960FB6F894DEBF0FBECDBFDF91CF911F910F91FF90EF90DF90CF90BF90EE 198 | :20188000AF909F908F9008952115310541F4DC01ED91FC910190F081E02D642F09940C94B6 199 | :2018A000EB0B9A01462F50E060E070E00C94440C40919E0450919F0420919C0430919D04C8 200 | :2018C00042175307B4F49091E8009570E1F39091E80092FD19C08093F10080919E04909182 201 | :2018E0009F0401968F739927892B19F48EEF8093E80080919E0490919F04019690939F04DF 202 | :2019000080939E0481E0089580E00895CF92DF92FF920F931F93CF93DF931F92CDB7DEB7C2 203 | :20192000082F162F862F880F8E5F99830E94580C83E00E94580CF02EC02E9981D92E8C2D81 204 | :201940008F19811778F4F60184910E94580C082F80E00E94580C8023FFEFCF1ADF0A811137 205 | :20196000EECF01C081E00F90DF91CF911F910F91FF90DF90CF900895615030F02091F1005C 206 | :20198000FC0120830196F8CF84E68093A10408952FB7FC012083F89467706093E900089528 207 | :2019A000CF93DF931F92CDB7DEB7682FCE0101960E94C80C8091F20099819FBF0F90DF918C 208 | :2019C000CF910895FF920F931F93CF93DF9300D0CDB7DEB7F62E8A019091A004992311F037 209 | :2019E00057FF03C08FEF9FEF2AC0682FCE0101967A830E94C80C8091F20090E0A8017A8151 210 | :201A0000801791070CF4AC01EF2DF72FF40E84E6FE1631F08093A1049091F1009193F8CF52 211 | :201A20004115510521F08091F200882321F089818FBFCA0104C08BE68093E800F8CF0F9071 212 | :201A40000F90DF91CF911F910F91FF900895CF93DF931F92CDB7DEB741E050E0BE016F5F1F 213 | :201A60007F4F0E94E20C019719F4898190E002C08FEF9FEF0F90DF91CF910895CF93DF933B 214 | :201A80001F92CDB7DEB7682FCE0101960E94C80C9091E800892F807295FF04C09091F200EB 215 | :201AA00080E4891B99819FBF0F90DF91CF9108956F927F928F929F92AF92BF92CF92DF92D2 216 | :201AC000EF92FF920F931F93CF93DF931F92CDB7DEB7782E7B01C42EB52E8091A0048823AB 217 | :201AE00069F0042F152F8AEFD82E872D8072982E9AE3A92E872D8074882E11C08FEF9FEF9C 218 | :201B000056C0872D0E943E0D682E81110CC0DA94A9F361E070E080E090E00E9422090115CC 219 | :201B2000110579F73BC0282F30E0021713070CF4602E672DCE0101960E94C80C8091E80093 220 | :201B400085FF29C0262D30E0021B130B992039F06A948FEF6816B1F01092F100F9CFF701AA 221 | :201B6000862D77FE07C0815058F094919093F1003196F9CF815020F091919093F100FACFB5 222 | :201B8000E20EF31E8091E80085FF0EC00115110511F4811009C089818FBFC1CF84E6809309 223 | :201BA000A2048C2D9B2D03C0A092E800F4CF0F90DF91CF911F910F91FF90EF90DF90CF90C3 224 | :201BC000BF90AF909F908F907F906F9008951092E90010929F0410929E0490939D04809393 225 | :201BE0009C040895CF92DF92FF920F931F93CF93DF9300D0CDB7DEB7F82E8A016B01011501 226 | :201C00001105B1F0F601F7FE02C0849101C0808149835A830E94580C01501109FFEFCF1A97 227 | :201C2000DF0A49815A818111EACF8FEF9FEF01C0CA010F900F90DF91CF911F910F91FF9046 228 | :201C4000DF90CF9008951F93CF93DF931F92CDB7DEB7162F2091E80022FFFCCF612F798373 229 | :201C60000E94BC0C8BEF8093E800812F7981972F0F90DF91CF911F910895CF93DF931F92D4 230 | :201C8000CDB7DEB71982CE0101960E94E70ACE0101960E940E10898190E00F90DF91CF9188 231 | :201CA00008951F920F920FB60F921124EF92FF920F931F932F933F934F935F936F937F93B8 232 | :201CC0008F939F93AF93BF93EF93FF93CF93DF93CDB7DEB76297DEBFCDBF1092E90080915D 233 | :201CE000E80083FFEBC068E0CE010A960E94BC0C82EF8093E8009A8597FF05C08091E800CF 234 | :201D000080FFFCCF03C08EEF8093E800892F807609F0B9C08B85811105C01092F100109282 235 | :201D2000F100C5C0282F2D7F213009F4C0C0853049F48091E80080FFFCCF8C85806880931B 236 | :201D4000E300B5C0863009F076C02D85E888F988223071F580E090E02A8B0E94E70D0E942E 237 | :201D60003D0E99E08E010F5F1F4FF801392F11923A95E9F799832A892A8391E09E8390E8FB 238 | :201D800098879AEF998720919E0430919F04275F3F4F3C832B838D83C7010E94E70D49E0AC 239 | :201DA00050E0B80180E00E94F20D0E943D0E7FC0C7012A8B0E94E70D2A89223241F482E25A 240 | :201DC00090E00E941910892B09F071C074C0213069F488899989089711F420939B048091CE 241 | :201DE0009B04811118C06BEA71E01AC0233009F062C08C85882391F0823021F467E08BEC2A 242 | :201E000091E006C0813009F056C06DE08DEB91E00E94860C4AC069E971E002C063ED71E051 243 | :201E20006115710509F447C0FB01449150E080E80E94F20D3CC0873009F43DC0883021F42E 244 | :201E400081E08093F10033C0893089F5937099F5E5E9F1E081E021E096E38093E900209339 245 | :201E6000EB0034913093EC009093ED008F5F3196853099F78EE78093EA001092EA008C85EA 246 | :201E80008093A00414C0888999890E94E70D8E85811105C0CE010A960E94F20A06C08230FF 247 | :201EA00051F4CE010A960E944110882321F08EEF8093E80003C081E28093EB0062960FB666 248 | :201EC000F894DEBF0FBECDBFDF91CF91FF91EF91BF91AF919F918F917F916F915F914F91E0 249 | :201EE0003F912F911F910F91FF90EF900F900FBE0F901F9018958093E9008091F2008823E3 250 | :201F000019F08AE38093E80008951F920F920FB60F9211242F933F934F935F936F937F934A 251 | :201F20008F939F93AF93BF93EF93FF938091E1001092E10083FF0FC01092E90091E09093C0 252 | :201F4000EB001092EC0092E39093ED001092A00498E09093F00082FF15C083E00E947B0FCD 253 | :201F60008091A204882329F08091A20481508093A2048091A104882329F08091A1048150A4 254 | :201F80008093A104FF91EF91BF91AF919F918F917F916F915F914F913F912F910F900FBEBD 255 | :201FA0000F901F9018951092A00481E08093D70080EA8093D80082E189BD09B400FEFDCF10 256 | :201FC00061E070E080E090E00E94220980E98093D8008CE08093E2001092E0000895089562 257 | :201FE000FF920F931F93CF93DF93EC01F62EE881F9810480F581E02D09958C01E881F9811F 258 | :202000000680F781E02D6F2DCE010995C8019927DF91CF911F910F91FF900895FC01208139 259 | :202020002F5F208349E150E067E572E080E80C94F20D45E650E060E772E080E80C94F20D75 260 | :20204000EF92FF920F931F93CF93DF931F92CDB7DEB789838B017A0141E050E0BE016F5F8B 261 | :202060007F4F84E00E94580DA701B80184E40E94580D0F90DF91CF911F910F91FF90EF908A 262 | :202080000895FC0191818081813A31F481E0913091F0933089F411C0813271F49B3021F407 263 | :2020A000828180932E0105C09A3031F4828180932D0181E00895089580E0089548E050E0F3 264 | :2020C00082E00C942010CF93DF93DC01683818F0E8E7E60F25C0E62FF0E067FF11C0E05878 265 | :2020E000F10981E090E001C0880FEA95EAF714969C911497982B14969C931497E0E010C004 266 | :20210000E952FE4FE491EE2309F440C0E7FF08C014968C911497826014968C931497EF77D8 267 | :2021200016968C9116978E1741F117968C9117978E1719F118968C9118978E17F1F019967C 268 | :202140008C9119978E17C9F01A968C911A978E17A1F01B968C911B978E1779F080E090E042 269 | :20216000ED01C80FD91F2E81211102C0EE8305C0019686309105A1F709C0BD016C5F7F4F2E 270 | :20218000CD010E945E1081E090E008C081E090E013969C938E93129780E090E0DF91CF91B5 271 | :2021A0000895683818F0E8E7E60F25C0E62FF0E067FF12C0E058F10921E030E001C0220FDF 272 | :2021C000EA95EAF72095DC0114963C911497322314963C93E0E00FC0E952FE4FE491EE2380 273 | :2021E00029F1E7FF08C0DC0114962C9114972D7F14962C93EF7720E030E0EE2351F0DC016E 274 | :20220000A20FB31F16964C9116974E1302C016961C922F5F3F4F2630310579F7BC016C5FE8 275 | :202220007F4F0E945E1081E090E0089580E090E00895FC0116821782108611861286138659 276 | :202240001482BC016C5F7F4F0C945E101092AF041092A6041092A50485E293E09093A404F3 277 | :202260008093A30408950CD0EBC0E3D040F0DAD030F021F45F3F19F0CCC0511115C1CFC0C4 278 | :20228000F0D098F39923C9F35523B1F3951B550BBB27AA2762177307840738F09F5F5F4F4A 279 | :2022A000220F331F441FAA1FA9F333D00E2E3AF0E0E830D091505040E695001CCAF729D0E0 280 | :2022C000FE2F27D0660F771F881FBB1F261737074807AB07B0E809F0BB0B802DBF01FF27E8 281 | :2022E00093585F4F2AF09E3F510568F092C0DCC05F3FECF3983EDCF3869577956795B79521 282 | :20230000F7959F5FC9F7880F911D9695879597F90895E1E0660F771F881FBB1F621773077A 283 | :202320008407BA0720F0621B730B840BBA0BEE1F88F7E095089504D06894B111B5C00895B0 284 | :2023400098D088F09F5790F0B92F9927B751A0F0D1F0660F771F881F991F1AF0BA95C9F7A3 285 | :2023600012C0B13081F09FD0B1E008959CC0672F782F8827B85F39F0B93FCCF38695779536 286 | :202380006795B395D9F73EF490958095709561957F4F8F4F9F4F0895E89409C097FB3EF482 287 | :2023A00090958095709561957F4F8F4F9F4F9923A9F0F92F96E9BB279395F6958795779595 288 | :2023C0006795B795F111F8CFFAF4BB0F11F460FF1BC06F5F7F4F8F4F9F4F16C0882311F00B 289 | :2023E00096E911C0772321F09EE8872F762F05C0662371F096E8862F70E060E02AF09A9546 290 | :20240000660F771F881FDAF7880F9695879597F9089597F99F6780E870E060E008959FEF75 291 | :2024200080EC089500240A941616170618060906089500240A9412161306140605060895F9 292 | :20244000092E0394000C11F4882352F0BB0F40F4BF2B11F460FF04C06F5F7F4F8F4F9F4F38 293 | :20246000089557FD9058440F551F59F05F3F71F04795880F97FB991F61F09F3F79F0879503 294 | :202480000895121613061406551FF2CF4695F1DF08C0161617061806991FF1CF869571052C 295 | :2024A000610508940895E894BB2766277727CB0197F908950BD0C4CFB5DF28F0BADF18F040 296 | :2024C000952309F0A6CFABCF1124EECFCADFA0F3959FD1F3950F50E0551F629FF001729FEB 297 | :2024E000BB27F00DB11D639FAA27F00DB11DAA1F649F6627B00DA11D661F829F2227B00D11 298 | :20250000A11D621F739FB00DA11D621F839FA00D611D221F749F3327A00D611D231F849FE3 299 | :20252000600D211D822F762F6A2F11249F5750408AF0E1F088234AF0EE0FFF1FBB1F661F9C 300 | :20254000771F881F91505040A9F79E3F510570F060CFAACF5F3FECF3983EDCF386957795A9 301 | :202560006795B795F795E7959F5FC1F7FE2B880F911D9695879597F90895A1E21A2EAA1BE3 302 | :20258000BB1BFD010DC0AA1FBB1FEE1FFF1FA217B307E407F50720F0A21BB30BE40BF50BF8 303 | :2025A000661F771F881F991F1A9469F760957095809590959B01AC01BD01CF010895EE0FEE 304 | :2025C000FF1F0590F491E02D0994B7FF0C94ED120E94ED12821B930B08950E94F812A59F5B 305 | :2025E000900DB49F900DA49F800D911D11240895A29FB001B39FC001A39F700D811D112467 306 | :20260000911DB29F700D811D1124911D0895AEE0B0E0EDE0F3E10C94AF150D891E8986E05A 307 | :202620008C831A8309838FEF9FE79E838D83AE01475E5F4F6F89788DCE0101960E942913EA 308 | :20264000EF81F885E00FF11F10822E96E4E00C94CB15ACE0B0E0EFE2F3E10C94A1157C0160 309 | :202660006B018A01FC0117821682838181FFBDC1CE0101964C01F7019381F60193FD8591D7 310 | :2026800093FF81916F01882309F4ABC1853239F493FD859193FF81916F01853229F4B701E8 311 | :2026A00090E00E941115E7CF512C312C20E02032A0F48B3269F030F4803259F0833269F425 312 | :2026C00020612CC08D3239F0803339F4216026C02260246023C0286021C027FD27C030EDE4 313 | :2026E000380F3A3078F426FF06C0FAE05F9E300D1124532E13C08AE0389E300D1124332E22 314 | :2027000020620CC08E3221F426FD6BC1206406C08C3611F4206802C0883641F4F60193FD72 315 | :20272000859193FF81916F018111C1CF982F9F7D9554933028F40C5F1F4FFFE3F9830DC09E 316 | :20274000833631F0833771F0833509F05BC022C0F801808189830E5F1F4F44244394512C39 317 | :20276000540115C03801F2E06F0E711CF801A080B18026FF03C0652D70E002C06FEF7FEF78 318 | :20278000C5012C870E9406152C0183012C852F77222E17C03801F2E06F0E711CF801A080A6 319 | :2027A000B18026FF03C0652D70E002C06FEF7FEFC5012C870E94FB142C012C852068222EB0 320 | :2027C000830123FC1BC0832D90E048165906B0F4B70180E290E00E9411153A94F4CFF50121 321 | :2027E00027FC859127FE81915F01B70190E00E94111531103A94F1E04F1A510841145104CD 322 | :2028000071F7E5C0843611F0893639F5F80127FF07C060817181828193810C5F1F4F08C092 323 | :2028200060817181882777FD8095982F0E5F1F4F2F76B22E97FF09C09095809570956195D2 324 | :202840007F4F8F4F9F4F2068B22E2AE030E0A4010E944315A82EA81844C0853729F42F7E9D 325 | :20286000B22E2AE030E025C0F22FF97FBF2E8F36C1F018F4883579F0B4C0803719F0883758 326 | :2028800021F0AFC02F2F2061B22EB4FE0DC08B2D8460B82E09C024FF0AC09F2F9660B92E97 327 | :2028A00006C028E030E005C020E130E002C020E132E0F801B7FE07C0608171818281938130 328 | :2028C0000C5F1F4F06C06081718180E090E00E5F1F4FA4010E944315A82EA818FB2DFF7708 329 | :2028E000BF2EB6FE0BC02B2D2E7FA51450F4B4FE0AC0B2FC08C02B2D2E7E05C07A2C2B2DB1 330 | :2029000003C07A2C01C0752C24FF0DC0FE01EA0DF11D8081803311F4297E09C022FF06C0E8 331 | :202920007394739404C0822F867809F0739423FD13C020FF06C05A2C731418F4530C571857 332 | :20294000732C731468F4B70180E290E02C870E94111573942C85F5CF731410F4371801C0D9 333 | :20296000312C24FF12C0B70180E390E02C870E9411152C8522FF17C021FF03C088E590E096 334 | :2029800002C088E790E0B7010CC0822F867859F021FD02C080E201C08BE227FD8DE2B7015F 335 | :2029A00090E00E941115A51438F4B70180E390E00E9411155A94F7CFAA94F401EA0DF11DBB 336 | :2029C0008081B70190E00E941115A110F5CF332009F451CEB70180E290E00E9411153A9402 337 | :2029E000F6CFF7018681978102C08FEF9FEF2C96E2E10C94BD15FC01059061507040011032 338 | :202A0000D8F7809590958E0F9F1F0895FC016150704001900110D8F7809590958E0F9F1F51 339 | :202A200008950F931F93CF93DF93182F092FEB018B8181FD03C08FEF9FEF20C082FF10C0DC 340 | :202A40004E815F812C813D81421753077CF4E881F9819F012F5F3F4F39832883108306C0DA 341 | :202A6000E885F985812F0995892B29F72E813F812F5F3F4F3F832E83812F902FDF91CF910C 342 | :202A80001F910F910895FA01AA27283051F1203181F1E8946F936E7F6E5F7F4F8F4F9F4F4E 343 | :202AA000AF4FB1E03ED0B4E03CD0670F781F891F9A1FA11D680F791F8A1F911DA11D6A0F10 344 | :202AC000711D811D911DA11D20D009F468943F912AE0269F11243019305D3193DEF6CF0163 345 | :202AE0000895462F4770405D4193B3E00FD0C9F7F6CF462F4F70405D4A3318F0495D31FD7B 346 | :202B00004052419302D0A9F7EACFB4E0A6959795879577956795BA95C9F700976105710583 347 | :202B200008959B01AC010A2E06945795479537952795BA95C9F7620F731F841F951FA01D67 348 | :202B400008952F923F924F925F926F927F928F929F92AF92BF92CF92DF92EF92FF920F93F8 349 | :202B60001F93CF93DF93CDB7DEB7CA1BDB0B0FB6F894DEBF0FBECDBF09942A8839884888C4 350 | :202B80005F846E847D848C849B84AA84B984C884DF80EE80FD800C811B81AA81B981CE0FC4 351 | :202BA000D11D0FB6F894DEBF0FBECDBFED010895F894FFCFAA029801B6029D02C702C602CE 352 | :202BC000DE022A03F802B503000000000701000801080300FFFFFFFF0100E100000000003C 353 | :202BE00000010123207573625F6964203D200023202573203D20256C640023202573203D18 354 | :202C00002025660025733A202566206973206E6F74206265747765656E20256620616E6417 355 | :202C20002025660025733A20256C64206973206E6F74206265747765656E20256C642061F5 356 | :202C40006E6420256C6400696E7075745F6F6666736574006F75747075745F6F6666736554 357 | :202C6000745F6E6567006F75747075745F6F66667365745F706F7300696E7075745F616DA2 358 | :202C8000705F3600696E7075745F616D705F3100696E7075745F616D705F3000696E7075BA 359 | :202CA000745F667265715F316B00696E7075745F70686173655F316B00696E7075745F6608 360 | :202CC0007265715F3130306B00696E7075745F70686173655F3130306B00696E7075745FC7 361 | :202CE000667265715F316D00696E7075745F70686173655F316D002E2E2E20004641494CC6 362 | :202D0000004F4B00457865637574696E6720746573742073756974652E2E2E00416C6C2080 363 | :202D20007465737473207061737365642E0053656C6674657374204641494C2E00507265B7 364 | :202D4000737320656E74657220746F20726572756E20746573742073756974652E005265F1 365 | :202D60006164205553422049440054657374207A65726F206F6666736574005465737420F0 366 | :202D80006F66667365742061646A7573746D656E74005465737420616D706C6974756465C8 367 | :202DA0002061646A7573746D656E740054657374206672657175656E637920616E6420703F 368 | :202DC0006861736500000000008A0AD10AB40AC40AA00AB10A00000000F00F7E0B6310D126 369 | :042DE00010191100B5 370 | :207000005FC0000078C0000076C0000074C0000072C0000070C000006EC000006CC00000F3 371 | :207020006AC0000068C00000C1C4000064C0000062C0000060C000005EC000005CC00000D9 372 | :207040005AC00000E3C0000056C0000054C0000052C0000050C000004EC000004CC000000D 373 | :207060004AC0000048C0000046C0000044C0000042C0000040C000003EC000003CC00000F8 374 | :207080003AC0000038C0000036C0000034C0000032C0000030C000002EC000002CC0000058 375 | :2070A0002AC0000028C0000026C00000453E5E3E073F5E3E073F983EBA3E073FDA3EEC3E7B 376 | :2070C00011241FBECFEFDAE0DEBFCDBF11E0A0E0B1E0E8E9FEE702C005900D92AE3AB107AF 377 | :2070E000D9F711E0AEEAB1E001C01D92A43CB107E1F759D3CFC684CF84E08093E900809141 378 | :20710000E80085FD0DC08091E8008B778093E8008091E80082FDF3CF8EB38111F9CF02C0AB 379 | :207120008091F100089593E09093E9009091E80095FF0AC08093F1005D9884E690E09093D4 380 | :20714000B3018093B20108959091E8009E779093E8009091E80090FDEDCF9EB39111F9CFE2 381 | :207160000895F89410926F0010928100109285001092840081E085BF15BE47985D9A289A55 382 | :207180000C94000008952091B8013091B9012F5F3F4F3093B9012093B801832F37FF02C01E 383 | :2071A0008EEF831B880F821710F447980895479A089584B7877F84BF88E10FB6F894809335 384 | :2071C0006000109260000FBE90E880E00FB6F89490936100809361000FBE81E085BF82E08B 385 | :2071E00085BF3F9A209A559AE1E6F0E09083108247985D9A289A109289009AEF9093880096 386 | :2072000080936F0083E080938100B8C31F920F920FB60F9211242F938F939F93EF93FF9363 387 | :2072200010928500109284008091B2019091B301009741F001979093B3018093B201892BB7 388 | :2072400009F45D9A8091B0019091B101009741F001979093B1018093B001892B09F4289A39 389 | :20726000E0E0F0E085919491019649F08091AE019091AF0101969093AF018093AE01FF9126 390 | :20728000EF919F918F912F910F900FBE0F901F90189542E061EC82E006D342E161E883E07E 391 | :2072A00002D342E160E884E0FEC28091BC01982F9F77913239F59091BD01903291F09132E9 392 | :2072C00009F5813AF9F48091E800877F8093E80067E070E083E091E01AD48091E8008B77BA 393 | :2072E0000FC0813279F48091E800877F8093E80067E070E083E091E067D48091E8008E7701 394 | :207300008093E80008956F927F928F929F92AF92BF92CF92DF92EF92FF920F931F93CF9345 395 | :20732000DF9384E08093E9008091E80082FF2CC2289884E690E09093B1018093B001DCDE26 396 | :20734000C82F853481F48CE49DE19093AF018093AE0107B600FCFDCFF999FECF81E180932C 397 | :207360005700E89503C0843521F4C6DE8DE0DBDEE2C18C34D9F38035C9F3843721F484E406 398 | :20738000D2DE80E0F4CF813611F489E5F0CF813491F4B2DEC82FB0DE90E0880F991F2C2FC8 399 | :2073A00030E0322F2227330F822B932BAA2797FDA095BA2F94C1803711F483E5D8CF83350B 400 | :2073C00049F4C4EAD1E01C2F195F8991ACDE1C13FCCFB1C1863521F481E3A5DE80E3C7CF8E 401 | :2073E000833731F487E89FDE85E99DDE8EE1BFCF8536B9F4E0E0F0E083E095E080935700A2 402 | :20740000E89507B600FCFDCF90935700E89507B600FCFDCFE058FF4FE11520E7F20771F70A 403 | :20742000A5CF823719F4E1E0F0E00EC0863419F4E0E0F0E009C08E3419F4E3E0F0E004C06D 404 | :20744000813539F4E2E0F0E089E08093570084918ECF823631F489E566DE80E064DE80E8D9 405 | :2074600086CF823419F0873609F0D1C01092AF011092AE0141DE082F3FDED82F3DDE182F32 406 | :207480008BEB810F823010F08FE371CF2D2F30E0302B10926F00C73609F043C081E180933C 407 | :2074A0005700E895E90100E0BB24B394209709F4AAC0C090B401D090B501E090B601F09028 408 | :2074C000B7011634B9F4E02FF0E0EC29FD2984912ADE002369F082E0C80ED11CE11CF11C1B 409 | :2074E000C092B401D092B501E092B601F092B7010B2515C0D701C601B695A7959795879597 410 | :20750000B3D411DEE2E0CE0ED11CE11CF11CC092B401D092B501E092B601F092B701219726 411 | :20752000C5CF8090B4019090B501A090B601B090B701163441F483E0F40180935700E8957F 412 | :2075400007B600FCFDCFE901712C00E066246394209709F44CC0163431F5002301F1C09029 413 | :20756000B401D090B501E090B601F090B701C4DD272D30E0382BF601090160925700E895B2 414 | :207580001124F2E0CF0ED11CE11CF11CC092B401D092B501E092B601F092B70102C0ACDD43 415 | :2075A000782E062522C0A8DD682F8091B4019091B501A091B601B091B701B695A7959795CB 416 | :2075C00087955AD48091B4019091B501A091B601B091B7010296A11DB11D8093B4019093D4 417 | :2075E000B501A093B601B093B7012197B1CF163441F485E0F40180935700E89507B600FC3F 418 | :20760000FDCF8DE090DD82E080936F0094C0833471F4C091B401D091B5016EDD90E021E097 419 | :20762000FE010C0120935700E89511249FCE833601F5C090B401D090B501E090B601F090A4 420 | :20764000B7015ADDF601E16090E021E00C0120935700E8951124F2E0CF0ED11CE11CF11C23 421 | :20766000C092B401D092B501E092B601F092B7017DCE8D3661F4E091B401F091B50185E063 422 | :2076800080935700E89507B600FCFDCF6FCE823551F4E091B401F091B501C591D4918D2F71 423 | :2076A00042DD8C2F64CE843419F526DD682F8091B4019091B501A091B601B091B701B69595 424 | :2076C000A79597958795D8D38091B4019091B501A091B601B091B7010296A11DB11D8093C6 425 | :2076E000B4019093B501A093B601B093B7013ECE8436F1F4C090B401D090B501E090B6012A 426 | :20770000F090B701D701C601B695A79597958795ABD309DD22E0C20ED11CE11CF11CC09244 427 | :20772000B401D092B501E092B601F092B70103C08B3109F0A9CE83E08093E9009091E800C2 428 | :207740008091E8008E778093E80095FD11C08091E80080FD04C08EB38111F9CF15C0809112 429 | :20776000E8008E778093E80003C08EB3882361F08091E80080FFF9CF84E08093E900809170 430 | :20778000E8008B778093E800DF91CF911F910F91FF90EF90DF90CF90BF90AF909F908F902C 431 | :2077A0007F906F9008952091000830910108109201081092000884B714BE98E10FB6F8946F 432 | :2077C00090936000109260000FBE81FD1AC080FF09C0E0E0F0E0459154914F3F5F4F11F02F 433 | :2077E000C0DC0FC083FF0DC080910001909101012817390731F0E0E0F0E085919491019698 434 | :2078000079F7D7DC78941092AF011092AE0180910201882369F077DD10D38091AE01909166 435 | :20782000AF0181349F4110F010920201ACDCEFCF8091E00081608093E00093DC80E090E014 436 | :207840000895292F30E02230310559F02330310569F021303105F9F482E190E022E931E00D 437 | :207860001EC08EE390E024E531E019C0882349F0813061F0823079F48CE190E02AE031E059 438 | :207880000EC084E090E020E531E009C086E290E028E231E004C080E090E020E030E0FA01D5 439 | :2078A0003183208308958093E900EBEEF0E0808181608083EDEEF0E010826093EC0040836B 440 | :2078C0008091EE00881F8827881F08958091BC0187FF0FC08091E80082FD04C08EB38111DD 441 | :2078E000F9CF10C08091E8008B770AC08EB3882349F08091E80080FFF9CF8091E8008E77C8 442 | :207900008093E80008950F931F93CF93DF9341D048D0C8EDD0E088818F778883888180680E 443 | :20792000888388818F7D888319BC1EBA1092BA0100EE10E0F80180818B7F80838881816043 444 | :20794000888342E060E080E0AEDFE1EEF0E080818E7F8083E2EEF0E0808181608083808197 445 | :2079600088608083F80180818E7F8083888180618883DF91CF911F910F910895E7EDF0E0BC 446 | :207980008081816080838AE482BF81E08093BB01BACFE8EDF0E080818E7F80831092E20060 447 | :2079A00008951092DA001092E10008951F920F920FB60F9211242F933F934F935F936F9337 448 | :2079C0007F938F939F93AF93BF93EF93FF938091DA0080FF1BC08091D80080FF17C0809104 449 | :2079E000DA008E7F8093DA008091D90080FF0BC080E189BD82E189BD09B400FEFDCF81E047 450 | :207A00008EBB1AD203C019BC1EBA16D28091E10080FF17C08091E20080FF13C08091E20059 451 | :207A20008E7F8093E2008091E20080618093E2008091D80080628093D80019BC85E08EBB42 452 | :207A4000FBD18091E10084FF2CC08091E20084FF28C080E189BD82E189BD09B400FEFDCFC4 453 | :207A60008091D8008F7D8093D8008091E1008F7E8093E1008091E2008F7E8093E20080912D 454 | :207A8000E20081608093E2008091BA01811106C08091E30087FD02C081E001C084E08EBB01 455 | :207AA000CBD18091E10083FF21C08091E20083FF1DC08091E100877F8093E10082E08EBBEC 456 | :207AC0001092BA018091E1008E7F8093E1008091E2008E7F8093E2008091E200806180937A 457 | :207AE000E20042E060E080E0DEDEA6D1FF91EF91BF91AF919F918F917F916F915F914F91DF 458 | :207B00003F912F910F900FBE0F901F9018952091C2013091C3012617370748F061157105D6 459 | :207B200039F42091E8002E772093E80001C0B90140E061157105A1F12EB3222309F440C003 460 | :207B4000253009F43FC02091E80023FD37C02091E80022FD2DC02091E80020FFEACF40913D 461 | :207B6000F3002091F20030E0342BFC01CF016115710559F02830310540F481918093F10026 462 | :207B8000615071092F5F3F4FF1CF41E02830310509F040E02091E8002E772093E800C9CFA5 463 | :207BA0004111CACF05C08EB3882351F0853051F08091E80082FFF7CF80E0089581E00895B7 464 | :207BC00082E0089583E008956115710529F42091E8002B772093E800FC016115710531F1BC 465 | :207BE0008EB3882359F1853059F18091E80083FD23C08091E80082FFF0CF2091F300809106 466 | :207C0000F20090E0922B892B31F08091F10081936150710991F78091E8008B778093E800B1 467 | :207C2000DCCF8EB3882351F0853051F08091E80080FFF7CF80E0089581E0089582E008953E 468 | :207C400083E008950F931F93CF93DF9300D0CDB7DEB7ECEBF1E088E08E0F9091F100919330 469 | :207C60008E13FBCF22DB8091E80083FFD0C08091BC01E091BD014E2F50E04A30510508F01F 470 | :207C8000C6C0FA01EA5AF74CE9C0823809F0BFC08091C00187708093E9008091EB0010929E 471 | :207CA000E9009091E800977F9093E80085FB882780F98093F1001092F10087C0982F9D7F48 472 | :207CC00009F0A5C0823009F0A2C08091BE01811129C08091C001877009F499C08093E900D3 473 | :207CE0009091EB0090FF1EC09091BD01933021F48091EB00806214C09091EB009061909382 474 | :207D0000EB0021E030E0A90102C0440F551F8A95E2F74093EA001092EA008091EB0088600F 475 | :207D20008093EB001092E9008091E800877F50C081116DC01091BE011F770FB7F894809193 476 | :207D4000E800877F8093E800C1DD8091E80080FFFCCF8091E3008078812B8093E3008068E3 477 | :207D60008093E300111102C082E001C083E08EBB0FBF4DC08058823008F049C0AE014F5F97 478 | :207D80005F4F6091C0018091BE019091BF0159DDBC01009709F43BC09091E800977F90930E 479 | :207DA000E80089819A81B3DE8091E8008B778093E8002DC0803859F58091E800877F80932A 480 | :207DC000E8008091BA018093F1008091E8008E778093E8007BDD1BC0811119C09091BE0174 481 | :207DE0009230A8F48091E800877F8093E8009093BA016CDD8091BA01811106C08091E300EC 482 | :207E000087FD02C081E001C084E08EBB42DA8091E80083FF0AC08091EB0080628093EB0010 483 | :207E20008091E800877F8093E8000F900F90DF91CF911F910F9108950895CF938EB3882362 484 | :207E400059F0C091E900C7701092E9008091E80083FDF8DEC093E900CF910895EE0FFF1F3A 485 | :207E60000590F491E02D0994F999FECF92BD81BDF89A992780B50895262FF999FECF1FBAA1 486 | :207E800092BD81BD20BD0FB6F894FA9AF99A0FBE01960895F894FFCF7777010000000000B6 487 | :207EA00000081C03410072006100630068006E006900640020004C006100620073000000DF 488 | :207EC00026035400730075006E0061006D006900200042006F006F0074006C006F006100A8 489 | :207EE00064006500720000000403090409023E0002010080320904000001020201000524F9 490 | :207F0000001001042402040524060001070582030800FF09040100020A000000070504022E 491 | :207F20001000010705830210000112011001020000080912B61A0100020100014341544553 492 | :067F400052494E41000011 493 | :040000050000700087 494 | :00000001FF 495 | -------------------------------------------------------------------------------- /src/ad983x/.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | -------------------------------------------------------------------------------- /src/ad983x/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arachnid Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/ad983x/README.md: -------------------------------------------------------------------------------- 1 | # ad983x 2 | Library for interfacing with the AD983x series of DDS ICs. 3 | -------------------------------------------------------------------------------- /src/ad983x/ad983x.c: -------------------------------------------------------------------------------- 1 | #include "ad983x.h" 2 | 3 | #define REG_FREQ1 0x8000 4 | #define REG_FREQ0 0x4000 5 | #define REG_PHASE0 0xC000 6 | #define REG_PHASE1 0xE000 7 | 8 | #define REG_B28 0x2000 9 | #define REG_HLB 0x1000 10 | #define REG_FSEL 0x0800 11 | #define REG_PSEL 0x0400 12 | #define REG_PINSW 0x0200 13 | #define REG_RESET 0x0100 14 | #define REG_SLEEP1 0x0080 15 | #define REG_SLEEP12 0x0040 16 | #define REG_OPBITEN 0x0020 17 | #define REG_SIGNPIB 0x0010 18 | #define REG_DIV2 0x0008 19 | #define REG_MODE 0x0002 20 | 21 | #define SIGN_OUTPUT_MASK (REG_OPBITEN | REG_SIGNPIB | REG_DIV2 | REG_MODE) 22 | 23 | static void write_reg(ad983x_t *dds, uint16_t value) { 24 | char data[2]; 25 | data[0] = value >> 8; 26 | data[1] = value & 0xFF; 27 | dds->spi_transfer(dds->ctx, &data, sizeof(data)); 28 | } 29 | 30 | void ad983x_init(ad983x_t *dds, spi_transfer_func func, void *ctx) { 31 | dds->spi_transfer = func; 32 | dds->ctx = ctx; 33 | // Set to two writes per frequency register, and pin control of functions. 34 | dds->reg = REG_B28 | REG_PINSW; 35 | } 36 | 37 | void ad983x_start(ad983x_t *dds) { 38 | write_reg(dds, dds->reg); 39 | ad983x_set_frequency(dds, 0, 0); 40 | ad983x_set_frequency(dds, 1, 0); 41 | ad983x_set_phase(dds, 0, 0); 42 | ad983x_set_phase(dds, 1, 0); 43 | } 44 | 45 | void ad983x_set_frequency(ad983x_t *dds, uint8_t reg, uint32_t frequency) { 46 | write_reg(dds, (reg?REG_FREQ1:REG_FREQ0) | (frequency & 0x3FFF)); 47 | write_reg(dds, (reg?REG_FREQ1:REG_FREQ0) | ((frequency >> 14) & 0x3FFF)); 48 | } 49 | 50 | void ad983x_set_phase(ad983x_t *dds, uint8_t reg, uint32_t phase) { 51 | write_reg(dds, (reg?REG_PHASE1:REG_PHASE0) | (phase & 0x0FFF)); 52 | } 53 | 54 | void ad983x_set_sign_output(ad983x_t *dds, ad983x_sign_output_t output) { 55 | dds->reg = (dds->reg & ~SIGN_OUTPUT_MASK) | output; 56 | write_reg(dds, dds->reg); 57 | } 58 | 59 | void ad983x_set_output_mode(ad983x_t *dds, ad983x_output_mode_t mode) { 60 | if(mode == AD983X_OUTPUT_MODE_TRIANGLE) { 61 | dds->reg = (dds->reg & ~SIGN_OUTPUT_MASK) | mode; 62 | } else { 63 | dds->reg &= ~REG_MODE; 64 | } 65 | write_reg(dds, dds->reg); 66 | } 67 | -------------------------------------------------------------------------------- /src/ad983x/ad983x.h: -------------------------------------------------------------------------------- 1 | #ifndef __AD983X_H 2 | #define __AD983X_H 3 | 4 | #include 5 | 6 | typedef void (*spi_transfer_func)(void *ctx, char data[], int len); 7 | 8 | typedef struct { 9 | spi_transfer_func spi_transfer; 10 | void *ctx; 11 | uint16_t reg; 12 | } ad983x_t; 13 | 14 | typedef enum { 15 | AD983X_SIGN_OUTPUT_NONE = 0x0000, 16 | AD983X_SIGN_OUTPUT_MSB = 0x0028, 17 | AD983X_SIGN_OUTPUT_MSB_2 = 0x0020, 18 | AD983X_SIGN_OUTPUT_COMPARATOR = 0x0038, 19 | } ad983x_sign_output_t; 20 | 21 | typedef enum { 22 | AD983X_OUTPUT_MODE_SINE = 0x0000, 23 | AD983X_OUTPUT_MODE_TRIANGLE = 0x0002, 24 | } ad983x_output_mode_t; 25 | 26 | void ad983x_init(ad983x_t *dds, spi_transfer_func func, void *ctx); 27 | void ad983x_start(ad983x_t *dds); 28 | void ad983x_set_frequency(ad983x_t *dds, uint8_t reg, uint32_t frequency); 29 | void ad983x_set_phase(ad983x_t *dds, uint8_t reg, uint32_t phase); 30 | void ad983x_set_sign_output(ad983x_t *dds, ad983x_sign_output_t output); 31 | void ad983x_set_output_mode(ad983x_t *dds, ad983x_output_mode_t mode); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/mcp49xx/.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | -------------------------------------------------------------------------------- /src/mcp49xx/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arachnid Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/mcp49xx/README.md: -------------------------------------------------------------------------------- 1 | # MCP49XX 2 | 3 | Simple library for interfacing with the MCP49xx DACs from Microchip. 4 | -------------------------------------------------------------------------------- /src/mcp49xx/mcp49xx.c: -------------------------------------------------------------------------------- 1 | #include "mcp49xx.h" 2 | 3 | #define MCP49XX_DAC_ID 15 4 | #define MCP49XX_DAC_BUF 14 5 | #define MCP49XX_DAC_GAIN 13 6 | #define MCP49XX_DAC_SHDN 12 7 | #define MCP49XX_DAC_VALUE_MASK 0x0FFF 8 | 9 | static void _transfer(mcp49xx_t *dac, uint8_t idx) { 10 | uint8_t data[2]; 11 | data[0] = dac->registers[idx] >> 8; 12 | data[1] = dac->registers[idx] & 0xFF; 13 | dac->spi_transfer(dac->ctx, data, sizeof(data)); 14 | } 15 | 16 | void mcp49xx_init(mcp49xx_t *dac, spi_transfer_func func, void *ctx) { 17 | dac->spi_transfer = func; 18 | dac->ctx = ctx; 19 | dac->registers[0] = 0; 20 | dac->registers[1] = 1 << MCP49XX_DAC_ID; 21 | _transfer(dac, 0); 22 | _transfer(dac, 1); 23 | } 24 | 25 | void mcp49xx_set_is_buffered(mcp49xx_t *dac, uint8_t idx, uint8_t is_buffered) { 26 | if(is_buffered) { 27 | dac->registers[idx] |= 1 << MCP49XX_DAC_BUF; 28 | } else { 29 | dac->registers[idx] &= ~(1 << MCP49XX_DAC_BUF); 30 | } 31 | _transfer(dac, idx); 32 | } 33 | 34 | void mcp49xx_set_gain(mcp49xx_t *dac, uint8_t idx, mcp49xx_gain_t gain) { 35 | if(gain == MCP49XX_GAIN_2X) { 36 | dac->registers[idx] &= ~(1 << MCP49XX_DAC_GAIN); 37 | } else { 38 | dac->registers[idx] |= 1 << MCP49XX_DAC_GAIN; 39 | } 40 | _transfer(dac, idx); 41 | } 42 | 43 | void mcp49xx_set_is_shutdown(mcp49xx_t *dac, uint8_t idx, uint8_t is_shutdown) { 44 | if(is_shutdown) { 45 | dac->registers[idx] &= ~(1 << MCP49XX_DAC_SHDN); 46 | } else { 47 | dac->registers[idx] |= 1 << MCP49XX_DAC_SHDN; 48 | } 49 | _transfer(dac, idx); 50 | } 51 | 52 | void mcp49xx_write(mcp49xx_t *dac, uint8_t idx, uint16_t value) { 53 | dac->registers[idx] = (dac->registers[idx] & ~MCP49XX_DAC_VALUE_MASK) 54 | | (value & MCP49XX_DAC_VALUE_MASK); 55 | _transfer(dac, idx); 56 | } 57 | -------------------------------------------------------------------------------- /src/mcp49xx/mcp49xx.h: -------------------------------------------------------------------------------- 1 | #ifndef __MCP49XX_H 2 | #define __MCP49XX_H 3 | 4 | #include 5 | 6 | typedef void (*spi_transfer_func)(void *ctx, char data[], int len); 7 | 8 | typedef struct { 9 | spi_transfer_func spi_transfer; 10 | void *ctx; 11 | uint16_t registers[2]; 12 | } mcp49xx_t; 13 | 14 | typedef enum { 15 | MCP49XX_GAIN_1X, 16 | MCP49XX_GAIN_2X 17 | } mcp49xx_gain_t; 18 | 19 | void mcp49xx_init(mcp49xx_t *dac, spi_transfer_func func, void *ctx); 20 | void mcp49xx_set_is_buffered(mcp49xx_t *dac, uint8_t idx, uint8_t is_buffered); 21 | void mcp49xx_set_gain(mcp49xx_t *dac, uint8_t idx, mcp49xx_gain_t gain); 22 | void mcp49xx_set_is_shutdown(mcp49xx_t *dac, uint8_t idx, uint8_t is_shutdown); 23 | void mcp49xx_write(mcp49xx_t *dac, uint8_t idx, uint16_t value); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/tsunami.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | COUNTER_STATUS_INVALID, 5 | COUNTER_STATUS_PENDING, 6 | COUNTER_STATUS_VALID 7 | } counter_status_t; 8 | 9 | static volatile uint16_t last_edge = -1; 10 | static volatile counter_status_t counter_status = COUNTER_STATUS_INVALID; 11 | static volatile uint8_t counter_divider = 0; 12 | static volatile uint16_t counter_topword = 0; 13 | static volatile uint32_t instant_interval = 0; 14 | static volatile uint32_t average_interval = 0; 15 | 16 | calibration_data_t cal_data[CAL_DATA_ALL]; 17 | 18 | /* This implements a counter state machine: 19 | * Initial state: COUNTER_STATUS_INVALID 20 | * COUNTER_STATUS_INVALID: Falling edge -> COUNTER_STATUS_PENDING 21 | * COUNTER_STATUS_PENDING: Falling edge -> if value low AND divider++ 22 | * then COUNTER_STATUS_INVALID, 23 | * else COUNTER_STATUS_VALID 24 | * COUNTER_STATUS_VALID: Falling edge -> if value low AND divider++ 25 | * then COUNTER_STATUS_INVALID, 26 | * else COUNTER_STATUS_VALID 27 | * ANY: Overflow -> if value high AND divider-- 28 | * then COUNTER_STATUS_INVALID 29 | */ 30 | 31 | static inline void _set_divider() { 32 | digitalWrite(TSUNAMI_FDIV_SEL_0, counter_divider & (1 << 2)); 33 | digitalWrite(TSUNAMI_FDIV_SEL_1, counter_divider & (1 << 3)); 34 | } 35 | 36 | static inline uint8_t _increase_divider() { 37 | if(counter_divider < 12) { 38 | counter_divider += 4; 39 | _set_divider(); 40 | return 1; 41 | } 42 | return 0; 43 | } 44 | 45 | static inline uint8_t _reduce_divider() { 46 | if(counter_divider >= 4) { 47 | counter_divider -= 4; 48 | _set_divider(); 49 | return 1; 50 | } 51 | return 0; 52 | } 53 | 54 | ISR(TIMER1_CAPT_vect) { 55 | uint16_t this_edge; 56 | 57 | // It's good practice to only read a "live" register once: store ICR1 58 | this_edge = ICR1; 59 | 60 | // If there isn't any recent edge capture count recorded ("invalid"), 61 | // just move into "pending" state and wait for a second falling edge; 62 | // Otherwise calculate the full count (including any roll-overs) from 63 | // the last edge to this one, then multiply it by sixteen; we do that 64 | // because even though the next step - calculating a moving average - 65 | // is quite useful to smooth out things, it has the drawback of never 66 | // actually reaching the exact measured value it filters; however, by 67 | // feeding it sixteen times the real value to be filtered the average 68 | // gets a chance to converge close enough to its target that dividing 69 | // it back by sixteen at the end returns the exact value that we seek 70 | switch(counter_status) { 71 | case COUNTER_STATUS_INVALID: 72 | average_interval = 0; 73 | counter_status = COUNTER_STATUS_PENDING; 74 | break; 75 | default: 76 | instant_interval = (((uint32_t)(counter_topword) << 16) + this_edge - last_edge) << 4; 77 | 78 | // Same thing as Iavg = 1/8*Inew + 7/8*Iavg but it gets rid of "x7" 79 | if (average_interval == 0) { 80 | average_interval = instant_interval; 81 | } else { 82 | average_interval += (int32_t)(instant_interval - average_interval) >> 3; 83 | } 84 | 85 | // 0x50000 is the interval count for 3.2MHz, our upper limit point; 86 | // 0x100000 (1 << 16 times sixteen) is our low-count limit where we 87 | // switch dividers; it's about one divider step (x16) times smaller 88 | // than our other switching threshold, assuring that the count will 89 | // still fit below that threshold after the divider gets increased; 90 | // The ratio of the two thresholds is not exactly 1:16 though since 91 | // we want some hysteresis there to avoid endless up/down switching 92 | if(((instant_interval < 0x100000) && (_increase_divider())) || (instant_interval < 0x50000)) { 93 | counter_status = COUNTER_STATUS_INVALID; 94 | } else { 95 | counter_status = COUNTER_STATUS_VALID; 96 | } 97 | break; 98 | } 99 | 100 | // Store the current edge time until we capture the next falling edge 101 | last_edge = this_edge; 102 | 103 | // Start counting the number of times Timer1 rolls over between edges 104 | counter_topword = 0; 105 | } 106 | 107 | ISR(TIMER1_OVF_vect) { 108 | // 272 << 16 is the approximate count for 0.9Hz, our lower limit point; 109 | // 16 << 16 is our high-count threshold for divider switching; we could 110 | // keep counting actually, but the divided signal would drop under 15Hz 111 | // and as a direct consequence the responsiveness would start to suffer 112 | if(((counter_topword > 16) && (_reduce_divider())) || (counter_topword > 272)) { 113 | counter_topword = 0; 114 | counter_status = COUNTER_STATUS_INVALID; 115 | } else { 116 | counter_topword++; 117 | } 118 | } 119 | 120 | typedef struct { 121 | uint8_t cs_pin; 122 | uint8_t bitorder; 123 | uint8_t mode; 124 | } spi_settings_t; 125 | 126 | spi_settings_t dds_spi_settings = {TSUNAMI_DDS_CS, MSBFIRST, SPI_MODE2}; 127 | spi_settings_t dac_spi_settings = {TSUNAMI_DAC_CS, MSBFIRST, SPI_MODE0}; 128 | 129 | // Generic SPI transfer function 130 | static void spi_transfer(void *ctx, char data[], int len) { 131 | spi_settings_t *settings = (spi_settings_t*)ctx; 132 | 133 | SPI.setBitOrder(settings->bitorder); 134 | SPI.setDataMode(settings->mode); 135 | 136 | digitalWrite(settings->cs_pin, LOW); 137 | delayMicroseconds(1); 138 | for(int i = 0; i < len; i++) { 139 | SPI.transfer(data[i]); 140 | } 141 | delayMicroseconds(1); 142 | digitalWrite(settings->cs_pin, HIGH); 143 | } 144 | 145 | Tsunami_Class::Tsunami_Class() { 146 | for(int i = 0; i < CAL_DATA_ALL; i++) { 147 | cal_data[i].scale = 1.0; 148 | cal_data[i].shift = 0.0; 149 | } 150 | ad983x_init(&dds, spi_transfer, &dds_spi_settings); 151 | } 152 | 153 | void Tsunami_Class::begin() { 154 | // DDS control pin config. 155 | pinMode(TSUNAMI_DDS_SLEEP, OUTPUT); 156 | digitalWrite(TSUNAMI_DDS_SLEEP, LOW); 157 | 158 | pinMode(TSUNAMI_DDS_FSEL, OUTPUT); 159 | digitalWrite(TSUNAMI_DDS_FSEL, LOW); 160 | 161 | pinMode(TSUNAMI_DDS_PSEL, OUTPUT); 162 | digitalWrite(TSUNAMI_DDS_PSEL, LOW); 163 | 164 | pinMode(TSUNAMI_DAC_CS, OUTPUT); 165 | digitalWrite(TSUNAMI_DAC_CS, HIGH); 166 | 167 | pinMode(TSUNAMI_DDS_CS, OUTPUT); 168 | digitalWrite(TSUNAMI_DDS_CS, HIGH); 169 | 170 | // Other control pins 171 | pinMode(TSUNAMI_SIGN_EN, OUTPUT); 172 | digitalWrite(TSUNAMI_SIGN_EN, LOW); 173 | 174 | // Frequency divider selection pin config. 175 | pinMode(TSUNAMI_FDIV_SEL_0, OUTPUT); 176 | pinMode(TSUNAMI_FDIV_SEL_1, OUTPUT); 177 | _set_divider(); 178 | 179 | // Enable SPI 180 | SPI.begin(); 181 | SPI.setClockDivider(SPI_CLOCK_DIV2); 182 | 183 | // Initialize DDS 184 | pinMode(TSUNAMI_DDS_RESET, OUTPUT); 185 | digitalWrite(TSUNAMI_DDS_RESET, HIGH); 186 | ad983x_start(&dds); 187 | ad983x_set_sign_output(&dds, AD983X_SIGN_OUTPUT_MSB); 188 | digitalWrite(TSUNAMI_DDS_RESET, LOW); 189 | 190 | // Initialize DAC 191 | mcp49xx_init(&dac, spi_transfer, &dac_spi_settings); 192 | 193 | // Configure amplitude DAC 194 | mcp49xx_set_is_buffered(&dac, TSUNAMI_AMPLITUDE_ID, true); 195 | mcp49xx_set_gain(&dac, TSUNAMI_AMPLITUDE_ID, MCP49XX_GAIN_1X); 196 | mcp49xx_set_is_shutdown(&dac, TSUNAMI_AMPLITUDE_ID, false); 197 | mcp49xx_write(&dac, TSUNAMI_AMPLITUDE_ID, 0); 198 | 199 | // Configure offset DAC 200 | mcp49xx_set_is_buffered(&dac, TSUNAMI_OFFSET_ID, true); 201 | mcp49xx_set_gain(&dac, TSUNAMI_OFFSET_ID, MCP49XX_GAIN_2X); 202 | mcp49xx_set_is_shutdown(&dac, TSUNAMI_OFFSET_ID, false); 203 | mcp49xx_write(&dac, TSUNAMI_OFFSET_ID, 2048); 204 | 205 | // Configure ADC to use internal 2.56V reference 206 | setAnalogRef(INTERNAL); 207 | 208 | current_frequency_reg = 0; 209 | current_phase_reg = 0; 210 | 211 | // Initialize timer 1 to run at 16MHz and capture external events 212 | TCCR1A &= ~_BV(WGM11) & ~_BV(WGM10); 213 | TCCR1B &= ~_BV(WGM13) & ~_BV(WGM12) & ~_BV(CS12) & ~_BV(CS11); 214 | TCCR1B |= _BV(ICNC1) | _BV(CS10); 215 | TIMSK1 |= _BV(ICIE1) | _BV(TOIE1); 216 | 217 | // Enable PWM output on pin 10 from timer 4, so analogWrite works 218 | TCCR4A |= _BV(PWM4B); 219 | 220 | // Uncomment to increase pin 10 PWM frequency from ~500hz to ~8KHz 221 | // TCCR4B &= ~_BV(CS42); 222 | } 223 | 224 | /* Applies and saves calibration data for a single value; "scale" does not 225 | * have a unit and "shift" is in whatever unit the value has (millivolts). 226 | */ 227 | uint8_t Tsunami_Class::setCalibrationData(CalibratedValue value, float scale, float shift) { 228 | calibration_record_t cal_record; 229 | uint8_t result = 0; 230 | 231 | // This can only access a specific value; no global requests allowed 232 | if(value < CAL_DATA_ALL) { 233 | cal_record.magic = TSUNAMI_CALIBRATION; 234 | cal_record.scale = scale; 235 | cal_record.shift = shift; 236 | 237 | // Write then read back to check whether the data was really saved 238 | EEPROM.put(sizeof(cal_record) * value, cal_record); 239 | EEPROM.get(sizeof(cal_record) * value, cal_record); 240 | 241 | // Verify the retrieved data and if it is saved correctly apply it 242 | if((cal_record.magic == TSUNAMI_CALIBRATION) && 243 | (cal_record.scale == scale) && (cal_record.shift == shift)) { 244 | cal_data[value].scale = scale; 245 | cal_data[value].shift = shift; 246 | result = 1; 247 | } 248 | } 249 | 250 | return result; 251 | } 252 | 253 | /* Returns the saved calibration data for a single value; "scale" does not 254 | * have a unit and "shift" is in whatever unit the value has (millivolts); 255 | * Note: this is the stored calibration data, not the current data in use! 256 | */ 257 | uint8_t Tsunami_Class::getCalibrationData(CalibratedValue value, float *scale, float *shift) { 258 | calibration_record_t cal_record; 259 | uint8_t result = 0; 260 | 261 | // This can only access a specific value; no global requests allowed 262 | if(value < CAL_DATA_ALL) { 263 | EEPROM.get(sizeof(cal_record) * value, cal_record); 264 | 265 | // Verify the retrieved signature and if it passes return the data 266 | if(cal_record.magic == TSUNAMI_CALIBRATION) { 267 | *scale = cal_record.scale; 268 | *shift = cal_record.shift; 269 | result = 1; 270 | } 271 | } 272 | 273 | return result; 274 | } 275 | 276 | /* Applies saved calibration data for either a specific value, all values, 277 | * or none of them: restores scale 1.0 and shift 0.0 but keeps saved data. 278 | */ 279 | uint8_t Tsunami_Class::useCalibrationData(CalibratedValue value) { 280 | calibration_record_t cal_record; 281 | uint8_t this_value, last_value; 282 | uint8_t result = 1; 283 | 284 | // The value can be either a specific one or global ("all" / "none") 285 | if(value < CAL_DATA_ALL) { 286 | this_value = value; 287 | last_value = value; 288 | } else { 289 | this_value = 0; 290 | last_value = CAL_DATA_ALL; 291 | } 292 | 293 | // A global value will loop for all values, a specific one only once 294 | do { 295 | if(value > CAL_DATA_ALL) { 296 | cal_data[this_value].scale = 1.0; 297 | cal_data[this_value].shift = 0.0; 298 | } else { 299 | EEPROM.get(sizeof(cal_record) * this_value, cal_record); 300 | 301 | if(cal_record.magic == TSUNAMI_CALIBRATION) { 302 | cal_data[this_value].scale = cal_record.scale; 303 | cal_data[this_value].shift = cal_record.shift; 304 | } else { 305 | result = 0; 306 | } 307 | } 308 | this_value++; 309 | } while (this_value < last_value); 310 | 311 | return result; 312 | } 313 | 314 | /* Returns the measured frequency on the Tsunami input, in hertz. 315 | * If no valid signal is measured, or the Tsunami is not done measuring, NAN 316 | * is returned. 317 | */ 318 | float Tsunami_Class::measureFrequency() { 319 | uint32_t signal_period; 320 | uint8_t signal_divider; 321 | float ret; 322 | 323 | // Disable interrupts so we get a consistent reading from the registers 324 | cli(); 325 | 326 | // Retrieve the instant interval length (multiplied by sixteen) and the 327 | // divider expressed as the power of two by which the input was divided 328 | signal_period = instant_interval; 329 | signal_divider = counter_divider; 330 | 331 | // Re-enable interrupts while the time consuming float division is done 332 | sei(); 333 | 334 | // Calculate the frequency if available, not forgetting that the signal 335 | // period needs to be divided by sixteen to get the real counted value; 336 | // Adding "8" to the 16x signal period is just the equivalent of adding 337 | // "0.5" to it so it gets rounded to the nearest value, not simply down 338 | if(counter_status == COUNTER_STATUS_VALID) { 339 | ret = (16000000.0 / ((signal_period + 8) >> 4)) * (1 << signal_divider); 340 | } else { 341 | ret = NAN; 342 | } 343 | 344 | return ret; 345 | } 346 | 347 | /* Same as above, but with a moving average ratio of "1/8 new data" applied. 348 | * Ordinarily it tracks to the instant value rather quickly but whenever the 349 | * divided signal is a really low frequency the reaction time is observable. 350 | */ 351 | float Tsunami_Class::measureAverageFrequency() { 352 | uint32_t signal_period; 353 | uint8_t signal_divider; 354 | float ret; 355 | 356 | // Disable interrupts so we get a consistent reading from the registers 357 | cli(); 358 | 359 | // Retrieve the average interval length (multiplied by sixteen) and the 360 | // divider expressed as the power of two by which the input was divided 361 | signal_period = average_interval; 362 | signal_divider = counter_divider; 363 | 364 | // Re-enable interrupts while the time consuming float division is done 365 | sei(); 366 | 367 | // Calculate the frequency if available, not forgetting that the signal 368 | // period needs to be divided by sixteen to get the real counted value; 369 | // Adding "8" to the 16x signal period is just the equivalent of adding 370 | // "0.5" to it so it gets rounded to the nearest value, not simply down 371 | if(counter_status == COUNTER_STATUS_VALID) { 372 | ret = (16000000.0 / ((signal_period + 8) >> 4)) * (1 << signal_divider); 373 | } else { 374 | ret = NAN; 375 | } 376 | 377 | return ret; 378 | } 379 | 380 | Tsunami_Class Tsunami; 381 | -------------------------------------------------------------------------------- /src/tsunami.h: -------------------------------------------------------------------------------- 1 | #ifndef __ARDUIN_TSUNAMI_H 2 | #define __ARDUINO_TSUNAMI_H 3 | 4 | #include 5 | #include 6 | #include 7 | extern "C" { 8 | #include 9 | #include 10 | } 11 | 12 | #define TSUNAMI_DDS_CS 8 13 | #define TSUNAMI_DDS_FSEL 5 14 | #define TSUNAMI_DDS_PSEL 6 15 | #define TSUNAMI_DDS_SLEEP 9 16 | #define TSUNAMI_DDS_RESET 11 17 | #define TSUNAMI_AUX 10 18 | #define TSUNAMI_PHASE A1 19 | #define TSUNAMI_PEAK A5 20 | #define TSUNAMI_VIN A4 21 | #define TSUNAMI_AUX_FILTER A0 22 | #define TSUNAMI_SIGN_EN A2 23 | #define TSUNAMI_VAVG A3 24 | #define TSUNAMI_FREQIN_1 4 25 | #define TSUNAMI_FREQIN_2 12 26 | #define TSUNAMI_DAC_CS 7 27 | #define TSUNAMI_FDIV_SEL_0 31 28 | #define TSUNAMI_FDIV_SEL_1 30 29 | #define TSUNAMI_OFFSET_ID 1 30 | #define TSUNAMI_AMPLITUDE_ID 0 31 | #define TSUNAMI_FREQUENCY 16 // MHz 32 | 33 | #define TSUNAMI_DAC_BITS 12 34 | #define TSUNAMI_DAC_RANGE (1 << TSUNAMI_DAC_BITS) 35 | #define TSUNAMI_OFFSET_FS 4074 // Fullscale voltage offset in millivolts 36 | #define TSUNAMI_AMPLITUDE_FS 6606 // Fullscale amplitude in millivolts 37 | 38 | #define TSUNAMI_ADC_BITS 10 39 | #define TSUNAMI_ADC_RANGE (1 << TSUNAMI_ADC_BITS) 40 | #define TSUNAMI_VIN_RANGE 3037 // Fullscale voltage offset in millivolts 41 | #define TSUNAMI_VIN_SCALING ((((int32_t)TSUNAMI_VIN_RANGE) << 17) / TSUNAMI_ADC_RANGE) 42 | 43 | #define TSUNAMI_CALIBRATION 0xDa7aDa7a 44 | 45 | enum OutputMode { 46 | OUTPUT_MODE_SINE = AD983X_OUTPUT_MODE_SINE, 47 | OUTPUT_MODE_TRIANGLE = AD983X_OUTPUT_MODE_TRIANGLE, 48 | }; 49 | 50 | enum CalibratedValue { 51 | CAL_DATA_OFFSET = 0, 52 | CAL_DATA_AMPLITUDE = 1, 53 | CAL_DATA_MEAN_VALUE = 2, 54 | CAL_DATA_PEAK_VALUE = 3, 55 | CAL_DATA_CURRENT_VALUE = 4, 56 | CAL_DATA_ALL = 5, 57 | CAL_DATA_NONE = 6, 58 | }; 59 | 60 | typedef struct { 61 | float scale; 62 | float shift; 63 | } calibration_data_t ; 64 | 65 | typedef struct { 66 | uint32_t magic; 67 | float scale; 68 | float shift; 69 | } calibration_record_t ; 70 | 71 | extern calibration_data_t cal_data[CAL_DATA_ALL]; 72 | 73 | class Tsunami_Class { 74 | public: 75 | Tsunami_Class(); 76 | void begin(); 77 | 78 | /* Applies and saves calibration data for a single value; "scale" does not 79 | * have a unit and "shift" is in whatever unit the value has (millivolts). 80 | */ 81 | uint8_t setCalibrationData(CalibratedValue value, float scale, float shift); 82 | 83 | /* Returns the saved calibration data for a single value; "scale" does not 84 | * have a unit and "shift" is in whatever unit the value has (millivolts); 85 | * Note: this is the stored calibration data, not the current data in use! 86 | */ 87 | uint8_t getCalibrationData(CalibratedValue value, float *scale, float *shift); 88 | 89 | /* Applies saved calibration data for either a specific value, all values, 90 | * or none of them: restores scale 1.0 and shift 0.0 but keeps saved data. 91 | */ 92 | uint8_t useCalibrationData(CalibratedValue value); 93 | 94 | /* Set the output waveform to sine or triangle 95 | */ 96 | inline void setOutputMode(OutputMode out) { 97 | ad983x_set_output_mode(&dds, (ad983x_output_mode_t)out); 98 | } 99 | 100 | // These overloads automatically set the unused frequency register, then 101 | // switch to it. 102 | 103 | /* Set the output frequency, in Hz. 104 | */ 105 | inline void setFrequency(long int frequency) { 106 | current_frequency_reg = 1 - current_frequency_reg; 107 | setFrequency(current_frequency_reg, frequency); 108 | selectFrequency(current_frequency_reg); 109 | } 110 | 111 | /* Set the output frequency, in Hz. 112 | */ 113 | inline void setFrequency(double frequency) { 114 | current_frequency_reg = 1 - current_frequency_reg; 115 | setFrequency(current_frequency_reg, (float)frequency); 116 | selectFrequency(current_frequency_reg); 117 | } 118 | 119 | /* Set the output frequency, in Hz. 120 | */ 121 | inline void setFrequency(float frequency) { 122 | current_frequency_reg = 1 - current_frequency_reg; 123 | setFrequency(current_frequency_reg, frequency); 124 | selectFrequency(current_frequency_reg); 125 | } 126 | 127 | /* Set the output frequency on a given register, in Hz. 128 | */ 129 | inline void setFrequency(byte reg, long int frequency) { 130 | setFrequencyWord(reg, computeFrequencyWord(frequency)); 131 | } 132 | 133 | /* Set the output frequency on a given register, in Hz. 134 | */ 135 | inline void setFrequency(byte reg, double frequency) { 136 | setFrequency(reg, (float)frequency); 137 | } 138 | 139 | /* Set the output frequency on a given register, in Hz. 140 | */ 141 | inline void setFrequency(byte reg, float frequency) { 142 | setFrequencyWord(reg, (frequency * (1l << 28)) / (TSUNAMI_FREQUENCY * 1000000)); 143 | } 144 | 145 | // TODO: Provide setPhase methods. 146 | 147 | /* Enables or disables the DDS's reset mode. 148 | * Reset sets phase accumulator registers to 0, setting the output to 149 | * midscale and resetting the starting phase. 150 | */ 151 | inline void reset(boolean in_reset) { 152 | digitalWrite(TSUNAMI_DDS_RESET, in_reset); 153 | } 154 | 155 | /* Enables or disables the DDS's sleep mode. 156 | * Sleep disables the DDS's DAC. The DDS keeps counting, and a square wave is 157 | * still output to the AUX port if enabled with auxSignOutput(). 158 | */ 159 | inline void sleep(boolean sleeping) { 160 | digitalWrite(TSUNAMI_DDS_SLEEP, sleeping); 161 | } 162 | 163 | /* Selects which frequency register (0 or 1) is used to control the DDS. 164 | */ 165 | inline void selectFrequency(byte reg) { 166 | digitalWrite(TSUNAMI_DDS_FSEL, reg); 167 | } 168 | 169 | /* Selects which phase register (0 or 1) is used to control the DDS. 170 | */ 171 | inline void selectPhase(byte reg) { 172 | digitalWrite(TSUNAMI_DDS_PSEL, reg); 173 | } 174 | 175 | /* Sets signal offset in millivolts. 176 | * When the DDS is disabled (sleep and reset are true), this function can be 177 | * used to generate an output waveform directly, albeit at a very low sample 178 | * rate. 179 | */ 180 | inline void setOffset(int millivolts) { 181 | float scale = cal_data[CAL_DATA_OFFSET].scale; 182 | float shift = cal_data[CAL_DATA_OFFSET].shift; 183 | int32_t value = (float)(millivolts * scale) + shift; 184 | value += TSUNAMI_OFFSET_FS; 185 | value *= TSUNAMI_DAC_RANGE; 186 | value /= TSUNAMI_OFFSET_FS * 2; 187 | if(value < 0) 188 | value = 0; 189 | if(value >= TSUNAMI_DAC_RANGE) 190 | value = TSUNAMI_DAC_RANGE - 1; 191 | 192 | mcp49xx_write(&dac, TSUNAMI_OFFSET_ID, value); 193 | } 194 | 195 | /* 196 | * Sets signal amplitude in millivolts. 197 | */ 198 | inline void setAmplitude(int millivolts) { 199 | float scale = cal_data[CAL_DATA_AMPLITUDE].scale; 200 | float shift = cal_data[CAL_DATA_AMPLITUDE].shift; 201 | int32_t value = (float)(millivolts * scale) + shift; 202 | value *= TSUNAMI_DAC_RANGE; 203 | value /= TSUNAMI_AMPLITUDE_FS; 204 | if(value < 0) 205 | value = 0; 206 | if(value >= TSUNAMI_DAC_RANGE) 207 | value = TSUNAMI_DAC_RANGE - 1; 208 | 209 | mcp49xx_write(&dac, TSUNAMI_AMPLITUDE_ID, TSUNAMI_DAC_RANGE - value - 1); 210 | } 211 | 212 | /* Measures peak to peak amplitude, returning a value in millivolts. Note that a 213 | * decrease in the amplitude will take some time to show up on the output, as 214 | * charge leaks from the storage cap. For a more accurate instantaneous 215 | * reading, set the TSUNAMI_PEAK pin to output, bring it low briefly, then 216 | * return it to input and wait a while for the capacitor to charge. 217 | */ 218 | inline int16_t measurePeakVoltage() { 219 | // TODO: Discharge cap, delay, read. 220 | float scale = cal_data[CAL_DATA_PEAK_VALUE].scale; 221 | float shift = cal_data[CAL_DATA_PEAK_VALUE].shift; 222 | int32_t value = analogRead(TSUNAMI_PEAK); 223 | value *= TSUNAMI_VIN_SCALING; 224 | value >>= 16; 225 | value -= TSUNAMI_VIN_RANGE; 226 | value = (float)(value * scale) + shift; 227 | return (int16_t)value; 228 | } 229 | 230 | /* Measures mean voltage, returning a value in millivolts. 231 | */ 232 | inline int16_t measureMeanVoltage() { 233 | float scale = cal_data[CAL_DATA_MEAN_VALUE].scale; 234 | float shift = cal_data[CAL_DATA_MEAN_VALUE].shift; 235 | int32_t value = analogRead(TSUNAMI_VAVG); 236 | value *= TSUNAMI_VIN_SCALING; 237 | value >>= 16; 238 | value -= TSUNAMI_VIN_RANGE; 239 | value = (float)(value * scale) + shift; 240 | return (int16_t)value; 241 | } 242 | 243 | /* Measures instantaneous voltage, returning a value in millivolts. 244 | */ 245 | inline int16_t measureCurrentVoltage() { 246 | float scale = cal_data[CAL_DATA_CURRENT_VALUE].scale; 247 | float shift = cal_data[CAL_DATA_CURRENT_VALUE].shift; 248 | int32_t value = analogRead(TSUNAMI_VIN); 249 | value *= TSUNAMI_VIN_SCALING; 250 | value >>= 16; 251 | value -= TSUNAMI_VIN_RANGE; 252 | value = (float)(value * scale) + shift; 253 | return (int16_t)value; 254 | } 255 | 256 | /* Measures frequency, returning a value in Hz. 257 | * This works from approximately 1.0Hz upwards. 258 | * 259 | * Return values are accurate, but will suffer some jitter due to the analog 260 | * nature of the input signal. Measuring the square wave output will give a more 261 | * precise result than measuring the sine wave output. 262 | */ 263 | float measureFrequency(); 264 | 265 | /* Same as above, but with a moving average ratio of "1/8 new data" applied. 266 | * Ordinarily it tracks to the instant value rather quickly but whenever the 267 | * divided signal is a really low frequency the reaction time is observable. 268 | */ 269 | float measureAverageFrequency(); 270 | 271 | /* Measures phase offset, returning a figure between 0 and 1. 0 indicates the 272 | * signals are 180 degrees out of phase, while 1 indicates the signals are 273 | * exactly in phase. Note that the analog signal chain imposes some delay, 274 | * meaning that at higher frequencies there is a significant phase shift that 275 | * must be accounted for to get accurate measurements; this function does not 276 | * make any adjustment for that. 277 | */ 278 | inline float measurePhase() { 279 | // TODO: Provide for frequency calibration 280 | 281 | int vphase; 282 | 283 | // Configure ADC to use Vcc power rail reference 284 | setAnalogRef(DEFAULT); 285 | 286 | vphase = analogRead(TSUNAMI_PHASE); 287 | 288 | // Configure ADC to use internal 2.56V reference 289 | setAnalogRef(INTERNAL); 290 | 291 | return (float)vphase / 1024; 292 | } 293 | 294 | /* Configures whether or not the DDS sign signal is output on the AUX port. 295 | */ 296 | inline void enableSignOutput() { 297 | pinMode(TSUNAMI_AUX, INPUT); 298 | digitalWrite(TSUNAMI_AUX, LOW); 299 | digitalWrite(TSUNAMI_SIGN_EN, HIGH); 300 | } 301 | 302 | inline void disableSignOutput() { 303 | digitalWrite(TSUNAMI_SIGN_EN, LOW); 304 | } 305 | 306 | /* Enable the RC filter on the AUX output. 307 | * By disabling auxSignOutput, you can use analogWrite to output either a PWM 308 | * signal (with auxFiltering(false)) or a rectified voltage 309 | * (with auxFiltering(true)). This can be useful, for instance, to generate 310 | * parameter sweeps and graph them on an external tool like an oscilloscope. 311 | */ 312 | inline void enableAuxiliaryFiltering() { 313 | pinMode(TSUNAMI_AUX_FILTER, OUTPUT); 314 | digitalWrite(TSUNAMI_AUX_FILTER, LOW); 315 | } 316 | 317 | inline void disableAuxiliaryFiltering() { 318 | pinMode(TSUNAMI_AUX_FILTER, INPUT); 319 | digitalWrite(TSUNAMI_AUX_FILTER, LOW); 320 | } 321 | 322 | // Handle to the underlying DAC. 323 | mcp49xx_t dac; 324 | 325 | // Handle to the underlying DDS. 326 | ad983x_t dds; 327 | 328 | // The frequency register currently being used 329 | uint8_t current_frequency_reg; 330 | 331 | // The phase register currently being used 332 | uint8_t current_phase_reg; 333 | 334 | private: 335 | /* Set the raw frequency control word used by the DDS 336 | */ 337 | inline void setFrequencyWord(byte reg, uint32_t frequency) { 338 | ad983x_set_frequency(&dds, reg, frequency); 339 | } 340 | 341 | /* Set the raw phase control word used by the DDS 342 | */ 343 | inline void setPhaseWord(byte reg, uint32_t phase) { 344 | ad983x_set_phase(&dds, reg, phase); 345 | } 346 | 347 | /* Select a reference voltage compared to which analog reads get measured 348 | */ 349 | inline void setAnalogRef(uint8_t source) { 350 | // Let the system know we changed the reference so it won't override us 351 | analogReference(source); 352 | 353 | // Switch to the selected Vref with the REFS bits in the ADMUX register 354 | if(source == INTERNAL) { 355 | ADMUX |= _BV(REFS0); 356 | ADMUX |= _BV(REFS1); 357 | } else { 358 | ADMUX |= _BV(REFS0); 359 | ADMUX &= ~_BV(REFS1); 360 | } 361 | 362 | // Apparently NOT waiting for the Vref capacitor to settle is NOT smart 363 | delayMicroseconds(3000); 364 | } 365 | 366 | inline uint32_t computeFrequencyWord(uint32_t frequency) { 367 | // This is a manual expansion of (frequency * 2^28) / m_frequency_mhz 368 | // Since it doesn't require 64 bit multiplies or divides, it results in 369 | // substantially smaller code sizes. 370 | uint32_t lval = ((frequency & 0xFF) << 22) / (15625l * TSUNAMI_FREQUENCY); 371 | uint32_t mval = ((frequency & 0xFF00) << 14) / (15625l * TSUNAMI_FREQUENCY); 372 | uint32_t hval = ((frequency & 0xFF0000) << 6) / (15625l * TSUNAMI_FREQUENCY); 373 | return (hval << 16) + (mval << 8) + lval; 374 | } 375 | }; 376 | 377 | extern Tsunami_Class Tsunami; 378 | 379 | #endif 380 | -------------------------------------------------------------------------------- /test_and_program.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import itertools 3 | import os 4 | import platform 5 | import serial 6 | import subprocess 7 | import sys 8 | import time 9 | 10 | 11 | AVRDUDE_CMD = [ 12 | "avrdude", 13 | "-v", 14 | "-patmega32u4", 15 | "-cavrispmkii", 16 | "-Ulock:w:0x3F:m", 17 | "-Uefuse:w:0xcb:m", 18 | "-Uhfuse:w:0xd8:m", 19 | "-Ulfuse:w:0xff:m", 20 | "-Uflash:w:production.hex:i", 21 | ] 22 | 23 | def find_port(): 24 | if platform.system() == "Windows": 25 | import _winreg as winreg 26 | while True: 27 | key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM") 28 | portname = None 29 | for i in itertools.count(): 30 | try: 31 | portname = winreg.EnumValue(key, i)[1] 32 | except WindowsError: 33 | if portname: 34 | return portname 35 | else: 36 | break 37 | time.sleep(0.1) 38 | else: 39 | while True: 40 | ttys = [filename for filename in os.listdir("/dev") 41 | if filename.startswith("cu.") 42 | and not "luetooth" in filename] 43 | ttys.sort(key=lambda k:(k.startswith("cu."), k)) 44 | if ttys: 45 | return "/dev/" + ttys[0] 46 | time.sleep(0.1) 47 | 48 | 49 | def main(): 50 | print "Calling avrdude..." 51 | result = subprocess.check_call(AVRDUDE_CMD) 52 | if result != 0: 53 | print "avrdude failed with return code %d; quitting." % (result,) 54 | sys.exit(result) 55 | 56 | print "Waiting for serial port..." 57 | time.sleep(9.0) 58 | portname = find_port() 59 | print "Guessing port is %r" % (portname,) 60 | 61 | print "Fetching test results..." 62 | out = open("testresults.txt", "a") 63 | 64 | port = serial.Serial(portname, 115200) 65 | while True: 66 | line = port.readline() 67 | print line, 68 | out.write(line) 69 | if line.startswith("All tests passed"): 70 | sys.exit(0) 71 | elif line.startswith("Selftest FAIL"): 72 | sys.exit(1) 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | --------------------------------------------------------------------------------