Files
homeclaw/web-ui/vendor/avr8js/peripherals/spi.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

131 lines
4.5 KiB
JavaScript

// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
// Register bits:
const SPCR_SPIE = 0x80; // SPI Interrupt Enable
const SPCR_SPE = 0x40; // SPI Enable
const SPCR_DORD = 0x20; // Data Order
const SPCR_MSTR = 0x10; // Master/Slave Select
const SPCR_CPOL = 0x8; // Clock Polarity
const SPCR_CPHA = 0x4; // Clock Phase
const SPCR_SPR1 = 0x2; // SPI Clock Rate Select 1
const SPCR_SPR0 = 0x1; // SPI Clock Rate Select 0
const SPSR_SPR_MASK = SPCR_SPR1 | SPCR_SPR0;
const SPSR_SPIF = 0x80; // SPI Interrupt Flag
const SPSR_WCOL = 0x40; // Write COLlision Flag
const SPSR_SPI2X = 0x1; // Double SPI Speed Bit
export const spiConfig = {
spiInterrupt: 0x22,
SPCR: 0x4c,
SPSR: 0x4d,
SPDR: 0x4e,
};
const bitsPerByte = 8;
export class AVRSPI {
constructor(cpu, config, freqHz) {
this.cpu = cpu;
this.config = config;
this.freqHz = freqHz;
/** @deprecated Use onByte() instead */
this.onTransfer = () => 0;
/**
* SPI byte transfer callback. Invoked whenever the user code starts an SPI transaction.
* You can override this with your own SPI handler logic.
*
* The callback receives a argument: the byte sent over the SPI MOSI line.
* It should call `completeTransfer()` within `transferCycles` CPU cycles.
*/
this.onByte = (value) => {
const valueIn = this.onTransfer(value);
this.cpu.addClockEvent(() => this.completeTransfer(valueIn), this.transferCycles);
};
this.transmissionActive = false;
// Interrupts
this.SPI = {
address: this.config.spiInterrupt,
flagRegister: this.config.SPSR,
flagMask: SPSR_SPIF,
enableRegister: this.config.SPCR,
enableMask: SPCR_SPIE,
};
const { SPCR, SPSR, SPDR } = config;
cpu.writeHooks[SPDR] = (value) => {
if (!(cpu.data[SPCR] & SPCR_SPE)) {
// SPI not enabled, ignore write
return;
}
// Write collision
if (this.transmissionActive) {
cpu.data[SPSR] |= SPSR_WCOL;
return true;
}
// Clear write collision / interrupt flags
cpu.data[SPSR] &= ~SPSR_WCOL;
this.cpu.clearInterrupt(this.SPI);
this.transmissionActive = true;
this.onByte(value);
return true;
};
cpu.writeHooks[SPCR] = (value) => {
this.cpu.updateInterruptEnable(this.SPI, value);
};
cpu.writeHooks[SPSR] = (value) => {
this.cpu.data[SPSR] = value;
this.cpu.clearInterruptByFlag(this.SPI, value);
};
}
reset() {
this.transmissionActive = false;
}
/**
* Completes an SPI transaction. Call this method only from the `onByte` callback.
*
* @param receivedByte Byte read from the SPI MISO line.
*/
completeTransfer(receivedByte) {
const { SPDR } = this.config;
this.cpu.data[SPDR] = receivedByte;
this.cpu.setInterruptFlag(this.SPI);
this.transmissionActive = false;
}
get isMaster() {
return this.cpu.data[this.config.SPCR] & SPCR_MSTR ? true : false;
}
get dataOrder() {
return this.cpu.data[this.config.SPCR] & SPCR_DORD ? 'lsbFirst' : 'msbFirst';
}
get spiMode() {
const CPHA = this.cpu.data[this.config.SPCR] & SPCR_CPHA;
const CPOL = this.cpu.data[this.config.SPCR] & SPCR_CPOL;
return ((CPHA ? 2 : 0) | (CPOL ? 1 : 0));
}
/**
* The clock divider is only relevant for Master mode
*/
get clockDivider() {
const base = this.cpu.data[this.config.SPSR] & SPSR_SPI2X ? 2 : 4;
switch (this.cpu.data[this.config.SPCR] & SPSR_SPR_MASK) {
case 0b00:
return base;
case 0b01:
return base * 4;
case 0b10:
return base * 16;
case 0b11:
return base * 32;
}
// We should never get here:
throw new Error('Invalid divider value!');
}
/** Number of cycles to complete a single byte SPI transaction */
get transferCycles() {
return this.clockDivider * bitsPerByte;
}
/**
* The SPI freqeuncy is only relevant to Master mode.
* In slave mode, the frequency can be as high as F(osc) / 4.
*/
get spiFrequency() {
return this.freqHz / this.clockDivider;
}
}