- USB CDC 경로 복원: #define Serial Serial1 우회 제거 - rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화 - /vendor 정적 라우팅 추가 (Express 5 catch-all 우회) - 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환) - 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화 - 시리얼 패널의 내부 메시지를 console.log로 이동
119 lines
4.2 KiB
JavaScript
119 lines
4.2 KiB
JavaScript
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) Uri Shaked and contributors
|
|
const USICR = 0x2d;
|
|
const USISR = 0x2e;
|
|
const USIDR = 0x2f;
|
|
const USIBR = 0x30;
|
|
// USISR bits
|
|
const USICNT_MASK = 0xf;
|
|
const USIDC = 1 << 4;
|
|
const USIPF = 1 << 5;
|
|
const USIOIF = 1 << 6;
|
|
const USISIF = 1 << 7;
|
|
// USICR bits
|
|
const USITC = 1 << 0;
|
|
const USICLK = 1 << 1;
|
|
const USICS0 = 1 << 2;
|
|
const USICS1 = 1 << 3;
|
|
const USIWM0 = 1 << 4;
|
|
const USIWM1 = 1 << 5;
|
|
const USIOIE = 1 << 6;
|
|
const USISIE = 1 << 7;
|
|
export class AVRUSI {
|
|
constructor(cpu, port, portPin, dataPin, clockPin) {
|
|
// Interrupts
|
|
this.START = {
|
|
address: 0xd,
|
|
flagRegister: USISR,
|
|
flagMask: USISIF,
|
|
enableRegister: USICR,
|
|
enableMask: USISIE,
|
|
};
|
|
this.OVF = {
|
|
address: 0xe,
|
|
flagRegister: USISR,
|
|
flagMask: USIOIF,
|
|
enableRegister: USICR,
|
|
enableMask: USIOIE,
|
|
};
|
|
const PIN = portPin;
|
|
const PORT = PIN + 2;
|
|
port.addListener((value) => {
|
|
const twoWire = (cpu.data[USICR] & USIWM1) === USIWM1;
|
|
if (twoWire) {
|
|
if (value & (1 << clockPin) && !(value & (1 << dataPin))) {
|
|
// Start condition detected
|
|
cpu.setInterruptFlag(this.START);
|
|
}
|
|
if (value & (1 << clockPin) && value & (1 << dataPin)) {
|
|
// Stop condition detected
|
|
cpu.data[USISR] |= USIPF;
|
|
}
|
|
}
|
|
});
|
|
const updateOutput = () => {
|
|
const oldValue = cpu.data[PORT];
|
|
const newValue = cpu.data[USIDR] & 0x80 ? oldValue | (1 << dataPin) : oldValue & ~(1 << dataPin);
|
|
cpu.writeHooks[PORT](newValue, oldValue, PORT, 0xff);
|
|
if (newValue & 0x80 && !(cpu.data[PIN] & 0x80)) {
|
|
cpu.data[USISR] |= USIDC; // Shout output HIGH (pulled-up), but input is LOW
|
|
}
|
|
else {
|
|
cpu.data[USISR] &= ~USIDC;
|
|
}
|
|
};
|
|
const count = () => {
|
|
const counter = (cpu.data[USISR] + 1) & USICNT_MASK;
|
|
cpu.data[USISR] = (cpu.data[USISR] & ~USICNT_MASK) | counter;
|
|
if (!counter) {
|
|
cpu.data[USIBR] = cpu.data[USIDR];
|
|
cpu.setInterruptFlag(this.OVF);
|
|
}
|
|
};
|
|
const shift = (inputValue) => {
|
|
cpu.data[USIDR] = (cpu.data[USIDR] << 1) | inputValue;
|
|
updateOutput();
|
|
};
|
|
cpu.writeHooks[USIDR] = (value) => {
|
|
cpu.data[USIDR] = value;
|
|
updateOutput();
|
|
return true;
|
|
};
|
|
cpu.writeHooks[USISR] = (value) => {
|
|
const writeClearMask = USISIF | USIOIF | USIPF;
|
|
cpu.data[USISR] = (cpu.data[USISR] & writeClearMask & ~value) | (value & 0xf);
|
|
cpu.clearInterruptByFlag(this.START, value);
|
|
cpu.clearInterruptByFlag(this.OVF, value);
|
|
return true;
|
|
};
|
|
cpu.writeHooks[USICR] = (value) => {
|
|
cpu.data[USICR] = value & ~(USICLK | USITC);
|
|
cpu.updateInterruptEnable(this.START, value);
|
|
cpu.updateInterruptEnable(this.OVF, value);
|
|
const clockSrc = value & ((USICS1 | USICS0) >> 2);
|
|
const mode = value & ((USIWM1 | USIWM0) >> 4);
|
|
const usiClk = value & USICLK;
|
|
port.openCollector = mode >= 2 ? 1 << dataPin : 0;
|
|
const inputValue = cpu.data[PIN] & (1 << dataPin) ? 1 : 0;
|
|
if (usiClk && !clockSrc) {
|
|
shift(inputValue);
|
|
count();
|
|
}
|
|
if (value & USITC) {
|
|
cpu.writeHooks[PIN](1 << clockPin, cpu.data[PIN], PIN, 0xff);
|
|
const newValue = cpu.data[PIN] & (1 << clockPin);
|
|
if (usiClk && (clockSrc === 2 || clockSrc === 3)) {
|
|
if (clockSrc === 2 && newValue) {
|
|
shift(inputValue);
|
|
}
|
|
if (clockSrc === 3 && !newValue) {
|
|
shift(inputValue);
|
|
}
|
|
count();
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
}
|