EMC Pre-Compliance Testing: A Complete Guide
Failing EMC certification costs weeks of delay and tens of thousands in retest fees. Pre-compliance testing in your own lab catches problems early, when they're cheap to fix. TofuPilot tracks your EMC test data so you can monitor margins, compare design revisions, and build confidence before the formal test.
What EMC Testing Covers
| Test | Standard | What it measures |
|---|---|---|
| Radiated emissions | CISPR 32, FCC Part 15 | RF energy radiated from the product |
| Conducted emissions | CISPR 32, FCC Part 15 | RF noise on power lines |
| Radiated immunity | IEC 61000-4-3 | Resistance to external RF fields |
| ESD immunity | IEC 61000-4-2 | Resistance to electrostatic discharge |
| Surge immunity | IEC 61000-4-5 | Resistance to power line surges |
| Conducted immunity | IEC 61000-4-6 | Resistance to RF on cables |
Pre-compliance focuses on emissions (radiated and conducted) because they're the most common failure modes and can be tested with relatively affordable equipment.
Pre-Compliance Equipment
| Equipment | Purpose | Typical cost |
|---|---|---|
| Near-field probe set | Locate emission sources on PCB | $500-2,000 |
| Spectrum analyzer | Measure frequency content | $5,000-30,000 |
| LISN (Line Impedance Stabilization Network) | Conducted emissions measurement | $2,000-5,000 |
| EMC antenna (biconical + log-periodic) | Radiated emissions measurement | $3,000-8,000 |
| Shielded enclosure or open area test site | Controlled measurement environment | $10,000+ |
You don't need a fully equipped chamber for pre-compliance. A spectrum analyzer with near-field probes catches most issues at the board level.
Logging EMC Data to TofuPilot
Radiated Emissions Scan
from tofupilot import TofuPilotClient
client = TofuPilotClient()
# Peak emissions at key frequencies
emissions = [
{"freq_mhz": 30, "level_dbuv_m": 28.5, "limit_dbuv_m": 40.0},
{"freq_mhz": 100, "level_dbuv_m": 32.1, "limit_dbuv_m": 43.5},
{"freq_mhz": 230, "level_dbuv_m": 35.8, "limit_dbuv_m": 46.0},
{"freq_mhz": 500, "level_dbuv_m": 22.3, "limit_dbuv_m": 46.0},
{"freq_mhz": 1000, "level_dbuv_m": 18.7, "limit_dbuv_m": 50.0},
]
measurements = []
all_pass = True
for e in emissions:
margin = e["limit_dbuv_m"] - e["level_dbuv_m"]
passed = margin > 0
if not passed:
all_pass = False
measurements.append({
"name": f"radiated_{e['freq_mhz']}mhz",
"value": e["level_dbuv_m"],
"unit": "dBuV/m",
"limit_high": e["limit_dbuv_m"],
})
measurements.append({
"name": f"margin_{e['freq_mhz']}mhz",
"value": margin,
"unit": "dB",
"limit_low": 6.0, # 6dB margin target
})
client.create_run(
procedure_id="EMC-RADIATED-PRECOMPLIANCE",
unit_under_test={
"serial_number": "PROTO-REV-C",
"part_number": "PRODUCT-V2",
},
run_passed=all_pass,
steps=[{
"name": "Radiated Emissions 30MHz-1GHz",
"step_type": "measurement",
"status": all_pass,
"measurements": measurements,
}],
)Conducted Emissions
# Conducted emissions on power input lines
conducted = [
{"freq_mhz": 0.15, "level_dbuv": 52.3, "limit_avg": 56.0, "limit_qp": 66.0},
{"freq_mhz": 0.5, "level_dbuv": 48.7, "limit_avg": 46.0, "limit_qp": 56.0},
{"freq_mhz": 5.0, "level_dbuv": 38.2, "limit_avg": 46.0, "limit_qp": 56.0},
{"freq_mhz": 30.0, "level_dbuv": 32.1, "limit_avg": 46.0, "limit_qp": 56.0},
]
measurements = []
for c in conducted:
measurements.append({
"name": f"conducted_avg_{c['freq_mhz']}mhz",
"value": c["level_dbuv"],
"unit": "dBuV",
"limit_high": c["limit_avg"],
})
client.create_run(
procedure_id="EMC-CONDUCTED-PRECOMPLIANCE",
unit_under_test={"serial_number": "PROTO-REV-C"},
run_passed=True,
steps=[{
"name": "Conducted Emissions 150kHz-30MHz",
"step_type": "measurement",
"status": True,
"measurements": measurements,
}],
)Tracking EMC Margins
The goal of pre-compliance isn't just pass/fail. It's knowing your margin. A product that passes with 2dB of margin will likely fail at the test house (measurement uncertainty alone is 3-5dB).
| Margin | Risk level | Action |
|---|---|---|
| > 10 dB | Low | Proceed to certification |
| 6-10 dB | Medium | Proceed, but have mitigation plans ready |
| 3-6 dB | High | Fix before certification |
| < 3 dB | Very high | Will likely fail certification |
Track margins in TofuPilot across design revisions. If Rev A had 4dB margin at 230MHz and Rev B has 8dB after adding a filter, you can see the improvement quantitatively.
Comparing Design Revisions
EMC performance changes with every board revision, layout change, and component swap. TofuPilot lets you compare emissions data across revisions side by side.
Rev A (no filter): Rev B (added pi filter):
┌─────────────────────┐ ┌─────────────────────┐
│ 230MHz: 42.1 dBuV/m │ │ 230MHz: 35.8 dBuV/m │
│ Limit: 46.0 dBuV/m │ │ Limit: 46.0 dBuV/m │
│ Margin: 3.9 dB ⚠ │ │ Margin: 10.2 dB ✓ │
└─────────────────────┘ └─────────────────────┘
The pi filter bought 6.3dB of margin at 230MHz. This kind of data-driven design feedback is only possible when you track every test iteration.
Common EMC Failure Modes
| Failure | Typical cause | Fix |
|---|---|---|
| Harmonics of switching frequency | DC-DC converter, clock oscillator | Add input/output filtering, spread-spectrum clocking |
| Cable-radiated emissions | Unshielded cables acting as antennas | Add common-mode chokes, use shielded cables |
| High-frequency broadband | Fast digital edges, poor grounding | Slow edge rates, improve ground plane |
| Low-frequency conducted | Switching power supply ripple | Add differential-mode filter on input |
Track which failure modes appear across products in TofuPilot. If every product fails at the 3rd harmonic of the switching frequency, standardize your input filter design.
ESD Testing
ESD is the most common immunity test failure. Log ESD test results systematically.
esd_levels = [
{"mode": "contact", "voltage_kv": 4, "result": "pass"},
{"mode": "contact", "voltage_kv": 6, "result": "pass"},
{"mode": "contact", "voltage_kv": 8, "result": "fail_recoverable"},
{"mode": "air", "voltage_kv": 8, "result": "pass"},
{"mode": "air", "voltage_kv": 15, "result": "pass"},
]
measurements = []
for test in esd_levels:
passed = test["result"] == "pass"
measurements.append({
"name": f"esd_{test['mode']}_{test['voltage_kv']}kv",
"value": 1 if passed else 0,
"unit": "pass/fail",
"limit_low": 1,
})
client.create_run(
procedure_id="EMC-ESD-PRECOMPLIANCE",
unit_under_test={"serial_number": "PROTO-REV-C"},
run_passed=all(t["result"] == "pass" for t in esd_levels),
steps=[{
"name": "ESD Immunity per IEC 61000-4-2",
"step_type": "measurement",
"status": True,
"measurements": measurements,
}],
)From Pre-Compliance to Certification
Pre-compliance data in TofuPilot serves as your engineering notebook for EMC. When you arrive at the test house:
- You know your margins at every frequency
- You know which design changes improved or degraded performance
- You have a history of every test iteration
- If you fail, you have data to guide the fix instead of starting from scratch