DACs are often used together with analog circuits. As an example for this we will use the MCP4725 to build a programmable current source.
In the first part of this tutorial, we learned how the MCP4725 works and can be used for signal generation. We looked at a very simple example and generated a sawtooth signal. However, DACs can not only be used for signal generation. The true power of DACs and ADCs lays in the fact, that they allow us to combine analog and digital circuits.
While there lots of example out there on how to use an ADC to e.g. integrate an analog sensor in to microcontroller projects, there are far fewer examples on how to use DACs in conjunction with other analog circuits. There is nothing wrong about that. A lot of amazing projects work without a DAC. Additionally, there are lots of Arduino modules, that take care of almost everything themselves, which means that we often don't need any external analog circuits. In facts, it's probably not a big issue that the Arduino Uno doesn't have a DAC. Only very few people would use it anyway. Many fear the higher complexity of analog circuits.
However, I think to understand what a DAC is really capable of if used in conjunction with analog circuits, it is important, to once look at a slightly more advanced circuits. As our second example we will thus build a programmable current source and use the DAC to set the desired output current. Nothing fancy, nothing that hasn't been done before, but I think this is a good example, that is not too complicated and still useful.
In the last tutorial, we already looked at how to connect the MCP4725 to the Arduino Uno, which isn't that hard. Building an adjustable current source, however, might seem to be more difficult. We are going to use a voltage-to-current converter circuit with an operational amplifier, or short op amp, for this. This circuit allows us to control the current by changing the output voltage of the DAC. It is shown in the picture below and only requires few components. The most important component is the LM358 op amp. I choose the LM358 because it is common which increases the likelihood that you have one laying around and don't need to buy one. Feel free to replace it with another suitable op amp. The LM358 really isn't anything special.
If you are not familiar with op amp circuits, it's probably difficult for you to understand how this circuit works. Op amps can be used in a lot of different circuits and one could probably create a whole tutorial series just for them. At this place, however, I just want to give a quick introduction which hopefully helps you to understand how this circuit works If you already know how a voltage-to-current converter circuit works or just don't care, just skip over this explanation and take a look at the code and how the DAC is used to control the current.
The image below shows the schematic symbol for operational amplifiers. The supply voltage pins are often omitted for simplicity.
Operational amplifiers are differential amplifiers. They amplify the difference between the voltages at their two input pins IN+
and IN-
. Op amps have a very high amplification factor. For the LM358 this is typically a factor of 100000. This means that a difference of 1 mV between IN+
and IN-
would in theory result in an output voltage of 100 V. This is of course not possible if the op amps supply voltage is only 5 V. The op amp is saturated. It outputs the highest possible voltage. For most op amps this isn't the supply voltage. Only special rail-to-rail op amps can output voltages up to the supply voltage. For the LM358 the maximum output voltage is 1.5 V below the supply voltage which equates to 3.5V at 5 V.
It is not really useful if the op amp is always saturated. Additionally, the amplification factor in this so-called open loop configuration is highly temperature dependent. This is why op amps are almost always used with feedback. To understand what this means, let's have a look at a very simple op amp circuit a so-called voltage buffer.
A voltage buffer is kind of the extreme example for op amp feedback. The op amps output is directly connected to IN-
. As we know the op amp amplifies the voltage difference between IN+
and IN-
. In this case, however, the output is connected to IN-
and we thus get an interesting effect.
If the output voltage and thus the voltage at IN-
is slightly lower than the one at IN+
this difference gets amplified and causes the output voltage to rise. If, however, the output voltage is higher than the voltage at IN+
this negative voltage difference gets amplified and causes the output voltage to fall. This two effects oppose each other and cause the output voltage to level off to the same voltage as supplied at IN+
. We can generalize this and say, if an op amp is used with feedback, the output voltage always adjusts in a way that minimizes the voltage difference between the inputs IN+
and IN-
.
The most popular example for the use of op amps with feedback is the so-called non-inverting amplifier. It is very similar to the voltage buffer, but it uses a voltage divider for feedback. This voltage divider allows to configure the desired amplification factor - the gain of the amplifier circuit. If R1
and R2
have the same value, we get only half the output voltage at IN-
which in turn means that the output voltage of the op amp needs to be exactly twice as high as the voltage at IN+
to minimize the voltage difference between IN+
and IN-
to zero. The amplification circuit has a gain of 2.
Let's have a look at the voltage-to-current converter circuit we need for our example.
At first sight, this circuit looks very similar to the non-inverting amplifier. There is, however, and important difference: we don't know the resistance value for \(R_{LOAD}\). In our example circuit this load is an LED. Similar to all other diodes LEDs don't have a fixed resistance. This is the reason why we can't control an LEDs brightness by changing the voltage. Once the voltage rises above the forward voltage of the LED and the LED starts to light up and the current quickly increases, if we increase the voltage further. If the current is not limited by a resistor, the LED gets damaged.
In this example we don't want to control the brightness by changing the voltage, but by adjusting the current via the op amp. For this, we don't have to know the value for \(R_{LOAD}\). The current through the LED is the same that flows through the resistor \(R_{SENSE}\). This sense resistor has a known value - in our case 100 Ω. Part of the output voltage of the op amp drops over the LED, the other part is dropped over the sense resistor. The voltage dropped over the sense resistor is important for us, because it is also the voltage supplied to the IN-
input of our op amp. As we know the output voltage of the op amp adjusts itself to minimize the difference between IN+
and IN-
. This means, we can use the DAC to set the voltage on IN+
and the op amp adjusts the output voltage in a way, that the voltage drop over \(R_{SENSE}\) matches the voltage set by the DAC. If we are able to control the voltage drop over \(R_{SENSE}\) this means we can also control the current flowing trough it and the LED. The current for a specific DAC output voltage can be easily calculated using ohm's law:
\(I = {V_{SET} \over R_{SENSE}}\)
Of course, this circuits has its limits. The value of the sense resistor determines the current range we can choose from. With the DAC we can output a maximum voltage of 5 V. This means that the maximum current using our 100 Ω sense resistor is \(I = {5 V \over 100 Ω} = 50 mA\). In theory one could increase this value by choosing a smaller resistor, however, the LM358 is not capable of supplying more than 40 mA anyway. We should thus not exceed this value. There is however, another limit we should keep in mind. If we use the 5 V supply voltage of our Arduino as supply voltage for the op amp, the LM358 reaches saturation at roughly 3.5 V. If we subtract the 2 V forward voltage for our red LED, we are left with a maximum of 1.5 V that can be dropped over the sense resistor. This means our maximum current is around 15 mA, just because the op amp can't increase its output voltage further.
To work around this issue, I connected the op amp's supply voltage to Vin
and not to 5V
. This was done intentionally. If we use a higher voltage to power the Arduino Uno, e.g. a 9 V battery, the op amp is able to output a higher voltage of up to 7.5 V. This allows us to use currents up to 40 mA and even drive more than one LED in series.
Warning
Do not connect Vin
or the output of the DAC to any other Arduino pin. The voltages on Vin
and the DAC's output can exceed 5V and would damage your Arduino Uno.
Now that we have build up the circuit, let's have a look at the code required to control the current:
#include <Adafruit_MCP4725.h>
// DAC
const int I2C_ADDR = 0x60;
Adafruit_MCP4725 dac;
// Amplifier
const int Rsense = 100;
void setup() {
Serial.begin(9600);
dac.begin(I2C_ADDR);
}
void loop() {
// Read current
Serial.print("Set Current (mA): ");
while(!Serial.available());
float current = Serial.parseFloat();
Serial.println(current);
// Calculate required voltage drop over Rsense
float setVoltage = current*Rsense/1000;
// Set DAC Value
int dacValue = setVoltage/5*4096;
if(dacValue > 4095 || dacValue < 0)
{
Serial.println("Error: specified value is out of range");
}
else
{
dac.setVoltage(dacValue, false);
}
}
The program starts by reading the desired current, the user entered into the serial monitor, using Serial.parseFloat()
. We then use this value to calculate the output voltage for the DAC that is needed to achieve the desired current. This value is then converted into a value between 0
and 4095
and send to the DAC. I added a little check, to show an error, if the required voltage exceeds the output range of the DAC. I did not include a check to limit the maximum current to 40 mA. This allows you to adjust the resistor value and configure higher currents, if you use a more powerful op amp or an additional transistor as voltage buffer at the op amp output to increase the maximum current.
Once you upload this program onto your Arduino, you can choose different current values to control the LEDs brightness. If you additionally use an 9 V battery to power the Arduino, you will be able to add a second LED in series to the first one. The current flow and the brightness will stay the same. Impressive, isn't it?
Note
If you have issues when inputting the current value in the serial monitor, please make sure, that you have configured the serial monitor to send 'No line endings'. You can configure this in the combo box in the bottom right corner of the serial monitor.