Shine one fixed lamp on an ANYSOLAR KXOB25-05X3F. Connect eight resistors one at a time and measure the voltage across each. Find which of those resistors receives the most power in this setup. Reconnect the same reference resistor before, during and after the sweep to see whether the panel’s response stayed close enough to compare the readings.
A photovoltaic device converts light into electricity. This small module, sold as a SolarBIT, contains three silicon cells connected in series. A load draws current from its output. Voltage and current both matter when comparing how much electrical power reaches that load. A large resistor can give the largest voltage while receiving much less power than a smaller resistor.
This lesson describes a bench setup for you to build. The worked numbers are constructed examples. Use the blank worksheet for your own observations.
Use the active Gen3 ANYSOLAR KXOB25-05X3F, the 23 × 8 × 1.8 mm model. ANYSOLAR’s preliminary June 2026 datasheet shows the module’s light-sensitive front and the printed rear separately. Looking directly at the rear with the part number upright, the left end pad is positive and the right end pad is negative. The opposite face, with three dark cell areas, faces the lamp. Check the marking and rear drawing before attaching wires. Keep the exact order label with the module. ANYSOLAR’s finish notice ASL2026-002 describes a texture change for products with ethylene tetrafluoroethylene (ETFE) film and retains the electrical specifications. Use the order label and pad markings to establish the identity and orientation.
Use a securely mounted and prewired specimen of that exact part. If starting with the bare surface-mount part, have a builder familiar with small surface-mount parts prepare it using the manufacturer’s soldering instructions on page 3. ANYSOLAR specifies manual soldering for 5 seconds below 400 °C and gives a separate low-temperature reflow recommendation. Once the leads are attached, fix them to a holder so swapping resistors cannot pull on the pads. Leave the front uncovered and avoid clamping or bending the module.
| SolarBIT terminal | Connect here |
|---|---|
| P, rear + pad | One end of the chosen load resistor and the meter’s red voltage lead. |
| N, rear − pad | The other end of the chosen load resistor and the meter’s black COM lead. |
The voltmeter stays in parallel, across the same two points as the resistor. The two voltages are therefore the same. With the resistor removed, the meter still draws a little current through its nominal 10 MΩ input. Call this reading near-open voltage. During the sweep, the extra meter current relative to the resistor current is approximately RL ÷ 10 MΩ. It is about 0.082% at 8.2 kΩ. The calculation reports power in the resistor alone. The meter has a small effect on the operating voltage.
At the 3.000 V upper limit of this worksheet, 47 Ω at its 1% low tolerance would dissipate about 0.193 W. A ¼ W part covers that limit for this setup. The worksheet limit is an experiment boundary. Use ordinary indoor lighting geometry without reflectors, lenses or concentrated sunlight.
If the voltage falls to zero for every load, verify that the module is illuminated on its front, the resistor is actually connected across both nodes and the meter is still in DC voltage mode. Do not switch the meter to current mode across the panel. The procedure estimates current through each known resistor instead of shorting the output.
Use Ohm’s law, IL = V ÷ RL, for the load current. Power in the resistor is PL = V × IL = V² ÷ RL. A watt (W) is one joule of energy transferred per second. A milliwatt (mW) is one thousandth of a watt. For example, a constructed reading of 0.994 V with 390 Ω gives about 2.55 mA and 2.53 mW. At 1.279 V with 8.2 kΩ, the larger voltage gives about 0.156 mA and 0.199 mW.
The program uses nominal resistor values. It gives each point a screening band from the 1% load tolerance and Fluke’s specified ±(0.05% of reading + one count) on the 6.000 V DC range, where one count is 0.001 V. For the low bound, use the smallest nonnegative voltage and the largest allowed resistance. For the high bound, use the largest voltage and the smallest resistance. Use a meter in calibration and within the manufacturer’s specified conditions. These bands cover those two instrument and part terms. They leave out shadows, contact changes, temperature drift and other setup errors.
The program checks the 820 Ω readings first. It computes the largest minus the smallest of the three reference voltages as a percentage of the mean of the first and last references. All three readings must reach 0.100 V and the spread must stay at or below 3%. This is a practical screening rule for this worksheet. It can catch changes in the lamp, panel temperature or connections, and can miss a change between the reference readings. When it fails, the program keeps the individual points and leaves the ranking empty. It omits the percentage if a reference is below 0.100 V. Inspect the setup before repeating.
When the reference passes, compare the eight estimated powers. A single highest estimate counts as resolved under this screen only if its lower bound exceeds every other upper bound. Close readings can remain unresolved. The strongest sample applies to the eight loads under these conditions. The physical peak may be between the tested values or outside the sweep. Test more resistors around the highest region in a separate run to narrow the bracket, and keep that run separate. The supplied program uses the stated eight-load plan.
The June 2026 preliminary manufacturer datasheet lists typical open-circuit voltage 2.07 V, short-circuit current 19.5 mA and 30.7 mW at a maximum-power operating point of 1.67 V and 18.4 mA. Those values are under 1 sun (100 mW/cm²), Air Mass 1.5 and 25 °C. Air Mass 1.5 describes the standardized spectrum used for that sunlight test. The maker’s typical point gives an equivalent load near 91 Ω in those conditions. Your lamp has its own spectrum, output and geometry. The reference resistor checks repeatability under that lamp. It provides no calibrated light level. Review the site’s power and component ratings lesson for more practice with V² ÷ R.
Save the code as solar_sweep.py. Copy the blank comma-separated values (CSV) worksheet to observations.csv, fill in the actual elapsed seconds and volts, and run python3 solar_sweep.py observations.csv. Preserve the row labels and the load column. The output is JavaScript Object Notation (JSON), a text format with named values. Read reference_check first, then the eight sweep_points. A null result in resolved_best_load_ohm leaves the best choice unresolved under the screen. Missing, out-of-range or malformed data produces a clear error.
"""Analyze one fixed-lamp KXOB25-05X3F resistor sweep."""
import argparse
import csv
import json
import math
import sys
LOADS = (47, 91, 180, 390, 820, 1800, 3900, 8200)
REFERENCE = 820
HEADER = ("reading", "load_ohm", "elapsed_s", "voltage_v")
PLAN = (
("near_open_before", None),
("reference_before", REFERENCE),
*(("sweep", load) for load in LOADS),
("reference_after", REFERENCE),
("near_open_after", None),
)
def finite(raw, name):
try:
value = float(raw)
except ValueError:
raise ValueError(f"{name} must be a finite number") from None
if not math.isfinite(value):
raise ValueError(f"{name} must be a finite number")
return value
def power_point(load, time, voltage):
voltage_error = 0.0005 * voltage + 0.001
low_voltage = max(0.0, voltage - voltage_error)
high_voltage = voltage + voltage_error
return {
"load_ohm": load,
"elapsed_s": time,
"voltage_v": voltage,
"load_current_ma": 1000 * voltage / load,
"load_power_mw": 1000 * voltage**2 / load,
"power_screen_mw": [
1000 * low_voltage**2 / (1.01 * load),
1000 * high_voltage**2 / (0.99 * load),
],
}
def analyze(stream):
reader = csv.reader(stream, strict=True)
if next(reader, None) != list(HEADER):
raise ValueError("CSV header must match the blank worksheet")
rows = list(reader)
if len(rows) != len(PLAN):
raise ValueError("record all 12 readings in worksheet order")
fixed = {}
sweep = []
previous_time = -1.0
for line, (row, (role, load)) in enumerate(zip(rows, PLAN), start=2):
if len(row) != len(HEADER):
raise ValueError(f"line {line}: each reading needs four columns")
expected_load = "" if load is None else str(load)
if row[:2] != [role, expected_load]:
raise ValueError(f"line {line}: expected {role} with load {expected_load or 'open'}")
time = finite(row[2], f"line {line} elapsed time")
voltage = finite(row[3], f"line {line} voltage")
if not 0 <= time <= 180:
raise ValueError(f"line {line}: elapsed time must be 0–180 s")
if time <= previous_time:
raise ValueError(f"line {line}: elapsed time must increase")
if not 0 <= voltage <= 3.0:
raise ValueError(f"line {line}: voltage must be 0–3.000 V; check polarity and units")
previous_time = time
if role == "sweep":
sweep.append(power_point(load, time, voltage))
else:
fixed[role] = voltage
reference_middle = next(point["voltage_v"] for point in sweep if point["load_ohm"] == REFERENCE)
references = [fixed["reference_before"], reference_middle, fixed["reference_after"]]
if min(references) < 0.100:
spread = None
status = "too_low"
else:
endpoint_mean = (references[0] + references[2]) / 2
# Remove binary roundoff far below the meter's millivolt resolution.
spread = round(100 * (max(references) - min(references)) / endpoint_mean, 12)
status = "changed" if spread > 3.0 else "stable"
highest = []
overlaps = []
resolved = None
if status == "stable":
high_power = max(point["load_power_mw"] for point in sweep)
highest = [point["load_ohm"] for point in sweep if point["load_power_mw"] == high_power]
if len(highest) == 1:
winner = next(point for point in sweep if point["load_ohm"] == highest[0])
overlaps = [
point["load_ohm"] for point in sweep
if point is not winner and point["power_screen_mw"][1] >= winner["power_screen_mw"][0]
]
if not overlaps:
resolved = winner["load_ohm"]
return {
"scope": "Resistor power at eight sampled loads under one fixed lamp.",
"near_open_voltage_v": {"before": fixed["near_open_before"], "after": fixed["near_open_after"]},
"reference_check": {
"load_ohm": REFERENCE,
"before_v": references[0],
"mid_sweep_v": references[1],
"after_v": references[2],
"spread_percent_of_endpoint_mean": spread,
"status": status,
},
"sweep_points": sweep,
"highest_estimate_loads_ohm": highest,
"overlapping_other_loads_ohm": overlaps,
"resolved_best_load_ohm": resolved,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("worksheet", help="filled CSV worksheet")
args = parser.parse_args()
try:
with open(args.worksheet, encoding="utf-8-sig", newline="") as stream:
result = analyze(stream)
except (OSError, ValueError, csv.Error) as error:
print(f"Cannot analyze: {error}", file=sys.stderr)
return 2
print(json.dumps(result, indent=2, allow_nan=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())
A trial gives 1.279 V at 8.2 kΩ and 0.994 V at 390 Ω. Which resistor receives more estimated power? Now suppose the 820 Ω reference is 1.100 V before and after, but 1.146 V in the middle. What changes in the conclusion?
The two power estimates are about 0.199 mW at 8.2 kΩ and 2.53 mW at 390 Ω. The lower voltage in this pair corresponds to greater power. The three reference readings have about a 4.2% spread, above the 3% screen. Retain the two arithmetic results and withhold the sweep ranking. Check for changed shadows, fixture movement or a change in panel response before repeating.
The supplied Python tests use constructed rows and check arithmetic, drift handling and input errors. The module, lamp and fixture have not been tested on a physical bench for this lesson.