Arduino Sinewave Generator
Quote from Matoo robot on 2024年7月6日, pm5:30This project describes how to use an I2C DAC and an Arduino Uno to make a sinewave.
Project description
Overview:
I was reading through the Arduino Forum one day and found a young man who wanted to know how you could use the Arduino to make a sine wave generator. Apparently, he was an engineering student and this was his assignment He was told by some it was not possible, others said it was, but only as fast as 50 Hz. Since our company makes an I2C DAC dev board for the Arduino I thought it would be educational (for me) to make a sine wave generator using the DAC board.
About this Project
A signal generator usually has various signals that is can generate, such as Sine, Square and triangle. Others have a sweep function and an arbitrary waveform. These are useful tools in the workshop. They can be used to test out audio circuits, op amp circuits and testing circuit response. Most modern function generators can easily put out frequencies up to 1 Mhz.
So, while I did not expect an Arduino based sine wave generator to replace my desktop function generator, I thought it would be interesting to see how to go about designing one and how it would perform.
DAC PCB, Arduino and Solderless breadboard
Lookup table
I decided the easiest way to start would be to create a lookup table of values to be used in my sinewave output. The more values you have in the table the better the output will approximate a true sinewave. It is also very convenient to use powers of 2 when creating a lookup table that you are going to cycle through repeatedly. So, 8 values were not going to work and 128 was probably going to tax the ability of the MCU. I decided that 32 values would be a good place to start.
Next, I needed to decide how much resolution to provide. The SF-5 is based on the MCP4725 DAC which is a 12-bit device. (Note: You can purchase the SF-5 on Tindie, or you can buy the DIP package of the device and put it directly onto your solderless breadboard). So, 12 bits it was. I opened an Excel spreadsheet and wrote down the numbers from 0 to 31. The next column I needed was the angle in radians. That is just the index times 2 times Pi divided by 32. The next column is the normalized amplitude of the signal. I simply took the sine of the angle in the previous column. This created a signal that was 2 units peak to peak and centered on zero. Its maximum was +1 and minimum was -1.
Having a 0 to 5V output range I would need a signal that was centered on 2.5V and had an amplitude of ± 2.5V. Next column just multiplied the signal by 2.5. Then we offset it by 2.5V. The signal showed a maximum of 5V and a minimum of 0V. Perfect!
Now we just needed to convert that into a 12-bit number to put in our table. Since we wanted the value to be 4095 when the voltage was 5V, we multiply by 4095 and divide by 5. To check, look at line 8. The voltage output is 5V and the bit count is 4095.
The table
32 2-byte numbers is not a lot of data and could easily be stored in RAM, but this was a learning experience and I wanted to learn how to store the table in FLASH and read it as needed. The advantage is that one day I will have too much data to put in RAM and will need to use the FLASH, so this is a good time to learn. The command is PROGMEM and the statement that stored the data is:
const PROGMEM unsigned int mysine [] = {TABLE}
The code
I wrote a simple looping program that looked for user input on the serial line. In my case when the number 6 was seen, it would jump into the code that dumped the table to the DAC over the I2C bus. Having never used FLASH directly before I first thought I could just index my array and read the data. RTFM – Read the Fine Manual. When reading from FLASH you use a different command –
temp = pgm_read_word_near(mysine + i);
This reads a 16-bit word, or one entry, from the array. I start with i=0 and increase until it is 31 and then set it back to zero again (i = I & 0x1F;). It is nice that we can read from the FLASH in words, but we can only write to the DAC in bytes. We need to do some editing on our data.
The MSB is sent first. The top two bits are the speed, the next two bits are the power down select mode, the next 4 bits are the data. That gets sent off fist using the Wire.write command. Next, we send the lower 8 bits of data, also using the Wire.write command.
How about frequency?
When you go into a loop and just continuously send out the 32 table entries, this is the fastest you can go and the highest frequency you will get. The frequency I get is 92Hz. I then wanted to adjust the frequency and I decided to add a delay () after each table entry. That will be 32ms (roughly) per cycle. So, subsequent frequencies are 23Hz, 13Hz, 9Hz. You can go up to a delay of 256. I think that gave me a period of 8 seconds.
Raw output from DAC
What a terrible output!
Looking at the picture of the raw output you might thing, “What a terrible looking output.” It has a stairstep pattern. That is because those are the 32 voltages we are putting out of the DAC. We could add more entries to the table to make the stairstep pattern become smoother, but that will decrease the maximum frequency. The other thing we can do is to filter out the high frequencies that are making those sharp corners in the waveform when we change voltage levels. I used a simple RC low pass filter; R = 10K, C = 0.1uF for a cutoff frequency of about 160Hz. As you can see it looks much better and is a better approximation of a true sinewave.
Filtered Output
Improving user interface
I allow the delay to be modified by the user from their keyboard by increasing the delay by one unit when a ‘+’ character is received and decreasing the delay when a ‘-‘ character is received.
I also don’t want to lock the user into an endless loop, so I check to see if the user sends down an ‘e’ or an ‘E’ (because nothing is more annoying than having to worry about case sensitivity). When I get that, I treat it as an exit request and jump back to the main loop. I do so a bit inelegantly, without regard for where I leave the voltage output. But remember, we are just having fun here. If this were a product, I would want to leave the output in a known state, probably 0 volts.
This is the main Menu (Numbers should be sequential but didn't copy well)
Improvements
I used a simple user interface that anyone could write on the Arduino. I am sure you can use Python or something on your PC to make a really cool interface. I also did not have a specific reason for doing this, so if you have a project with real requirements, you will likely change the code to suit.
Demo
I made a demo video that includes all the options in the menu including the Sinewave output (6) and put it on Youtube. If you skip to the end you can see the demo of the sinewave:
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.
// Written by Celtic Engineering Solutions LLC 2021 // SF5.ino is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // SF5.ino is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with SF5.ino. If not, see http://www.gnu.org/licenses/. // Title: SF5.ino // Author: The Celtic Engineer // Description: This is example code of a polled interface for the // SF-5 Digital to Analog Converter board // Revision History: (Created Monday June 28 13:08 2021) #include<Wire.h> #include<avr/pgmspace.h> // Definitions #define addr 0b1100011 // Default device address (pin is pulled up to VDD). YOU WILL HAVE TO CHANGE THIS IF YOU CHANGE THE JUMPER. // REGISTERS #define Conver_reg 0X00 // Conversion Register (read only) #define Config_reg 0x01 // Configuration Register #define Low_lim_reg 0X02 // Low Threshold Register #define High_lim_reg 0X03 // High Threshold Register // COMMAND TYPE #define FM 0x00 // Fast Mode #define LD 0x02 // Load DAC #define DaEP 0x03 // Load DAC and EEPROM //Variables unsigned char incomingByte = 0; // Byte from user unsigned char DataH = 0; // High DAC byte unsigned char DataL = 0; // Low DAC byte unsigned char Cmd = 0x00; // Command Byte default is normal unsigned char PDM = 0; // Power Down Mode unsigned char Array[6] = {0, 0, 0, 0, 0}; // Input value unsigned char i = 0; // Counter unsigned char val = 0; // 8 bit character scratchpad unsigned int temp = 0; // 16 bit integer scratchpad unsigned char wait = 0; // Trap unsigned char x = 0; // delays for sine wave const PROGMEM unsigned int mysine[] = { // Save this to FLASH. It is not really necessary because it is a small amount // of data, but an interesting exercise in case you want to use larger data blocks. 2048, 2447, 2831, 3185, 3495, 3750, 3939, 4056, 4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447, 2048, 1648, 1264, 910, 600, 345, 156, 39, 0, 39, 156, 345, 600, 910, 1264, 1648 }; void setup() { // This is your initialization code Wire.begin(); // Start I2C Bus as Master Serial.begin(9600); banner(); // This is part of your user interface } void loop() { // This is the main loop of the program if (Serial.available() > 0) // Wait for a command from user to do something. { // Read the incoming byte from User: incomingByte = Serial.read(); //==================== if (incomingByte == '1') // Read DAC Registers { incomingByte = 0; // Clear incomming byte Wire.requestFrom(addr, 5, 1); i = 0; while (Wire.available()) // Keep doing this as long as there is data avialable { i++; // Incrament the counter Array[i] = Wire.read(); // Get a byte } Serial.print(" This is the Command Byte: "); Serial.print(Array[1], HEX); Serial.print(" DAC Reg MSB Byte: "); Serial.print(Array[2], HEX); Serial.print(" DAC REG LSB Byte: "); Serial.print(Array[3], HEX); Serial.print(" EEPROM Power down + MSB Byte: "); Serial.print(Array[4], HEX); Serial.print(" EEPROM LSB Byte: "); Serial.println(Array[5], HEX); banner(); } //==================== if (incomingByte == '2') // Power Down Select { incomingByte = 0; // Clear incomming byte // Sub-Menu Power Down Mode Select Serial.print(" Select the power down mode "); Serial.print("1 - Normal Mode (0) "); Serial.print("2 - 1K Resistor to GND (1) "); Serial.print("3 - 100K Resistor to GND (2) "); Serial.print("4 - 500K Resistor to GND (3) "); wait = 1; // Set up trap PDM = 0; // Clear out old power down mode while (wait) { // Hang out until user responds if (Serial.available() > 0) // Wait for a command from user to do something. { // Read the incoming byte from User: incomingByte = Serial.read(); if ((incomingByte > 47) && (incomingByte < 53)) {// Make sure it is a number wait = 0; } } } switch (incomingByte) {// Set variable based on user selection case 49: PDM = 0x00; break; case 50: PDM = 0x01; break; case 51: PDM = 0x02; break; case 52: PDM = 0x03; break; } incomingByte = 0; Serial.print(" PDM mode: "); Serial.print(PDM); Serial.print(" "); banner(); } //==================== if (incomingByte == '3') // Command Mode Select { incomingByte = 0; // Clear incomming byte // Sub-Menu Command Mode Select Serial.print(" Select the Command Mode "); Serial.print("1 - Fast (0) "); Serial.print("2 - Write to DAC (2) "); Serial.print("3 - Write to DAC and EEPROM (3) "); wait = 1; // Set up trap Cmd = 0; // Clear out old power down mode while (wait) { // Hang out until user responds if (Serial.available() > 0) // Wait for a command from user to do something. { // Read the incoming byte from User: incomingByte = Serial.read(); if ((incomingByte > 48) && (incomingByte < 52)) {// Only accept 1 , 2 or 3 wait = 0; } } } switch (incomingByte) {// Set variable base on user selection case 49: Cmd = 0x00; break; case 50: Cmd = 0x02; break; case 51: Cmd = 0x03; break; } Serial.print(" CMD mode: "); Serial.print(Cmd); Serial.print(" "); banner(); } //==================== if (incomingByte == '4') // Get value to write { incomingByte = 0; // Clear incomming byte // Get value to write Serial.print(" Enter a value between 0 and 4095 "); Serial.print("You must use leading zeros "); Serial.print("Value: "); wait = 1; i = 0; while (wait) { if (Serial.available() > 0) // Wait for a command from user to do something. { // Read the incoming byte from User: incomingByte = Serial.read(); if ((incomingByte) != 10) { i++; Array[i] = (incomingByte - 48); if (i >= 4) { wait = 0; } } } } // Assemble value temp = 0; temp = 1000 * Array[1] + 100 * Array[2] + 10 * Array[3] + Array[4]; if (temp > 4095) { Serial.print(" Error: Value too large -> "); Serial.print(temp); } else { // Format data for a Normal write DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character } Serial.print(" "); Serial.print(temp); Serial.print(" "); banner(); } //==================== if (incomingByte == '5') // Execute a write { incomingByte = 0; // Clear incomming byte if (Cmd & 0x06) // Reg or Reg and EEPROM { // Normal data 3 bytes val = (((Cmd << 5) & 0xE0) | ((PDM < 1) & 0x06)); // C2 C1 C0 X X PD1 PD0 X Wire.beginTransmission(addr); Wire.write(val); // Command and power down Wire.write(DataH); // MSB Data out Wire.write(DataL); // LSB Data out Wire.endTransmission(); Serial.print(" MSB: "); Serial.print(DataH, HEX); Serial.print(" LSB: "); Serial.println(DataL, HEX); } else // Fast Mode { // Compressed data to 2 bytes val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8 Wire.beginTransmission(addr); Wire.write(val); val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data Wire.write(val); Wire.endTransmission(); Serial.print(" Fast MSB: "); Serial.print(DataH, HEX); Serial.print(" LSB: "); Serial.println(DataL, HEX); } banner(); } //==================== if (incomingByte == '6') // Sine wave example { /* This outputs a sine wave based on the Table of 32 steps saved to FLASH * It is best to look at on an o-scope. * The output is a bit ragged, but you can improve the look by adding an RC * fileter. Place a resistor (10K - 1.5M) and a Cap (0.1uF) depending * on your frequency. A 0-Delay will output a sine wave of 92Hz, while * a 255-Delay will output a sine wave of 0.122Hz (8.2 second period). */ incomingByte = 0; // Clear incomming byte Serial.print(" Outputs a sine wave "); Serial.print("Enter the 'e' key to exit "); Serial.print("+ to increase period and - to decrease period "); Serial.print("Delay time will be printed to Serial Monitor "); Serial.print("Outputing wave now "); wait = 1; // Set trap i = 0; while (wait) { if (Serial.available() > 0) { // Read the incoming byte from User: Should we leave or should we stay? incomingByte = Serial.read(); if ((incomingByte == 'e') | (incomingByte == 'E')) { wait = 0; } if (incomingByte == 43) { // increment x++; Serial.println(x); } else if (incomingByte == 45) { // decrement x--; Serial.println(x); } incomingByte = 0; // Clear incomming byte } // Crude way of waiting. Assumes you don't want to do anything else delay(x); // Get next voltage to send out temp = pgm_read_word_near(mysine + i); //temp = (mysine[i]); DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8 Wire.beginTransmission(addr); Wire.write(val); val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data Wire.write(val); Wire.endTransmission(); // Update counter i++; // incrament couter i = i & 0x1F; // count to 31 then go back to zero } banner(); } //==================== if ((incomingByte == 'H') | (incomingByte == 'h')) // Print Banner { incomingByte = 0; // Clear incomming byte Serial.print(" "); banner(); // This is your User Interface - Menu } } } void banner() { // This is your User Interface - Menu Serial.print (" Digital to Analog Converter SF-5: Main Menu "); Serial.print ("1. Read DAC Registers "); Serial.print ("2. Power Down Select "); Serial.print ("3. Command Mode Select "); Serial.print ("4. Get value to write "); Serial.print ("5. Execute a write "); Serial.print ("6. Sine wave output "); Serial.print ("H. Help Menu "); }
Downloadable files
Schematic Diagram
This shows how to connect the SF-5 to the Arduino Uno
https://projects.arduinocontent.cc/f4b79efa-173e-42eb-8c33-f08b9615ef7e.pdf
This project describes how to use an I2C DAC and an Arduino Uno to make a sinewave.
Project description
Overview:
I was reading through the Arduino Forum one day and found a young man who wanted to know how you could use the Arduino to make a sine wave generator. Apparently, he was an engineering student and this was his assignment He was told by some it was not possible, others said it was, but only as fast as 50 Hz. Since our company makes an I2C DAC dev board for the Arduino I thought it would be educational (for me) to make a sine wave generator using the DAC board.
About this Project
A signal generator usually has various signals that is can generate, such as Sine, Square and triangle. Others have a sweep function and an arbitrary waveform. These are useful tools in the workshop. They can be used to test out audio circuits, op amp circuits and testing circuit response. Most modern function generators can easily put out frequencies up to 1 Mhz.
So, while I did not expect an Arduino based sine wave generator to replace my desktop function generator, I thought it would be interesting to see how to go about designing one and how it would perform.
DAC PCB, Arduino and Solderless breadboard
Lookup table
I decided the easiest way to start would be to create a lookup table of values to be used in my sinewave output. The more values you have in the table the better the output will approximate a true sinewave. It is also very convenient to use powers of 2 when creating a lookup table that you are going to cycle through repeatedly. So, 8 values were not going to work and 128 was probably going to tax the ability of the MCU. I decided that 32 values would be a good place to start.
Next, I needed to decide how much resolution to provide. The SF-5 is based on the MCP4725 DAC which is a 12-bit device. (Note: You can purchase the SF-5 on Tindie, or you can buy the DIP package of the device and put it directly onto your solderless breadboard). So, 12 bits it was. I opened an Excel spreadsheet and wrote down the numbers from 0 to 31. The next column I needed was the angle in radians. That is just the index times 2 times Pi divided by 32. The next column is the normalized amplitude of the signal. I simply took the sine of the angle in the previous column. This created a signal that was 2 units peak to peak and centered on zero. Its maximum was +1 and minimum was -1.
Having a 0 to 5V output range I would need a signal that was centered on 2.5V and had an amplitude of ± 2.5V. Next column just multiplied the signal by 2.5. Then we offset it by 2.5V. The signal showed a maximum of 5V and a minimum of 0V. Perfect!
Now we just needed to convert that into a 12-bit number to put in our table. Since we wanted the value to be 4095 when the voltage was 5V, we multiply by 4095 and divide by 5. To check, look at line 8. The voltage output is 5V and the bit count is 4095.
The table
32 2-byte numbers is not a lot of data and could easily be stored in RAM, but this was a learning experience and I wanted to learn how to store the table in FLASH and read it as needed. The advantage is that one day I will have too much data to put in RAM and will need to use the FLASH, so this is a good time to learn. The command is PROGMEM and the statement that stored the data is:
const PROGMEM unsigned int mysine [] = {TABLE}
The code
I wrote a simple looping program that looked for user input on the serial line. In my case when the number 6 was seen, it would jump into the code that dumped the table to the DAC over the I2C bus. Having never used FLASH directly before I first thought I could just index my array and read the data. RTFM – Read the Fine Manual. When reading from FLASH you use a different command –
temp = pgm_read_word_near(mysine + i);
This reads a 16-bit word, or one entry, from the array. I start with i=0 and increase until it is 31 and then set it back to zero again (i = I & 0x1F;). It is nice that we can read from the FLASH in words, but we can only write to the DAC in bytes. We need to do some editing on our data.
The MSB is sent first. The top two bits are the speed, the next two bits are the power down select mode, the next 4 bits are the data. That gets sent off fist using the Wire.write command. Next, we send the lower 8 bits of data, also using the Wire.write command.
How about frequency?
When you go into a loop and just continuously send out the 32 table entries, this is the fastest you can go and the highest frequency you will get. The frequency I get is 92Hz. I then wanted to adjust the frequency and I decided to add a delay () after each table entry. That will be 32ms (roughly) per cycle. So, subsequent frequencies are 23Hz, 13Hz, 9Hz. You can go up to a delay of 256. I think that gave me a period of 8 seconds.
Raw output from DAC
What a terrible output!
Looking at the picture of the raw output you might thing, “What a terrible looking output.” It has a stairstep pattern. That is because those are the 32 voltages we are putting out of the DAC. We could add more entries to the table to make the stairstep pattern become smoother, but that will decrease the maximum frequency. The other thing we can do is to filter out the high frequencies that are making those sharp corners in the waveform when we change voltage levels. I used a simple RC low pass filter; R = 10K, C = 0.1uF for a cutoff frequency of about 160Hz. As you can see it looks much better and is a better approximation of a true sinewave.
Filtered Output
Improving user interface
I allow the delay to be modified by the user from their keyboard by increasing the delay by one unit when a ‘+’ character is received and decreasing the delay when a ‘-‘ character is received.
I also don’t want to lock the user into an endless loop, so I check to see if the user sends down an ‘e’ or an ‘E’ (because nothing is more annoying than having to worry about case sensitivity). When I get that, I treat it as an exit request and jump back to the main loop. I do so a bit inelegantly, without regard for where I leave the voltage output. But remember, we are just having fun here. If this were a product, I would want to leave the output in a known state, probably 0 volts.
This is the main Menu (Numbers should be sequential but didn't copy well)
Improvements
I used a simple user interface that anyone could write on the Arduino. I am sure you can use Python or something on your PC to make a really cool interface. I also did not have a specific reason for doing this, so if you have a project with real requirements, you will likely change the code to suit.
Demo
I made a demo video that includes all the options in the menu including the Sinewave output (6) and put it on Youtube. If you skip to the end you can see the demo of the sinewave:
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.
// Written by Celtic Engineering Solutions LLC 2021
// SF5.ino is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
// SF5.ino is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with SF5.ino. If not, see http://www.gnu.org/licenses/.
// Title: SF5.ino
// Author: The Celtic Engineer
// Description: This is example code of a polled interface for the
// SF-5 Digital to Analog Converter board
// Revision History: (Created Monday June 28 13:08 2021)
#include<Wire.h>
#include<avr/pgmspace.h>
// Definitions
#define addr 0b1100011 // Default device address (pin is pulled up to VDD). YOU WILL HAVE TO CHANGE THIS IF YOU CHANGE THE JUMPER.
// REGISTERS
#define Conver_reg 0X00 // Conversion Register (read only)
#define Config_reg 0x01 // Configuration Register
#define Low_lim_reg 0X02 // Low Threshold Register
#define High_lim_reg 0X03 // High Threshold Register
// COMMAND TYPE
#define FM 0x00 // Fast Mode
#define LD 0x02 // Load DAC
#define DaEP 0x03 // Load DAC and EEPROM
//Variables
unsigned char incomingByte = 0; // Byte from user
unsigned char DataH = 0; // High DAC byte
unsigned char DataL = 0; // Low DAC byte
unsigned char Cmd = 0x00; // Command Byte default is normal
unsigned char PDM = 0; // Power Down Mode
unsigned char Array[6] = {0, 0, 0, 0, 0}; // Input value
unsigned char i = 0; // Counter
unsigned char val = 0; // 8 bit character scratchpad
unsigned int temp = 0; // 16 bit integer scratchpad
unsigned char wait = 0; // Trap
unsigned char x = 0; // delays for sine wave
const PROGMEM unsigned int mysine[] = {
// Save this to FLASH. It is not really necessary because it is a small amount
// of data, but an interesting exercise in case you want to use larger data blocks.
2048,
2447,
2831,
3185,
3495,
3750,
3939,
4056,
4095,
4056,
3939,
3750,
3495,
3185,
2831,
2447,
2048,
1648,
1264,
910,
600,
345,
156,
39,
0,
39,
156,
345,
600,
910,
1264,
1648
};
void setup() {
// This is your initialization code
Wire.begin(); // Start I2C Bus as Master
Serial.begin(9600);
banner(); // This is part of your user interface
}
void loop() {
// This is the main loop of the program
if (Serial.available() > 0) // Wait for a command from user to do something.
{
// Read the incoming byte from User:
incomingByte = Serial.read();
//====================
if (incomingByte == '1') // Read DAC Registers
{
incomingByte = 0; // Clear incomming byte
Wire.requestFrom(addr, 5, 1);
i = 0;
while (Wire.available()) // Keep doing this as long as there is data avialable
{
i++; // Incrament the counter
Array[i] = Wire.read(); // Get a byte
}
Serial.print("
This is the Command Byte: "); Serial.print(Array[1], HEX);
Serial.print("
DAC Reg MSB Byte: "); Serial.print(Array[2], HEX);
Serial.print("
DAC REG LSB Byte: "); Serial.print(Array[3], HEX);
Serial.print("
EEPROM Power down + MSB Byte: "); Serial.print(Array[4], HEX);
Serial.print("
EEPROM LSB Byte: "); Serial.println(Array[5], HEX);
banner();
}
//====================
if (incomingByte == '2') // Power Down Select
{
incomingByte = 0; // Clear incomming byte
// Sub-Menu Power Down Mode Select
Serial.print("
Select the power down mode
");
Serial.print("1 - Normal Mode (0)
");
Serial.print("2 - 1K Resistor to GND (1)
");
Serial.print("3 - 100K Resistor to GND (2)
");
Serial.print("4 - 500K Resistor to GND (3)
");
wait = 1; // Set up trap
PDM = 0; // Clear out old power down mode
while (wait) { // Hang out until user responds
if (Serial.available() > 0) // Wait for a command from user to do something.
{
// Read the incoming byte from User:
incomingByte = Serial.read();
if ((incomingByte > 47) && (incomingByte < 53)) {// Make sure it is a number
wait = 0;
}
}
}
switch (incomingByte) {// Set variable based on user selection
case 49:
PDM = 0x00;
break;
case 50:
PDM = 0x01;
break;
case 51:
PDM = 0x02;
break;
case 52:
PDM = 0x03;
break;
}
incomingByte = 0;
Serial.print("
PDM mode: ");
Serial.print(PDM);
Serial.print("
");
banner();
}
//====================
if (incomingByte == '3') // Command Mode Select
{
incomingByte = 0; // Clear incomming byte
// Sub-Menu Command Mode Select
Serial.print("
Select the Command Mode
");
Serial.print("1 - Fast (0)
");
Serial.print("2 - Write to DAC (2)
");
Serial.print("3 - Write to DAC and EEPROM (3)
");
wait = 1; // Set up trap
Cmd = 0; // Clear out old power down mode
while (wait) { // Hang out until user responds
if (Serial.available() > 0) // Wait for a command from user to do something.
{
// Read the incoming byte from User:
incomingByte = Serial.read();
if ((incomingByte > 48) && (incomingByte < 52)) {// Only accept 1 , 2 or 3
wait = 0;
}
}
}
switch (incomingByte) {// Set variable base on user selection
case 49:
Cmd = 0x00;
break;
case 50:
Cmd = 0x02;
break;
case 51:
Cmd = 0x03;
break;
}
Serial.print("
CMD mode: ");
Serial.print(Cmd);
Serial.print("
");
banner();
}
//====================
if (incomingByte == '4') // Get value to write
{
incomingByte = 0; // Clear incomming byte
// Get value to write
Serial.print("
Enter a value between 0 and 4095
");
Serial.print("You must use leading zeros
");
Serial.print("Value: ");
wait = 1;
i = 0;
while (wait) {
if (Serial.available() > 0) // Wait for a command from user to do something.
{
// Read the incoming byte from User:
incomingByte = Serial.read();
if ((incomingByte) != 10) {
i++;
Array[i] = (incomingByte - 48);
if (i >= 4) {
wait = 0;
}
}
}
}
// Assemble value
temp = 0;
temp = 1000 * Array[1] + 100 * Array[2] + 10 * Array[3] + Array[4];
if (temp > 4095) {
Serial.print("
Error: Value too large -> ");
Serial.print(temp);
}
else { // Format data for a Normal write
DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified
DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character
}
Serial.print("
");
Serial.print(temp);
Serial.print("
");
banner();
}
//====================
if (incomingByte == '5') // Execute a write
{
incomingByte = 0; // Clear incomming byte
if (Cmd & 0x06) // Reg or Reg and EEPROM
{
// Normal data 3 bytes
val = (((Cmd << 5) & 0xE0) | ((PDM < 1) & 0x06)); // C2 C1 C0 X X PD1 PD0 X
Wire.beginTransmission(addr);
Wire.write(val); // Command and power down
Wire.write(DataH); // MSB Data out
Wire.write(DataL); // LSB Data out
Wire.endTransmission();
Serial.print("
MSB: "); Serial.print(DataH, HEX);
Serial.print("
LSB: "); Serial.println(DataL, HEX);
}
else // Fast Mode
{
// Compressed data to 2 bytes
val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8
Wire.beginTransmission(addr);
Wire.write(val);
val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data
Wire.write(val);
Wire.endTransmission();
Serial.print("
Fast MSB: "); Serial.print(DataH, HEX);
Serial.print("
LSB: "); Serial.println(DataL, HEX);
}
banner();
}
//====================
if (incomingByte == '6') // Sine wave example
{
/* This outputs a sine wave based on the Table of 32 steps saved to FLASH
* It is best to look at on an o-scope.
* The output is a bit ragged, but you can improve the look by adding an RC
* fileter. Place a resistor (10K - 1.5M) and a Cap (0.1uF) depending
* on your frequency. A 0-Delay will output a sine wave of 92Hz, while
* a 255-Delay will output a sine wave of 0.122Hz (8.2 second period).
*/
incomingByte = 0; // Clear incomming byte
Serial.print("
Outputs a sine wave
");
Serial.print("Enter the 'e' key to exit
");
Serial.print("+ to increase period and - to decrease period
");
Serial.print("Delay time will be printed to Serial Monitor
");
Serial.print("Outputing wave now
");
wait = 1; // Set trap
i = 0;
while (wait)
{
if (Serial.available() > 0)
{
// Read the incoming byte from User: Should we leave or should we stay?
incomingByte = Serial.read();
if ((incomingByte == 'e') | (incomingByte == 'E')) {
wait = 0;
}
if (incomingByte == 43) { // increment
x++;
Serial.println(x);
}
else if (incomingByte == 45) { // decrement
x--;
Serial.println(x);
}
incomingByte = 0; // Clear incomming byte
}
// Crude way of waiting. Assumes you don't want to do anything else
delay(x);
// Get next voltage to send out
temp = pgm_read_word_near(mysine + i);
//temp = (mysine[i]);
DataL = ((temp << 4) & 0x00F0); // Only want the bottom 4 bits, but left justified
DataH = ((temp >> 4) & 0x00FF); // Top 8 bits of integer moved to a character
val = (((PDM << 4) & 0x30) | (( DataH >> 4) & 0x0F)); // 0 0 PD1 PD0 D11 D10 D9 D8
Wire.beginTransmission(addr);
Wire.write(val);
val = (((DataH << 4) & 0xF0) | ((DataL >> 4) & 0x0F)); // Low 8 bits of data
Wire.write(val);
Wire.endTransmission();
// Update counter
i++; // incrament couter
i = i & 0x1F; // count to 31 then go back to zero
}
banner();
}
//====================
if ((incomingByte == 'H') | (incomingByte == 'h')) // Print Banner
{
incomingByte = 0; // Clear incomming byte
Serial.print("
");
banner(); // This is your User Interface - Menu
}
}
}
void banner() {
// This is your User Interface - Menu
Serial.print ("
Digital to Analog Converter SF-5: Main Menu
");
Serial.print ("1. Read DAC Registers
");
Serial.print ("2. Power Down Select
");
Serial.print ("3. Command Mode Select
");
Serial.print ("4. Get value to write
");
Serial.print ("5. Execute a write
");
Serial.print ("6. Sine wave output
");
Serial.print ("H. Help Menu
");
}
Downloadable files
Schematic Diagram
This shows how to connect the SF-5 to the Arduino Uno
https://projects.arduinocontent.cc/f4b79efa-173e-42eb-8c33-f08b9615ef7e.pdf