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:
+212
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user