Files
homeclaw/web-ui/vendor/avr8js/peripherals/twi.js
T
kim 209f3a3aef v3.0.6: RP2040 에뮬레이터 개선 — USB CDC, vendor화, 부팅 감지
- USB CDC 경로 복원: #define Serial Serial1 우회 제거
- rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화
- /vendor 정적 라우팅 추가 (Express 5 catch-all 우회)
- 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환)
- 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화
- 시리얼 패널의 내부 메시지를 console.log로 이동
2026-06-19 15:49:36 +09:00

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);
}
}