11from electronics_abstract_parts import *
2- from typing import Dict
2+ from typing import Dict , Optional
33
44
5- class CharlieplexedLedMatrix (Light , GeneratorBlock ):
5+ class CharlieplexedLedMatrix (Light , GeneratorBlock , SvgPcbTemplateBlock ):
66 """A LED matrix that saves on IO pins by charlieplexing, only requiring max(rows + 1, cols) GPIOs to control.
77 Requires IOs that can tri-state, and requires scanning through rows (so not all LEDs are simultaneously on).
88
99 Anodes (columns) are directly connected to the IO line, while the cathodes (rows) are connected through a resistor.
1010 A generalization of https://en.wikipedia.org/wiki/Charlieplexing#/media/File:3-pin_Charlieplexing_matrix_with_common_resistors.svg
1111 """
12+ def _svgpcb_fn_name_adds (self ) -> Optional [str ]:
13+ return f"{ self ._svgpcb_get (self .ncols )} _{ self ._svgpcb_get (self .nrows )} "
14+
15+ def _svgpcb_template (self ) -> str :
16+ led_block = self ._svgpcb_footprint_block_path_of (['led[0_0]' ])
17+ res_block = self ._svgpcb_footprint_block_path_of (['res[0]' ])
18+ assert led_block is not None and res_block is not None
19+ led_footprint = self ._svgpcb_footprint_of (led_block )
20+ led_a_pin = self ._svgpcb_pin_of (['led[0_0]' ], ['a' ], led_block )
21+ led_k_pin = self ._svgpcb_pin_of (['led[0_0]' ], ['k' ], led_block )
22+ res_footprint = self ._svgpcb_footprint_of (res_block )
23+ res_a_pin = self ._svgpcb_pin_of (['res[0]' ], ['a' ], res_block )
24+ res_b_pin = self ._svgpcb_pin_of (['res[0]' ], ['b' ], res_block )
25+ assert all ([pin is not None for pin in [led_a_pin , led_k_pin , res_a_pin , res_b_pin ]])
26+
27+ return f"""\
28+ function { self ._svgpcb_fn_name ()} (xy, colSpacing=1, rowSpacing=1) {{
29+ const kXCount = { self ._svgpcb_get (self .ncols )} // number of columns (x dimension)
30+ const kYCount = { self ._svgpcb_get (self .nrows )} // number of rows (y dimension)
31+
32+ // Global params
33+ const traceSize = 0.015
34+ const viaTemplate = via(0.02, 0.035)
35+
36+ // Return object
37+ const obj = {{
38+ footprints: {{}},
39+ pts: {{}}
40+ }}
41+
42+ allLeds = []
43+ allVias = []
44+ lastViasPreResistor = [] // state preserved between rows
45+ for (let yIndex=0; yIndex < kYCount; yIndex++) {{
46+ rowLeds = []
47+ rowVias = []
48+
49+ viasPreResistor = []
50+ viasPostResistor = [] // on the same net as the prior row pre-resistor
51+
52+ for (let xIndex=0; xIndex < kXCount; xIndex++) {{
53+ ledPos = [xy[0] + colSpacing * xIndex, xy[1] + rowSpacing * yIndex]
54+ obj.footprints[`d[${{yIndex}}_${{xIndex}}]`] = led = board.add({ led_footprint } , {{
55+ translate: ledPos,
56+ id: `{ self ._svgpcb_pathname ()} _d[${{yIndex}}_${{xIndex}}]`
57+ }})
58+ rowLeds.push(led)
59+
60+ // anode line
61+ thisVia = board.add(viaTemplate, {{
62+ translate: [ledPos[0] + colSpacing*1/3, ledPos[1]]
63+ }})
64+ rowVias.push(thisVia)
65+ board.wire([led.pad("{ led_a_pin } "), thisVia.pos], traceSize, "F.Cu")
66+ if (xIndex <= yIndex) {{
67+ viasPreResistor.push(thisVia)
68+ }} else {{
69+ viasPostResistor.push(thisVia)
70+ }}
71+ }}
72+ allLeds.push(rowLeds)
73+ allVias.push(rowVias)
74+
75+ // Wire the anode lines, including the row-crossing one accounting for the diagonal-skip where the resistor is in the schematic matrix
76+ // viasPreResistor guaranteed nonempty
77+ board.wire([viasPreResistor[0].pos, viasPreResistor[viasPreResistor.length - 1].pos], traceSize, "B.Cu")
78+ if (viasPostResistor.length > 0) {{
79+ board.wire([viasPostResistor[0].pos, viasPostResistor[viasPostResistor.length - 1].pos], traceSize, "B.Cu")
80+ }}
81+
82+ // Create the inter-row bridging trace, if applicable
83+ if (viasPostResistor.length > 0 && lastViasPreResistor.length > 0) {{
84+ via1Pos = lastViasPreResistor[lastViasPreResistor.length - 1].pos
85+ via2Pos = viasPostResistor[0].pos
86+ centerY = (via1Pos[1] + via2Pos[1]) / 2
87+ board.wire([via1Pos,
88+ [via1Pos[0], centerY],
89+ [via2Pos[0], centerY],
90+ via2Pos
91+ ],
92+ traceSize, "B.Cu")
93+ }}
94+
95+ lastViasPreResistor = viasPreResistor
96+ }}
97+
98+ allResistors = []
99+ for (let xIndex=0; xIndex < kXCount; xIndex++) {{
100+ const resPos = [xy[0] + colSpacing * xIndex, xy[1] + rowSpacing * kYCount]
101+ obj.footprints[`r[${{xIndex + 1}}]`] = res = board.add({ res_footprint } , {{
102+ translate: resPos,
103+ id: `{ self ._svgpcb_pathname ()} _r[${{xIndex + 1}}]`
104+ }})
105+ allResistors.push(res)
106+
107+ if (xIndex < allVias.length && xIndex < allVias[xIndex].length - 1) {{
108+ targetVia = allVias[xIndex][xIndex + 1]
109+ thisVia = board.add(viaTemplate, {{
110+ translate: [resPos[0] + colSpacing*2/3, targetVia.pos[1]]
111+ }})
112+
113+ board.wire([
114+ res.pad("{ res_b_pin } "),
115+ [resPos[0] + colSpacing*2/3, res.padY("{ res_b_pin } ")],
116+ thisVia.pos
117+ ], traceSize, "F.Cu")
118+ board.wire([
119+ thisVia.pos,
120+ targetVia.pos,
121+ ], traceSize, "B.Cu")
122+ }} else if (xIndex <= allVias.length && xIndex < allVias[xIndex - 1].length) {{
123+ // connect the last via
124+ thisVia = board.add(viaTemplate, {{
125+ translate: [resPos[0] + colSpacing*2/3, resPos[1]]
126+ }})
127+ targetVia = allVias[xIndex - 1][xIndex - 1]
128+ board.wire([
129+ res.pad("{ res_b_pin } "),
130+ thisVia.pos
131+ ], traceSize, "F.Cu")
132+ centerY = targetVia.pos[1] + colSpacing/2
133+ board.wire([
134+ thisVia.pos,
135+ [thisVia.pos[0], centerY],
136+ [targetVia.pos[0], centerY],
137+ targetVia.pos,
138+ ], traceSize, "B.Cu")
139+ }}
140+ }}
141+
142+ // create the along-column cathode line
143+ for (let xIndex=0; xIndex < kXCount; xIndex++) {{
144+ colPads = allLeds.flatMap(row => row.length > xIndex ? [row[xIndex].pad("{ led_k_pin } ")] : [])
145+ if (xIndex < allResistors.length) {{
146+ colPads.push(allResistors[xIndex].pad("{ res_a_pin } "))
147+ }}
148+
149+ for (let i=0; i<colPads.length - 1; i++) {{
150+ board.wire([
151+ colPads[i],
152+ colPads[i+1]
153+ ], traceSize, "F.Cu")
154+ }}
155+ }}
156+
157+ return obj
158+ }}
159+ """
160+
12161 @init_in_parent
13- def __init__ (self , rows : IntLike , cols : IntLike ,
162+ def __init__ (self , nrows : IntLike , ncols : IntLike ,
14163 color : LedColorLike = Led .Any , current_draw : RangeLike = (1 , 10 )* mAmp ):
15164 super ().__init__ ()
16165
@@ -20,14 +169,14 @@ def __init__(self, rows: IntLike, cols: IntLike,
20169 # note that IOs supply both the positive and negative
21170 self .ios = self .Port (Vector (DigitalSink .empty ()))
22171
23- self .rows = self .ArgParameter (rows )
24- self .cols = self .ArgParameter (cols )
25- self .generator_param (self .rows , self .cols )
172+ self .nrows = self .ArgParameter (nrows )
173+ self .ncols = self .ArgParameter (ncols )
174+ self .generator_param (self .nrows , self .ncols )
26175
27176 def generate (self ):
28177 super ().generate ()
29- rows = self .get (self .rows )
30- cols = self .get (self .cols )
178+ nrows = self .get (self .nrows )
179+ ncols = self .get (self .ncols )
31180
32181 io_voltage = self .ios .hull (lambda x : x .link ().voltage )
33182 io_voltage_upper = io_voltage .upper ()
@@ -52,11 +201,11 @@ def connect_passive_io(index: int, io: Passive):
52201 led_model = Led (color = self .color )
53202
54203 # generate the resistor and LEDs for each column
55- for col in range (cols ):
204+ for col in range (ncols ):
56205 # generate the cathode resistor, guaranteed one per column
57206 self .res [str (col )] = res = self .Block (res_model )
58- connect_passive_io (col , res .b )
59- for row in range (rows ):
207+ connect_passive_io (col , res .b )
208+ for row in range (nrows ):
60209 self .led [f"{ row } _{ col } " ] = led = self .Block (led_model )
61210 self .connect (led .k , res .a )
62211 if row >= col : # displaced by resistor
@@ -67,15 +216,15 @@ def connect_passive_io(index: int, io: Passive):
67216 # generate the adapters and connect the internal passive IO to external typed IO
68217 for index , passive_io in passive_ios .items ():
69218 # if there is a cathode resistor attached to this index, then include the sunk current
70- if index < cols :
219+ if index < ncols :
71220 sink_res = self .res [str (index )]
72- sink_current = - (io_voltage / sink_res .actual_resistance ).upper () * cols
221+ sink_current = - (io_voltage / sink_res .actual_resistance ).upper () * ncols
73222 else :
74223 sink_current = 0 * mAmp
75224
76225 # then add the maximum of the LED source currents, for the rest of the cathode lines
77226 source_current = 0 * mAmp
78- for col in range (cols ):
227+ for col in range (ncols ):
79228 col_res = self .res [str (col )]
80229 source_current = (io_voltage / col_res .actual_resistance ).upper ().max (source_current )
81230
0 commit comments