- USB CDC 경로 복원: #define Serial Serial1 우회 제거 - rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화 - /vendor 정적 라우팅 추가 (Express 5 catch-all 우회) - 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환) - 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화 - 시리얼 패널의 내부 메시지를 console.log로 이동
169 lines
5.8 KiB
JavaScript
169 lines
5.8 KiB
JavaScript
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) Uri Shaked and contributors
|
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
// Register bits:
|
|
const TWCR_TWINT = 0x80; // TWI Interrupt Flag
|
|
const TWCR_TWEA = 0x40; // TWI Enable Acknowledge Bit
|
|
const TWCR_TWSTA = 0x20; // TWI START Condition Bit
|
|
const TWCR_TWSTO = 0x10; // TWI STOP Condition Bit
|
|
const TWCR_TWWC = 0x8; //TWI Write Collision Flag
|
|
const TWCR_TWEN = 0x4; // TWI Enable Bit
|
|
const TWCR_TWIE = 0x1; // TWI Interrupt Enable
|
|
const TWSR_TWS_MASK = 0xf8; // TWI Status
|
|
const TWSR_TWPS1 = 0x2; // TWI Prescaler Bits
|
|
const TWSR_TWPS0 = 0x1; // TWI Prescaler Bits
|
|
const TWSR_TWPS_MASK = TWSR_TWPS1 | TWSR_TWPS0; // TWI Prescaler mask
|
|
const TWAR_TWA_MASK = 0xfe; // TWI (Slave) Address Register
|
|
const TWAR_TWGCE = 0x1; // TWI General Call Recognition Enable Bit
|
|
const STATUS_BUS_ERROR = 0x0;
|
|
const STATUS_TWI_IDLE = 0xf8;
|
|
// Master states
|
|
const STATUS_START = 0x08;
|
|
const STATUS_REPEATED_START = 0x10;
|
|
const STATUS_SLAW_ACK = 0x18;
|
|
const STATUS_SLAW_NACK = 0x20;
|
|
const STATUS_DATA_SENT_ACK = 0x28;
|
|
const STATUS_DATA_SENT_NACK = 0x30;
|
|
const STATUS_DATA_LOST_ARBITRATION = 0x38;
|
|
const STATUS_SLAR_ACK = 0x40;
|
|
const STATUS_SLAR_NACK = 0x48;
|
|
const STATUS_DATA_RECEIVED_ACK = 0x50;
|
|
const STATUS_DATA_RECEIVED_NACK = 0x58;
|
|
// TODO: add slave states
|
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
export const twiConfig = {
|
|
twiInterrupt: 0x30,
|
|
TWBR: 0xb8,
|
|
TWSR: 0xb9,
|
|
TWAR: 0xba,
|
|
TWDR: 0xbb,
|
|
TWCR: 0xbc,
|
|
TWAMR: 0xbd,
|
|
};
|
|
// A simple TWI Event Handler that sends a NACK for all events
|
|
export class NoopTWIEventHandler {
|
|
constructor(twi) {
|
|
this.twi = twi;
|
|
}
|
|
start() {
|
|
this.twi.completeStart();
|
|
}
|
|
stop() {
|
|
this.twi.completeStop();
|
|
}
|
|
connectToSlave() {
|
|
this.twi.completeConnect(false);
|
|
}
|
|
writeByte() {
|
|
this.twi.completeWrite(false);
|
|
}
|
|
readByte() {
|
|
this.twi.completeRead(0xff);
|
|
}
|
|
}
|
|
export class AVRTWI {
|
|
constructor(cpu, config, freqHz) {
|
|
this.cpu = cpu;
|
|
this.config = config;
|
|
this.freqHz = freqHz;
|
|
this.eventHandler = new NoopTWIEventHandler(this);
|
|
this.busy = false;
|
|
// Interrupts
|
|
this.TWI = {
|
|
address: this.config.twiInterrupt,
|
|
flagRegister: this.config.TWCR,
|
|
flagMask: TWCR_TWINT,
|
|
enableRegister: this.config.TWCR,
|
|
enableMask: TWCR_TWIE,
|
|
};
|
|
this.updateStatus(STATUS_TWI_IDLE);
|
|
this.cpu.writeHooks[config.TWCR] = (value) => {
|
|
this.cpu.data[config.TWCR] = value;
|
|
const clearInt = value & TWCR_TWINT;
|
|
this.cpu.clearInterruptByFlag(this.TWI, value);
|
|
this.cpu.updateInterruptEnable(this.TWI, value);
|
|
const { status } = this;
|
|
if (clearInt && value & TWCR_TWEN && !this.busy) {
|
|
const twdrValue = this.cpu.data[this.config.TWDR];
|
|
this.cpu.addClockEvent(() => {
|
|
if (value & TWCR_TWSTA) {
|
|
this.busy = true;
|
|
this.eventHandler.start(status !== STATUS_TWI_IDLE);
|
|
}
|
|
else if (value & TWCR_TWSTO) {
|
|
this.busy = true;
|
|
this.eventHandler.stop();
|
|
}
|
|
else if (status === STATUS_START || status === STATUS_REPEATED_START) {
|
|
this.busy = true;
|
|
this.eventHandler.connectToSlave(twdrValue >> 1, twdrValue & 0x1 ? false : true);
|
|
}
|
|
else if (status === STATUS_SLAW_ACK || status === STATUS_DATA_SENT_ACK) {
|
|
this.busy = true;
|
|
this.eventHandler.writeByte(twdrValue);
|
|
}
|
|
else if (status === STATUS_SLAR_ACK || status === STATUS_DATA_RECEIVED_ACK) {
|
|
this.busy = true;
|
|
const ack = !!(value & TWCR_TWEA);
|
|
this.eventHandler.readByte(ack);
|
|
}
|
|
}, 0);
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
get prescaler() {
|
|
switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) {
|
|
case 0:
|
|
return 1;
|
|
case 1:
|
|
return 4;
|
|
case 2:
|
|
return 16;
|
|
case 3:
|
|
return 64;
|
|
}
|
|
// We should never get here:
|
|
throw new Error('Invalid prescaler value!');
|
|
}
|
|
get sclFrequency() {
|
|
return this.freqHz / (16 + 2 * this.cpu.data[this.config.TWBR] * this.prescaler);
|
|
}
|
|
completeStart() {
|
|
this.busy = false;
|
|
this.updateStatus(this.status === STATUS_TWI_IDLE ? STATUS_START : STATUS_REPEATED_START);
|
|
}
|
|
completeStop() {
|
|
this.busy = false;
|
|
this.cpu.data[this.config.TWCR] &= ~TWCR_TWSTO;
|
|
this.updateStatus(STATUS_TWI_IDLE);
|
|
}
|
|
completeConnect(ack) {
|
|
this.busy = false;
|
|
if (this.cpu.data[this.config.TWDR] & 0x1) {
|
|
this.updateStatus(ack ? STATUS_SLAR_ACK : STATUS_SLAR_NACK);
|
|
}
|
|
else {
|
|
this.updateStatus(ack ? STATUS_SLAW_ACK : STATUS_SLAW_NACK);
|
|
}
|
|
}
|
|
completeWrite(ack) {
|
|
this.busy = false;
|
|
this.updateStatus(ack ? STATUS_DATA_SENT_ACK : STATUS_DATA_SENT_NACK);
|
|
}
|
|
completeRead(value) {
|
|
this.busy = false;
|
|
const ack = !!(this.cpu.data[this.config.TWCR] & TWCR_TWEA);
|
|
this.cpu.data[this.config.TWDR] = value;
|
|
this.updateStatus(ack ? STATUS_DATA_RECEIVED_ACK : STATUS_DATA_RECEIVED_NACK);
|
|
}
|
|
get status() {
|
|
return this.cpu.data[this.config.TWSR] & TWSR_TWS_MASK;
|
|
}
|
|
updateStatus(value) {
|
|
const { TWSR } = this.config;
|
|
this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value;
|
|
this.cpu.setInterruptFlag(this.TWI);
|
|
}
|
|
}
|