Components and supplies
Arduino UNO
4.99K Resistor
Capacitor 100 nF
SF-5 DAC Board
Resistor 10k ohm
Jumper wires (generic)
Solderless Breadboard Half Size
Tools and machines
Oscilloscope
Project description
Code
SF-5 DAC Example Arduino Code
arduino
This code will output the sinewave discussed, but it also reads and writes to the registers of the MSP4725.
1// Written by Celtic Engineering Solutions LLC 2021 2 3// SF5.ino is free software: you can redistribute it and/or modify 4// it under the terms of the GNU General Public License as published by 5// the Free Software Foundation, either version 3 of the License, or 6// any later version. 7 8// SF5.ino is distributed in the hope that it will be useful, 9// but WITHOUT ANY WARRANTY; without even the implied warranty of 10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11// GNU General Public License for more details. 12 13// You should have received a copy of the GNU General Public License 14// along with SF5.ino. If not, see http://www.gnu.org/licenses/. 15 16// Title: SF5.ino 17// Author: The Celtic Engineer 18// Description: This is example code of a polled interface for the 19// SF-5 Digital to Analog Converter board 20// Revision History: (Created Monday June 28 13:08 2021) 21 22#include<Wire.h> 23#include<avr/pgmspace.h> 24 25// Definitions 26#define addr 0b1100011 // Default device address (pin is pulled up to VDD). YOU WILL HAVE TO CHANGE THIS IF YOU CHANGE THE JUMPER. 27 28// REGISTERS 29#define Conver_reg 0X00 // Conversion Register (read only) 30#define Config_reg 0x01 // Configuration Register 31#define Low_lim_reg 0X02 // Low Threshold Register 32#define High_lim_reg 0X03 // High Threshold Register 33 34// COMMAND TYPE 35#define FM 0x00 // Fast Mode 36#define LD 0x02 // Load DAC 37#define DaEP 0x03 // Load DAC and EEPROM 38 39//Variables 40unsigned char incomingByte = 0; // Byte from user 41unsigned char DataH = 0; // High DAC byte 42unsigned char DataL = 0; // Low DAC byte 43unsigned char Cmd = 0x00; // Command Byte default is normal 44unsigned char PDM = 0; // Power Down Mode 45unsigned char Array[6] = {0, 0, 0, 0, 0}; // Input value 46unsigned char i = 0; // Counter 47unsigned char val = 0; // 8 bit character scratchpad 48unsigned int temp = 0; // 16 bit integer scratchpad 49unsigned char wait = 0; // Trap 50unsigned char x = 0; // delays for sine wave 51const PROGMEM unsigned int mysine[] = { 52 // Save this to FLASH. It is not really necessary because it is a small amount 53 // of data, but an interesting exercise in case you want to use larger data blocks. 54 2048, 55 2447, 56 2831, 57 3185, 58 3495, 59 3750, 60 3939, 61 4056, 62 4095, 63 4056, 64 3939, 65 3750, 66 3495, 67 3185, 68 2831, 69 2447, 70 2048, 71 1648, 72 1264, 73 910, 74 600, 75 345, 76 156, 77 39, 78 0, 79 39, 80 156, 81 345, 82 600, 83 910, 84 1264, 85 1648 86}; 87 88void setup() { 89 // This is your initialization code 90 Wire.begin(); // Start I2C Bus as Master 91 Serial.begin(9600); 92 banner(); // This is part of your user interface 93} 94 95void loop() { 96 // This is the main loop of the program 97 98 if (Serial.available() > 0) // Wait for a command from user to do something. 99 { 100 // Read the incoming byte from User: 101 incomingByte = Serial.read(); 102 103 //==================== 104 if (incomingByte == '1') // Read DAC Registers 105 { 106 incomingByte = 0; // Clear incomming byte 107 108 Wire.requestFrom(addr, 5, 1); 109 i = 0; 110 while (Wire.available()) // Keep doing this as long as there is data avialable 111 { 112 i++; // Incrament the counter 113 Array[i] = Wire.read(); // Get a byte 114 } 115 Serial.print("\ 116 This is the Command Byte: "); Serial.print(Array[1], HEX); 117 Serial.print("\ 118 DAC Reg MSB Byte: "); Serial.print(Array[2], HEX); 119 Serial.print("\ 120 DAC REG LSB Byte: "); Serial.print(Array[3], HEX); 121 Serial.print("\ 122 EEPROM Power down + MSB Byte: "); Serial.print(Array[4], HEX); 123 Serial.print("\ 124 EEPROM LSB Byte: "); Serial.println(Array[5], HEX); 125 banner(); 126 } 127 128 //==================== 129 if (incomingByte == '2') // Power Down Select 130 { 131 incomingByte = 0; // Clear incomming byte 132 133 // Sub-Menu Power Down Mode Select 134 Serial.print("\ 135Select the power down mode\ 136"); 137 Serial.print("1 - Normal Mode (0)\ 138"); 139 Serial.print("2 - 1K Resistor to GND (1)\ 140"); 141 Serial.print("3 - 100K Resistor to GND (2)\ 142"); 143 Serial.print("4 - 500K Resistor to GND (3)\ 144"); 145 wait = 1; // Set up trap 146 PDM = 0; // Clear out old power down mode 147 while (wait) { // Hang out until user responds 148 if (Serial.available() > 0) // Wait for a command from user to do something. 149 { 150 // Read the incoming byte from User: 151 incomingByte = Serial.read(); 152 if ((incomingByte > 47) && (incomingByte < 53)) {// Make sure it is a number 153 wait = 0; 154 } 155 } 156 } 157 switch (incomingByte) {// Set variable based on user selection 158 case 49: 159 PDM = 0x00; 160 break; 161 case 50: 162 PDM = 0x01; 163 break; 164 case 51: 165 PDM = 0x02; 166 break; 167 case 52: 168 PDM = 0x03; 169 break; 170 } 171 incomingByte = 0; 172 Serial.print("\ 173PDM mode: "); 174 Serial.print(PDM); 175 Serial.print("\ 176"); 177 banner(); 178 } 179 180 //==================== 181 if (incomingByte == '3') // Command Mode Select 182 { 183 incomingByte = 0; // Clear incomming byte 184 185 // Sub-Menu Command Mode Select 186 Serial.print("\ 187Select the Command Mode\ 188"); 189 Serial.print("1 - Fast (0)\ 190"); 191 Serial.print("2 - Write to DAC (2)\ 192"); 193 Serial.print("3 - Write to DAC and EEPROM (3)\ 194"); 195 196 wait = 1; // Set up trap 197 Cmd = 0; // Clear out old power down mode 198 while (wait) { // Hang out until user responds 199 if (Serial.available() > 0) // Wait for a command from user to do something. 200 { 201 // Read the incoming byte from User: 202 incomingByte = Serial.read(); 203 if ((incomingByte > 48) && (incomingByte < 52)) {// Only accept 1 , 2 or 3 204 wait = 0; 205 } 206 } 207 } 208 switch (incomingByte) {// Set variable base on user selection 209 case 49: 210 Cmd = 0x00; 211 break; 212 case 50: 213 Cmd = 0x02; 214 break; 215 case 51: 216 Cmd = 0x03; 217 break; 218 } 219 Serial.print("\ 220CMD mode: "); 221 Serial.print(Cmd); 222 Serial.print("\ 223"); 224 banner(); 225 } 226 227 //==================== 228 if (incomingByte == '4') // Get value to write 229 { 230 incomingByte = 0; // Clear incomming byte 231 232 // Get value to write 233 Serial.print("\ 234Enter a value between 0 and 4095\ 235"); 236 Serial.print("You must use leading zeros\ 237"); 238 Serial.print("Value: "); 239 240 wait = 1; 241 i = 0; 242 while (wait) { 243 if (Serial.available() > 0) // Wait for a command from user to do something. 244 { 245 // Read the incoming byte from User: 246 incomingByte = Serial.read(); 247 if ((incomingByte) != 10) { 248 i++; 249 Array[i] = (incomingByte - 48); 250 if (i >= 4) { 251 wait = 0; 252 } 253 } 254 } 255 } 256 257 // Assemble value 258 temp = 0; 259 temp = 1000 * Array[1] + 100 * Array[2] + 10 * Array[3] + Array[4]; 260 if (temp > 4095) { 261 Serial.print("\ 262Error: Value too large -> "); 263 Serial.print(temp); 264 } 265 else { // Format data for a Normal write 266 DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified 267 DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character 268 } 269 Serial.print("\ 270"); 271 Serial.print(temp); 272 Serial.print("\ 273"); 274 banner(); 275 } 276 277 //==================== 278 if (incomingByte == '5') // Execute a write 279 { 280 incomingByte = 0; // Clear incomming byte 281 282 if (Cmd & 0x06) // Reg or Reg and EEPROM 283 { 284 // Normal data 3 bytes 285 val = (((Cmd << 5) & 0xE0) | ((PDM < 1) & 0x06)); // C2 C1 C0 X X PD1 PD0 X 286 Wire.beginTransmission(addr); 287 Wire.write(val); // Command and power down 288 Wire.write(DataH); // MSB Data out 289 Wire.write(DataL); // LSB Data out 290 Wire.endTransmission(); 291 292 Serial.print("\ 293 MSB: "); Serial.print(DataH, HEX); 294 Serial.print("\ 295 LSB: "); Serial.println(DataL, HEX); 296 } 297 298 else // Fast Mode 299 { 300 // Compressed data to 2 bytes 301 val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8 302 Wire.beginTransmission(addr); 303 Wire.write(val); 304 val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data 305 Wire.write(val); 306 Wire.endTransmission(); 307 Serial.print("\ 308Fast MSB: "); Serial.print(DataH, HEX); 309 Serial.print("\ 310 LSB: "); Serial.println(DataL, HEX); 311 } 312 banner(); 313 } 314 315 //==================== 316 if (incomingByte == '6') // Sine wave example 317 { 318 /* This outputs a sine wave based on the Table of 32 steps saved to FLASH 319 * It is best to look at on an o-scope. 320 * The output is a bit ragged, but you can improve the look by adding an RC 321 * fileter. Place a resistor (10K - 1.5M) and a Cap (0.1uF) depending 322 * on your frequency. A 0-Delay will output a sine wave of 92Hz, while 323 * a 255-Delay will output a sine wave of 0.122Hz (8.2 second period). 324 */ 325 incomingByte = 0; // Clear incomming byte 326 Serial.print("\ 327Outputs a sine wave\ 328"); 329 330 Serial.print("Enter the 'e' key to exit\ 331"); 332 Serial.print("+ to increase period and - to decrease period\ 333"); 334 Serial.print("Delay time will be printed to Serial Monitor \ 335"); 336 Serial.print("Outputing wave now\ 337"); 338 339 wait = 1; // Set trap 340 i = 0; 341 342 while (wait) 343 { 344 if (Serial.available() > 0) 345 { 346 // Read the incoming byte from User: Should we leave or should we stay? 347 incomingByte = Serial.read(); 348 if ((incomingByte == 'e') | (incomingByte == 'E')) { 349 wait = 0; 350 } 351 if (incomingByte == 43) { // increment 352 x++; 353 Serial.println(x); 354 } 355 else if (incomingByte == 45) { // decrement 356 x--; 357 Serial.println(x); 358 } 359 incomingByte = 0; // Clear incomming byte 360 } 361 // Crude way of waiting. Assumes you don't want to do anything else 362 delay(x); 363 364 // Get next voltage to send out 365 temp = pgm_read_word_near(mysine + i); 366 //temp = (mysine[i]); 367 DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified 368 DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character 369 370 val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8 371 Wire.beginTransmission(addr); 372 Wire.write(val); 373 val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data 374 Wire.write(val); 375 Wire.endTransmission(); 376 377 // Update counter 378 i++; // incrament couter 379 i = i & 0x1F; // count to 31 then go back to zero 380 } 381 banner(); 382 } 383 384 //==================== 385 if ((incomingByte == 'H') | (incomingByte == 'h')) // Print Banner 386 { 387 incomingByte = 0; // Clear incomming byte 388 389 Serial.print("\ 390"); 391 banner(); // This is your User Interface - Menu 392 } 393 } 394} 395 396void banner() { 397 // This is your User Interface - Menu 398 Serial.print ("\ 399Digital to Analog Converter SF-5: Main Menu\ 400\ 401"); 402 Serial.print ("1. Read DAC Registers\ 403"); 404 Serial.print ("2. Power Down Select\ 405"); 406 Serial.print ("3. Command Mode Select\ 407"); 408 Serial.print ("4. Get value to write\ 409"); 410 Serial.print ("5. Execute a write\ 411"); 412 Serial.print ("6. Sine wave output\ 413"); 414 Serial.print ("H. Help Menu\ 415"); 416} 417
Downloadable files
Schematic Diagram
This shows how to connect the SF-5 to the Arduino Uno
Schematic Diagram
Schematic Diagram
This shows how to connect the SF-5 to the Arduino Uno
Schematic Diagram
Comments
Only logged in users can leave comments
Anonymous user
3 years ago
I did a bunch of experiments a few years ago generating sine waves for a morse code project. I used both PWM techniques and a Teensy processor which has a real DAC. I used all integer math to speed things up. One thing you could do is shrink the size of your sine table by exploiting symmetry in the sine curve. You can get a complete table by storing only one quarter of the values. https://wb8nbs.wordpress.com/2015/12/15/still-more-fun-with-direct-digital-synthesis-teensylc-with-dac/
thomash73
a year ago
Skipping the MSP4725 and using the Arduino itself as a 6-bit DDS, I built a 6-bit R-2R DAC and with the Uno, I get a clean sine wave up to 41 kHz with 64 samples/cycle, progressively less clean as I reduce the number of samples by half, all the way up to a (clean) square wave at 1.33MHz. This is of course with a hand-optimized assembler loop writing to the GPIO's, with interrupts disabled and checking for break conditions in the loop. I'm planning to put this project up soon, but I want to disprove the claims of those saying this type of thing isn't possible without an external synthesizer chip.