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

369 lines
12 KiB
JavaScript

import { IRQ } from '../irq.js';
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
import { DREQChannel } from './dma.js';
import { BasePeripheral } from './peripheral.js';
/** Control and status register */
const CHn_CSR = 0x00;
/**
* INT and FRAC form a fixed-point fractional number.
* Counting rate is system clock frequency divided by this number.
* Fractional division uses simple 1st-order sigma-delta.
*/
const CHn_DIV = 0x04;
/** Direct access to the PWM counter */
const CHn_CTR = 0x08;
/** Counter compare values */
const CHn_CC = 0x0c;
/** Counter wrap value */
const CHn_TOP = 0x10;
/**
* This register aliases the CSR_EN bits for all channels.
* Writing to this register allows multiple channels to be enabled
* or disabled simultaneously, so they can run in perfect sync.
* For each channel, there is only one physical EN register bit,
* which can be accessed through here or CHx_CSR.
*/
const EN = 0xa0;
/** Raw Interrupts */
const INTR = 0xa4;
/** Interrupt Enable */
const INTE = 0xa8;
/** Interrupt Force */
const INTF = 0xac;
/** Interrupt status after masking & forcing */
const INTS = 0xb0;
const INT_MASK = 0xff;
/* CHn_CSR bits */
const CSR_PH_ADV = 1 << 7;
const CSR_PH_RET = 1 << 6;
const CSR_DIVMODE_SHIFT = 4;
const CSR_DIVMODE_MASK = 0x3;
const CSR_B_INV = 1 << 3;
const CSR_A_INV = 1 << 2;
const CSR_PH_CORRECT = 1 << 1;
const CSR_EN = 1 << 0;
var PWMDivMode;
(function (PWMDivMode) {
PWMDivMode[PWMDivMode["FreeRunning"] = 0] = "FreeRunning";
PWMDivMode[PWMDivMode["BGated"] = 1] = "BGated";
PWMDivMode[PWMDivMode["BRisingEdge"] = 2] = "BRisingEdge";
PWMDivMode[PWMDivMode["BFallingEdge"] = 3] = "BFallingEdge";
})(PWMDivMode || (PWMDivMode = {}));
class PWMChannel {
constructor(pwm, clock, index) {
this.pwm = pwm;
this.clock = clock;
this.index = index;
this.timer = new Timer32(this.clock, this.pwm.clockFreq);
this.alarmA = new Timer32PeriodicAlarm(this.timer, () => {
this.setA(false);
});
this.alarmB = new Timer32PeriodicAlarm(this.timer, () => {
this.setB(false);
});
this.alarmBottom = new Timer32PeriodicAlarm(this.timer, () => this.wrap());
this.csr = 0;
this.div = 0;
this.cc = 0;
this.top = 0;
this.lastBValue = false;
this.countingUp = true;
this.ccUpdated = false;
this.topUpdated = false;
this.tickCounter = 0;
this.divMode = PWMDivMode.FreeRunning;
// GPIO pin indices: Table 525. Mapping of PWM channels to GPIO pins on RP2040
this.pinA1 = this.index * 2;
this.pinB1 = this.index * 2 + 1;
this.pinA2 = this.index < 7 ? 16 + this.index * 2 : -1;
this.pinB2 = this.index < 7 ? 16 + this.index * 2 + 1 : -1;
this.alarmA.enable = true;
this.alarmB.enable = true;
this.alarmBottom.enable = true;
}
readRegister(offset) {
switch (offset) {
case CHn_CSR:
return this.csr;
case CHn_DIV:
return this.div;
case CHn_CTR:
return this.timer.counter;
case CHn_CC:
return this.cc;
case CHn_TOP:
return this.top;
}
/* Shouldn't get here */
return 0;
}
writeRegister(offset, value) {
switch (offset) {
case CHn_CSR:
if (value & CSR_EN && !(this.csr & CSR_EN)) {
this.updateDoubleBuffered();
}
this.csr = value & ~(CSR_PH_ADV | CSR_PH_RET);
if (this.csr & CSR_PH_ADV) {
this.timer.advance(1);
}
if (this.csr & CSR_PH_RET) {
this.timer.advance(-1);
}
this.divMode = (this.csr >> CSR_DIVMODE_SHIFT) & CSR_DIVMODE_MASK;
this.setBDirection(this.divMode === PWMDivMode.FreeRunning);
this.updateEnable();
this.lastBValue = this.gpioBValue;
this.timer.mode = value & CSR_PH_CORRECT ? TimerMode.ZigZag : TimerMode.Increment;
break;
case CHn_DIV: {
this.div = value & 1048575;
const intValue = (value >> 4) & 0xff;
const fracValue = value & 0xf;
this.timer.prescaler = (intValue ? intValue : 256) + fracValue / 16;
break;
}
case CHn_CTR:
this.timer.set(value & 0xffff);
break;
case CHn_CC:
this.cc = value;
this.ccUpdated = true;
break;
case CHn_TOP:
this.top = value & 0xffff;
this.topUpdated = true;
break;
}
}
reset() {
this.writeRegister(CHn_CSR, 0);
this.writeRegister(CHn_DIV, 0x01 << 4);
this.writeRegister(CHn_CTR, 0);
this.writeRegister(CHn_CC, 0);
this.writeRegister(CHn_TOP, 0xffff);
this.countingUp = true;
this.timer.enable = false;
this.timer.reset();
}
updateDoubleBuffered() {
if (this.ccUpdated) {
this.alarmB.target = this.cc >>> 16;
this.alarmA.target = this.cc & 0xffff;
this.ccUpdated = false;
}
if (this.topUpdated) {
this.timer.top = this.top;
this.topUpdated = false;
}
}
wrap() {
this.pwm.channelInterrupt(this.index);
this.updateDoubleBuffered();
if (!(this.csr & CSR_PH_CORRECT)) {
this.setA(this.alarmA.target > 0);
this.setB(this.alarmB.target > 0);
}
}
setA(value) {
if (this.csr & CSR_A_INV) {
value = !value;
}
this.pwm.gpioSet(this.pinA1, value);
if (this.pinA2 >= 0) {
this.pwm.gpioSet(this.pinA2, value);
}
}
setB(value) {
if (this.csr & CSR_B_INV) {
value = !value;
}
this.pwm.gpioSet(this.pinB1, value);
if (this.pinB2 >= 0) {
this.pwm.gpioSet(this.pinB2, value);
}
}
get gpioBValue() {
return (this.pwm.gpioRead(this.pinB1) || (this.pinB2 > 0 ? this.pwm.gpioRead(this.pinB2) : false));
}
setBDirection(value) {
this.pwm.gpioSetDir(this.pinB1, value);
if (this.pinB2 >= 0) {
this.pwm.gpioSetDir(this.pinB2, value);
}
}
gpioBChanged() {
const value = this.gpioBValue;
if (value === this.lastBValue) {
return;
}
this.lastBValue = value;
switch (this.divMode) {
case PWMDivMode.BGated:
this.updateEnable();
break;
case PWMDivMode.BRisingEdge:
if (value) {
this.tickCounter++;
}
break;
case PWMDivMode.BFallingEdge:
if (!value) {
this.tickCounter++;
}
break;
}
if (this.tickCounter >= this.timer.prescaler) {
this.timer.advance(1);
this.tickCounter -= this.timer.prescaler;
}
}
updateEnable() {
const { csr, divMode } = this;
const enable = !!(csr & CSR_EN);
this.timer.enable =
enable &&
(divMode === PWMDivMode.FreeRunning || (divMode === PWMDivMode.BGated && this.gpioBValue));
}
set en(value) {
if (value && !(this.csr & CSR_EN)) {
this.updateDoubleBuffered();
}
if (value) {
this.csr |= CSR_EN;
}
else {
this.csr &= ~CSR_EN;
}
this.updateEnable();
}
}
export class RPPWM extends BasePeripheral {
constructor() {
super(...arguments);
this.channels = [
new PWMChannel(this, this.rp2040.clock, 0),
new PWMChannel(this, this.rp2040.clock, 1),
new PWMChannel(this, this.rp2040.clock, 2),
new PWMChannel(this, this.rp2040.clock, 3),
new PWMChannel(this, this.rp2040.clock, 4),
new PWMChannel(this, this.rp2040.clock, 5),
new PWMChannel(this, this.rp2040.clock, 6),
new PWMChannel(this, this.rp2040.clock, 7),
];
this.intRaw = 0;
this.intEnable = 0;
this.intForce = 0;
this.gpioValue = 0;
this.gpioDirection = 0;
}
get intStatus() {
return (this.intRaw & this.intEnable) | this.intForce;
}
readUint32(offset) {
if (offset < EN) {
const channel = Math.floor(offset / 0x14);
return this.channels[channel].readRegister(offset % 0x14);
}
switch (offset) {
case EN:
return ((this.channels[7].en << 7) |
(this.channels[6].en << 6) |
(this.channels[5].en << 5) |
(this.channels[4].en << 4) |
(this.channels[3].en << 3) |
(this.channels[2].en << 2) |
(this.channels[1].en << 1) |
(this.channels[0].en << 0));
case INTR:
return this.intRaw;
case INTE:
return this.intEnable;
case INTF:
return this.intForce;
case INTS:
return this.intStatus;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
if (offset < EN) {
const channel = Math.floor(offset / 0x14);
return this.channels[channel].writeRegister(offset % 0x14, value);
}
switch (offset) {
case EN:
this.channels[7].en = value & (1 << 7);
this.channels[6].en = value & (1 << 6);
this.channels[5].en = value & (1 << 5);
this.channels[4].en = value & (1 << 4);
this.channels[3].en = value & (1 << 3);
this.channels[2].en = value & (1 << 2);
this.channels[1].en = value & (1 << 1);
this.channels[0].en = value & (1 << 0);
break;
case INTR:
this.intRaw &= ~(value & INT_MASK);
this.checkInterrupts();
break;
case INTE:
this.intEnable = value & INT_MASK;
this.checkInterrupts();
break;
case INTF:
this.intForce = value & INT_MASK;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
get clockFreq() {
return this.rp2040.clkSys;
}
channelInterrupt(index) {
this.intRaw |= 1 << index;
this.checkInterrupts();
// We also set the DMA Request (DREQ) for the channel
this.rp2040.dma.setDREQ(DREQChannel.DREQ_PWM_WRAP0 + index);
}
checkInterrupts() {
this.rp2040.setInterrupt(IRQ.PWM_WRAP, !!this.intStatus);
}
gpioSet(index, value) {
const bit = 1 << index;
const newGpioValue = value ? this.gpioValue | bit : this.gpioValue & ~bit;
if (this.gpioValue != newGpioValue) {
this.gpioValue = newGpioValue;
this.rp2040.gpio[index].checkForUpdates();
}
}
gpioSetDir(index, output) {
const bit = 1 << index;
const newGpioDirection = output ? this.gpioDirection | bit : this.gpioDirection & ~bit;
if (this.gpioDirection != newGpioDirection) {
this.gpioDirection = newGpioDirection;
this.rp2040.gpio[index].checkForUpdates();
}
}
gpioRead(index) {
return this.rp2040.gpio[index].inputValue;
}
gpioOnInput(index) {
if (this.gpioDirection && 1 << index) {
return;
}
for (const channel of this.channels) {
if (channel.pinB1 === index || channel.pinB2 === index) {
channel.gpioBChanged();
}
}
}
reset() {
this.gpioDirection = 0xffffffff;
for (const channel of this.channels) {
channel.reset();
}
}
}