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로 이동
This commit is contained in:
kim
2026-06-19 15:49:36 +09:00
parent 23831f79ba
commit 209f3a3aef
192 changed files with 68520 additions and 76 deletions
+212
View File
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export var ADCReference;
(function (ADCReference) {
ADCReference[ADCReference["AVCC"] = 0] = "AVCC";
ADCReference[ADCReference["AREF"] = 1] = "AREF";
ADCReference[ADCReference["Internal1V1"] = 2] = "Internal1V1";
ADCReference[ADCReference["Internal2V56"] = 3] = "Internal2V56";
ADCReference[ADCReference["Reserved"] = 4] = "Reserved";
})(ADCReference || (ADCReference = {}));
export var ADCMuxInputType;
(function (ADCMuxInputType) {
ADCMuxInputType[ADCMuxInputType["SingleEnded"] = 0] = "SingleEnded";
ADCMuxInputType[ADCMuxInputType["Differential"] = 1] = "Differential";
ADCMuxInputType[ADCMuxInputType["Constant"] = 2] = "Constant";
ADCMuxInputType[ADCMuxInputType["Temperature"] = 3] = "Temperature";
})(ADCMuxInputType || (ADCMuxInputType = {}));
export const atmega328Channels = {
0: { type: ADCMuxInputType.SingleEnded, channel: 0 },
1: { type: ADCMuxInputType.SingleEnded, channel: 1 },
2: { type: ADCMuxInputType.SingleEnded, channel: 2 },
3: { type: ADCMuxInputType.SingleEnded, channel: 3 },
4: { type: ADCMuxInputType.SingleEnded, channel: 4 },
5: { type: ADCMuxInputType.SingleEnded, channel: 5 },
6: { type: ADCMuxInputType.SingleEnded, channel: 6 },
7: { type: ADCMuxInputType.SingleEnded, channel: 7 },
8: { type: ADCMuxInputType.Temperature },
14: { type: ADCMuxInputType.Constant, voltage: 1.1 },
15: { type: ADCMuxInputType.Constant, voltage: 0 },
};
const fallbackMuxInput = {
type: ADCMuxInputType.Constant,
voltage: 0,
};
export const adcConfig = {
ADMUX: 0x7c,
ADCSRA: 0x7a,
ADCSRB: 0x7b,
ADCL: 0x78,
ADCH: 0x79,
DIDR0: 0x7e,
adcInterrupt: 0x2a,
numChannels: 8,
muxInputMask: 0xf,
muxChannels: atmega328Channels,
adcReferences: [
ADCReference.AREF,
ADCReference.AVCC,
ADCReference.Reserved,
ADCReference.Internal1V1,
],
};
// Register bits:
const ADPS_MASK = 0x7;
const ADIE = 0x8;
const ADIF = 0x10;
const ADSC = 0x40;
const ADEN = 0x80;
const MUX_MASK = 0x1f;
const ADLAR = 0x20;
const MUX5 = 0x8;
const REFS2 = 0x8;
const REFS_MASK = 0x3;
const REFS_SHIFT = 6;
export class AVRADC {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
/**
* ADC Channel values, in voltage (0..5). The number of channels depends on the chip.
*
* Changing the values here will change the ADC reading, unless you override onADCRead() with a custom implementation.
*/
this.channelValues = new Array(this.config.numChannels);
/** AVCC Reference voltage */
this.avcc = 5;
/** AREF Reference voltage */
this.aref = 5;
/**
* Invoked whenever the code performs an ADC read.
*
* The default implementation reads the result from the `channelValues` array, and then calls
* `completeADCRead()` after `sampleCycles` CPU cycles.
*
* If you override the default implementation, make sure to call `completeADCRead()` after
* `sampleCycles` cycles (or else the ADC read will never complete).
*/
this.onADCRead = (input) => {
var _a;
// Default implementation
let voltage = 0;
switch (input.type) {
case ADCMuxInputType.Constant:
voltage = input.voltage;
break;
case ADCMuxInputType.SingleEnded:
voltage = (_a = this.channelValues[input.channel]) !== null && _a !== void 0 ? _a : 0;
break;
case ADCMuxInputType.Differential:
voltage =
input.gain *
((this.channelValues[input.positiveChannel] || 0) -
(this.channelValues[input.negativeChannel] || 0));
break;
case ADCMuxInputType.Temperature:
voltage = 0.378125; // 25 celcius
break;
}
const rawValue = (voltage / this.referenceVoltage) * 1024;
const result = Math.min(Math.max(Math.floor(rawValue), 0), 1023);
this.cpu.addClockEvent(() => this.completeADCRead(result), this.sampleCycles);
};
this.converting = false;
this.conversionCycles = 25;
// Interrupts
this.ADC = {
address: this.config.adcInterrupt,
flagRegister: this.config.ADCSRA,
flagMask: ADIF,
enableRegister: this.config.ADCSRA,
enableMask: ADIE,
};
cpu.writeHooks[config.ADCSRA] = (value, oldValue) => {
var _a;
if (value & ADEN && !(oldValue && ADEN)) {
this.conversionCycles = 25;
}
cpu.data[config.ADCSRA] = value;
cpu.updateInterruptEnable(this.ADC, value);
if (!this.converting && value & ADSC) {
if (!(value & ADEN)) {
// Special case: reading while the ADC is not enabled should return 0
this.cpu.addClockEvent(() => this.completeADCRead(0), this.sampleCycles);
return true;
}
let channel = this.cpu.data[this.config.ADMUX] & MUX_MASK;
if (cpu.data[config.ADCSRB] & MUX5) {
channel |= 0x20;
}
channel &= config.muxInputMask;
const muxInput = (_a = config.muxChannels[channel]) !== null && _a !== void 0 ? _a : fallbackMuxInput;
this.converting = true;
this.onADCRead(muxInput);
return true; // don't update
}
};
}
completeADCRead(value) {
const { ADCL, ADCH, ADMUX, ADCSRA } = this.config;
this.converting = false;
this.conversionCycles = 13;
if (this.cpu.data[ADMUX] & ADLAR) {
this.cpu.data[ADCL] = (value << 6) & 0xff;
this.cpu.data[ADCH] = value >> 2;
}
else {
this.cpu.data[ADCL] = value & 0xff;
this.cpu.data[ADCH] = (value >> 8) & 0x3;
}
this.cpu.data[ADCSRA] &= ~ADSC;
this.cpu.setInterruptFlag(this.ADC);
}
get prescaler() {
const { ADCSRA } = this.config;
const adcsra = this.cpu.data[ADCSRA];
const adps = adcsra & ADPS_MASK;
switch (adps) {
case 0:
case 1:
return 2;
case 2:
return 4;
case 3:
return 8;
case 4:
return 16;
case 5:
return 32;
case 6:
return 64;
case 7:
default:
return 128;
}
}
get referenceVoltageType() {
var _a;
const { ADMUX, adcReferences } = this.config;
let refs = (this.cpu.data[ADMUX] >> REFS_SHIFT) & REFS_MASK;
if (adcReferences.length > 4 && this.cpu.data[ADMUX] & REFS2) {
refs |= 0x4;
}
return (_a = adcReferences[refs]) !== null && _a !== void 0 ? _a : ADCReference.Reserved;
}
get referenceVoltage() {
switch (this.referenceVoltageType) {
case ADCReference.AVCC:
return this.avcc;
case ADCReference.AREF:
return this.aref;
case ADCReference.Internal1V1:
return 1.1;
case ADCReference.Internal2V56:
return 2.56;
default:
return this.avcc;
}
}
get sampleCycles() {
return this.conversionCycles * this.prescaler;
}
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
const CLKPCE = 128;
export const clockConfig = {
CLKPR: 0x61,
};
const prescalers = [
1, 2, 4, 8, 16, 32, 64, 128, 256,
// The following values are "reserved" according to the datasheet, so we measured
// with a scope to figure them out (on ATmega328p)
2, 4, 8, 16, 32, 64, 128,
];
export class AVRClock {
constructor(cpu, baseFreqHz, config = clockConfig) {
this.cpu = cpu;
this.baseFreqHz = baseFreqHz;
this.config = config;
this.clockEnabledCycles = 0;
this.prescalerValue = 1;
this.cyclesDelta = 0;
this.cpu.writeHooks[this.config.CLKPR] = (clkpr) => {
if ((!this.clockEnabledCycles || this.clockEnabledCycles < cpu.cycles) && clkpr === CLKPCE) {
this.clockEnabledCycles = this.cpu.cycles + 4;
}
else if (this.clockEnabledCycles && this.clockEnabledCycles >= cpu.cycles) {
this.clockEnabledCycles = 0;
const index = clkpr & 0xf;
const oldPrescaler = this.prescalerValue;
this.prescalerValue = prescalers[index];
this.cpu.data[this.config.CLKPR] = index;
if (oldPrescaler !== this.prescalerValue) {
this.cyclesDelta =
(cpu.cycles + this.cyclesDelta) * (oldPrescaler / this.prescalerValue) - cpu.cycles;
}
}
return true;
};
}
get frequency() {
return this.baseFreqHz / this.prescalerValue;
}
get prescaler() {
return this.prescalerValue;
}
get timeNanos() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e9;
}
get timeMicros() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e6;
}
get timeMillis() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e3;
}
}
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export class EEPROMMemoryBackend {
constructor(size) {
this.memory = new Uint8Array(size);
this.memory.fill(0xff);
}
readMemory(addr) {
return this.memory[addr];
}
writeMemory(addr, value) {
this.memory[addr] &= value;
}
eraseMemory(addr) {
this.memory[addr] = 0xff;
}
}
export const eepromConfig = {
eepromReadyInterrupt: 0x2c,
EECR: 0x3f,
EEDR: 0x40,
EEARL: 0x41,
EEARH: 0x42,
eraseCycles: 28800, // 1.8ms at 16MHz
writeCycles: 28800, // 1.8ms at 16MHz
};
const EERE = 1 << 0;
const EEPE = 1 << 1;
const EEMPE = 1 << 2;
const EERIE = 1 << 3;
const EEPM0 = 1 << 4;
const EEPM1 = 1 << 5;
const EECR_WRITE_MASK = EEPE | EEMPE | EERIE | EEPM0 | EEPM1;
export class AVREEPROM {
constructor(cpu, backend, config = eepromConfig) {
this.cpu = cpu;
this.backend = backend;
this.config = config;
/**
* Used to keep track on the last write to EEMPE. From the datasheet:
* The EEMPE bit determines whether setting EEPE to one causes the EEPROM to be written.
* When EEMPE is set, setting EEPE within four clock cycles will write data to the EEPROM
* at the selected address If EEMPE is zero, setting EEPE will have no effect.
*/
this.writeEnabledCycles = 0;
this.writeCompleteCycles = 0;
// Interrupts
this.EER = {
address: this.config.eepromReadyInterrupt,
flagRegister: this.config.EECR,
flagMask: EEPE,
enableRegister: this.config.EECR,
enableMask: EERIE,
constant: true,
inverseFlag: true,
};
this.cpu.writeHooks[this.config.EECR] = (eecr) => {
const { EEARH, EEARL, EECR, EEDR } = this.config;
const addr = (this.cpu.data[EEARH] << 8) | this.cpu.data[EEARL];
this.cpu.data[EECR] = (this.cpu.data[EECR] & ~EECR_WRITE_MASK) | (eecr & EECR_WRITE_MASK);
this.cpu.updateInterruptEnable(this.EER, eecr);
if (eecr & EERE) {
this.cpu.clearInterrupt(this.EER);
}
if (eecr & EEMPE) {
const eempeCycles = 4;
this.writeEnabledCycles = this.cpu.cycles + eempeCycles;
this.cpu.addClockEvent(() => {
this.cpu.data[EECR] &= ~EEMPE;
}, eempeCycles);
}
// Read
if (eecr & EERE) {
this.cpu.data[EEDR] = this.backend.readMemory(addr);
// When the EEPROM is read, the CPU is halted for four cycles before the
// next instruction is executed.
this.cpu.cycles += 4;
return true;
}
// Write
if (eecr & EEPE) {
// If EEMPE is zero, setting EEPE will have no effect.
if (this.cpu.cycles >= this.writeEnabledCycles) {
this.cpu.data[EECR] &= ~EEPE;
return true;
}
// Check for write-in-progress
if (this.cpu.cycles < this.writeCompleteCycles) {
return true;
}
const eedr = this.cpu.data[EEDR];
this.writeCompleteCycles = this.cpu.cycles;
// Erase
if (!(eecr & EEPM1)) {
this.backend.eraseMemory(addr);
this.writeCompleteCycles += this.config.eraseCycles;
}
// Write
if (!(eecr & EEPM0)) {
this.backend.writeMemory(addr, eedr);
this.writeCompleteCycles += this.config.writeCycles;
}
this.cpu.data[EECR] |= EEPE;
this.cpu.addClockEvent(() => {
this.cpu.setInterruptFlag(this.EER);
}, this.writeCompleteCycles - this.cpu.cycles);
// When EEPE has been set, the CPU is halted for two cycles before the
// next instruction is executed.
this.cpu.cycles += 2;
}
return true;
};
}
}
+422
View File
@@ -0,0 +1,422 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export const INT0 = {
EICR: 0x69,
EIMSK: 0x3d,
EIFR: 0x3c,
index: 0,
iscOffset: 0,
interrupt: 2,
};
export const INT1 = {
EICR: 0x69,
EIMSK: 0x3d,
EIFR: 0x3c,
index: 1,
iscOffset: 2,
interrupt: 4,
};
export const PCINT0 = {
PCIE: 0,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6b,
pinChangeInterrupt: 6,
mask: 0xff,
offset: 0,
};
export const PCINT1 = {
PCIE: 1,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6c,
pinChangeInterrupt: 8,
mask: 0xff,
offset: 0,
};
export const PCINT2 = {
PCIE: 2,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6d,
pinChangeInterrupt: 10,
mask: 0xff,
offset: 0,
};
export const portAConfig = {
PIN: 0x20,
DDR: 0x21,
PORT: 0x22,
externalInterrupts: [],
};
export const portBConfig = {
PIN: 0x23,
DDR: 0x24,
PORT: 0x25,
// Interrupt settings
pinChange: PCINT0,
externalInterrupts: [],
};
export const portCConfig = {
PIN: 0x26,
DDR: 0x27,
PORT: 0x28,
// Interrupt settings
pinChange: PCINT1,
externalInterrupts: [],
};
export const portDConfig = {
PIN: 0x29,
DDR: 0x2a,
PORT: 0x2b,
// Interrupt settings
pinChange: PCINT2,
externalInterrupts: [null, null, INT0, INT1],
};
export const portEConfig = {
PIN: 0x2c,
DDR: 0x2d,
PORT: 0x2e,
externalInterrupts: [],
};
export const portFConfig = {
PIN: 0x2f,
DDR: 0x30,
PORT: 0x31,
externalInterrupts: [],
};
export const portGConfig = {
PIN: 0x32,
DDR: 0x33,
PORT: 0x34,
externalInterrupts: [],
};
export const portHConfig = {
PIN: 0x100,
DDR: 0x101,
PORT: 0x102,
externalInterrupts: [],
};
export const portJConfig = {
PIN: 0x103,
DDR: 0x104,
PORT: 0x105,
externalInterrupts: [],
};
export const portKConfig = {
PIN: 0x106,
DDR: 0x107,
PORT: 0x108,
externalInterrupts: [],
};
export const portLConfig = {
PIN: 0x109,
DDR: 0x10a,
PORT: 0x10b,
externalInterrupts: [],
};
export var PinState;
(function (PinState) {
PinState[PinState["Low"] = 0] = "Low";
PinState[PinState["High"] = 1] = "High";
PinState[PinState["Input"] = 2] = "Input";
PinState[PinState["InputPullUp"] = 3] = "InputPullUp";
})(PinState || (PinState = {}));
/* This mechanism allows timers to override specific GPIO pins */
export var PinOverrideMode;
(function (PinOverrideMode) {
PinOverrideMode[PinOverrideMode["None"] = 0] = "None";
PinOverrideMode[PinOverrideMode["Enable"] = 1] = "Enable";
PinOverrideMode[PinOverrideMode["Set"] = 2] = "Set";
PinOverrideMode[PinOverrideMode["Clear"] = 3] = "Clear";
PinOverrideMode[PinOverrideMode["Toggle"] = 4] = "Toggle";
})(PinOverrideMode || (PinOverrideMode = {}));
var InterruptMode;
(function (InterruptMode) {
InterruptMode[InterruptMode["LowLevel"] = 0] = "LowLevel";
InterruptMode[InterruptMode["Change"] = 1] = "Change";
InterruptMode[InterruptMode["FallingEdge"] = 2] = "FallingEdge";
InterruptMode[InterruptMode["RisingEdge"] = 3] = "RisingEdge";
})(InterruptMode || (InterruptMode = {}));
export class AVRIOPort {
constructor(cpu, portConfig) {
var _a, _b, _c, _d;
this.cpu = cpu;
this.portConfig = portConfig;
this.externalClockListeners = [];
this.listeners = [];
this.pinValue = 0;
this.overrideMask = 0xff;
this.overrideValue = 0;
this.lastValue = 0;
this.lastDdr = 0;
this.lastPin = 0;
this.openCollector = 0;
cpu.gpioPorts.add(this);
cpu.gpioByPort[portConfig.PORT] = this;
cpu.writeHooks[portConfig.DDR] = (value) => {
const portValue = cpu.data[portConfig.PORT];
cpu.data[portConfig.DDR] = value;
this.writeGpio(portValue, value);
this.updatePinRegister(value);
return true;
};
cpu.writeHooks[portConfig.PORT] = (value) => {
const ddrMask = cpu.data[portConfig.DDR];
cpu.data[portConfig.PORT] = value;
this.writeGpio(value, ddrMask);
this.updatePinRegister(ddrMask);
return true;
};
cpu.writeHooks[portConfig.PIN] = (value, oldValue, addr, mask) => {
// Writing to 1 PIN toggles PORT bits
const oldPortValue = cpu.data[portConfig.PORT];
const ddrMask = cpu.data[portConfig.DDR];
const portValue = oldPortValue ^ (value & mask);
cpu.data[portConfig.PORT] = portValue;
this.writeGpio(portValue, ddrMask);
this.updatePinRegister(ddrMask);
return true;
};
// External interrupts
const { externalInterrupts } = portConfig;
this.externalInts = externalInterrupts.map((externalConfig) => externalConfig
? {
address: externalConfig.interrupt,
flagRegister: externalConfig.EIFR,
flagMask: 1 << externalConfig.index,
enableRegister: externalConfig.EIMSK,
enableMask: 1 << externalConfig.index,
}
: null);
const EICR = new Set(externalInterrupts.map((item) => item === null || item === void 0 ? void 0 : item.EICR));
for (const EICRx of EICR) {
this.attachInterruptHook(EICRx || 0);
}
const EIMSK = (_b = (_a = externalInterrupts.find((item) => item && item.EIMSK)) === null || _a === void 0 ? void 0 : _a.EIMSK) !== null && _b !== void 0 ? _b : 0;
this.attachInterruptHook(EIMSK, 'mask');
const EIFR = (_d = (_c = externalInterrupts.find((item) => item && item.EIFR)) === null || _c === void 0 ? void 0 : _c.EIFR) !== null && _d !== void 0 ? _d : 0;
this.attachInterruptHook(EIFR, 'flag');
// Pin change interrupts
const { pinChange } = portConfig;
this.PCINT = pinChange
? {
address: pinChange.pinChangeInterrupt,
flagRegister: pinChange.PCIFR,
flagMask: 1 << pinChange.PCIE,
enableRegister: pinChange.PCICR,
enableMask: 1 << pinChange.PCIE,
}
: null;
if (pinChange) {
const { PCIFR, PCMSK } = pinChange;
cpu.writeHooks[PCIFR] = (value) => {
for (const gpio of this.cpu.gpioPorts) {
const { PCINT } = gpio;
if (PCINT) {
cpu.clearInterruptByFlag(PCINT, value);
}
}
return true;
};
cpu.writeHooks[PCMSK] = (value) => {
cpu.data[PCMSK] = value;
for (const gpio of this.cpu.gpioPorts) {
const { PCINT } = gpio;
if (PCINT) {
cpu.updateInterruptEnable(PCINT, value);
}
}
return true;
};
}
}
addListener(listener) {
this.listeners.push(listener);
}
removeListener(listener) {
this.listeners = this.listeners.filter((l) => l !== listener);
}
/**
* Get the state of a given GPIO pin
*
* @param index Pin index to return from 0 to 7
* @returns PinState.Low or PinState.High if the pin is set to output, PinState.Input if the pin is set
* to input, and PinState.InputPullUp if the pin is set to input and the internal pull-up resistor has
* been enabled.
*/
pinState(index) {
const ddr = this.cpu.data[this.portConfig.DDR];
const port = this.cpu.data[this.portConfig.PORT];
const bitMask = 1 << index;
const openState = port & bitMask ? PinState.InputPullUp : PinState.Input;
const highValue = this.openCollector & bitMask ? openState : PinState.High;
if (ddr & bitMask) {
return this.lastValue & bitMask ? highValue : PinState.Low;
}
else {
return openState;
}
}
/**
* Sets the input value for the given pin. This is the value that
* will be returned when reading from the PIN register.
*/
setPin(index, value) {
const bitMask = 1 << index;
this.pinValue &= ~bitMask;
if (value) {
this.pinValue |= bitMask;
}
this.updatePinRegister(this.cpu.data[this.portConfig.DDR]);
}
/**
* Internal method - do not call this directly!
* Used by the timer compare output units to override GPIO pins.
*/
timerOverridePin(pin, mode) {
const { cpu, portConfig } = this;
const pinMask = 1 << pin;
if (mode === PinOverrideMode.None) {
this.overrideMask |= pinMask;
this.overrideValue &= ~pinMask;
}
else {
this.overrideMask &= ~pinMask;
switch (mode) {
case PinOverrideMode.Enable:
this.overrideValue &= ~pinMask;
this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
break;
case PinOverrideMode.Set:
this.overrideValue |= pinMask;
break;
case PinOverrideMode.Clear:
this.overrideValue &= ~pinMask;
break;
case PinOverrideMode.Toggle:
this.overrideValue ^= pinMask;
break;
}
}
const ddrMask = cpu.data[portConfig.DDR];
this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
this.updatePinRegister(ddrMask);
}
updatePinRegister(ddr) {
var _a, _b;
const newPin = (this.pinValue & ~ddr) | (this.lastValue & ddr);
this.cpu.data[this.portConfig.PIN] = newPin;
if (this.lastPin !== newPin) {
for (let index = 0; index < 8; index++) {
if ((newPin & (1 << index)) !== (this.lastPin & (1 << index))) {
const value = !!(newPin & (1 << index));
this.toggleInterrupt(index, value);
(_b = (_a = this.externalClockListeners)[index]) === null || _b === void 0 ? void 0 : _b.call(_a, value);
}
}
this.lastPin = newPin;
}
}
toggleInterrupt(pin, risingEdge) {
const { cpu, portConfig, externalInts, PCINT } = this;
const { externalInterrupts, pinChange } = portConfig;
const externalConfig = externalInterrupts[pin];
const external = externalInts[pin];
if (external && externalConfig) {
const { EIMSK, index, EICR, iscOffset } = externalConfig;
if (cpu.data[EIMSK] & (1 << index)) {
const configuration = (cpu.data[EICR] >> iscOffset) & 0x3;
let generateInterrupt = false;
external.constant = false;
switch (configuration) {
case InterruptMode.LowLevel:
generateInterrupt = !risingEdge;
external.constant = true;
break;
case InterruptMode.Change:
generateInterrupt = true;
break;
case InterruptMode.FallingEdge:
generateInterrupt = !risingEdge;
break;
case InterruptMode.RisingEdge:
generateInterrupt = risingEdge;
break;
}
if (generateInterrupt) {
cpu.setInterruptFlag(external);
}
else if (external.constant) {
cpu.clearInterrupt(external, true);
}
}
}
if (pinChange && PCINT && pinChange.mask & (1 << pin)) {
const { PCMSK } = pinChange;
if (cpu.data[PCMSK] & (1 << (pin + pinChange.offset))) {
cpu.setInterruptFlag(PCINT);
}
}
}
attachInterruptHook(register, registerType = 'other') {
if (!register) {
return;
}
const { cpu } = this;
cpu.writeHooks[register] = (value) => {
if (registerType !== 'flag') {
cpu.data[register] = value;
}
for (const gpio of cpu.gpioPorts) {
for (const external of gpio.externalInts) {
if (external && registerType === 'mask') {
cpu.updateInterruptEnable(external, value);
}
if (external && !external.constant && registerType === 'flag') {
cpu.clearInterruptByFlag(external, value);
}
}
gpio.checkExternalInterrupts();
}
return true;
};
}
checkExternalInterrupts() {
const { cpu } = this;
const { externalInterrupts } = this.portConfig;
for (let pin = 0; pin < 8; pin++) {
const external = externalInterrupts[pin];
if (!external) {
continue;
}
const pinValue = !!(this.lastPin & (1 << pin));
const { EIFR, EIMSK, index, EICR, iscOffset, interrupt } = external;
if (!(cpu.data[EIMSK] & (1 << index)) || pinValue) {
continue;
}
const configuration = (cpu.data[EICR] >> iscOffset) & 0x3;
if (configuration === InterruptMode.LowLevel) {
cpu.queueInterrupt({
address: interrupt,
flagRegister: EIFR,
flagMask: 1 << index,
enableRegister: EIMSK,
enableMask: 1 << index,
constant: true,
});
}
}
}
writeGpio(value, ddr) {
const newValue = (((value & this.overrideMask) | this.overrideValue) & ddr) | (value & ~ddr);
const prevValue = this.lastValue;
if (newValue !== prevValue || ddr !== this.lastDdr) {
this.lastValue = newValue;
this.lastDdr = ddr;
for (const listener of this.listeners) {
listener(newValue, prevValue);
}
}
}
}
+130
View File
@@ -0,0 +1,130 @@
// 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;
}
}
+343
View File
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { PinOverrideMode } from './gpio.js';
// TCCR1 bits
const CTC1 = 1 << 7;
const PWM1A = 1 << 6;
const CS_MASK = 0x0f;
// GTCCR bits
const PWM1B_BIT = 1 << 6;
const FOC1B = 1 << 3;
const FOC1A = 1 << 2;
const PSR1 = 1 << 1;
export const attinyTimer1Config = {
TCCR1: 0x50,
GTCCR: 0x4c,
TCNT1: 0x4f,
OCR1A: 0x4e,
OCR1B: 0x4b,
OCR1C: 0x4d,
TIFR: 0x58,
TIMSK: 0x59,
ovfInterrupt: 0x04,
compAInterrupt: 0x03,
compBInterrupt: 0x09,
TOV1: 1 << 2,
OCF1A: 1 << 6,
OCF1B: 1 << 5,
TOIE1: 1 << 2,
OCIE1A: 1 << 6,
OCIE1B: 1 << 5,
compPortB: 0x38,
compPinA: 1, // PB1
compPinB: 4, // PB4
dividers: {
0: 0,
1: 1,
2: 2,
3: 4,
4: 8,
5: 16,
6: 32,
7: 64,
8: 128,
9: 256,
10: 512,
11: 1024,
12: 2048,
13: 4096,
14: 8192,
15: 16384,
},
};
export class ATtinyTimer1 {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
this.lastCycle = 0;
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.ocrA = 0;
this.ocrB = 0;
this.ocrC = 0;
this.divider = 0;
this.updateDivider = false;
this.countingUp = true;
this.OVF = {
address: this.config.ovfInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.TOV1,
enableRegister: this.config.TIMSK,
enableMask: this.config.TOIE1,
};
this.OCFA = {
address: this.config.compAInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCF1A,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIE1A,
};
this.OCFB = {
address: this.config.compBInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCF1B,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIE1B,
};
this.count = (reschedule = true) => {
var _a;
const { divider, lastCycle, cpu } = this;
const { cycles } = cpu;
const delta = cycles - lastCycle;
if (divider && delta >= divider) {
const counterDelta = Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const top = this.TOP;
const phasePwm = (this.pwmA || this.pwmB) && !this.ctcMode;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
: (val + counterDelta) % (top + 1);
const overflow = val + counterDelta > top;
if (!this.tcntUpdated) {
this.tcnt = newVal;
if (!phasePwm) {
this.timerUpdated(newVal, val);
}
}
if (!phasePwm && overflow) {
cpu.setInterruptFlag(this.OVF);
}
}
if (this.tcntUpdated) {
this.tcnt = this.tcntNext;
this.tcntUpdated = false;
}
if (this.updateDivider) {
const cs = this.CS;
const newDivider = (_a = this.config.dividers[cs]) !== null && _a !== void 0 ? _a : 0;
this.lastCycle = newDivider ? this.cpu.cycles : 0;
this.updateDivider = false;
this.divider = newDivider;
if (newDivider) {
cpu.addClockEvent(this.count, this.lastCycle + newDivider - cpu.cycles);
}
return;
}
if (reschedule && divider) {
cpu.addClockEvent(this.count, this.lastCycle + divider - cpu.cycles);
}
};
const { TCCR1, GTCCR, TCNT1, OCR1A, OCR1B, OCR1C, TIFR, TIMSK } = config;
cpu.readHooks[TCNT1] = () => {
this.count(false);
return (cpu.data[TCNT1] = this.tcnt & 0xff);
};
cpu.writeHooks[TCNT1] = (value) => {
this.tcntNext = value;
this.countingUp = true;
this.tcntUpdated = true;
cpu.updateClockEvent(this.count, 0);
if (this.divider) {
this.timerUpdated(this.tcntNext, this.tcntNext);
}
};
cpu.writeHooks[OCR1A] = (value) => {
this.ocrA = value;
};
cpu.writeHooks[OCR1B] = (value) => {
this.ocrB = value;
};
cpu.writeHooks[OCR1C] = (value) => {
this.ocrC = value;
};
cpu.writeHooks[TCCR1] = (value) => {
cpu.data[TCCR1] = value;
this.updateDivider = true;
cpu.clearClockEvent(this.count);
cpu.addClockEvent(this.count, 0);
this.updateCompConfig();
return true;
};
// GTCCR is shared with Timer0 (PSR0) — chain with existing hook
const prevGtccrHook = cpu.writeHooks[GTCCR];
cpu.writeHooks[GTCCR] = (value, oldValue, addr, mask) => {
if (value & FOC1A) {
this.forceCompare('A');
}
if (value & FOC1B) {
this.forceCompare('B');
}
if (value & PSR1) {
this.lastCycle = this.cpu.cycles;
}
value &= ~(FOC1A | FOC1B | PSR1);
if (prevGtccrHook) {
prevGtccrHook(value, oldValue, addr, mask);
}
else {
cpu.data[GTCCR] = value;
}
this.updateCompConfig();
return true;
};
// TIFR/TIMSK are shared with Timer0 — chain with existing hooks
const prevTifrHook = cpu.writeHooks[TIFR];
cpu.writeHooks[TIFR] = (value, oldValue, addr, mask) => {
if (prevTifrHook) {
prevTifrHook(value, oldValue, addr, mask);
}
else {
cpu.data[TIFR] = value;
}
cpu.clearInterruptByFlag(this.OVF, value);
cpu.clearInterruptByFlag(this.OCFA, value);
cpu.clearInterruptByFlag(this.OCFB, value);
return true;
};
const prevTimskHook = cpu.writeHooks[TIMSK];
cpu.writeHooks[TIMSK] = (value, oldValue, addr, mask) => {
if (prevTimskHook) {
prevTimskHook(value, oldValue, addr, mask);
}
cpu.updateInterruptEnable(this.OVF, value);
cpu.updateInterruptEnable(this.OCFA, value);
cpu.updateInterruptEnable(this.OCFB, value);
};
}
get tccr1() {
return this.cpu.data[this.config.TCCR1];
}
get gtccr() {
return this.cpu.data[this.config.GTCCR];
}
get CS() {
return this.tccr1 & CS_MASK;
}
get ctcMode() {
return !!(this.tccr1 & CTC1);
}
get pwmA() {
return !!(this.tccr1 & PWM1A);
}
get pwmB() {
return !!(this.gtccr & PWM1B_BIT);
}
get comA() {
return (this.tccr1 >> 4) & 0x3;
}
get comB() {
return (this.gtccr >> 4) & 0x3;
}
/** TOP = OCR1C in CTC/PWM modes, 0xFF in Normal mode */
get TOP() {
if (this.ctcMode || this.pwmA || this.pwmB) {
return this.ocrC;
}
return 0xff;
}
phasePwmCount(value, delta) {
const top = this.TOP;
while (delta > 0) {
if (this.countingUp) {
value++;
if (value >= top) {
value = top;
this.countingUp = false;
}
}
else {
value--;
if (value <= 0) {
value = 0;
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
}
}
if (!this.tcntUpdated) {
if (value === this.ocrA) {
this.cpu.setInterruptFlag(this.OCFA);
this.updateCompPinPwm('A');
}
if (value === this.ocrB) {
this.cpu.setInterruptFlag(this.OCFB);
this.updateCompPinPwm('B');
}
}
delta--;
}
return value & 0xff;
}
timerUpdated(value, prevValue) {
const { ocrA, ocrB } = this;
const overflow = prevValue > value;
if (((prevValue < ocrA || overflow) && value >= ocrA) || (prevValue < ocrA && overflow)) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.comA && !this.pwmA) {
this.updateCompPinNonPwm('A');
}
}
if (((prevValue < ocrB || overflow) && value >= ocrB) || (prevValue < ocrB && overflow)) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.comB && !this.pwmB) {
this.updateCompPinNonPwm('B');
}
}
}
forceCompare(channel) {
if (channel === 'A' && !this.pwmA && this.comA) {
this.updateCompPinNonPwm('A');
}
else if (channel === 'B' && !this.pwmB && this.comB) {
this.updateCompPinNonPwm('B');
}
}
updateCompPinNonPwm(channel) {
var _a;
const com = channel === 'A' ? this.comA : this.comB;
const pin = channel === 'A' ? this.config.compPinA : this.config.compPinB;
let mode;
switch (com) {
case 1:
mode = PinOverrideMode.Toggle;
break;
case 2:
mode = PinOverrideMode.Clear;
break;
case 3:
mode = PinOverrideMode.Set;
break;
default:
return;
}
(_a = this.cpu.gpioByPort[this.config.compPortB]) === null || _a === void 0 ? void 0 : _a.timerOverridePin(pin, mode);
}
updateCompPinPwm(channel) {
var _a;
const com = channel === 'A' ? this.comA : this.comB;
const pin = channel === 'A' ? this.config.compPinA : this.config.compPinB;
const invertingMode = com === 3;
const isSet = this.countingUp === invertingMode;
let mode;
switch (com) {
case 1:
mode = PinOverrideMode.Toggle;
break;
case 2:
case 3:
mode = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
break;
default:
return;
}
(_a = this.cpu.gpioByPort[this.config.compPortB]) === null || _a === void 0 ? void 0 : _a.timerOverridePin(pin, mode);
}
updateCompConfig() {
const port = this.cpu.gpioByPort[this.config.compPortB];
if (!port)
return;
port.timerOverridePin(this.config.compPinA, this.comA ? PinOverrideMode.Enable : PinOverrideMode.None);
port.timerOverridePin(this.config.compPinB, this.comB ? PinOverrideMode.Enable : PinOverrideMode.None);
}
}
+595
View File
@@ -0,0 +1,595 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { PinOverrideMode, portBConfig, portDConfig } from './gpio.js';
const timer01Dividers = {
0: 0,
1: 1,
2: 8,
3: 64,
4: 256,
5: 1024,
6: 0, // External clock - see ExternalClockMode
7: 0, // Ditto
};
var ExternalClockMode;
(function (ExternalClockMode) {
ExternalClockMode[ExternalClockMode["FallingEdge"] = 6] = "FallingEdge";
ExternalClockMode[ExternalClockMode["RisingEdge"] = 7] = "RisingEdge";
})(ExternalClockMode || (ExternalClockMode = {}));
/** These are differnet for some devices (e.g. ATtiny85) */
const defaultTimerBits = {
// TIFR bits
TOV: 1,
OCFA: 2,
OCFB: 4,
OCFC: 0, // Unused
// TIMSK bits
TOIE: 1,
OCIEA: 2,
OCIEB: 4,
OCIEC: 0, // Unused
};
export const timer0Config = Object.assign({ bits: 8, captureInterrupt: 0, compAInterrupt: 0x1c, compBInterrupt: 0x1e, compCInterrupt: 0, ovfInterrupt: 0x20, TIFR: 0x35, OCRA: 0x47, OCRB: 0x48, OCRC: 0, ICR: 0, TCNT: 0x46, TCCRA: 0x44, TCCRB: 0x45, TCCRC: 0, TIMSK: 0x6e, dividers: timer01Dividers, compPortA: portDConfig.PORT, compPinA: 6, compPortB: portDConfig.PORT, compPinB: 5, compPortC: 0, compPinC: 0, externalClockPort: portDConfig.PORT, externalClockPin: 4 }, defaultTimerBits);
export const timer1Config = Object.assign({ bits: 16, captureInterrupt: 0x14, compAInterrupt: 0x16, compBInterrupt: 0x18, compCInterrupt: 0, ovfInterrupt: 0x1a, TIFR: 0x36, OCRA: 0x88, OCRB: 0x8a, OCRC: 0, ICR: 0x86, TCNT: 0x84, TCCRA: 0x80, TCCRB: 0x81, TCCRC: 0x82, TIMSK: 0x6f, dividers: timer01Dividers, compPortA: portBConfig.PORT, compPinA: 1, compPortB: portBConfig.PORT, compPinB: 2, compPortC: 0, compPinC: 0, externalClockPort: portDConfig.PORT, externalClockPin: 5 }, defaultTimerBits);
export const timer2Config = Object.assign({ bits: 8, captureInterrupt: 0, compAInterrupt: 0x0e, compBInterrupt: 0x10, compCInterrupt: 0, ovfInterrupt: 0x12, TIFR: 0x37, OCRA: 0xb3, OCRB: 0xb4, OCRC: 0, ICR: 0, TCNT: 0xb2, TCCRA: 0xb0, TCCRB: 0xb1, TCCRC: 0, TIMSK: 0x70, dividers: {
0: 0,
1: 1,
2: 8,
3: 32,
4: 64,
5: 128,
6: 256,
7: 1024,
}, compPortA: portBConfig.PORT, compPinA: 3, compPortB: portDConfig.PORT, compPinB: 3, compPortC: 0, compPinC: 0, externalClockPort: 0, externalClockPin: 0 }, defaultTimerBits);
/* All the following types and constants are related to WGM (Waveform Generation Mode) bits: */
var TimerMode;
(function (TimerMode) {
TimerMode[TimerMode["Normal"] = 0] = "Normal";
TimerMode[TimerMode["PWMPhaseCorrect"] = 1] = "PWMPhaseCorrect";
TimerMode[TimerMode["CTC"] = 2] = "CTC";
TimerMode[TimerMode["FastPWM"] = 3] = "FastPWM";
TimerMode[TimerMode["PWMPhaseFrequencyCorrect"] = 4] = "PWMPhaseFrequencyCorrect";
TimerMode[TimerMode["Reserved"] = 5] = "Reserved";
})(TimerMode || (TimerMode = {}));
var TOVUpdateMode;
(function (TOVUpdateMode) {
TOVUpdateMode[TOVUpdateMode["Max"] = 0] = "Max";
TOVUpdateMode[TOVUpdateMode["Top"] = 1] = "Top";
TOVUpdateMode[TOVUpdateMode["Bottom"] = 2] = "Bottom";
})(TOVUpdateMode || (TOVUpdateMode = {}));
var OCRUpdateMode;
(function (OCRUpdateMode) {
OCRUpdateMode[OCRUpdateMode["Immediate"] = 0] = "Immediate";
OCRUpdateMode[OCRUpdateMode["Top"] = 1] = "Top";
OCRUpdateMode[OCRUpdateMode["Bottom"] = 2] = "Bottom";
})(OCRUpdateMode || (OCRUpdateMode = {}));
const TopOCRA = 1;
const TopICR = 2;
// Enable Toggle mode for OCxA in PWM Wave Generation mode
const OCToggle = 1;
const { Normal, PWMPhaseCorrect, CTC, FastPWM, Reserved, PWMPhaseFrequencyCorrect } = TimerMode;
const wgmModes8Bit = [
/*0*/ [Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*1*/ [PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*2*/ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*3*/ [FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max, 0],
/*4*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*5*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
/*6*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*7*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
// Table 16-4 in the datasheet
const wgmModes16Bit = [
/*0 */ [Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*1 */ [PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*2 */ [PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*3 */ [PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*4 */ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*5 */ [FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*6 */ [FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*7 */ [FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*8 */ [PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, 0],
/*9 */ [PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, OCToggle],
/*10*/ [PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*11*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
/*12*/ [CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*13*/ [Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*14*/ [FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
/*15*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
function compToOverride(comp) {
switch (comp) {
case 1:
return PinOverrideMode.Toggle;
case 2:
return PinOverrideMode.Clear;
case 3:
return PinOverrideMode.Set;
default:
return PinOverrideMode.Enable;
}
}
// Force Output Compare (FOC) bits
const FOCA = 1 << 7;
const FOCB = 1 << 6;
const FOCC = 1 << 5;
export class AVRTimer {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
this.MAX = this.config.bits === 16 ? 0xffff : 0xff;
this.lastCycle = 0;
this.ocrA = 0;
this.nextOcrA = 0;
this.ocrB = 0;
this.nextOcrB = 0;
this.hasOCRC = this.config.OCRC > 0;
this.ocrC = 0;
this.nextOcrC = 0;
this.ocrUpdateMode = OCRUpdateMode.Immediate;
this.tovUpdateMode = TOVUpdateMode.Max;
this.icr = 0; // only for 16-bit timers
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.updateDivider = false;
this.countingUp = true;
this.divider = 0;
this.externalClockRisingEdge = false;
// This is the temporary register used to access 16-bit registers (section 16.3 of the datasheet)
this.highByteTemp = 0;
// Interrupts
this.OVF = {
address: this.config.ovfInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.TOV,
enableRegister: this.config.TIMSK,
enableMask: this.config.TOIE,
};
this.OCFA = {
address: this.config.compAInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFA,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEA,
};
this.OCFB = {
address: this.config.compBInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFB,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEB,
};
this.OCFC = {
address: this.config.compCInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFC,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEC,
};
this.count = (reschedule = true, external = false) => {
const { divider, lastCycle, cpu } = this;
const { cycles } = cpu;
const delta = cycles - lastCycle;
if ((divider && delta >= divider) || external) {
const counterDelta = external ? 1 : Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const { timerMode, TOP } = this;
const phasePwm = timerMode === PWMPhaseCorrect || timerMode === PWMPhaseFrequencyCorrect;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
: (val + counterDelta) % (TOP + 1);
const overflow = val + counterDelta > TOP;
// A CPU write overrides (has priority over) all counter clear or count operations.
if (!this.tcntUpdated) {
this.tcnt = newVal;
if (!phasePwm) {
this.timerUpdated(newVal, val);
}
}
if (!phasePwm) {
if (timerMode === FastPWM && overflow) {
const { compA, compB } = this;
if (compA) {
this.updateCompPin(compA, 'A', true);
}
if (compB) {
this.updateCompPin(compB, 'B', true);
}
}
if (this.ocrUpdateMode == OCRUpdateMode.Bottom && overflow) {
// OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
// OCRUpdateMode.Bottom only occurs in Phase Correct modes, handled by phasePwmCount().
// Thus we only handle TOVUpdateMode.Top or TOVUpdateMode.Max here.
if (overflow && (this.tovUpdateMode == TOVUpdateMode.Top || TOP === this.MAX)) {
cpu.setInterruptFlag(this.OVF);
}
}
}
if (this.tcntUpdated) {
this.tcnt = this.tcntNext;
this.tcntUpdated = false;
if ((this.tcnt === 0 && this.ocrUpdateMode === OCRUpdateMode.Bottom) ||
(this.tcnt === this.TOP && this.ocrUpdateMode === OCRUpdateMode.Top)) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
if (this.updateDivider) {
const { CS } = this;
const { externalClockPin } = this.config;
const newDivider = this.config.dividers[CS];
this.lastCycle = newDivider ? this.cpu.cycles : 0;
this.updateDivider = false;
this.divider = newDivider;
if (this.config.externalClockPort && !this.externalClockPort) {
this.externalClockPort = this.cpu.gpioByPort[this.config.externalClockPort];
}
if (this.externalClockPort) {
this.externalClockPort.externalClockListeners[externalClockPin] = null;
}
if (newDivider) {
cpu.addClockEvent(this.count, this.lastCycle + newDivider - cpu.cycles);
}
else if (this.externalClockPort &&
(CS === ExternalClockMode.FallingEdge || CS === ExternalClockMode.RisingEdge)) {
this.externalClockPort.externalClockListeners[externalClockPin] =
this.externalClockCallback;
this.externalClockRisingEdge = CS === ExternalClockMode.RisingEdge;
}
return;
}
if (reschedule && divider) {
cpu.addClockEvent(this.count, this.lastCycle + divider - cpu.cycles);
}
};
this.externalClockCallback = (value) => {
if (value === this.externalClockRisingEdge) {
this.count(false, true);
}
};
this.updateWGMConfig();
this.cpu.readHooks[config.TCNT] = (addr) => {
this.count(false);
if (this.config.bits === 16) {
this.cpu.data[addr + 1] = this.tcnt >> 8;
}
return (this.cpu.data[addr] = this.tcnt & 0xff);
};
this.cpu.writeHooks[config.TCNT] = (value) => {
this.tcntNext = (this.highByteTemp << 8) | value;
this.countingUp = true;
this.tcntUpdated = true;
this.cpu.updateClockEvent(this.count, 0);
if (this.divider) {
this.timerUpdated(this.tcntNext, this.tcntNext);
}
};
this.cpu.writeHooks[config.OCRA] = (value) => {
this.nextOcrA = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrA = this.nextOcrA;
}
};
this.cpu.writeHooks[config.OCRB] = (value) => {
this.nextOcrB = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrB = this.nextOcrB;
}
};
if (this.hasOCRC) {
this.cpu.writeHooks[config.OCRC] = (value) => {
this.nextOcrC = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrC = this.nextOcrC;
}
};
}
if (this.config.bits === 16) {
this.cpu.writeHooks[config.ICR] = (value) => {
this.icr = (this.highByteTemp << 8) | value;
};
const updateTempRegister = (value) => {
this.highByteTemp = value;
};
const updateOCRHighRegister = (value, old, addr) => {
this.highByteTemp = value & (this.ocrMask >> 8);
cpu.data[addr] = this.highByteTemp;
return true;
};
this.cpu.writeHooks[config.TCNT + 1] = updateTempRegister;
this.cpu.writeHooks[config.OCRA + 1] = updateOCRHighRegister;
this.cpu.writeHooks[config.OCRB + 1] = updateOCRHighRegister;
if (this.hasOCRC) {
this.cpu.writeHooks[config.OCRC + 1] = updateOCRHighRegister;
}
this.cpu.writeHooks[config.ICR + 1] = updateTempRegister;
}
cpu.writeHooks[config.TCCRA] = (value) => {
this.cpu.data[config.TCCRA] = value;
this.updateWGMConfig();
return true;
};
cpu.writeHooks[config.TCCRB] = (value) => {
if (!config.TCCRC) {
this.checkForceCompare(value);
value &= ~(FOCA | FOCB);
}
this.cpu.data[config.TCCRB] = value;
this.updateDivider = true;
this.cpu.clearClockEvent(this.count);
this.cpu.addClockEvent(this.count, 0);
this.updateWGMConfig();
return true;
};
if (config.TCCRC) {
cpu.writeHooks[config.TCCRC] = (value) => {
this.checkForceCompare(value);
};
}
cpu.writeHooks[config.TIFR] = (value) => {
this.cpu.data[config.TIFR] = value;
this.cpu.clearInterruptByFlag(this.OVF, value);
this.cpu.clearInterruptByFlag(this.OCFA, value);
this.cpu.clearInterruptByFlag(this.OCFB, value);
return true;
};
cpu.writeHooks[config.TIMSK] = (value) => {
this.cpu.updateInterruptEnable(this.OVF, value);
this.cpu.updateInterruptEnable(this.OCFA, value);
this.cpu.updateInterruptEnable(this.OCFB, value);
};
}
reset() {
this.divider = 0;
this.lastCycle = 0;
this.ocrA = 0;
this.nextOcrA = 0;
this.ocrB = 0;
this.nextOcrB = 0;
this.ocrC = 0;
this.nextOcrC = 0;
this.icr = 0;
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.countingUp = false;
this.updateDivider = true;
}
get TCCRA() {
return this.cpu.data[this.config.TCCRA];
}
get TCCRB() {
return this.cpu.data[this.config.TCCRB];
}
get TIMSK() {
return this.cpu.data[this.config.TIMSK];
}
get CS() {
return (this.TCCRB & 0x7);
}
get WGM() {
const mask = this.config.bits === 16 ? 0x18 : 0x8;
return ((this.TCCRB & mask) >> 1) | (this.TCCRA & 0x3);
}
get TOP() {
switch (this.topValue) {
case TopOCRA:
return this.ocrA;
case TopICR:
return this.icr;
default:
return this.topValue;
}
}
get ocrMask() {
switch (this.topValue) {
case TopOCRA:
case TopICR:
return 0xffff;
default:
return this.topValue;
}
}
/** Expose the raw value of TCNT, for use by the unit tests */
get debugTCNT() {
return this.tcnt;
}
updateWGMConfig() {
const { config, WGM } = this;
const wgmModes = config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
const TCCRA = this.cpu.data[config.TCCRA];
const [timerMode, topValue, ocrUpdateMode, tovUpdateMode, flags] = wgmModes[WGM];
this.timerMode = timerMode;
this.topValue = topValue;
this.ocrUpdateMode = ocrUpdateMode;
this.tovUpdateMode = tovUpdateMode;
const pwmMode = timerMode === FastPWM ||
timerMode === PWMPhaseCorrect ||
timerMode === PWMPhaseFrequencyCorrect;
const prevCompA = this.compA;
this.compA = ((TCCRA >> 6) & 0x3);
if (this.compA === 1 && pwmMode && !(flags & OCToggle)) {
this.compA = 0;
}
if (!!prevCompA !== !!this.compA) {
this.updateCompA(this.compA ? PinOverrideMode.Enable : PinOverrideMode.None);
}
const prevCompB = this.compB;
this.compB = ((TCCRA >> 4) & 0x3);
if (this.compB === 1 && pwmMode) {
this.compB = 0; // Reserved, according to the datasheet
}
if (!!prevCompB !== !!this.compB) {
this.updateCompB(this.compB ? PinOverrideMode.Enable : PinOverrideMode.None);
}
if (this.hasOCRC) {
const prevCompC = this.compC;
this.compC = ((TCCRA >> 2) & 0x3);
if (this.compC === 1 && pwmMode) {
this.compC = 0; // Reserved, according to the datasheet
}
if (!!prevCompC !== !!this.compC) {
this.updateCompC(this.compC ? PinOverrideMode.Enable : PinOverrideMode.None);
}
}
}
phasePwmCount(value, delta) {
const { ocrA, ocrB, ocrC, hasOCRC, TOP, MAX, tcntUpdated } = this;
if (!value && !TOP) {
delta = 0;
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
while (delta > 0) {
if (this.countingUp) {
value++;
if (value === TOP && !tcntUpdated) {
this.countingUp = false;
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
}
else {
value--;
if (!value && !tcntUpdated) {
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
}
if (!tcntUpdated) {
if (value === ocrA) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
if (value === ocrB) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
}
}
if (hasOCRC && value === ocrC) {
this.cpu.setInterruptFlag(this.OCFC);
if (this.compC) {
this.updateCompPin(this.compC, 'C');
}
}
}
delta--;
}
return value & MAX;
}
timerUpdated(value, prevValue) {
const { ocrA, ocrB, ocrC, hasOCRC } = this;
const overflow = prevValue > value;
if (((prevValue < ocrA || overflow) && value >= ocrA) || (prevValue < ocrA && overflow)) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
if (((prevValue < ocrB || overflow) && value >= ocrB) || (prevValue < ocrB && overflow)) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
}
}
if (hasOCRC &&
(((prevValue < ocrC || overflow) && value >= ocrC) || (prevValue < ocrC && overflow))) {
this.cpu.setInterruptFlag(this.OCFC);
if (this.compC) {
this.updateCompPin(this.compC, 'C');
}
}
}
checkForceCompare(value) {
if (this.timerMode == TimerMode.FastPWM ||
this.timerMode == TimerMode.PWMPhaseCorrect ||
this.timerMode == TimerMode.PWMPhaseFrequencyCorrect) {
// The FOCnA/FOCnB/FOCnC bits are only active when the WGMn3:0 bits specifies a non-PWM mode
return;
}
if (value & FOCA) {
this.updateCompPin(this.compA, 'A');
}
if (value & FOCB) {
this.updateCompPin(this.compB, 'B');
}
if (this.config.compPortC && value & FOCC) {
this.updateCompPin(this.compC, 'C');
}
}
updateCompPin(compValue, pinName, bottom = false) {
let newValue = PinOverrideMode.None;
const invertingMode = compValue === 3;
const isSet = this.countingUp === invertingMode;
switch (this.timerMode) {
case Normal:
case CTC:
newValue = compToOverride(compValue);
break;
case FastPWM:
if (compValue === 1) {
newValue = bottom ? PinOverrideMode.None : PinOverrideMode.Toggle;
}
else {
newValue = invertingMode !== bottom ? PinOverrideMode.Set : PinOverrideMode.Clear;
}
break;
case PWMPhaseCorrect:
case PWMPhaseFrequencyCorrect:
if (compValue === 1) {
newValue = PinOverrideMode.Toggle;
}
else {
newValue = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
}
break;
}
if (newValue !== PinOverrideMode.None) {
if (pinName === 'A') {
this.updateCompA(newValue);
}
else if (pinName === 'B') {
this.updateCompB(newValue);
}
else {
this.updateCompC(newValue);
}
}
}
updateCompA(value) {
const { compPortA, compPinA } = this.config;
const port = this.cpu.gpioByPort[compPortA];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinA, value);
}
updateCompB(value) {
const { compPortB, compPinB } = this.config;
const port = this.cpu.gpioByPort[compPortB];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinB, value);
}
updateCompC(value) {
const { compPortC, compPinC } = this.config;
const port = this.cpu.gpioByPort[compPortC];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinC, value);
}
}
+168
View File
@@ -0,0 +1,168 @@
// 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);
}
}
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export const usart0Config = {
rxCompleteInterrupt: 0x24,
dataRegisterEmptyInterrupt: 0x26,
txCompleteInterrupt: 0x28,
UCSRA: 0xc0,
UCSRB: 0xc1,
UCSRC: 0xc2,
UBRRL: 0xc4,
UBRRH: 0xc5,
UDR: 0xc6,
};
/* eslint-disable @typescript-eslint/no-unused-vars */
// Register bits:
const UCSRA_RXC = 0x80; // USART Receive Complete
const UCSRA_TXC = 0x40; // USART Transmit Complete
const UCSRA_UDRE = 0x20; // USART Data Register Empty
const UCSRA_FE = 0x10; // Frame Error
const UCSRA_DOR = 0x8; // Data OverRun
const UCSRA_UPE = 0x4; // USART Parity Error
const UCSRA_U2X = 0x2; // Double the USART Transmission Speed
const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode
const UCSRA_CFG_MASK = UCSRA_U2X;
const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable
const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable
const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable
const UCSRB_RXEN = 0x10; // Receiver Enable
const UCSRB_TXEN = 0x8; // Transmitter Enable
const UCSRB_UCSZ2 = 0x4; // Character Size 2
const UCSRB_RXB8 = 0x2; // Receive Data Bit 8
const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8
const UCSRB_CFG_MASK = UCSRB_UCSZ2 | UCSRB_RXEN | UCSRB_TXEN;
const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1
const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0
const UCSRC_UPM1 = 0x20; // Parity Mode 1
const UCSRC_UPM0 = 0x10; // Parity Mode 0
const UCSRC_USBS = 0x8; // Stop Bit Select
const UCSRC_UCSZ1 = 0x4; // Character Size 1
const UCSRC_UCSZ0 = 0x2; // Character Size 0
const UCSRC_UCPOL = 0x1; // Clock Polarity
/* eslint-enable @typescript-eslint/no-unused-vars */
const rxMasks = {
5: 0x1f,
6: 0x3f,
7: 0x7f,
8: 0xff,
9: 0xff,
};
export class AVRUSART {
constructor(cpu, config, freqHz) {
this.cpu = cpu;
this.config = config;
this.freqHz = freqHz;
this.onByteTransmit = null;
this.onLineTransmit = null;
this.onRxComplete = null;
this.onConfigurationChange = null;
this.rxBusyValue = false;
this.rxByte = 0;
this.lineBuffer = '';
// Interrupts
this.RXC = {
address: this.config.rxCompleteInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_RXC,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_RXCIE,
constant: true,
};
this.UDRE = {
address: this.config.dataRegisterEmptyInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_UDRE,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_UDRIE,
};
this.TXC = {
address: this.config.txCompleteInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_TXC,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_TXCIE,
};
this.reset();
this.cpu.writeHooks[config.UCSRA] = (value, oldValue) => {
var _a;
cpu.data[config.UCSRA] = value & (UCSRA_MPCM | UCSRA_U2X);
cpu.clearInterruptByFlag(this.TXC, value);
if ((value & UCSRA_CFG_MASK) !== (oldValue & UCSRA_CFG_MASK)) {
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
}
return true;
};
this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
var _a;
cpu.updateInterruptEnable(this.RXC, value);
cpu.updateInterruptEnable(this.UDRE, value);
cpu.updateInterruptEnable(this.TXC, value);
if (value & UCSRB_RXEN && oldValue & UCSRB_RXEN) {
cpu.clearInterrupt(this.RXC);
}
if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
// Enabling the transmission - mark UDR as empty
cpu.setInterruptFlag(this.UDRE);
}
cpu.data[config.UCSRB] = value;
if ((value & UCSRB_CFG_MASK) !== (oldValue & UCSRB_CFG_MASK)) {
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
}
return true;
};
this.cpu.writeHooks[config.UCSRC] = (value) => {
var _a;
cpu.data[config.UCSRC] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
this.cpu.readHooks[config.UDR] = () => {
var _a;
const mask = (_a = rxMasks[this.bitsPerChar]) !== null && _a !== void 0 ? _a : 0xff;
const result = this.rxByte & mask;
this.rxByte = 0;
this.cpu.clearInterrupt(this.RXC);
return result;
};
this.cpu.writeHooks[config.UDR] = (value) => {
if (this.onByteTransmit) {
this.onByteTransmit(value);
}
if (this.onLineTransmit) {
const ch = String.fromCharCode(value);
if (ch === '\n') {
this.onLineTransmit(this.lineBuffer);
this.lineBuffer = '';
}
else {
this.lineBuffer += ch;
}
}
this.cpu.addClockEvent(() => {
cpu.setInterruptFlag(this.UDRE);
cpu.setInterruptFlag(this.TXC);
}, this.cyclesPerChar);
this.cpu.clearInterrupt(this.TXC);
this.cpu.clearInterrupt(this.UDRE);
};
this.cpu.writeHooks[config.UBRRH] = (value) => {
var _a;
this.cpu.data[config.UBRRH] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
this.cpu.writeHooks[config.UBRRL] = (value) => {
var _a;
this.cpu.data[config.UBRRL] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
}
reset() {
this.cpu.data[this.config.UCSRA] = UCSRA_UDRE;
this.cpu.data[this.config.UCSRB] = 0;
this.cpu.data[this.config.UCSRC] = UCSRC_UCSZ1 | UCSRC_UCSZ0; // default: 8 bits per byte
this.rxBusyValue = false;
this.rxByte = 0;
this.lineBuffer = '';
}
get rxBusy() {
return this.rxBusyValue;
}
writeByte(value, immediate = false) {
var _a;
const { cpu } = this;
if (this.rxBusyValue || !this.rxEnable) {
return false;
}
if (immediate) {
this.rxByte = value;
cpu.setInterruptFlag(this.RXC);
(_a = this.onRxComplete) === null || _a === void 0 ? void 0 : _a.call(this);
}
else {
this.rxBusyValue = true;
cpu.addClockEvent(() => {
this.rxBusyValue = false;
this.writeByte(value, true);
}, this.cyclesPerChar);
return true;
}
}
get cyclesPerChar() {
const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
return (this.UBRR + 1) * this.multiplier * symbolsPerChar;
}
get UBRR() {
const { UBRRH, UBRRL } = this.config;
return (this.cpu.data[UBRRH] << 8) | this.cpu.data[UBRRL];
}
get multiplier() {
return this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16;
}
get rxEnable() {
return !!(this.cpu.data[this.config.UCSRB] & UCSRB_RXEN);
}
get txEnable() {
return !!(this.cpu.data[this.config.UCSRB] & UCSRB_TXEN);
}
get baudRate() {
return Math.floor(this.freqHz / (this.multiplier * (1 + this.UBRR)));
}
get bitsPerChar() {
const ucsz = ((this.cpu.data[this.config.UCSRC] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) |
(this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2);
switch (ucsz) {
case 0:
return 5;
case 1:
return 6;
case 2:
return 7;
case 3:
return 8;
default: // 4..6 are reserved
case 7:
return 9;
}
}
get stopBits() {
return this.cpu.data[this.config.UCSRC] & UCSRC_USBS ? 2 : 1;
}
get parityEnabled() {
return this.cpu.data[this.config.UCSRC] & UCSRC_UPM1 ? true : false;
}
get parityOdd() {
return this.cpu.data[this.config.UCSRC] & UCSRC_UPM0 ? true : false;
}
}
+118
View File
@@ -0,0 +1,118 @@
// 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;
}
};
}
}
+110
View File
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
// Register bits:
const MCUSR_WDRF = 0x8; // Watchdog System Reset Flag
const WDTCSR_WDIF = 0x80;
const WDTCSR_WDIE = 0x40;
const WDTCSR_WDP3 = 0x20;
const WDTCSR_WDCE = 0x10; // Watchdog Change Enable
const WDTCSR_WDE = 0x8;
const WDTCSR_WDP2 = 0x4;
const WDTCSR_WDP1 = 0x2;
const WDTCSR_WDP0 = 0x1;
const WDTCSR_WDP210 = WDTCSR_WDP2 | WDTCSR_WDP1 | WDTCSR_WDP0;
const WDTCSR_PROTECT_MASK = WDTCSR_WDE | WDTCSR_WDP3 | WDTCSR_WDP210;
export const watchdogConfig = {
watchdogInterrupt: 0x0c,
MCUSR: 0x54,
WDTCSR: 0x60,
};
export class AVRWatchdog {
constructor(cpu, config, clock) {
this.cpu = cpu;
this.config = config;
this.clock = clock;
this.clockFrequency = 128000;
/**
* Used to keep track on the last write to WDCE. Once written, the WDE/WDP* bits can be changed.
*/
this.changeEnabledCycles = 0;
this.watchdogTimeout = 0;
this.enabledValue = false;
this.scheduled = false;
// Interrupts
this.Watchdog = {
address: this.config.watchdogInterrupt,
flagRegister: this.config.WDTCSR,
flagMask: WDTCSR_WDIF,
enableRegister: this.config.WDTCSR,
enableMask: WDTCSR_WDIE,
};
this.checkWatchdog = () => {
if (this.enabled && this.cpu.cycles >= this.watchdogTimeout) {
// Watchdog timed out!
const wdtcsr = this.cpu.data[this.config.WDTCSR];
if (wdtcsr & WDTCSR_WDIE) {
this.cpu.setInterruptFlag(this.Watchdog);
}
if (wdtcsr & WDTCSR_WDE) {
if (wdtcsr & WDTCSR_WDIE) {
this.cpu.data[this.config.WDTCSR] &= ~WDTCSR_WDIE;
}
else {
this.cpu.reset();
this.scheduled = false;
this.cpu.data[this.config.MCUSR] |= MCUSR_WDRF;
return;
}
}
this.resetWatchdog();
}
if (this.enabled) {
this.scheduled = true;
this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
}
else {
this.scheduled = false;
}
};
const { WDTCSR } = config;
this.cpu.onWatchdogReset = () => {
this.resetWatchdog();
};
cpu.writeHooks[WDTCSR] = (value, oldValue) => {
if (value & WDTCSR_WDCE && value & WDTCSR_WDE) {
this.changeEnabledCycles = this.cpu.cycles + 4;
value = value & ~WDTCSR_PROTECT_MASK;
}
else {
if (this.cpu.cycles >= this.changeEnabledCycles) {
value = (value & ~WDTCSR_PROTECT_MASK) | (oldValue & WDTCSR_PROTECT_MASK);
}
this.enabledValue = !!(value & WDTCSR_WDE || value & WDTCSR_WDIE);
this.cpu.data[WDTCSR] = value;
}
if (this.enabled) {
this.resetWatchdog();
}
if (this.enabled && !this.scheduled) {
this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
}
this.cpu.clearInterruptByFlag(this.Watchdog, value);
return true;
};
}
resetWatchdog() {
const cycles = Math.floor((this.clock.frequency / this.clockFrequency) * this.prescaler);
this.watchdogTimeout = this.cpu.cycles + cycles;
}
get enabled() {
return this.enabledValue;
}
/**
* The base clock frequency is 128KHz. Thus, a prescaler of 2048 gives 16ms timeout.
*/
get prescaler() {
const wdtcsr = this.cpu.data[this.config.WDTCSR];
const value = ((wdtcsr & WDTCSR_WDP3) >> 2) | (wdtcsr & WDTCSR_WDP210);
return 2048 << value;
}
}