- USB CDC 경로 복원: #define Serial Serial1 우회 제거 - rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화 - /vendor 정적 라우팅 추가 (Express 5 catch-all 우회) - 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환) - 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화 - 시리얼 패널의 내부 메시지를 console.log로 이동
131 lines
4.5 KiB
JavaScript
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;
|
|
}
|
|
}
|