Start with a USB-C sink receptacle that attaches in both orientations. Remove just one of its two pull-down paths and flip the same cable at the sink end. Watch which Configuration Channel pin is active and whether the power bus still turns on.
Evidence scope. This is a proposed isolated bench fixture. The complete analyzer was tested on synthetic data. The experiment observes settled direct-current (DC) voltages on this setup. It does not measure attach timing or current capability. The fixture neither requests USB Power Delivery (USB PD) nor exchanges data.
USB stands for Universal Serial Bus. A USB Type-C source provides power and a sink receives it. The connector’s Configuration Channel (CC) lets a source detect a sink. The source presents a pull-up, called Rp. The sink presents a pull-down to ground, called Rd. The resulting voltage on the connected path lets a source recognize the termination and then enable VBUS, the bus supply rail.
A receptacle has separate contacts CC1 at A5 and CC2 at B5. A standard Type-C cable carries one through CC wire. Keeping one plug fixed and rotating the other changes which receptacle CC pin that wire reaches. Each sink contact therefore gets its own Rd. The two contacts must remain separate. The other plug contact can have a cable-side function such as VCONN, the power used by supported cable electronics. The idle or open-contact reading alone does not establish which end-to-end CC path is connected.
The USB Implementers Forum (USB-IF) Release 2.5, Table 3-4, identifies both receptacle contacts. Figure 4-22 and its accompanying sequence show the single path and source attach behavior. Table 4-28 gives 5.1 kΩ as nominal for the resistive sink termination. The meter readings here are for the local fixture and do not decode current advertisements.

