Ohm's Law Measurements on Arduino
Arduino boards use the analogRead() function to sample analog voltages through a built-in 10-bit ADC1. On an Uno, the default voltage reference is 5 V, so each ADC count represents roughly 4.9 mV across the 0 to 1023 count range. Applying Ohm's Law on Arduino most often means reading a voltage across a known shunt resistor to derive current, or building a voltage divider with a reference resistor to derive an unknown resistance from a measured output voltage. Consequently, the Arduino acts as a measurement front-end: analogRead() captures the voltage, and the Ohm's Law calculation runs as float arithmetic in the sketch. This guide covers the two most practical Ohm's Law circuits for Arduino and includes complete sketches with Serial Monitor output for each.
Reading voltage with analogRead() and converting to volts
The Arduino ADC samples 0 to VREF across 1024 counts (0 to 1023). Converting a raw reading to voltage requires multiplying by the reference voltage divided by 1023: float vOut = raw * (VREF / 1023.0). On a 5 V Uno, the smallest detectable voltage step is about 4.9 mV. For signals well below 1 V, switching to the internal 1.1 V reference with analogReference(INTERNAL) improves resolution to about 1.1 mV per count, which is useful for measuring the drop across a small shunt resistor at low current.
Yet analogReference() takes effect only after a subsequent analogRead() call; the first reading after switching references is unreliable and should be discarded2. Place a dummy analogRead() call immediately after changing the reference to flush the sample-and-hold capacitor before taking the real measurement. Skipping this step is one of the most common reasons hobbyists see a stale or noisy reading after switching to the internal reference, and adding the dummy call costs nothing but eliminates an entire class of measurement errors.
Improving ADC accuracy with averaging and calibration
The Arduino ADC has an inherent noise floor of about 2 LSBs, which translates to roughly 10 mV at 5 V reference. Averaging 16 consecutive readings reduces this noise by a factor of four3, bringing the effective resolution down to about 2.5 mV. For even better results, take 64 samples and accumulate them in a 32-bit integer before dividing, which avoids floating-point rounding errors during the accumulation. If you need absolute voltage accuracy, calibrate the internal reference against a known voltage source: measure the 1.1 V reference with a DMM, store the correction factor in EEPROM, and apply it to every subsequent reading. This simple calibration can improve absolute accuracy from 5 percent to below 1 percent.
Voltage divider ohmmeter: measuring unknown resistance
A voltage divider with a known reference resistor and an unknown resistor in series converts the unknown resistance to a measurable voltage. Connect the 5 V pin to the reference resistor, the junction between them to the analog pin, and the unknown resistor from the junction to GND. The junction voltage is V_out = 5 × R_unknown / (R_known + R_unknown). Rearranging for R_unknown: R_unknown = R_known × V_out / (VREF - V_out)4.
Choose R_known to be approximately equal to the expected unknown value to maximise ADC sensitivity in the middle of the range. When R_known matches R_unknown, the junction voltage sits at half the supply, giving the largest possible voltage swing for a change in the unknown resistance and therefore the most counts per ohm of change across the measurement.
Choosing R_known for best ADC sensitivity
A 1 kΩ reference gives best resolution for unknowns near 1 kΩ; swap to 10 kΩ for resistors near 10 kΩ. The formula breaks down if V_out is very close to 0 V or VREF, which corresponds to very low or very high R_unknown values. For unknowns more than ten times R_known, a single ADC count error produces a large percentage error in the resistance result; switch to a larger R_known for that range.
A practical approach for wide-range measurement is to use an analog switch or MOSFET to select between two or three reference resistors, giving you a high-resolution window in each range. For most single-range applications, pick R_known at the geometric centre of your expected measurement range to minimise the worst-case error.
Shunt resistor current measurement and power calculation
Placing a known low-value resistor in series with a load and reading the voltage across it gives the current via I = V_shunt / R_shunt5. For a 1 Ω shunt at 500 mA, the voltage is 0.5 V, which maps to about 102 ADC counts at 5 V reference, a usable signal. Using a 0.1 Ω shunt for the same current drops the voltage to 50 mV, requiring the 1.1 V internal reference for adequate resolution.
After calculating current, power follows from P = V_load × I, where V_load is measured simultaneously on a second analog pin scaled down by a voltage divider if the load voltage exceeds VREF. Measuring both values in the same loop iteration keeps the voltage and current samples aligned in time, which matters when the load is a motor or a pulsing LED whose current varies from moment to moment.
Reading voltage and current in the same loop() call
Reading both voltages in the same loop() iteration minimises the time offset between the two ADC samples, which matters most when the current is varying quickly. Place the V_load and shunt analogRead() calls consecutively without any delay between them. If you insert a Serial.print() or delay() between the two readings, the load current may have changed between samples, and the calculated power no longer represents a single operating point. For the most accurate power measurement, take both readings back-to-back, compute the result, and then print or store it. This approach gives you a true instantaneous power reading rather than a time-skewed average.
Notes
analogRead(pin) returns an integer from 0 to 1023. Convert to voltage with: float v = raw * (VREF / 1023.0). On Uno, the default VREF is 5.0 V; use analogReference(INTERNAL) to switch to the built-in 1.1 V reference for better resolution on small signals. Measure current indirectly: place a known shunt resistor in series with the load, read the voltage drop across it with analogRead(), then I = vShunt / R_shunt. On 3.3 V boards (Nano 33 IoT, MKR series), VREF is 3.3 V; update the scaling constant accordingly. Serial.begin(baud) must be called in setup() before Serial.print() works.
Examples
Voltage divider ohmmeter: measuring unknown resistance
const int SENSOR_PIN = A0;
const float VREF = 5.0;
const float R_KNOWN = 1000.0; // 1 kOhm reference resistor
void setup() { Serial.begin(9600); }
void loop() {
int raw = analogRead(SENSOR_PIN);
float vOut = raw * (VREF / 1023.0);
float rUnknown = R_KNOWN * vOut / (VREF - vOut);
Serial.print("R = ");
Serial.print(rUnknown, 1);
Serial.println(" Ohm");
delay(500);
} Wire: 5V -> R_KNOWN -> A0 junction -> R_unknown -> GND. The formula R = R_known * Vout / (VREF - Vout) is the voltage divider rearranged for R_unknown. For best accuracy, choose R_KNOWN close to the expected unknown value.
Shunt resistor current measurement (I = V / R)
const int SHUNT_PIN = A0;
const float VREF = 5.0;
const float R_SHUNT = 1.0; // 1 Ohm shunt in series with load
void setup() { Serial.begin(9600); }
void loop() {
int raw = analogRead(SHUNT_PIN);
float vShunt = raw * (VREF / 1023.0);
float current = vShunt / R_SHUNT; // I = V / R
Serial.print("I = ");
Serial.print(current * 1000.0, 1); // display in mA
Serial.println(" mA");
delay(250);
} Place R_SHUNT in series between supply and load. Read the voltage drop with A0. Minimum detectable current = VREF / (1023 * R_SHUNT). For loads above 500 mA, use a 0.1 Ohm shunt and switch to the INTERNAL 1.1 V reference for higher resolution.
Power calculation from voltage and current (P = V x I)
const int V_PIN = A0; // load voltage (through divider if > VREF)
const int I_PIN = A1; // shunt voltage
const float VREF = 5.0;
const float R_DIV = 3.0; // voltage divider scale factor (e.g. 12V -> 4V)
const float R_SHUNT = 0.1;
void loop() {
float vLoad = analogRead(V_PIN) * (VREF / 1023.0) * R_DIV;
float current = analogRead(I_PIN) * (VREF / 1023.0) / R_SHUNT;
float power = vLoad * current; // P = V x I
Serial.print(power, 2);
Serial.println(" W");
delay(500);
} R_DIV is the reciprocal of the divider ratio: if a 12V supply is scaled to 4V for the ADC, R_DIV = 12/4 = 3.0. Read both channels in the same loop() call to minimise the sampling time offset.
Verify with the Ohm's Law & Power Calculator tool.
Voltage divider ohmmeter: measuring unknown resistance
const int SENSOR_PIN = A0;
const float VREF = 5.0;
const float R_KNOWN = 1000.0; // 1 kOhm reference resistor
void setup() { Serial.begin(9600); }
void loop() {
int raw = analogRead(SENSOR_PIN);
float vOut = raw * (VREF / 1023.0);
float rUnknown = R_KNOWN * vOut / (VREF - vOut);
Serial.print("R = ");
Serial.print(rUnknown, 1);
Serial.println(" Ohm");
delay(500);
} Wire: 5V -> R_KNOWN -> A0 junction -> R_unknown -> GND. The formula R = R_known * Vout / (VREF - Vout) is the voltage divider rearranged for R_unknown. For best accuracy, choose R_KNOWN close to the expected unknown value.
- 1.
Microchip, "ATmega328P ADC Characteristics," microchip.com, accessed June 2026. https://onlinedocs.microchip.com/oxy/GUID-74F8229E-4C43-4FA0-BE7D-1AA303C6F8A4-en-US-6/GUID-20D14AD7-0145-41D0-B958-AEAEB668566A.html
- 2.
Arduino Forum, "Bug with analogReference," forum.arduino.cc, accessed June 2026. https://forum.arduino.cc/t/bug-with-analogreference/22994
- 3.
Analog Devices, "AN-2003: On-Chip Oversampling for the AD7380 Family of SAR ADCs," analog.com, accessed June 2026. https://www.analog.com/en/resources/app-notes/an-2003.html
- 4.
All About Circuits, "Voltage Divider Circuits," allaboutcircuits.com, accessed June 2026. https://www.allaboutcircuits.com/textbook/direct-current/chpt-6/voltage-divider-circuits/
- 5.
All About Circuits, "Resistive Current Sensing: Low-Side vs. High-Side Sensing," allaboutcircuits.com, accessed June 2026. https://www.allaboutcircuits.com/technical-articles/resistive-current-sensing-low-side-versus-high-side-sensing/
ADC noise on Arduino produces ±1-2 count variations that translate directly to resistance error. Average 10 to 20 readings with a running sum and divide by the count before calculating R_unknown. Also confirm the 5 V pin actually reads 5.0 V with a multimeter; USB-powered boards often deliver 4.7 to 4.9 V, and using the wrong VREF constant shifts all calculated values.
At 5 V reference with a 1 Ω shunt, the minimum detectable current is VREF / (1023 × R_shunt) = 5.0 / 1023 = 4.9 mA per ADC count. Switching to the 1.1 V internal reference with a 1 Ω shunt lowers the floor to about 1.1 mA per count. For microamp-level measurements, an external instrumentation amplifier is required before the ADC input.
Not directly. analogRead() captures a single DC-like sample and cannot track an alternating waveform. For AC current, use a current transformer (CT) with a burden resistor and a bias voltage on the input, then sample rapidly and calculate RMS in software. The SCT-013 is a common open-source CT for this purpose. DC shunt measurement with analogRead() is straightforward and accurate.
The Arduino Uno ADC has roughly ±2 LSB accuracy at 5 V reference, which is about ±10 mV absolute uncertainty. For a voltage divider ohmmeter, this translates to roughly 1 to 3% resistance error near the midpoint of the range and worse at the extremes. For high-accuracy work, use an external 16-bit ADC such as ADS1115 over I2C with a stable 3.3 V reference.
It improves resolution on small signals by using the 1.1 V reference instead of the 5 V rail. For resistors well below R_KNOWN, the divider output stays near 0 V and the 5 V reference wastes most of its range. Switching to 1.1 V stretches the usable range and reduces noise per count. Always choose R_KNOWN so the expected V_out falls in the middle third of the reference range for best resolution. CapyToolkit lets you calculate the expected ADC count and resistance result for any R_KNOWN and VREF combination before you write the sketch, which saves time during code debugging and calibration.
Ohm's Law Calculations in Python
Python reduces all four Ohm's Law variants to single-line float expressions. Voltage, current, resistance, and power relate through V = IR and P = VI1, giving four rearrangements that cover every combination of two known quantities. Implementing them as a single solver function with keyword arguments turns the formula sheet into callable code that raises a ValueError when fewer than two inputs arrive or more than two arrive. For engineering scripts, this approach avoids the scattered if-else logic of checking which variable is unknown. Building on this, a Python solver function can drive LED resistor sizing, power budget aggregation across multiple rails, and sweep table generation in the same script with no additional packages beyond the standard library's math module for sqrt().
Implementing the four-formula solver as a Python function
A keyword-argument solver function accepts up to four named parameters and determines the unknown from the two that are provided. Checking which parameter is None identifies the unknown. Each branch applies one of the four formulas: V = I * R, I = V / R, R = V / I, P = V * I, plus the derived forms V = P / I, I = P / V, R = V**2 / P, and P = I**2 * R2.
Raising a ValueError when the function receives fewer than two non-None arguments prevents silent wrong answers from incomplete input. Without this guard, a missing argument would cause a TypeError or produce a division-by-zero error that is harder to diagnose than a clear message telling the caller exactly which arguments are needed.
Raising ValueError to prevent silent wrong answers
The caller sees an explicit error message rather than a None return that propagates through subsequent calculations undetected. For scripts that sweep component values, calling the solver in a list comprehension generates the entire table in one line: rows = [(r, ohms_law(v=5.0, r=r)['i']) for r in resistor_values]. The same function handles all four unknowns without the caller needing to know which formula applies.
This design pattern, where the function validates its inputs and raises on bad data, is more robust than returning None or zero because it forces the calling code to handle the error explicitly rather than silently producing wrong results that propagate through the rest of the script, making the failure impossible to miss.
LED resistor sizing script with E24 rounding
An LED resistor sizing script takes supply voltage, LED forward voltage, and target current as inputs and returns the required resistance and the nearest E24 standard value above it. The resistor value is R = (Vsupply - Vf) / I_target3. Rounding upward to the nearest E24 value keeps current at or below the target4, which protects the LED when supply voltage varies.
Print the chosen resistor value, the resulting current at that standard value, and the resistor power dissipation so you can check wattage as well as resistance in one pass. Including all three values in the same output line makes it easy to scan a batch of LED designs and catch any that exceed a quarter-watt rating before you order parts or wire up a breadboard.
Printing resistor, current, and power together
Include a warning flag in the output when the calculated dissipation exceeds 50 percent of a quarter-watt resistor rating, so the script catches high-dissipation scenarios automatically rather than requiring a separate check. Displaying all three values together makes it obvious when the E24 rounding choice is acceptable versus when a different value or a half-watt part is needed. For example, a high-power LED at 350 mA from a 12 V supply with a 3.2 V forward voltage needs a 25.1 Ω resistor dissipating 3.0 W, which immediately flags as a half-watt or 5 W part rather than a standard quarter-watt component.
Multi-rail power budget script using a list of subsystems
A multi-rail power budget script takes a list of subsystems, each with a name, wattage, and supply voltage, and groups them by rail. For each rail, it sums the watts using sum(), calculates total current using I = P_total / V_rail, and adds 30 percent margin to recommend a supply current rating. Printing a formatted table shows rail voltage, total watts, total amps, and recommended supply rating per rail.
Grouping by voltage uses a defaultdict(list) to bucket each subsystem into its voltage key, then iterating over the keys in sorted order. Conversely, a simpler single-rail version requires only three lines: summing wattage, dividing by voltage, and multiplying by 1.3. For multi-board systems with two or more distinct rails, the grouped approach avoids manually partitioning the load list and scales to any number of rails without modifying the aggregation logic.
Extending the script to generate BOM cost estimates
Once you have the per-rail current totals, extend the script to estimate component costs by looking up the price of appropriately rated regulators, capacitors, and connectors from a supplier API or a local CSV price list. Adding a cost column to the output table turns the power budget into a bill-of-materials cost estimate, which helps you compare the economics of different supply architectures. For example, a single 12 V supply with local buck converters on each board may cost less than a multi-output supply with long cable runs, and the script can quantify that difference before you commit to a design direction.
Notes
All calculations use Python built-in float arithmetic; no external packages are required. Define ohms_law(v=None, i=None, r=None, p=None) using keyword arguments with None as the unknown; the function raises ValueError if more than one unknown is passed. For larger multi-node circuit problems, sympy can solve simultaneous equations symbolically. math.sqrt() is available from the standard library for P = V²/R -> V = sqrt(P * R) derivations. On CircuitPython (MicroPython-based), the same float arithmetic runs directly on the microcontroller with no modification to the core solver logic.
Examples
Four-variable Ohm's Law solver function
import math
def ohms_law(v=None, i=None, r=None, p=None):
"""Solve Ohm's Law for the one unknown given exactly two known values."""
known = {k: val for k, val in dict(v=v, i=i, r=r, p=p).items() if val is not None}
if len(known) < 2:
raise ValueError("Provide at least two known values.")
if len(known) > 3:
raise ValueError("Over-constrained: provide exactly two known values.")
v, i, r, p = known.get('v'), known.get('i'), known.get('r'), known.get('p')
if v is None:
v = i * r if (i and r) else p / i if (p and i) else math.sqrt(p * r)
if i is None:
i = v / r if (v and r) else p / v if (p and v) else math.sqrt(p / r)
if r is None:
r = v / i if (v and i) else v**2 / p
if p is None:
p = v * i
return {'v': v, 'i': i, 'r': r, 'p': p}
result = ohms_law(v=5.0, r=220.0)
print(f"I = {result['i']*1000:.2f} mA, P = {result['p']*1000:.2f} mW") Call with any two of the four keyword arguments. The function solves for the remaining two and returns all four values in a dict. Raises ValueError if fewer than two values are provided.
LED resistor sizing with nearest E24 lookup
import math
E24 = [1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0,
3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1]
def nearest_e24_above(r_ohm):
decade = 10 ** math.floor(math.log10(r_ohm))
for e in E24:
val = e * decade
if val >= r_ohm - 1e-9:
return val
return E24[0] * decade * 10
v_supply, v_f, i_target = 5.0, 1.8, 0.015 # 5V supply, red LED, 15 mA
r_calc = (v_supply - v_f) / i_target
r_e24 = nearest_e24_above(r_calc)
i_actual = (v_supply - v_f) / r_e24
p_resistor = i_actual**2 * r_e24
print(f"Calculated R: {r_calc:.1f} Ohm")
print(f"Nearest E24: {r_e24:.1f} Ohm")
print(f"Actual I: {i_actual*1000:.1f} mA")
print(f"Resistor P: {p_resistor*1000:.1f} mW") nearest_e24_above() returns the first E24 value at or above the calculated resistance. Always rounding up keeps actual LED current at or below the target. Confirm the wattage is within the chosen resistor package rating.
Multi-rail power budget: current per rail with 30% margin
from collections import defaultdict
subsystems = [
{'name': 'Raspberry Pi 5', 'w': 12.0, 'v': 5},
{'name': 'SSD', 'w': 4.5, 'v': 5},
{'name': 'Touchscreen', 'w': 2.5, 'v': 5},
{'name': 'Relay coils x8', 'w': 7.7, 'v': 12},
{'name': 'Fan', 'w': 2.4, 'v': 12},
]
rails = defaultdict(list)
for s in subsystems:
rails[s['v']].append(s)
for voltage in sorted(rails):
total_w = sum(s['w'] for s in rails[voltage])
total_a = total_w / voltage
recommended = total_a * 1.30
print(f"{voltage}V rail: {total_w:.1f} W {total_a:.2f} A -> supply >= {recommended:.1f} A") Add or remove subsystems from the list without changing the aggregation logic. The 30% margin covers inrush and future growth. Each voltage key becomes an independent rail recommendation.
Verify with the Ohm's Law & Power Calculator tool.
Four-variable Ohm's Law solver function
import math
def ohms_law(v=None, i=None, r=None, p=None):
"""Solve Ohm's Law for the one unknown given exactly two known values."""
known = {k: val for k, val in dict(v=v, i=i, r=r, p=p).items() if val is not None}
if len(known) < 2:
raise ValueError("Provide at least two known values.")
if len(known) > 3:
raise ValueError("Over-constrained: provide exactly two known values.")
v, i, r, p = known.get('v'), known.get('i'), known.get('r'), known.get('p')
if v is None:
v = i * r if (i and r) else p / i if (p and i) else math.sqrt(p * r)
if i is None:
i = v / r if (v and r) else p / v if (p and v) else math.sqrt(p / r)
if r is None:
r = v / i if (v and i) else v**2 / p
if p is None:
p = v * i
return {'v': v, 'i': i, 'r': r, 'p': p}
result = ohms_law(v=5.0, r=220.0)
print(f"I = {result['i']*1000:.2f} mA, P = {result['p']*1000:.2f} mW") Call with any two of the four keyword arguments. The function solves for the remaining two and returns all four values in a dict. Raises ValueError if fewer than two values are provided.
- 1.
All About Circuits, "Ohm's Law - How Voltage, Current, and Resistance Relate," allaboutcircuits.com, accessed June 2026. https://www.allaboutcircuits.com/textbook/direct-current/chpt-2/voltage-current-resistance-relate/
- 2.
All About Circuits, "Power in Electric Circuits," allaboutcircuits.com, accessed June 2026. https://www.allaboutcircuits.com/textbook/direct-current/chpt-2/power-electric-circuits/
- 3.
Electronics Tutorials, "LED Resistor and Choosing the Correct Resistor for LED Circuits," electronics-tutorials.ws, accessed June 2026. https://www.electronics-tutorials.ws/resistor/led-resistor.html
- 4.
Wikipedia, "E series of preferred numbers," en.wikipedia.org, accessed June 2026. https://en.wikipedia.org/wiki/E_series_of_preferred_numbers
Use sympy when the circuit involves multiple unknown node voltages that require simultaneous equations, such as Kirchhoff's current law at every node. For single-loop circuits where only one quantity is unknown, the hand-coded four-formula function is faster to write and run. sympy is useful for ladder networks, bridge circuits, and multi-source circuits where manually rearranging formulas for each unknown becomes error-prone.
For most engineering calculations, Python float (64-bit IEEE 754 double) provides 15 to 16 significant digits, which is more than adequate. Rounding errors only become visible when displaying results: use round(value, n) or f-string formatting with a precision specifier to control displayed decimal places. For financial or precision measurement applications, use the decimal module with a defined precision context instead of float.
Yes. CircuitPython supports Python float arithmetic and the math module, so the solver function runs unchanged on any CircuitPython board. The main difference is that CircuitPython uses MicroPython's float, which is 32-bit on some boards (7 significant digits instead of 15). For Ohm's Law at typical engineering precision, 32-bit float is adequate. Avoid math.log10() on very small values on 32-bit targets; the limited range can cause unexpected results near zero.
Work in SI base units throughout the script: volts, amps, ohms, and watts. Convert milliamps and kilohms only at input parsing and output display. Mixing units inside the calculation (milliamps multiplied by kilohms) produces results that are off by a factor of 1,000,000 without any error. A clear comment at the top of the script stating the unit convention prevents this class of error in scripts shared across a team.
Use a list comprehension to sweep voltage from a start to a stop value in steps, calling the solver at each step. For a 100 Ω resistor from 0 to 12 V in 1 V steps: rows = [(v, v/100, v**2/100) for v in range(13)]. Print as a formatted table with print(f"{v:5.1f} V {i*1000:6.1f} mA {p*1000:7.1f} mW") for each row. Swap range() for numpy.linspace() if sub-integer voltage steps are needed. CapyToolkit produces the same sweep results instantly in the browser without writing any code, which is useful for quick checks before committing to a full Python script.
Verifying Ohm's Law in LTSpice
LTSpice verifies Ohm's Law relationships before physical prototyping by solving circuit equations exactly. Whereas a calculator confirms a single calculation, a SPICE simulation confirms every node voltage, branch current, and power dissipation simultaneously, flagging inconsistencies that a manual calculation might miss. LTSpice is free from analog.com and runs on Windows and macOS.1 Its SPICE3f5 compatible netlist language2 describes a circuit in a plain text file, and two directives cover nearly all Ohm's Law verification work: .op for a single DC operating-point solution and .dc for a swept voltage-to-current characteristic. Consequently, a circuit that takes five minutes to build and verify on a breadboard takes two minutes to verify in LTSpice before any components are touched.
Building a resistive circuit netlist and reading .op results
A SPICE netlist describes a circuit as a list of elements, each on one line naming the component, its two nodes, and its value. A single resistor and voltage source need only three lines plus a .op directive. The .op directive tells LTSpice to solve the DC operating point once and report all node voltages and element currents.3
After running the simulation, right-clicking a node in the schematic shows its voltage. Right-clicking a resistor or voltage source shows the current through it, the voltage across it, and the power dissipated, so you can verify V = IR and P = VI for every element in the circuit without picking up a multimeter or working through the arithmetic by hand.
Reading node voltages and element currents after .op
A V = 5 V, R = 100 Ω circuit shows I = 50 mA and P = 250 mW on the resistor without any manual calculation. For LED circuits, the operating point also shows the exact LED current after the junction voltage settles, verifying that the series resistor choice lands within the LED's rated current.
Save your netlist file before running any simulation so the output stays linked to the correct circuit revision. A useful workflow: create the schematic in LTSpice's graphical editor, run .op, then view the netlist (View > SPICE Netlist) to see the exact syntax. This teaches you the netlist format while giving you a visual cross-check that the schematic matches your intent.
Using .dc sweep to plot the V-I characteristic and confirm slope = 1/R
The .dc directive sweeps a voltage source from a start value to a stop value in defined steps, solving the operating point at each step.4 Plotting the resulting current versus voltage produces the V-I characteristic of the circuit. For a linear resistor, the V-I curve is a straight line whose slope equals 1/R.
Yet the slope is visible only after the simulation completes. In LTSpice, probe the current through the voltage source by clicking on the source symbol after simulation; the probe icon changes to a current probe. The resulting trace shows current on the Y axis and swept voltage on the X axis. A steeper slope means lower resistance; a shallower slope means higher resistance.
Sweeping resistor values to explore design tradeoffs
Sweeping the source from 0 to 12 V in 0.1 V steps produces 120 data points that confirm linearity across the full voltage range. Extend this technique by adding a .step param Rval list 100 220 470 1000 directive to run the same sweep for multiple resistor values in a single simulation.5 The waveform viewer overlays all four V-I curves on one plot, letting you visually compare how each resistor value affects the current at every voltage point. This is especially useful for selecting a current-limiting resistor: you can sweep the candidate values and immediately see which one keeps the current within the LED's safe operating area across the full supply voltage range, including the upper and lower tolerance bounds of the supply.
Simulating an LED + resistor circuit with a built-in diode model
LTSpice includes generic diode models that approximate the exponential V-I characteristic of real LEDs. Placing a diode in series with a resistor and a DC voltage source simulates the actual LED current after the junction voltage settles, which differs from the ideal Ohm's Law calculation that uses a fixed forward voltage assumption.
The generic D model in LTSpice has default parameters that approximate a silicon signal diode rather than an LED, but it still demonstrates the non-linear V-I curve that makes an LED circuit behave differently from a simple resistive load. The exponential turn-on characteristic means the current stays near zero until the junction voltage approaches the forward voltage, then rises steeply, which is exactly the behaviour a fixed Vf estimate in a hand calculation cannot capture.
Comparing SPICE results with bench measurements
For a more accurate LED simulation, define a .model DLED D() statement with the LED's saturation current and ideality factor from its datasheet.6 The .op result shows the exact LED current, the voltage across the resistor, and the voltage across the diode junction, confirming that the resistor choice produces the intended operating point. If the simulation current differs noticeably from the Ohm's Law estimate, the difference reveals how much the fixed forward-voltage assumption overstates or understates the real operating current. A practical workflow: simulate the circuit in LTSpice first, note the predicted current, then build the circuit on a breadboard and measure the actual current with a multimeter. The difference between the two values teaches you how well the models match reality and where the simplifying assumptions in your hand calculations break down.
Notes
LTSpice is free from analog.com. Voltage sources use the syntax Vname n+ n- value (e.g., V1 in 0 DC 5). Resistors use Rname n+ n- value (e.g., R1 in out 100). The .op directive computes the DC operating point; right-clicking a node after simulation shows its voltage, and right-clicking an element shows the current through it. The .dc Vname vstart vstop vstep directive sweeps a source and outputs V-I data for plotting. LTSpice uses SPICE3f5 compatible netlist syntax.
Examples
Single resistor .op netlist: verify V = IR
* Ohm's Law verification: V = 5V, R = 100 Ohm * Expected: I = 50 mA, P = 250 mW V1 in 0 DC 5 R1 in 0 100 .op .end
Save as a .sp or .cir file and open in LTSpice. After running .op, right-click R1 to see I = 50 mA and P = 250 mW. Right-click node "in" to confirm V = 5 V. The .op directive runs once and reports the DC solution.
.dc sweep: plot V-I characteristic and confirm slope = 1/R
* V-I sweep: R = 220 Ohm from 0 to 12V * Expected slope: 1/220 = 4.545 mA/V V1 in 0 DC 0 R1 in out 220 R2 out 0 0 ; short to GND via zero-ohm element (ensures a measurable node) .dc V1 0 12 0.1 .end
After running, click on V1 to probe its current. The plot shows a straight line from 0 to 54.5 mA. Measure any two points: delta_I / delta_V = 1/R. A straight line confirms the element is Ohmic across the full voltage range.
LED + resistor simulation with a generic diode model
* LED + 220 Ohm resistor from 5V supply * Generic diode model approximates LED junction V1 in 0 DC 5 R1 in anode 220 D1 anode 0 DLED .model DLED D(Is=1e-12 N=2.0) .op .end
Right-click D1 after .op to see the actual junction voltage and diode current. Compare against the ideal calculation I = (5 - Vf) / 220 where Vf is the simulated junction voltage. The diode model captures the exponential V-I curve; the fixed-Vf assumption is an approximation.
Verify with the Ohm's Law & Power Calculator tool.
Single resistor .op netlist: verify V = IR
* Ohm's Law verification: V = 5V, R = 100 Ohm * Expected: I = 50 mA, P = 250 mW V1 in 0 DC 5 R1 in 0 100 .op .end
Save as a .sp or .cir file and open in LTSpice. After running .op, right-click R1 to see I = 50 mA and P = 250 mW. Right-click node "in" to confirm V = 5 V. The .op directive runs once and reports the DC solution.
- 1.
Analog Devices, "LTspice Simulator," analog.com, accessed June 2026. https://www.analog.com/en/resources/design-tools-and-calculators/ltspice-simulator.html
- 2.
LTwiki, "B. Circuit description," ltwiki.org, accessed June 2026. https://ltwiki.org/LTspiceHelpXVII/LTspiceHelp/html/CircuitDescription.htm
- 3.
LTwiki, ".OP -- Find the DC operating point," ltwiki.org, accessed June 2026. https://ltwiki.org/LTspiceHelpXVII/LTspiceHelp/html/DotOp.htm
- 4.
All About Circuits, "Analysis Options," allaboutcircuits.com, accessed June 2026. https://www.allaboutcircuits.com/textbook/reference/chpt-7/analysis-options/
- 5.
Analog Devices, "LTspice: Using the .STEP Directive to Perform Repeated Analysis," analog.com, June 2026. https://www.analog.com/en/resources/technical-articles/ltspice-using-the-step-command-to-perform-repeated-analysis.html
- 6.
University at Buffalo, "SPICE diode parameter table," acsu.buffalo.edu, accessed June 2026. https://www.acsu.buffalo.edu/~wie/applet/spice_pndiode/spice_diode_table.html
Download the installer from analog.com/en/resources/design-tools-and-calculators/ltspice-simulator.html. On Windows, run the .exe installer and accept the defaults. On macOS, open the downloaded .dmg and drag LTSpice to the Applications folder. No licence key is required; LTSpice is free with no simulation time limits. Update through Help > Check for Updates inside the application.
.op solves the circuit once at a fixed operating point and reports node voltages, branch currents, and power dissipation for that single bias condition. .dc sweeps a voltage or current source across a range of values and solves the operating point at each step, producing data for a V-I characteristic plot. Use .op to verify a specific design point; use .dc to see how the circuit behaves across a range of inputs.
After running .op, move the cursor over the component in the schematic. When the cursor changes to a current clamp icon (two rectangles), click the component. LTSpice displays the current, voltage across the element, and power dissipated in the status bar or in a floating label. For a resistor, the current direction follows from the node order in the netlist: current flows from n+ to n- through the element.
Yes. Add .param Rval=100 to the netlist and use {Rval} in place of the resistor value: R1 in out {Rval}. Then use .step param Rval list 100 220 470 1000 to run a separate .op for each value. The simulation runs once per parameter value, and the waveform viewer overlays all results on one plot, making it easy to compare currents and voltages across the resistor sweep.
For resistors, LTSpice .op is exact for the given component values: the SPICE3f5 solver applies Kirchhoff's laws numerically to convergence tolerances below 0.01%. Bench measurements differ from the simulation because real resistors have tolerance (1 to 5%), the supply voltage has ripple and drift, and lead and contact resistances add small series impedances. The simulation represents the ideal nominal case; the bench represents the real-world distribution around that nominal. CapyToolkit provides the same nominal-case verification before you simulate or build: enter the expected values and confirm the currents and power dissipations match your hand calculations, so the LTSpice result becomes a third independent check rather than the first.