v6 T11 accelerometer (submodule 23ed361) + T11 script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
This commit is contained in:
parent
7fea0a5c11
commit
0a50ff7d21
@ -1 +1 @@
|
|||||||
Subproject commit 83ed72e2d63ef357640189df993fd8765a87f543
|
Subproject commit 23ed361f0d1bbb72e6ba2978d9f0834dae5c9e79
|
||||||
256
scripts/2026-09-01_v6_T11_accel.py
Normal file
256
scripts/2026-09-01_v6_T11_accel.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""v6 T11 - Block U: SC7A20TR 3-axis accelerometer (vibration telemetry).
|
||||||
|
|
||||||
|
U26 SC7A20TR (Silan, LGA-12 2x2, LCSC C5126709, ~$0.25; register-compatible with ST
|
||||||
|
LIS3DH driver). I2C mode on the existing bus: CS->3V3, SDO/SA0->GND (addr 0x18 - no
|
||||||
|
clash: SHT40 0x44, PCF8574 0x20+). INT1 -> /ACC_INT1 -> U16 PB9 (pin 62, EXTI9).
|
||||||
|
INT2 left NC. Decoupling 100nF x2 + 10uF.
|
||||||
|
|
||||||
|
Pin mapping is resolved FROM THE VENDOR SYMBOL'S PIN NAMES (Drogheda-v6.kicad_sym,
|
||||||
|
easyeda2kicad C5126709) and asserted - nothing is guessed by pin number. Review the
|
||||||
|
printed pin->net table in the dry run before --apply.
|
||||||
|
|
||||||
|
Prereq on the Mac:
|
||||||
|
easyeda2kicad --full --lcsc_id C5126709 --output ~/Documents/Claude/Projects/Drogheda/03-Architecture-Hardware/kicad/Drogheda-v6
|
||||||
|
|
||||||
|
Usage: python3 2026-09-01_v6_T11_accel.py [--apply]
|
||||||
|
"""
|
||||||
|
import os, sys, shutil, uuid, re, glob
|
||||||
|
|
||||||
|
KI = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '03-Architecture-Hardware', 'kicad')
|
||||||
|
SCH = os.path.join(KI, 'hbox-watchdog-v6.kicad_sch')
|
||||||
|
VSYM= os.path.join(KI, 'Drogheda-v6.kicad_sym')
|
||||||
|
APPLY = '--apply' in sys.argv
|
||||||
|
ROOT = '/561403b3-895e-4153-a7be-3876ae930d43'
|
||||||
|
NC = '<NC>'
|
||||||
|
|
||||||
|
def f(v):
|
||||||
|
v = 0.0 if abs(v) < 1e-9 else v
|
||||||
|
return '%.10g' % v
|
||||||
|
|
||||||
|
def bal(t):
|
||||||
|
d=0; instr=False; esc=False
|
||||||
|
for ch in t:
|
||||||
|
if esc: esc=False; continue
|
||||||
|
if ch=='\\': esc=True; continue
|
||||||
|
if ch=='"': instr = not instr; continue
|
||||||
|
if instr: continue
|
||||||
|
if ch=='(': d+=1
|
||||||
|
elif ch==')': d-=1
|
||||||
|
return d
|
||||||
|
|
||||||
|
def span_from(text, i):
|
||||||
|
d=0; instr=False; esc=False; j=i
|
||||||
|
while j < len(text):
|
||||||
|
ch=text[j]
|
||||||
|
if esc: esc=False
|
||||||
|
elif ch=='\\': esc=True
|
||||||
|
elif ch=='"': instr = not instr
|
||||||
|
elif not instr:
|
||||||
|
if ch=='(': d+=1
|
||||||
|
elif ch==')':
|
||||||
|
d-=1
|
||||||
|
if d==0: return j+1
|
||||||
|
j+=1
|
||||||
|
raise ValueError('unbalanced')
|
||||||
|
|
||||||
|
# pin extractor that also captures the pin NAME
|
||||||
|
PIN_NAMED = re.compile(r'\(pin \w+ \w+\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\(pin ).)*?'
|
||||||
|
r'\(name "([^"]*)"(?:(?!\(pin ).)*?\(number "([^"]+)"', re.S)
|
||||||
|
|
||||||
|
def libpins_named(libsrc, name):
|
||||||
|
i = libsrc.find('(symbol "%s"' % name)
|
||||||
|
if i < 0: return None
|
||||||
|
j = libsrc.find('\n (symbol "', i+10)
|
||||||
|
if j < 0: j = libsrc.find('\n\t\t(symbol "', i+10)
|
||||||
|
body = libsrc[i: j if j>0 else len(libsrc)]
|
||||||
|
return [(num, nm, float(x), float(y)) for x,y,rot,nm,num in PIN_NAMED.findall(body)]
|
||||||
|
|
||||||
|
sch = open(SCH, encoding='utf-8').read()
|
||||||
|
vsym = open(VSYM, encoding='utf-8').read()
|
||||||
|
uuids_before = set(re.findall(r'\(uuid "([0-9a-f-]+)"\)', sch))
|
||||||
|
|
||||||
|
# ---------- 1. find the vendor symbol + footprint ----------
|
||||||
|
symname = None
|
||||||
|
for m in re.finditer(r'\(symbol "((?:SC7A20|Sc7a20)[^"]*)"', vsym):
|
||||||
|
symname = m.group(1); break
|
||||||
|
if not symname:
|
||||||
|
sys.exit('SC7A20 symbol not found in Drogheda-v6.kicad_sym - run on the Mac first:\n'
|
||||||
|
' easyeda2kicad --full --lcsc_id C5126709 --output ~/Documents/Claude/Projects/Drogheda/03-Architecture-Hardware/kicad/Drogheda-v6')
|
||||||
|
fp_hits = [p for p in glob.glob(os.path.join(KI,'Drogheda-v6.pretty','*.kicad_mod'))
|
||||||
|
if re.search(r'LGA-12_L2|SC7A20', os.path.basename(p), re.I)]
|
||||||
|
if not fp_hits:
|
||||||
|
sys.exit('SC7A20 LGA-12 footprint not found in Drogheda-v6.pretty - run easyeda2kicad C5126709 first')
|
||||||
|
u26_fp = 'Drogheda-v6:' + os.path.basename(fp_hits[0])[:-len('.kicad_mod')]
|
||||||
|
|
||||||
|
pins = libpins_named(vsym, symname)
|
||||||
|
if not pins or len(pins) < 10:
|
||||||
|
sys.exit('pin extraction failed for %s: got %s' % (symname, pins))
|
||||||
|
|
||||||
|
# ---------- 2. map pins to nets BY NAME ----------
|
||||||
|
RULES = [(r'^VDD_?IO$','3V3'), (r'^VDD$','3V3'),
|
||||||
|
(r'^(GND|RES|RESERVED)',' GND'.strip()),
|
||||||
|
(r'(SDO.*SA0|SA0.*SDO|^SDO$|^SA0$)','GND'), # addr select -> 0x18 (before SDA rule)
|
||||||
|
(r'(SCL|SPC|^SCX$)','I2C_SCL'),
|
||||||
|
(r'(SDA|SDI|^SDX$)','I2C_SDA'),
|
||||||
|
(r'^CS','3V3'),
|
||||||
|
(r'INT_?1','ACC_INT1'), (r'INT_?2',NC),
|
||||||
|
(r'^(NC|N\.C\.?)$',NC)]
|
||||||
|
mapping = {}
|
||||||
|
for num, nm, px, py in pins:
|
||||||
|
nmu = nm.upper().replace('~','').strip()
|
||||||
|
net = None
|
||||||
|
for pat, tgt in RULES:
|
||||||
|
if re.search(pat, nmu):
|
||||||
|
net = tgt; break
|
||||||
|
if net is None: sys.exit('unmapped pin: number %s name %r - extend RULES' % (num, nm))
|
||||||
|
mapping[num] = (nm, net, px, py)
|
||||||
|
print('U26 %s (fp %s) pin -> net:' % (symname, u26_fp))
|
||||||
|
for num in sorted(mapping, key=lambda n: int(re.sub(r'\D','',n) or 0)):
|
||||||
|
nm, net, _, _ = mapping[num]
|
||||||
|
print(' %3s %-12s -> %s' % (num, nm, net))
|
||||||
|
nets_used = [net for _,(nm,net,_,_) in mapping.items()]
|
||||||
|
for req, cnt in (('I2C_SCL',1),('I2C_SDA',1),('ACC_INT1',1)):
|
||||||
|
assert nets_used.count(req) == cnt, 'expected exactly %d %s pin, got %d' % (cnt, req, nets_used.count(req))
|
||||||
|
assert nets_used.count('3V3') >= 2, 'expected VDD+VDDIO/CS on 3V3'
|
||||||
|
|
||||||
|
# ---------- 3. guards ----------
|
||||||
|
for r in ('U26','C158','C159','C160'):
|
||||||
|
if '(property "Reference" "%s"' % r in sch: sys.exit('refdes %s already used' % r)
|
||||||
|
|
||||||
|
lib_cached = sch.split('(lib_symbols',1)[1]
|
||||||
|
PIN_RE2= re.compile(r'\(pin \w+ \w+\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\(pin ).)*?\(number "([^"]+)"', re.S)
|
||||||
|
def cachedpins(name):
|
||||||
|
i = lib_cached.find('(symbol "%s"' % name)
|
||||||
|
if i < 0: return None
|
||||||
|
j = lib_cached.find('\n\t\t(symbol "', i+10)
|
||||||
|
body = lib_cached[i: j if j>0 else len(lib_cached)]
|
||||||
|
return [(num, float(x), float(y)) for x,y,rot,num in PIN_RE2.findall(body)]
|
||||||
|
|
||||||
|
# U16 PB9 = pin 62: coords + existing NC
|
||||||
|
u16 = None
|
||||||
|
for b in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*', sch, re.S):
|
||||||
|
blk=b.group(0)
|
||||||
|
if '(property "Reference" "U16"' in blk:
|
||||||
|
l=re.search(r'\(lib_id "([^"]+)"',blk).group(1)
|
||||||
|
a=re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)',blk)
|
||||||
|
assert a.group(3)=='0' and '(mirror' not in blk[:400]
|
||||||
|
u16=(l,float(a.group(1)),float(a.group(2))); break
|
||||||
|
assert u16, 'U16 not found'
|
||||||
|
pb9 = [(px,py) for num,px,py in cachedpins(u16[0]) if num=='62']
|
||||||
|
assert len(pb9)==1, 'U16 pin 62 (PB9) not found'
|
||||||
|
PB9 = (round(u16[1]+pb9[0][0],3), round(u16[2]-pb9[0][1],3))
|
||||||
|
print('PB9 sheet coord:', PB9)
|
||||||
|
|
||||||
|
# ---------- 4. cache vendor symbol into schematic ----------
|
||||||
|
def grab(libsrc, name, prefix):
|
||||||
|
i = libsrc.find(' (symbol "%s"\n' % name)
|
||||||
|
if i < 0: sys.exit('symbol %s not found for caching' % name)
|
||||||
|
j = libsrc.index('(', i)
|
||||||
|
k = span_from(libsrc, j)
|
||||||
|
return libsrc[j:k].replace('(symbol "%s"' % name, '(symbol "%s:%s"' % (prefix,name), 1)
|
||||||
|
if '(symbol "Drogheda-v6:%s"' % symname not in sch:
|
||||||
|
m = re.search(r'\n\t\(lib_symbols\n', sch)
|
||||||
|
k = span_from(sch, sch.index('(', m.start()))
|
||||||
|
sch = sch[:k-1] + '\n\t\t' + grab(vsym, symname, 'Drogheda-v6') + '\n\t' + sch[k-1:]
|
||||||
|
lib_cached = sch.split('(lib_symbols',1)[1]
|
||||||
|
|
||||||
|
# ---------- 5. build additions ----------
|
||||||
|
add = ''
|
||||||
|
newlabels=[]; nnc=0
|
||||||
|
def add_lab(net,x,y,rot,just):
|
||||||
|
global add
|
||||||
|
add += ('\n\t(label "%s"\n\t\t(at %s %s %d)\n\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.27 1.27)\n'
|
||||||
|
'\t\t\t)\n\t\t\t(justify %s)\n\t\t)\n\t\t(uuid "%s")\n\t)') % (net,f(x),f(y),rot,just,uuid.uuid4())
|
||||||
|
newlabels.append((net,round(x,3),round(y,3)))
|
||||||
|
def add_nc(x,y):
|
||||||
|
global add,nnc
|
||||||
|
add += '\n\t(no_connect\n\t\t(at %s %s)\n\t\t(uuid "%s")\n\t)' % (f(x),f(y),uuid.uuid4())
|
||||||
|
nnc += 1
|
||||||
|
|
||||||
|
def mksym(lib_id, ref, val, x, y, fp, npins, lcsc=''):
|
||||||
|
props=[('Reference',ref,x-3,y-3,''),('Value',val,x+3,y-3,''),('Footprint',fp,x,y,'\n\t\t\t(hide yes)'),
|
||||||
|
('Datasheet','~',x,y,'\n\t\t\t(hide yes)'),('Description','',x,y,'\n\t\t\t(hide yes)')]
|
||||||
|
if lcsc: props.append(('LCSC',lcsc,x,y,'\n\t\t\t(hide yes)'))
|
||||||
|
p=''
|
||||||
|
for nm,v,px,py,hide in props:
|
||||||
|
p += ('\t\t(property "%s" "%s"\n\t\t\t(at %s %s 0)%s\n\t\t\t(show_name no)\n\t\t\t(do_not_autoplace no)\n'
|
||||||
|
'\t\t\t(effects\n\t\t\t\t(font\n\t\t\t\t\t(size 1.27 1.27)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n') % (nm,v,f(px),f(py),hide)
|
||||||
|
pn=''.join('\t\t(pin "%s"\n\t\t\t(uuid "%s")\n\t\t)\n' % (n,uuid.uuid4()) for n in npins)
|
||||||
|
return ('\n\t(symbol\n\t\t(lib_id "%s")\n\t\t(at %s %s 0)\n\t\t(unit 1)\n\t\t(body_style 1)\n'
|
||||||
|
'\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n\t\t(in_pos_files yes)\n'
|
||||||
|
'\t\t(dnp no)\n\t\t(uuid "%s")\n%s%s\t\t(instances\n\t\t\t(project "hbox-watchdog-v6"\n'
|
||||||
|
'\t\t\t\t(path "%s"\n\t\t\t\t\t(reference "%s")\n\t\t\t\t\t(unit 1)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)'
|
||||||
|
) % (lib_id,f(x),f(y),uuid.uuid4(),p,pn,ROOT,ref)
|
||||||
|
|
||||||
|
def orient(px,py):
|
||||||
|
if abs(px) < 0.01: return 90,'left bottom'
|
||||||
|
return (0,'left bottom') if px>0 else (180,'right bottom')
|
||||||
|
|
||||||
|
add += ('\n\t(text "BLOCK U - VIBRATION (T11): U26 SC7A20 3-axis accel, I2C addr 0x18 '
|
||||||
|
'(CS=3V3, SA0=GND), INT1 -> PB9. LAYOUT: place near a mounting hole for rigid '
|
||||||
|
'chassis coupling; not on a flexy board edge."\n'
|
||||||
|
'\t\t(exclude_from_sim no)\n\t\t(at 40 390 0)\n\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.6 1.6)\n'
|
||||||
|
'\t\t\t)\n\t\t\t(justify left bottom)\n\t\t)\n\t\t(uuid "%s")\n\t)') % uuid.uuid4()
|
||||||
|
|
||||||
|
U26X,U26Y = 60.0, 408.0
|
||||||
|
add += mksym('Drogheda-v6:%s'%symname,'U26','SC7A20TR',U26X,U26Y,u26_fp,
|
||||||
|
[num for num,_,_,_ in pins],'C5126709')
|
||||||
|
for num,(nm,net,px,py) in mapping.items():
|
||||||
|
rot,just = orient(px,py)
|
||||||
|
x,y = U26X+px, U26Y-py
|
||||||
|
if net==NC: add_nc(x,y)
|
||||||
|
else: add_lab(net,x,y,rot,just)
|
||||||
|
|
||||||
|
for ref,val,fp,x,y,n1,n2 in (
|
||||||
|
('C158','100nF','Capacitor_SMD:C_0402_1005Metric',85.0,404.0,'3V3','GND'),
|
||||||
|
('C159','100nF','Capacitor_SMD:C_0402_1005Metric',91.0,404.0,'3V3','GND'),
|
||||||
|
('C160','10uF','Capacitor_SMD:C_0805_2012Metric',97.0,404.0,'3V3','GND')):
|
||||||
|
pp={num:(px,py) for num,px,py in cachedpins('Device:C')}
|
||||||
|
add += mksym('Device:C',ref,val,x,y,fp,['1','2'])
|
||||||
|
add_lab(n1,x+pp['1'][0],y-pp['1'][1],90,'left bottom')
|
||||||
|
add_lab(n2,x+pp['2'][0],y-pp['2'][1],90,'left bottom')
|
||||||
|
|
||||||
|
# PB9 label + stale NC removal
|
||||||
|
add_lab('ACC_INT1', PB9[0], PB9[1], 0, 'left bottom')
|
||||||
|
out=[]; last=0; pb9_nc=0
|
||||||
|
for m in re.finditer(r'\n\t\(no_connect\s*\n\s*\(at ([-\d.]+) ([-\d.]+)\)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||||
|
c=(round(float(m.group(1)),3),round(float(m.group(2)),3))
|
||||||
|
if c == PB9:
|
||||||
|
out.append(sch[last:m.start()]); last=m.end(); pb9_nc+=1
|
||||||
|
out.append(sch[last:]); sch=''.join(out)
|
||||||
|
|
||||||
|
# ---------- 6. collision check ----------
|
||||||
|
labs=set()
|
||||||
|
for m in re.finditer(r'\n\t\((label|global_label) "[^"]+"\s*\n\s*\(at ([-\d.]+) ([-\d.]+)', sch):
|
||||||
|
labs.add((round(float(m.group(2)),3), round(float(m.group(3)),3)))
|
||||||
|
wire_vts=set()
|
||||||
|
for m in re.finditer(r'\(wire\s*\n\s*\(pts\s*\n((?:\s*\(xy [-\d.]+ [-\d.]+\)\s*\n?)+)', sch):
|
||||||
|
for a,b in re.findall(r'\(xy ([-\d.]+) ([-\d.]+)\)', m.group(1)):
|
||||||
|
wire_vts.add((round(float(a),3), round(float(b),3)))
|
||||||
|
pin_coords=set()
|
||||||
|
for b in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*', sch, re.S):
|
||||||
|
blk=b.group(0)
|
||||||
|
ll=re.search(r'\(lib_id "([^"]+)"',blk); aa=re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)',blk)
|
||||||
|
if ll and aa:
|
||||||
|
for num,px,py in (cachedpins(ll.group(1)) or []):
|
||||||
|
pin_coords.add((round(float(aa.group(1))+px,3), round(float(aa.group(2))-py,3)))
|
||||||
|
INTENTIONAL = {PB9}
|
||||||
|
existing = (labs | wire_vts | pin_coords) - INTENTIONAL
|
||||||
|
coll = [(n,x,y) for n,x,y in newlabels if (x,y) in existing]
|
||||||
|
if coll: sys.exit('LABEL COLLISION: %s' % coll[:8])
|
||||||
|
|
||||||
|
end = sch.rstrip(); assert end.endswith(')')
|
||||||
|
sch = end[:-1].rstrip('\n') + add + '\n)\n'
|
||||||
|
lost = uuids_before - set(re.findall(r'\(uuid "([0-9a-f-]+)"\)', sch))
|
||||||
|
if len(lost) > pb9_nc: sys.exit('REFUSING: %d unexpected pre-existing UUIDs vanished' % len(lost))
|
||||||
|
|
||||||
|
print('added: U26 + C158/C159/C160; labels %d, NCs %d; PB9 label placed, stale NC removed: %d'
|
||||||
|
% (len(newlabels), nnc, pb9_nc))
|
||||||
|
print('paren: %d' % bal(sch))
|
||||||
|
if not APPLY:
|
||||||
|
print('\nDRY RUN - review the pin->net table above, then re-run with --apply.'); sys.exit(0)
|
||||||
|
if bal(sch)!=0: sys.exit('REFUSING: paren imbalance')
|
||||||
|
shutil.copy2(SCH,SCH+'.T11bak')
|
||||||
|
open(SCH,'w',encoding='utf-8').write(sch)
|
||||||
|
print('\nAPPLIED. Backup: .T11bak')
|
||||||
Loading…
Reference in New Issue
Block a user