Disconnect the cable completely before modifying the board. Under magnification, identify each fitted resistor by continuity from its pad to the corresponding marked CC hole and to GND. Remove the two resistors without cutting traces or levering against the connector. A qualified assembler can prepare the board if you lack the tools for surface-mount rework. Stop if the board differs from the documentation or if you cannot identify the pads.
With all meters and the cable disconnected, verify that CC1 and CC2 are each open to GND on the bare modified board and separate from each other and from VBUS. Inspect for solder bridges. After fitting the external parts, test each branch with its link removed and fitted. Expect open with a link removed and about 5.1 kΩ to GND when fitted. With both fitted, resistance between CC1 and CC2 is about 10.2 kΩ through their two ground paths. Verify about 100 kΩ from VBUS to GND and stop if any expected path is shorted.
The Fluke input adds a weak parallel path. One 10 MΩ meter changes a 5.1 kΩ branch to about 5.097 kΩ. That is about a 0.051% change. A meter on a deliberately open branch still loads it weakly, so record its voltage without classifying it as a valid Rd. The VBUS bleeder draws about 50 µA at 5 V. No payload or current measurement is attached.
Keep the same cable, source, breakout, meter clips and ground throughout. Call the marked plug face up A and the flipped orientation B. Those labels do not tell you which CC path is active.
| Capture | CC1 link | CC2 link | Expected VBUS |
|---|---|---|---|
no_rd | Open | Open | Off in A and B |
dual_rd | Fitted | Fitted | Present in A and B |
cc2_open | Fitted | Open | Present only on mapped CC1 |
restored | Fitted | Fitted | Present in A and B |
no_rd control, or if the setup behaves unexpectedly. Investigate the source and wiring while disconnected before continuing.dual_rd control, one CC voltage should be higher than the other and the pattern should swap in the other orientation. Record which mark maps to each CC pin. A mark that gives ambiguous or inconsistent readings provides no basis for interpreting the fault.
For background on cable construction, see the Tricky Circuit cable overview. This fixture establishes an orientation-dependent attachment pattern with one cable. It does not identify the wiring inside its plugs.
Use Python 3.9 or later. Put the three settled voltage readings for each insertion on one CSV row. The worksheet uses configuration,orientation,repeat,cc1_v,cc2_v,vbus_v. Fill all 24 rows. Readings must be numeric volts. Preserve the actual marks and never infer a missing measurement from the example.
python3 cc_orientation.py --kind synthetic synthetic-captures.csv
python3 cc_orientation.py --kind observed my-captures.csv
The analyzer calls a group off when all three VBUS readings are from −0.10 to 0.20 V. It calls VBUS present when all three are from 4.75 to 5.50 V with a span of at most 0.20 V. Everything between or unstable is inconclusive. In the dual and restored controls, a loaded CC candidate must stay from 0.20 to 2.20 V with at most 0.15 V span, while the other stays from −0.10 to 0.10 V. These are broad fixture screens, separate from any USB-IF compliance limits.
The result is positive only when both no-Rd controls are off, the dual-Rd controls map opposite paths, opening CC2 removes VBUS only on its mapped orientation, and restoration repeats the map. The retained CC1 median while CC2 is open and both restored active medians must each be within 0.20 V of the corresponding dual-Rd median. A complete but inconsistent data set returns investigate. A malformed or incomplete file exits with code 2.
#!/usr/bin/env python3
"""Compare isolated USB-C orientation captures with a controlled open CC2 pull-down."""
import argparse
import csv
import json
from math import isfinite
from pathlib import Path
from statistics import median
STATES = ('no_rd', 'dual_rd', 'cc2_open', 'restored')
MARKS = ('A', 'B')
REPEATS = ('1', '2', '3')
HEADER = ['configuration', 'orientation', 'repeat', 'cc1_v', 'cc2_v', 'vbus_v']
EPS = 1e-9 # Numerical roundoff only, not a measurement uncertainty allowance.
def read_captures(path):
captures = {(state, mark): {} for state in STATES for mark in MARKS}
with path.open(encoding='utf-8-sig', newline='') as stream:
rows = csv.reader(stream, strict=True)
if next(rows, None) != HEADER:
raise ValueError('expected header ' + ','.join(HEADER))
for row in rows:
if len(row) != len(HEADER):
raise ValueError(f'line {rows.line_num}: expected six fields')
state, mark, repeat = (x.strip() for x in row[:3])
if state not in STATES or mark not in MARKS or repeat not in REPEATS:
raise ValueError(f'line {rows.line_num}: invalid configuration, orientation or repeat')
group = captures[state, mark]
if repeat in group:
raise ValueError(f'line {rows.line_num}: duplicate configuration, orientation and repeat')
try:
volts = tuple(map(float, row[3:]))
except ValueError as exc:
raise ValueError(f'line {rows.line_num}: expected three finite voltages') from exc
if not all(isfinite(v) for v in volts):
raise ValueError(f'line {rows.line_num}: expected three finite voltages')
if not all(-.10 <= v <= 6.0 for v in volts):
raise ValueError(f'line {rows.line_num}: outside the −0.10 to 6.00 V fixture voltage envelope')
group[repeat] = volts
if any(len(g) != len(REPEATS) for g in captures.values()):
raise ValueError('expected all 24 complete capture rows')
return captures
def inside(values, low, high):
return min(values) >= low - EPS and max(values) <= high + EPS
def spread(values):
return max(values) - min(values)
def loaded_cc(values):
return inside(values, .20, 2.20) and spread(values) <= .15 + EPS
def summarize(group):
cc1, cc2, vbus = zip(*(group[i] for i in REPEATS))
if inside(vbus, -.10, .20):
state = 'off'
elif inside(vbus, 4.75, 5.50) and spread(vbus) <= .20 + EPS:
state = 'present'
else:
state = 'inconclusive'
active = None
if state == 'present':
if loaded_cc(cc1) and inside(cc2, -.10, .10):
active = 'CC1'
elif loaded_cc(cc2) and inside(cc1, -.10, .10):
active = 'CC2'
return {'vbus_state': state, 'dual_rd_active_cc_candidate': active,
'median_v': {name:median(values) for name, values in zip(('CC1','CC2','VBUS'), (cc1,cc2,vbus))},
'span_v': {name:spread(values) for name, values in zip(('CC1','CC2','VBUS'), (cc1,cc2,vbus))}}
def analyze(captures, kind):
groups = {state:{mark:summarize(captures[state, mark]) for mark in MARKS} for state in STATES}
for state in ('no_rd', 'cc2_open'):
for row in groups[state].values():
# An open input need not sit low, so only the dual-Rd controls get a candidate.
row['dual_rd_active_cc_candidate'] = None
candidates = {mark:groups['dual_rd'][mark]['dual_rd_active_cc_candidate'] for mark in MARKS}
mapping = candidates if set(candidates.values()) == {'CC1','CC2'} else None
only_cc1 = False
restored = False
if mapping:
works = next(mark for mark, cc in mapping.items() if cc == 'CC1')
fails = next(mark for mark, cc in mapping.items() if cc == 'CC2')
baseline = groups['dual_rd'][works]['median_v']['CC1']
partial = groups['cc2_open'][works]
open_cc1 = [captures['cc2_open', works][i][0] for i in REPEATS]
only_cc1 = (partial['vbus_state']=='present'
and groups['cc2_open'][fails]['vbus_state']=='off'
and loaded_cc(open_cc1)
and abs(partial['median_v']['CC1']-baseline) <= .20 + EPS)
restored = all(groups['restored'][mark]['dual_rd_active_cc_candidate'] == mapping[mark]
and abs(groups['restored'][mark]['median_v'][mapping[mark]]
- groups['dual_rd'][mark]['median_v'][mapping[mark]]) <= .20 + EPS
for mark in MARKS)
checks = {
'source_baseline_off_with_no_rd': all(row['vbus_state']=='off' for row in groups['no_rd'].values()),
'dual_control_maps_opposite_cc_paths': mapping is not None,
'only_mapped_cc1_works_when_cc2_open': only_cc1,
'restored_repeats_dual_control': restored,
}
return {'kind':kind,'result':'consistent_with_open_cc2' if all(checks.values()) else 'investigate',
'checks':checks,'dual_orientation_map':mapping,'groups':groups,
'scope':'Stable readings for this fixture only. No USB compliance, current, PD or timing result.'}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--kind', choices=('synthetic','observed'), required=True)
parser.add_argument('csv', type=Path)
args = parser.parse_args()
try:
report = analyze(read_captures(args.csv), args.kind)
except (OSError, ValueError, UnicodeError, csv.Error) as exc:
parser.error(str(exc))
print(json.dumps(report, indent=2, allow_nan=False))
if __name__ == '__main__':
main()
Blank capture worksheet · Synthetic captures · Full reader experiment and tests ZIP
The dual-Rd control maps mark A to CC2 and mark B to CC1. You open CC2. Which orientation should keep VBUS, and does 4 V on the open CC2 pin change your conclusion?
B should retain VBUS because the cable still reaches the fitted CC1 pull-down. A can show voltage on the open CC2 path through the source’s pull-up and the meter. That voltage does not supply a 5.1 kΩ path to ground. Compare the measured VBUS and repeat both orientations after restoring CC2.