128 lines
5.0 KiB
TypeScript
128 lines
5.0 KiB
TypeScript
/**
|
||
* wokwi-74hc595 — 8-bit Serial-to-Parallel Shift Register (SN74HC595)
|
||
*
|
||
* DIP-16 package custom element for use in Velxio/Wokwi simulations.
|
||
*/
|
||
|
||
class IC74HC595Element extends HTMLElement {
|
||
private _values: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; // Q0-Q7
|
||
private _shadow: ShadowRoot;
|
||
|
||
// ── Pin layout (DIP-16, matching wokwi-elements coordinate style) ─────────
|
||
// Top row (y=3): pins 16→9, left to right
|
||
// Bottom row (y=51): pins 1→8, left to right
|
||
readonly pinInfo = [
|
||
// Bottom row: pins 1-8 (Q1–Q7, GND)
|
||
{ name: 'Q1', number: 1, x: 8.1, y: 51.3, signals: [] },
|
||
{ name: 'Q2', number: 2, x: 17.7, y: 51.3, signals: [] },
|
||
{ name: 'Q3', number: 3, x: 27.3, y: 51.3, signals: [] },
|
||
{ name: 'Q4', number: 4, x: 36.9, y: 51.3, signals: [] },
|
||
{ name: 'Q5', number: 5, x: 46.5, y: 51.3, signals: [] },
|
||
{ name: 'Q6', number: 6, x: 56.1, y: 51.3, signals: [] },
|
||
{ name: 'Q7', number: 7, x: 65.7, y: 51.3, signals: [] },
|
||
{ name: 'GND', number: 8, x: 75.3, y: 51.3, signals: [] },
|
||
// Top row: pins 9-16 (Q7S, MR, SHCP, STCP, OE, DS, Q0, VCC) right to left
|
||
{ name: 'Q7S', number: 9, x: 75.3, y: 3, signals: [] },
|
||
{ name: 'MR', number: 10, x: 65.7, y: 3, signals: [] },
|
||
{ name: 'SHCP', number: 11, x: 56.1, y: 3, signals: [] },
|
||
{ name: 'STCP', number: 12, x: 46.5, y: 3, signals: [] },
|
||
{ name: 'OE', number: 13, x: 36.9, y: 3, signals: [] },
|
||
{ name: 'DS', number: 14, x: 27.3, y: 3, signals: [] },
|
||
{ name: 'Q0', number: 15, x: 17.7, y: 3, signals: [] },
|
||
{ name: 'VCC', number: 16, x: 8.1, y: 3, signals: [] },
|
||
];
|
||
|
||
constructor() {
|
||
super();
|
||
this._shadow = this.attachShadow({ mode: 'open' });
|
||
this._render();
|
||
}
|
||
|
||
get values() {
|
||
return this._values;
|
||
}
|
||
|
||
set values(v: number[]) {
|
||
this._values = Array.isArray(v) ? [...v] : [0, 0, 0, 0, 0, 0, 0, 0];
|
||
this._renderOutputDots();
|
||
}
|
||
|
||
private _render() {
|
||
const PIN_X = [8.1, 17.7, 27.3, 36.9, 46.5, 56.1, 65.7, 75.3];
|
||
const TOP_Y = 3;
|
||
const BOT_Y = 51.3;
|
||
const CHIP_Y1 = 9;
|
||
const CHIP_H = 37;
|
||
|
||
const topLabels = ['VCC', 'Q0', 'DS', 'OE', 'SCP', 'HCP', 'MR', 'Q7S'];
|
||
const botLabels = ['Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7', 'GND'];
|
||
|
||
const pinLines = PIN_X.map((x) => `
|
||
<line x1="${x}" y1="${TOP_Y}" x2="${x}" y2="${CHIP_Y1}" stroke="#aaa" stroke-width="1.5"/>
|
||
<circle cx="${x}" cy="${TOP_Y}" r="2" fill="#aaa"/>
|
||
<line x1="${x}" y1="${BOT_Y}" x2="${x}" y2="${CHIP_Y1 + CHIP_H}" stroke="#aaa" stroke-width="1.5"/>
|
||
<circle cx="${x}" cy="${BOT_Y}" r="2" fill="#aaa"/>
|
||
`).join('');
|
||
|
||
const topText = PIN_X.map((x, i) => `
|
||
<text x="${x}" y="${CHIP_Y1 + 7}" text-anchor="middle" font-size="3.5" fill="#0ff">${topLabels[i]}</text>
|
||
`).join('');
|
||
|
||
const botText = PIN_X.map((x, i) => `
|
||
<text x="${x}" y="${CHIP_Y1 + CHIP_H - 3}" text-anchor="middle" font-size="3.5" fill="#0ff">${botLabels[i]}</text>
|
||
`).join('');
|
||
|
||
const outputDots = PIN_X.map((x, i) => `
|
||
<circle id="dot-q${i + 1}" cx="${x}" cy="${BOT_Y - 6}" r="2" fill="#333" opacity="0.6"/>
|
||
`).join('');
|
||
|
||
this._shadow.innerHTML = `
|
||
<style>
|
||
:host { display: inline-block; }
|
||
svg { overflow: visible; }
|
||
</style>
|
||
<svg width="84" height="55" viewBox="0 0 84 55" xmlns="http://www.w3.org/2000/svg">
|
||
${pinLines}
|
||
<!-- Chip body -->
|
||
<rect x="5" y="${CHIP_Y1}" width="74" height="${CHIP_H}" rx="2" fill="#1a1a2e" stroke="#555" stroke-width="0.8"/>
|
||
<!-- Notch (pin 1 indicator) -->
|
||
<ellipse cx="5" cy="${CHIP_Y1 + CHIP_H / 2}" rx="3" ry="4" fill="#111" stroke="#555" stroke-width="0.5"/>
|
||
<!-- Chip label -->
|
||
<text x="42" y="${CHIP_Y1 + 14}" text-anchor="middle" font-size="6" font-weight="bold" fill="#eee" font-family="monospace">74HC595</text>
|
||
<!-- Pin labels top -->
|
||
${topText}
|
||
<!-- Pin labels bottom -->
|
||
${botText}
|
||
<!-- Q1-Q7 output state dots (bottom side, animated) -->
|
||
${outputDots}
|
||
<!-- Q0 dot on top side -->
|
||
<circle id="dot-q0" cx="${PIN_X[1]}" cy="${CHIP_Y1 + 6}" r="2" fill="#333" opacity="0.6"/>
|
||
</svg>
|
||
`;
|
||
}
|
||
|
||
private _renderOutputDots() {
|
||
if (!this._shadow) return;
|
||
// Update Q1-Q7 output dots (bottom, indices 1-7 of values)
|
||
for (let i = 1; i <= 7; i++) {
|
||
const dot = this._shadow.getElementById(`dot-q${i}`) as SVGCircleElement | null;
|
||
if (dot) {
|
||
dot.setAttribute('fill', this._values[i] ? '#00ff88' : '#333');
|
||
dot.setAttribute('opacity', this._values[i] ? '1' : '0.5');
|
||
}
|
||
}
|
||
// Q0 on top side
|
||
const dotQ0 = this._shadow.getElementById('dot-q0') as SVGCircleElement | null;
|
||
if (dotQ0) {
|
||
dotQ0.setAttribute('fill', this._values[0] ? '#00ff88' : '#333');
|
||
dotQ0.setAttribute('opacity', this._values[0] ? '1' : '0.5');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!customElements.get('wokwi-74hc595')) {
|
||
customElements.define('wokwi-74hc595', IC74HC595Element);
|
||
}
|
||
|
||
export { IC74HC595Element };
|