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:
Generated
+63
@@ -12,11 +12,13 @@
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"avr8js": "^0.21.0",
|
||||
"better-sqlite3": "^12.9.0",
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.6",
|
||||
"croner": "^10.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"esptool-js": "^0.4.1",
|
||||
"express": "^5.2.1",
|
||||
"imapflow": "^1.3.3",
|
||||
"mailparser": "^3.9.8",
|
||||
@@ -26,6 +28,7 @@
|
||||
"ollama": "^0.6.3",
|
||||
"playwright": "^1.59.1",
|
||||
"pptxgenjs": "^4.0.1",
|
||||
"rp2040js": "^1.3.3",
|
||||
"tesseract.js": "^7.0.0",
|
||||
"ws": "^8.20.0"
|
||||
},
|
||||
@@ -714,6 +717,15 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/avr8js": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/avr8js/-/avr8js-0.21.0.tgz",
|
||||
"integrity": "sha512-kjFzxorERow5YFFV25ElhscG57TJqpT6M0m5GDDbXiMYoBmG6K99MQcq6AU1Ly4L+uB1g1o79dMmBNb5O+toxQ==",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0",
|
||||
"npm": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -1222,6 +1234,44 @@
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esptool-js": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/esptool-js/-/esptool-js-0.4.1.tgz",
|
||||
"integrity": "sha512-JhPHyjBncwnZDmOWxzjCQV5e6m1qP9kXPsZy5Zn/cu4tBOfPL8McncIzNVCzmLn7Jvgi06Jg09OK2HrkyMSboA==",
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"pako": "^2.1.0",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/esptool-js/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/esptool-js/node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
@@ -2354,6 +2404,14 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/rp2040js": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rp2040js/-/rp2040js-1.3.3.tgz",
|
||||
"integrity": "sha512-6uE6/Ht8L654aF3fB8HS6ICmywzEq1KN71XZq45RR0BUs9SUitPoy+mySoLEHPso6mu2695RCS1hKnchRlGhMA==",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -2736,6 +2794,11 @@
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
|
||||
@@ -40,11 +40,13 @@
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"avr8js": "^0.21.0",
|
||||
"better-sqlite3": "^12.9.0",
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.6",
|
||||
"croner": "^10.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"esptool-js": "^0.4.1",
|
||||
"express": "^5.2.1",
|
||||
"imapflow": "^1.3.3",
|
||||
"mailparser": "^3.9.8",
|
||||
@@ -54,6 +56,7 @@
|
||||
"ollama": "^0.6.3",
|
||||
"playwright": "^1.59.1",
|
||||
"pptxgenjs": "^4.0.1",
|
||||
"rp2040js": "^1.3.3",
|
||||
"tesseract.js": "^7.0.0",
|
||||
"ws": "^8.20.0"
|
||||
},
|
||||
|
||||
@@ -521,13 +521,8 @@ app.post('/api/arduino/compile', async (req, res) => {
|
||||
const fqbnVendor = fqbn.split(':')[0] || '';
|
||||
const fqbnArch = fqbn.split(':')[1] || '';
|
||||
|
||||
// RP2040 브라우저 에뮬레이터: Serial(USB CDC)을 Serial1(UART0)으로 리다이렉트
|
||||
// USB CDC 에뮬레이션은 rp2040js에서 실용적으로 지원되지 않으므로
|
||||
// UART0을 통해 시리얼 출력을 받는다.
|
||||
const isRP2040Arch = fqbnArch === 'rp2040' || fqbnArch === 'mbed_rp2040';
|
||||
const sketchCode = isRP2040Arch
|
||||
? `// [에뮬레이터] Serial → Serial1(UART0) 자동 리다이렉트\n#define Serial Serial1\n\n${code}`
|
||||
: code;
|
||||
// RP2040 브라우저 에뮬레이터: USB CDC를 그대로 사용 (Serial → UART0 우회 제거)
|
||||
const sketchCode = code;
|
||||
fs.writeFileSync(path.join(sketchDir, `${sketchName}.ino`), sketchCode);
|
||||
// AVR 계열만 .hex 생성 (esp8266은 xtensa → .bin)
|
||||
const producesHex = fqbnArch === 'avr';
|
||||
|
||||
@@ -9319,6 +9319,12 @@ app.get('/api/admin/context-viewer', async (req, res) => {
|
||||
res.json({ sessions: withOnline });
|
||||
});
|
||||
|
||||
app.use('/vendor', express.static(path.join(webUiPath, 'vendor'), {
|
||||
setHeaders: (res, filePath) => {
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
if (filePath.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript');
|
||||
}
|
||||
}));
|
||||
app.use(express.static(webUiPath, { setHeaders: (res) => { res.setHeader('Cache-Control', 'no-cache'); } }));
|
||||
|
||||
// Serve code directory files for HTML preview (window.open)
|
||||
|
||||
+235
-54
@@ -310,9 +310,9 @@
|
||||
/* 속도 슬라이더 */
|
||||
#speed-range { width:70px; height:4px; accent-color:var(--accent); vertical-align:middle; cursor:pointer; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/clike/clike.min.js"></script>
|
||||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||||
<script src="/vendor/codemirror/mode/clike/clike.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -792,7 +792,7 @@ let CPU, AVRIOPort, AVRUSART, AVRTimer, AVRTWI,
|
||||
timer0Config, timer1Config, timer2Config, twiConfig;
|
||||
|
||||
try {
|
||||
const mod = await import('https://esm.sh/avr8js@0.21.0');
|
||||
const mod = await import('./vendor/avr8js/index.js');
|
||||
let avrInstruction;
|
||||
({ CPU, AVRIOPort, AVRUSART, AVRTimer, AVRTWI,
|
||||
portAConfig, portBConfig, portCConfig, portDConfig,
|
||||
@@ -2117,6 +2117,54 @@ let _rp2040Sim = null;
|
||||
let _rp2040Running = false;
|
||||
let _rp2040RafId = null;
|
||||
let _rp2040UartBuf = '';
|
||||
let _rp2040PinStates = {};
|
||||
let _rp2040UartDec = null;
|
||||
let _rp2040BootromU32 = null;
|
||||
let _rp2040Uf2Bytes = null;
|
||||
let _rp2040Fname = 'sketch.ino';
|
||||
let _rp2040BootTimeoutId = null;
|
||||
let _origConsoleWarn = null;
|
||||
const _RP2040_WARN_FILTER = /Write to invalid SIO address|Read from invalid SIO address|2sio\.js/i;
|
||||
const _PICO_LEFT_PINS = [null,null,null,null,null,null,28,null,27,26,null,22,null,21,20,19,18,null,17,16];
|
||||
|
||||
function _silenceRp2040jsWarnings() {
|
||||
if (_origConsoleWarn) return;
|
||||
_origConsoleWarn = console.warn;
|
||||
console.warn = function(...args) {
|
||||
const msg = args.join(' ');
|
||||
if (_RP2040_WARN_FILTER.test(msg)) return;
|
||||
return _origConsoleWarn.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
function _restoreConsoleWarn() {
|
||||
if (_origConsoleWarn) { console.warn = _origConsoleWarn; _origConsoleWarn = null; }
|
||||
}
|
||||
const _PICO_RIGHT_PINS = [0,1,null,2,3,4,5,null,6,7,8,9,null,10,11,12,13,null,14,15];
|
||||
|
||||
function _updateRp2040Board() {
|
||||
const on25 = !!_rp2040PinStates[25];
|
||||
const toolbar = document.getElementById('rp2040-led');
|
||||
if (toolbar) toolbar.style.background = on25 ? '#4ade80' : '#1a2e1a';
|
||||
const svgLed = document.getElementById('pico-led');
|
||||
if (svgLed) svgLed.setAttribute('fill', on25 ? '#22c55e' : '#0a1f0a');
|
||||
_PICO_LEFT_PINS.forEach((g, i) => {
|
||||
if (g === null) return;
|
||||
const el = document.getElementById('rp-lp-' + i);
|
||||
if (!el) return;
|
||||
const st = _rp2040PinStates[g];
|
||||
el.setAttribute('fill', st === undefined ? '#555' : st ? '#4ade80' : '#2a3a2a');
|
||||
el.setAttribute('stroke', st ? '#86efac' : '#aaa');
|
||||
});
|
||||
_PICO_RIGHT_PINS.forEach((g, i) => {
|
||||
if (g === null) return;
|
||||
const el = document.getElementById('rp-rp-' + i);
|
||||
if (!el) return;
|
||||
const st = _rp2040PinStates[g];
|
||||
el.setAttribute('fill', st === undefined ? '#555' : st ? '#4ade80' : '#2a3a2a');
|
||||
el.setAttribute('stroke', st ? '#86efac' : '#aaa');
|
||||
});
|
||||
}
|
||||
let _avrBoardType = 'board_uno';
|
||||
let _esp8266SessionId = null;
|
||||
let _esp8266EvtSource = null;
|
||||
@@ -3348,9 +3396,9 @@ window.stopEmulator = () => {
|
||||
};
|
||||
|
||||
// ── RP2040 emulation ─────────────────────────────────────────────────────────
|
||||
const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 200" xmlns="http://www.w3.org/2000/svg">
|
||||
const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 215" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- PCB -->
|
||||
<rect x="1" y="1" width="88" height="198" rx="5" fill="#1a5c2a" stroke="#3a9a4a" stroke-width="1.2"/>
|
||||
<rect x="1" y="1" width="88" height="213" rx="5" fill="#1a5c2a" stroke="#3a9a4a" stroke-width="1.2"/>
|
||||
<!-- USB Micro-B -->
|
||||
<rect x="31" y="0" width="28" height="10" rx="2" fill="#aaa" stroke="#ccc" stroke-width=".5"/>
|
||||
<rect x="34" y="1.5" width="22" height="6" rx="1" fill="#666"/>
|
||||
@@ -3366,33 +3414,61 @@ const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 200" xmlns="http://
|
||||
<!-- Built-in LED GPIO25 -->
|
||||
<circle id="pico-led" cx="76" cy="25" r="3.5" fill="#0a1f0a" stroke="#2a6a2a" stroke-width=".8"/>
|
||||
<text x="76" y="20" text-anchor="middle" fill="#6aaa6a" font-size="3" font-family="monospace">LED</text>
|
||||
<!-- Power LED indicator -->
|
||||
<circle id="board-pwr-led" cx="14" cy="25" r="2.5" fill="#0d2a0d" stroke="#1a5a1a" stroke-width=".6"/>
|
||||
<text x="14" y="20" text-anchor="middle" fill="#6aaa6a" font-size="3" font-family="monospace">PWR</text>
|
||||
<!-- BOOTSEL button -->
|
||||
<rect x="58" y="148" width="14" height="7" rx="1.5" fill="#2a2a2a" stroke="#666" stroke-width=".5"/>
|
||||
<text x="65" y="153.5" text-anchor="middle" fill="#aaa" font-size="3" font-family="monospace">BOOT</text>
|
||||
<rect x="50" y="148" width="14" height="7" rx="1.5" fill="#2a2a2a" stroke="#666" stroke-width=".5"/>
|
||||
<text x="57" y="153.5" text-anchor="middle" fill="#aaa" font-size="3" font-family="monospace">BOOT</text>
|
||||
<!-- Power button -->
|
||||
<g id="power-switch" onclick="togglePower()" style="cursor:pointer">
|
||||
<circle id="pw-bg" cx="20" cy="197" r="7.5" fill="#1a0808" stroke="#803030" stroke-width="1.2"/>
|
||||
<circle cx="20" cy="197" r="5.5" fill="none" id="pw-ring" stroke="#602020" stroke-width=".8"/>
|
||||
<path id="pw-arc" d="M 16.8 194.5 A 4 4 0 1 0 23.2 194.5" stroke="#c0404088" stroke-width="1.6" fill="none" stroke-linecap="round"/>
|
||||
<line id="pw-line" x1="20" y1="192.5" x2="20" y2="196.5" stroke="#c0404088" stroke-width="1.6" stroke-linecap="round"/>
|
||||
<circle cx="20" cy="197" r="7.5" fill="transparent" stroke="transparent"/>
|
||||
</g>
|
||||
<!-- Reset button -->
|
||||
<g id="reset-btn" onclick="resetEmulator()" style="cursor:pointer">
|
||||
<rect x="57" y="190" width="20" height="11" rx="2.5" fill="#1a2060" stroke="#3050b0" stroke-width=".8"/>
|
||||
<text x="67" y="197.5" text-anchor="middle" fill="#88aaff" font-size="3.5" font-family="monospace">RESET</text>
|
||||
<rect x="57" y="190" width="20" height="11" rx="2.5" fill="transparent" stroke="transparent"/>
|
||||
</g>
|
||||
<!-- Left pins (20) -->
|
||||
${Array.from({length:20},(_,i)=>{
|
||||
const y=18+i*9;
|
||||
const labels=['VBUS','VSYS','GND','3V3','3V3_EN','ADC_REF','GP28','AGND','GP27','GP26','RUN','GP22','GND','GP21','GP20','GP19','GP18','GND','GP17','GP16'];
|
||||
const gpNums=[null,null,null,null,null,null,28,null,27,26,null,22,null,21,20,19,18,null,17,16];
|
||||
const isGnd=labels[i].includes('GND')||labels[i]==='AGND';
|
||||
const is3v=labels[i].includes('3V3')||labels[i]==='VBUS'||labels[i]==='VSYS';
|
||||
const col=isGnd?'#888':is3v?'#f80':'#5cf';
|
||||
return `<rect x="1" y="${y}" width="6" height="5" rx="1" fill="#888" stroke="#aaa" stroke-width=".4"/>
|
||||
const gp=gpNums[i];
|
||||
const id=gp!==null?` id="rp-lp-${i}"`:'';
|
||||
return `<rect${id} x="1" y="${y}" width="6" height="5" rx="1" fill="#555" stroke="#aaa" stroke-width=".4"/>
|
||||
<text x="9" y="${y+4}" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`;
|
||||
}).join('')}
|
||||
<!-- Right pins (20) -->
|
||||
${Array.from({length:20},(_,i)=>{
|
||||
const y=18+i*9;
|
||||
const labels=['GP0','GP1','GND','GP2','GP3','GP4','GP5','GND','GP6','GP7','GP8','GP9','GND','GP10','GP11','GP12','GP13','GND','GP14','GP15'];
|
||||
const gpNums=[0,1,null,2,3,4,5,null,6,7,8,9,null,10,11,12,13,null,14,15];
|
||||
const isGnd=labels[i].includes('GND');
|
||||
const col=isGnd?'#888':'#5cf';
|
||||
return `<rect x="83" y="${y}" width="6" height="5" rx="1" fill="#888" stroke="#aaa" stroke-width=".4"/>
|
||||
const gp=gpNums[i];
|
||||
const id=gp!==null?` id="rp-rp-${i}"`:'';
|
||||
return `<rect${id} x="83" y="${y}" width="6" height="5" rx="1" fill="#555" stroke="#aaa" stroke-width=".4"/>
|
||||
<text x="81" y="${y+4}" text-anchor="end" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`;
|
||||
}).join('')}
|
||||
</svg>`;
|
||||
|
||||
function _rp2040Start() {
|
||||
if (_rp2040Running || !_rp2040Sim) return;
|
||||
if (!_rp2040Sim) return;
|
||||
// 이미 실행 중이면 무시 (정지/시작 토글용)
|
||||
if (_rp2040Running) return;
|
||||
// 이전 스케줄러가 남아 있을 수 있으므로 먼저 정리
|
||||
_rp2040Stop();
|
||||
_rp2040Running = true;
|
||||
_updatePowerSwitch();
|
||||
const NS = 8; // 125 MHz → 8 ns/cycle
|
||||
const rp2040 = _rp2040Sim.rp2040;
|
||||
const clock = _rp2040Sim.clock;
|
||||
@@ -3510,6 +3586,29 @@ function _rp2040Stop() {
|
||||
clearTimeout(_rp2040RafId);
|
||||
_rp2040RafId = null;
|
||||
}
|
||||
if (_rp2040BootTimeoutId) {
|
||||
clearTimeout(_rp2040BootTimeoutId);
|
||||
_rp2040BootTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function _rp2040MarkBooted() {
|
||||
if (_rp2040Sim && !_rp2040Sim._booted) {
|
||||
_rp2040Sim._booted = true;
|
||||
if (_rp2040BootTimeoutId) { clearTimeout(_rp2040BootTimeoutId); _rp2040BootTimeoutId = null; }
|
||||
console.log('[RP2040] 부팅 완료');
|
||||
}
|
||||
}
|
||||
|
||||
function _rp2040ScheduleBootTimeout(ms = 8000) {
|
||||
if (_rp2040BootTimeoutId) clearTimeout(_rp2040BootTimeoutId);
|
||||
_rp2040BootTimeoutId = setTimeout(() => {
|
||||
_rp2040BootTimeoutId = null;
|
||||
if (_rp2040Sim && !_rp2040Sim._booted) {
|
||||
_rp2040Sim._booted = true;
|
||||
console.log('[RP2040] 부팅 완료 (타임아웃)');
|
||||
}
|
||||
}, ms);
|
||||
}
|
||||
|
||||
function _loadUF2(data, rp2040) {
|
||||
@@ -3534,12 +3633,18 @@ async function initRP2040Mode(uf2B64, fname) {
|
||||
if (footerEl) footerEl.textContent = 'rp2040js · RP2040 @ 125 MHz';
|
||||
document.getElementById('btn-compile').style.display = 'none';
|
||||
|
||||
// rp2040js의 SIO 미구현 주소 경고 콘솔 범람 방지
|
||||
_silenceRp2040jsWarnings();
|
||||
|
||||
let Simulator, USBCDC;
|
||||
try {
|
||||
({ Simulator, USBCDC } = await import('https://esm.sh/rp2040js@1.3.3'));
|
||||
({ Simulator, USBCDC } = await import('./vendor/rp2040js/index.js'));
|
||||
window._rp2040Modules = { Simulator, USBCDC };
|
||||
} catch(e) { showOverlayError('rp2040js 로드 실패\n' + e.message); return; }
|
||||
|
||||
const uf2Bytes = Uint8Array.from(atob(uf2B64), c => c.charCodeAt(0));
|
||||
_rp2040Uf2Bytes = uf2Bytes;
|
||||
_rp2040Fname = fname || 'sketch.ino';
|
||||
const sim = new Simulator();
|
||||
_rp2040Sim = sim;
|
||||
|
||||
@@ -3551,8 +3656,9 @@ async function initRP2040Mode(uf2B64, fname) {
|
||||
const bootrom = Uint8Array.from(atob(brData.bin), c => c.charCodeAt(0));
|
||||
if (bootrom.length !== 16 * 1024) throw new Error('bootrom size mismatch: ' + bootrom.length);
|
||||
const bootromU32 = new Uint32Array(bootrom.buffer, bootrom.byteOffset, 4096);
|
||||
_rp2040BootromU32 = bootromU32;
|
||||
sim.rp2040.loadBootrom(bootromU32);
|
||||
appendSerial(`[RP2040 bootrom 로드 완료: ${bootrom.length} bytes]\n`, 'sys');
|
||||
console.log(`[RP2040] bootrom 로드 완료: ${bootrom.length} bytes`);
|
||||
} catch(e) {
|
||||
appendSerial('[RP2040 bootrom 로드 실패: ' + e.message + ']\n', 'error');
|
||||
// bootrom 없이도 계속 시도 (helper 호출 시 멈출 수 있음)
|
||||
@@ -3582,16 +3688,14 @@ async function initRP2040Mode(uf2B64, fname) {
|
||||
// 4) CPU 리셋 (VTOR → flash 앱 vector 테이블에서 SP/PC 로드)
|
||||
sim.rp2040.core.reset();
|
||||
|
||||
// rp2040js 로거 경고 억제: 미구현 명령어·주변장치 warn이 콘솔에 범람하는 것을 방지
|
||||
// rp2040js 로거 억제: SEV, 미구현 명령어·주변장치 로그가 콘솔에 범람하는 것을 방지
|
||||
{
|
||||
const logger = sim.rp2040.logger;
|
||||
if (logger && typeof logger.warn === 'function') {
|
||||
logger.warn = () => {}; // 모든 warn 억제 (error/info는 유지)
|
||||
}
|
||||
// core가 별도 logger를 가질 경우도 처리
|
||||
const clogger = sim.rp2040.core?.logger;
|
||||
if (clogger && clogger !== logger && typeof clogger.warn === 'function') {
|
||||
clogger.warn = () => {};
|
||||
const noop = () => {};
|
||||
for (const logger of [sim.rp2040.logger, sim.rp2040.core?.logger]) {
|
||||
if (!logger) continue;
|
||||
for (const lvl of ['debug', 'info', 'log', 'warn']) {
|
||||
if (typeof logger[lvl] === 'function') logger[lvl] = noop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3611,18 +3715,10 @@ async function initRP2040Mode(uf2B64, fname) {
|
||||
};
|
||||
}
|
||||
|
||||
// Serial 출력 발생 시 부팅 완료로 전환 (setTimeout→RAF 스케줄러 전환)
|
||||
function _markBooted() {
|
||||
if (_rp2040Sim && !_rp2040Sim._booted) {
|
||||
_rp2040Sim._booted = true;
|
||||
appendSerial('[부팅 완료]\n', 'sys');
|
||||
}
|
||||
}
|
||||
|
||||
// UART0 → Serial1 (UTF-8 스트리밍 디코딩)
|
||||
const _rp2040UartDec = new TextDecoder('utf-8', { fatal: false });
|
||||
_rp2040UartDec = new TextDecoder('utf-8', { fatal: false });
|
||||
sim.rp2040.uart[0].onByte = (value) => {
|
||||
_markBooted();
|
||||
_rp2040MarkBooted();
|
||||
const decoded = _rp2040UartDec.decode(new Uint8Array([value]), { stream: true });
|
||||
if (decoded) appendSerial(decoded);
|
||||
};
|
||||
@@ -3630,48 +3726,132 @@ async function initRP2040Mode(uf2B64, fname) {
|
||||
// USB CDC → Serial (default Arduino-Pico Serial)
|
||||
try {
|
||||
const cdc = new USBCDC(sim.rp2040.usbCtrl);
|
||||
window._rp2040Cdc = cdc;
|
||||
const dec = new TextDecoder();
|
||||
cdc.onSerialData = (buf) => { _markBooted(); appendSerial(dec.decode(buf)); };
|
||||
cdc.onDeviceConnected = () => appendSerial('[USB CDC 연결됨]\n', 'sys');
|
||||
cdc.onSerialData = (buf) => {
|
||||
_rp2040MarkBooted();
|
||||
appendSerial(dec.decode(buf));
|
||||
};
|
||||
cdc.onDeviceConnected = () => console.log('[RP2040] USB CDC 연결됨');
|
||||
console.log('[RP2040] USB CDC 초기화 완료');
|
||||
} catch(e) {
|
||||
appendSerial('[USB CDC 초기화 실패: ' + e.message + ']\n', 'error');
|
||||
}
|
||||
|
||||
// GPIO25 = 내장 LED
|
||||
sim.rp2040.gpio[25].addListener((state) => {
|
||||
const on = state === 1;
|
||||
const toolbar = document.getElementById('rp2040-led');
|
||||
if (toolbar) toolbar.style.background = on ? '#4ade80' : '#1a2e1a';
|
||||
const svg = document.getElementById('pico-led');
|
||||
if (svg) svg.setAttribute('fill', on ? '#22c55e' : '#0a1f0a');
|
||||
});
|
||||
// 모든 GPIO 핀 리스너 등록 (GP0-GP22, GP26-GP28, GP25=LED)
|
||||
const _allPicoGpios = [...new Set([..._PICO_LEFT_PINS, ..._PICO_RIGHT_PINS, 25].filter(g => g !== null))];
|
||||
for (const g of _allPicoGpios) {
|
||||
if (!sim.rp2040.gpio[g]) continue;
|
||||
sim.rp2040.gpio[g].addListener((state) => {
|
||||
_rp2040PinStates[g] = state;
|
||||
_updateRp2040Board();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('overlay').style.display = 'none';
|
||||
const ledEl2 = document.getElementById('rp2040-led');
|
||||
if (ledEl2) ledEl2.style.display = 'inline-block';
|
||||
document.getElementById('btn-stop').disabled = false;
|
||||
setStatus('running', '실행 중');
|
||||
const resetBtn = document.getElementById('btn-reset');
|
||||
if (resetBtn) { resetBtn.disabled = false; }
|
||||
appendSerial(`[RP2040 에뮬레이터 시작]\n[Serial → USB CDC, Serial1 → UART0]\n[VTOR=${sim.rp2040.core.VTOR.toString(16)} SP=${sim.rp2040.core.SP.toString(16)} PC=${sim.rp2040.core.PC.toString(16)}]\n`, 'sys');
|
||||
console.log(`[RP2040] 에뮬레이터 시작 — VTOR=${sim.rp2040.core.VTOR.toString(16)} SP=${sim.rp2040.core.SP.toString(16)} PC=${sim.rp2040.core.PC.toString(16)}`);
|
||||
_rp2040Start();
|
||||
_rp2040ScheduleBootTimeout(8000);
|
||||
setStatus('running', '실행 중');
|
||||
}
|
||||
|
||||
window.resetEmulator = () => {
|
||||
if (_IS_RP2040) {
|
||||
if (!_rp2040Sim) return;
|
||||
if (!_rp2040Sim || !_rp2040Uf2Bytes) return;
|
||||
_rp2040Stop();
|
||||
_rp2040UartBuf = '';
|
||||
_rp2040PinStates = {};
|
||||
_updateRp2040Board();
|
||||
_updatePowerSwitch();
|
||||
appendSerial('\n--- 리셋 ---\n', 'sys');
|
||||
// 주의: rp2040.reset()은 flash.fill(0xff)를 실행하므로 앱 코드가 날아감.
|
||||
// VTOR를 다시 앱 vector table로 재설정한 뒤 core.reset()만 수행.
|
||||
// (flash offset 0x3000 우선, 없으면 0x100)
|
||||
const fv2 = new DataView(_rp2040Sim.rp2040.flash.buffer);
|
||||
const readVTOR2 = (base) => ({ sp: fv2.getUint32(base, true), pc: fv2.getUint32(base + 4, true), base });
|
||||
const cand2 = [readVTOR2(0x3000), readVTOR2(0x100)].filter(v => v.sp && v.pc && (v.pc & ~1) >= 0x10000000);
|
||||
_rp2040Sim.rp2040.core.VTOR = cand2.length ? (0x10000000 + cand2[0].base) : 0x10003000;
|
||||
_rp2040Sim.rp2040.core.reset();
|
||||
// 주변 장치(clock/timer/uart 등) 상태가 남아 있으면 멈추므로 Simulator를 새로 만듦.
|
||||
// (RP2040.reset()은 flash를 지우지만, Simulator 재생성 + UF2 재로드는 flash를 보존)
|
||||
const { Simulator } = window._rp2040Modules || {};
|
||||
_rp2040Sim = Simulator ? new Simulator() : null;
|
||||
const sim = _rp2040Sim;
|
||||
if (!sim) { appendSerial('[리셋 실패: Simulator 클래스 없음]\n', 'error'); return; }
|
||||
if (_rp2040BootromU32) {
|
||||
try { sim.rp2040.loadBootrom(_rp2040BootromU32); } catch(_) {}
|
||||
}
|
||||
_loadUF2(_rp2040Uf2Bytes, sim.rp2040);
|
||||
|
||||
// 벡터 테이블에서 SP/PC/VTOR 복원
|
||||
const fv2 = new DataView(sim.rp2040.flash.buffer);
|
||||
const readEntry = (base) => ({ sp: fv2.getUint32(base, true), pc: fv2.getUint32(base + 4, true) & ~1, base });
|
||||
const cands = [readEntry(0x3000), readEntry(0x100)].filter(v => v.sp > 0x20000000 && v.pc >= 0x10000000);
|
||||
const entry = cands[0] || readEntry(0x3000);
|
||||
sim.rp2040.core.VTOR = 0x10000000 + entry.base;
|
||||
sim.rp2040.core.SP = entry.sp;
|
||||
sim.rp2040.core.PC = entry.pc;
|
||||
|
||||
// rp2040js 로거 억제 (info/debug/warn 등 콘솔 범람 방지)
|
||||
{
|
||||
const noop = () => {};
|
||||
for (const logger of [sim.rp2040.logger, sim.rp2040.core?.logger]) {
|
||||
if (!logger) continue;
|
||||
for (const lvl of ['debug', 'info', 'log', 'warn']) {
|
||||
if (typeof logger[lvl] === 'function') logger[lvl] = noop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 예외 패치 재설정
|
||||
{
|
||||
const core = sim.rp2040.core;
|
||||
const _origException = core.exceptionEntry.bind(core);
|
||||
core.exceptionEntry = function(exceptionNumber) {
|
||||
if (exceptionNumber === 3) {
|
||||
_rp2040Running = false;
|
||||
_origException(exceptionNumber);
|
||||
appendSerial(`\n[HardFault (LR=${this.LR.toString(16)}, PC=${this.PC.toString(16)}) — 실행 중지]\n`, 'error');
|
||||
} else {
|
||||
_origException(exceptionNumber);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// UART / USB CDC 재연결
|
||||
_rp2040UartDec = new TextDecoder('utf-8', { fatal: false });
|
||||
sim.rp2040.uart[0].onByte = (value) => {
|
||||
_rp2040MarkBooted();
|
||||
const decoded = _rp2040UartDec.decode(new Uint8Array([value]), { stream: true });
|
||||
if (decoded) appendSerial(decoded);
|
||||
};
|
||||
try {
|
||||
let USBCDC;
|
||||
// eslint-disable-next-line no-undef
|
||||
({ USBCDC } = window._rp2040Modules || {});
|
||||
if (USBCDC) {
|
||||
const cdc = new USBCDC(sim.rp2040.usbCtrl);
|
||||
window._rp2040Cdc = cdc;
|
||||
const dec = new TextDecoder();
|
||||
cdc.onSerialData = (buf) => {
|
||||
_rp2040MarkBooted();
|
||||
appendSerial(dec.decode(buf));
|
||||
};
|
||||
cdc.onDeviceConnected = () => console.log('[RP2040] USB CDC 연결됨');
|
||||
console.log('[RP2040] USB CDC 초기화 완료');
|
||||
}
|
||||
} catch(_) {}
|
||||
|
||||
// GPIO 리스너 재등록
|
||||
const _allPicoGpios = [...new Set([..._PICO_LEFT_PINS, ..._PICO_RIGHT_PINS, 25].filter(g => g !== null))];
|
||||
for (const g of _allPicoGpios) {
|
||||
if (!sim.rp2040.gpio[g]) continue;
|
||||
sim.rp2040.gpio[g].addListener((state) => {
|
||||
_rp2040PinStates[g] = state;
|
||||
_updateRp2040Board();
|
||||
});
|
||||
}
|
||||
|
||||
_rp2040Start();
|
||||
_rp2040ScheduleBootTimeout(8000);
|
||||
setStatus('running', '실행 중');
|
||||
return;
|
||||
}
|
||||
if (!hexData) return;
|
||||
@@ -3704,7 +3884,7 @@ function _updatePowerSwitch() {
|
||||
const ring = document.getElementById('pw-ring');
|
||||
const pwrLed = document.getElementById('board-pwr-led');
|
||||
if (!arc) return;
|
||||
const isRunning = running;
|
||||
const isRunning = _IS_RP2040 ? _rp2040Running : running;
|
||||
if (isRunning) {
|
||||
arc.setAttribute('stroke','#22c55e');
|
||||
line.setAttribute('stroke','#22c55e');
|
||||
@@ -3750,6 +3930,7 @@ window.togglePower = () => {
|
||||
if (_IS_RP2040 && _rp2040Sim) {
|
||||
if (_rp2040Running) { _rp2040Stop(); setStatus('stopped', '정지'); }
|
||||
else { _rp2040Start(); setStatus('running', '실행 중'); }
|
||||
_updatePowerSwitch();
|
||||
return;
|
||||
}
|
||||
if (running) { stopEmulator(); return; }
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||||
<link rel="stylesheet" href="/vendor/xterm.css">
|
||||
<script src="/vendor/xterm.js"></script>
|
||||
<script src="/vendor/addon-fit.js"></script>
|
||||
|
||||
+13
-13
@@ -9,23 +9,23 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="apple-touch-icon" sizes="128x128" href="cherry_logo.png">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/markdown/markdown.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js"></script>
|
||||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||||
<script src="/vendor/codemirror/mode/xml/xml.js"></script>
|
||||
<script src="/vendor/codemirror/mode/css/css.js"></script>
|
||||
<script src="/vendor/codemirror/mode/javascript/javascript.js"></script>
|
||||
<script src="/vendor/codemirror/mode/htmlmixed/htmlmixed.js"></script>
|
||||
<script src="/vendor/codemirror/mode/python/python.js"></script>
|
||||
<script src="/vendor/codemirror/mode/markdown/markdown.js"></script>
|
||||
<link rel="stylesheet" href="/vendor/xterm.css">
|
||||
<script src="/vendor/xterm.js"></script>
|
||||
<script src="/vendor/addon-fit.js"></script>
|
||||
<script src="/vendor/addon-web-links.js"></script>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tonejs/midi@2.0.28/build/Midi.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/combine/npm/tone@14,npm/@magenta/music@1.23.1/es6/core.js,npm/html-midi-player@1.6.0" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||
<script src="/vendor/vs/loader.js"></script>
|
||||
<script>
|
||||
// Hide AMD from deferred UMD libs (tone, @magenta/music, html-midi-player) so they
|
||||
// don't call Monaco's anonymous define() — that would cause "Can only have one
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
|
||||
//# sourceMappingURL=addon-fit.js.map
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(self,(()=>(()=>{"use strict";var e={6:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n<e.length;++n){e.getCell(n,i);const s=i.getChars();if(i.getWidth()&&(o-=s.length||1,n===e.length-1&&""===s)){const e=r.getLine(t+1);e&&e.isWrapped&&(e.getCell(0,i),2===i.getWidth()&&(o+=1))}if(o<0)return[t,n]}t++,s=0}return[t,s]}}t.LinkComputer=o}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}var o={};return(()=>{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(6),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})()));
|
||||
//# sourceMappingURL=addon-web-links.js.map
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
import { avrInterrupt } from './interrupt.js';
|
||||
const registerSpace = 0x100;
|
||||
const MAX_INTERRUPTS = 128; // Enough for ATMega2560
|
||||
export class CPU {
|
||||
constructor(progMem, sramBytes = 8192) {
|
||||
this.progMem = progMem;
|
||||
this.sramBytes = sramBytes;
|
||||
this.data = new Uint8Array(this.sramBytes + registerSpace);
|
||||
this.data16 = new Uint16Array(this.data.buffer);
|
||||
this.dataView = new DataView(this.data.buffer);
|
||||
this.progBytes = new Uint8Array(this.progMem.buffer);
|
||||
this.readHooks = [];
|
||||
this.writeHooks = [];
|
||||
this.pendingInterrupts = new Array(MAX_INTERRUPTS);
|
||||
this.nextClockEvent = null;
|
||||
this.clockEventPool = []; // helps avoid garbage collection
|
||||
/**
|
||||
* Whether the program counter (PC) can address 22 bits (the default is 16)
|
||||
*/
|
||||
this.pc22Bits = this.progBytes.length > 0x20000;
|
||||
this.gpioPorts = new Set();
|
||||
this.gpioByPort = [];
|
||||
/**
|
||||
* This function is called by the WDR instruction. The Watchdog peripheral attaches
|
||||
* to it to listen for WDR (watchdog reset).
|
||||
*/
|
||||
this.onWatchdogReset = () => {
|
||||
/* empty by default */
|
||||
};
|
||||
/**
|
||||
* Program counter
|
||||
*/
|
||||
this.pc = 0;
|
||||
/**
|
||||
* Clock cycle counter
|
||||
*/
|
||||
this.cycles = 0;
|
||||
this.nextInterrupt = -1;
|
||||
this.maxInterrupt = 0;
|
||||
this.reset();
|
||||
}
|
||||
reset() {
|
||||
this.SP = this.data.length - 1;
|
||||
this.pc = 0;
|
||||
this.pendingInterrupts.fill(null);
|
||||
this.nextInterrupt = -1;
|
||||
this.nextClockEvent = null;
|
||||
}
|
||||
readData(addr) {
|
||||
if (addr >= 32 && this.readHooks[addr]) {
|
||||
return this.readHooks[addr](addr);
|
||||
}
|
||||
return this.data[addr];
|
||||
}
|
||||
writeData(addr, value, mask = 0xff) {
|
||||
const hook = this.writeHooks[addr];
|
||||
if (hook) {
|
||||
if (hook(value, this.data[addr], addr, mask)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.data[addr] = value;
|
||||
}
|
||||
get SP() {
|
||||
return this.dataView.getUint16(93, true);
|
||||
}
|
||||
set SP(value) {
|
||||
this.dataView.setUint16(93, value, true);
|
||||
}
|
||||
get SREG() {
|
||||
return this.data[95];
|
||||
}
|
||||
get interruptsEnabled() {
|
||||
return this.SREG & 0x80 ? true : false;
|
||||
}
|
||||
setInterruptFlag(interrupt) {
|
||||
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
|
||||
if (interrupt.inverseFlag) {
|
||||
this.data[flagRegister] &= ~flagMask;
|
||||
}
|
||||
else {
|
||||
this.data[flagRegister] |= flagMask;
|
||||
}
|
||||
if (this.data[enableRegister] & enableMask) {
|
||||
this.queueInterrupt(interrupt);
|
||||
}
|
||||
}
|
||||
updateInterruptEnable(interrupt, registerValue) {
|
||||
const { enableMask, flagRegister, flagMask, inverseFlag } = interrupt;
|
||||
if (registerValue & enableMask) {
|
||||
const bitSet = this.data[flagRegister] & flagMask;
|
||||
if (inverseFlag ? !bitSet : bitSet) {
|
||||
this.queueInterrupt(interrupt);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.clearInterrupt(interrupt, false);
|
||||
}
|
||||
}
|
||||
queueInterrupt(interrupt) {
|
||||
const { address } = interrupt;
|
||||
this.pendingInterrupts[address] = interrupt;
|
||||
if (this.nextInterrupt === -1 || this.nextInterrupt > address) {
|
||||
this.nextInterrupt = address;
|
||||
}
|
||||
if (address > this.maxInterrupt) {
|
||||
this.maxInterrupt = address;
|
||||
}
|
||||
}
|
||||
clearInterrupt({ address, flagRegister, flagMask }, clearFlag = true) {
|
||||
if (clearFlag) {
|
||||
this.data[flagRegister] &= ~flagMask;
|
||||
}
|
||||
const { pendingInterrupts, maxInterrupt } = this;
|
||||
if (!pendingInterrupts[address]) {
|
||||
return;
|
||||
}
|
||||
pendingInterrupts[address] = null;
|
||||
if (this.nextInterrupt === address) {
|
||||
this.nextInterrupt = -1;
|
||||
for (let i = address + 1; i <= maxInterrupt; i++) {
|
||||
if (pendingInterrupts[i]) {
|
||||
this.nextInterrupt = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearInterruptByFlag(interrupt, registerValue) {
|
||||
const { flagRegister, flagMask } = interrupt;
|
||||
if (registerValue & flagMask) {
|
||||
this.data[flagRegister] &= ~flagMask;
|
||||
this.clearInterrupt(interrupt);
|
||||
}
|
||||
}
|
||||
addClockEvent(callback, cycles) {
|
||||
const { clockEventPool } = this;
|
||||
cycles = this.cycles + Math.max(1, cycles);
|
||||
const maybeEntry = clockEventPool.pop();
|
||||
const entry = maybeEntry !== null && maybeEntry !== void 0 ? maybeEntry : { cycles, callback, next: null };
|
||||
entry.cycles = cycles;
|
||||
entry.callback = callback;
|
||||
let { nextClockEvent: clockEvent } = this;
|
||||
let lastItem = null;
|
||||
while (clockEvent && clockEvent.cycles < cycles) {
|
||||
lastItem = clockEvent;
|
||||
clockEvent = clockEvent.next;
|
||||
}
|
||||
if (lastItem) {
|
||||
lastItem.next = entry;
|
||||
entry.next = clockEvent;
|
||||
}
|
||||
else {
|
||||
this.nextClockEvent = entry;
|
||||
entry.next = clockEvent;
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
updateClockEvent(callback, cycles) {
|
||||
if (this.clearClockEvent(callback)) {
|
||||
this.addClockEvent(callback, cycles);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
clearClockEvent(callback) {
|
||||
let { nextClockEvent: clockEvent } = this;
|
||||
if (!clockEvent) {
|
||||
return false;
|
||||
}
|
||||
const { clockEventPool } = this;
|
||||
let lastItem = null;
|
||||
while (clockEvent) {
|
||||
if (clockEvent.callback === callback) {
|
||||
if (lastItem) {
|
||||
lastItem.next = clockEvent.next;
|
||||
}
|
||||
else {
|
||||
this.nextClockEvent = clockEvent.next;
|
||||
}
|
||||
if (clockEventPool.length < 10) {
|
||||
clockEventPool.push(clockEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
lastItem = clockEvent;
|
||||
clockEvent = clockEvent.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
tick() {
|
||||
const { nextClockEvent } = this;
|
||||
if (nextClockEvent && nextClockEvent.cycles <= this.cycles) {
|
||||
nextClockEvent.callback();
|
||||
this.nextClockEvent = nextClockEvent.next;
|
||||
if (this.clockEventPool.length < 10) {
|
||||
this.clockEventPool.push(nextClockEvent);
|
||||
}
|
||||
}
|
||||
const { nextInterrupt } = this;
|
||||
if (this.interruptsEnabled && nextInterrupt >= 0) {
|
||||
const interrupt = this.pendingInterrupts[nextInterrupt];
|
||||
avrInterrupt(this, interrupt.address);
|
||||
if (!interrupt.constant) {
|
||||
this.clearInterrupt(interrupt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+859
@@ -0,0 +1,859 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
function isTwoWordInstruction(opcode) {
|
||||
return (
|
||||
/* LDS */
|
||||
(opcode & 0xfe0f) === 0x9000 ||
|
||||
/* STS */
|
||||
(opcode & 0xfe0f) === 0x9200 ||
|
||||
/* CALL */
|
||||
(opcode & 0xfe0e) === 0x940e ||
|
||||
/* JMP */
|
||||
(opcode & 0xfe0e) === 0x940c);
|
||||
}
|
||||
export function avrInstruction(cpu) {
|
||||
const opcode = cpu.progMem[cpu.pc];
|
||||
if ((opcode & 0xfc00) === 0x1c00) {
|
||||
/* ADC, 0001 11rd dddd rrrr */
|
||||
const d = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
const sum = d + r + (cpu.data[95] & 1);
|
||||
const R = sum & 255;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (R ^ r) & (d ^ R) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= sum & 256 ? 1 : 0;
|
||||
sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0xc00) {
|
||||
/* ADD, 0000 11rd dddd rrrr */
|
||||
const d = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
const R = (d + r) & 255;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (R ^ r) & (R ^ d) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= (d + r) & 256 ? 1 : 0;
|
||||
sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9600) {
|
||||
/* ADIW, 1001 0110 KKdd KKKK */
|
||||
const addr = 2 * ((opcode & 0x30) >> 4) + 24;
|
||||
const value = cpu.dataView.getUint16(addr, true);
|
||||
const R = (value + ((opcode & 0xf) | ((opcode & 0xc0) >> 2))) & 0xffff;
|
||||
cpu.dataView.setUint16(addr, R, true);
|
||||
let sreg = cpu.data[95] & 0xe0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 0x8000 & R ? 4 : 0;
|
||||
sreg |= ~value & R & 0x8000 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= ~R & value & 0x8000 ? 1 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x2000) {
|
||||
/* AND, 0010 00rd dddd rrrr */
|
||||
const R = cpu.data[(opcode & 0x1f0) >> 4] & cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0x7000) {
|
||||
/* ANDI, 0111 KKKK dddd KKKK */
|
||||
const R = cpu.data[((opcode & 0xf0) >> 4) + 16] & ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
|
||||
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9405) {
|
||||
/* ASR, 1001 010d dddd 0101 */
|
||||
const value = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const R = (value >>> 1) | (128 & value);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= value & 1;
|
||||
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xff8f) === 0x9488) {
|
||||
/* BCLR, 1001 0100 1sss 1000 */
|
||||
cpu.data[95] &= ~(1 << ((opcode & 0x70) >> 4));
|
||||
}
|
||||
else if ((opcode & 0xfe08) === 0xf800) {
|
||||
/* BLD, 1111 100d dddd 0bbb */
|
||||
const b = opcode & 7;
|
||||
const d = (opcode & 0x1f0) >> 4;
|
||||
cpu.data[d] = (~(1 << b) & cpu.data[d]) | (((cpu.data[95] >> 6) & 1) << b);
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0xf400) {
|
||||
/* BRBC, 1111 01kk kkkk ksss */
|
||||
if (!(cpu.data[95] & (1 << (opcode & 7)))) {
|
||||
cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
|
||||
cpu.cycles++;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0xf000) {
|
||||
/* BRBS, 1111 00kk kkkk ksss */
|
||||
if (cpu.data[95] & (1 << (opcode & 7))) {
|
||||
cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
|
||||
cpu.cycles++;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xff8f) === 0x9408) {
|
||||
/* BSET, 1001 0100 0sss 1000 */
|
||||
cpu.data[95] |= 1 << ((opcode & 0x70) >> 4);
|
||||
}
|
||||
else if ((opcode & 0xfe08) === 0xfa00) {
|
||||
/* BST, 1111 101d dddd 0bbb */
|
||||
const d = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const b = opcode & 7;
|
||||
cpu.data[95] = (cpu.data[95] & 0xbf) | ((d >> b) & 1 ? 0x40 : 0);
|
||||
}
|
||||
else if ((opcode & 0xfe0e) === 0x940e) {
|
||||
/* CALL, 1001 010k kkkk 111k kkkk kkkk kkkk kkkk */
|
||||
const k = cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13);
|
||||
const ret = cpu.pc + 2;
|
||||
const sp = cpu.dataView.getUint16(93, true);
|
||||
const { pc22Bits } = cpu;
|
||||
cpu.data[sp] = 255 & ret;
|
||||
cpu.data[sp - 1] = (ret >> 8) & 255;
|
||||
if (pc22Bits) {
|
||||
cpu.data[sp - 2] = (ret >> 16) & 255;
|
||||
}
|
||||
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
|
||||
cpu.pc = k - 1;
|
||||
cpu.cycles += pc22Bits ? 4 : 3;
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9800) {
|
||||
/* CBI, 1001 1000 AAAA Abbb */
|
||||
const A = opcode & 0xf8;
|
||||
const b = opcode & 7;
|
||||
const R = cpu.readData((A >> 3) + 32);
|
||||
const mask = 1 << b;
|
||||
cpu.writeData((A >> 3) + 32, R & ~mask, mask);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9400) {
|
||||
/* COM, 1001 010d dddd 0000 */
|
||||
const d = (opcode & 0x1f0) >> 4;
|
||||
const R = 255 - cpu.data[d];
|
||||
cpu.data[d] = R;
|
||||
let sreg = (cpu.data[95] & 0xe1) | 1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x1400) {
|
||||
/* CP, 0001 01rd dddd rrrr */
|
||||
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
const R = val1 - val2;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= 0 !== ((val1 ^ val2) & (val1 ^ R) & 128) ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= val2 > val1 ? 1 : 0;
|
||||
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x400) {
|
||||
/* CPC, 0000 01rd dddd rrrr */
|
||||
const arg1 = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const arg2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
let sreg = cpu.data[95];
|
||||
const r = arg1 - arg2 - (sreg & 1);
|
||||
sreg = (sreg & 0xc0) | (!r && (sreg >> 1) & 1 ? 2 : 0) | (arg2 + (sreg & 1) > arg1 ? 1 : 0);
|
||||
sreg |= 128 & r ? 4 : 0;
|
||||
sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0x3000) {
|
||||
/* CPI, 0011 KKKK dddd KKKK */
|
||||
const arg1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
|
||||
const arg2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
|
||||
const r = arg1 - arg2;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= r ? 0 : 2;
|
||||
sreg |= 128 & r ? 4 : 0;
|
||||
sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= arg2 > arg1 ? 1 : 0;
|
||||
sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x1000) {
|
||||
/* CPSE, 0001 00rd dddd rrrr */
|
||||
if (cpu.data[(opcode & 0x1f0) >> 4] === cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)]) {
|
||||
const nextOpcode = cpu.progMem[cpu.pc + 1];
|
||||
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
|
||||
cpu.pc += skipSize;
|
||||
cpu.cycles += skipSize;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x940a) {
|
||||
/* DEC, 1001 010d dddd 1010 */
|
||||
const value = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const R = value - 1;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= 128 === value ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if (opcode === 0x9519) {
|
||||
/* EICALL, 1001 0101 0001 1001 */
|
||||
const retAddr = cpu.pc + 1;
|
||||
const sp = cpu.dataView.getUint16(93, true);
|
||||
const eind = cpu.data[0x5c];
|
||||
cpu.data[sp] = retAddr & 255;
|
||||
cpu.data[sp - 1] = (retAddr >> 8) & 255;
|
||||
cpu.data[sp - 2] = (retAddr >> 16) & 255;
|
||||
cpu.dataView.setUint16(93, sp - 3, true);
|
||||
cpu.pc = ((eind << 16) | cpu.dataView.getUint16(30, true)) - 1;
|
||||
cpu.cycles += 3;
|
||||
}
|
||||
else if (opcode === 0x9419) {
|
||||
/* EIJMP, 1001 0100 0001 1001 */
|
||||
const eind = cpu.data[0x5c];
|
||||
cpu.pc = ((eind << 16) | cpu.dataView.getUint16(30, true)) - 1;
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if (opcode === 0x95d8) {
|
||||
/* ELPM, 1001 0101 1101 1000 */
|
||||
const rampz = cpu.data[0x5b];
|
||||
cpu.data[0] = cpu.progBytes[(rampz << 16) | cpu.dataView.getUint16(30, true)];
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9006) {
|
||||
/* ELPM(REG), 1001 000d dddd 0110 */
|
||||
const rampz = cpu.data[0x5b];
|
||||
cpu.data[(opcode & 0x1f0) >> 4] =
|
||||
cpu.progBytes[(rampz << 16) | cpu.dataView.getUint16(30, true)];
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9007) {
|
||||
/* ELPM(INC), 1001 000d dddd 0111 */
|
||||
const rampz = cpu.data[0x5b];
|
||||
const i = cpu.dataView.getUint16(30, true);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[(rampz << 16) | i];
|
||||
cpu.dataView.setUint16(30, i + 1, true);
|
||||
if (i === 0xffff) {
|
||||
cpu.data[0x5b] = (rampz + 1) % (cpu.progBytes.length >> 16);
|
||||
}
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x2400) {
|
||||
/* EOR, 0010 01rd dddd rrrr */
|
||||
const R = cpu.data[(opcode & 0x1f0) >> 4] ^ cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xff88) === 0x308) {
|
||||
/* FMUL, 0000 0011 0ddd 1rrr */
|
||||
const v1 = cpu.data[((opcode & 0x70) >> 4) + 16];
|
||||
const v2 = cpu.data[(opcode & 7) + 16];
|
||||
const R = (v1 * v2) << 1;
|
||||
cpu.dataView.setUint16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xff88) === 0x380) {
|
||||
/* FMULS, 0000 0011 1ddd 0rrr */
|
||||
const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
|
||||
const v2 = cpu.dataView.getInt8((opcode & 7) + 16);
|
||||
const R = (v1 * v2) << 1;
|
||||
cpu.dataView.setInt16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xff88) === 0x388) {
|
||||
/* FMULSU, 0000 0011 1ddd 1rrr */
|
||||
const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
|
||||
const v2 = cpu.data[(opcode & 7) + 16];
|
||||
const R = (v1 * v2) << 1;
|
||||
cpu.dataView.setInt16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 2 : 0) | ((v1 * v2) & 0x8000 ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if (opcode === 0x9509) {
|
||||
/* ICALL, 1001 0101 0000 1001 */
|
||||
const retAddr = cpu.pc + 1;
|
||||
const sp = cpu.dataView.getUint16(93, true);
|
||||
const { pc22Bits } = cpu;
|
||||
cpu.data[sp] = retAddr & 255;
|
||||
cpu.data[sp - 1] = (retAddr >> 8) & 255;
|
||||
if (pc22Bits) {
|
||||
cpu.data[sp - 2] = (retAddr >> 16) & 255;
|
||||
}
|
||||
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
|
||||
cpu.pc = cpu.dataView.getUint16(30, true) - 1;
|
||||
cpu.cycles += pc22Bits ? 3 : 2;
|
||||
}
|
||||
else if (opcode === 0x9409) {
|
||||
/* IJMP, 1001 0100 0000 1001 */
|
||||
cpu.pc = cpu.dataView.getUint16(30, true) - 1;
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xf800) === 0xb000) {
|
||||
/* IN, 1011 0AAd dddd AAAA */
|
||||
const i = cpu.readData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = i;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9403) {
|
||||
/* INC, 1001 010d dddd 0011 */
|
||||
const d = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const r = (d + 1) & 255;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = r;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= r ? 0 : 2;
|
||||
sreg |= 128 & r ? 4 : 0;
|
||||
sreg |= 127 === d ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfe0e) === 0x940c) {
|
||||
/* JMP, 1001 010k kkkk 110k kkkk kkkk kkkk kkkk */
|
||||
cpu.pc = (cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13)) - 1;
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9206) {
|
||||
/* LAC, 1001 001r rrrr 0110 */
|
||||
const r = (opcode & 0x1f0) >> 4;
|
||||
const clear = cpu.data[r];
|
||||
const value = cpu.readData(cpu.dataView.getUint16(30, true));
|
||||
cpu.writeData(cpu.dataView.getUint16(30, true), value & (255 - clear));
|
||||
cpu.data[r] = value;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9205) {
|
||||
/* LAS, 1001 001r rrrr 0101 */
|
||||
const r = (opcode & 0x1f0) >> 4;
|
||||
const set = cpu.data[r];
|
||||
const value = cpu.readData(cpu.dataView.getUint16(30, true));
|
||||
cpu.writeData(cpu.dataView.getUint16(30, true), value | set);
|
||||
cpu.data[r] = value;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9207) {
|
||||
/* LAT, 1001 001r rrrr 0111 */
|
||||
const r = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const R = cpu.readData(cpu.dataView.getUint16(30, true));
|
||||
cpu.writeData(cpu.dataView.getUint16(30, true), r ^ R);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0xe000) {
|
||||
/* LDI, 1110 KKKK dddd KKKK */
|
||||
cpu.data[((opcode & 0xf0) >> 4) + 16] = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9000) {
|
||||
/* LDS, 1001 000d dddd 0000 kkkk kkkk kkkk kkkk */
|
||||
cpu.cycles++;
|
||||
const value = cpu.readData(cpu.progMem[cpu.pc + 1]);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = value;
|
||||
cpu.pc++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x900c) {
|
||||
/* LDX, 1001 000d dddd 1100 */
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(26, true));
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x900d) {
|
||||
/* LDX(INC), 1001 000d dddd 1101 */
|
||||
const x = cpu.dataView.getUint16(26, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
|
||||
cpu.dataView.setUint16(26, x + 1, true);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x900e) {
|
||||
/* LDX(DEC), 1001 000d dddd 1110 */
|
||||
const x = cpu.dataView.getUint16(26, true) - 1;
|
||||
cpu.dataView.setUint16(26, x, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x8008) {
|
||||
/* LDY, 1000 000d dddd 1000 */
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(28, true));
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9009) {
|
||||
/* LDY(INC), 1001 000d dddd 1001 */
|
||||
const y = cpu.dataView.getUint16(28, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
|
||||
cpu.dataView.setUint16(28, y + 1, true);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x900a) {
|
||||
/* LDY(DEC), 1001 000d dddd 1010 */
|
||||
const y = cpu.dataView.getUint16(28, true) - 1;
|
||||
cpu.dataView.setUint16(28, y, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
|
||||
}
|
||||
else if ((opcode & 0xd208) === 0x8008 &&
|
||||
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
|
||||
/* LDDY, 10q0 qq0d dddd 1qqq */
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(28, true) +
|
||||
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)));
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x8000) {
|
||||
/* LDZ, 1000 000d dddd 0000 */
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(30, true));
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9001) {
|
||||
/* LDZ(INC), 1001 000d dddd 0001 */
|
||||
const z = cpu.dataView.getUint16(30, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
|
||||
cpu.dataView.setUint16(30, z + 1, true);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9002) {
|
||||
/* LDZ(DEC), 1001 000d dddd 0010 */
|
||||
const z = cpu.dataView.getUint16(30, true) - 1;
|
||||
cpu.dataView.setUint16(30, z, true);
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
|
||||
}
|
||||
else if ((opcode & 0xd208) === 0x8000 &&
|
||||
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
|
||||
/* LDDZ, 10q0 qq0d dddd 0qqq */
|
||||
cpu.cycles++;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(30, true) +
|
||||
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)));
|
||||
}
|
||||
else if (opcode === 0x95c8) {
|
||||
/* LPM, 1001 0101 1100 1000 */
|
||||
cpu.data[0] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9004) {
|
||||
/* LPM(REG), 1001 000d dddd 0100 */
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9005) {
|
||||
/* LPM(INC), 1001 000d dddd 0101 */
|
||||
const i = cpu.dataView.getUint16(30, true);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[i];
|
||||
cpu.dataView.setUint16(30, i + 1, true);
|
||||
cpu.cycles += 2;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9406) {
|
||||
/* LSR, 1001 010d dddd 0110 */
|
||||
const value = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const R = value >>> 1;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= value & 1;
|
||||
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x2c00) {
|
||||
/* MOV, 0010 11rd dddd rrrr */
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x100) {
|
||||
/* MOVW, 0000 0001 dddd rrrr */
|
||||
const r2 = 2 * (opcode & 0xf);
|
||||
const d2 = 2 * ((opcode & 0xf0) >> 4);
|
||||
cpu.data[d2] = cpu.data[r2];
|
||||
cpu.data[d2 + 1] = cpu.data[r2 + 1];
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x9c00) {
|
||||
/* MUL, 1001 11rd dddd rrrr */
|
||||
const R = cpu.data[(opcode & 0x1f0) >> 4] * cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
cpu.dataView.setUint16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x200) {
|
||||
/* MULS, 0000 0010 dddd rrrr */
|
||||
const R = cpu.dataView.getInt8(((opcode & 0xf0) >> 4) + 16) * cpu.dataView.getInt8((opcode & 0xf) + 16);
|
||||
cpu.dataView.setInt16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xff88) === 0x300) {
|
||||
/* MULSU, 0000 0011 0ddd 0rrr */
|
||||
const R = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16) * cpu.data[(opcode & 7) + 16];
|
||||
cpu.dataView.setInt16(0, R, true);
|
||||
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9401) {
|
||||
/* NEG, 1001 010d dddd 0001 */
|
||||
const d = (opcode & 0x1f0) >> 4;
|
||||
const value = cpu.data[d];
|
||||
const R = 0 - value;
|
||||
cpu.data[d] = R;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= 128 === R ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= R ? 1 : 0;
|
||||
sreg |= 1 & (R | value) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if (opcode === 0) {
|
||||
/* NOP, 0000 0000 0000 0000 */
|
||||
/* NOP */
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x2800) {
|
||||
/* OR, 0010 10rd dddd rrrr */
|
||||
const R = cpu.data[(opcode & 0x1f0) >> 4] | cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0x6000) {
|
||||
/* SBR, 0110 KKKK dddd KKKK */
|
||||
const R = cpu.data[((opcode & 0xf0) >> 4) + 16] | ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
|
||||
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
|
||||
let sreg = cpu.data[95] & 0xe1;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf800) === 0xb800) {
|
||||
/* OUT, 1011 1AAr rrrr AAAA */
|
||||
cpu.writeData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32, cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x900f) {
|
||||
/* POP, 1001 000d dddd 1111 */
|
||||
const value = cpu.dataView.getUint16(93, true) + 1;
|
||||
cpu.dataView.setUint16(93, value, true);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[value];
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x920f) {
|
||||
/* PUSH, 1001 001d dddd 1111 */
|
||||
const value = cpu.dataView.getUint16(93, true);
|
||||
cpu.data[value] = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
cpu.dataView.setUint16(93, value - 1, true);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0xd000) {
|
||||
/* RCALL, 1101 kkkk kkkk kkkk */
|
||||
const k = (opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0);
|
||||
const retAddr = cpu.pc + 1;
|
||||
const sp = cpu.dataView.getUint16(93, true);
|
||||
const { pc22Bits } = cpu;
|
||||
cpu.data[sp] = 255 & retAddr;
|
||||
cpu.data[sp - 1] = (retAddr >> 8) & 255;
|
||||
if (pc22Bits) {
|
||||
cpu.data[sp - 2] = (retAddr >> 16) & 255;
|
||||
}
|
||||
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
|
||||
cpu.pc += k;
|
||||
cpu.cycles += pc22Bits ? 3 : 2;
|
||||
}
|
||||
else if (opcode === 0x9508) {
|
||||
/* RET, 1001 0101 0000 1000 */
|
||||
const { pc22Bits } = cpu;
|
||||
const i = cpu.dataView.getUint16(93, true) + (pc22Bits ? 3 : 2);
|
||||
cpu.dataView.setUint16(93, i, true);
|
||||
cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
|
||||
if (pc22Bits) {
|
||||
cpu.pc |= cpu.data[i - 2] << 16;
|
||||
}
|
||||
cpu.cycles += pc22Bits ? 4 : 3;
|
||||
}
|
||||
else if (opcode === 0x9518) {
|
||||
/* RETI, 1001 0101 0001 1000 */
|
||||
const { pc22Bits } = cpu;
|
||||
const i = cpu.dataView.getUint16(93, true) + (pc22Bits ? 3 : 2);
|
||||
cpu.dataView.setUint16(93, i, true);
|
||||
cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
|
||||
if (pc22Bits) {
|
||||
cpu.pc |= cpu.data[i - 2] << 16;
|
||||
}
|
||||
cpu.cycles += pc22Bits ? 4 : 3;
|
||||
cpu.data[95] |= 0x80; // Enable interrupts
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0xc000) {
|
||||
/* RJMP, 1100 kkkk kkkk kkkk */
|
||||
cpu.pc = cpu.pc + ((opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0));
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9407) {
|
||||
/* ROR, 1001 010d dddd 0111 */
|
||||
const d = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const r = (d >>> 1) | ((cpu.data[95] & 1) << 7);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = r;
|
||||
let sreg = cpu.data[95] & 0xe0;
|
||||
sreg |= r ? 0 : 2;
|
||||
sreg |= 128 & r ? 4 : 0;
|
||||
sreg |= 1 & d ? 1 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x800) {
|
||||
/* SBC, 0000 10rd dddd rrrr */
|
||||
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
let sreg = cpu.data[95];
|
||||
const R = val1 - val2 - (sreg & 1);
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0x4000) {
|
||||
/* SBCI, 0100 KKKK dddd KKKK */
|
||||
const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
|
||||
const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
|
||||
let sreg = cpu.data[95];
|
||||
const R = val1 - val2 - (sreg & 1);
|
||||
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
|
||||
sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9a00) {
|
||||
/* SBI, 1001 1010 AAAA Abbb */
|
||||
const target = ((opcode & 0xf8) >> 3) + 32;
|
||||
const mask = 1 << (opcode & 7);
|
||||
cpu.writeData(target, cpu.readData(target) | mask, mask);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9900) {
|
||||
/* SBIC, 1001 1001 AAAA Abbb */
|
||||
const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
|
||||
if (!(value & (1 << (opcode & 7)))) {
|
||||
const nextOpcode = cpu.progMem[cpu.pc + 1];
|
||||
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
|
||||
cpu.cycles += skipSize;
|
||||
cpu.pc += skipSize;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9b00) {
|
||||
/* SBIS, 1001 1011 AAAA Abbb */
|
||||
const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
|
||||
if (value & (1 << (opcode & 7))) {
|
||||
const nextOpcode = cpu.progMem[cpu.pc + 1];
|
||||
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
|
||||
cpu.cycles += skipSize;
|
||||
cpu.pc += skipSize;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xff00) === 0x9700) {
|
||||
/* SBIW, 1001 0111 KKdd KKKK */
|
||||
const i = 2 * ((opcode & 0x30) >> 4) + 24;
|
||||
const a = cpu.dataView.getUint16(i, true);
|
||||
const l = (opcode & 0xf) | ((opcode & 0xc0) >> 2);
|
||||
const R = a - l;
|
||||
cpu.dataView.setUint16(i, R, true);
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 0x8000 & R ? 4 : 0;
|
||||
sreg |= a & ~R & 0x8000 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= l > a ? 1 : 0;
|
||||
sreg |= 1 & ((~a & l) | (l & R) | (R & ~a)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe08) === 0xfc00) {
|
||||
/* SBRC, 1111 110r rrrr 0bbb */
|
||||
if (!(cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7)))) {
|
||||
const nextOpcode = cpu.progMem[cpu.pc + 1];
|
||||
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
|
||||
cpu.cycles += skipSize;
|
||||
cpu.pc += skipSize;
|
||||
}
|
||||
}
|
||||
else if ((opcode & 0xfe08) === 0xfe00) {
|
||||
/* SBRS, 1111 111r rrrr 0bbb */
|
||||
if (cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7))) {
|
||||
const nextOpcode = cpu.progMem[cpu.pc + 1];
|
||||
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
|
||||
cpu.cycles += skipSize;
|
||||
cpu.pc += skipSize;
|
||||
}
|
||||
}
|
||||
else if (opcode === 0x9588) {
|
||||
/* SLEEP, 1001 0101 1000 1000 */
|
||||
/* not implemented */
|
||||
}
|
||||
else if (opcode === 0x95e8) {
|
||||
/* SPM, 1001 0101 1110 1000 */
|
||||
/* not implemented */
|
||||
}
|
||||
else if (opcode === 0x95f8) {
|
||||
/* SPM(INC), 1001 0101 1111 1000 */
|
||||
/* not implemented */
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9200) {
|
||||
/* STS, 1001 001d dddd 0000 kkkk kkkk kkkk kkkk */
|
||||
const value = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const addr = cpu.progMem[cpu.pc + 1];
|
||||
cpu.writeData(addr, value);
|
||||
cpu.pc++;
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x920c) {
|
||||
/* STX, 1001 001r rrrr 1100 */
|
||||
cpu.writeData(cpu.dataView.getUint16(26, true), cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x920d) {
|
||||
/* STX(INC), 1001 001r rrrr 1101 */
|
||||
const x = cpu.dataView.getUint16(26, true);
|
||||
cpu.writeData(x, cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.dataView.setUint16(26, x + 1, true);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x920e) {
|
||||
/* STX(DEC), 1001 001r rrrr 1110 */
|
||||
const i = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const x = cpu.dataView.getUint16(26, true) - 1;
|
||||
cpu.dataView.setUint16(26, x, true);
|
||||
cpu.writeData(x, i);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x8208) {
|
||||
/* STY, 1000 001r rrrr 1000 */
|
||||
cpu.writeData(cpu.dataView.getUint16(28, true), cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9209) {
|
||||
/* STY(INC), 1001 001r rrrr 1001 */
|
||||
const i = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const y = cpu.dataView.getUint16(28, true);
|
||||
cpu.writeData(y, i);
|
||||
cpu.dataView.setUint16(28, y + 1, true);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x920a) {
|
||||
/* STY(DEC), 1001 001r rrrr 1010 */
|
||||
const i = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const y = cpu.dataView.getUint16(28, true) - 1;
|
||||
cpu.dataView.setUint16(28, y, true);
|
||||
cpu.writeData(y, i);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xd208) === 0x8208 &&
|
||||
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
|
||||
/* STDY, 10q0 qq1r rrrr 1qqq */
|
||||
cpu.writeData(cpu.dataView.getUint16(28, true) +
|
||||
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)), cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x8200) {
|
||||
/* STZ, 1000 001r rrrr 0000 */
|
||||
cpu.writeData(cpu.dataView.getUint16(30, true), cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9201) {
|
||||
/* STZ(INC), 1001 001r rrrr 0001 */
|
||||
const z = cpu.dataView.getUint16(30, true);
|
||||
cpu.writeData(z, cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.dataView.setUint16(30, z + 1, true);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9202) {
|
||||
/* STZ(DEC), 1001 001r rrrr 0010 */
|
||||
const i = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const z = cpu.dataView.getUint16(30, true) - 1;
|
||||
cpu.dataView.setUint16(30, z, true);
|
||||
cpu.writeData(z, i);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xd208) === 0x8200 &&
|
||||
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
|
||||
/* STDZ, 10q0 qq1r rrrr 0qqq */
|
||||
cpu.writeData(cpu.dataView.getUint16(30, true) +
|
||||
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)), cpu.data[(opcode & 0x1f0) >> 4]);
|
||||
cpu.cycles++;
|
||||
}
|
||||
else if ((opcode & 0xfc00) === 0x1800) {
|
||||
/* SUB, 0001 10rd dddd rrrr */
|
||||
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
|
||||
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
|
||||
const R = val1 - val2;
|
||||
cpu.data[(opcode & 0x1f0) >> 4] = R;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= val2 > val1 ? 1 : 0;
|
||||
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xf000) === 0x5000) {
|
||||
/* SUBI, 0101 KKKK dddd KKKK */
|
||||
const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
|
||||
const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
|
||||
const R = val1 - val2;
|
||||
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
|
||||
let sreg = cpu.data[95] & 0xc0;
|
||||
sreg |= R ? 0 : 2;
|
||||
sreg |= 128 & R ? 4 : 0;
|
||||
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
|
||||
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
|
||||
sreg |= val2 > val1 ? 1 : 0;
|
||||
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
|
||||
cpu.data[95] = sreg;
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9402) {
|
||||
/* SWAP, 1001 010d dddd 0010 */
|
||||
const d = (opcode & 0x1f0) >> 4;
|
||||
const i = cpu.data[d];
|
||||
cpu.data[d] = ((15 & i) << 4) | ((240 & i) >>> 4);
|
||||
}
|
||||
else if (opcode === 0x95a8) {
|
||||
/* WDR, 1001 0101 1010 1000 */
|
||||
cpu.onWatchdogReset();
|
||||
}
|
||||
else if ((opcode & 0xfe0f) === 0x9204) {
|
||||
/* XCH, 1001 001r rrrr 0100 */
|
||||
const r = (opcode & 0x1f0) >> 4;
|
||||
const val1 = cpu.data[r];
|
||||
const val2 = cpu.data[cpu.dataView.getUint16(30, true)];
|
||||
cpu.data[cpu.dataView.getUint16(30, true)] = val1;
|
||||
cpu.data[r] = val2;
|
||||
}
|
||||
cpu.pc = (cpu.pc + 1) % cpu.progMem.length;
|
||||
cpu.cycles++;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
export function avrInterrupt(cpu, addr) {
|
||||
const sp = cpu.dataView.getUint16(93, true);
|
||||
cpu.data[sp] = cpu.pc & 0xff;
|
||||
cpu.data[sp - 1] = (cpu.pc >> 8) & 0xff;
|
||||
if (cpu.pc22Bits) {
|
||||
cpu.data[sp - 2] = (cpu.pc >> 16) & 0xff;
|
||||
}
|
||||
cpu.dataView.setUint16(93, sp - (cpu.pc22Bits ? 3 : 2), true);
|
||||
cpu.data[95] &= 0x7f; // clear global interrupt flag
|
||||
cpu.cycles += 2;
|
||||
cpu.pc = addr;
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
export { CPU } from './cpu/cpu.js';
|
||||
export { avrInstruction } from './cpu/instruction.js';
|
||||
export { avrInterrupt } from './cpu/interrupt.js';
|
||||
export { ADCMuxInputType, ADCReference, AVRADC, adcConfig, atmega328Channels, } from './peripherals/adc.js';
|
||||
export { AVRClock, clockConfig } from './peripherals/clock.js';
|
||||
export { AVREEPROM, EEPROMMemoryBackend, eepromConfig } from './peripherals/eeprom.js';
|
||||
export { AVRIOPort, INT0, INT1, PCINT0, PCINT1, PCINT2, PinState, portAConfig, portBConfig, portCConfig, portDConfig, portEConfig, portFConfig, portGConfig, portHConfig, portJConfig, portKConfig, portLConfig, } from './peripherals/gpio.js';
|
||||
export { AVRSPI, spiConfig } from './peripherals/spi.js';
|
||||
export { AVRTimer, timer0Config, timer1Config, timer2Config } from './peripherals/timer.js';
|
||||
export { ATtinyTimer1, attinyTimer1Config } from './peripherals/timer-attiny.js';
|
||||
export * from './peripherals/twi.js';
|
||||
export { AVRUSART, usart0Config } from './peripherals/usart.js';
|
||||
export { AVRUSI } from './peripherals/usi.js';
|
||||
export { AVRWatchdog, watchdogConfig } from './peripherals/watchdog.js';
|
||||
+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;
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
export {};
|
||||
+915
@@ -0,0 +1,915 @@
|
||||
/**
|
||||
* Assemble AVR programs into a list of bytes.
|
||||
* Based on code from https://github.com/tadpol/Avrian-Jump,
|
||||
* refactored to TypeScript by Uri Shaked.
|
||||
*
|
||||
* This was written with http://www.atmel.com/atmel/acrobat/doc0856.pdf
|
||||
* It is a bit short of features often found in an assembler, but there is enough here to build
|
||||
* simple programs and run them.
|
||||
*
|
||||
* It would be nice someday to add device support, just to give errors on unsupported
|
||||
* instructions. Macros would be nice too.
|
||||
*
|
||||
* Copyright (C) 2020, Uri Shaked
|
||||
* Copyright (c) 2012 Michael Conrad Tadpol Tilstra
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
/**
|
||||
* Get a destination register index from a string and shift it to where it
|
||||
* is most commonly found.
|
||||
* Also, make sure it is within the valid range.
|
||||
*/
|
||||
function destRindex(r, min = 0, max = 31) {
|
||||
const match = r.match(/[Rr](\d{1,2})/);
|
||||
if (!match) {
|
||||
throw 'Not a register: ' + r;
|
||||
}
|
||||
const d = parseInt(match[1]);
|
||||
if (d < min || d > max) {
|
||||
throw 'Rd out of range: ' + min + '<>' + max;
|
||||
}
|
||||
return (d & 0x1f) << 4;
|
||||
}
|
||||
/**
|
||||
* Get a source register index from a string and shift it to where it is
|
||||
* most commonly found.
|
||||
* Also, make sure it is within the valid range.
|
||||
*/
|
||||
function srcRindex(r, min = 0, max = 31) {
|
||||
const match = r.match(/[Rr](\d{1,2})/);
|
||||
if (!match) {
|
||||
throw 'Not a register: ' + r;
|
||||
}
|
||||
const d = parseInt(match[1]);
|
||||
if (d < min || d > max) {
|
||||
throw 'Rd out of range: ' + min + '<>' + max;
|
||||
}
|
||||
let s = d & 0xf;
|
||||
s |= ((d >> 4) & 1) << 9;
|
||||
return s;
|
||||
}
|
||||
/**
|
||||
* Get a constant value and check that it is in range.
|
||||
*/
|
||||
function constValue(r, min = 0, max = 255) {
|
||||
const d = typeof r === 'string' ? parseInt(r) : r;
|
||||
if (isNaN(d)) {
|
||||
throw 'constant is not a number.';
|
||||
}
|
||||
if (d < min || d > max) {
|
||||
throw '[Ks] out of range: ' + min + '<>' + max;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
/*
|
||||
* Fit a twos-complement number into the specific bit count
|
||||
*/
|
||||
function fitTwoC(r, bits) {
|
||||
if (bits < 2) {
|
||||
throw 'Need at least 2 bits to be signed.';
|
||||
}
|
||||
if (bits > 16) {
|
||||
throw 'fitTwoC only works on 16bit numbers for now.';
|
||||
}
|
||||
if (Math.abs(r) > Math.pow(2, bits - 1))
|
||||
throw 'Not enough bits for number. (' + r + ', ' + bits + ')';
|
||||
if (r < 0) {
|
||||
r = 0xffff + r + 1;
|
||||
}
|
||||
const mask = 0xffff >> (16 - bits);
|
||||
return r & mask;
|
||||
}
|
||||
/**
|
||||
* Determin if input is an address or label and lookup if required.
|
||||
* If label that doesn't exist, return NaN.
|
||||
* If offset is not 0, convert from absolute address to relative.
|
||||
*/
|
||||
function constOrLabel(c, labels, offset = 0) {
|
||||
if (typeof c === 'string') {
|
||||
let d = parseInt(c);
|
||||
if (isNaN(d)) {
|
||||
if (c in labels) {
|
||||
d = labels[c] - offset;
|
||||
}
|
||||
else {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
c = d;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
/**
|
||||
* Convert number to hex and left pad it
|
||||
* @param len default to words.
|
||||
*/
|
||||
function zeroPad(r, len = 4) {
|
||||
r = Number(r).toString(16);
|
||||
const base = Array(len + 1).join('0');
|
||||
const t = base.substr(0, len - r.length) + r;
|
||||
return t;
|
||||
}
|
||||
/**
|
||||
* Get an Indirect Address Register and shift it to where it is commonly found.
|
||||
*/
|
||||
function stldXYZ(xyz) {
|
||||
switch (xyz) {
|
||||
case 'X':
|
||||
return 0x900c;
|
||||
case 'X+':
|
||||
return 0x900d;
|
||||
case '-X':
|
||||
return 0x900e;
|
||||
case 'Y':
|
||||
return 0x8008;
|
||||
case 'Y+':
|
||||
return 0x9009;
|
||||
case '-Y':
|
||||
return 0x900a;
|
||||
case 'Z':
|
||||
return 0x8000;
|
||||
case 'Z+':
|
||||
return 0x9001;
|
||||
case '-Z':
|
||||
return 0x9002;
|
||||
default:
|
||||
throw 'Not -?[XYZ]\\+?';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get an Indirect Address Register with displacement and shift it to where it is commonly found.
|
||||
*/
|
||||
function stldYZq(yzq) {
|
||||
const d = yzq.match(/([YZ])\+(\d+)/);
|
||||
let r = 0x8000;
|
||||
if (d == null) {
|
||||
throw 'Invalid arguments';
|
||||
}
|
||||
switch (d[1]) {
|
||||
case 'Y':
|
||||
r |= 0x8;
|
||||
break;
|
||||
case 'Z':
|
||||
/* r|= 0; */
|
||||
break;
|
||||
default:
|
||||
throw 'Not Y or Z with q';
|
||||
}
|
||||
const q = parseInt(d[2]);
|
||||
if (q < 0 || q > 64) {
|
||||
throw 'q is out of range';
|
||||
}
|
||||
r |= ((q & 0x20) << 8) | ((q & 0x18) << 7) | (q & 0x7);
|
||||
return r;
|
||||
}
|
||||
const SEflag = (a) => zeroPad(0x9408 | (constValue(a, 0, 7) << 4));
|
||||
/**
|
||||
* Table of instructions that can be assembled.
|
||||
*/
|
||||
const OPTABLE = {
|
||||
ADD(a, b) {
|
||||
const r = 0x0c00 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ADC(a, b) {
|
||||
const r = 0x1c00 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ADIW(a, b) {
|
||||
let r = 0x9600;
|
||||
const dm = a.match(/[Rr](24|26|28|30)/);
|
||||
if (!dm) {
|
||||
throw 'Rd must be 24, 26, 28, or 30';
|
||||
}
|
||||
let d = parseInt(dm[1]);
|
||||
d = (d - 24) / 2;
|
||||
r |= (d & 0x3) << 4;
|
||||
const k = constValue(b, 0, 63);
|
||||
r |= ((k & 0x30) << 2) | (k & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
AND(a, b) {
|
||||
const r = 0x2000 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ANDI(a, b) {
|
||||
let r = 0x7000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ASR(a) {
|
||||
const r = 0x9405 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
BCLR(a) {
|
||||
let r = 0x9488;
|
||||
const s = constValue(a, 0, 7);
|
||||
r |= (s & 0x7) << 4;
|
||||
return zeroPad(r);
|
||||
},
|
||||
BLD(a, b) {
|
||||
const r = 0xf800 | destRindex(a) | (constValue(b, 0, 7) & 0x7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
BRBC(a, b, byteLoc, labels) {
|
||||
const k = constOrLabel(b, labels, byteLoc + 2);
|
||||
if (isNaN(k)) {
|
||||
return (l) => OPTABLE['BRBC'](a, b, byteLoc, l);
|
||||
}
|
||||
let r = 0xf400 | constValue(a, 0, 7);
|
||||
r |= fitTwoC(constValue(k >> 1, -64, 63), 7) << 3;
|
||||
return zeroPad(r);
|
||||
},
|
||||
BRBS(a, b, byteLoc, labels) {
|
||||
const k = constOrLabel(b, labels, byteLoc + 2);
|
||||
if (isNaN(k)) {
|
||||
return (l) => OPTABLE['BRBS'](a, b, byteLoc, l);
|
||||
}
|
||||
let r = 0xf000 | constValue(a, 0, 7);
|
||||
r |= fitTwoC(constValue(k >> 1, -64, 63), 7) << 3;
|
||||
return zeroPad(r);
|
||||
},
|
||||
BRCC(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('0', a, byteLoc, labels);
|
||||
},
|
||||
BRCS(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('0', a, byteLoc, labels);
|
||||
},
|
||||
BREAK() {
|
||||
return '9598';
|
||||
},
|
||||
BREQ(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('1', a, byteLoc, labels);
|
||||
},
|
||||
BRGE(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('4', a, byteLoc, labels);
|
||||
},
|
||||
BRHC(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('5', a, byteLoc, labels);
|
||||
},
|
||||
BRHS(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('5', a, byteLoc, labels);
|
||||
},
|
||||
BRID(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('7', a, byteLoc, labels);
|
||||
},
|
||||
BRIE(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('7', a, byteLoc, labels);
|
||||
},
|
||||
BRLO(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('0', a, byteLoc, labels);
|
||||
},
|
||||
BRLT(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('4', a, byteLoc, labels);
|
||||
},
|
||||
BRMI(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('2', a, byteLoc, labels);
|
||||
},
|
||||
BRNE(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('1', a, byteLoc, labels);
|
||||
},
|
||||
BRPL(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('2', a, byteLoc, labels);
|
||||
},
|
||||
BRSH(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('0', a, byteLoc, labels);
|
||||
},
|
||||
BRTC(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('6', a, byteLoc, labels);
|
||||
},
|
||||
BRTS(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('6', a, byteLoc, labels);
|
||||
},
|
||||
BRVC(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBC']('3', a, byteLoc, labels);
|
||||
},
|
||||
BRVS(a, _, byteLoc, labels) {
|
||||
return OPTABLE['BRBS']('3', a, byteLoc, labels);
|
||||
},
|
||||
BSET(a) {
|
||||
let r = 0x9408;
|
||||
const s = constValue(a, 0, 7);
|
||||
r |= (s & 0x7) << 4;
|
||||
return zeroPad(r);
|
||||
},
|
||||
BST(a, b) {
|
||||
const r = 0xfa00 | destRindex(a) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CALL(a, b, byteLoc, labels) {
|
||||
let k = constOrLabel(a, labels);
|
||||
if (isNaN(k)) {
|
||||
return [(l) => OPTABLE['CALL'](a, b, byteLoc, l), 'xxxx'];
|
||||
}
|
||||
let r = 0x940e;
|
||||
k = constValue(k, 0, 0x400000) >> 1;
|
||||
const lk = k & 0xffff;
|
||||
const hk = (k >> 16) & 0x3f;
|
||||
r |= ((hk & 0x3e) << 3) | (hk & 1);
|
||||
return [zeroPad(r), zeroPad(lk)];
|
||||
},
|
||||
CBI(a, b) {
|
||||
const r = 0x9800 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CRB(a, b, byteLoc, l) {
|
||||
const k = constValue(b);
|
||||
return OPTABLE['ANDI'](a.toString(), (~k & 0xff).toString(), byteLoc, l);
|
||||
},
|
||||
CLC() {
|
||||
return '9488';
|
||||
},
|
||||
CLH() {
|
||||
return '94d8';
|
||||
},
|
||||
CLI() {
|
||||
return '94f8';
|
||||
},
|
||||
CLN() {
|
||||
return '94a8';
|
||||
},
|
||||
CLR(a, _, byteLoc, l) {
|
||||
return OPTABLE['EOR'](a, a, byteLoc, l);
|
||||
},
|
||||
CLS() {
|
||||
return '94c8';
|
||||
},
|
||||
CLT() {
|
||||
return '94e8';
|
||||
},
|
||||
CLV() {
|
||||
return '94b8';
|
||||
},
|
||||
CLZ() {
|
||||
return '9498';
|
||||
},
|
||||
COM(a) {
|
||||
const r = 0x9400 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CP(a, b) {
|
||||
const r = 0x1400 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CPC(a, b) {
|
||||
const r = 0x0400 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CPI(a, b) {
|
||||
let r = 0x3000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
CPSE(a, b) {
|
||||
const r = 0x1000 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
DEC(a) {
|
||||
const r = 0x940a | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
DES(a) {
|
||||
const r = 0x940b | (constValue(a, 0, 15) << 4);
|
||||
return zeroPad(r);
|
||||
},
|
||||
EICALL() {
|
||||
return '9519';
|
||||
},
|
||||
EIJMP() {
|
||||
return '9419';
|
||||
},
|
||||
ELPM(a, b) {
|
||||
if (typeof a === 'undefined' || a === '') {
|
||||
return '95d8';
|
||||
}
|
||||
else {
|
||||
let r = 0x9000 | destRindex(a);
|
||||
switch (b) {
|
||||
case 'Z':
|
||||
r |= 6;
|
||||
break;
|
||||
case 'Z+':
|
||||
r |= 7;
|
||||
break;
|
||||
default:
|
||||
throw 'Bad operand';
|
||||
}
|
||||
return zeroPad(r);
|
||||
}
|
||||
},
|
||||
EOR(a, b) {
|
||||
const r = 0x2400 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
FMUL(a, b) {
|
||||
const r = 0x0308 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
FMULS(a, b) {
|
||||
const r = 0x0380 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
FMULSU(a, b) {
|
||||
const r = 0x0388 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ICALL() {
|
||||
return '9509';
|
||||
},
|
||||
IJMP() {
|
||||
return '9409';
|
||||
},
|
||||
IN(a, b) {
|
||||
let r = 0xb000 | destRindex(a);
|
||||
const A = constValue(b, 0, 63);
|
||||
r |= ((A & 0x30) << 5) | (A & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
INC(a) {
|
||||
const r = 0x9403 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
JMP(a, b, byteLoc, labels) {
|
||||
let k = constOrLabel(a, labels);
|
||||
if (isNaN(k)) {
|
||||
return [(l) => OPTABLE['JMP'](a, b, byteLoc, l), 'xxxx'];
|
||||
}
|
||||
let r = 0x940c;
|
||||
k = constValue(k, 0, 0x400000) >> 1;
|
||||
const lk = k & 0xffff;
|
||||
const hk = (k >> 16) & 0x3f;
|
||||
r |= ((hk & 0x3e) << 3) | (hk & 1);
|
||||
return [zeroPad(r), zeroPad(lk)];
|
||||
},
|
||||
LAC(a, b) {
|
||||
if (a !== 'Z') {
|
||||
throw 'First Operand is not Z';
|
||||
}
|
||||
const r = 0x9206 | destRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LAS(a, b) {
|
||||
if (a !== 'Z') {
|
||||
throw 'First Operand is not Z';
|
||||
}
|
||||
const r = 0x9205 | destRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LAT(a, b) {
|
||||
if (a !== 'Z') {
|
||||
throw 'First Operand is not Z';
|
||||
}
|
||||
const r = 0x9207 | destRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LD(a, b) {
|
||||
const r = 0x0000 | destRindex(a) | stldXYZ(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LDD(a, b) {
|
||||
const r = 0x0000 | destRindex(a) | stldYZq(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LDI(a, b) {
|
||||
let r = 0xe000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
LDS(a, b) {
|
||||
const k = constValue(b, 0, 65535);
|
||||
const r = 0x9000 | destRindex(a);
|
||||
return [zeroPad(r), zeroPad(k)];
|
||||
},
|
||||
LPM(a, b) {
|
||||
if (typeof a === 'undefined' || a === '') {
|
||||
return '95c8';
|
||||
}
|
||||
else {
|
||||
let r = 0x9000 | destRindex(a);
|
||||
switch (b) {
|
||||
case 'Z':
|
||||
r |= 4;
|
||||
break;
|
||||
case 'Z+':
|
||||
r |= 5;
|
||||
break;
|
||||
default:
|
||||
throw 'Bad operand';
|
||||
}
|
||||
return zeroPad(r);
|
||||
}
|
||||
},
|
||||
LSL(a, _, byteLoc, l) {
|
||||
return OPTABLE['ADD'](a, a, byteLoc, l);
|
||||
},
|
||||
LSR(a) {
|
||||
const r = 0x9406 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
MOV(a, b) {
|
||||
const r = 0x2c00 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
MOVW(a, b) {
|
||||
/* use destRindex on both here for simpler shifting */
|
||||
const r = 0x0100 | ((destRindex(a) >> 1) & 0xf0) | ((destRindex(b) >> 5) & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
MUL(a, b) {
|
||||
const r = 0x9c00 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
MULS(a, b) {
|
||||
const r = 0x0200 | (destRindex(a, 16, 31) & 0xf0) | (srcRindex(b, 16, 31) & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
MULSU(a, b) {
|
||||
const r = 0x0300 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
NEG(a) {
|
||||
const r = 0x9401 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
NOP() {
|
||||
return '0000';
|
||||
},
|
||||
OR(a, b) {
|
||||
const r = 0x2800 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ORI(a, b) {
|
||||
let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
OUT(a, b) {
|
||||
let r = 0xb800 | destRindex(b);
|
||||
const A = constValue(a, 0, 63);
|
||||
r |= ((A & 0x30) << 5) | (A & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
POP(a) {
|
||||
const r = 0x900f | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
PUSH(a) {
|
||||
const r = 0x920f | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
RCALL(a, b, byteLoc, labels) {
|
||||
const k = constOrLabel(a, labels, byteLoc + 2);
|
||||
if (isNaN(k)) {
|
||||
return (l) => OPTABLE['RCALL'](a, b, byteLoc, l);
|
||||
}
|
||||
const r = 0xd000 | fitTwoC(constValue(k >> 1, -2048, 2047), 12);
|
||||
return zeroPad(r);
|
||||
},
|
||||
RET() {
|
||||
return '9508';
|
||||
},
|
||||
RETI() {
|
||||
return '9518';
|
||||
},
|
||||
RJMP(a, b, byteLoc, labels) {
|
||||
const k = constOrLabel(a, labels, byteLoc + 2);
|
||||
if (isNaN(k)) {
|
||||
return (l) => OPTABLE['RJMP'](a, b, byteLoc, l);
|
||||
}
|
||||
const r = 0xc000 | fitTwoC(constValue(k >> 1, -2048, 2047), 12);
|
||||
return zeroPad(r);
|
||||
},
|
||||
ROL(a, _, byteLoc, l) {
|
||||
return OPTABLE['ADC'](a, a, byteLoc, l);
|
||||
},
|
||||
ROR(a) {
|
||||
const r = 0x9407 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBC(a, b) {
|
||||
const r = 0x0800 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBCI(a, b) {
|
||||
let r = 0x4000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBI(a, b) {
|
||||
const r = 0x9a00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBIC(a, b) {
|
||||
const r = 0x9900 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBIS(a, b) {
|
||||
const r = 0x9b00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBIW(a, b) {
|
||||
let r = 0x9700;
|
||||
const dm = a.match(/[Rr](24|26|28|30)/);
|
||||
if (!dm) {
|
||||
throw 'Rd must be 24, 26, 28, or 30';
|
||||
}
|
||||
let d = parseInt(dm[1]);
|
||||
d = (d - 24) / 2;
|
||||
r |= (d & 0x3) << 4;
|
||||
const k = constValue(b, 0, 63);
|
||||
r |= ((k & 0x30) << 2) | (k & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBR(a, b) {
|
||||
let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0x0f);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBRC(a, b) {
|
||||
const r = 0xfc00 | destRindex(a) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SBRS(a, b) {
|
||||
const r = 0xfe00 | destRindex(a) | constValue(b, 0, 7);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SEC() {
|
||||
return SEflag(0);
|
||||
},
|
||||
SEH() {
|
||||
return SEflag(5);
|
||||
},
|
||||
SEI() {
|
||||
return SEflag(7);
|
||||
},
|
||||
SEN() {
|
||||
return SEflag(2);
|
||||
},
|
||||
SER(a) {
|
||||
const r = 0xef0f | (destRindex(a, 16, 31) & 0xf0);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SES() {
|
||||
return SEflag(4);
|
||||
},
|
||||
SET() {
|
||||
return SEflag(6);
|
||||
},
|
||||
SEV() {
|
||||
return SEflag(3);
|
||||
},
|
||||
SEZ() {
|
||||
return SEflag(1);
|
||||
},
|
||||
SLEEP() {
|
||||
return '9588';
|
||||
},
|
||||
SPM(a) {
|
||||
if (typeof a === 'undefined' || a === '') {
|
||||
return '95e8';
|
||||
}
|
||||
else {
|
||||
if (a !== 'Z+') {
|
||||
throw 'Bad param to SPM';
|
||||
}
|
||||
return '95f8';
|
||||
}
|
||||
},
|
||||
ST(a, b) {
|
||||
const r = 0x0200 | destRindex(b) | stldXYZ(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
STD(a, b) {
|
||||
const r = 0x0200 | destRindex(b) | stldYZq(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
STS(a, b) {
|
||||
const k = constValue(a, 0, 65535);
|
||||
const r = 0x9200 | destRindex(b);
|
||||
return [zeroPad(r), zeroPad(k)];
|
||||
},
|
||||
SUB(a, b) {
|
||||
const r = 0x1800 | destRindex(a) | srcRindex(b);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SUBI(a, b) {
|
||||
let r = 0x5000 | (destRindex(a, 16, 31) & 0xf0);
|
||||
const k = constValue(b);
|
||||
r |= ((k & 0xf0) << 4) | (k & 0xf);
|
||||
return zeroPad(r);
|
||||
},
|
||||
SWAP(a) {
|
||||
const r = 0x9402 | destRindex(a);
|
||||
return zeroPad(r);
|
||||
},
|
||||
TST(a, _, byteLoc, l) {
|
||||
return OPTABLE['AND'](a, a, byteLoc, l);
|
||||
},
|
||||
WDR() {
|
||||
return '95a8';
|
||||
},
|
||||
XCH(a, b) {
|
||||
const r = 0x9204 | destRindex(b);
|
||||
if (a !== 'Z') {
|
||||
throw 'Bad param, not Z';
|
||||
}
|
||||
return zeroPad(r);
|
||||
},
|
||||
};
|
||||
function passOne(inputdata) {
|
||||
const lines = inputdata.split('\n');
|
||||
const commentReg = /[#;].*$/;
|
||||
const labelReg = /^(\w+):/;
|
||||
const codeReg = /^\s*(\w+)(?:\s+([^,]+)(?:,\s*(\S+))?)?\s*$/;
|
||||
let lt;
|
||||
let res;
|
||||
let rets;
|
||||
let instruction;
|
||||
let byteOffset = 0;
|
||||
const lableTable = {};
|
||||
const replacements = {};
|
||||
const errorTable = [];
|
||||
const lineTable = [];
|
||||
for (let idx = 0; idx < lines.length; idx++) {
|
||||
res = lines[idx].trim();
|
||||
if (res.length === 0) {
|
||||
continue;
|
||||
}
|
||||
lt = { line: idx + 1, text: res, bytes: [], byteOffset: 0 };
|
||||
res = res.replace(commentReg, '').trim(); /* strip off comments. */
|
||||
if (res.length === 0) {
|
||||
continue;
|
||||
}
|
||||
/* check for a label */
|
||||
rets = res.match(labelReg);
|
||||
if (rets) {
|
||||
lableTable[rets[1]] = byteOffset;
|
||||
res = res.replace(labelReg, '').trim(); /* strip out label. */
|
||||
}
|
||||
if (res.length === 0) {
|
||||
continue;
|
||||
}
|
||||
/* Check for a mnemonic line */
|
||||
const resMatch = res.match(codeReg);
|
||||
try {
|
||||
if (resMatch === null) {
|
||||
throw "doesn't match as code!";
|
||||
}
|
||||
if (!resMatch[1]) {
|
||||
throw 'Empty mnemonic field!';
|
||||
}
|
||||
/* do opcode */
|
||||
instruction = resMatch[1].toUpperCase().trim();
|
||||
/* This switch is ok for just these three.
|
||||
* If ever to add more, then need to figure out how to merge all of the
|
||||
* mnemonics into the OPTABLE. (or build a seperate internal op table)
|
||||
*/
|
||||
switch (instruction) {
|
||||
case '_REPLACE':
|
||||
replacements[resMatch[2]] = resMatch[3];
|
||||
continue;
|
||||
case '_LOC': {
|
||||
const num = parseInt(resMatch[2]);
|
||||
if (isNaN(num)) {
|
||||
throw 'Location is not a number.';
|
||||
}
|
||||
if (num & 0x1) {
|
||||
throw 'Location is odd';
|
||||
}
|
||||
byteOffset = num;
|
||||
continue;
|
||||
}
|
||||
case '_IW': {
|
||||
const num = parseInt(resMatch[2]);
|
||||
if (isNaN(num)) {
|
||||
throw 'Immeadiate Word is not a number.';
|
||||
}
|
||||
lt.bytes = zeroPad(num);
|
||||
lt.byteOffset = byteOffset;
|
||||
byteOffset += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!(instruction in OPTABLE)) {
|
||||
throw 'No such instruction: ' + instruction;
|
||||
}
|
||||
/* do replacements on parameters. */
|
||||
if (resMatch[2] in replacements) {
|
||||
resMatch[2] = replacements[resMatch[2]];
|
||||
}
|
||||
if (resMatch[3] in replacements) {
|
||||
resMatch[3] = replacements[resMatch[3]];
|
||||
}
|
||||
const bytes = OPTABLE[instruction](resMatch[2], resMatch[3], byteOffset, lableTable);
|
||||
lt.byteOffset = byteOffset;
|
||||
switch (typeof bytes) {
|
||||
case 'function':
|
||||
case 'string':
|
||||
byteOffset += 2;
|
||||
break;
|
||||
case 'object' /* assumed as an array. */:
|
||||
byteOffset += bytes.length * 2;
|
||||
break;
|
||||
default:
|
||||
throw 'unknown return type from optable.';
|
||||
}
|
||||
lt.bytes = bytes;
|
||||
lineTable.push(lt);
|
||||
}
|
||||
catch (err) {
|
||||
errorTable.push('Line ' + idx + ': ' + err);
|
||||
}
|
||||
}
|
||||
return {
|
||||
labels: lableTable,
|
||||
errors: errorTable,
|
||||
lines: lineTable,
|
||||
};
|
||||
}
|
||||
function elementSize(lt) {
|
||||
return typeof lt.bytes === 'string' ? lt.bytes.length / 2 : lt.bytes.length * 2;
|
||||
}
|
||||
/**
|
||||
* Handle any forward referenced labels that were deferred in passone.
|
||||
*/
|
||||
function passTwo(lineTable, labels) {
|
||||
const errorTable = [];
|
||||
const lastElement = lineTable[lineTable.length - 1];
|
||||
const byteSize = lastElement ? lastElement.byteOffset + elementSize(lastElement) : 0;
|
||||
const resultTable = new Uint8Array(byteSize);
|
||||
for (const ltEntry of lineTable) {
|
||||
try {
|
||||
/* Look for functions left over from passone. */
|
||||
if (typeof ltEntry.bytes === 'function') {
|
||||
ltEntry.bytes = ltEntry.bytes(labels);
|
||||
}
|
||||
if (ltEntry.bytes instanceof Array &&
|
||||
ltEntry.bytes.length >= 1 &&
|
||||
typeof ltEntry.bytes[0] === 'function') {
|
||||
/* a bit gross. FIXME */
|
||||
ltEntry.bytes = ltEntry.bytes[0](labels);
|
||||
}
|
||||
/* copy bytes out of linetable into the results. */
|
||||
switch (typeof ltEntry.bytes) {
|
||||
case 'string':
|
||||
resultTable[ltEntry.byteOffset + 1] = parseInt(ltEntry.bytes.substr(0, 2), 16);
|
||||
resultTable[ltEntry.byteOffset] = parseInt(ltEntry.bytes.substr(2, 4), 16);
|
||||
break;
|
||||
case 'object' /* also array. */:
|
||||
if (ltEntry.bytes.length < 1) {
|
||||
throw 'Empty array in lineTable.';
|
||||
}
|
||||
for (let j = 0, bi = ltEntry.byteOffset; j < ltEntry.bytes.length; j++, bi += 2) {
|
||||
const value = ltEntry.bytes[j];
|
||||
if (typeof value !== 'string') {
|
||||
throw 'Not an array of strings.';
|
||||
}
|
||||
resultTable[bi + 1] = parseInt(value.substr(0, 2), 16);
|
||||
resultTable[bi] = parseInt(value.substr(2, 4), 16);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw 'unknown return type from optable.';
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
errorTable.push('Line: ' + ltEntry.line + ': ' + err);
|
||||
}
|
||||
}
|
||||
return { errors: errorTable, bytes: resultTable, lines: lineTable, labels };
|
||||
}
|
||||
/**
|
||||
* The assembler.
|
||||
*/
|
||||
export function assemble(input) {
|
||||
const mid = passOne(input);
|
||||
if (mid.errors.length > 0) {
|
||||
return {
|
||||
bytes: new Uint8Array(0),
|
||||
errors: mid.errors,
|
||||
lines: [],
|
||||
labels: {},
|
||||
};
|
||||
}
|
||||
return passTwo(mid.lines, mid.labels);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) Uri Shaked and contributors
|
||||
import { avrInstruction } from '../cpu/instruction.js';
|
||||
import { assemble } from './assembler.js';
|
||||
const BREAK_OPCODE = 0x9598;
|
||||
export function asmProgram(source) {
|
||||
const { bytes, errors, lines, labels } = assemble(source);
|
||||
if (errors.length) {
|
||||
throw new Error('Assembly failed: ' + errors);
|
||||
}
|
||||
return { program: new Uint16Array(bytes.buffer), lines, instructionCount: lines.length, labels };
|
||||
}
|
||||
const defaultOnBreak = () => {
|
||||
throw new Error('BREAK instruction encountered');
|
||||
};
|
||||
export class TestProgramRunner {
|
||||
constructor(cpu, onBreak = defaultOnBreak) {
|
||||
this.cpu = cpu;
|
||||
this.onBreak = onBreak;
|
||||
}
|
||||
runInstructions(count) {
|
||||
const { cpu, onBreak } = this;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
|
||||
onBreak === null || onBreak === void 0 ? void 0 : onBreak(cpu);
|
||||
}
|
||||
avrInstruction(cpu);
|
||||
cpu.tick();
|
||||
}
|
||||
}
|
||||
runUntil(predicate, maxIterations = 5000) {
|
||||
const { cpu, onBreak } = this;
|
||||
for (let i = 0; i < maxIterations; i++) {
|
||||
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
|
||||
onBreak === null || onBreak === void 0 ? void 0 : onBreak(cpu);
|
||||
}
|
||||
if (predicate(cpu)) {
|
||||
return;
|
||||
}
|
||||
avrInstruction(cpu);
|
||||
cpu.tick();
|
||||
}
|
||||
throw new Error('Test program ran for too long, check your predicate');
|
||||
}
|
||||
runToBreak() {
|
||||
this.runUntil((cpu) => cpu.progMem[cpu.pc] === BREAK_OPCODE);
|
||||
}
|
||||
runToAddress(byteAddr) {
|
||||
this.runUntil((cpu) => cpu.pc * 2 === byteAddr);
|
||||
}
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],t):t(CodeMirror)}(function(m){"use strict";var l={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};var a={};function d(t,e){e=t.match(a[t=e]||(a[t]=new RegExp("\\s+"+t+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")));return e?/^\s*(.*?)\s*$/.exec(e[2])[1]:""}function g(t,e){return new RegExp((e?"^":"")+"</\\s*"+t+"\\s*>","i")}function o(t,e){for(var a in t)for(var n=e[a]||(e[a]=[]),l=t[a],o=l.length-1;0<=o;o--)n.unshift(l[o])}m.defineMode("htmlmixed",function(i,t){var c=m.getMode(i,{name:"xml",htmlMode:!0,multilineTagIndentFactor:t.multilineTagIndentFactor,multilineTagIndentPastTag:t.multilineTagIndentPastTag,allowMissingTagName:t.allowMissingTagName}),s={},e=t&&t.tags,a=t&&t.scriptTypes;if(o(l,s),e&&o(e,s),a)for(var n=a.length-1;0<=n;n--)s.script.unshift(["type",a[n].matches,a[n].mode]);function u(t,e){var a,o,r,n=c.token(t,e.htmlState),l=/\btag\b/.test(n);return l&&!/[<>\s\/]/.test(t.current())&&(a=e.htmlState.tagName&&e.htmlState.tagName.toLowerCase())&&s.hasOwnProperty(a)?e.inTag=a+" ":e.inTag&&l&&/>$/.test(t.current())?(a=/^([\S]+) (.*)/.exec(e.inTag),e.inTag=null,l=">"==t.current()&&function(t,e){for(var a=0;a<t.length;a++){var n=t[a];if(!n[0]||n[1].test(d(e,n[0])))return n[2]}}(s[a[1]],a[2]),l=m.getMode(i,l),o=g(a[1],!0),r=g(a[1],!1),e.token=function(t,e){return t.match(o,!1)?(e.token=u,e.localState=e.localMode=null):(a=t,n=r,t=e.localMode.token(t,e.localState),e=a.current(),-1<(l=e.search(n))?a.backUp(e.length-l):e.match(/<\/?$/)&&(a.backUp(e.length),a.match(n,!1)||a.match(e)),t);var a,n,l},e.localMode=l,e.localState=m.startState(l,c.indent(e.htmlState,"",""))):e.inTag&&(e.inTag+=t.current(),t.eol()&&(e.inTag+=" ")),n}return{startState:function(){return{token:u,inTag:null,localMode:null,localState:null,htmlState:m.startState(c)}},copyState:function(t){var e;return t.localState&&(e=m.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:e,htmlState:m.copyState(c,t.htmlState)}},token:function(t,e){return e.token(t,e)},indent:function(t,e,a){return!t.localMode||/^\s*<\//.test(e)?c.indent(t.htmlState,e,a):t.localMode.indent?t.localMode.indent(t.localState,e,a):m.Pass},innerMode:function(t){return{state:t.localState||t.htmlState,mode:t.localMode||c}}}},"xml","javascript","css"),m.defineMIME("text/html","htmlmixed")});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { SimulationClock } from './simulation-clock.js';
|
||||
export class MockClock extends SimulationClock {
|
||||
advance(deltaMicros) {
|
||||
this.tick(this.nanos + deltaMicros * 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
export class ClockAlarm {
|
||||
constructor(clock, callback) {
|
||||
this.clock = clock;
|
||||
this.callback = callback;
|
||||
this.next = null;
|
||||
this.nanos = 0;
|
||||
this.scheduled = false;
|
||||
}
|
||||
schedule(deltaNanos) {
|
||||
if (this.scheduled) {
|
||||
this.cancel();
|
||||
}
|
||||
this.clock.linkAlarm(deltaNanos, this);
|
||||
}
|
||||
cancel() {
|
||||
this.clock.unlinkAlarm(this);
|
||||
this.scheduled = false;
|
||||
}
|
||||
}
|
||||
export class SimulationClock {
|
||||
constructor(frequency = 125e6) {
|
||||
this.frequency = frequency;
|
||||
this.nextAlarm = null;
|
||||
this.nanosCounter = 0;
|
||||
}
|
||||
get nanos() {
|
||||
return this.nanosCounter;
|
||||
}
|
||||
get micros() {
|
||||
return this.nanos / 1000;
|
||||
}
|
||||
createAlarm(callback) {
|
||||
return new ClockAlarm(this, callback);
|
||||
}
|
||||
linkAlarm(nanos, alarm) {
|
||||
alarm.nanos = this.nanos + nanos;
|
||||
let alarmListItem = this.nextAlarm;
|
||||
let lastItem = null;
|
||||
while (alarmListItem && alarmListItem.nanos < alarm.nanos) {
|
||||
lastItem = alarmListItem;
|
||||
alarmListItem = alarmListItem.next;
|
||||
}
|
||||
if (lastItem) {
|
||||
lastItem.next = alarm;
|
||||
alarm.next = alarmListItem;
|
||||
}
|
||||
else {
|
||||
this.nextAlarm = alarm;
|
||||
alarm.next = alarmListItem;
|
||||
}
|
||||
alarm.scheduled = true;
|
||||
return alarm;
|
||||
}
|
||||
unlinkAlarm(alarm) {
|
||||
let alarmListItem = this.nextAlarm;
|
||||
if (!alarmListItem) {
|
||||
return false;
|
||||
}
|
||||
let lastItem = null;
|
||||
while (alarmListItem) {
|
||||
if (alarmListItem === alarm) {
|
||||
if (lastItem) {
|
||||
lastItem.next = alarmListItem.next;
|
||||
}
|
||||
else {
|
||||
this.nextAlarm = alarmListItem.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
lastItem = alarmListItem;
|
||||
alarmListItem = alarmListItem.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
tick(deltaNanos) {
|
||||
const targetNanos = this.nanosCounter + deltaNanos;
|
||||
let alarm = this.nextAlarm;
|
||||
while (alarm && alarm.nanos <= targetNanos) {
|
||||
this.nextAlarm = alarm.next;
|
||||
this.nanosCounter = alarm.nanos;
|
||||
alarm.callback();
|
||||
alarm = this.nextAlarm;
|
||||
}
|
||||
this.nanosCounter = targetNanos;
|
||||
}
|
||||
get nanosToNextAlarm() {
|
||||
if (this.nextAlarm) {
|
||||
return this.nextAlarm.nanos - this.nanos;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+1249
File diff suppressed because it is too large
Load Diff
+53
@@ -0,0 +1,53 @@
|
||||
import { STOP_REPLY_SIGINT, STOP_REPLY_TRAP } from './gdb-server.js';
|
||||
import { gdbChecksum, gdbMessage } from './gdb-utils.js';
|
||||
export class GDBConnection {
|
||||
constructor(server, onResponse) {
|
||||
this.server = server;
|
||||
this.onResponse = onResponse;
|
||||
this.target = this.server.target;
|
||||
this.buf = '';
|
||||
server.addConnection(this);
|
||||
onResponse('+');
|
||||
}
|
||||
feedData(data) {
|
||||
const { onResponse } = this;
|
||||
if (data.charCodeAt(0) === 3) {
|
||||
this.server.info('BREAK');
|
||||
this.target.stop();
|
||||
onResponse(gdbMessage(STOP_REPLY_SIGINT));
|
||||
data = data.slice(1);
|
||||
}
|
||||
this.buf += data;
|
||||
for (;;) {
|
||||
const dolla = this.buf.indexOf('$');
|
||||
const hash = this.buf.indexOf('#', dolla + 1);
|
||||
if (dolla < 0 || hash < 0 || hash + 2 > this.buf.length) {
|
||||
return;
|
||||
}
|
||||
const cmd = this.buf.substring(dolla + 1, hash);
|
||||
const cksum = this.buf.substr(hash + 1, 2);
|
||||
this.buf = this.buf.substr(hash + 2);
|
||||
if (gdbChecksum(cmd) !== cksum) {
|
||||
this.server.warn(`GDB checksum error in message: ${cmd}`);
|
||||
onResponse('-');
|
||||
}
|
||||
else {
|
||||
onResponse('+');
|
||||
this.server.debug(`>${cmd}`);
|
||||
const response = this.server.processGDBMessage(cmd);
|
||||
if (response) {
|
||||
this.server.debug(`<${response}`);
|
||||
onResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onBreakpoint() {
|
||||
try {
|
||||
this.onResponse(gdbMessage(STOP_REPLY_TRAP));
|
||||
}
|
||||
catch (e) {
|
||||
this.server.removeConnection(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* RP2040 GDB Server
|
||||
*
|
||||
* Copyright (C) 2021, Uri Shaked
|
||||
*/
|
||||
import { SYSM_CONTROL, SYSM_MSP, SYSM_PRIMASK, SYSM_PSP } from '../cortex-m0-core.js';
|
||||
import { ConsoleLogger, LogLevel } from '../utils/logging.js';
|
||||
import { decodeHexBuf, encodeHexBuf, encodeHexByte, encodeHexUint32, gdbMessage, } from './gdb-utils.js';
|
||||
export const STOP_REPLY_SIGINT = 'S02';
|
||||
export const STOP_REPLY_TRAP = 'S05';
|
||||
/* string value: armv6m-none-unknown-eabi */
|
||||
const lldbTriple = '61726d76366d2d6e6f6e652d756e6b6e6f776e2d65616269';
|
||||
const registers = [
|
||||
`name:r0;bitsize:32;offset:0;encoding:int;format:hex;set:General Purpose Registers;generic:arg1;gcc:0;dwarf:0;`,
|
||||
`name:r1;bitsize:32;offset:4;encoding:int;format:hex;set:General Purpose Registers;generic:arg2;gcc:1;dwarf:1;`,
|
||||
`name:r2;bitsize:32;offset:8;encoding:int;format:hex;set:General Purpose Registers;generic:arg3;gcc:2;dwarf:2;`,
|
||||
`name:r3;bitsize:32;offset:12;encoding:int;format:hex;set:General Purpose Registers;generic:arg4;gcc:3;dwarf:3;`,
|
||||
`name:r4;bitsize:32;offset:16;encoding:int;format:hex;set:General Purpose Registers;gcc:4;dwarf:4;`,
|
||||
`name:r5;bitsize:32;offset:20;encoding:int;format:hex;set:General Purpose Registers;gcc:5;dwarf:5;`,
|
||||
`name:r6;bitsize:32;offset:24;encoding:int;format:hex;set:General Purpose Registers;gcc:6;dwarf:6;`,
|
||||
`name:r7;bitsize:32;offset:28;encoding:int;format:hex;set:General Purpose Registers;gcc:7;dwarf:7;`,
|
||||
`name:r8;bitsize:32;offset:32;encoding:int;format:hex;set:General Purpose Registers;gcc:8;dwarf:8;`,
|
||||
`name:r9;bitsize:32;offset:36;encoding:int;format:hex;set:General Purpose Registers;gcc:9;dwarf:9;`,
|
||||
`name:r10;bitsize:32;offset:40;encoding:int;format:hex;set:General Purpose Registers;gcc:10;dwarf:10;`,
|
||||
`name:r11;bitsize:32;offset:44;encoding:int;format:hex;set:General Purpose Registers;generic:fp;gcc:11;dwarf:11;`,
|
||||
`name:r12;bitsize:32;offset:48;encoding:int;format:hex;set:General Purpose Registers;gcc:12;dwarf:12;`,
|
||||
`name:sp;bitsize:32;offset:52;encoding:int;format:hex;set:General Purpose Registers;generic:sp;alt-name:r13;gcc:13;dwarf:13;`,
|
||||
`name:lr;bitsize:32;offset:56;encoding:int;format:hex;set:General Purpose Registers;generic:ra;alt-name:r14;gcc:14;dwarf:14;`,
|
||||
`name:pc;bitsize:32;offset:60;encoding:int;format:hex;set:General Purpose Registers;generic:pc;alt-name:r15;gcc:15;dwarf:15;`,
|
||||
`name:cpsr;bitsize:32;offset:64;encoding:int;format:hex;set:General Purpose Registers;generic:flags;alt-name:psr;gcc:16;dwarf:16;`,
|
||||
];
|
||||
const targetXML = `<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target version="1.0">
|
||||
<architecture>arm</architecture>
|
||||
<feature name="org.gnu.gdb.arm.m-profile">
|
||||
<reg name="r0" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r1" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r2" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r3" bitsize="32" regnum="3" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r4" bitsize="32" regnum="4" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r5" bitsize="32" regnum="5" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r6" bitsize="32" regnum="6" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r7" bitsize="32" regnum="7" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r8" bitsize="32" regnum="8" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r9" bitsize="32" regnum="9" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r10" bitsize="32" regnum="10" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r11" bitsize="32" regnum="11" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="r12" bitsize="32" regnum="12" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="sp" bitsize="32" regnum="13" save-restore="yes" type="data_ptr" group="general"/>
|
||||
<reg name="lr" bitsize="32" regnum="14" save-restore="yes" type="int" group="general"/>
|
||||
<reg name="pc" bitsize="32" regnum="15" save-restore="yes" type="code_ptr" group="general"/>
|
||||
<reg name="xPSR" bitsize="32" regnum="16" save-restore="yes" type="int" group="general"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.arm.m-system">
|
||||
<reg name="msp" bitsize="32" regnum="17" save-restore="yes" type="data_ptr" group="system"/>
|
||||
<reg name="psp" bitsize="32" regnum="18" save-restore="yes" type="data_ptr" group="system"/>
|
||||
<reg name="primask" bitsize="1" regnum="19" save-restore="yes" type="int8" group="system"/>
|
||||
<reg name="basepri" bitsize="8" regnum="20" save-restore="yes" type="int8" group="system"/>
|
||||
<reg name="faultmask" bitsize="1" regnum="21" save-restore="yes" type="int8" group="system"/>
|
||||
<reg name="control" bitsize="2" regnum="22" save-restore="yes" type="int8" group="system"/>
|
||||
</feature>
|
||||
</target>`;
|
||||
const LOG_NAME = 'GDBServer';
|
||||
export class GDBServer {
|
||||
constructor(target) {
|
||||
this.target = target;
|
||||
this.logger = new ConsoleLogger(LogLevel.Warn, true);
|
||||
this.connections = new Set();
|
||||
}
|
||||
processGDBMessage(cmd) {
|
||||
const { rp2040 } = this.target;
|
||||
const { core } = rp2040;
|
||||
if (cmd === 'Hg0') {
|
||||
return gdbMessage('OK');
|
||||
}
|
||||
switch (cmd[0]) {
|
||||
case '?':
|
||||
return gdbMessage(STOP_REPLY_TRAP);
|
||||
case 'q':
|
||||
// Query things
|
||||
if (cmd.startsWith('qSupported:')) {
|
||||
return gdbMessage('PacketSize=4000;vContSupported+;qXfer:features:read+');
|
||||
}
|
||||
if (cmd === 'qAttached') {
|
||||
return gdbMessage('1');
|
||||
}
|
||||
if (cmd.startsWith('qXfer:features:read:target.xml')) {
|
||||
return gdbMessage('l' + targetXML);
|
||||
}
|
||||
if (cmd.startsWith('qRegisterInfo')) {
|
||||
const index = parseInt(cmd.substring(13), 16);
|
||||
const register = registers[index];
|
||||
if (register) {
|
||||
return gdbMessage(register);
|
||||
}
|
||||
else {
|
||||
return gdbMessage(`E45`);
|
||||
}
|
||||
}
|
||||
if (cmd === 'qHostInfo') {
|
||||
return gdbMessage(`triple:${lldbTriple};endian:little;ptrsize:4;`);
|
||||
}
|
||||
if (cmd === 'qProcessInfo') {
|
||||
return gdbMessage('pid:1;endian:little;ptrsize:4;');
|
||||
}
|
||||
return gdbMessage('');
|
||||
case 'v':
|
||||
if (cmd === 'vCont?') {
|
||||
return gdbMessage('vCont;c;C;s;S');
|
||||
}
|
||||
if (cmd.startsWith('vCont;c')) {
|
||||
if (!this.target.executing) {
|
||||
this.target.execute();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cmd.startsWith('vCont;s')) {
|
||||
rp2040.step();
|
||||
const registerStatus = [];
|
||||
for (let i = 0; i < 17; i++) {
|
||||
const value = i === 16 ? core.xPSR : core.registers[i];
|
||||
registerStatus.push(`${encodeHexByte(i)}:${encodeHexUint32(value)}`);
|
||||
}
|
||||
return gdbMessage(`T05${registerStatus.join(';')};reason:trace;`);
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
if (!this.target.executing) {
|
||||
this.target.execute();
|
||||
}
|
||||
return gdbMessage('OK');
|
||||
case 'g': {
|
||||
// Read registers
|
||||
const buf = new Uint32Array(17);
|
||||
buf.set(core.registers);
|
||||
buf[16] = core.xPSR;
|
||||
return gdbMessage(encodeHexBuf(new Uint8Array(buf.buffer)));
|
||||
}
|
||||
case 'p': {
|
||||
// Read register
|
||||
const registerIndex = parseInt(cmd.substr(1), 16);
|
||||
if (registerIndex >= 0 && registerIndex <= 15) {
|
||||
return gdbMessage(encodeHexUint32(core.registers[registerIndex]));
|
||||
}
|
||||
const specialRegister = (sysm) => gdbMessage(encodeHexUint32(core.readSpecialRegister(sysm)));
|
||||
switch (registerIndex) {
|
||||
case 0x10:
|
||||
return gdbMessage(encodeHexUint32(core.xPSR));
|
||||
case 0x11:
|
||||
return specialRegister(SYSM_MSP);
|
||||
case 0x12:
|
||||
return specialRegister(SYSM_PSP);
|
||||
case 0x13:
|
||||
return specialRegister(SYSM_PRIMASK);
|
||||
case 0x14:
|
||||
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
|
||||
return gdbMessage(encodeHexUint32(0)); // TODO BASEPRI
|
||||
case 0x15:
|
||||
this.logger.warn(LOG_NAME, 'TODO faultmask');
|
||||
return gdbMessage(encodeHexUint32(0)); // TODO faultmask
|
||||
case 0x16:
|
||||
return specialRegister(SYSM_CONTROL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P': {
|
||||
// Write register
|
||||
const params = cmd.substring(1).split('=');
|
||||
const registerIndex = parseInt(params[0], 16);
|
||||
const registerValue = params[1].trim();
|
||||
const registerBytes = registerIndex > 0x12 ? 1 : 4;
|
||||
const decodedValue = decodeHexBuf(registerValue);
|
||||
if (registerIndex < 0 || registerIndex > 0x16 || decodedValue.length !== registerBytes) {
|
||||
return gdbMessage('E00');
|
||||
}
|
||||
const valueBuffer = new Uint8Array(4);
|
||||
valueBuffer.set(decodedValue.slice(0, 4));
|
||||
const value = new DataView(valueBuffer.buffer).getUint32(0, true);
|
||||
switch (registerIndex) {
|
||||
case 0x10:
|
||||
core.xPSR = value;
|
||||
break;
|
||||
case 0x11:
|
||||
core.writeSpecialRegister(SYSM_MSP, value);
|
||||
break;
|
||||
case 0x12:
|
||||
core.writeSpecialRegister(SYSM_PSP, value);
|
||||
break;
|
||||
case 0x13:
|
||||
core.writeSpecialRegister(SYSM_PRIMASK, value);
|
||||
break;
|
||||
case 0x14:
|
||||
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
|
||||
break; // TODO BASEPRI
|
||||
case 0x15:
|
||||
this.logger.warn(LOG_NAME, 'TODO faultmask');
|
||||
break; // TODO faultmask
|
||||
case 0x16:
|
||||
core.writeSpecialRegister(SYSM_CONTROL, value);
|
||||
break;
|
||||
default:
|
||||
core.registers[registerIndex] = value;
|
||||
break;
|
||||
}
|
||||
return gdbMessage('OK');
|
||||
}
|
||||
case 'm': {
|
||||
// Read memory
|
||||
const params = cmd.substr(1).split(',');
|
||||
const address = parseInt(params[0], 16);
|
||||
const length = parseInt(params[1], 16);
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += encodeHexByte(rp2040.readUint8(address + i));
|
||||
}
|
||||
return gdbMessage(result);
|
||||
}
|
||||
case 'M': {
|
||||
// Write memory
|
||||
const params = cmd.substr(1).split(/[,:]/);
|
||||
const address = parseInt(params[0], 16);
|
||||
const length = parseInt(params[1], 16);
|
||||
const data = decodeHexBuf(params[2].substr(0, length * 2));
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
this.debug(`Write ${data[i].toString(16)} to ${(address + i).toString(16)}`);
|
||||
rp2040.writeUint8(address + i, data[i]);
|
||||
}
|
||||
return gdbMessage('OK');
|
||||
}
|
||||
}
|
||||
return gdbMessage('');
|
||||
}
|
||||
addConnection(connection) {
|
||||
const { rp2040 } = this.target;
|
||||
this.connections.add(connection);
|
||||
rp2040.onBreak = () => {
|
||||
this.target.stop();
|
||||
rp2040.core.PC -= rp2040.core.breakRewind;
|
||||
for (const connection of this.connections) {
|
||||
connection.onBreakpoint();
|
||||
}
|
||||
};
|
||||
}
|
||||
removeConnection(connection) {
|
||||
this.connections.delete(connection);
|
||||
}
|
||||
debug(msg) {
|
||||
this.logger.debug(LOG_NAME, msg);
|
||||
}
|
||||
info(msg) {
|
||||
this.logger.info(LOG_NAME, msg);
|
||||
}
|
||||
warn(msg) {
|
||||
this.logger.warn(LOG_NAME, msg);
|
||||
}
|
||||
error(msg) {
|
||||
this.logger.error(LOG_NAME, msg);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { createServer } from 'net';
|
||||
import { GDBConnection } from './gdb-connection.js';
|
||||
import { GDBServer } from './gdb-server.js';
|
||||
export class GDBTCPServer extends GDBServer {
|
||||
constructor(target, port = 3333) {
|
||||
super(target);
|
||||
this.port = port;
|
||||
this.socketServer = createServer();
|
||||
this.socketServer.listen(port);
|
||||
this.socketServer.on('connection', (socket) => this.handleConnection(socket));
|
||||
}
|
||||
handleConnection(socket) {
|
||||
this.info('GDB connected');
|
||||
socket.setNoDelay(true);
|
||||
const connection = new GDBConnection(this, (data) => {
|
||||
socket.write(data);
|
||||
});
|
||||
socket.on('data', (data) => {
|
||||
connection.feedData(data.toString('utf-8'));
|
||||
});
|
||||
socket.on('error', (err) => {
|
||||
this.removeConnection(connection);
|
||||
this.error(`GDB socket error ${err}`);
|
||||
});
|
||||
socket.on('close', () => {
|
||||
this.removeConnection(connection);
|
||||
this.info('GDB disconnected');
|
||||
});
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
export function encodeHexByte(value) {
|
||||
return (value >> 4).toString(16) + (value & 0xf).toString(16);
|
||||
}
|
||||
export function encodeHexBuf(buf) {
|
||||
return Array.from(buf).map(encodeHexByte).join('');
|
||||
}
|
||||
export function encodeHexUint32BE(value) {
|
||||
return encodeHexBuf(new Uint8Array([(value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]));
|
||||
}
|
||||
export function encodeHexUint32(value) {
|
||||
const buf = new Uint32Array([value]);
|
||||
return encodeHexBuf(new Uint8Array(buf.buffer));
|
||||
}
|
||||
export function decodeHexBuf(encoded) {
|
||||
const result = new Uint8Array(encoded.length / 2);
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
result[i] = parseInt(encoded.substr(i * 2, 2), 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export function decodeHexUint32Array(encoded) {
|
||||
return new Uint32Array(decodeHexBuf(encoded).buffer);
|
||||
}
|
||||
export function decodeHexUint32(encoded) {
|
||||
return decodeHexUint32Array(encoded)[0];
|
||||
}
|
||||
export function gdbChecksum(text) {
|
||||
const value = text
|
||||
.split('')
|
||||
.map((c) => c.charCodeAt(0))
|
||||
.reduce((a, b) => a + b, 0) & 0xff;
|
||||
return encodeHexByte(value);
|
||||
}
|
||||
export function gdbMessage(value) {
|
||||
return `$${value}#${gdbChecksum(value)}`;
|
||||
}
|
||||
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
import { WaitType } from './peripherals/pio.js';
|
||||
export var GPIOPinState;
|
||||
(function (GPIOPinState) {
|
||||
GPIOPinState[GPIOPinState["Low"] = 0] = "Low";
|
||||
GPIOPinState[GPIOPinState["High"] = 1] = "High";
|
||||
GPIOPinState[GPIOPinState["Input"] = 2] = "Input";
|
||||
GPIOPinState[GPIOPinState["InputPullUp"] = 3] = "InputPullUp";
|
||||
GPIOPinState[GPIOPinState["InputPullDown"] = 4] = "InputPullDown";
|
||||
GPIOPinState[GPIOPinState["InputBusKeeper"] = 5] = "InputBusKeeper";
|
||||
})(GPIOPinState || (GPIOPinState = {}));
|
||||
export const FUNCTION_PWM = 4;
|
||||
export const FUNCTION_SIO = 5;
|
||||
export const FUNCTION_PIO0 = 6;
|
||||
export const FUNCTION_PIO1 = 7;
|
||||
function applyOverride(value, overrideType) {
|
||||
switch (overrideType) {
|
||||
case 0:
|
||||
return value;
|
||||
case 1:
|
||||
return !value;
|
||||
case 2:
|
||||
return false;
|
||||
case 3:
|
||||
return true;
|
||||
}
|
||||
console.error('applyOverride received invalid override type', overrideType);
|
||||
return value;
|
||||
}
|
||||
const IRQ_EDGE_HIGH = 1 << 3;
|
||||
const IRQ_EDGE_LOW = 1 << 2;
|
||||
const IRQ_LEVEL_HIGH = 1 << 1;
|
||||
const IRQ_LEVEL_LOW = 1 << 0;
|
||||
export class GPIOPin {
|
||||
constructor(rp2040, index, name = index.toString()) {
|
||||
this.rp2040 = rp2040;
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.rawInputValue = false;
|
||||
this.lastValue = this.value;
|
||||
this.ctrl = 0x1f;
|
||||
this.padValue = 0b0110110;
|
||||
this.irqEnableMask = 0;
|
||||
this.irqForceMask = 0;
|
||||
this.irqStatus = 0;
|
||||
this.listeners = new Set();
|
||||
}
|
||||
get rawInterrupt() {
|
||||
return !!((this.irqStatus & this.irqEnableMask) | this.irqForceMask);
|
||||
}
|
||||
get isSlewFast() {
|
||||
return !!(this.padValue & 1);
|
||||
}
|
||||
get schmittEnabled() {
|
||||
return !!(this.padValue & 2);
|
||||
}
|
||||
get pulldownEnabled() {
|
||||
return !!(this.padValue & 4);
|
||||
}
|
||||
get pullupEnabled() {
|
||||
return !!(this.padValue & 8);
|
||||
}
|
||||
get driveStrength() {
|
||||
return (this.padValue >> 4) & 0x3;
|
||||
}
|
||||
get inputEnable() {
|
||||
return !!(this.padValue & 0x40);
|
||||
}
|
||||
get outputDisable() {
|
||||
return !!(this.padValue & 0x80);
|
||||
}
|
||||
get functionSelect() {
|
||||
return this.ctrl & 0x1f;
|
||||
}
|
||||
get outputOverride() {
|
||||
return (this.ctrl >> 8) & 0x3;
|
||||
}
|
||||
get outputEnableOverride() {
|
||||
return (this.ctrl >> 12) & 0x3;
|
||||
}
|
||||
get inputOverride() {
|
||||
return (this.ctrl >> 16) & 0x3;
|
||||
}
|
||||
get irqOverride() {
|
||||
return (this.ctrl >> 28) & 0x3;
|
||||
}
|
||||
get rawOutputEnable() {
|
||||
const { index, rp2040, functionSelect } = this;
|
||||
const bitmask = 1 << index;
|
||||
switch (functionSelect) {
|
||||
case FUNCTION_PWM:
|
||||
return !!(rp2040.pwm.gpioDirection & bitmask);
|
||||
case FUNCTION_SIO:
|
||||
return !!(rp2040.sio.gpioOutputEnable & bitmask);
|
||||
case FUNCTION_PIO0:
|
||||
return !!(rp2040.pio[0].pinDirections & bitmask);
|
||||
case FUNCTION_PIO1:
|
||||
return !!(rp2040.pio[1].pinDirections & bitmask);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
get rawOutputValue() {
|
||||
const { index, rp2040, functionSelect } = this;
|
||||
const bitmask = 1 << index;
|
||||
switch (functionSelect) {
|
||||
case FUNCTION_PWM:
|
||||
return !!(rp2040.pwm.gpioValue & bitmask);
|
||||
case FUNCTION_SIO:
|
||||
return !!(rp2040.sio.gpioValue & bitmask);
|
||||
case FUNCTION_PIO0:
|
||||
return !!(rp2040.pio[0].pinValues & bitmask);
|
||||
case FUNCTION_PIO1:
|
||||
return !!(rp2040.pio[1].pinValues & bitmask);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
get inputValue() {
|
||||
return applyOverride(this.rawInputValue && this.inputEnable, this.inputOverride);
|
||||
}
|
||||
get irqValue() {
|
||||
return applyOverride(this.rawInterrupt, this.irqOverride);
|
||||
}
|
||||
get outputEnable() {
|
||||
return applyOverride(this.rawOutputEnable, this.outputEnableOverride);
|
||||
}
|
||||
get outputValue() {
|
||||
return applyOverride(this.rawOutputValue, this.outputOverride);
|
||||
}
|
||||
/**
|
||||
* Returns the STATUS register value for the pin, as outlined in section 2.19.6 of the datasheet
|
||||
*/
|
||||
get status() {
|
||||
const irqToProc = this.irqValue ? 1 << 26 : 0;
|
||||
const irqFromPad = this.rawInterrupt ? 1 << 24 : 0;
|
||||
const inToPeri = this.inputValue ? 1 << 19 : 0;
|
||||
const inFromPad = this.rawInputValue ? 1 << 17 : 0;
|
||||
const oeToPad = this.outputEnable ? 1 << 13 : 0;
|
||||
const oeFromPeri = this.rawOutputEnable ? 1 << 12 : 0;
|
||||
const outToPad = this.outputValue ? 1 << 9 : 0;
|
||||
const outFromPeri = this.rawOutputValue ? 1 << 8 : 0;
|
||||
return (irqToProc | irqFromPad | inToPeri | inFromPad | oeToPad | oeFromPeri | outToPad | outFromPeri);
|
||||
}
|
||||
get value() {
|
||||
if (this.outputEnable) {
|
||||
return this.outputValue ? GPIOPinState.High : GPIOPinState.Low;
|
||||
}
|
||||
else {
|
||||
// TODO: check what happens when we enable both pullup/pulldown
|
||||
// ANSWER: It is valid, see: 2.19.4.1. Bus Keeper Mode, datasheet p240
|
||||
if (this.pulldownEnabled && this.pullupEnabled) {
|
||||
// Pull high when high, pull low when low:
|
||||
return GPIOPinState.InputBusKeeper;
|
||||
}
|
||||
else if (this.pulldownEnabled) {
|
||||
return GPIOPinState.InputPullDown;
|
||||
}
|
||||
else if (this.pullupEnabled) {
|
||||
return GPIOPinState.InputPullUp;
|
||||
}
|
||||
return GPIOPinState.Input;
|
||||
}
|
||||
}
|
||||
setInputValue(value) {
|
||||
this.rawInputValue = value;
|
||||
const prevIrqValue = this.irqValue;
|
||||
if (value && this.inputEnable) {
|
||||
this.irqStatus |= IRQ_EDGE_HIGH | IRQ_LEVEL_HIGH;
|
||||
this.irqStatus &= ~IRQ_LEVEL_LOW;
|
||||
}
|
||||
else {
|
||||
this.irqStatus |= IRQ_EDGE_LOW | IRQ_LEVEL_LOW;
|
||||
this.irqStatus &= ~IRQ_LEVEL_HIGH;
|
||||
}
|
||||
if (this.irqValue !== prevIrqValue) {
|
||||
this.rp2040.updateIOInterrupt();
|
||||
}
|
||||
if (this.functionSelect === FUNCTION_PWM) {
|
||||
this.rp2040.pwm.gpioOnInput(this.index);
|
||||
}
|
||||
for (const pio of this.rp2040.pio) {
|
||||
for (const machine of pio.machines) {
|
||||
if (machine.enabled &&
|
||||
machine.waiting &&
|
||||
machine.waitType === WaitType.Pin &&
|
||||
machine.waitIndex === this.index) {
|
||||
machine.checkWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForUpdates() {
|
||||
const { lastValue, value } = this;
|
||||
if (value !== lastValue) {
|
||||
this.lastValue = value;
|
||||
for (const listener of this.listeners) {
|
||||
listener(value, lastValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshInput() {
|
||||
this.setInputValue(this.rawInputValue);
|
||||
}
|
||||
updateIRQValue(value) {
|
||||
if (value & IRQ_EDGE_LOW && this.irqStatus & IRQ_EDGE_LOW) {
|
||||
this.irqStatus &= ~IRQ_EDGE_LOW;
|
||||
this.rp2040.updateIOInterrupt();
|
||||
}
|
||||
if (value & IRQ_EDGE_HIGH && this.irqStatus & IRQ_EDGE_HIGH) {
|
||||
this.irqStatus &= ~IRQ_EDGE_HIGH;
|
||||
this.rp2040.updateIOInterrupt();
|
||||
}
|
||||
}
|
||||
addListener(callback) {
|
||||
this.listeners.add(callback);
|
||||
return () => this.listeners.delete(callback);
|
||||
}
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
export { GDBConnection } from './gdb/gdb-connection.js';
|
||||
export { GDBServer } from './gdb/gdb-server.js';
|
||||
export { GPIOPin, GPIOPinState } from './gpio-pin.js';
|
||||
export { I2CMode, I2CSpeed, RPI2C } from './peripherals/i2c.js';
|
||||
export { BasePeripheral } from './peripherals/peripheral.js';
|
||||
export { RPPIO, StateMachine } from './peripherals/pio.js';
|
||||
export { RPUSBController } from './peripherals/usb.js';
|
||||
export { RP2040 } from './rp2040.js';
|
||||
export { Simulator } from './simulator.js';
|
||||
export { USBCDC } from './usb/cdc.js';
|
||||
export { DataDirection, DescriptorType, SetupRecipient, SetupRequest, SetupType, } from './usb/interfaces.js';
|
||||
export { createSetupPacket, getDescriptorPacket, setDeviceAddressPacket, setDeviceConfigurationPacket, } from './usb/setup.js';
|
||||
export { parseSetupPacket, } from './usb/usb-device.js';
|
||||
export { FIFO } from './utils/fifo.js';
|
||||
export { ConsoleLogger, LogLevel } from './utils/logging.js';
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import { s32, u32 } from './utils/bit.js';
|
||||
export class InterpolatorConfig {
|
||||
constructor(value) {
|
||||
this.shift = 0;
|
||||
this.maskLSB = 0;
|
||||
this.maskMSB = 0;
|
||||
this.signed = false;
|
||||
this.crossInput = false;
|
||||
this.crossResult = false;
|
||||
this.addRaw = false;
|
||||
this.forceMSB = 0;
|
||||
this.blend = false;
|
||||
this.clamp = false;
|
||||
this.overf0 = false;
|
||||
this.overf1 = false;
|
||||
this.overf = false;
|
||||
this.shift = (value >>> 0) & 0b11111;
|
||||
this.maskLSB = (value >>> 5) & 0b11111;
|
||||
this.maskMSB = (value >>> 10) & 0b11111;
|
||||
this.signed = Boolean((value >>> 15) & 1);
|
||||
this.crossInput = Boolean((value >>> 16) & 1);
|
||||
this.crossResult = Boolean((value >>> 17) & 1);
|
||||
this.addRaw = Boolean((value >>> 18) & 1);
|
||||
this.forceMSB = (value >>> 19) & 0b11;
|
||||
this.blend = Boolean((value >>> 21) & 1);
|
||||
this.clamp = Boolean((value >>> 22) & 1);
|
||||
this.overf0 = Boolean((value >>> 23) & 1);
|
||||
this.overf1 = Boolean((value >>> 24) & 1);
|
||||
this.overf = Boolean((value >>> 25) & 1);
|
||||
}
|
||||
toUint32() {
|
||||
return (((this.shift & 0b11111) << 0) |
|
||||
((this.maskLSB & 0b11111) << 5) |
|
||||
((this.maskMSB & 0b11111) << 10) |
|
||||
((Number(this.signed) & 1) << 15) |
|
||||
((Number(this.crossInput) & 1) << 16) |
|
||||
((Number(this.crossResult) & 1) << 17) |
|
||||
((Number(this.addRaw) & 1) << 18) |
|
||||
((this.forceMSB & 0b11) << 19) |
|
||||
((Number(this.blend) & 1) << 21) |
|
||||
((Number(this.clamp) & 1) << 22) |
|
||||
((Number(this.overf0) & 1) << 23) |
|
||||
((Number(this.overf1) & 1) << 24) |
|
||||
((Number(this.overf) & 1) << 25));
|
||||
}
|
||||
}
|
||||
export class Interpolator {
|
||||
constructor(index) {
|
||||
this.index = index;
|
||||
this.accum0 = 0;
|
||||
this.accum1 = 0;
|
||||
this.base0 = 0;
|
||||
this.base1 = 0;
|
||||
this.base2 = 0;
|
||||
this.ctrl0 = 0;
|
||||
this.ctrl1 = 0;
|
||||
this.result0 = 0;
|
||||
this.result1 = 0;
|
||||
this.result2 = 0;
|
||||
this.smresult0 = 0;
|
||||
this.smresult1 = 0;
|
||||
this.update();
|
||||
}
|
||||
update() {
|
||||
const N = this.index;
|
||||
const ctrl0 = new InterpolatorConfig(this.ctrl0);
|
||||
const ctrl1 = new InterpolatorConfig(this.ctrl1);
|
||||
const do_clamp = ctrl0.clamp && N == 1;
|
||||
const do_blend = ctrl0.blend && N == 0;
|
||||
ctrl0.clamp = do_clamp;
|
||||
ctrl0.blend = do_blend;
|
||||
ctrl1.clamp = false;
|
||||
ctrl1.blend = false;
|
||||
ctrl1.overf0 = false;
|
||||
ctrl1.overf1 = false;
|
||||
ctrl1.overf = false;
|
||||
const input0 = s32(ctrl0.crossInput ? this.accum1 : this.accum0);
|
||||
const input1 = s32(ctrl1.crossInput ? this.accum0 : this.accum1);
|
||||
const msbmask0 = ctrl0.maskMSB == 31 ? 0xffffffff : (1 << (ctrl0.maskMSB + 1)) - 1;
|
||||
const msbmask1 = ctrl1.maskMSB == 31 ? 0xffffffff : (1 << (ctrl1.maskMSB + 1)) - 1;
|
||||
const mask0 = msbmask0 & ~((1 << ctrl0.maskLSB) - 1);
|
||||
const mask1 = msbmask1 & ~((1 << ctrl1.maskLSB) - 1);
|
||||
const uresult0 = (input0 >>> ctrl0.shift) & mask0;
|
||||
const uresult1 = (input1 >>> ctrl1.shift) & mask1;
|
||||
const overf0 = Boolean((input0 >>> ctrl0.shift) & ~msbmask0);
|
||||
const overf1 = Boolean((input1 >>> ctrl1.shift) & ~msbmask1);
|
||||
const overf = overf0 || overf1;
|
||||
const sextmask0 = uresult0 & (1 << ctrl0.maskMSB) ? -1 << ctrl0.maskMSB : 0;
|
||||
const sextmask1 = uresult1 & (1 << ctrl1.maskMSB) ? -1 << ctrl1.maskMSB : 0;
|
||||
const sresult0 = uresult0 | sextmask0;
|
||||
const sresult1 = uresult1 | sextmask1;
|
||||
const result0 = ctrl0.signed ? sresult0 : uresult0;
|
||||
const result1 = ctrl1.signed ? sresult1 : uresult1;
|
||||
const addresult0 = this.base0 + (ctrl0.addRaw ? input0 : result0);
|
||||
const addresult1 = this.base1 + (ctrl1.addRaw ? input1 : result1);
|
||||
const addresult2 = this.base2 + result0 + (do_blend ? 0 : result1);
|
||||
const uclamp0 = u32(result0) < u32(this.base0)
|
||||
? this.base0
|
||||
: u32(result0) > u32(this.base1)
|
||||
? this.base1
|
||||
: result0;
|
||||
const sclamp0 = s32(result0) < s32(this.base0)
|
||||
? this.base0
|
||||
: s32(result0) > s32(this.base1)
|
||||
? this.base1
|
||||
: result0;
|
||||
const clamp0 = ctrl0.signed ? sclamp0 : uclamp0;
|
||||
const alpha1 = result1 & 0xff;
|
||||
const ublend1 = u32(this.base0) + (Math.floor((alpha1 * (u32(this.base1) - u32(this.base0))) / 256) | 0);
|
||||
const sblend1 = s32(this.base0) + (Math.floor((alpha1 * (s32(this.base1) - s32(this.base0))) / 256) | 0);
|
||||
const blend1 = ctrl1.signed ? sblend1 : ublend1;
|
||||
this.smresult0 = u32(result0);
|
||||
this.smresult1 = u32(result1);
|
||||
this.result0 = u32(do_blend ? alpha1 : (do_clamp ? clamp0 : addresult0) | (ctrl0.forceMSB << 28));
|
||||
this.result1 = u32((do_blend ? blend1 : addresult1) | (ctrl0.forceMSB << 28));
|
||||
this.result2 = u32(addresult2);
|
||||
ctrl0.overf0 = overf0;
|
||||
ctrl0.overf1 = overf1;
|
||||
ctrl0.overf = overf;
|
||||
this.ctrl0 = ctrl0.toUint32();
|
||||
this.ctrl1 = ctrl1.toUint32();
|
||||
}
|
||||
writeback() {
|
||||
const ctrl0 = new InterpolatorConfig(this.ctrl0);
|
||||
const ctrl1 = new InterpolatorConfig(this.ctrl1);
|
||||
this.accum0 = u32(ctrl0.crossResult ? this.result1 : this.result0);
|
||||
this.accum1 = u32(ctrl1.crossResult ? this.result0 : this.result1);
|
||||
this.update();
|
||||
}
|
||||
setBase01(value) {
|
||||
const N = this.index;
|
||||
const ctrl0 = new InterpolatorConfig(this.ctrl0);
|
||||
const ctrl1 = new InterpolatorConfig(this.ctrl1);
|
||||
const do_blend = ctrl0.blend && N == 0;
|
||||
const input0 = value & 0xffff;
|
||||
const input1 = (value >>> 16) & 0xffff;
|
||||
const sextmask0 = input0 & (1 << 15) ? -1 << 15 : 0;
|
||||
const sextmask1 = input1 & (1 << 15) ? -1 << 15 : 0;
|
||||
const base0 = (do_blend ? ctrl1.signed : ctrl0.signed) ? input0 | sextmask0 : input0;
|
||||
const base1 = ctrl1.signed ? input1 | sextmask1 : input1;
|
||||
this.base0 = u32(base0);
|
||||
this.base1 = u32(base1);
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
export var IRQ;
|
||||
(function (IRQ) {
|
||||
IRQ[IRQ["TIMER_0"] = 0] = "TIMER_0";
|
||||
IRQ[IRQ["TIMER_1"] = 1] = "TIMER_1";
|
||||
IRQ[IRQ["TIMER_2"] = 2] = "TIMER_2";
|
||||
IRQ[IRQ["TIMER_3"] = 3] = "TIMER_3";
|
||||
IRQ[IRQ["PWM_WRAP"] = 4] = "PWM_WRAP";
|
||||
IRQ[IRQ["USBCTRL"] = 5] = "USBCTRL";
|
||||
IRQ[IRQ["XIP"] = 6] = "XIP";
|
||||
IRQ[IRQ["PIO0_IRQ0"] = 7] = "PIO0_IRQ0";
|
||||
IRQ[IRQ["PIO0_IRQ1"] = 8] = "PIO0_IRQ1";
|
||||
IRQ[IRQ["PIO1_IRQ0"] = 9] = "PIO1_IRQ0";
|
||||
IRQ[IRQ["PIO1_IRQ1"] = 10] = "PIO1_IRQ1";
|
||||
IRQ[IRQ["DMA_IRQ0"] = 11] = "DMA_IRQ0";
|
||||
IRQ[IRQ["DMA_IRQ1"] = 12] = "DMA_IRQ1";
|
||||
IRQ[IRQ["IO_BANK0"] = 13] = "IO_BANK0";
|
||||
IRQ[IRQ["IO_QSPI"] = 14] = "IO_QSPI";
|
||||
IRQ[IRQ["SIO_PROC0"] = 15] = "SIO_PROC0";
|
||||
IRQ[IRQ["SIO_PROC1"] = 16] = "SIO_PROC1";
|
||||
IRQ[IRQ["CLOCKS"] = 17] = "CLOCKS";
|
||||
IRQ[IRQ["SPI0"] = 18] = "SPI0";
|
||||
IRQ[IRQ["SPI1"] = 19] = "SPI1";
|
||||
IRQ[IRQ["UART0"] = 20] = "UART0";
|
||||
IRQ[IRQ["UART1"] = 21] = "UART1";
|
||||
IRQ[IRQ["ADC_FIFO"] = 22] = "ADC_FIFO";
|
||||
IRQ[IRQ["I2C0"] = 23] = "I2C0";
|
||||
IRQ[IRQ["I2C1"] = 24] = "I2C1";
|
||||
IRQ[IRQ["RTC"] = 25] = "RTC";
|
||||
})(IRQ || (IRQ = {}));
|
||||
export const MAX_HARDWARE_IRQ = IRQ.RTC;
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
import { IRQ } from '../irq.js';
|
||||
import { FIFO } from '../utils/fifo.js';
|
||||
import { DREQChannel } from './dma.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const CS = 0x00; // ADC Control and Status
|
||||
const RESULT = 0x04; // Result of most recent ADC conversion
|
||||
const FCS = 0x08; // FIFO control and status
|
||||
const FIFO_REG = 0x0c; // Conversion result FIFO
|
||||
const DIV = 0x10; // Clock divider.0x14 INTR Raw Interrupts
|
||||
const INTR = 0x14; // Raw Interrupts
|
||||
const INTE = 0x18; // Interrupt Enable
|
||||
const INTF = 0x1c; // Interrupt Force
|
||||
const INTS = 0x20; // Interrupt status after masking & forcing
|
||||
// CS bits
|
||||
const CS_RROBIN_MASK = 0x1f;
|
||||
const CS_RROBIN_SHIFT = 16;
|
||||
const CS_AINSEL_MASK = 0x7;
|
||||
const CS_AINSEL_SHIFT = 12;
|
||||
const CS_ERR_STICKY = 1 << 10;
|
||||
const CS_ERR = 1 << 9;
|
||||
const CS_READY = 1 << 8;
|
||||
const CS_START_MANY = 1 << 3;
|
||||
const CS_START_ONE = 1 << 2;
|
||||
const CS_TS_EN = 1 << 1;
|
||||
const CS_EN = 1 << 0;
|
||||
const CS_WRITE_MASK = (CS_RROBIN_MASK << CS_RROBIN_SHIFT) |
|
||||
(CS_AINSEL_MASK << CS_AINSEL_SHIFT) |
|
||||
CS_START_MANY |
|
||||
CS_START_ONE |
|
||||
CS_TS_EN |
|
||||
CS_EN;
|
||||
// FCS bits
|
||||
const FCS_THRES_MASK = 0xf;
|
||||
const FCS_THRESH_SHIFT = 24;
|
||||
const FCS_LEVEL_MASK = 0xf;
|
||||
const FCS_LEVEL_SHIFT = 16;
|
||||
const FCS_OVER = 1 << 11;
|
||||
const FCS_UNDER = 1 << 10;
|
||||
const FCS_FULL = 1 << 9;
|
||||
const FCS_EMPTY = 1 << 8;
|
||||
const FCS_DREQ_EN = 1 << 3;
|
||||
const FCS_ERR = 1 << 2;
|
||||
const FCS_SHIFT = 1 << 1;
|
||||
const FCS_EN = 1 << 0;
|
||||
const FCS_WRITE_MASK = (FCS_THRES_MASK << FCS_THRESH_SHIFT) | FCS_DREQ_EN | FCS_ERR | FCS_SHIFT | FCS_EN;
|
||||
// FIFO_REG bits
|
||||
const FIFO_ERR = 1 << 15;
|
||||
// DIV bits
|
||||
const DIV_INT_MASK = 0xffff;
|
||||
const DIV_INT_SHIFT = 8;
|
||||
const DIV_FRAC_MASK = 0xff;
|
||||
const DIV_FRAC_SHIFT = 0;
|
||||
// Interrupt bits
|
||||
const FIFO_INT = 1 << 0;
|
||||
export class RPADC extends BasePeripheral {
|
||||
get temperatueEnable() {
|
||||
return this.cs & CS_TS_EN;
|
||||
}
|
||||
get enabled() {
|
||||
return this.cs & CS_EN;
|
||||
}
|
||||
get divider() {
|
||||
return (1 +
|
||||
((this.clockDiv >> DIV_INT_SHIFT) & DIV_INT_MASK) +
|
||||
((this.clockDiv >> DIV_FRAC_SHIFT) & DIV_FRAC_MASK) / 256);
|
||||
}
|
||||
get intRaw() {
|
||||
const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK;
|
||||
return this.fifo.itemCount >= thres ? FIFO_INT : 0;
|
||||
}
|
||||
get intStatus() {
|
||||
return (this.intRaw & this.intEnable) | this.intForce;
|
||||
}
|
||||
get activeChannel() {
|
||||
return (this.cs >> CS_AINSEL_SHIFT) & CS_AINSEL_MASK;
|
||||
}
|
||||
set activeChannel(channel) {
|
||||
this.cs &= ~(CS_AINSEL_MASK << CS_AINSEL_SHIFT);
|
||||
this.cs |= (channel & CS_AINSEL_SHIFT) << CS_AINSEL_SHIFT;
|
||||
}
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
/* Number of ADC channels */
|
||||
this.numChannels = 5;
|
||||
/** ADC resolution (in bits) */
|
||||
this.resolution = 12;
|
||||
/** Time to read a single sample, in microseconds */
|
||||
this.sampleTime = 2;
|
||||
/**
|
||||
* ADC Channel values. Channels 0...3 are connected to GPIO 26...29, and channel 4 is connected to the built-in
|
||||
* temperature sensor: T=27-(ADC_voltage-0.706)/0.001721.
|
||||
*
|
||||
* Changing the values will change the ADC reading, unless you override onADCRead() with a custom implementation.
|
||||
*/
|
||||
this.channelValues = [0, 0, 0, 0, 0];
|
||||
/**
|
||||
* Invoked whenever the emulated code performs an ADC read.
|
||||
*
|
||||
* The default implementation reads the result from the `channelValues` array, and then calls
|
||||
* completeADCRead() after `sampleTime` microseconds.
|
||||
*
|
||||
* If you override the default implementation, make sure to call `completeADCRead()` after
|
||||
* `sampleTime` microseconds (or else the ADC read will never complete).
|
||||
*/
|
||||
this.onADCRead = (channel) => {
|
||||
// Default implementation
|
||||
this.currentChannel = channel;
|
||||
this.sampleAlarm.schedule(this.sampleTime * 1000);
|
||||
};
|
||||
this.fifo = new FIFO(4);
|
||||
this.dreq = DREQChannel.DREQ_ADC;
|
||||
// Registers
|
||||
this.cs = 0;
|
||||
this.fcs = 0;
|
||||
this.clockDiv = 0;
|
||||
this.intEnable = 0;
|
||||
this.intForce = 0;
|
||||
this.result = 0;
|
||||
// Status
|
||||
this.busy = false;
|
||||
this.err = false;
|
||||
this.currentChannel = 0;
|
||||
this.sampleAlarm = this.rp2040.clock.createAlarm(() => this.completeADCRead(this.channelValues[this.currentChannel], false));
|
||||
this.multiShotAlarm = this.rp2040.clock.createAlarm(() => {
|
||||
if (this.cs & CS_START_MANY) {
|
||||
this.startADCRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
checkInterrupts() {
|
||||
this.rp2040.setInterrupt(IRQ.ADC_FIFO, !!this.intStatus);
|
||||
}
|
||||
startADCRead() {
|
||||
this.busy = true;
|
||||
this.onADCRead(this.activeChannel);
|
||||
}
|
||||
updateDMA() {
|
||||
if (this.fcs & FCS_DREQ_EN) {
|
||||
const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK;
|
||||
if (this.fifo.itemCount >= thres) {
|
||||
this.rp2040.dma.setDREQ(this.dreq);
|
||||
}
|
||||
else {
|
||||
this.rp2040.dma.clearDREQ(this.dreq);
|
||||
}
|
||||
}
|
||||
}
|
||||
completeADCRead(value, error) {
|
||||
this.busy = false;
|
||||
this.result = value;
|
||||
if (error) {
|
||||
this.cs |= CS_ERR_STICKY | CS_ERR;
|
||||
}
|
||||
else {
|
||||
this.cs &= ~CS_ERR;
|
||||
}
|
||||
// FIFO
|
||||
if (this.fcs & FCS_EN) {
|
||||
if (this.fifo.full) {
|
||||
this.fcs |= FCS_OVER;
|
||||
}
|
||||
else {
|
||||
value &= 0xfff; // 12 bits
|
||||
if (this.fcs & FCS_SHIFT) {
|
||||
value >>= 4;
|
||||
}
|
||||
if (error && this.fcs & FCS_ERR) {
|
||||
value |= FIFO_ERR;
|
||||
}
|
||||
this.fifo.push(value);
|
||||
this.updateDMA();
|
||||
this.checkInterrupts();
|
||||
}
|
||||
}
|
||||
// Round-robin
|
||||
const round = (this.cs >> CS_RROBIN_SHIFT) & CS_RROBIN_MASK;
|
||||
if (round) {
|
||||
let channel = this.activeChannel + 1;
|
||||
while (!(round & (1 << channel))) {
|
||||
channel = (channel + 1) % this.numChannels;
|
||||
}
|
||||
this.activeChannel = channel;
|
||||
}
|
||||
// Multi-shot conversions
|
||||
if (this.cs & CS_START_MANY) {
|
||||
const clockMHZ = 48;
|
||||
const sampleTicks = clockMHZ * this.sampleTime;
|
||||
if (this.divider > sampleTicks) {
|
||||
// clock runs at 48MHz, subtract 2uS
|
||||
const micros = (this.divider - sampleTicks) / clockMHZ;
|
||||
this.multiShotAlarm.schedule(micros * 1000);
|
||||
}
|
||||
else {
|
||||
this.startADCRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case CS:
|
||||
return this.cs | (this.err ? CS_ERR : 0) | (this.busy ? 0 : CS_READY);
|
||||
case RESULT:
|
||||
return this.result;
|
||||
case FCS:
|
||||
return (this.fcs |
|
||||
((this.fifo.itemCount & FCS_LEVEL_MASK) << FCS_LEVEL_SHIFT) |
|
||||
(this.fifo.full ? FCS_FULL : 0) |
|
||||
(this.fifo.empty ? FCS_EMPTY : 0));
|
||||
case FIFO_REG:
|
||||
if (this.fifo.empty) {
|
||||
this.fcs |= FCS_UNDER;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
const value = this.fifo.pull();
|
||||
this.updateDMA();
|
||||
return value;
|
||||
}
|
||||
case DIV:
|
||||
return this.clockDiv;
|
||||
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) {
|
||||
switch (offset) {
|
||||
case CS:
|
||||
this.fcs &= ~(value & CS_ERR_STICKY); // Write-clear bits
|
||||
this.cs = (this.cs & ~CS_WRITE_MASK) | (value & CS_WRITE_MASK);
|
||||
if (value & CS_EN && !this.busy && (value & CS_START_ONE || value & CS_START_MANY)) {
|
||||
this.startADCRead();
|
||||
}
|
||||
break;
|
||||
case FCS:
|
||||
this.fcs &= ~(value & (FCS_OVER | FCS_UNDER)); // Write-clear bits
|
||||
this.fcs = (this.fcs & ~FCS_WRITE_MASK) | (value & FCS_WRITE_MASK);
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case DIV:
|
||||
this.clockDiv = value;
|
||||
break;
|
||||
case INTE:
|
||||
this.intEnable = value & FIFO_INT;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case INTF:
|
||||
this.intForce = value & FIFO_INT;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
/** Bus priority acknowledge */
|
||||
const BUS_PRIORITY_ACK = 0x004;
|
||||
/** Bus fabric performance counter 0 */
|
||||
const PERFCTR0 = 0x008;
|
||||
/** Bus fabric performance event select for PERFCTR0 */
|
||||
const PERFSEL0 = 0x00c;
|
||||
/** Bus fabric performance counter 1 */
|
||||
const PERFCTR1 = 0x010;
|
||||
/** Bus fabric performance event select for PERFCTR1 */
|
||||
const PERFSEL1 = 0x014;
|
||||
/** Bus fabric performance counter 2 */
|
||||
const PERFCTR2 = 0x018;
|
||||
/** Bus fabric performance event select for PERFCTR2 */
|
||||
const PERFSEL2 = 0x01c;
|
||||
/** Bus fabric performance counter 3 */
|
||||
const PERFCTR3 = 0x020;
|
||||
/** Bus fabric performance event select for PERFCTR3 */
|
||||
const PERFSEL3 = 0x024;
|
||||
export class RPBUSCTRL extends BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
this.voltageSelect = 0;
|
||||
this.perfCtr = [0, 0, 0, 0];
|
||||
this.perfSel = [0x1f, 0x1f, 0x1f, 0x1f];
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case BUS_PRIORITY_ACK:
|
||||
return 1;
|
||||
case PERFCTR0:
|
||||
return this.perfCtr[0];
|
||||
case PERFSEL0:
|
||||
return this.perfSel[0];
|
||||
case PERFCTR1:
|
||||
return this.perfCtr[1];
|
||||
case PERFSEL1:
|
||||
return this.perfSel[1];
|
||||
case PERFCTR2:
|
||||
return this.perfCtr[2];
|
||||
case PERFSEL2:
|
||||
return this.perfSel[2];
|
||||
case PERFCTR3:
|
||||
return this.perfCtr[3];
|
||||
case PERFSEL3:
|
||||
return this.perfSel[3];
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case PERFCTR0:
|
||||
this.perfCtr[0] = 0;
|
||||
break;
|
||||
case PERFSEL0:
|
||||
this.perfSel[0] = value & 0x1f;
|
||||
break;
|
||||
case PERFCTR1:
|
||||
this.perfCtr[1] = 0;
|
||||
break;
|
||||
case PERFSEL1:
|
||||
this.perfSel[1] = value & 0x1f;
|
||||
break;
|
||||
case PERFCTR2:
|
||||
this.perfCtr[2] = 0;
|
||||
break;
|
||||
case PERFSEL2:
|
||||
this.perfSel[2] = value & 0x1f;
|
||||
break;
|
||||
case PERFCTR3:
|
||||
this.perfCtr[3] = 0;
|
||||
break;
|
||||
case PERFSEL3:
|
||||
this.perfSel[3] = value & 0x1f;
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const CLK_GPOUT0_CTRL = 0x00;
|
||||
const CLK_GPOUT0_DIV = 0x04;
|
||||
const CLK_GPOUT0_SELECTED = 0x8;
|
||||
const CLK_GPOUT1_CTRL = 0x0c;
|
||||
const CLK_GPOUT1_DIV = 0x10;
|
||||
const CLK_GPOUT1_SELECTED = 0x14;
|
||||
const CLK_GPOUT2_CTRL = 0x18;
|
||||
const CLK_GPOUT2_DIV = 0x01c;
|
||||
const CLK_GPOUT2_SELECTED = 0x20;
|
||||
const CLK_GPOUT3_CTRL = 0x24;
|
||||
const CLK_GPOUT3_DIV = 0x28;
|
||||
const CLK_GPOUT3_SELECTED = 0x2c;
|
||||
const CLK_REF_CTRL = 0x30;
|
||||
const CLK_REF_DIV = 0x34;
|
||||
const CLK_REF_SELECTED = 0x38;
|
||||
const CLK_SYS_CTRL = 0x3c;
|
||||
const CLK_SYS_DIV = 0x40;
|
||||
const CLK_SYS_SELECTED = 0x44;
|
||||
const CLK_PERI_CTRL = 0x48;
|
||||
const CLK_PERI_DIV = 0x4c;
|
||||
const CLK_PERI_SELECTED = 0x50;
|
||||
const CLK_USB_CTRL = 0x54;
|
||||
const CLK_USB_DIV = 0x58;
|
||||
const CLK_USB_SELECTED = 0x5c;
|
||||
const CLK_ADC_CTRL = 0x60;
|
||||
const CLK_ADC_DIV = 0x64;
|
||||
const CLK_ADC_SELECTED = 0x68;
|
||||
const CLK_RTC_CTRL = 0x6c;
|
||||
const CLK_RTC_DIV = 0x70;
|
||||
const CLK_RTC_SELECTED = 0x74;
|
||||
const CLK_SYS_RESUS_CTRL = 0x78;
|
||||
const CLK_SYS_RESUS_STATUS = 0x7c;
|
||||
export class RPClocks extends BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
this.gpout0Ctrl = 0;
|
||||
this.gpout0Div = 0x100;
|
||||
this.gpout1Ctrl = 0;
|
||||
this.gpout1Div = 0x100;
|
||||
this.gpout2Ctrl = 0;
|
||||
this.gpout2Div = 0x100;
|
||||
this.gpout3Ctrl = 0;
|
||||
this.gpout3Div = 0x100;
|
||||
this.refCtrl = 0;
|
||||
this.refDiv = 0x100;
|
||||
this.periCtrl = 0;
|
||||
this.periDiv = 0x100;
|
||||
this.usbCtrl = 0;
|
||||
this.usbDiv = 0x100;
|
||||
this.sysCtrl = 0;
|
||||
this.sysDiv = 0x100;
|
||||
this.adcCtrl = 0;
|
||||
this.adcDiv = 0x100;
|
||||
this.rtcCtrl = 0;
|
||||
this.rtcDiv = 0x100;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case CLK_GPOUT0_CTRL:
|
||||
return this.gpout0Ctrl & 0b100110001110111100000;
|
||||
case CLK_GPOUT0_DIV:
|
||||
return this.gpout0Div;
|
||||
case CLK_GPOUT0_SELECTED:
|
||||
return 1;
|
||||
case CLK_GPOUT1_CTRL:
|
||||
return this.gpout1Ctrl & 0b100110001110111100000;
|
||||
case CLK_GPOUT1_DIV:
|
||||
return this.gpout1Div;
|
||||
case CLK_GPOUT1_SELECTED:
|
||||
return 1;
|
||||
case CLK_GPOUT2_CTRL:
|
||||
return this.gpout2Ctrl & 0b100110001110111100000;
|
||||
case CLK_GPOUT2_DIV:
|
||||
return this.gpout2Div;
|
||||
case CLK_GPOUT2_SELECTED:
|
||||
return 1;
|
||||
case CLK_GPOUT3_CTRL:
|
||||
return this.gpout3Ctrl & 0b100110001110111100000;
|
||||
case CLK_GPOUT3_DIV:
|
||||
return this.gpout3Div;
|
||||
case CLK_GPOUT3_SELECTED:
|
||||
return 1;
|
||||
case CLK_REF_CTRL:
|
||||
return this.refCtrl & 0b000001100011;
|
||||
case CLK_REF_DIV:
|
||||
return this.refDiv & 0x30; // b8..9 = int divisor. no frac divisor present
|
||||
case CLK_REF_SELECTED:
|
||||
return 1 << (this.refCtrl & 0x03);
|
||||
case CLK_SYS_CTRL:
|
||||
return this.sysCtrl & 0b000011100001;
|
||||
case CLK_SYS_DIV:
|
||||
return this.sysDiv;
|
||||
case CLK_SYS_SELECTED:
|
||||
return 1 << (this.sysCtrl & 0x01);
|
||||
case CLK_PERI_CTRL:
|
||||
return this.periCtrl & 0b110011100000;
|
||||
case CLK_PERI_DIV:
|
||||
return this.periDiv;
|
||||
case CLK_PERI_SELECTED:
|
||||
return 1;
|
||||
case CLK_USB_CTRL:
|
||||
return this.usbCtrl & 0b100110000110011100000;
|
||||
case CLK_USB_DIV:
|
||||
return this.usbDiv;
|
||||
case CLK_USB_SELECTED:
|
||||
return 1;
|
||||
case CLK_ADC_CTRL:
|
||||
return this.adcCtrl & 0b100110000110011100000;
|
||||
case CLK_ADC_DIV:
|
||||
return this.adcDiv & 0x30;
|
||||
case CLK_ADC_SELECTED:
|
||||
return 1;
|
||||
case CLK_RTC_CTRL:
|
||||
return this.rtcCtrl & 0b100110000110011100000;
|
||||
case CLK_RTC_DIV:
|
||||
return this.rtcDiv & 0x30;
|
||||
case CLK_RTC_SELECTED:
|
||||
return 1;
|
||||
case CLK_SYS_RESUS_CTRL:
|
||||
return 0xff;
|
||||
case CLK_SYS_RESUS_STATUS:
|
||||
return 0; /* clock resus not implemented */
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case CLK_GPOUT0_CTRL:
|
||||
this.gpout0Ctrl = value;
|
||||
break;
|
||||
case CLK_GPOUT0_DIV:
|
||||
this.gpout0Div = value;
|
||||
break;
|
||||
case CLK_GPOUT1_CTRL:
|
||||
this.gpout1Ctrl = value;
|
||||
break;
|
||||
case CLK_GPOUT1_DIV:
|
||||
this.gpout1Div = value;
|
||||
break;
|
||||
case CLK_GPOUT2_CTRL:
|
||||
this.gpout2Ctrl = value;
|
||||
break;
|
||||
case CLK_GPOUT2_DIV:
|
||||
this.gpout2Div = value;
|
||||
break;
|
||||
case CLK_GPOUT3_CTRL:
|
||||
this.gpout3Ctrl = value;
|
||||
break;
|
||||
case CLK_GPOUT3_DIV:
|
||||
this.gpout3Div = value;
|
||||
break;
|
||||
case CLK_REF_CTRL:
|
||||
this.refCtrl = value;
|
||||
break;
|
||||
case CLK_REF_DIV:
|
||||
this.refDiv = value;
|
||||
break;
|
||||
case CLK_SYS_CTRL:
|
||||
this.sysCtrl = value;
|
||||
break;
|
||||
case CLK_SYS_DIV:
|
||||
this.sysDiv = value;
|
||||
break;
|
||||
case CLK_PERI_CTRL:
|
||||
this.periCtrl = value;
|
||||
break;
|
||||
case CLK_PERI_DIV:
|
||||
this.periDiv = value;
|
||||
break;
|
||||
case CLK_USB_CTRL:
|
||||
this.usbCtrl = value;
|
||||
break;
|
||||
case CLK_USB_DIV:
|
||||
this.usbDiv = value;
|
||||
break;
|
||||
case CLK_ADC_CTRL:
|
||||
this.adcCtrl = value;
|
||||
break;
|
||||
case CLK_ADC_DIV:
|
||||
this.adcDiv = value;
|
||||
break;
|
||||
case CLK_RTC_CTRL:
|
||||
this.rtcCtrl = value;
|
||||
break;
|
||||
case CLK_RTC_DIV:
|
||||
this.rtcDiv = value;
|
||||
break;
|
||||
case CLK_SYS_RESUS_CTRL:
|
||||
return; /* clock resus not implemented */
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
import { IRQ } from '../irq.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
export var DREQChannel;
|
||||
(function (DREQChannel) {
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_TX0"] = 0] = "DREQ_PIO0_TX0";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_TX1"] = 1] = "DREQ_PIO0_TX1";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_TX2"] = 2] = "DREQ_PIO0_TX2";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_TX3"] = 3] = "DREQ_PIO0_TX3";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_RX0"] = 4] = "DREQ_PIO0_RX0";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_RX1"] = 5] = "DREQ_PIO0_RX1";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_RX2"] = 6] = "DREQ_PIO0_RX2";
|
||||
DREQChannel[DREQChannel["DREQ_PIO0_RX3"] = 7] = "DREQ_PIO0_RX3";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_TX0"] = 8] = "DREQ_PIO1_TX0";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_TX1"] = 9] = "DREQ_PIO1_TX1";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_TX2"] = 10] = "DREQ_PIO1_TX2";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_TX3"] = 11] = "DREQ_PIO1_TX3";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_RX0"] = 12] = "DREQ_PIO1_RX0";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_RX1"] = 13] = "DREQ_PIO1_RX1";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_RX2"] = 14] = "DREQ_PIO1_RX2";
|
||||
DREQChannel[DREQChannel["DREQ_PIO1_RX3"] = 15] = "DREQ_PIO1_RX3";
|
||||
DREQChannel[DREQChannel["DREQ_SPI0_TX"] = 16] = "DREQ_SPI0_TX";
|
||||
DREQChannel[DREQChannel["DREQ_SPI0_RX"] = 17] = "DREQ_SPI0_RX";
|
||||
DREQChannel[DREQChannel["DREQ_SPI1_TX"] = 18] = "DREQ_SPI1_TX";
|
||||
DREQChannel[DREQChannel["DREQ_SPI1_RX"] = 19] = "DREQ_SPI1_RX";
|
||||
DREQChannel[DREQChannel["DREQ_UART0_TX"] = 20] = "DREQ_UART0_TX";
|
||||
DREQChannel[DREQChannel["DREQ_UART0_RX"] = 21] = "DREQ_UART0_RX";
|
||||
DREQChannel[DREQChannel["DREQ_UART1_TX"] = 22] = "DREQ_UART1_TX";
|
||||
DREQChannel[DREQChannel["DREQ_UART1_RX"] = 23] = "DREQ_UART1_RX";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP0"] = 24] = "DREQ_PWM_WRAP0";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP1"] = 25] = "DREQ_PWM_WRAP1";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP2"] = 26] = "DREQ_PWM_WRAP2";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP3"] = 27] = "DREQ_PWM_WRAP3";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP4"] = 28] = "DREQ_PWM_WRAP4";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP5"] = 29] = "DREQ_PWM_WRAP5";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP6"] = 30] = "DREQ_PWM_WRAP6";
|
||||
DREQChannel[DREQChannel["DREQ_PWM_WRAP7"] = 31] = "DREQ_PWM_WRAP7";
|
||||
DREQChannel[DREQChannel["DREQ_I2C0_TX"] = 32] = "DREQ_I2C0_TX";
|
||||
DREQChannel[DREQChannel["DREQ_I2C0_RX"] = 33] = "DREQ_I2C0_RX";
|
||||
DREQChannel[DREQChannel["DREQ_I2C1_TX"] = 34] = "DREQ_I2C1_TX";
|
||||
DREQChannel[DREQChannel["DREQ_I2C1_RX"] = 35] = "DREQ_I2C1_RX";
|
||||
DREQChannel[DREQChannel["DREQ_ADC"] = 36] = "DREQ_ADC";
|
||||
DREQChannel[DREQChannel["DREQ_XIP_STREAM"] = 37] = "DREQ_XIP_STREAM";
|
||||
DREQChannel[DREQChannel["DREQ_XIP_SSITX"] = 38] = "DREQ_XIP_SSITX";
|
||||
DREQChannel[DREQChannel["DREQ_XIP_SSIRX"] = 39] = "DREQ_XIP_SSIRX";
|
||||
DREQChannel[DREQChannel["DREQ_MAX"] = 40] = "DREQ_MAX";
|
||||
})(DREQChannel || (DREQChannel = {}));
|
||||
var TREQ;
|
||||
(function (TREQ) {
|
||||
TREQ[TREQ["Timer0"] = 59] = "Timer0";
|
||||
TREQ[TREQ["Timer1"] = 60] = "Timer1";
|
||||
TREQ[TREQ["Timer2"] = 61] = "Timer2";
|
||||
TREQ[TREQ["Timer3"] = 62] = "Timer3";
|
||||
TREQ[TREQ["Permanent"] = 63] = "Permanent";
|
||||
})(TREQ || (TREQ = {}));
|
||||
// Per-channel registers
|
||||
const CHn_READ_ADDR = 0x000; // DMA Channel n Read Address pointer
|
||||
const CHn_WRITE_ADDR = 0x004; // DMA Channel n Write Address pointer
|
||||
const CHn_TRANS_COUNT = 0x008; // DMA Channel n Transfer Count
|
||||
const CHn_CTRL_TRIG = 0x00c; // DMA Channel n Control and Status
|
||||
const CHn_AL1_CTRL = 0x010; // Alias for channel n CTRL register
|
||||
const CHn_AL1_READ_ADDR = 0x014; // Alias for channel n READ_ADDR register
|
||||
const CHn_AL1_WRITE_ADDR = 0x018; // Alias for channel n WRITE_ADDR register
|
||||
const CHn_AL1_TRANS_COUNT_TRIG = 0x01c; // Alias for channel n TRANS_COUNT register
|
||||
const CHn_AL2_CTRL = 0x020; // Alias for channel n CTRL register
|
||||
const CHn_AL2_TRANS_COUNT = 0x024; // Alias for channel n TRANS_COUNT register
|
||||
const CHn_AL2_READ_ADDR = 0x028; // Alias for channel n READ_ADDR register
|
||||
const CHn_AL2_WRITE_ADDR_TRIG = 0x02c; // Alias for channel n WRITE_ADDR register
|
||||
const CHn_AL3_CTRL = 0x030; // Alias for channel n CTRL register
|
||||
const CHn_AL3_WRITE_ADDR = 0x034; // Alias for channel n WRITE_ADDR register
|
||||
const CHn_AL3_TRANS_COUNT = 0x038; // Alias for channel n TRANS_COUNT register
|
||||
const CHn_AL3_READ_ADDR_TRIG = 0x03c; // Alias for channel n READ_ADDR register
|
||||
const CHn_DBG_CTDREQ = 0x800;
|
||||
const CHn_DBG_TCR = 0x804;
|
||||
const CHANNEL_REGISTERS_SIZE = 12 * 0x40;
|
||||
const CHANNEL_REGISTERS_MASK = 0x83f;
|
||||
// General DMA registers
|
||||
const INTR = 0x400; // Interrupt Status (raw)
|
||||
const INTE0 = 0x404; // Interrupt Enables for IRQ 0
|
||||
const INTF0 = 0x408; // Force Interrupts
|
||||
const INTS0 = 0x40c; // Interrupt Status for IRQ 0
|
||||
const INTE1 = 0x414; // Interrupt Enables for IRQ 1
|
||||
const INTF1 = 0x418; // Force Interrupts for IRQ 1
|
||||
const INTS1 = 0x41c; // Interrupt Status (masked) for IRQ 1
|
||||
const TIMER0 = 0x420; // Pacing (X/Y) Fractional Timer
|
||||
const TIMER1 = 0x424; // Pacing (X/Y) Fractional Timer
|
||||
const TIMER2 = 0x428; // Pacing (X/Y) Fractional Timer
|
||||
const TIMER3 = 0x42c; // Pacing (X/Y) Fractional Timer
|
||||
const MULTI_CHAN_TRIGGER = 0x430; // Trigger one or more channels simultaneously
|
||||
const SNIFF_CTRL = 0x434; // Sniffer Control
|
||||
const SNIFF_DATA = 0x438; // Data accumulator for sniff hardware
|
||||
const FIFO_LEVELS = 0x440; // Debug RAF, WAF, TDF levels
|
||||
const CHAN_ABORT = 0x444; // Abort an in-progress transfer sequence on one or more channels
|
||||
const N_CHANNELS = 0x448;
|
||||
// CHn_CTRL_TRIG bits
|
||||
const AHB_ERROR = 1 << 31;
|
||||
const READ_ERROR = 1 << 30;
|
||||
const WRITE_ERROR = 1 << 29;
|
||||
const BUSY = 1 << 24;
|
||||
const SNIFF_EN = 1 << 23;
|
||||
const BSWAP = 1 << 22;
|
||||
const IRQ_QUIET = 1 << 21;
|
||||
const TREQ_SEL_MASK = 0x3f;
|
||||
const TREQ_SEL_SHIFT = 15;
|
||||
const CHAIN_TO_MASK = 0xf;
|
||||
const CHAIN_TO_SHIFT = 11;
|
||||
const RING_SEL = 1 << 10;
|
||||
const RING_SIZE_MASK = 0xf;
|
||||
const RING_SIZE_SHIFT = 6;
|
||||
const INCR_WRITE = 1 << 5;
|
||||
const INCR_READ = 1 << 4;
|
||||
const DATA_SIZE_MASK = 0x3;
|
||||
const DATA_SIZE_SHIFT = 2;
|
||||
const HIGH_PRIORITY = 1 << 1;
|
||||
const EN = 1 << 0;
|
||||
const CHn_CTRL_TRIG_WRITE_MASK = 0xffffff;
|
||||
const CHn_CTRL_TRIG_WC_MASK = READ_ERROR | WRITE_ERROR;
|
||||
export class RPDMAChannel {
|
||||
constructor(dma, rp2040, index) {
|
||||
this.dma = dma;
|
||||
this.rp2040 = rp2040;
|
||||
this.index = index;
|
||||
this.ctrl = 0;
|
||||
this.readAddr = 0;
|
||||
this.writeAddr = 0;
|
||||
this.transCount = 0;
|
||||
this.dreqCounter = 0;
|
||||
this.transCountReload = 0;
|
||||
this.treqValue = 0;
|
||||
this.dataSize = 1;
|
||||
this.chainTo = 0;
|
||||
this.ringMask = 0;
|
||||
this.transferFn = () => 0;
|
||||
this.transfer8 = () => {
|
||||
const { rp2040 } = this;
|
||||
rp2040.writeUint8(this.writeAddr, rp2040.readUint8(this.readAddr));
|
||||
};
|
||||
this.transfer16 = () => {
|
||||
const { rp2040 } = this;
|
||||
rp2040.writeUint16(this.writeAddr, rp2040.readUint16(this.readAddr));
|
||||
};
|
||||
this.transferSwap16 = () => {
|
||||
const { rp2040 } = this;
|
||||
const input = rp2040.readUint16(this.readAddr);
|
||||
rp2040.writeUint16(this.writeAddr, ((input & 0xff) << 8) | (input >> 8));
|
||||
};
|
||||
this.transfer32 = () => {
|
||||
const { rp2040 } = this;
|
||||
rp2040.writeUint32(this.writeAddr, rp2040.readUint32(this.readAddr));
|
||||
};
|
||||
this.transferSwap32 = () => {
|
||||
const { rp2040 } = this;
|
||||
const input = rp2040.readUint32(this.readAddr);
|
||||
rp2040.writeUint32(this.writeAddr, ((input & 0x000000ff) << 24) |
|
||||
((input & 0x0000ff00) << 8) |
|
||||
((input & 0x00ff0000) >> 8) |
|
||||
((input >> 24) & 0xff));
|
||||
};
|
||||
this.transfer = () => {
|
||||
var _a;
|
||||
const { ctrl, dataSize, ringMask } = this;
|
||||
this.transferFn();
|
||||
if (ctrl & INCR_READ) {
|
||||
if (ringMask && !(ctrl & RING_SEL)) {
|
||||
this.readAddr = (this.readAddr & ~ringMask) | ((this.readAddr + dataSize) & ringMask);
|
||||
}
|
||||
else {
|
||||
this.readAddr += dataSize;
|
||||
}
|
||||
}
|
||||
if (ctrl & INCR_WRITE) {
|
||||
if (ringMask && ctrl & RING_SEL) {
|
||||
this.writeAddr = (this.writeAddr & ~ringMask) | ((this.writeAddr + dataSize) & ringMask);
|
||||
}
|
||||
else {
|
||||
this.writeAddr += dataSize;
|
||||
}
|
||||
}
|
||||
this.transCount--;
|
||||
if (this.transCount > 0) {
|
||||
this.scheduleTransfer();
|
||||
}
|
||||
else {
|
||||
this.ctrl &= ~BUSY;
|
||||
if (!(this.ctrl & IRQ_QUIET)) {
|
||||
this.dma.intRaw |= 1 << this.index;
|
||||
this.dma.checkInterrupts();
|
||||
}
|
||||
if (this.chainTo !== this.index) {
|
||||
(_a = this.dma.channels[this.chainTo]) === null || _a === void 0 ? void 0 : _a.start();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.transferAlarm = rp2040.clock.createAlarm(this.transfer);
|
||||
this.reset();
|
||||
}
|
||||
start() {
|
||||
if (!(this.ctrl & EN) || this.ctrl & BUSY) {
|
||||
return;
|
||||
}
|
||||
this.ctrl |= BUSY;
|
||||
this.transCount = this.transCountReload;
|
||||
if (this.transCount) {
|
||||
this.scheduleTransfer();
|
||||
}
|
||||
}
|
||||
get treq() {
|
||||
return this.treqValue;
|
||||
}
|
||||
get active() {
|
||||
return this.ctrl & EN && this.ctrl & BUSY;
|
||||
}
|
||||
scheduleTransfer() {
|
||||
if (this.dma.dreq[this.treqValue] || this.treqValue === TREQ.Permanent) {
|
||||
this.transferAlarm.schedule(0);
|
||||
}
|
||||
else {
|
||||
const delay = this.dma.getTimer(this.treqValue);
|
||||
if (delay) {
|
||||
this.transferAlarm.schedule(delay * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
abort() {
|
||||
this.ctrl &= ~BUSY;
|
||||
this.transferAlarm.cancel();
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case CHn_READ_ADDR:
|
||||
case CHn_AL1_READ_ADDR:
|
||||
case CHn_AL2_READ_ADDR:
|
||||
case CHn_AL3_READ_ADDR_TRIG:
|
||||
return this.readAddr;
|
||||
case CHn_WRITE_ADDR:
|
||||
case CHn_AL1_WRITE_ADDR:
|
||||
case CHn_AL2_WRITE_ADDR_TRIG:
|
||||
case CHn_AL3_WRITE_ADDR:
|
||||
return this.writeAddr;
|
||||
case CHn_TRANS_COUNT:
|
||||
case CHn_AL1_TRANS_COUNT_TRIG:
|
||||
case CHn_AL2_TRANS_COUNT:
|
||||
case CHn_AL3_TRANS_COUNT:
|
||||
return this.transCount;
|
||||
case CHn_CTRL_TRIG:
|
||||
case CHn_AL1_CTRL:
|
||||
case CHn_AL2_CTRL:
|
||||
case CHn_AL3_CTRL:
|
||||
return this.ctrl;
|
||||
case CHn_DBG_CTDREQ:
|
||||
return this.dreqCounter;
|
||||
case CHn_DBG_TCR:
|
||||
return this.transCountReload;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case CHn_READ_ADDR:
|
||||
case CHn_AL1_READ_ADDR:
|
||||
case CHn_AL2_READ_ADDR:
|
||||
case CHn_AL3_READ_ADDR_TRIG:
|
||||
this.readAddr = value;
|
||||
break;
|
||||
case CHn_WRITE_ADDR:
|
||||
case CHn_AL1_WRITE_ADDR:
|
||||
case CHn_AL2_WRITE_ADDR_TRIG:
|
||||
case CHn_AL3_WRITE_ADDR:
|
||||
this.writeAddr = value;
|
||||
break;
|
||||
case CHn_TRANS_COUNT:
|
||||
case CHn_AL1_TRANS_COUNT_TRIG:
|
||||
case CHn_AL2_TRANS_COUNT:
|
||||
case CHn_AL3_TRANS_COUNT:
|
||||
this.transCountReload = value;
|
||||
break;
|
||||
case CHn_CTRL_TRIG:
|
||||
case CHn_AL1_CTRL:
|
||||
case CHn_AL2_CTRL:
|
||||
case CHn_AL3_CTRL: {
|
||||
this.ctrl = (this.ctrl & ~CHn_CTRL_TRIG_WRITE_MASK) | (value & CHn_CTRL_TRIG_WRITE_MASK);
|
||||
this.ctrl &= ~(value & CHn_CTRL_TRIG_WC_MASK); // Handle write-clear (WC) bits
|
||||
this.treqValue = (this.ctrl >> TREQ_SEL_SHIFT) & TREQ_SEL_MASK;
|
||||
this.chainTo = (this.ctrl >> CHAIN_TO_SHIFT) & CHAIN_TO_MASK;
|
||||
const ringSize = (this.ctrl >> RING_SIZE_SHIFT) & RING_SIZE_MASK;
|
||||
this.ringMask = ringSize ? (1 << ringSize) - 1 : 0;
|
||||
switch ((this.ctrl >> DATA_SIZE_SHIFT) & DATA_SIZE_MASK) {
|
||||
case 1:
|
||||
this.dataSize = 2;
|
||||
this.transferFn = this.ctrl & BSWAP ? this.transferSwap16 : this.transfer16;
|
||||
break;
|
||||
case 2:
|
||||
this.dataSize = 4;
|
||||
this.transferFn = this.ctrl & BSWAP ? this.transferSwap32 : this.transfer32;
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
this.transferFn = this.transfer8;
|
||||
this.dataSize = 1;
|
||||
}
|
||||
if (this.ctrl & EN && this.ctrl & BUSY) {
|
||||
this.scheduleTransfer();
|
||||
}
|
||||
if (!(this.ctrl & EN)) {
|
||||
this.transferAlarm.cancel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHn_DBG_CTDREQ:
|
||||
this.dreqCounter = 0;
|
||||
break;
|
||||
}
|
||||
if (offset === CHn_AL3_READ_ADDR_TRIG ||
|
||||
offset === CHn_AL2_WRITE_ADDR_TRIG ||
|
||||
offset === CHn_AL1_TRANS_COUNT_TRIG ||
|
||||
offset === CHn_CTRL_TRIG) {
|
||||
if (value) {
|
||||
this.start();
|
||||
}
|
||||
else if (this.ctrl & IRQ_QUIET) {
|
||||
// Null trigger interrupts
|
||||
this.dma.intRaw |= 1 << this.index;
|
||||
this.dma.checkInterrupts();
|
||||
}
|
||||
}
|
||||
}
|
||||
reset() {
|
||||
this.writeUint32(CHn_CTRL_TRIG, this.index << CHAIN_TO_SHIFT);
|
||||
}
|
||||
}
|
||||
export class RPDMA extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.channels = [
|
||||
new RPDMAChannel(this, this.rp2040, 0),
|
||||
new RPDMAChannel(this, this.rp2040, 1),
|
||||
new RPDMAChannel(this, this.rp2040, 2),
|
||||
new RPDMAChannel(this, this.rp2040, 3),
|
||||
new RPDMAChannel(this, this.rp2040, 4),
|
||||
new RPDMAChannel(this, this.rp2040, 5),
|
||||
new RPDMAChannel(this, this.rp2040, 6),
|
||||
new RPDMAChannel(this, this.rp2040, 7),
|
||||
new RPDMAChannel(this, this.rp2040, 8),
|
||||
new RPDMAChannel(this, this.rp2040, 9),
|
||||
new RPDMAChannel(this, this.rp2040, 10),
|
||||
new RPDMAChannel(this, this.rp2040, 11),
|
||||
];
|
||||
this.intRaw = 0;
|
||||
this.intEnable0 = 0;
|
||||
this.intForce0 = 0;
|
||||
this.intEnable1 = 0;
|
||||
this.intForce1 = 0;
|
||||
this.timer0 = 0;
|
||||
this.timer1 = 0;
|
||||
this.timer2 = 0;
|
||||
this.timer3 = 0;
|
||||
this.dreq = Array(DREQChannel.DREQ_MAX);
|
||||
}
|
||||
get intStatus0() {
|
||||
return (this.intRaw & this.intEnable0) | this.intForce0;
|
||||
}
|
||||
get intStatus1() {
|
||||
return (this.intRaw & this.intEnable1) | this.intForce1;
|
||||
}
|
||||
readUint32(offset) {
|
||||
if ((offset & 0x7ff) < CHANNEL_REGISTERS_SIZE) {
|
||||
const channelIndex = (offset & 0x7ff) >> 6;
|
||||
return this.channels[channelIndex].readUint32(offset & CHANNEL_REGISTERS_MASK);
|
||||
}
|
||||
switch (offset) {
|
||||
case TIMER0:
|
||||
return this.timer0;
|
||||
case TIMER1:
|
||||
return this.timer1;
|
||||
case TIMER2:
|
||||
return this.timer2;
|
||||
case TIMER3:
|
||||
return this.timer3;
|
||||
case INTR:
|
||||
return this.intRaw;
|
||||
case INTE0:
|
||||
return this.intEnable0;
|
||||
case INTF0:
|
||||
return this.intForce0;
|
||||
case INTS0:
|
||||
return this.intStatus0;
|
||||
case INTE1:
|
||||
return this.intEnable1;
|
||||
case INTF1:
|
||||
return this.intForce1;
|
||||
case INTS1:
|
||||
return this.intStatus1;
|
||||
case N_CHANNELS:
|
||||
return this.channels.length;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
if ((offset & 0x7ff) < CHANNEL_REGISTERS_SIZE) {
|
||||
const channelIndex = (offset & 0x7ff) >> 6;
|
||||
this.channels[channelIndex].writeUint32(offset & CHANNEL_REGISTERS_MASK, value);
|
||||
return;
|
||||
}
|
||||
switch (offset) {
|
||||
case TIMER0:
|
||||
this.timer0 = value;
|
||||
return;
|
||||
case TIMER1:
|
||||
this.timer1 = value;
|
||||
return;
|
||||
case TIMER2:
|
||||
this.timer2 = value;
|
||||
return;
|
||||
case TIMER3:
|
||||
this.timer3 = value;
|
||||
return;
|
||||
case INTR:
|
||||
case INTS0:
|
||||
case INTS1:
|
||||
this.intRaw &= ~this.rawWriteValue;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case INTE0:
|
||||
this.intEnable0 = value & 0xffff;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case INTF0:
|
||||
this.intForce0 = value & 0xffff;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case INTE1:
|
||||
this.intEnable1 = value & 0xffff;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case INTF1:
|
||||
this.intForce1 = value & 0xffff;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case MULTI_CHAN_TRIGGER:
|
||||
for (const chan of this.channels) {
|
||||
if (value & (1 << chan.index)) {
|
||||
chan.start();
|
||||
}
|
||||
}
|
||||
return;
|
||||
case CHAN_ABORT:
|
||||
for (const chan of this.channels) {
|
||||
if (value & (1 << chan.index)) {
|
||||
chan.abort();
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
setDREQ(dreqChannel) {
|
||||
const { dreq } = this;
|
||||
if (!dreq[dreqChannel]) {
|
||||
dreq[dreqChannel] = true;
|
||||
for (const channel of this.channels) {
|
||||
if (channel.treq === dreqChannel && channel.active) {
|
||||
channel.scheduleTransfer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearDREQ(dreqChannel) {
|
||||
this.dreq[dreqChannel] = false;
|
||||
}
|
||||
/**
|
||||
* Returns the number of microseconds for a cycle of the given DMA timer, or 0 if the timer is disabled.
|
||||
*/
|
||||
getTimer(treq) {
|
||||
let dividend = 0, divisor = 1;
|
||||
switch (treq) {
|
||||
case TREQ.Permanent:
|
||||
dividend = 1;
|
||||
divisor = 1;
|
||||
break;
|
||||
case TREQ.Timer0:
|
||||
dividend = this.timer0 >>> 16;
|
||||
divisor = this.timer0 & 0xffff;
|
||||
break;
|
||||
case TREQ.Timer1:
|
||||
dividend = this.timer1 >>> 16;
|
||||
divisor = this.timer1 & 0xffff;
|
||||
break;
|
||||
case TREQ.Timer2:
|
||||
dividend = this.timer2 >>> 16;
|
||||
divisor = this.timer2 & 0xffff;
|
||||
break;
|
||||
case TREQ.Timer3:
|
||||
dividend = this.timer3 >>> 36;
|
||||
divisor = this.timer3 & 0xffff;
|
||||
break;
|
||||
}
|
||||
if (divisor === 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((dividend / divisor) * 1e6) / this.rp2040.clkSys;
|
||||
}
|
||||
checkInterrupts() {
|
||||
this.rp2040.setInterrupt(IRQ.DMA_IRQ0, !!this.intStatus0);
|
||||
this.rp2040.setInterrupt(IRQ.DMA_IRQ1, !!this.intStatus1);
|
||||
}
|
||||
}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
import { FIFO } from '../utils/fifo.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const IC_CON = 0x00; // I2C Control Register
|
||||
const IC_TAR = 0x04; // I2C Target Address Register
|
||||
const IC_SAR = 0x08; // I2C Slave Address Register
|
||||
const IC_DATA_CMD = 0x10; // I2C Rx/Tx Data Buffer and Command Register
|
||||
const IC_SS_SCL_HCNT = 0x14; // Standard Speed I2C Clock SCL High Count Register
|
||||
const IC_SS_SCL_LCNT = 0x18; // Standard Speed I2C Clock SCL Low Count Register
|
||||
const IC_FS_SCL_HCNT = 0x1c; // Fast Mode or Fast Mode Plus I2C Clock SCL High Count Register
|
||||
const IC_FS_SCL_LCNT = 0x20; // Fast Mode or Fast Mode Plus I2C Clock SCL Low Count Register
|
||||
const IC_INTR_STAT = 0x2c; // I2C Interrupt Status Register
|
||||
const IC_INTR_MASK = 0x30; // I2C Interrupt Mask Register
|
||||
const IC_RAW_INTR_STAT = 0x34; // I2C Raw Interrupt Status Register
|
||||
const IC_RX_TL = 0x38; // I2C Receive FIFO Threshold Register
|
||||
const IC_TX_TL = 0x3c; // I2C Transmit FIFO Threshold Register
|
||||
const IC_CLR_INTR = 0x40; // Clear Combined and Individual Interrupt Register
|
||||
const IC_CLR_RX_UNDER = 0x44; // Clear RX_UNDER Interrupt Register
|
||||
const IC_CLR_RX_OVER = 0x48; // Clear RX_OVER Interrupt Register
|
||||
const IC_CLR_TX_OVER = 0x4c; // Clear TX_OVER Interrupt Register
|
||||
const IC_CLR_RD_REQ = 0x50; // Clear RD_REQ Interrupt Register
|
||||
const IC_CLR_TX_ABRT = 0x54; // Clear TX_ABRT Interrupt Register
|
||||
const IC_CLR_RX_DONE = 0x58; // Clear RX_DONE Interrupt Register
|
||||
const IC_CLR_ACTIVITY = 0x5c; // Clear ACTIVITY Interrupt Register
|
||||
const IC_CLR_STOP_DET = 0x60; // Clear STOP_DET Interrupt Register
|
||||
const IC_CLR_START_DET = 0x64; // Clear START_DET Interrupt Register
|
||||
const IC_CLR_GEN_CALL = 0x68; // Clear GEN_CALL Interrupt Register
|
||||
const IC_ENABLE = 0x6c; // I2C ENABLE Register
|
||||
const IC_STATUS = 0x70; // I2C STATUS Register
|
||||
const IC_TXFLR = 0x74; // I2C Transmit FIFO Level Register
|
||||
const IC_RXFLR = 0x78; // I2C Receive FIFO Level Register
|
||||
const IC_SDA_HOLD = 0x7c; // I2C SDA Hold Time Length Register
|
||||
const IC_TX_ABRT_SOURCE = 0x80; // I2C Transmit Abort Source Register
|
||||
const IC_SLV_DATA_NACK_ONLY = 0x84; // Generate Slave Data NACK Register
|
||||
const IC_DMA_CR = 0x88; // DMA Control Register
|
||||
const IC_DMA_TDLR = 0x8c; // DMA Transmit Data Level Register
|
||||
const IC_DMA_RDLR = 0x90; // DMA Transmit Data Level Register
|
||||
const IC_SDA_SETUP = 0x94; // I2C SDA Setup Register
|
||||
const IC_ACK_GENERAL_CALL = 0x98; // I2C ACK General Call Register
|
||||
const IC_ENABLE_STATUS = 0x9c; // I2C Enable Status Register
|
||||
const IC_FS_SPKLEN = 0xa0; // I2C SS, FS or FM+ spike suppression limit
|
||||
const IC_CLR_RESTART_DET = 0xa8; // Clear RESTART_DET Interrupt Register
|
||||
const IC_COMP_PARAM_1 = 0xf4; // Component Parameter Register 1
|
||||
const IC_COMP_VERSION = 0xf8; // I2C Component Version Register
|
||||
const IC_COMP_TYPE = 0xfc; // I2C Component Type Register
|
||||
// IC_CON bits:
|
||||
const STOP_DET_IF_MASTER_ACTIVE = 1 << 10;
|
||||
const RX_FIFO_FULL_HLD_CTRL = 1 << 9;
|
||||
const TX_EMPTY_CTRL = 1 << 8;
|
||||
const STOP_DET_IFADDRESSED = 1 << 7;
|
||||
const IC_SLAVE_DISABLE = 1 << 6;
|
||||
const IC_RESTART_EN = 1 << 5;
|
||||
const IC_10BITADDR_MASTER = 1 << 4;
|
||||
const IC_10BITADDR_SLAVE = 1 << 3;
|
||||
const SPEED_SHIFT = 1;
|
||||
const SPEED_MASK = 0x3;
|
||||
const MASTER_MODE = 1 << 0;
|
||||
// IC_TAR bits:
|
||||
const SPECIAL = 1 << 11;
|
||||
const GC_OR_START = 1 << 10;
|
||||
// IC_STATUS bits:
|
||||
const SLV_ACTIVITY = 1 << 6;
|
||||
const MST_ACTIVITY = 1 << 5;
|
||||
const RFF = 1 << 4;
|
||||
const RFNE = 1 << 3;
|
||||
const TFE = 1 << 2;
|
||||
const TFNF = 1 << 1;
|
||||
const ACTIVITY = 1 << 0;
|
||||
// IC_ENABLE bits:
|
||||
const TX_CMD_BLOCK = 1 << 2;
|
||||
const ABORT = 1 << 1;
|
||||
const ENABLE = 1 << 0;
|
||||
// IC_TX_ABRT_SOURCE bits:
|
||||
const TX_FLUSH_CNT_MASK = 0x1ff;
|
||||
const TX_FLUSH_CNT_SHIFT = 23;
|
||||
const ABRT_USER_ABRT = 1 << 16;
|
||||
const ABRT_SLVRD_INT = 1 << 15;
|
||||
const ABRT_SLV_ARBLOST = 1 << 14;
|
||||
const ABRT_SLVFLUSH_TXFIFO = 1 << 13;
|
||||
const ARB_LOST = 1 << 12;
|
||||
const ABRT_MASTER_DIS = 1 << 11;
|
||||
const ABRT_10B_RD_NORSTRT = 1 << 10;
|
||||
const ABRT_SBYTE_NORSTRT = 1 << 9;
|
||||
const ABRT_HS_NORSTRT = 1 << 8;
|
||||
const ABRT_SBYTE_ACKDET = 1 << 7;
|
||||
const ABRT_HS_ACKDET = 1 << 6;
|
||||
const ABRT_GCALL_READ = 1 << 5;
|
||||
const ABRT_GCALL_NOACK = 1 << 4;
|
||||
const ABRT_TXDATA_NOACK = 1 << 3;
|
||||
const ABRT_10ADDR2_NOACK = 1 << 2;
|
||||
const ABRT_10ADDR1_NOACK = 1 << 1;
|
||||
const ABRT_7B_ADDR_NOACK = 1 << 0;
|
||||
/* Connection parameters */
|
||||
export var I2CMode;
|
||||
(function (I2CMode) {
|
||||
I2CMode[I2CMode["Write"] = 0] = "Write";
|
||||
I2CMode[I2CMode["Read"] = 1] = "Read";
|
||||
})(I2CMode || (I2CMode = {}));
|
||||
export var I2CSpeed;
|
||||
(function (I2CSpeed) {
|
||||
I2CSpeed[I2CSpeed["Invalid"] = 0] = "Invalid";
|
||||
/* standard mode (100 kbit/s) */
|
||||
I2CSpeed[I2CSpeed["Standard"] = 1] = "Standard";
|
||||
/* fast mode (<=400 kbit/s) or fast mode plus (<=1000Kbit/s) */
|
||||
I2CSpeed[I2CSpeed["FastMode"] = 2] = "FastMode";
|
||||
/* high speed mode (3.4 Mbit/s) */
|
||||
I2CSpeed[I2CSpeed["HighSpeedMode"] = 3] = "HighSpeedMode";
|
||||
})(I2CSpeed || (I2CSpeed = {}));
|
||||
var I2CState;
|
||||
(function (I2CState) {
|
||||
I2CState[I2CState["Idle"] = 0] = "Idle";
|
||||
I2CState[I2CState["Start"] = 1] = "Start";
|
||||
I2CState[I2CState["Connect"] = 2] = "Connect";
|
||||
I2CState[I2CState["Connected"] = 3] = "Connected";
|
||||
I2CState[I2CState["Stop"] = 4] = "Stop";
|
||||
})(I2CState || (I2CState = {}));
|
||||
// Interrupts
|
||||
const R_RESTART_DET = 1 << 12; // Slave mode only
|
||||
const R_GEN_CALL = 1 << 11;
|
||||
const R_START_DET = 1 << 10;
|
||||
const R_STOP_DET = 1 << 9;
|
||||
const R_ACTIVITY = 1 << 8;
|
||||
const R_RX_DONE = 1 << 7;
|
||||
const R_TX_ABRT = 1 << 6;
|
||||
const R_RD_REQ = 1 << 5;
|
||||
const R_TX_EMPTY = 1 << 4;
|
||||
const R_TX_OVER = 1 << 3;
|
||||
const R_RX_FULL = 1 << 2;
|
||||
const R_RX_OVER = 1 << 1;
|
||||
const R_RX_UNDER = 1 << 0;
|
||||
// FIFO entry bits
|
||||
const FIRST_DATA_BYTE = 1 << 10;
|
||||
const RESTART = 1 << 10;
|
||||
const STOP = 1 << 9;
|
||||
const CMD = 1 << 8; // 0 for write, 1 for read
|
||||
export class RPI2C extends BasePeripheral {
|
||||
get intStatus() {
|
||||
return this.intRaw & this.intEnable;
|
||||
}
|
||||
get speed() {
|
||||
return ((this.control >> SPEED_SHIFT) & SPEED_MASK);
|
||||
}
|
||||
get sclLowPeriod() {
|
||||
return this.speed === I2CSpeed.Standard ? this.ssClockLowPeriod : this.fsClockLowPeriod;
|
||||
}
|
||||
get sclHighPeriod() {
|
||||
return this.speed === I2CSpeed.Standard ? this.ssClockHighPeriod : this.fsClockHighPeriod;
|
||||
}
|
||||
get masterBits() {
|
||||
return this.control & IC_10BITADDR_MASTER ? 10 : 7;
|
||||
}
|
||||
constructor(rp2040, name, irq) {
|
||||
super(rp2040, name);
|
||||
this.irq = irq;
|
||||
this.state = I2CState.Idle;
|
||||
this.busy = false;
|
||||
this.stop = false;
|
||||
this.pendingRestart = false;
|
||||
this.firstByte = false;
|
||||
this.rxFIFO = new FIFO(16);
|
||||
this.txFIFO = new FIFO(16);
|
||||
// user provided callbacks
|
||||
this.onStart = () => this.completeStart();
|
||||
this.onConnect = () => this.completeConnect(false);
|
||||
this.onWriteByte = () => this.completeWrite(false);
|
||||
this.onReadByte = () => this.completeRead(0xff);
|
||||
this.onStop = () => this.completeStop();
|
||||
this.enable = 0;
|
||||
this.rxThreshold = 0;
|
||||
this.txThreshold = 0;
|
||||
this.control = IC_SLAVE_DISABLE | IC_RESTART_EN | (I2CSpeed.FastMode << SPEED_SHIFT) | MASTER_MODE;
|
||||
this.ssClockHighPeriod = 0x0028;
|
||||
this.ssClockLowPeriod = 0x002f;
|
||||
this.fsClockHighPeriod = 0x0006;
|
||||
this.fsClockLowPeriod = 0x000d;
|
||||
this.targetAddress = 0x55;
|
||||
this.slaveAddress = 0x55;
|
||||
this.abortSource = 0;
|
||||
this.intRaw = 0;
|
||||
this.intEnable = 0;
|
||||
this.spikelen = 0x07;
|
||||
}
|
||||
checkInterrupts() {
|
||||
this.rp2040.setInterrupt(this.irq, !!this.intStatus);
|
||||
}
|
||||
clearInterrupts(mask) {
|
||||
if (this.intRaw & mask) {
|
||||
this.intRaw &= ~mask;
|
||||
this.checkInterrupts();
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
setInterrupts(mask) {
|
||||
if (!(this.intRaw & mask)) {
|
||||
this.intRaw |= mask;
|
||||
this.checkInterrupts();
|
||||
}
|
||||
}
|
||||
abort(reason) {
|
||||
this.abortSource &= ~TX_FLUSH_CNT_MASK;
|
||||
this.abortSource |= reason | (this.txFIFO.itemCount << TX_FLUSH_CNT_SHIFT);
|
||||
this.txFIFO.reset();
|
||||
this.setInterrupts(R_TX_ABRT);
|
||||
}
|
||||
nextCommand() {
|
||||
const enabled = this.enable & ENABLE;
|
||||
const blocked = this.enable & TX_CMD_BLOCK;
|
||||
if (this.txFIFO.empty || this.busy || blocked || !enabled) {
|
||||
return;
|
||||
}
|
||||
this.busy = true;
|
||||
const restart = !!(this.txFIFO.peek() & RESTART) && !this.pendingRestart && !this.stop;
|
||||
if (this.state === I2CState.Idle || restart) {
|
||||
this.pendingRestart = restart;
|
||||
this.stop = false;
|
||||
this.state = I2CState.Start;
|
||||
this.onStart(restart);
|
||||
return;
|
||||
}
|
||||
this.pendingRestart = false;
|
||||
const cmd = this.txFIFO.pull();
|
||||
const readMode = !!(cmd & CMD);
|
||||
this.stop = !!(cmd & STOP);
|
||||
if (readMode) {
|
||||
this.onReadByte(!this.stop);
|
||||
}
|
||||
else {
|
||||
this.onWriteByte(cmd & 0xff);
|
||||
}
|
||||
if (this.txFIFO.itemCount <= this.txThreshold) {
|
||||
this.setInterrupts(R_TX_EMPTY);
|
||||
}
|
||||
}
|
||||
pushRX(value) {
|
||||
if (this.rxFIFO.full) {
|
||||
this.setInterrupts(R_RX_OVER);
|
||||
return;
|
||||
}
|
||||
this.rxFIFO.push(value);
|
||||
if (this.rxFIFO.itemCount > this.rxThreshold) {
|
||||
this.setInterrupts(R_RX_FULL);
|
||||
}
|
||||
}
|
||||
completeStart() {
|
||||
if (this.txFIFO.empty || this.state !== I2CState.Start || this.stop) {
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
const mode = this.txFIFO.peek() & CMD ? I2CMode.Read : I2CMode.Write;
|
||||
this.state = I2CState.Connect;
|
||||
this.setInterrupts(R_START_DET);
|
||||
const addressMask = this.masterBits === 10 ? 0x3ff : 0xff;
|
||||
this.onConnect(this.targetAddress & addressMask, mode);
|
||||
}
|
||||
completeConnect(ack, nackByte = 0) {
|
||||
if (!ack || this.stop) {
|
||||
if (!ack) {
|
||||
if (!this.targetAddress) {
|
||||
this.abort(ABRT_GCALL_NOACK);
|
||||
}
|
||||
else if (this.control & IC_10BITADDR_MASTER) {
|
||||
this.abort(nackByte === 0 ? ABRT_10ADDR1_NOACK : ABRT_10ADDR2_NOACK);
|
||||
}
|
||||
else {
|
||||
this.abort(ABRT_7B_ADDR_NOACK);
|
||||
}
|
||||
}
|
||||
this.state = I2CState.Stop;
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
this.state = I2CState.Connected;
|
||||
this.busy = false;
|
||||
this.firstByte = true;
|
||||
this.nextCommand();
|
||||
}
|
||||
completeWrite(ack) {
|
||||
if (!ack || this.stop) {
|
||||
if (!ack) {
|
||||
this.abort(ABRT_TXDATA_NOACK);
|
||||
}
|
||||
this.state = I2CState.Stop;
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
this.busy = false;
|
||||
this.nextCommand();
|
||||
}
|
||||
completeRead(value) {
|
||||
this.pushRX(value | (this.firstByte ? FIRST_DATA_BYTE : 0));
|
||||
if (this.stop) {
|
||||
this.state = I2CState.Stop;
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
this.firstByte = false;
|
||||
this.busy = false;
|
||||
this.nextCommand();
|
||||
}
|
||||
completeStop() {
|
||||
this.state = I2CState.Idle;
|
||||
this.setInterrupts(R_STOP_DET);
|
||||
this.busy = false;
|
||||
this.pendingRestart = false;
|
||||
if (this.enable & ABORT) {
|
||||
this.enable &= ~ABORT;
|
||||
}
|
||||
else {
|
||||
this.nextCommand();
|
||||
}
|
||||
}
|
||||
arbitrationLost() {
|
||||
this.state = I2CState.Idle;
|
||||
this.busy = false;
|
||||
this.abort(ARB_LOST);
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case IC_CON:
|
||||
return this.control;
|
||||
case IC_TAR:
|
||||
return this.targetAddress;
|
||||
case IC_SAR:
|
||||
return this.slaveAddress;
|
||||
case IC_DATA_CMD:
|
||||
if (this.rxFIFO.empty) {
|
||||
this.setInterrupts(R_RX_UNDER);
|
||||
return 0;
|
||||
}
|
||||
this.clearInterrupts(R_RX_FULL);
|
||||
return this.rxFIFO.pull();
|
||||
case IC_SS_SCL_HCNT:
|
||||
return this.ssClockHighPeriod;
|
||||
case IC_SS_SCL_LCNT:
|
||||
return this.ssClockLowPeriod;
|
||||
case IC_FS_SCL_HCNT:
|
||||
return this.fsClockHighPeriod;
|
||||
case IC_FS_SCL_LCNT:
|
||||
return this.fsClockLowPeriod;
|
||||
case IC_INTR_STAT:
|
||||
return this.intStatus;
|
||||
case IC_INTR_MASK:
|
||||
return this.intEnable;
|
||||
case IC_RAW_INTR_STAT:
|
||||
return this.intRaw;
|
||||
case IC_RX_TL:
|
||||
return this.rxThreshold;
|
||||
case IC_TX_TL:
|
||||
return this.txThreshold;
|
||||
case IC_CLR_INTR:
|
||||
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
|
||||
return this.clearInterrupts(R_RX_UNDER |
|
||||
R_RX_OVER |
|
||||
R_TX_OVER |
|
||||
R_RD_REQ |
|
||||
R_TX_ABRT |
|
||||
R_RX_DONE |
|
||||
R_ACTIVITY |
|
||||
R_STOP_DET |
|
||||
R_START_DET |
|
||||
R_GEN_CALL);
|
||||
case IC_CLR_RX_UNDER:
|
||||
return this.clearInterrupts(R_RX_UNDER);
|
||||
case IC_CLR_RX_OVER:
|
||||
return this.clearInterrupts(R_RX_OVER);
|
||||
case IC_CLR_TX_OVER:
|
||||
return this.clearInterrupts(R_TX_OVER);
|
||||
case IC_CLR_RD_REQ:
|
||||
return this.clearInterrupts(R_RD_REQ);
|
||||
case IC_CLR_TX_ABRT:
|
||||
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
|
||||
return this.clearInterrupts(R_TX_ABRT);
|
||||
case IC_CLR_RX_DONE:
|
||||
return this.clearInterrupts(R_RX_DONE);
|
||||
case IC_CLR_ACTIVITY:
|
||||
return this.clearInterrupts(R_ACTIVITY);
|
||||
case IC_CLR_STOP_DET:
|
||||
return this.clearInterrupts(R_STOP_DET);
|
||||
case IC_CLR_START_DET:
|
||||
return this.clearInterrupts(R_START_DET);
|
||||
case IC_CLR_GEN_CALL:
|
||||
return this.clearInterrupts(R_GEN_CALL);
|
||||
case IC_ENABLE:
|
||||
return this.enable;
|
||||
case IC_STATUS:
|
||||
return ((this.state !== I2CState.Idle ? MST_ACTIVITY | ACTIVITY : 0) |
|
||||
(this.rxFIFO.full ? RFF : 0) |
|
||||
(!this.rxFIFO.empty ? RFNE : 0) |
|
||||
(this.txFIFO.empty ? TFE : 0) |
|
||||
(!this.txFIFO.full ? TFNF : 0));
|
||||
case IC_TXFLR:
|
||||
return this.txFIFO.itemCount;
|
||||
case IC_RXFLR:
|
||||
return this.rxFIFO.itemCount;
|
||||
case IC_SDA_HOLD:
|
||||
return 0x01;
|
||||
case IC_TX_ABRT_SOURCE: {
|
||||
const value = this.abortSource;
|
||||
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
|
||||
return value;
|
||||
}
|
||||
case IC_ENABLE_STATUS:
|
||||
// I2C status - read only. bit 0 reflects IC_ENABLE, bit 1,2 relate to i2c slave mode.
|
||||
return this.enable & 0x1;
|
||||
case IC_FS_SPKLEN:
|
||||
return this.spikelen & 0xff;
|
||||
case IC_COMP_PARAM_1:
|
||||
// From the datasheet:
|
||||
// Note This register is not implemented and therefore reads as 0. If it was implemented it would be a constant read-only
|
||||
// register that contains encoded information about the component's parameter settings.
|
||||
return 0;
|
||||
case IC_COMP_VERSION:
|
||||
return 0x3230312a;
|
||||
case IC_COMP_TYPE:
|
||||
return 0x44570140;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case IC_CON:
|
||||
if (((value >> SPEED_SHIFT) & SPEED_MASK) === I2CSpeed.Invalid) {
|
||||
value = (value & ~(SPEED_MASK << SPEED_SHIFT)) | (I2CSpeed.HighSpeedMode << SPEED_SHIFT);
|
||||
}
|
||||
this.control = value;
|
||||
return;
|
||||
case IC_TAR:
|
||||
this.targetAddress = value & 0x3ff;
|
||||
return;
|
||||
case IC_SAR:
|
||||
this.slaveAddress = value & 0x3ff;
|
||||
return;
|
||||
case IC_DATA_CMD:
|
||||
if (this.txFIFO.full) {
|
||||
this.setInterrupts(R_TX_OVER);
|
||||
}
|
||||
else {
|
||||
this.txFIFO.push(value);
|
||||
this.clearInterrupts(R_TX_EMPTY);
|
||||
this.nextCommand();
|
||||
}
|
||||
return;
|
||||
case IC_SS_SCL_HCNT:
|
||||
this.ssClockHighPeriod = value & 0xffff;
|
||||
return;
|
||||
case IC_SS_SCL_LCNT:
|
||||
this.ssClockLowPeriod = value & 0xffff;
|
||||
return;
|
||||
case IC_FS_SCL_HCNT:
|
||||
this.fsClockHighPeriod = value & 0xffff;
|
||||
return;
|
||||
case IC_FS_SCL_LCNT:
|
||||
this.fsClockLowPeriod = value & 0xffff;
|
||||
return;
|
||||
case IC_SDA_HOLD:
|
||||
if (!(value & ENABLE)) {
|
||||
if (value != 0x1) {
|
||||
this.warn('Unimplemented write to IC_SDA_HOLD');
|
||||
}
|
||||
}
|
||||
return;
|
||||
case IC_RX_TL:
|
||||
this.rxThreshold = value & 0xff;
|
||||
if (this.rxThreshold > this.rxFIFO.size) {
|
||||
this.rxThreshold = this.rxFIFO.size;
|
||||
}
|
||||
return;
|
||||
case IC_TX_TL:
|
||||
this.txThreshold = value & 0xff;
|
||||
if (this.txThreshold > this.txFIFO.size) {
|
||||
this.txThreshold = this.txFIFO.size;
|
||||
}
|
||||
return;
|
||||
case IC_ENABLE:
|
||||
// ABORT bit can only be set by software, not cleared.
|
||||
value |= this.enable & ABORT;
|
||||
if (value & ABORT) {
|
||||
if (this.state === I2CState.Idle) {
|
||||
value &= ~ABORT;
|
||||
}
|
||||
else {
|
||||
this.abort(ABRT_USER_ABRT);
|
||||
this.stop = true;
|
||||
}
|
||||
}
|
||||
if (!(value & ENABLE)) {
|
||||
this.txFIFO.reset();
|
||||
this.rxFIFO.reset();
|
||||
}
|
||||
this.enable = value;
|
||||
this.nextCommand(); // TX_CMD_BLOCK may have changed
|
||||
return;
|
||||
case IC_FS_SPKLEN:
|
||||
if (!(value & ENABLE) && value > 0) {
|
||||
this.spikelen = value;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const GPIO_CTRL_LAST = 0x0ec;
|
||||
const INTR0 = 0xf0;
|
||||
const PROC0_INTE0 = 0x100;
|
||||
const PROC0_INTF0 = 0x110;
|
||||
const PROC0_INTS0 = 0x120;
|
||||
const PROC0_INTS3 = 0x12c;
|
||||
export class RPIO extends BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
}
|
||||
getPinFromOffset(offset) {
|
||||
const gpioIndex = offset >>> 3;
|
||||
return {
|
||||
gpio: this.rp2040.gpio[gpioIndex],
|
||||
isCtrl: !!(offset & 0x4),
|
||||
};
|
||||
}
|
||||
readUint32(offset) {
|
||||
if (offset <= GPIO_CTRL_LAST) {
|
||||
const { gpio, isCtrl } = this.getPinFromOffset(offset);
|
||||
return isCtrl ? gpio.ctrl : gpio.status;
|
||||
}
|
||||
if (offset >= INTR0 && offset <= PROC0_INTS3) {
|
||||
const startIndex = (offset & 0xf) * 2;
|
||||
const register = offset & ~0xf;
|
||||
const { gpio } = this.rp2040;
|
||||
let result = 0;
|
||||
for (let index = 7; index >= 0; index--) {
|
||||
const pin = gpio[index + startIndex];
|
||||
if (!pin) {
|
||||
continue;
|
||||
}
|
||||
result <<= 4;
|
||||
switch (register) {
|
||||
case INTR0:
|
||||
result |= pin.irqStatus;
|
||||
break;
|
||||
case PROC0_INTE0:
|
||||
result |= pin.irqEnableMask;
|
||||
break;
|
||||
case PROC0_INTF0:
|
||||
result |= pin.irqForceMask;
|
||||
break;
|
||||
case PROC0_INTS0:
|
||||
result |= (pin.irqStatus & pin.irqEnableMask) | pin.irqForceMask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
if (offset <= GPIO_CTRL_LAST) {
|
||||
const { gpio, isCtrl } = this.getPinFromOffset(offset);
|
||||
if (isCtrl) {
|
||||
gpio.ctrl = value;
|
||||
gpio.checkForUpdates();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (offset >= INTR0 && offset <= PROC0_INTS3) {
|
||||
const startIndex = (offset & 0xf) * 2;
|
||||
const register = offset & ~0xf;
|
||||
const { gpio } = this.rp2040;
|
||||
for (let index = 0; index < 8; index++) {
|
||||
const pin = gpio[index + startIndex];
|
||||
if (!pin) {
|
||||
continue;
|
||||
}
|
||||
const pinValue = (value >> (index * 4)) & 0xf;
|
||||
const pinRawWriteValue = (this.rawWriteValue >> (index * 4)) & 0xf;
|
||||
switch (register) {
|
||||
case INTR0:
|
||||
pin.updateIRQValue(pinRawWriteValue);
|
||||
break;
|
||||
case PROC0_INTE0:
|
||||
if (pin.irqEnableMask !== pinValue) {
|
||||
pin.irqEnableMask = pinValue;
|
||||
this.rp2040.updateIOInterrupt();
|
||||
}
|
||||
break;
|
||||
case PROC0_INTF0:
|
||||
if (pin.irqForceMask !== pinValue) {
|
||||
pin.irqForceMask = pinValue;
|
||||
this.rp2040.updateIOInterrupt();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const VOLTAGE_SELECT = 0;
|
||||
const GPIO_FIRST = 0x4;
|
||||
const GPIO_LAST = 0x78;
|
||||
const QSPI_FIRST = 0x4;
|
||||
const QSPI_LAST = 0x18;
|
||||
export class RPPADS extends BasePeripheral {
|
||||
constructor(rp2040, name, bank) {
|
||||
super(rp2040, name);
|
||||
this.bank = bank;
|
||||
this.voltageSelect = 0;
|
||||
this.firstPadRegister = this.bank === 'qspi' ? QSPI_FIRST : GPIO_FIRST;
|
||||
this.lastPadRegister = this.bank === 'qspi' ? QSPI_LAST : GPIO_LAST;
|
||||
}
|
||||
getPinFromOffset(offset) {
|
||||
const gpioIndex = (offset - this.firstPadRegister) >>> 2;
|
||||
if (this.bank === 'qspi') {
|
||||
return this.rp2040.qspi[gpioIndex];
|
||||
}
|
||||
else {
|
||||
return this.rp2040.gpio[gpioIndex];
|
||||
}
|
||||
}
|
||||
readUint32(offset) {
|
||||
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
|
||||
const gpio = this.getPinFromOffset(offset);
|
||||
return gpio.padValue;
|
||||
}
|
||||
switch (offset) {
|
||||
case VOLTAGE_SELECT:
|
||||
return this.voltageSelect;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
|
||||
const gpio = this.getPinFromOffset(offset);
|
||||
const oldInputEnable = gpio.inputEnable;
|
||||
gpio.padValue = value;
|
||||
gpio.checkForUpdates();
|
||||
if (oldInputEnable !== gpio.inputEnable) {
|
||||
gpio.refreshInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (offset) {
|
||||
case VOLTAGE_SELECT:
|
||||
this.voltageSelect = value & 1;
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
const ATOMIC_NORMAL = 0;
|
||||
const ATOMIC_XOR = 1;
|
||||
const ATOMIC_SET = 2;
|
||||
const ATOMIC_CLEAR = 3;
|
||||
export function atomicUpdate(currentValue, atomicType, newValue) {
|
||||
switch (atomicType) {
|
||||
case ATOMIC_XOR:
|
||||
return currentValue ^ newValue;
|
||||
case ATOMIC_SET:
|
||||
return currentValue | newValue;
|
||||
case ATOMIC_CLEAR:
|
||||
return currentValue & ~newValue;
|
||||
default:
|
||||
console.warn('Atomic update called with invalid writeType', atomicType);
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
export class BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
this.rp2040 = rp2040;
|
||||
this.name = name;
|
||||
this.rawWriteValue = 0;
|
||||
}
|
||||
readUint32(offset) {
|
||||
this.warn(`Unimplemented peripheral read from 0x${offset.toString(16)}`);
|
||||
if (offset > 0x1000) {
|
||||
this.warn('Unimplemented read from peripheral in the atomic operation region');
|
||||
}
|
||||
return 0xffffffff;
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
this.warn(`Unimplemented peripheral write to 0x${offset.toString(16)}: 0x${value.toString(16)}`);
|
||||
}
|
||||
writeUint32Atomic(offset, value, atomicType) {
|
||||
this.rawWriteValue = value;
|
||||
const newValue = atomicType != ATOMIC_NORMAL
|
||||
? atomicUpdate(this.readUint32(offset), atomicType, value)
|
||||
: value;
|
||||
this.writeUint32(offset, newValue);
|
||||
}
|
||||
debug(msg) {
|
||||
this.rp2040.logger.debug(this.name, msg);
|
||||
}
|
||||
info(msg) {
|
||||
this.rp2040.logger.info(this.name, msg);
|
||||
}
|
||||
warn(msg) {
|
||||
this.rp2040.logger.warn(this.name, msg);
|
||||
}
|
||||
error(msg) {
|
||||
this.rp2040.logger.error(this.name, msg);
|
||||
}
|
||||
}
|
||||
export class UnimplementedPeripheral extends BasePeripheral {
|
||||
}
|
||||
+1090
File diff suppressed because it is too large
Load Diff
+225
@@ -0,0 +1,225 @@
|
||||
import { MAX_HARDWARE_IRQ } from '../irq.js';
|
||||
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
export const CPUID = 0xd00;
|
||||
export const ICSR = 0xd04;
|
||||
export const VTOR = 0xd08;
|
||||
export const SHPR2 = 0xd1c;
|
||||
export const SHPR3 = 0xd20;
|
||||
const SYST_CSR = 0x010; // SysTick Control and Status Register
|
||||
const SYST_RVR = 0x014; // SysTick Reload Value Register
|
||||
const SYST_CVR = 0x018; // SysTick Current Value Register
|
||||
const SYST_CALIB = 0x01c; // SysTick Calibration Value Register
|
||||
const NVIC_ISER = 0x100; // Interrupt Set-Enable Register
|
||||
const NVIC_ICER = 0x180; // Interrupt Clear-Enable Register
|
||||
const NVIC_ISPR = 0x200; // Interrupt Set-Pending Register
|
||||
const NVIC_ICPR = 0x280; // Interrupt Clear-Pending Register
|
||||
// Interrupt priority registers:
|
||||
const NVIC_IPR0 = 0x400;
|
||||
const NVIC_IPR1 = 0x404;
|
||||
const NVIC_IPR2 = 0x408;
|
||||
const NVIC_IPR3 = 0x40c;
|
||||
const NVIC_IPR4 = 0x410;
|
||||
const NVIC_IPR5 = 0x414;
|
||||
const NVIC_IPR6 = 0x418;
|
||||
const NVIC_IPR7 = 0x41c;
|
||||
/** ICSR Bits */
|
||||
const NMIPENDSET = 1 << 31;
|
||||
const PENDSVSET = 1 << 28;
|
||||
const PENDSVCLR = 1 << 27;
|
||||
const PENDSTSET = 1 << 26;
|
||||
const PENDSTCLR = 1 << 25;
|
||||
const ISRPREEMPT = 1 << 23;
|
||||
const ISRPENDING = 1 << 22;
|
||||
const VECTPENDING_MASK = 0x1ff;
|
||||
const VECTPENDING_SHIFT = 12;
|
||||
const VECTACTIVE_MASK = 0x1ff;
|
||||
const VECTACTIVE_SHIFT = 0;
|
||||
/** PPB stands for Private Periphral Bus.
|
||||
* These are peripherals that are part of the ARM Cortex Core, and there's one copy for each processor core.
|
||||
*
|
||||
* Included peripheral: NVIC, SysTick timer
|
||||
*/
|
||||
export class RPPPB extends BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
// Systick
|
||||
this.systickCountFlag = false;
|
||||
this.systickClkSource = false;
|
||||
this.systickIntEnable = false;
|
||||
this.systickReload = 0;
|
||||
this.systickTimer = new Timer32(this.rp2040.clock, this.rp2040.clkSys);
|
||||
this.systickAlarm = new Timer32PeriodicAlarm(this.systickTimer, () => {
|
||||
this.systickCountFlag = true;
|
||||
if (this.systickIntEnable) {
|
||||
this.rp2040.core.pendingSystick = true;
|
||||
this.rp2040.core.interruptsUpdated = true;
|
||||
}
|
||||
this.systickTimer.set(this.systickReload);
|
||||
});
|
||||
this.systickTimer.top = 0xffffff;
|
||||
this.systickTimer.mode = TimerMode.Decrement;
|
||||
this.systickAlarm.target = 0;
|
||||
this.systickAlarm.enable = true;
|
||||
this.reset();
|
||||
}
|
||||
reset() {
|
||||
this.writeUint32(SYST_CSR, 0);
|
||||
this.writeUint32(SYST_RVR, 0xffffff);
|
||||
this.systickTimer.set(0xffffff);
|
||||
}
|
||||
readUint32(offset) {
|
||||
const { rp2040 } = this;
|
||||
const { core } = rp2040;
|
||||
switch (offset) {
|
||||
case CPUID:
|
||||
return 0x410cc601; /* Verified against actual hardware */
|
||||
case ICSR: {
|
||||
const pendingInterrupts = core.pendingInterrupts || core.pendingPendSV || core.pendingSystick || core.pendingSVCall;
|
||||
const vectPending = core.vectPending;
|
||||
return ((core.pendingNMI ? NMIPENDSET : 0) |
|
||||
(core.pendingPendSV ? PENDSVSET : 0) |
|
||||
(core.pendingSystick ? PENDSTSET : 0) |
|
||||
(pendingInterrupts ? ISRPENDING : 0) |
|
||||
(vectPending << VECTPENDING_SHIFT) |
|
||||
((core.IPSR & VECTACTIVE_MASK) << VECTACTIVE_SHIFT));
|
||||
}
|
||||
case VTOR:
|
||||
return core.VTOR;
|
||||
/* NVIC */
|
||||
case NVIC_ISPR:
|
||||
return core.pendingInterrupts >>> 0;
|
||||
case NVIC_ICPR:
|
||||
return core.pendingInterrupts >>> 0;
|
||||
case NVIC_ISER:
|
||||
return core.enabledInterrupts >>> 0;
|
||||
case NVIC_ICER:
|
||||
return core.enabledInterrupts >>> 0;
|
||||
case NVIC_IPR0:
|
||||
case NVIC_IPR1:
|
||||
case NVIC_IPR2:
|
||||
case NVIC_IPR3:
|
||||
case NVIC_IPR4:
|
||||
case NVIC_IPR5:
|
||||
case NVIC_IPR6:
|
||||
case NVIC_IPR7: {
|
||||
const regIndex = (offset - NVIC_IPR0) >> 2;
|
||||
let result = 0;
|
||||
for (let byteIndex = 0; byteIndex < 4; byteIndex++) {
|
||||
const interruptNumber = regIndex * 4 + byteIndex;
|
||||
for (let priority = 0; priority < core.interruptPriorities.length; priority++) {
|
||||
if (core.interruptPriorities[priority] & (1 << interruptNumber)) {
|
||||
result |= priority << (8 * byteIndex + 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case SHPR2:
|
||||
return core.SHPR2;
|
||||
case SHPR3:
|
||||
return core.SHPR3;
|
||||
/* SysTick */
|
||||
case SYST_CSR: {
|
||||
const countFlagValue = this.systickCountFlag ? 1 << 16 : 0;
|
||||
const clkSourceValue = this.systickClkSource ? 1 << 2 : 0;
|
||||
const tickIntValue = this.systickIntEnable ? 1 << 1 : 0;
|
||||
const enableFlagValue = this.systickTimer.enable ? 1 << 0 : 0;
|
||||
this.systickCountFlag = false;
|
||||
return countFlagValue | clkSourceValue | tickIntValue | enableFlagValue;
|
||||
}
|
||||
case SYST_CVR:
|
||||
return this.systickTimer.counter;
|
||||
case SYST_RVR:
|
||||
return this.systickReload;
|
||||
case SYST_CALIB:
|
||||
return 0x0000270f;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
const { rp2040 } = this;
|
||||
const { core } = rp2040;
|
||||
const hardwareInterruptMask = (1 << MAX_HARDWARE_IRQ) - 1;
|
||||
switch (offset) {
|
||||
case ICSR:
|
||||
if (value & NMIPENDSET) {
|
||||
core.pendingNMI = true;
|
||||
core.interruptsUpdated = true;
|
||||
}
|
||||
if (value & PENDSVSET) {
|
||||
core.pendingPendSV = true;
|
||||
core.interruptsUpdated = true;
|
||||
}
|
||||
if (value & PENDSVCLR) {
|
||||
core.pendingPendSV = false;
|
||||
}
|
||||
if (value & PENDSTSET) {
|
||||
core.pendingSystick = true;
|
||||
core.interruptsUpdated = true;
|
||||
}
|
||||
if (value & PENDSTCLR) {
|
||||
core.pendingSystick = false;
|
||||
}
|
||||
return;
|
||||
case VTOR:
|
||||
core.VTOR = value;
|
||||
return;
|
||||
/* NVIC */
|
||||
case NVIC_ISPR:
|
||||
core.pendingInterrupts |= value;
|
||||
core.interruptsUpdated = true;
|
||||
return;
|
||||
case NVIC_ICPR:
|
||||
core.pendingInterrupts &= ~value | hardwareInterruptMask;
|
||||
return;
|
||||
case NVIC_ISER:
|
||||
core.enabledInterrupts |= value;
|
||||
core.interruptsUpdated = true;
|
||||
return;
|
||||
case NVIC_ICER:
|
||||
core.enabledInterrupts &= ~value;
|
||||
return;
|
||||
case NVIC_IPR0:
|
||||
case NVIC_IPR1:
|
||||
case NVIC_IPR2:
|
||||
case NVIC_IPR3:
|
||||
case NVIC_IPR4:
|
||||
case NVIC_IPR5:
|
||||
case NVIC_IPR6:
|
||||
case NVIC_IPR7: {
|
||||
const regIndex = (offset - NVIC_IPR0) >> 2;
|
||||
for (let byteIndex = 0; byteIndex < 4; byteIndex++) {
|
||||
const interruptNumber = regIndex * 4 + byteIndex;
|
||||
const newPriority = (value >> (8 * byteIndex + 6)) & 0x3;
|
||||
for (let priority = 0; priority < core.interruptPriorities.length; priority++) {
|
||||
core.interruptPriorities[priority] &= ~(1 << interruptNumber);
|
||||
}
|
||||
core.interruptPriorities[newPriority] |= 1 << interruptNumber;
|
||||
}
|
||||
core.interruptsUpdated = true;
|
||||
return;
|
||||
}
|
||||
case SHPR2:
|
||||
core.SHPR2 = value;
|
||||
return;
|
||||
case SHPR3:
|
||||
core.SHPR3 = value;
|
||||
return;
|
||||
// SysTick
|
||||
case SYST_CSR:
|
||||
this.systickClkSource = value & (1 << 2) ? true : false;
|
||||
this.systickIntEnable = value & (1 << 1) ? true : false;
|
||||
this.systickTimer.enable = value & (1 << 0) ? true : false;
|
||||
return;
|
||||
case SYST_CVR:
|
||||
this.systickTimer.set(0);
|
||||
return;
|
||||
case SYST_RVR:
|
||||
this.systickReload = value;
|
||||
return;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const FRCE_ON = 0x00;
|
||||
const FRCE_OFF = 0x04;
|
||||
const WDSEL = 0x08;
|
||||
const DONE = 0x0c;
|
||||
const PSM_BITS_MASK = 0x0001ffff;
|
||||
export class RPPSM extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.frceOn = 0;
|
||||
this.frceOff = 0;
|
||||
this.wdsel = 0;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case FRCE_ON:
|
||||
return this.frceOn;
|
||||
case FRCE_OFF:
|
||||
return this.frceOff;
|
||||
case WDSEL:
|
||||
return this.wdsel;
|
||||
case DONE:
|
||||
// Domains are ready unless forced off (FRCE_ON overrides FRCE_OFF)
|
||||
return (PSM_BITS_MASK & ~this.frceOff) | (this.frceOn & this.frceOff);
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case FRCE_ON:
|
||||
this.frceOn = value & PSM_BITS_MASK;
|
||||
break;
|
||||
case FRCE_OFF:
|
||||
this.frceOff = value & PSM_BITS_MASK;
|
||||
break;
|
||||
case WDSEL:
|
||||
this.wdsel = value & PSM_BITS_MASK;
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const RESET = 0x0; //Reset control.
|
||||
const WDSEL = 0x4; //Watchdog select.
|
||||
const RESET_DONE = 0x8; //Reset Done
|
||||
export class RPReset extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.reset = 0;
|
||||
this.wdsel = 0;
|
||||
this.reset_done = 0x1ffffff;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case RESET:
|
||||
return this.reset;
|
||||
case WDSEL:
|
||||
return this.wdsel;
|
||||
case RESET_DONE:
|
||||
return this.reset_done;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case RESET:
|
||||
this.reset = value & 0x1ffffff;
|
||||
break;
|
||||
case WDSEL:
|
||||
this.wdsel = value & 0x1ffffff;
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const RTC_SETUP0 = 0x04;
|
||||
const RTC_SETUP1 = 0x08;
|
||||
const RTC_CTRL = 0x0c;
|
||||
const IRQ_SETUP_0 = 0x10;
|
||||
const RTC_RTC1 = 0x18;
|
||||
const RTC_RTC0 = 0x1c;
|
||||
const RTC_ENABLE_BITS = 0x01;
|
||||
const RTC_ACTIVE_BITS = 0x2;
|
||||
const RTC_LOAD_BITS = 0x10;
|
||||
const SETUP_0_YEAR_SHIFT = 12;
|
||||
const SETUP_0_YEAR_MASK = 0xfff;
|
||||
const SETUP_0_MONTH_SHIFT = 8;
|
||||
const SETUP_0_MONTH_MASK = 0xf;
|
||||
const SETUP_0_DAY_SHIFT = 0;
|
||||
const SETUP_0_DAY_MASK = 0x1f;
|
||||
const SETUP_1_DOTW_SHIFT = 24;
|
||||
const SETUP_1_DOTW_MASK = 0x7;
|
||||
const SETUP_1_HOUR_SHIFT = 16;
|
||||
const SETUP_1_HOUR_MASK = 0x1f;
|
||||
const SETUP_1_MIN_SHIFT = 8;
|
||||
const SETUP_1_MIN_MASK = 0x3f;
|
||||
const SETUP_1_SEC_SHIFT = 0;
|
||||
const SETUP_1_SEC_MASK = 0x3f;
|
||||
const RTC_0_YEAR_SHIFT = 12;
|
||||
const RTC_0_YEAR_MASK = 0xfff;
|
||||
const RTC_0_MONTH_SHIFT = 8;
|
||||
const RTC_0_MONTH_MASK = 0xf;
|
||||
const RTC_0_DAY_SHIFT = 0;
|
||||
const RTC_0_DAY_MASK = 0x1f;
|
||||
const RTC_1_DOTW_SHIFT = 24;
|
||||
const RTC_1_DOTW_MASK = 0x7;
|
||||
const RTC_1_HOUR_SHIFT = 16;
|
||||
const RTC_1_HOUR_MASK = 0x1f;
|
||||
const RTC_1_MIN_SHIFT = 8;
|
||||
const RTC_1_MIN_MASK = 0x3f;
|
||||
const RTC_1_SEC_SHIFT = 0;
|
||||
const RTC_1_SEC_MASK = 0x3f;
|
||||
export class RP2040RTC extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.setup0 = 0;
|
||||
this.setup1 = 0;
|
||||
this.ctrl = 0;
|
||||
this.baseline = new Date(2021, 0, 1);
|
||||
this.baselineNanos = 0;
|
||||
}
|
||||
readUint32(offset) {
|
||||
const date = new Date(this.baseline.getTime() + (this.rp2040.clock.nanos - this.baselineNanos) / 1000000);
|
||||
switch (offset) {
|
||||
case RTC_SETUP0:
|
||||
return this.setup0;
|
||||
case RTC_SETUP1:
|
||||
return this.setup1;
|
||||
case RTC_CTRL:
|
||||
return this.ctrl;
|
||||
case IRQ_SETUP_0:
|
||||
return 0;
|
||||
case RTC_RTC1:
|
||||
return (((date.getFullYear() & RTC_0_YEAR_MASK) << RTC_0_YEAR_SHIFT) |
|
||||
(((date.getMonth() + 1) & RTC_0_MONTH_MASK) << RTC_0_MONTH_SHIFT) |
|
||||
((date.getDate() & RTC_0_DAY_MASK) << RTC_0_DAY_SHIFT));
|
||||
case RTC_RTC0:
|
||||
return (((date.getDay() & RTC_1_DOTW_MASK) << RTC_1_DOTW_SHIFT) |
|
||||
((date.getHours() & RTC_1_HOUR_MASK) << RTC_1_HOUR_SHIFT) |
|
||||
((date.getMinutes() & RTC_1_MIN_MASK) << RTC_1_MIN_SHIFT) |
|
||||
((date.getSeconds() & RTC_1_SEC_MASK) << RTC_1_SEC_SHIFT));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case RTC_SETUP0:
|
||||
this.setup0 = value;
|
||||
break;
|
||||
case RTC_SETUP1:
|
||||
this.setup1 = value;
|
||||
break;
|
||||
case RTC_CTRL:
|
||||
// Though RTC_LOAD_BITS is type SC and should be cleared on next cycle, pico-sdk write
|
||||
// RTC_LOAD_BITS & RTC_ENABLE_BITS seperatly.
|
||||
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c#L76-L80
|
||||
if (value & RTC_LOAD_BITS) {
|
||||
this.ctrl |= RTC_LOAD_BITS;
|
||||
}
|
||||
if (value & RTC_ENABLE_BITS) {
|
||||
this.ctrl |= RTC_ENABLE_BITS;
|
||||
this.ctrl |= RTC_ACTIVE_BITS;
|
||||
if (this.ctrl & RTC_LOAD_BITS) {
|
||||
const year = (this.setup0 >> SETUP_0_YEAR_SHIFT) & SETUP_0_YEAR_MASK;
|
||||
const month = (this.setup0 >> SETUP_0_MONTH_SHIFT) & SETUP_0_MONTH_MASK;
|
||||
const day = (this.setup0 >> SETUP_0_DAY_SHIFT) & SETUP_0_DAY_MASK;
|
||||
const hour = (this.setup1 >> SETUP_1_HOUR_SHIFT) & SETUP_1_HOUR_MASK;
|
||||
const min = (this.setup1 >> SETUP_1_MIN_SHIFT) & SETUP_1_MIN_MASK;
|
||||
const sec = (this.setup1 >> SETUP_1_SEC_SHIFT) & SETUP_1_SEC_MASK;
|
||||
this.baseline = new Date(year, month - 1, day, hour, min, sec);
|
||||
this.baselineNanos = this.rp2040.clock.nanos;
|
||||
this.ctrl &= ~RTC_LOAD_BITS;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.ctrl &= ~RTC_ENABLE_BITS;
|
||||
this.ctrl &= ~RTC_ACTIVE_BITS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
import { FIFO } from '../utils/fifo.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const SSPCR0 = 0x000; // Control register 0, SSPCR0 on page 3-4
|
||||
const SSPCR1 = 0x004; // Control register 1, SSPCR1 on page 3-5
|
||||
const SSPDR = 0x008; // Data register, SSPDR on page 3-6
|
||||
const SSPSR = 0x00c; // Status register, SSPSR on page 3-7
|
||||
const SSPCPSR = 0x010; // Clock prescale register, SSPCPSR on page 3-8
|
||||
const SSPIMSC = 0x014; // Interrupt mask set or clear register, SSPIMSC on page 3-9
|
||||
const SSPRIS = 0x018; // Raw interrupt status register, SSPRIS on page 3-10
|
||||
const SSPMIS = 0x01c; // Masked interrupt status register, SSPMIS on page 3-11
|
||||
const SSPICR = 0x020; // Interrupt clear register, SSPICR on page 3-11
|
||||
const SSPDMACR = 0x024; // DMA control register, SSPDMACR on page 3-12
|
||||
const SSPPERIPHID0 = 0xfe0; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
|
||||
const SSPPERIPHID1 = 0xfe4; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
|
||||
const SSPPERIPHID2 = 0xfe8; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
|
||||
const SSPPERIPHID3 = 0xfec; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
|
||||
const SSPPCELLID0 = 0xff0; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
|
||||
const SSPPCELLID1 = 0xff4; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
|
||||
const SSPPCELLID2 = 0xff8; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
|
||||
const SSPPCELLID3 = 0xffc; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
|
||||
// SSPCR0 bits:
|
||||
const SCR_MASK = 0xff;
|
||||
const SCR_SHIFT = 8;
|
||||
const SPH = 1 << 7;
|
||||
const SPO = 1 << 6;
|
||||
const FRF_MASK = 0x3;
|
||||
const FRF_SHIFT = 4;
|
||||
const DSS_MASK = 0xf;
|
||||
const DSS_SHIFT = 0;
|
||||
// SSPCR1 bits:
|
||||
const SOD = 1 << 3;
|
||||
const MS = 1 << 2;
|
||||
const SSE = 1 << 1;
|
||||
const LBM = 1 << 0;
|
||||
// SSPSR bits:
|
||||
const BSY = 1 << 4;
|
||||
const RFF = 1 << 3;
|
||||
const RNE = 1 << 2;
|
||||
const TNF = 1 << 1;
|
||||
const TFE = 1 << 0;
|
||||
// SSPCPSR bits:
|
||||
const CPSDVSR_MASK = 0xfe;
|
||||
const CPSDVSR_SHIFT = 0;
|
||||
// SSPDMACR bits:
|
||||
const TXDMAE = 1 << 1;
|
||||
const RXDMAE = 1 << 0;
|
||||
// Interrupts:
|
||||
const SSPTXINTR = 1 << 3;
|
||||
const SSPRXINTR = 1 << 2;
|
||||
const SSPRTINTR = 1 << 1;
|
||||
const SSPRORINTR = 1 << 0;
|
||||
export class RPSPI extends BasePeripheral {
|
||||
get intStatus() {
|
||||
return this.intRaw & this.intEnable;
|
||||
}
|
||||
get enabled() {
|
||||
return !!(this.control1 & SSE);
|
||||
}
|
||||
/** Data size in bits: 4 to 16 bits */
|
||||
get dataBits() {
|
||||
return ((this.control0 >> DSS_SHIFT) & DSS_MASK) + 1;
|
||||
}
|
||||
get masterMode() {
|
||||
return !(this.control0 & MS);
|
||||
}
|
||||
get spiMode() {
|
||||
const cpol = this.control0 & SPO;
|
||||
const cpha = this.control0 & SPH;
|
||||
return cpol ? (cpha ? 2 : 3) : cpha ? 1 : 0;
|
||||
}
|
||||
get clockFrequency() {
|
||||
if (!this.clockDivisor) {
|
||||
return 0;
|
||||
}
|
||||
const scr = (this.control0 >> SCR_SHIFT) & SCR_MASK;
|
||||
return this.rp2040.clkPeri / (this.clockDivisor * (1 + scr));
|
||||
}
|
||||
updateDMATx() {
|
||||
if (this.txFIFO.full) {
|
||||
this.rp2040.dma.clearDREQ(this.dreq.tx);
|
||||
}
|
||||
else {
|
||||
this.rp2040.dma.setDREQ(this.dreq.tx);
|
||||
}
|
||||
}
|
||||
updateDMARx() {
|
||||
if (this.rxFIFO.empty) {
|
||||
this.rp2040.dma.clearDREQ(this.dreq.rx);
|
||||
}
|
||||
else {
|
||||
this.rp2040.dma.setDREQ(this.dreq.rx);
|
||||
}
|
||||
}
|
||||
constructor(rp2040, name, irq, dreq) {
|
||||
super(rp2040, name);
|
||||
this.irq = irq;
|
||||
this.dreq = dreq;
|
||||
this.rxFIFO = new FIFO(8);
|
||||
this.txFIFO = new FIFO(8);
|
||||
// User provided callbacks
|
||||
this.onTransmit = () => this.completeTransmit(0);
|
||||
this.busy = false;
|
||||
this.control0 = 0;
|
||||
this.control1 = 0;
|
||||
this.dmaControl = 0;
|
||||
this.clockDivisor = 0;
|
||||
this.intRaw = 0;
|
||||
this.intEnable = 0;
|
||||
this.updateDMATx();
|
||||
this.updateDMARx();
|
||||
}
|
||||
doTX() {
|
||||
if (!this.busy && !this.txFIFO.empty) {
|
||||
const value = this.txFIFO.pull();
|
||||
this.busy = true;
|
||||
this.onTransmit(value);
|
||||
this.fifosUpdated();
|
||||
}
|
||||
}
|
||||
completeTransmit(rxValue) {
|
||||
this.busy = false;
|
||||
if (!this.rxFIFO.full) {
|
||||
this.rxFIFO.push(rxValue);
|
||||
}
|
||||
else {
|
||||
this.intRaw |= SSPRORINTR;
|
||||
}
|
||||
this.fifosUpdated();
|
||||
this.doTX();
|
||||
}
|
||||
checkInterrupts() {
|
||||
this.rp2040.setInterrupt(this.irq, !!this.intStatus);
|
||||
}
|
||||
fifosUpdated() {
|
||||
const prevStatus = this.intStatus;
|
||||
if (this.txFIFO.itemCount <= this.txFIFO.size / 2) {
|
||||
this.intRaw |= SSPTXINTR;
|
||||
}
|
||||
else {
|
||||
this.intRaw &= ~SSPTXINTR;
|
||||
}
|
||||
if (this.rxFIFO.itemCount >= this.rxFIFO.size / 2) {
|
||||
this.intRaw |= SSPRXINTR;
|
||||
}
|
||||
else {
|
||||
this.intRaw &= ~SSPRXINTR;
|
||||
}
|
||||
if (this.intStatus !== prevStatus) {
|
||||
this.checkInterrupts();
|
||||
}
|
||||
this.updateDMATx();
|
||||
this.updateDMARx();
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case SSPCR0:
|
||||
return this.control0;
|
||||
case SSPCR1:
|
||||
return this.control1;
|
||||
case SSPDR:
|
||||
if (!this.rxFIFO.empty) {
|
||||
const value = this.rxFIFO.pull();
|
||||
this.fifosUpdated();
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
case SSPSR:
|
||||
return ((this.busy || !this.txFIFO.empty ? BSY : 0) |
|
||||
(this.rxFIFO.full ? RFF : 0) |
|
||||
(!this.rxFIFO.empty ? RNE : 0) |
|
||||
(!this.txFIFO.full ? TNF : 0) |
|
||||
(this.txFIFO.empty ? TFE : 0));
|
||||
case SSPCPSR:
|
||||
return this.clockDivisor;
|
||||
case SSPIMSC:
|
||||
return this.intEnable;
|
||||
case SSPRIS:
|
||||
return this.intRaw;
|
||||
case SSPMIS:
|
||||
return this.intStatus;
|
||||
case SSPDMACR:
|
||||
return this.dmaControl;
|
||||
case SSPPERIPHID0:
|
||||
return 0x22;
|
||||
case SSPPERIPHID1:
|
||||
return 0x10;
|
||||
case SSPPERIPHID2:
|
||||
return 0x34;
|
||||
case SSPPERIPHID3:
|
||||
return 0x00;
|
||||
case SSPPCELLID0:
|
||||
return 0x0d;
|
||||
case SSPPCELLID1:
|
||||
return 0xf0;
|
||||
case SSPPCELLID2:
|
||||
return 0x05;
|
||||
case SSPPCELLID3:
|
||||
return 0xb1;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case SSPCR0:
|
||||
this.control0 = value;
|
||||
return;
|
||||
case SSPCR1:
|
||||
this.control1 = value;
|
||||
return;
|
||||
case SSPDR:
|
||||
if (!this.txFIFO.full) {
|
||||
// decoded with respect to SSPCR0.DSS
|
||||
this.txFIFO.push(value & ((1 << this.dataBits) - 1));
|
||||
this.doTX();
|
||||
this.fifosUpdated();
|
||||
}
|
||||
return;
|
||||
case SSPCPSR:
|
||||
this.clockDivisor = value & CPSDVSR_MASK;
|
||||
return;
|
||||
case SSPIMSC:
|
||||
this.intEnable = value;
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
case SSPDMACR:
|
||||
this.dmaControl = value;
|
||||
return;
|
||||
case SSPICR:
|
||||
this.intRaw &= ~(value & (SSPRTINTR | SSPRORINTR));
|
||||
this.checkInterrupts();
|
||||
return;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
/* See RP2040 datasheet sect 4.10.13 */
|
||||
const SSI_CTRLR0 = 0x00000000;
|
||||
const SSI_CTRLR1 = 0x00000004;
|
||||
const SSI_SSIENR = 0x00000008;
|
||||
const SSI_MWCR = 0x0000000c;
|
||||
const SSI_SER = 0x00000010;
|
||||
const SSI_BAUDR = 0x00000014;
|
||||
const SSI_TXFTLR = 0x00000018;
|
||||
const SSI_RXFTLR = 0x0000001c;
|
||||
const SSI_TXFLR = 0x00000020;
|
||||
const SSI_RXFLR = 0x00000024;
|
||||
const SSI_SR = 0x00000028;
|
||||
const SSI_SR_TFNF_BITS = 0x00000002;
|
||||
const SSI_SR_TFE_BITS = 0x00000004;
|
||||
const SSI_SR_RFNE_BITS = 0x00000008;
|
||||
const SSI_IMR = 0x0000002c;
|
||||
const SSI_ISR = 0x00000030;
|
||||
const SSI_RISR = 0x00000034;
|
||||
const SSI_TXOICR = 0x00000038;
|
||||
const SSI_RXOICR = 0x0000003c;
|
||||
const SSI_RXUICR = 0x00000040;
|
||||
const SSI_MSTICR = 0x00000044;
|
||||
const SSI_ICR = 0x00000048;
|
||||
const SSI_DMACR = 0x0000004c;
|
||||
const SSI_DMATDLR = 0x00000050;
|
||||
const SSI_DMARDLR = 0x00000054;
|
||||
/** Identification register */
|
||||
const SSI_IDR = 0x00000058;
|
||||
const SSI_VERSION_ID = 0x0000005c;
|
||||
const SSI_DR0 = 0x00000060;
|
||||
const SSI_RX_SAMPLE_DLY = 0x000000f0;
|
||||
const SSI_SPI_CTRL_R0 = 0x000000f4;
|
||||
const SSI_TXD_DRIVE_EDGE = 0x000000f8;
|
||||
const CMD_READ_STATUS = 0x05;
|
||||
export class RPSSI extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.dr0 = 0;
|
||||
this.txflr = 0;
|
||||
this.rxflr = 0;
|
||||
this.baudr = 0;
|
||||
this.crtlr0 = 0;
|
||||
this.crtlr1 = 0;
|
||||
this.ssienr = 0;
|
||||
this.spictlr0 = 0;
|
||||
this.rxsampldly = 0;
|
||||
this.txddriveedge = 0;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case SSI_TXFLR:
|
||||
return this.txflr;
|
||||
case SSI_RXFLR:
|
||||
return this.rxflr;
|
||||
case SSI_CTRLR0:
|
||||
return this.crtlr0; /* & 0x017FFFFF = b23,b25..31 reserved */
|
||||
case SSI_CTRLR1:
|
||||
return this.crtlr1;
|
||||
case SSI_SSIENR:
|
||||
return this.ssienr;
|
||||
case SSI_BAUDR:
|
||||
return this.baudr;
|
||||
case SSI_SR:
|
||||
return SSI_SR_TFE_BITS | SSI_SR_RFNE_BITS | SSI_SR_TFNF_BITS;
|
||||
case SSI_IDR:
|
||||
return 0x51535049;
|
||||
case SSI_VERSION_ID:
|
||||
return 0x3430312a;
|
||||
case SSI_RX_SAMPLE_DLY:
|
||||
return this.rxsampldly;
|
||||
case SSI_TXD_DRIVE_EDGE:
|
||||
return this.txddriveedge;
|
||||
case SSI_SPI_CTRL_R0:
|
||||
return this.spictlr0; /* b6,7,10,19..23 reserved */
|
||||
case SSI_DR0:
|
||||
return this.dr0;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case SSI_TXFLR:
|
||||
this.txflr = value;
|
||||
return;
|
||||
case SSI_RXFLR:
|
||||
this.rxflr = value;
|
||||
return;
|
||||
case SSI_CTRLR0:
|
||||
this.crtlr0 = value; /* & 0x017FFFFF = b23,b25..31 reserved */
|
||||
return;
|
||||
case SSI_CTRLR1:
|
||||
this.crtlr1 = value;
|
||||
return;
|
||||
case SSI_SSIENR:
|
||||
this.ssienr = value;
|
||||
return;
|
||||
case SSI_BAUDR:
|
||||
this.baudr = value;
|
||||
return;
|
||||
case SSI_RX_SAMPLE_DLY:
|
||||
this.rxsampldly = value & 0xff;
|
||||
return;
|
||||
case SSI_TXD_DRIVE_EDGE:
|
||||
this.txddriveedge = value & 0xff;
|
||||
return;
|
||||
case SSI_SPI_CTRL_R0:
|
||||
this.spictlr0 = value;
|
||||
return;
|
||||
case SSI_DR0:
|
||||
if (value === CMD_READ_STATUS) {
|
||||
this.dr0 = 0; // tell stage2 that we completed a write
|
||||
}
|
||||
return;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const PROC0_NMI_MASK = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const PROC1_NMI_MASK = 4;
|
||||
export class RP2040SysCfg extends BasePeripheral {
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case PROC0_NMI_MASK:
|
||||
return this.rp2040.core.interruptNMIMask;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case PROC0_NMI_MASK:
|
||||
this.rp2040.core.interruptNMIMask = value;
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const CHIP_ID = 0;
|
||||
const PLATFORM = 0x4;
|
||||
const GITREF_RP2040 = 0x40;
|
||||
export class RP2040SysInfo extends BasePeripheral {
|
||||
readUint32(offset) {
|
||||
// All the values here were verified against the silicon
|
||||
switch (offset) {
|
||||
case CHIP_ID:
|
||||
return 0x10002927;
|
||||
case PLATFORM:
|
||||
return 0x00000002;
|
||||
case GITREF_RP2040:
|
||||
return 0xe0c912e8;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const PLATFORM = 0;
|
||||
const ASIC = 1;
|
||||
export class RPTBMAN extends BasePeripheral {
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case PLATFORM:
|
||||
return ASIC;
|
||||
default:
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
import { IRQ } from '../irq.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const TIMEHR = 0x08;
|
||||
const TIMELR = 0x0c;
|
||||
const TIMERAWH = 0x24;
|
||||
const TIMERAWL = 0x28;
|
||||
const ALARM0 = 0x10;
|
||||
const ALARM1 = 0x14;
|
||||
const ALARM2 = 0x18;
|
||||
const ALARM3 = 0x1c;
|
||||
const ARMED = 0x20;
|
||||
const PAUSE = 0x30;
|
||||
const INTR = 0x34;
|
||||
const INTE = 0x38;
|
||||
const INTF = 0x3c;
|
||||
const INTS = 0x40;
|
||||
const ALARM_0 = 1 << 0;
|
||||
const ALARM_1 = 1 << 1;
|
||||
const ALARM_2 = 1 << 2;
|
||||
const ALARM_3 = 1 << 3;
|
||||
const timerInterrupts = [IRQ.TIMER_0, IRQ.TIMER_1, IRQ.TIMER_2, IRQ.TIMER_3];
|
||||
class RPTimerAlarm {
|
||||
constructor(bitValue, clockAlarm) {
|
||||
this.bitValue = bitValue;
|
||||
this.clockAlarm = clockAlarm;
|
||||
this.armed = false;
|
||||
this.targetMicros = 0;
|
||||
}
|
||||
}
|
||||
export class RPTimer extends BasePeripheral {
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
this.latchedTimeHigh = 0;
|
||||
this.intRaw = 0;
|
||||
this.intEnable = 0;
|
||||
this.intForce = 0;
|
||||
this.paused = false;
|
||||
this.clock = rp2040.clock;
|
||||
this.alarms = [
|
||||
new RPTimerAlarm(ALARM_0, this.clock.createAlarm(() => this.fireAlarm(0))),
|
||||
new RPTimerAlarm(ALARM_1, this.clock.createAlarm(() => this.fireAlarm(1))),
|
||||
new RPTimerAlarm(ALARM_2, this.clock.createAlarm(() => this.fireAlarm(2))),
|
||||
new RPTimerAlarm(ALARM_3, this.clock.createAlarm(() => this.fireAlarm(3))),
|
||||
];
|
||||
}
|
||||
get intStatus() {
|
||||
return (this.intRaw & this.intEnable) | this.intForce;
|
||||
}
|
||||
readUint32(offset) {
|
||||
const time = this.clock.nanos / 1000;
|
||||
switch (offset) {
|
||||
case TIMEHR:
|
||||
return this.latchedTimeHigh;
|
||||
case TIMELR:
|
||||
this.latchedTimeHigh = Math.floor(time / 2 ** 32);
|
||||
return time >>> 0;
|
||||
case TIMERAWH:
|
||||
return Math.floor(time / 2 ** 32);
|
||||
case TIMERAWL:
|
||||
return time >>> 0;
|
||||
case ALARM0:
|
||||
return this.alarms[0].targetMicros;
|
||||
case ALARM1:
|
||||
return this.alarms[1].targetMicros;
|
||||
case ALARM2:
|
||||
return this.alarms[2].targetMicros;
|
||||
case ALARM3:
|
||||
return this.alarms[3].targetMicros;
|
||||
case PAUSE:
|
||||
return this.paused ? 1 : 0;
|
||||
case INTR:
|
||||
return this.intRaw;
|
||||
case INTE:
|
||||
return this.intEnable;
|
||||
case INTF:
|
||||
return this.intForce;
|
||||
case INTS:
|
||||
return this.intStatus;
|
||||
case ARMED:
|
||||
return ((this.alarms[0].armed ? this.alarms[0].bitValue : 0) |
|
||||
(this.alarms[1].armed ? this.alarms[1].bitValue : 0) |
|
||||
(this.alarms[2].armed ? this.alarms[2].bitValue : 0) |
|
||||
(this.alarms[3].armed ? this.alarms[3].bitValue : 0));
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case ALARM0:
|
||||
case ALARM1:
|
||||
case ALARM2:
|
||||
case ALARM3: {
|
||||
const alarmIndex = (offset - ALARM0) / 4;
|
||||
const alarm = this.alarms[alarmIndex];
|
||||
const deltaMicros = (value - this.clock.nanos / 1000) >>> 0;
|
||||
alarm.armed = true;
|
||||
alarm.targetMicros = value;
|
||||
alarm.clockAlarm.schedule(deltaMicros * 1000);
|
||||
break;
|
||||
}
|
||||
case ARMED:
|
||||
for (const alarm of this.alarms) {
|
||||
if (this.rawWriteValue & alarm.bitValue) {
|
||||
this.disarmAlarm(alarm);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PAUSE:
|
||||
this.paused = !!(value & 1);
|
||||
if (this.paused) {
|
||||
this.warn('Unimplemented Timer Pause');
|
||||
}
|
||||
// TODO actually pause the timer
|
||||
break;
|
||||
case INTR:
|
||||
this.intRaw &= ~this.rawWriteValue;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case INTE:
|
||||
this.intEnable = value & 0xf;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case INTF:
|
||||
this.intForce = value & 0xf;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
fireAlarm(index) {
|
||||
const alarm = this.alarms[index];
|
||||
this.disarmAlarm(alarm);
|
||||
this.intRaw |= alarm.bitValue;
|
||||
this.checkInterrupts();
|
||||
}
|
||||
checkInterrupts() {
|
||||
const { intStatus } = this;
|
||||
for (let i = 0; i < this.alarms.length; i++) {
|
||||
this.rp2040.setInterrupt(timerInterrupts[i], !!(intStatus & (1 << i)));
|
||||
}
|
||||
}
|
||||
disarmAlarm(alarm) {
|
||||
alarm.clockAlarm.cancel();
|
||||
alarm.armed = false;
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
import { FIFO } from '../utils/fifo.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const UARTDR = 0x0;
|
||||
const UARTFR = 0x18;
|
||||
const UARTIBRD = 0x24;
|
||||
const UARTFBRD = 0x28;
|
||||
const UARTLCR_H = 0x2c;
|
||||
const UARTCR = 0x30;
|
||||
const UARTIMSC = 0x38;
|
||||
const UARTIRIS = 0x3c;
|
||||
const UARTIMIS = 0x40;
|
||||
const UARTICR = 0x44;
|
||||
const UARTPERIPHID0 = 0xfe0;
|
||||
const UARTPERIPHID1 = 0xfe4;
|
||||
const UARTPERIPHID2 = 0xfe8;
|
||||
const UARTPERIPHID3 = 0xfec;
|
||||
const UARTPCELLID0 = 0xff0;
|
||||
const UARTPCELLID1 = 0xff4;
|
||||
const UARTPCELLID2 = 0xff8;
|
||||
const UARTPCELLID3 = 0xffc;
|
||||
// UARTFR bits:
|
||||
const TXFE = 1 << 7;
|
||||
const RXFF = 1 << 6;
|
||||
const RXFE = 1 << 4;
|
||||
// UARTLCR_H bits:
|
||||
const FEN = 1 << 4;
|
||||
// UARTCR bits:
|
||||
const RXE = 1 << 9;
|
||||
const TXE = 1 << 8;
|
||||
const UARTEN = 1 << 0;
|
||||
// Interrupt bits
|
||||
const UARTTXINTR = 1 << 5;
|
||||
const UARTRXINTR = 1 << 4;
|
||||
export class RPUART extends BasePeripheral {
|
||||
constructor(rp2040, name, irq, dreq) {
|
||||
super(rp2040, name);
|
||||
this.irq = irq;
|
||||
this.dreq = dreq;
|
||||
this.ctrlRegister = RXE | TXE;
|
||||
this.lineCtrlRegister = 0;
|
||||
this.rxFIFO = new FIFO(32);
|
||||
this.interruptMask = 0;
|
||||
this.interruptStatus = 0;
|
||||
this.intDivisor = 0;
|
||||
this.fracDivisor = 0;
|
||||
}
|
||||
get enabled() {
|
||||
return !!(this.ctrlRegister & UARTEN);
|
||||
}
|
||||
get txEnabled() {
|
||||
return !!(this.ctrlRegister & TXE);
|
||||
}
|
||||
get rxEnabled() {
|
||||
return !!(this.ctrlRegister & RXE);
|
||||
}
|
||||
get fifosEnabled() {
|
||||
return !!(this.lineCtrlRegister & FEN);
|
||||
}
|
||||
/**
|
||||
* Number of bits per UART character
|
||||
*/
|
||||
get wordLength() {
|
||||
switch ((this.lineCtrlRegister >>> 5) & 0x3) {
|
||||
case 0b00:
|
||||
return 5;
|
||||
case 0b01:
|
||||
return 6;
|
||||
case 0b10:
|
||||
return 7;
|
||||
case 0b11:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
get baudDivider() {
|
||||
return this.intDivisor + this.fracDivisor / 64;
|
||||
}
|
||||
get baudRate() {
|
||||
return Math.round(this.rp2040.clkPeri / (this.baudDivider * 16));
|
||||
}
|
||||
get flags() {
|
||||
return (this.rxFIFO.full ? RXFF : 0) | (this.rxFIFO.empty ? RXFE : 0) | TXFE;
|
||||
}
|
||||
checkInterrupts() {
|
||||
// TODO We should actually implement a proper FIFO for TX
|
||||
this.interruptStatus |= UARTTXINTR;
|
||||
this.rp2040.setInterrupt(this.irq, !!(this.interruptStatus & this.interruptMask));
|
||||
}
|
||||
feedByte(value) {
|
||||
this.rxFIFO.push(value);
|
||||
// TODO check if the FIFO has reached the threshold level
|
||||
this.interruptStatus |= UARTRXINTR;
|
||||
this.checkInterrupts();
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case UARTDR: {
|
||||
const value = this.rxFIFO.pull();
|
||||
if (!this.rxFIFO.empty) {
|
||||
this.interruptStatus |= UARTRXINTR;
|
||||
}
|
||||
else {
|
||||
this.interruptStatus &= ~UARTRXINTR;
|
||||
}
|
||||
this.checkInterrupts();
|
||||
return value;
|
||||
}
|
||||
case UARTFR:
|
||||
return this.flags;
|
||||
case UARTIBRD:
|
||||
return this.intDivisor;
|
||||
case UARTFBRD:
|
||||
return this.fracDivisor;
|
||||
case UARTLCR_H:
|
||||
return this.lineCtrlRegister;
|
||||
case UARTCR:
|
||||
return this.ctrlRegister;
|
||||
case UARTIMSC:
|
||||
return this.interruptMask;
|
||||
case UARTIRIS:
|
||||
return this.interruptStatus;
|
||||
case UARTIMIS:
|
||||
return this.interruptStatus & this.interruptMask;
|
||||
case UARTPERIPHID0:
|
||||
return 0x11;
|
||||
case UARTPERIPHID1:
|
||||
return 0x10;
|
||||
case UARTPERIPHID2:
|
||||
return 0x34;
|
||||
case UARTPERIPHID3:
|
||||
return 0x00;
|
||||
case UARTPCELLID0:
|
||||
return 0x0d;
|
||||
case UARTPCELLID1:
|
||||
return 0xf0;
|
||||
case UARTPCELLID2:
|
||||
return 0x05;
|
||||
case UARTPCELLID3:
|
||||
return 0xb1;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
var _a, _b, _c;
|
||||
switch (offset) {
|
||||
case UARTDR:
|
||||
(_a = this.onByte) === null || _a === void 0 ? void 0 : _a.call(this, value & 0xff);
|
||||
break;
|
||||
case UARTIBRD:
|
||||
this.intDivisor = value & 0xffff;
|
||||
(_b = this.onBaudRateChange) === null || _b === void 0 ? void 0 : _b.call(this, this.baudRate);
|
||||
break;
|
||||
case UARTFBRD:
|
||||
this.fracDivisor = value & 0x3f;
|
||||
(_c = this.onBaudRateChange) === null || _c === void 0 ? void 0 : _c.call(this, this.baudRate);
|
||||
break;
|
||||
case UARTLCR_H:
|
||||
this.lineCtrlRegister = value;
|
||||
break;
|
||||
case UARTCR:
|
||||
this.ctrlRegister = value;
|
||||
if (this.enabled) {
|
||||
this.rp2040.dma.setDREQ(this.dreq.tx);
|
||||
}
|
||||
else {
|
||||
this.rp2040.dma.clearDREQ(this.dreq.tx);
|
||||
}
|
||||
break;
|
||||
case UARTIMSC:
|
||||
this.interruptMask = value & 0x7ff;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case UARTICR:
|
||||
this.interruptStatus &= ~this.rawWriteValue;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+847
@@ -0,0 +1,847 @@
|
||||
import { IRQ } from '../irq.js';
|
||||
import { parseSetupPacket, } from '../usb/usb-device.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const ENDPOINT_COUNT = 16;
|
||||
const USB_HOST_INTERRUPT_ENDPOINTS = 15;
|
||||
// USB DPSRAM Registers - Device mode
|
||||
const EP1_IN_CONTROL = 0x8;
|
||||
const EP0_IN_BUFFER_CONTROL = 0x80;
|
||||
const EP0_OUT_BUFFER_CONTROL = 0x84;
|
||||
const EP15_OUT_BUFFER_CONTROL = 0xfc;
|
||||
// USB DPRAM Registers - Host mode
|
||||
// Host DPRAM layout (from pico-sdk hardware/structs/usb.h):
|
||||
// 0x00-0x07: setup_packet (8 bytes)
|
||||
// 0x08-0x7f: int_ep_ctrl[15] (15 × 8 bytes, ctrl + spare per entry)
|
||||
// 0x80: epx_buf_ctrl (4 bytes)
|
||||
// 0x84: _spare0 (4 bytes)
|
||||
// 0x88-0xff: int_ep_buffer_ctrl[15] (15 × 8 bytes, ctrl + spare per entry)
|
||||
// 0x100: epx_ctrl (4 bytes)
|
||||
// 0x104-0x17f: _spare1 (124 bytes)
|
||||
// 0x180+: epx_data buffer (up to end of DPRAM)
|
||||
const HOST_SETUP_PACKET = 0x00;
|
||||
const HOST_INT_EP_CTRL_BASE = 0x08; // int_ep_ctrl[0] at 0x08, stride 8
|
||||
const HOST_INT_EP_BUF_CTRL_BASE = 0x88; // int_ep_buffer_ctrl[0] at 0x88, stride 8
|
||||
const HOST_EPX_BUF_CTRL = 0x80;
|
||||
const HOST_EPX_DATA = 0x180;
|
||||
// Endpoint Control bits
|
||||
const USB_CTRL_DOUBLE_BUF = 1 << 30;
|
||||
const USB_CTRL_INTERRUPT_PER_TRANSFER = 1 << 29;
|
||||
const EP_CTRL_ENABLE_BITS = 1 << 31;
|
||||
const EP_CTRL_BUFFER_TYPE_LSB = 26;
|
||||
const EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB = 16;
|
||||
// Buffer Control bits
|
||||
const USB_BUF_CTRL_AVAILABLE = 1 << 10;
|
||||
const USB_BUF_CTRL_FULL = 1 << 15;
|
||||
const USB_BUF_CTRL_LEN_MASK = 0x3ff;
|
||||
const USB_BUF_CTRL_DATA1_PID = 1 << 13;
|
||||
const USB_BUF_CTRL_LAST = 1 << 14;
|
||||
// Buffer1
|
||||
const USB_BUF1_SHIFT = 16;
|
||||
const USB_BUF1_OFFSET = 64;
|
||||
// USB Peripheral Registers
|
||||
const ADDR_ENDP = 0x0;
|
||||
const ADDR_ENDP1 = 0x04;
|
||||
const ADDR_ENDP15 = 0x3c;
|
||||
const MAIN_CTRL = 0x40;
|
||||
const SOF_WR = 0x44;
|
||||
const SOF_RD = 0x48;
|
||||
const SIE_CTRL = 0x4c;
|
||||
const SIE_STATUS = 0x50;
|
||||
const INT_EP_CTRL = 0x54;
|
||||
const BUFF_STATUS = 0x58;
|
||||
const BUFF_CPU_SHOULD_HANDLE = 0x5c;
|
||||
const EP_ABORT = 0x60;
|
||||
const EP_ABORT_DONE = 0x64;
|
||||
const EP_STALL_ARM = 0x68;
|
||||
const NAK_POLL = 0x6c;
|
||||
const EP_STATUS_STALL_NAK = 0x70;
|
||||
const USB_MUXING = 0x74;
|
||||
const USB_PWR = 0x78;
|
||||
const USBPHY_DIRECT = 0x7c;
|
||||
const USBPHY_DIRECT_OVERRIDE = 0x80;
|
||||
const USBPHY_TRIM = 0x84;
|
||||
const INTR = 0x8c;
|
||||
const INTE = 0x90;
|
||||
const INTF = 0x94;
|
||||
const INTS = 0x98;
|
||||
// MAIN_CTRL bits
|
||||
const SIM_TIMING = 1 << 31;
|
||||
const HOST_NDEVICE = 1 << 1;
|
||||
const CONTROLLER_EN = 1 << 0;
|
||||
// SIE_CTRL bits (host mode)
|
||||
const SIE_CTRL_EP0_INT_STALL = 1 << 31;
|
||||
const SIE_CTRL_EP0_DOUBLE_BUF = 1 << 30;
|
||||
const SIE_CTRL_EP0_INT_1BUF = 1 << 29;
|
||||
const SIE_CTRL_EP0_INT_2BUF = 1 << 28;
|
||||
const SIE_CTRL_EP0_INT_NAK = 1 << 27;
|
||||
const SIE_CTRL_DIRECT_EN = 1 << 26;
|
||||
const SIE_CTRL_DIRECT_DP = 1 << 25;
|
||||
const SIE_CTRL_DIRECT_DM = 1 << 24;
|
||||
const SIE_CTRL_TRANSCEIVER_PD = 1 << 18;
|
||||
const SIE_CTRL_RPU_OPT = 1 << 17;
|
||||
const SIE_CTRL_PULLUP_EN = 1 << 16;
|
||||
const SIE_CTRL_PULLDOWN_EN = 1 << 15;
|
||||
const SIE_CTRL_RESET_BUS = 1 << 13;
|
||||
const SIE_CTRL_RESUME = 1 << 12;
|
||||
const SIE_CTRL_VBUS_EN = 1 << 11;
|
||||
const SIE_CTRL_KEEP_ALIVE_EN = 1 << 10;
|
||||
const SIE_CTRL_SOF_EN = 1 << 9;
|
||||
const SIE_CTRL_SOF_SYNC = 1 << 8;
|
||||
const SIE_CTRL_PREAMBLE_EN = 1 << 6;
|
||||
const SIE_CTRL_STOP_TRANS = 1 << 4;
|
||||
const SIE_CTRL_RECEIVE_DATA = 1 << 3;
|
||||
const SIE_CTRL_SEND_DATA = 1 << 2;
|
||||
const SIE_CTRL_SEND_SETUP = 1 << 1;
|
||||
const SIE_CTRL_START_TRANS = 1 << 0;
|
||||
// SIE_STATUS bits
|
||||
const SIE_DATA_SEQ_ERROR = 1 << 31;
|
||||
const SIE_ACK_REC = 1 << 30;
|
||||
const SIE_STALL_REC = 1 << 29;
|
||||
const SIE_NAK_REC = 1 << 28;
|
||||
const SIE_RX_TIMEOUT = 1 << 27;
|
||||
const SIE_RX_OVERFLOW = 1 << 26;
|
||||
const SIE_BIT_STUFF_ERROR = 1 << 25;
|
||||
const SIE_CRC_ERROR = 1 << 24;
|
||||
const SIE_BUS_RESET = 1 << 19;
|
||||
const SIE_TRANS_COMPLETE = 1 << 18;
|
||||
const SIE_SETUP_REC = 1 << 17;
|
||||
const SIE_CONNECTED = 1 << 16;
|
||||
const SIE_RESUME = 1 << 11;
|
||||
const SIE_VBUS_OVER_CURR = 1 << 10;
|
||||
const SIE_SPEED = 1 << 9;
|
||||
const SIE_SPEED_LS_VALUE = 1; // Low speed
|
||||
const SIE_SPEED_FS_VALUE = 2; // Full speed
|
||||
const SIE_SUSPENDED = 1 << 4;
|
||||
const SIE_LINE_STATE_MASK = 0x3;
|
||||
const SIE_LINE_STATE_SHIFT = 2;
|
||||
const SIE_VBUS_DETECTED = 1 << 0;
|
||||
// USB_MUXING bits
|
||||
const SOFTCON = 1 << 3;
|
||||
const TO_DIGITAL_PAD = 1 << 2;
|
||||
const TO_EXTPHY = 1 << 1;
|
||||
const TO_PHY = 1 << 0;
|
||||
// USB_PWR bits
|
||||
const PWR_VBUS_DETECT = 1 << 3;
|
||||
const PWR_VBUS_DETECT_OVERRIDE_EN = 1 << 2;
|
||||
const PWR_OVERCURR_DETECT = 1 << 1;
|
||||
const PWR_OVERCURR_DETECT_EN = 1 << 0;
|
||||
// INTR bits (directly from RP2040 datasheet)
|
||||
const INTR_EP_STALL_NAK = 1 << 19;
|
||||
const INTR_ABORT_DONE = 1 << 18;
|
||||
const INTR_DEV_SOF = 1 << 17;
|
||||
const INTR_SETUP_REQ = 1 << 16;
|
||||
const INTR_DEV_RESUME_FROM_HOST = 1 << 15;
|
||||
const INTR_DEV_SUSPEND = 1 << 14;
|
||||
const INTR_DEV_CONN_DIS = 1 << 13;
|
||||
const INTR_BUS_RESET = 1 << 12;
|
||||
const INTR_VBUS_DETECT = 1 << 11;
|
||||
const INTR_STALL = 1 << 10;
|
||||
const INTR_ERROR_CRC = 1 << 9;
|
||||
const INTR_ERROR_BIT_STUFF = 1 << 8;
|
||||
const INTR_ERROR_RX_OVERFLOW = 1 << 7;
|
||||
const INTR_ERROR_RX_TIMEOUT = 1 << 6;
|
||||
const INTR_ERROR_DATA_SEQ = 1 << 5;
|
||||
const INTR_BUFF_STATUS = 1 << 4;
|
||||
const INTR_TRANS_COMPLETE = 1 << 3;
|
||||
const INTR_HOST_SOF = 1 << 2;
|
||||
const INTR_HOST_RESUME = 1 << 1;
|
||||
const INTR_HOST_CONN_DIS = 1 << 0;
|
||||
// SIE Line states
|
||||
var SIELineState;
|
||||
(function (SIELineState) {
|
||||
SIELineState[SIELineState["SE0"] = 0] = "SE0";
|
||||
SIELineState[SIELineState["J"] = 1] = "J";
|
||||
SIELineState[SIELineState["K"] = 2] = "K";
|
||||
SIELineState[SIELineState["SE1"] = 3] = "SE1";
|
||||
})(SIELineState || (SIELineState = {}));
|
||||
const SIE_WRITECLEAR_MASK = SIE_DATA_SEQ_ERROR |
|
||||
SIE_ACK_REC |
|
||||
SIE_STALL_REC |
|
||||
SIE_NAK_REC |
|
||||
SIE_RX_TIMEOUT |
|
||||
SIE_RX_OVERFLOW |
|
||||
SIE_BIT_STUFF_ERROR |
|
||||
SIE_CONNECTED |
|
||||
SIE_CRC_ERROR |
|
||||
SIE_BUS_RESET |
|
||||
SIE_TRANS_COMPLETE |
|
||||
SIE_SETUP_REC |
|
||||
SIE_RESUME;
|
||||
class USBEndpointAlarm {
|
||||
constructor(alarm) {
|
||||
this.alarm = alarm;
|
||||
this.buffers = [];
|
||||
}
|
||||
schedule(buffer, delayNanos) {
|
||||
this.buffers.push(buffer);
|
||||
this.alarm.schedule(delayNanos);
|
||||
}
|
||||
}
|
||||
export class RPUSBController extends BasePeripheral {
|
||||
get intStatus() {
|
||||
return (this.intRaw & this.intEnable) | this.intForce;
|
||||
}
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
// Common registers
|
||||
this.addrEndp = 0;
|
||||
this.mainCtrl = 0;
|
||||
this.intRaw = 0;
|
||||
this.intEnable = 0;
|
||||
this.intForce = 0;
|
||||
this.sieStatus = 0;
|
||||
this.buffStatus = 0;
|
||||
// Host mode registers
|
||||
this.sieCtrl = 0;
|
||||
this.sofFrameNumber = 0;
|
||||
this.devAddrCtrl = 0; // Device address and endpoint for non-interrupt transfers
|
||||
this.intEpAddrCtrl = new Array(USB_HOST_INTERRUPT_ENDPOINTS).fill(0);
|
||||
this.intEpCtrl = 0; // Interrupt endpoint control (enable bits)
|
||||
this.usbPwr = 0;
|
||||
this.nakPoll = 0;
|
||||
this.epAbort = 0;
|
||||
this.epAbortDone = 0;
|
||||
this.epStallArm = 0;
|
||||
this.epStatusStallNak = 0;
|
||||
// Host mode state
|
||||
this.hostMode = false;
|
||||
this.sofEnabled = false;
|
||||
this.connectedDevice = null;
|
||||
this.pendingSetupResponse = null;
|
||||
this.controlDataPid = 1; // DATA0/DATA1 toggle for control transfers
|
||||
this.expectingStatusPhase = false; // True when expecting IN status for control OUT
|
||||
this.readDelayMicroseconds = 10;
|
||||
this.writeDelayMicroseconds = 10; // Determined empirically
|
||||
this.hostTransactionDelayMicroseconds = 5; // Host transaction delay
|
||||
const clock = rp2040.clock;
|
||||
this.endpointReadAlarms = [];
|
||||
this.endpointWriteAlarms = [];
|
||||
for (let i = 0; i < ENDPOINT_COUNT; ++i) {
|
||||
this.endpointReadAlarms.push(new USBEndpointAlarm(clock.createAlarm(() => {
|
||||
const buffer = this.endpointReadAlarms[i].buffers.shift();
|
||||
if (buffer) {
|
||||
this.finishRead(i, buffer);
|
||||
}
|
||||
})));
|
||||
this.endpointWriteAlarms.push(new USBEndpointAlarm(clock.createAlarm(() => {
|
||||
var _a;
|
||||
for (const buffer of this.endpointWriteAlarms[i].buffers) {
|
||||
(_a = this.onEndpointWrite) === null || _a === void 0 ? void 0 : _a.call(this, i, buffer);
|
||||
}
|
||||
this.endpointWriteAlarms[i].buffers = [];
|
||||
})));
|
||||
}
|
||||
this.resetAlarm = clock.createAlarm(() => {
|
||||
this.sieStatus |= SIE_BUS_RESET;
|
||||
this.sieStatusUpdated();
|
||||
});
|
||||
// Host mode alarms
|
||||
this.sofAlarm = clock.createAlarm(() => {
|
||||
this.generateSOF();
|
||||
});
|
||||
this.hostTransactionAlarm = clock.createAlarm(() => {
|
||||
this.completeHostTransaction();
|
||||
});
|
||||
}
|
||||
readUint32(offset) {
|
||||
// Handle interrupt endpoint address registers (ADDR_ENDP1 through ADDR_ENDP15)
|
||||
if (offset >= ADDR_ENDP1 && offset <= ADDR_ENDP15 && (offset & 0x3) === 0) {
|
||||
const epIndex = (offset - ADDR_ENDP1) >> 2;
|
||||
return this.intEpAddrCtrl[epIndex];
|
||||
}
|
||||
switch (offset) {
|
||||
case ADDR_ENDP:
|
||||
return this.hostMode ? this.devAddrCtrl : this.addrEndp & 0b1111000000001111111;
|
||||
case MAIN_CTRL:
|
||||
return this.mainCtrl;
|
||||
case SOF_WR:
|
||||
return 0; // Write-only
|
||||
case SOF_RD:
|
||||
return this.sofFrameNumber & 0x7ff;
|
||||
case SIE_CTRL:
|
||||
return this.sieCtrl;
|
||||
case SIE_STATUS:
|
||||
// In host mode, reading SIE_STATUS acknowledges the connection event
|
||||
// Clear HOST_CONN_DIS interrupt once firmware reads the status
|
||||
if (this.hostMode && this.intRaw & INTR_HOST_CONN_DIS) {
|
||||
this.intRaw &= ~INTR_HOST_CONN_DIS;
|
||||
this.checkInterrupts();
|
||||
}
|
||||
return this.sieStatus;
|
||||
case INT_EP_CTRL:
|
||||
return this.intEpCtrl;
|
||||
case BUFF_STATUS:
|
||||
return this.buffStatus;
|
||||
case BUFF_CPU_SHOULD_HANDLE:
|
||||
return 0;
|
||||
case EP_ABORT:
|
||||
return this.epAbort;
|
||||
case EP_ABORT_DONE:
|
||||
return this.epAbortDone;
|
||||
case EP_STALL_ARM:
|
||||
return this.epStallArm;
|
||||
case NAK_POLL:
|
||||
return this.nakPoll;
|
||||
case EP_STATUS_STALL_NAK:
|
||||
return this.epStatusStallNak;
|
||||
case USB_PWR:
|
||||
return this.usbPwr;
|
||||
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) {
|
||||
var _a, _b;
|
||||
// Handle interrupt endpoint address registers (ADDR_ENDP1 through ADDR_ENDP15)
|
||||
if (offset >= ADDR_ENDP1 && offset <= ADDR_ENDP15 && (offset & 0x3) === 0) {
|
||||
const epIndex = (offset - ADDR_ENDP1) >> 2;
|
||||
this.intEpAddrCtrl[epIndex] = value;
|
||||
return;
|
||||
}
|
||||
switch (offset) {
|
||||
case ADDR_ENDP:
|
||||
if (this.hostMode) {
|
||||
this.devAddrCtrl = value;
|
||||
}
|
||||
else {
|
||||
this.addrEndp = value;
|
||||
}
|
||||
break;
|
||||
case MAIN_CTRL:
|
||||
this.mainCtrl = value & (SIM_TIMING | CONTROLLER_EN | HOST_NDEVICE);
|
||||
this.hostMode = !!(value & HOST_NDEVICE);
|
||||
if (value & CONTROLLER_EN) {
|
||||
if (this.hostMode) {
|
||||
this.debug('USB Host mode enabled');
|
||||
// In host mode, check if a device is already connected
|
||||
if (this.connectedDevice) {
|
||||
this.onDeviceConnected();
|
||||
}
|
||||
}
|
||||
else {
|
||||
(_a = this.onUSBEnabled) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SOF_WR:
|
||||
this.sofFrameNumber = value & 0x7ff;
|
||||
break;
|
||||
case SIE_CTRL:
|
||||
this.handleSieCtrlWrite(value);
|
||||
break;
|
||||
case INT_EP_CTRL:
|
||||
this.intEpCtrl = value;
|
||||
break;
|
||||
case BUFF_STATUS:
|
||||
this.buffStatus &= ~this.rawWriteValue;
|
||||
this.buffStatusUpdated();
|
||||
break;
|
||||
case EP_ABORT:
|
||||
this.epAbort = value;
|
||||
// Immediately mark as done
|
||||
this.epAbortDone |= value;
|
||||
break;
|
||||
case EP_ABORT_DONE:
|
||||
this.epAbortDone &= ~this.rawWriteValue;
|
||||
break;
|
||||
case EP_STALL_ARM:
|
||||
this.epStallArm = value;
|
||||
break;
|
||||
case NAK_POLL:
|
||||
this.nakPoll = value;
|
||||
break;
|
||||
case EP_STATUS_STALL_NAK:
|
||||
this.epStatusStallNak &= ~this.rawWriteValue;
|
||||
break;
|
||||
case USB_MUXING:
|
||||
// Workaround for busy wait in hw_enumeration_fix_force_ls_j() / hw_enumeration_fix_finish():
|
||||
if (value & TO_DIGITAL_PAD && !(value & TO_PHY)) {
|
||||
this.sieStatus |= SIE_CONNECTED;
|
||||
}
|
||||
break;
|
||||
case USB_PWR:
|
||||
this.usbPwr = value;
|
||||
// VBUS detect override - set VBUS detected in SIE_STATUS
|
||||
if (value & PWR_VBUS_DETECT_OVERRIDE_EN) {
|
||||
if (value & PWR_VBUS_DETECT) {
|
||||
this.sieStatus |= SIE_VBUS_DETECTED;
|
||||
}
|
||||
else {
|
||||
this.sieStatus &= ~SIE_VBUS_DETECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SIE_STATUS:
|
||||
this.sieStatus &= ~(this.rawWriteValue & SIE_WRITECLEAR_MASK);
|
||||
if (this.rawWriteValue & SIE_BUS_RESET) {
|
||||
if (!this.hostMode) {
|
||||
(_b = this.onResetReceived) === null || _b === void 0 ? void 0 : _b.call(this);
|
||||
}
|
||||
this.sieStatus &= ~(SIE_LINE_STATE_MASK << SIE_LINE_STATE_SHIFT);
|
||||
this.sieStatus |= (SIELineState.J << SIE_LINE_STATE_SHIFT) | SIE_CONNECTED;
|
||||
}
|
||||
this.sieStatusUpdated();
|
||||
break;
|
||||
case INTE:
|
||||
this.intEnable = value & 0xfffff;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
case INTF:
|
||||
this.intForce = value & 0xfffff;
|
||||
this.checkInterrupts();
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
readEndpointControlReg(endpoint, out) {
|
||||
const controlRegOffset = EP1_IN_CONTROL + 8 * (endpoint - 1) + (out ? 4 : 0);
|
||||
return this.rp2040.usbDPRAMView.getUint32(controlRegOffset, true);
|
||||
}
|
||||
getEndpointBufferOffset(endpoint, out) {
|
||||
if (endpoint === 0) {
|
||||
return 0x100;
|
||||
}
|
||||
return this.readEndpointControlReg(endpoint, out) & 0xffc0;
|
||||
}
|
||||
DPRAMUpdated(offset, value) {
|
||||
var _a, _b;
|
||||
// Skip device-mode buffer control handling in host mode
|
||||
if (this.hostMode) {
|
||||
return;
|
||||
}
|
||||
if (value & USB_BUF_CTRL_AVAILABLE &&
|
||||
offset >= EP0_IN_BUFFER_CONTROL &&
|
||||
offset <= EP15_OUT_BUFFER_CONTROL) {
|
||||
const endpoint = (offset - EP0_IN_BUFFER_CONTROL) >> 3;
|
||||
const bufferOut = offset & 4 ? true : false;
|
||||
let doubleBuffer = false;
|
||||
let interrupt = true;
|
||||
if (endpoint != 0) {
|
||||
const control = this.readEndpointControlReg(endpoint, bufferOut);
|
||||
doubleBuffer = !!(control & USB_CTRL_DOUBLE_BUF);
|
||||
interrupt = !!(control & USB_CTRL_INTERRUPT_PER_TRANSFER);
|
||||
}
|
||||
if (doubleBuffer && (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_AVAILABLE) {
|
||||
const bufferLength = (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_LEN_MASK;
|
||||
const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut) + USB_BUF1_OFFSET;
|
||||
this.debug(`Start USB transfer, endPoint=${endpoint}, direction=${bufferOut ? 'out' : 'in'} buffer=${bufferOffset.toString(16)} length=${bufferLength}`);
|
||||
value &= ~(USB_BUF_CTRL_AVAILABLE << USB_BUF1_SHIFT);
|
||||
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
|
||||
if (bufferOut) {
|
||||
(_a = this.onEndpointRead) === null || _a === void 0 ? void 0 : _a.call(this, endpoint, bufferLength);
|
||||
}
|
||||
else {
|
||||
value &= ~(USB_BUF_CTRL_FULL << USB_BUF1_SHIFT);
|
||||
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
|
||||
const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength);
|
||||
this.indicateBufferReady(endpoint, false);
|
||||
this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000);
|
||||
}
|
||||
}
|
||||
const bufferLength = value & USB_BUF_CTRL_LEN_MASK;
|
||||
const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut);
|
||||
this.debug(`Start USB transfer, endPoint=${endpoint}, direction=${bufferOut ? 'out' : 'in'} buffer=${bufferOffset.toString(16)} length=${bufferLength}`);
|
||||
value &= ~USB_BUF_CTRL_AVAILABLE;
|
||||
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
|
||||
if (bufferOut) {
|
||||
(_b = this.onEndpointRead) === null || _b === void 0 ? void 0 : _b.call(this, endpoint, bufferLength);
|
||||
}
|
||||
else {
|
||||
value &= ~USB_BUF_CTRL_FULL;
|
||||
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
|
||||
const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength);
|
||||
if (interrupt || !doubleBuffer) {
|
||||
this.indicateBufferReady(endpoint, false);
|
||||
}
|
||||
this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
endpointReadDone(endpoint, buffer, delay = this.readDelayMicroseconds) {
|
||||
this.endpointReadAlarms[endpoint].schedule(buffer, delay * 1000);
|
||||
}
|
||||
finishRead(endpoint, buffer) {
|
||||
const bufferOffset = this.getEndpointBufferOffset(endpoint, true);
|
||||
const bufControlReg = EP0_OUT_BUFFER_CONTROL + endpoint * 8;
|
||||
let bufControl = this.rp2040.usbDPRAMView.getUint32(bufControlReg, true);
|
||||
const requestedLength = bufControl & USB_BUF_CTRL_LEN_MASK;
|
||||
const newLength = Math.min(buffer.length, requestedLength);
|
||||
bufControl |= USB_BUF_CTRL_FULL;
|
||||
bufControl = (bufControl & ~USB_BUF_CTRL_LEN_MASK) | (newLength & USB_BUF_CTRL_LEN_MASK);
|
||||
this.rp2040.usbDPRAMView.setUint32(bufControlReg, bufControl, true);
|
||||
this.rp2040.usbDPRAM.set(buffer.subarray(0, newLength), bufferOffset);
|
||||
this.indicateBufferReady(endpoint, true);
|
||||
}
|
||||
checkInterrupts() {
|
||||
const { intStatus } = this;
|
||||
this.rp2040.setInterrupt(IRQ.USBCTRL, !!intStatus);
|
||||
}
|
||||
resetDevice() {
|
||||
this.resetAlarm.schedule(10000000); // USB reset takes ~10ms
|
||||
}
|
||||
sendSetupPacket(setupPacket) {
|
||||
this.rp2040.usbDPRAM.set(setupPacket);
|
||||
this.sieStatus |= SIE_SETUP_REC;
|
||||
this.sieStatusUpdated();
|
||||
}
|
||||
indicateBufferReady(endpoint, out) {
|
||||
this.buffStatus |= 1 << (endpoint * 2 + (out ? 1 : 0));
|
||||
this.buffStatusUpdated();
|
||||
}
|
||||
buffStatusUpdated() {
|
||||
if (this.buffStatus) {
|
||||
this.intRaw |= INTR_BUFF_STATUS;
|
||||
}
|
||||
else {
|
||||
this.intRaw &= ~INTR_BUFF_STATUS;
|
||||
}
|
||||
this.checkInterrupts();
|
||||
}
|
||||
sieStatusUpdated() {
|
||||
if (this.hostMode) {
|
||||
// Host mode interrupt mapping
|
||||
const intRegisterMap = [
|
||||
[SIE_TRANS_COMPLETE, INTR_TRANS_COMPLETE],
|
||||
[SIE_STALL_REC, INTR_STALL],
|
||||
[SIE_CRC_ERROR, INTR_ERROR_CRC],
|
||||
[SIE_BIT_STUFF_ERROR, INTR_ERROR_BIT_STUFF],
|
||||
[SIE_RX_OVERFLOW, INTR_ERROR_RX_OVERFLOW],
|
||||
[SIE_RX_TIMEOUT, INTR_ERROR_RX_TIMEOUT],
|
||||
[SIE_DATA_SEQ_ERROR, INTR_ERROR_DATA_SEQ],
|
||||
];
|
||||
for (const [sieBit, intRawBit] of intRegisterMap) {
|
||||
if (this.sieStatus & sieBit) {
|
||||
this.intRaw |= intRawBit;
|
||||
}
|
||||
else {
|
||||
this.intRaw &= ~intRawBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Device mode interrupt mapping
|
||||
const intRegisterMap = [
|
||||
[SIE_SETUP_REC, INTR_SETUP_REQ],
|
||||
[SIE_RESUME, INTR_DEV_RESUME_FROM_HOST],
|
||||
[SIE_SUSPENDED, INTR_DEV_SUSPEND],
|
||||
[SIE_CONNECTED, INTR_DEV_CONN_DIS],
|
||||
[SIE_BUS_RESET, INTR_BUS_RESET],
|
||||
[SIE_VBUS_DETECTED, INTR_VBUS_DETECT],
|
||||
[SIE_STALL_REC, INTR_STALL],
|
||||
[SIE_CRC_ERROR, INTR_ERROR_CRC],
|
||||
[SIE_BIT_STUFF_ERROR, INTR_ERROR_BIT_STUFF],
|
||||
[SIE_RX_OVERFLOW, INTR_ERROR_RX_OVERFLOW],
|
||||
[SIE_RX_TIMEOUT, INTR_ERROR_RX_TIMEOUT],
|
||||
[SIE_DATA_SEQ_ERROR, INTR_ERROR_DATA_SEQ],
|
||||
];
|
||||
for (const [sieBit, intRawBit] of intRegisterMap) {
|
||||
if (this.sieStatus & sieBit) {
|
||||
this.intRaw |= intRawBit;
|
||||
}
|
||||
else {
|
||||
this.intRaw &= ~intRawBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkInterrupts();
|
||||
}
|
||||
// ============ Host Mode Methods ============
|
||||
/**
|
||||
* Connect a simulated USB device to the host controller.
|
||||
*/
|
||||
connectDevice(device) {
|
||||
this.connectedDevice = device;
|
||||
if (this.hostMode && this.mainCtrl & CONTROLLER_EN) {
|
||||
this.onDeviceConnected();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disconnect the simulated USB device from the host controller.
|
||||
*/
|
||||
disconnectDevice() {
|
||||
if (this.connectedDevice && this.hostMode) {
|
||||
this.connectedDevice = null;
|
||||
// Clear speed bits to indicate disconnection
|
||||
this.sieStatus &= ~(0x3 << 8); // Clear speed bits
|
||||
this.intRaw |= INTR_HOST_CONN_DIS;
|
||||
this.checkInterrupts();
|
||||
}
|
||||
this.connectedDevice = null;
|
||||
}
|
||||
onDeviceConnected() {
|
||||
// Set full-speed device connected (value 2 in speed field, bits 9:8)
|
||||
this.sieStatus &= ~(0x3 << 8);
|
||||
this.sieStatus |= SIE_SPEED_FS_VALUE << 8;
|
||||
this.intRaw |= INTR_HOST_CONN_DIS;
|
||||
this.checkInterrupts();
|
||||
this.debug('USB device connected (full-speed)');
|
||||
}
|
||||
handleSieCtrlWrite(value) {
|
||||
this.sieCtrl = value;
|
||||
// Handle SOF enable/disable
|
||||
if (value & SIE_CTRL_SOF_EN && !this.sofEnabled) {
|
||||
this.sofEnabled = true;
|
||||
this.scheduleSofPacket();
|
||||
this.debug('SOF generation enabled');
|
||||
}
|
||||
else if (!(value & SIE_CTRL_SOF_EN) && this.sofEnabled) {
|
||||
this.sofEnabled = false;
|
||||
this.debug('SOF generation disabled');
|
||||
}
|
||||
// Handle bus reset
|
||||
if (value & SIE_CTRL_RESET_BUS) {
|
||||
this.debug('USB bus reset initiated');
|
||||
if (this.connectedDevice) {
|
||||
this.connectedDevice.onReset();
|
||||
}
|
||||
this.controlDataPid = 1; // Reset data toggle
|
||||
}
|
||||
// Handle start transaction
|
||||
if (value & SIE_CTRL_START_TRANS) {
|
||||
this.startHostTransaction();
|
||||
}
|
||||
}
|
||||
scheduleSofPacket() {
|
||||
if (this.sofEnabled) {
|
||||
// SOF every 1ms = 1,000,000 ns
|
||||
this.sofAlarm.schedule(1000000);
|
||||
}
|
||||
}
|
||||
generateSOF() {
|
||||
this.sofFrameNumber = (this.sofFrameNumber + 1) & 0x7ff;
|
||||
this.intRaw |= INTR_HOST_SOF;
|
||||
this.checkInterrupts();
|
||||
// Poll interrupt endpoints
|
||||
this.pollInterruptEndpoints();
|
||||
// Schedule next SOF
|
||||
this.scheduleSofPacket();
|
||||
}
|
||||
pollInterruptEndpoints() {
|
||||
var _a;
|
||||
// Debug: log if any interrupt endpoints are enabled
|
||||
if (this.intEpCtrl && this.sofFrameNumber % 100 === 0) {
|
||||
this.debug(`INT_EP poll: intEpCtrl=0x${this.intEpCtrl.toString(16)} sofFrame=${this.sofFrameNumber}`);
|
||||
}
|
||||
// Check each enabled interrupt endpoint
|
||||
for (let i = 0; i < USB_HOST_INTERRUPT_ENDPOINTS; i++) {
|
||||
const epCtrlBit = 1 << (i + 1);
|
||||
if (!(this.intEpCtrl & epCtrlBit))
|
||||
continue;
|
||||
const addrEndp = this.intEpAddrCtrl[i];
|
||||
const devAddr = addrEndp & 0x7f;
|
||||
const epNum = (addrEndp >> 16) & 0xf;
|
||||
const isOut = !!(addrEndp & (1 << 25)); // INTEP_DIR bit
|
||||
// Debug: log endpoint config
|
||||
if (this.sofFrameNumber % 500 === 0) {
|
||||
this.debug(`INT_EP[${i}]: addrEndp=0x${addrEndp.toString(16)} devAddr=${devAddr} epNum=${epNum} connectedAddr=${(_a = this.connectedDevice) === null || _a === void 0 ? void 0 : _a.address}`);
|
||||
}
|
||||
if (!this.connectedDevice || this.connectedDevice.address !== devAddr)
|
||||
continue;
|
||||
// Get the endpoint control register for interval checking
|
||||
const epCtrlOffset = HOST_INT_EP_CTRL_BASE + i * 8;
|
||||
const epCtrl = this.rp2040.usbDPRAMView.getUint32(epCtrlOffset, true);
|
||||
const interval = ((epCtrl >> EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB) & 0x1ff) + 1;
|
||||
// Check if it's time to poll (simplified: poll every SOF for now)
|
||||
if (this.sofFrameNumber % interval !== 0)
|
||||
continue;
|
||||
// Get buffer control
|
||||
const bufCtrlOffset = HOST_INT_EP_BUF_CTRL_BASE + i * 8;
|
||||
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(bufCtrlOffset, true);
|
||||
// Debug: log buffer status
|
||||
if (this.sofFrameNumber % 500 === 0) {
|
||||
this.debug(`INT_EP[${i}]: bufCtrl=0x${bufCtrl.toString(16)} available=${!!(bufCtrl & USB_BUF_CTRL_AVAILABLE)}`);
|
||||
}
|
||||
// Only poll if buffer is available
|
||||
if (!(bufCtrl & USB_BUF_CTRL_AVAILABLE))
|
||||
continue;
|
||||
// For IN endpoints, request data from device
|
||||
if (!isOut) {
|
||||
const epAddr = 0x80 | epNum; // IN endpoint
|
||||
const result = this.connectedDevice.handleDataIn(epAddr);
|
||||
if (result.status === 'ack' && result.data) {
|
||||
// Write data to the interrupt endpoint buffer
|
||||
const bufferOffset = epCtrl & 0xffc0;
|
||||
this.rp2040.usbDPRAM.set(result.data, bufferOffset);
|
||||
// Update buffer control
|
||||
let newBufCtrl = bufCtrl & ~USB_BUF_CTRL_AVAILABLE;
|
||||
newBufCtrl |= USB_BUF_CTRL_FULL;
|
||||
newBufCtrl =
|
||||
(newBufCtrl & ~USB_BUF_CTRL_LEN_MASK) | (result.data.length & USB_BUF_CTRL_LEN_MASK);
|
||||
this.rp2040.usbDPRAMView.setUint32(bufCtrlOffset, newBufCtrl, true);
|
||||
// Set buffer status for this interrupt endpoint
|
||||
// Interrupt EPs use bits 2+ in buff_status (bit 0 is epx IN, bit 1 is epx OUT)
|
||||
this.buffStatus |= 1 << ((i + 1) * 2);
|
||||
this.buffStatusUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
startHostTransaction() {
|
||||
if (!this.hostMode)
|
||||
return;
|
||||
const devAddr = this.devAddrCtrl & 0x7f;
|
||||
const epNum = (this.devAddrCtrl >> 16) & 0xf;
|
||||
this.debug(`Host transaction: dev=${devAddr} ep=${epNum} sieCtrl=0x${this.sieCtrl.toString(16)}`);
|
||||
if (!this.connectedDevice) {
|
||||
// No device connected - timeout
|
||||
this.sieStatus |= SIE_RX_TIMEOUT;
|
||||
this.sieStatusUpdated();
|
||||
return;
|
||||
}
|
||||
// Determine transaction type
|
||||
if (this.sieCtrl & SIE_CTRL_SEND_SETUP) {
|
||||
this.handleSetupTransaction(devAddr);
|
||||
}
|
||||
else if (this.sieCtrl & SIE_CTRL_RECEIVE_DATA) {
|
||||
this.handleInTransaction(devAddr, epNum);
|
||||
}
|
||||
else if (this.sieCtrl & SIE_CTRL_SEND_DATA) {
|
||||
this.handleOutTransaction(devAddr, epNum);
|
||||
}
|
||||
}
|
||||
handleSetupTransaction(_devAddr) {
|
||||
var _a, _b;
|
||||
// Read setup packet from DPRAM
|
||||
const setupPacket = this.rp2040.usbDPRAM.slice(HOST_SETUP_PACKET, HOST_SETUP_PACKET + 8);
|
||||
const setup = parseSetupPacket(setupPacket);
|
||||
this.debug(`SETUP: bmRequestType=0x${setup.bmRequestType.toString(16)} bRequest=${setup.bRequest} wValue=0x${setup.wValue.toString(16)} wIndex=${setup.wIndex} wLength=${setup.wLength}`);
|
||||
// Forward to device
|
||||
const result = this.connectedDevice.handleSetupPacket(setupPacket);
|
||||
// Handle SET_ADDRESS specially
|
||||
if (setup.bRequest === 5 /* StandardRequest.SetAddress */ && setup.type === 0) {
|
||||
const newAddr = setup.wValue & 0x7f;
|
||||
this.connectedDevice.address = newAddr;
|
||||
(_b = (_a = this.connectedDevice).onAddressAssigned) === null || _b === void 0 ? void 0 : _b.call(_a, newAddr);
|
||||
this.debug(`Device address set to ${newAddr}`);
|
||||
}
|
||||
// Store response data for subsequent IN transaction
|
||||
if (setup.direction === 'in' && result.data) {
|
||||
this.pendingSetupResponse = result.data;
|
||||
this.expectingStatusPhase = false;
|
||||
}
|
||||
else {
|
||||
this.pendingSetupResponse = null;
|
||||
// Control OUT transfer - next IN will be status phase (zero-length ACK)
|
||||
this.expectingStatusPhase = true;
|
||||
}
|
||||
// Reset data toggle for data phase
|
||||
this.controlDataPid = 1;
|
||||
// SETUP always gets ACK (or STALL if error, but we handle that later)
|
||||
this.sieStatus |= SIE_ACK_REC;
|
||||
// Schedule transaction completion
|
||||
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
|
||||
}
|
||||
handleInTransaction(devAddr, epNum) {
|
||||
const epAddr = 0x80 | epNum;
|
||||
let result;
|
||||
if (epNum === 0 && this.expectingStatusPhase) {
|
||||
// Control OUT status phase - return zero-length ACK
|
||||
result = { status: 'ack', data: new Uint8Array(0) };
|
||||
this.expectingStatusPhase = false;
|
||||
this.debug('Control OUT status phase (ZLP)');
|
||||
}
|
||||
else if (epNum === 0 && this.pendingSetupResponse) {
|
||||
// Control IN - return pending setup response
|
||||
result = { status: 'ack', data: this.pendingSetupResponse };
|
||||
// Get requested length from buffer control
|
||||
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
|
||||
const maxLen = bufCtrl & USB_BUF_CTRL_LEN_MASK;
|
||||
// Trim data to requested length
|
||||
if (result.data && result.data.length > maxLen) {
|
||||
result.data = result.data.slice(0, maxLen);
|
||||
}
|
||||
// Clear pending response if all data sent
|
||||
if (!result.data || result.data.length <= maxLen) {
|
||||
this.pendingSetupResponse = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = this.connectedDevice.handleDataIn(epAddr);
|
||||
}
|
||||
if (result.status === 'ack' && result.data) {
|
||||
// Write data to EPX data buffer
|
||||
this.rp2040.usbDPRAM.set(result.data, HOST_EPX_DATA);
|
||||
// Update buffer control with actual length and FULL flag
|
||||
let bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
|
||||
bufCtrl &= ~USB_BUF_CTRL_LEN_MASK;
|
||||
bufCtrl |= result.data.length & USB_BUF_CTRL_LEN_MASK;
|
||||
bufCtrl |= USB_BUF_CTRL_FULL;
|
||||
bufCtrl &= ~USB_BUF_CTRL_AVAILABLE;
|
||||
// Set DATA1 PID for control transfers
|
||||
if (this.controlDataPid) {
|
||||
bufCtrl |= USB_BUF_CTRL_DATA1_PID;
|
||||
}
|
||||
else {
|
||||
bufCtrl &= ~USB_BUF_CTRL_DATA1_PID;
|
||||
}
|
||||
this.controlDataPid ^= 1;
|
||||
this.rp2040.usbDPRAMView.setUint32(HOST_EPX_BUF_CTRL, bufCtrl, true);
|
||||
// Set buffer status
|
||||
this.buffStatus |= 1; // Bit 0 for EPX
|
||||
this.buffStatusUpdated();
|
||||
this.sieStatus |= SIE_ACK_REC;
|
||||
}
|
||||
else if (result.status === 'nak') {
|
||||
this.sieStatus |= SIE_NAK_REC;
|
||||
}
|
||||
else if (result.status === 'stall') {
|
||||
this.sieStatus |= SIE_STALL_REC;
|
||||
}
|
||||
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
|
||||
}
|
||||
handleOutTransaction(devAddr, epNum) {
|
||||
const epAddr = epNum; // OUT endpoint
|
||||
// Read data from EPX data buffer
|
||||
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
|
||||
const dataLen = bufCtrl & USB_BUF_CTRL_LEN_MASK;
|
||||
const data = this.rp2040.usbDPRAM.slice(HOST_EPX_DATA, HOST_EPX_DATA + dataLen);
|
||||
let result;
|
||||
if (epNum === 0 && dataLen === 0) {
|
||||
// Zero-length status phase for control transfer
|
||||
result = { status: 'ack' };
|
||||
}
|
||||
else {
|
||||
result = this.connectedDevice.handleDataOut(epAddr, data);
|
||||
}
|
||||
// Update buffer control
|
||||
let newBufCtrl = bufCtrl;
|
||||
newBufCtrl &= ~USB_BUF_CTRL_AVAILABLE;
|
||||
// Toggle DATA PID
|
||||
if (this.controlDataPid) {
|
||||
newBufCtrl |= USB_BUF_CTRL_DATA1_PID;
|
||||
}
|
||||
else {
|
||||
newBufCtrl &= ~USB_BUF_CTRL_DATA1_PID;
|
||||
}
|
||||
this.controlDataPid ^= 1;
|
||||
this.rp2040.usbDPRAMView.setUint32(HOST_EPX_BUF_CTRL, newBufCtrl, true);
|
||||
if (result.status === 'ack') {
|
||||
this.sieStatus |= SIE_ACK_REC;
|
||||
this.buffStatus |= 1; // Bit 0 for EPX
|
||||
this.buffStatusUpdated();
|
||||
}
|
||||
else if (result.status === 'nak') {
|
||||
this.sieStatus |= SIE_NAK_REC;
|
||||
}
|
||||
else if (result.status === 'stall') {
|
||||
this.sieStatus |= SIE_STALL_REC;
|
||||
}
|
||||
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
|
||||
}
|
||||
completeHostTransaction() {
|
||||
// Clear START_TRANS bit
|
||||
this.sieCtrl &= ~SIE_CTRL_START_TRANS;
|
||||
// Set transaction complete
|
||||
this.sieStatus |= SIE_TRANS_COMPLETE;
|
||||
this.sieStatusUpdated();
|
||||
this.debug('Host transaction complete');
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
const CTRL = 0x00; // Control register
|
||||
const LOAD = 0x04; // Load the watchdog timer.
|
||||
const REASON = 0x08; // Logs the reason for the last reset.
|
||||
const SCRATCH0 = 0x0c; // Scratch register
|
||||
const SCRATCH1 = 0x10; // Scratch register
|
||||
const SCRATCH2 = 0x14; // Scratch register
|
||||
const SCRATCH3 = 0x18; // Scratch register
|
||||
const SCRATCH4 = 0x1c; // Scratch register
|
||||
const SCRATCH5 = 0x20; // Scratch register
|
||||
const SCRATCH6 = 0x24; // Scratch register
|
||||
const SCRATCH7 = 0x28; // Scratch register
|
||||
const TICK = 0x2c; // Controls the tick generator
|
||||
// CTRL bits:
|
||||
const TRIGGER = 1 << 31;
|
||||
const ENABLE = 1 << 30;
|
||||
const PAUSE_DBG1 = 1 << 26;
|
||||
const PAUSE_DBG0 = 1 << 25;
|
||||
const PAUSE_JTAG = 1 << 24;
|
||||
const TIME_MASK = 0xffffff;
|
||||
const TIME_SHIFT = 0;
|
||||
// LOAD bits
|
||||
const LOAD_MASK = 0xffffff;
|
||||
const LOAD_SHIFT = 0;
|
||||
// REASON bits:
|
||||
const FORCE = 1 << 1;
|
||||
const TIMER = 1 << 0;
|
||||
// TICK bits:
|
||||
const COUNT_MASK = 0x1ff;
|
||||
const COUNT_SHIFT = 11;
|
||||
const RUNNING = 1 << 10;
|
||||
const TICK_ENABLE = 1 << 9;
|
||||
const CYCLES_MASK = 0x1ff;
|
||||
const CYCLES_SHIFT = 0;
|
||||
const TICK_FREQUENCY = 2000000; // Actually 1 MHz, but due to errata RP2040-E1, the timer is decremented twice per tick
|
||||
export class RPWatchdog extends BasePeripheral {
|
||||
// User provided
|
||||
constructor(rp2040, name) {
|
||||
super(rp2040, name);
|
||||
this.scratchData = new Uint32Array(8);
|
||||
this.enable = false;
|
||||
this.tickEnable = true;
|
||||
this.reason = 0;
|
||||
this.pauseDbg0 = true;
|
||||
this.pauseDbg1 = true;
|
||||
this.pauseJtag = true;
|
||||
/** Called when the watchdog triggers - override with your own soft reset implementation */
|
||||
this.onWatchdogTrigger = () => {
|
||||
this.rp2040.logger.warn(this.name, 'Watchdog triggered, but no reset handler provided');
|
||||
};
|
||||
this.timer = new Timer32(rp2040.clock, TICK_FREQUENCY);
|
||||
this.timer.mode = TimerMode.Decrement;
|
||||
this.timer.enable = false;
|
||||
this.alarm = new Timer32PeriodicAlarm(this.timer, () => {
|
||||
var _a;
|
||||
this.reason = TIMER;
|
||||
(_a = this.onWatchdogTrigger) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
});
|
||||
this.alarm.target = 0;
|
||||
this.alarm.enable = false;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case CTRL:
|
||||
return ((this.timer.enable ? ENABLE : 0) |
|
||||
(this.pauseDbg0 ? PAUSE_DBG0 : 0) |
|
||||
(this.pauseDbg1 ? PAUSE_DBG1 : 0) |
|
||||
(this.pauseJtag ? PAUSE_JTAG : 0) |
|
||||
((this.timer.counter & TIME_MASK) << TIME_SHIFT));
|
||||
case REASON:
|
||||
return this.reason;
|
||||
case SCRATCH0:
|
||||
case SCRATCH1:
|
||||
case SCRATCH2:
|
||||
case SCRATCH3:
|
||||
case SCRATCH4:
|
||||
case SCRATCH5:
|
||||
case SCRATCH6:
|
||||
case SCRATCH7:
|
||||
return this.scratchData[(offset - SCRATCH0) >> 2];
|
||||
case TICK:
|
||||
// TODO COUNT bits
|
||||
return this.tickEnable ? RUNNING | TICK_ENABLE : 0;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
var _a;
|
||||
switch (offset) {
|
||||
case CTRL:
|
||||
if (value & TRIGGER) {
|
||||
this.reason = FORCE;
|
||||
(_a = this.onWatchdogTrigger) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
}
|
||||
this.enable = !!(value & ENABLE);
|
||||
this.timer.enable = this.enable && this.tickEnable;
|
||||
this.alarm.enable = this.enable && this.tickEnable;
|
||||
this.pauseDbg0 = !!(value & PAUSE_DBG0);
|
||||
this.pauseDbg1 = !!(value & PAUSE_DBG1);
|
||||
this.pauseJtag = !!(value & PAUSE_JTAG);
|
||||
break;
|
||||
case LOAD:
|
||||
this.timer.set((value >>> LOAD_SHIFT) & LOAD_MASK);
|
||||
break;
|
||||
case SCRATCH0:
|
||||
case SCRATCH1:
|
||||
case SCRATCH2:
|
||||
case SCRATCH3:
|
||||
case SCRATCH4:
|
||||
case SCRATCH5:
|
||||
case SCRATCH6:
|
||||
case SCRATCH7:
|
||||
this.scratchData[(offset - SCRATCH0) >> 2] = value;
|
||||
break;
|
||||
case TICK:
|
||||
this.tickEnable = !!(value & TICK_ENABLE);
|
||||
this.timer.enable = this.enable && this.tickEnable;
|
||||
this.alarm.enable = this.enable && this.tickEnable;
|
||||
// TODO - handle CYCLES (tick also affectes timer)
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { BasePeripheral } from './peripheral.js';
|
||||
// XOSC register offsets
|
||||
const XOSC_CTRL = 0x00;
|
||||
const XOSC_STATUS = 0x04;
|
||||
const XOSC_DORMANT = 0x08;
|
||||
const XOSC_STARTUP = 0x0c;
|
||||
const XOSC_COUNT = 0x1c;
|
||||
// CTRL register bits
|
||||
const CTRL_ENABLE_LSB = 12;
|
||||
const CTRL_ENABLE_BITS = 0x00fff000;
|
||||
const CTRL_FREQ_RANGE_BITS = 0x00000fff;
|
||||
// CTRL ENABLE values
|
||||
const CTRL_ENABLE_DISABLE = 0xd1e;
|
||||
const CTRL_ENABLE_ENABLE = 0xfab;
|
||||
// STATUS register bits
|
||||
const STATUS_STABLE = 0x80000000; // bit 31
|
||||
const STATUS_BADWRITE = 0x01000000; // bit 24
|
||||
const STATUS_ENABLED = 0x00001000; // bit 12
|
||||
const STATUS_FREQ_RANGE_BITS = 0x00000003;
|
||||
// DORMANT register values
|
||||
const DORMANT_VALUE = 0x636f6d61; // "coma" in ASCII
|
||||
const WAKE_VALUE = 0x77616b65; // "wake" in ASCII
|
||||
// STARTUP register bits
|
||||
const STARTUP_X4 = 0x00100000; // bit 20
|
||||
const STARTUP_DELAY_BITS = 0x00003fff;
|
||||
export class RPXOSC extends BasePeripheral {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.ctrl = 0;
|
||||
this.status = 0;
|
||||
this.dormant = 0;
|
||||
this.startup = 0;
|
||||
this.count = 0;
|
||||
this.enabled = false;
|
||||
this.stable = false;
|
||||
this.isDormant = false;
|
||||
}
|
||||
readUint32(offset) {
|
||||
switch (offset) {
|
||||
case XOSC_CTRL:
|
||||
return this.ctrl;
|
||||
case XOSC_STATUS: {
|
||||
let status = this.status;
|
||||
if (this.stable) {
|
||||
status |= STATUS_STABLE;
|
||||
}
|
||||
if (this.enabled) {
|
||||
status |= STATUS_ENABLED;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
case XOSC_DORMANT:
|
||||
return this.dormant;
|
||||
case XOSC_STARTUP:
|
||||
return this.startup;
|
||||
case XOSC_COUNT:
|
||||
return this.count;
|
||||
}
|
||||
return super.readUint32(offset);
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
switch (offset) {
|
||||
case XOSC_CTRL: {
|
||||
this.ctrl = value;
|
||||
const enableValue = (value & CTRL_ENABLE_BITS) >>> CTRL_ENABLE_LSB;
|
||||
const freqRange = value & CTRL_FREQ_RANGE_BITS;
|
||||
void freqRange; // Currently unused, but could be logged or validated
|
||||
if (enableValue === CTRL_ENABLE_ENABLE) {
|
||||
if (!this.isDormant) {
|
||||
this.enabled = true;
|
||||
// For simplicity, become stable immediately
|
||||
// In real hardware, this would take time based on STARTUP register
|
||||
this.stable = true;
|
||||
}
|
||||
}
|
||||
else if (enableValue === CTRL_ENABLE_DISABLE) {
|
||||
this.enabled = false;
|
||||
this.stable = false;
|
||||
}
|
||||
else if (enableValue !== 0) {
|
||||
// Invalid write to ENABLE field
|
||||
this.status |= STATUS_BADWRITE;
|
||||
this.warn(`Invalid ENABLE value written: 0x${enableValue.toString(16)}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XOSC_STATUS:
|
||||
// Clear BADWRITE bit if written as 1 (write-1-to-clear)
|
||||
if (value & STATUS_BADWRITE) {
|
||||
this.status &= ~STATUS_BADWRITE;
|
||||
}
|
||||
break;
|
||||
case XOSC_DORMANT:
|
||||
if (value === DORMANT_VALUE) {
|
||||
this.isDormant = true;
|
||||
this.stable = false;
|
||||
}
|
||||
else if (value === WAKE_VALUE) {
|
||||
this.isDormant = false;
|
||||
if (this.enabled) {
|
||||
this.stable = true;
|
||||
}
|
||||
}
|
||||
this.dormant = value;
|
||||
break;
|
||||
case XOSC_STARTUP:
|
||||
this.startup = value & (STARTUP_X4 | STARTUP_DELAY_BITS);
|
||||
break;
|
||||
case XOSC_COUNT:
|
||||
// Writing to COUNT starts the countdown
|
||||
this.count = value & 0xff;
|
||||
// For simplicity, we don't actually implement the countdown
|
||||
// In real hardware, this would decrement at the XOSC frequency
|
||||
break;
|
||||
default:
|
||||
super.writeUint32(offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+341
@@ -0,0 +1,341 @@
|
||||
import { SimulationClock } from './clock/simulation-clock.js';
|
||||
import { CortexM0Core } from './cortex-m0-core.js';
|
||||
import { GPIOPin } from './gpio-pin.js';
|
||||
import { IRQ } from './irq.js';
|
||||
import { RPADC } from './peripherals/adc.js';
|
||||
import { RPBUSCTRL } from './peripherals/busctrl.js';
|
||||
import { RPClocks } from './peripherals/clocks.js';
|
||||
import { DREQChannel, RPDMA } from './peripherals/dma.js';
|
||||
import { RPI2C } from './peripherals/i2c.js';
|
||||
import { RPIO } from './peripherals/io.js';
|
||||
import { RPPADS } from './peripherals/pads.js';
|
||||
import { UnimplementedPeripheral } from './peripherals/peripheral.js';
|
||||
import { RPPIO } from './peripherals/pio.js';
|
||||
import { RPPPB } from './peripherals/ppb.js';
|
||||
import { RPPSM } from './peripherals/psm.js';
|
||||
import { RPPWM } from './peripherals/pwm.js';
|
||||
import { RPReset } from './peripherals/reset.js';
|
||||
import { RP2040RTC } from './peripherals/rtc.js';
|
||||
import { RPSPI } from './peripherals/spi.js';
|
||||
import { RPSSI } from './peripherals/ssi.js';
|
||||
import { RP2040SysCfg } from './peripherals/syscfg.js';
|
||||
import { RP2040SysInfo } from './peripherals/sysinfo.js';
|
||||
import { RPTBMAN } from './peripherals/tbman.js';
|
||||
import { RPTimer } from './peripherals/timer.js';
|
||||
import { RPUART } from './peripherals/uart.js';
|
||||
import { RPUSBController } from './peripherals/usb.js';
|
||||
import { RPWatchdog } from './peripherals/watchdog.js';
|
||||
import { RPXOSC } from './peripherals/xosc.js';
|
||||
import { RPSIO } from './sio.js';
|
||||
import { ConsoleLogger, LogLevel } from './utils/logging.js';
|
||||
export const FLASH_START_ADDRESS = 0x10000000;
|
||||
export const FLASH_END_ADDRESS = 0x14000000;
|
||||
export const RAM_START_ADDRESS = 0x20000000;
|
||||
export const APB_START_ADDRESS = 0x40000000;
|
||||
export const DPRAM_START_ADDRESS = 0x50100000;
|
||||
export const SIO_START_ADDRESS = 0xd0000000;
|
||||
const LOG_NAME = 'RP2040';
|
||||
const KB = 1024;
|
||||
const MB = 1024 * KB;
|
||||
const MHz = 1000000;
|
||||
export class RP2040 {
|
||||
constructor(clock = new SimulationClock()) {
|
||||
this.clock = clock;
|
||||
this.bootrom = new Uint32Array(4 * KB);
|
||||
this.sram = new Uint8Array(264 * KB);
|
||||
this.sramView = new DataView(this.sram.buffer);
|
||||
this.flash = new Uint8Array(16 * MB);
|
||||
this.flash16 = new Uint16Array(this.flash.buffer);
|
||||
this.flashView = new DataView(this.flash.buffer);
|
||||
this.usbDPRAM = new Uint8Array(4 * KB);
|
||||
this.usbDPRAMView = new DataView(this.usbDPRAM.buffer);
|
||||
this.core = new CortexM0Core(this);
|
||||
/* Clocks */
|
||||
this.clkSys = 125 * MHz;
|
||||
this.clkPeri = 125 * MHz;
|
||||
this.ppb = new RPPPB(this, 'PPB');
|
||||
this.sio = new RPSIO(this);
|
||||
this.uart = [
|
||||
new RPUART(this, 'UART0', IRQ.UART0, {
|
||||
rx: DREQChannel.DREQ_UART0_RX,
|
||||
tx: DREQChannel.DREQ_UART0_TX,
|
||||
}),
|
||||
new RPUART(this, 'UART1', IRQ.UART1, {
|
||||
rx: DREQChannel.DREQ_UART1_RX,
|
||||
tx: DREQChannel.DREQ_UART1_TX,
|
||||
}),
|
||||
];
|
||||
this.i2c = [new RPI2C(this, 'I2C0', IRQ.I2C0), new RPI2C(this, 'I2C1', IRQ.I2C1)];
|
||||
this.pwm = new RPPWM(this, 'PWM_BASE');
|
||||
this.adc = new RPADC(this, 'ADC');
|
||||
this.gpio = [
|
||||
new GPIOPin(this, 0),
|
||||
new GPIOPin(this, 1),
|
||||
new GPIOPin(this, 2),
|
||||
new GPIOPin(this, 3),
|
||||
new GPIOPin(this, 4),
|
||||
new GPIOPin(this, 5),
|
||||
new GPIOPin(this, 6),
|
||||
new GPIOPin(this, 7),
|
||||
new GPIOPin(this, 8),
|
||||
new GPIOPin(this, 9),
|
||||
new GPIOPin(this, 10),
|
||||
new GPIOPin(this, 11),
|
||||
new GPIOPin(this, 12),
|
||||
new GPIOPin(this, 13),
|
||||
new GPIOPin(this, 14),
|
||||
new GPIOPin(this, 15),
|
||||
new GPIOPin(this, 16),
|
||||
new GPIOPin(this, 17),
|
||||
new GPIOPin(this, 18),
|
||||
new GPIOPin(this, 19),
|
||||
new GPIOPin(this, 20),
|
||||
new GPIOPin(this, 21),
|
||||
new GPIOPin(this, 22),
|
||||
new GPIOPin(this, 23),
|
||||
new GPIOPin(this, 24),
|
||||
new GPIOPin(this, 25),
|
||||
new GPIOPin(this, 26),
|
||||
new GPIOPin(this, 27),
|
||||
new GPIOPin(this, 28),
|
||||
new GPIOPin(this, 29),
|
||||
];
|
||||
this.qspi = [
|
||||
new GPIOPin(this, 0, 'SCLK'),
|
||||
new GPIOPin(this, 1, 'SS'),
|
||||
new GPIOPin(this, 2, 'SD0'),
|
||||
new GPIOPin(this, 3, 'SD1'),
|
||||
new GPIOPin(this, 4, 'SD2'),
|
||||
new GPIOPin(this, 5, 'SD3'),
|
||||
];
|
||||
this.dma = new RPDMA(this, 'DMA');
|
||||
this.pio = [
|
||||
new RPPIO(this, 'PIO0', IRQ.PIO0_IRQ0, 0),
|
||||
new RPPIO(this, 'PIO1', IRQ.PIO1_IRQ0, 1),
|
||||
];
|
||||
this.usbCtrl = new RPUSBController(this, 'USB');
|
||||
this.spi = [
|
||||
new RPSPI(this, 'SPI0', IRQ.SPI0, {
|
||||
rx: DREQChannel.DREQ_SPI0_RX,
|
||||
tx: DREQChannel.DREQ_SPI0_TX,
|
||||
}),
|
||||
new RPSPI(this, 'SPI1', IRQ.SPI1, {
|
||||
rx: DREQChannel.DREQ_SPI1_RX,
|
||||
tx: DREQChannel.DREQ_SPI1_TX,
|
||||
}),
|
||||
];
|
||||
this.logger = new ConsoleLogger(LogLevel.Debug, true);
|
||||
this.peripherals = {
|
||||
0x18000: new RPSSI(this, 'SSI'),
|
||||
0x40000: new RP2040SysInfo(this, 'SYSINFO_BASE'),
|
||||
0x40004: new RP2040SysCfg(this, 'SYSCFG'),
|
||||
0x40008: new RPClocks(this, 'CLOCKS_BASE'),
|
||||
0x4000c: new RPReset(this, 'RESETS_BASE'),
|
||||
0x40010: new RPPSM(this, 'PSM_BASE'),
|
||||
0x40014: new RPIO(this, 'IO_BANK0_BASE'),
|
||||
0x40018: new UnimplementedPeripheral(this, 'IO_QSPI_BASE'),
|
||||
0x4001c: new RPPADS(this, 'PADS_BANK0_BASE', 'bank0'),
|
||||
0x40020: new RPPADS(this, 'PADS_QSPI_BASE', 'qspi'),
|
||||
0x40024: new RPXOSC(this, 'XOSC_BASE'),
|
||||
0x40028: new UnimplementedPeripheral(this, 'PLL_SYS_BASE'),
|
||||
0x4002c: new UnimplementedPeripheral(this, 'PLL_USB_BASE'),
|
||||
0x40030: new RPBUSCTRL(this, 'BUSCTRL_BASE'),
|
||||
0x40034: this.uart[0],
|
||||
0x40038: this.uart[1],
|
||||
0x4003c: this.spi[0],
|
||||
0x40040: this.spi[1],
|
||||
0x40044: this.i2c[0],
|
||||
0x40048: this.i2c[1],
|
||||
0x4004c: this.adc,
|
||||
0x40050: this.pwm,
|
||||
0x40054: new RPTimer(this, 'TIMER_BASE'),
|
||||
0x40058: new RPWatchdog(this, 'WATCHDOG_BASE'),
|
||||
0x4005c: new RP2040RTC(this, 'RTC_BASE'),
|
||||
0x40060: new UnimplementedPeripheral(this, 'ROSC_BASE'),
|
||||
0x40064: new UnimplementedPeripheral(this, 'VREG_AND_CHIP_RESET_BASE'),
|
||||
0x4006c: new RPTBMAN(this, 'TBMAN_BASE'),
|
||||
0x50000: this.dma,
|
||||
0x50110: this.usbCtrl,
|
||||
0x50200: this.pio[0],
|
||||
0x50300: this.pio[1],
|
||||
};
|
||||
// Debugging
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
this.onBreak = (code) => {
|
||||
// TODO: raise HardFault exception
|
||||
// console.error('Breakpoint!', code);
|
||||
};
|
||||
this.reset();
|
||||
}
|
||||
loadBootrom(bootromData) {
|
||||
this.bootrom.set(bootromData);
|
||||
this.reset();
|
||||
}
|
||||
reset() {
|
||||
this.core.reset();
|
||||
this.pwm.reset();
|
||||
this.flash.fill(0xff);
|
||||
}
|
||||
readUint32(address) {
|
||||
address = address >>> 0; // round to 32-bits, unsigned
|
||||
if (address & 0x3) {
|
||||
this.logger.error(LOG_NAME, `read from address ${address.toString(16)}, which is not 32 bit aligned`);
|
||||
}
|
||||
const { bootrom } = this;
|
||||
if (address < bootrom.length * 4) {
|
||||
return bootrom[address / 4];
|
||||
}
|
||||
else if (address >= FLASH_START_ADDRESS && address < FLASH_END_ADDRESS) {
|
||||
// Flash is mirrored four times:
|
||||
// - 0x10000000 XIP
|
||||
// - 0x11000000 XIP_NOALLOC
|
||||
// - 0x12000000 XIP_NOCACHE
|
||||
// - 0x13000000 XIP_NOCACHE_NOALLOC
|
||||
const offset = address & 16777215;
|
||||
return this.flashView.getUint32(offset, true);
|
||||
}
|
||||
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
return this.sramView.getUint32(address - RAM_START_ADDRESS, true);
|
||||
}
|
||||
else if (address >= DPRAM_START_ADDRESS &&
|
||||
address < DPRAM_START_ADDRESS + this.usbDPRAM.length) {
|
||||
return this.usbDPRAMView.getUint32(address - DPRAM_START_ADDRESS, true);
|
||||
}
|
||||
else if (address >>> 12 === 0xe000e) {
|
||||
return this.ppb.readUint32(address & 0xfff);
|
||||
}
|
||||
else if (address >= SIO_START_ADDRESS && address < SIO_START_ADDRESS + 0x10000000) {
|
||||
return this.sio.readUint32(address - SIO_START_ADDRESS);
|
||||
}
|
||||
const peripheral = this.findPeripheral(address);
|
||||
if (peripheral) {
|
||||
return peripheral.readUint32(address & 0x3fff);
|
||||
}
|
||||
this.logger.warn(LOG_NAME, `Read from invalid memory address: ${address.toString(16)}`);
|
||||
return 0xffffffff;
|
||||
}
|
||||
findPeripheral(address) {
|
||||
return this.peripherals[(address >>> 14) << 2];
|
||||
}
|
||||
/** We assume the address is 16-bit aligned */
|
||||
readUint16(address) {
|
||||
if (address >= FLASH_START_ADDRESS && address < FLASH_START_ADDRESS + this.flash.length) {
|
||||
return this.flashView.getUint16(address - FLASH_START_ADDRESS, true);
|
||||
}
|
||||
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
return this.sramView.getUint16(address - RAM_START_ADDRESS, true);
|
||||
}
|
||||
const value = this.readUint32(address & 0xfffffffc);
|
||||
return address & 0x2 ? (value & 0xffff0000) >>> 16 : value & 0xffff;
|
||||
}
|
||||
readUint8(address) {
|
||||
if (address >= FLASH_START_ADDRESS && address < FLASH_START_ADDRESS + this.flash.length) {
|
||||
return this.flash[address - FLASH_START_ADDRESS];
|
||||
}
|
||||
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
return this.sram[address - RAM_START_ADDRESS];
|
||||
}
|
||||
const value = this.readUint16(address & 0xfffffffe);
|
||||
return (address & 0x1 ? (value & 0xff00) >>> 8 : value & 0xff) >>> 0;
|
||||
}
|
||||
writeUint32(address, value) {
|
||||
address = address >>> 0;
|
||||
const { bootrom } = this;
|
||||
const peripheral = this.findPeripheral(address);
|
||||
if (peripheral) {
|
||||
const atomicType = (address & 0x3000) >> 12;
|
||||
const offset = address & 0xfff;
|
||||
peripheral.writeUint32Atomic(offset, value, atomicType);
|
||||
}
|
||||
else if (address < bootrom.length * 4) {
|
||||
bootrom[address / 4] = value;
|
||||
}
|
||||
else if (address >= FLASH_START_ADDRESS &&
|
||||
address < FLASH_START_ADDRESS + this.flash.length) {
|
||||
this.flashView.setUint32(address - FLASH_START_ADDRESS, value, true);
|
||||
}
|
||||
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
this.sramView.setUint32(address - RAM_START_ADDRESS, value, true);
|
||||
}
|
||||
else if (address >= DPRAM_START_ADDRESS &&
|
||||
address < DPRAM_START_ADDRESS + this.usbDPRAM.length) {
|
||||
const offset = address - DPRAM_START_ADDRESS;
|
||||
this.usbDPRAMView.setUint32(offset, value, true);
|
||||
this.usbCtrl.DPRAMUpdated(offset, value);
|
||||
}
|
||||
else if (address >= SIO_START_ADDRESS && address < SIO_START_ADDRESS + 0x10000000) {
|
||||
this.sio.writeUint32(address - SIO_START_ADDRESS, value);
|
||||
}
|
||||
else if (address >>> 12 === 0xe000e) {
|
||||
this.ppb.writeUint32(address & 0xfff, value);
|
||||
}
|
||||
else {
|
||||
this.logger.warn(LOG_NAME, `Write to undefined address: ${address.toString(16)}`);
|
||||
}
|
||||
}
|
||||
writeUint8(address, value) {
|
||||
if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
this.sram[address - RAM_START_ADDRESS] = value;
|
||||
return;
|
||||
}
|
||||
const alignedAddress = (address & 0xfffffffc) >>> 0;
|
||||
const offset = address & 0x3;
|
||||
const peripheral = this.findPeripheral(address);
|
||||
if (peripheral) {
|
||||
const atomicType = (alignedAddress & 0x3000) >> 12;
|
||||
const offset = alignedAddress & 0xfff;
|
||||
peripheral.writeUint32Atomic(offset, (value & 0xff) | ((value & 0xff) << 8) | ((value & 0xff) << 16) | ((value & 0xff) << 24), atomicType);
|
||||
return;
|
||||
}
|
||||
const originalValue = this.readUint32(alignedAddress);
|
||||
const newValue = new Uint32Array([originalValue]);
|
||||
new DataView(newValue.buffer).setUint8(offset, value);
|
||||
this.writeUint32(alignedAddress, newValue[0]);
|
||||
}
|
||||
writeUint16(address, value) {
|
||||
// we assume that addess is 16-bit aligned.
|
||||
// Ideally we should generate a fault if not!
|
||||
if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
|
||||
this.sramView.setUint16(address - RAM_START_ADDRESS, value, true);
|
||||
return;
|
||||
}
|
||||
const alignedAddress = (address & 0xfffffffc) >>> 0;
|
||||
const offset = address & 0x3;
|
||||
const peripheral = this.findPeripheral(address);
|
||||
if (peripheral) {
|
||||
const atomicType = (alignedAddress & 0x3000) >> 12;
|
||||
const offset = alignedAddress & 0xfff;
|
||||
peripheral.writeUint32Atomic(offset, (value & 0xffff) | ((value & 0xffff) << 16), atomicType);
|
||||
return;
|
||||
}
|
||||
const originalValue = this.readUint32(alignedAddress);
|
||||
const newValue = new Uint32Array([originalValue]);
|
||||
new DataView(newValue.buffer).setUint16(offset, value, true);
|
||||
this.writeUint32(alignedAddress, newValue[0]);
|
||||
}
|
||||
get gpioValues() {
|
||||
const { gpio } = this;
|
||||
let result = 0;
|
||||
for (let gpioIndex = 0; gpioIndex < gpio.length; gpioIndex++) {
|
||||
if (gpio[gpioIndex].inputValue) {
|
||||
result |= 1 << gpioIndex;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
setInterrupt(irq, value) {
|
||||
this.core.setInterrupt(irq, value);
|
||||
}
|
||||
updateIOInterrupt() {
|
||||
let interruptValue = false;
|
||||
for (const pin of this.gpio) {
|
||||
if (pin.irqValue) {
|
||||
interruptValue = true;
|
||||
}
|
||||
}
|
||||
this.setInterrupt(IRQ.IO_BANK0, interruptValue);
|
||||
}
|
||||
step() {
|
||||
this.core.executeInstruction();
|
||||
}
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
import { SimulationClock } from './clock/simulation-clock.js';
|
||||
import { RP2040 } from './rp2040.js';
|
||||
export class Simulator {
|
||||
constructor(clock = new SimulationClock()) {
|
||||
this.clock = clock;
|
||||
this.executeTimer = null;
|
||||
this.stopped = true;
|
||||
this.rp2040 = new RP2040(clock);
|
||||
this.rp2040.onBreak = () => this.stop();
|
||||
}
|
||||
execute() {
|
||||
const { rp2040, clock } = this;
|
||||
this.executeTimer = null;
|
||||
this.stopped = false;
|
||||
const cycleNanos = 1e9 / 125000000; // 125 MHz
|
||||
for (let i = 0; i < 1000000 && !this.stopped; i++) {
|
||||
if (rp2040.core.waiting) {
|
||||
const { nanosToNextAlarm } = clock;
|
||||
clock.tick(nanosToNextAlarm);
|
||||
i += nanosToNextAlarm / cycleNanos;
|
||||
}
|
||||
else {
|
||||
const cycles = rp2040.core.executeInstruction();
|
||||
clock.tick(cycles * cycleNanos);
|
||||
}
|
||||
}
|
||||
if (!this.stopped) {
|
||||
this.executeTimer = setTimeout(() => this.execute(), 0);
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.stopped = true;
|
||||
if (this.executeTimer != null) {
|
||||
clearTimeout(this.executeTimer);
|
||||
this.executeTimer = null;
|
||||
}
|
||||
}
|
||||
get executing() {
|
||||
return !this.stopped;
|
||||
}
|
||||
}
|
||||
Vendored
+421
@@ -0,0 +1,421 @@
|
||||
import { Interpolator } from './interpolator.js';
|
||||
const CPUID = 0x000;
|
||||
// GPIO
|
||||
const GPIO_IN = 0x004; // Input value for GPIO pins
|
||||
const GPIO_HI_IN = 0x008; // Input value for QSPI pins
|
||||
const GPIO_OUT = 0x010; // GPIO output value
|
||||
const GPIO_OUT_SET = 0x014; // GPIO output value set
|
||||
const GPIO_OUT_CLR = 0x018; // GPIO output value clear
|
||||
const GPIO_OUT_XOR = 0x01c; // GPIO output value XOR
|
||||
const GPIO_OE = 0x020; // GPIO output enable
|
||||
const GPIO_OE_SET = 0x024; // GPIO output enable set
|
||||
const GPIO_OE_CLR = 0x028; // GPIO output enable clear
|
||||
const GPIO_OE_XOR = 0x02c; // GPIO output enable XOR
|
||||
const GPIO_HI_OUT = 0x030; // QSPI output value
|
||||
const GPIO_HI_OUT_SET = 0x034; // QSPI output value set
|
||||
const GPIO_HI_OUT_CLR = 0x038; // QSPI output value clear
|
||||
const GPIO_HI_OUT_XOR = 0x03c; // QSPI output value XOR
|
||||
const GPIO_HI_OE = 0x040; // QSPI output enable
|
||||
const GPIO_HI_OE_SET = 0x044; // QSPI output enable set
|
||||
const GPIO_HI_OE_CLR = 0x048; // QSPI output enable clear
|
||||
const GPIO_HI_OE_XOR = 0x04c; // QSPI output enable XOR
|
||||
const GPIO_MASK = 0x3fffffff;
|
||||
//HARDWARE DIVIDER
|
||||
const DIV_UDIVIDEND = 0x060; // Divider unsigned dividend
|
||||
const DIV_UDIVISOR = 0x064; // Divider unsigned divisor
|
||||
const DIV_SDIVIDEND = 0x068; // Divider signed dividend
|
||||
const DIV_SDIVISOR = 0x06c; // Divider signed divisor
|
||||
const DIV_QUOTIENT = 0x070; // Divider result quotient
|
||||
const DIV_REMAINDER = 0x074; //Divider result remainder
|
||||
const DIV_CSR = 0x078;
|
||||
//INTERPOLATOR
|
||||
const INTERP0_ACCUM0 = 0x080; // Read/write access to accumulator 0
|
||||
const INTERP0_ACCUM1 = 0x084; // Read/write access to accumulator 1
|
||||
const INTERP0_BASE0 = 0x088; // Read/write access to BASE0 register
|
||||
const INTERP0_BASE1 = 0x08c; // Read/write access to BASE1 register
|
||||
const INTERP0_BASE2 = 0x090; // Read/write access to BASE2 register
|
||||
const INTERP0_POP_LANE0 = 0x094; // Read LANE0 result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP0_POP_LANE1 = 0x098; // Read LANE1 result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP0_POP_FULL = 0x09c; // Read FULL result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP0_PEEK_LANE0 = 0x0a0; // Read LANE0 result, without altering any internal state (PEEK)
|
||||
const INTERP0_PEEK_LANE1 = 0x0a4; // Read LANE1 result, without altering any internal state (PEEK)
|
||||
const INTERP0_PEEK_FULL = 0x0a8; // Read FULL result, without altering any internal state (PEEK)
|
||||
const INTERP0_CTRL_LANE0 = 0x0ac; // Control register for lane 0
|
||||
const INTERP0_CTRL_LANE1 = 0x0b0; // Control register for lane 1
|
||||
const INTERP0_ACCUM0_ADD = 0x0b4; // Values written here are atomically added to ACCUM0
|
||||
const INTERP0_ACCUM1_ADD = 0x0b8; // Values written here are atomically added to ACCUM1
|
||||
const INTERP0_BASE_1AND0 = 0x0bc; // On write, the lower 16 bits go to BASE0, upper bits to BASE1 simultaneously
|
||||
const INTERP1_ACCUM0 = 0x0c0; // Read/write access to accumulator 0
|
||||
const INTERP1_ACCUM1 = 0x0c4; // Read/write access to accumulator 1
|
||||
const INTERP1_BASE0 = 0x0c8; // Read/write access to BASE0 register
|
||||
const INTERP1_BASE1 = 0x0cc; // Read/write access to BASE1 register
|
||||
const INTERP1_BASE2 = 0x0d0; // Read/write access to BASE2 register
|
||||
const INTERP1_POP_LANE0 = 0x0d4; // Read LANE0 result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP1_POP_LANE1 = 0x0d8; // Read LANE1 result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP1_POP_FULL = 0x0dc; // Read FULL result, and simultaneously write lane results to both accumulators (POP)
|
||||
const INTERP1_PEEK_LANE0 = 0x0e0; // Read LANE0 result, without altering any internal state (PEEK)
|
||||
const INTERP1_PEEK_LANE1 = 0x0e4; // Read LANE1 result, without altering any internal state (PEEK)
|
||||
const INTERP1_PEEK_FULL = 0x0e8; // Read FULL result, without altering any internal state (PEEK)
|
||||
const INTERP1_CTRL_LANE0 = 0x0ec; // Control register for lane 0
|
||||
const INTERP1_CTRL_LANE1 = 0x0f0; // Control register for lane 1
|
||||
const INTERP1_ACCUM0_ADD = 0x0f4; // Values written here are atomically added to ACCUM0
|
||||
const INTERP1_ACCUM1_ADD = 0x0f8; // Values written here are atomically added to ACCUM1
|
||||
const INTERP1_BASE_1AND0 = 0x0fc; // On write, the lower 16 bits go to BASE0, upper bits to BASE1 simultaneously
|
||||
//SPINLOCK
|
||||
const SPINLOCK_ST = 0x5c;
|
||||
const SPINLOCK0 = 0x100;
|
||||
const SPINLOCK31 = 0x17c;
|
||||
export class RPSIO {
|
||||
constructor(rp2040) {
|
||||
this.rp2040 = rp2040;
|
||||
this.gpioValue = 0;
|
||||
this.gpioOutputEnable = 0;
|
||||
this.qspiGpioValue = 0;
|
||||
this.qspiGpioOutputEnable = 0;
|
||||
this.divDividend = 0;
|
||||
this.divDivisor = 1;
|
||||
this.divQuotient = 0;
|
||||
this.divRemainder = 0;
|
||||
this.divCSR = 0;
|
||||
this.spinLock = 0;
|
||||
this.interp0 = new Interpolator(0);
|
||||
this.interp1 = new Interpolator(1);
|
||||
}
|
||||
updateHardwareDivider(signed) {
|
||||
if (this.divDivisor == 0) {
|
||||
this.divQuotient = this.divDividend > 0 ? -1 : 1;
|
||||
this.divRemainder = this.divDividend;
|
||||
}
|
||||
else {
|
||||
if (signed) {
|
||||
this.divQuotient = (this.divDividend | 0) / (this.divDivisor | 0);
|
||||
this.divRemainder = (this.divDividend | 0) % (this.divDivisor | 0);
|
||||
}
|
||||
else {
|
||||
this.divQuotient = (this.divDividend >>> 0) / (this.divDivisor >>> 0);
|
||||
this.divRemainder = (this.divDividend >>> 0) % (this.divDivisor >>> 0);
|
||||
}
|
||||
}
|
||||
this.divCSR = 0b11;
|
||||
this.rp2040.core.cycles += 8;
|
||||
}
|
||||
readUint32(offset) {
|
||||
if (offset >= SPINLOCK0 && offset <= SPINLOCK31) {
|
||||
const bitIndexMask = 1 << ((offset - SPINLOCK0) / 4);
|
||||
if (this.spinLock & bitIndexMask) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
this.spinLock |= bitIndexMask;
|
||||
return bitIndexMask;
|
||||
}
|
||||
}
|
||||
switch (offset) {
|
||||
case GPIO_IN:
|
||||
return this.rp2040.gpioValues;
|
||||
case GPIO_HI_IN: {
|
||||
const { qspi } = this.rp2040;
|
||||
let result = 0;
|
||||
for (let qspiIndex = 0; qspiIndex < qspi.length; qspiIndex++) {
|
||||
if (qspi[qspiIndex].inputValue) {
|
||||
result |= 1 << qspiIndex;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case GPIO_OUT:
|
||||
return this.gpioValue;
|
||||
case GPIO_OE:
|
||||
return this.gpioOutputEnable;
|
||||
case GPIO_HI_OUT:
|
||||
return this.qspiGpioValue;
|
||||
case GPIO_HI_OE:
|
||||
return this.qspiGpioOutputEnable;
|
||||
case GPIO_OUT_SET:
|
||||
case GPIO_OUT_CLR:
|
||||
case GPIO_OUT_XOR:
|
||||
case GPIO_OE_SET:
|
||||
case GPIO_OE_CLR:
|
||||
case GPIO_OE_XOR:
|
||||
case GPIO_HI_OUT_SET:
|
||||
case GPIO_HI_OUT_CLR:
|
||||
case GPIO_HI_OUT_XOR:
|
||||
case GPIO_HI_OE_SET:
|
||||
case GPIO_HI_OE_CLR:
|
||||
case GPIO_HI_OE_XOR:
|
||||
return 0; // TODO verify with silicone
|
||||
case CPUID:
|
||||
// Returns the current CPU core id (always 0 for now)
|
||||
return 0;
|
||||
case SPINLOCK_ST:
|
||||
return this.spinLock;
|
||||
case DIV_UDIVIDEND:
|
||||
return this.divDividend;
|
||||
case DIV_SDIVIDEND:
|
||||
return this.divDividend;
|
||||
case DIV_UDIVISOR:
|
||||
return this.divDivisor;
|
||||
case DIV_SDIVISOR:
|
||||
return this.divDivisor;
|
||||
case DIV_QUOTIENT:
|
||||
this.divCSR &= ~0b10;
|
||||
return this.divQuotient;
|
||||
case DIV_REMAINDER:
|
||||
return this.divRemainder;
|
||||
case DIV_CSR:
|
||||
return this.divCSR;
|
||||
case INTERP0_ACCUM0:
|
||||
return this.interp0.accum0;
|
||||
case INTERP0_ACCUM1:
|
||||
return this.interp0.accum1;
|
||||
case INTERP0_BASE0:
|
||||
return this.interp0.base0;
|
||||
case INTERP0_BASE1:
|
||||
return this.interp0.base1;
|
||||
case INTERP0_BASE2:
|
||||
return this.interp0.base2;
|
||||
case INTERP0_CTRL_LANE0:
|
||||
return this.interp0.ctrl0;
|
||||
case INTERP0_CTRL_LANE1:
|
||||
return this.interp0.ctrl1;
|
||||
case INTERP0_PEEK_LANE0:
|
||||
return this.interp0.result0;
|
||||
case INTERP0_PEEK_LANE1:
|
||||
return this.interp0.result1;
|
||||
case INTERP0_PEEK_FULL:
|
||||
return this.interp0.result2;
|
||||
case INTERP0_POP_LANE0: {
|
||||
const value = this.interp0.result0;
|
||||
this.interp0.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP0_POP_LANE1: {
|
||||
const value = this.interp0.result1;
|
||||
this.interp0.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP0_POP_FULL: {
|
||||
const value = this.interp0.result2;
|
||||
this.interp0.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP0_ACCUM0_ADD:
|
||||
return this.interp0.smresult0;
|
||||
case INTERP0_ACCUM1_ADD:
|
||||
return this.interp0.smresult1;
|
||||
case INTERP1_ACCUM0:
|
||||
return this.interp1.accum0;
|
||||
case INTERP1_ACCUM1:
|
||||
return this.interp1.accum1;
|
||||
case INTERP1_BASE0:
|
||||
return this.interp1.base0;
|
||||
case INTERP1_BASE1:
|
||||
return this.interp1.base1;
|
||||
case INTERP1_BASE2:
|
||||
return this.interp1.base2;
|
||||
case INTERP1_CTRL_LANE0:
|
||||
return this.interp1.ctrl0;
|
||||
case INTERP1_CTRL_LANE1:
|
||||
return this.interp1.ctrl1;
|
||||
case INTERP1_PEEK_LANE0:
|
||||
return this.interp1.result0;
|
||||
case INTERP1_PEEK_LANE1:
|
||||
return this.interp1.result1;
|
||||
case INTERP1_PEEK_FULL:
|
||||
return this.interp1.result2;
|
||||
case INTERP1_POP_LANE0: {
|
||||
const value = this.interp1.result0;
|
||||
this.interp1.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP1_POP_LANE1: {
|
||||
const value = this.interp1.result1;
|
||||
this.interp1.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP1_POP_FULL: {
|
||||
const value = this.interp1.result2;
|
||||
this.interp1.writeback();
|
||||
return value;
|
||||
}
|
||||
case INTERP1_ACCUM0_ADD:
|
||||
return this.interp1.smresult0;
|
||||
case INTERP1_ACCUM1_ADD:
|
||||
return this.interp1.smresult1;
|
||||
}
|
||||
console.warn(`Read from invalid SIO address: ${offset.toString(16)}`);
|
||||
return 0xffffffff;
|
||||
}
|
||||
writeUint32(offset, value) {
|
||||
if (offset >= SPINLOCK0 && offset <= SPINLOCK31) {
|
||||
const bitIndexMask = ~(1 << ((offset - SPINLOCK0) / 4));
|
||||
this.spinLock &= bitIndexMask;
|
||||
return;
|
||||
}
|
||||
const prevGpioValue = this.gpioValue;
|
||||
const prevGpioOutputEnable = this.gpioOutputEnable;
|
||||
switch (offset) {
|
||||
case GPIO_OUT:
|
||||
this.gpioValue = value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_OUT_SET:
|
||||
this.gpioValue |= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_OUT_CLR:
|
||||
this.gpioValue &= ~value;
|
||||
break;
|
||||
case GPIO_OUT_XOR:
|
||||
this.gpioValue ^= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_OE:
|
||||
this.gpioOutputEnable = value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_OE_SET:
|
||||
this.gpioOutputEnable |= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_OE_CLR:
|
||||
this.gpioOutputEnable &= ~value;
|
||||
break;
|
||||
case GPIO_OE_XOR:
|
||||
this.gpioOutputEnable ^= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OUT:
|
||||
this.qspiGpioValue = value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OUT_SET:
|
||||
this.qspiGpioValue |= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OUT_CLR:
|
||||
this.qspiGpioValue &= ~value;
|
||||
break;
|
||||
case GPIO_HI_OUT_XOR:
|
||||
this.qspiGpioValue ^= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OE:
|
||||
this.qspiGpioOutputEnable = value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OE_SET:
|
||||
this.qspiGpioOutputEnable |= value & GPIO_MASK;
|
||||
break;
|
||||
case GPIO_HI_OE_CLR:
|
||||
this.qspiGpioOutputEnable &= ~value;
|
||||
break;
|
||||
case GPIO_HI_OE_XOR:
|
||||
this.qspiGpioOutputEnable ^= value & GPIO_MASK;
|
||||
break;
|
||||
case DIV_UDIVIDEND:
|
||||
this.divDividend = value;
|
||||
this.updateHardwareDivider(false);
|
||||
break;
|
||||
case DIV_SDIVIDEND:
|
||||
this.divDividend = value;
|
||||
this.updateHardwareDivider(true);
|
||||
break;
|
||||
case DIV_UDIVISOR:
|
||||
this.divDivisor = value;
|
||||
this.updateHardwareDivider(false);
|
||||
break;
|
||||
case DIV_SDIVISOR:
|
||||
this.divDivisor = value;
|
||||
this.updateHardwareDivider(true);
|
||||
break;
|
||||
case DIV_QUOTIENT:
|
||||
this.divQuotient = value;
|
||||
this.divCSR = 0b11;
|
||||
break;
|
||||
case DIV_REMAINDER:
|
||||
this.divRemainder = value;
|
||||
this.divCSR = 0b11;
|
||||
break;
|
||||
case INTERP0_ACCUM0:
|
||||
this.interp0.accum0 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_ACCUM1:
|
||||
this.interp0.accum1 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_BASE0:
|
||||
this.interp0.base0 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_BASE1:
|
||||
this.interp0.base1 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_BASE2:
|
||||
this.interp0.base2 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_CTRL_LANE0:
|
||||
this.interp0.ctrl0 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_CTRL_LANE1:
|
||||
this.interp0.ctrl1 = value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_ACCUM0_ADD:
|
||||
this.interp0.accum0 += value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_ACCUM1_ADD:
|
||||
this.interp0.accum1 += value;
|
||||
this.interp0.update();
|
||||
break;
|
||||
case INTERP0_BASE_1AND0:
|
||||
this.interp0.setBase01(value);
|
||||
break;
|
||||
case INTERP1_ACCUM0:
|
||||
this.interp1.accum0 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_ACCUM1:
|
||||
this.interp1.accum1 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_BASE0:
|
||||
this.interp1.base0 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_BASE1:
|
||||
this.interp1.base1 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_BASE2:
|
||||
this.interp1.base2 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_CTRL_LANE0:
|
||||
this.interp1.ctrl0 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_CTRL_LANE1:
|
||||
this.interp1.ctrl1 = value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_ACCUM0_ADD:
|
||||
this.interp1.accum0 += value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_ACCUM1_ADD:
|
||||
this.interp1.accum1 += value;
|
||||
this.interp1.update();
|
||||
break;
|
||||
case INTERP1_BASE_1AND0:
|
||||
this.interp1.setBase01(value);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Write to invalid SIO address: ${offset.toString(16)}, value=${value.toString(16)}`);
|
||||
}
|
||||
const pinsToUpdate = (this.gpioValue ^ prevGpioValue) | (this.gpioOutputEnable ^ prevGpioOutputEnable);
|
||||
if (pinsToUpdate) {
|
||||
const { gpio } = this.rp2040;
|
||||
for (let gpioIndex = 0; gpioIndex < gpio.length; gpioIndex++) {
|
||||
if (pinsToUpdate & (1 << gpioIndex)) {
|
||||
gpio[gpioIndex].checkForUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+121
@@ -0,0 +1,121 @@
|
||||
import { FIFO } from '../utils/fifo.js';
|
||||
import { DataDirection, DescriptorType, SetupRecipient, SetupType } from './interfaces.js';
|
||||
import { createSetupPacket, getDescriptorPacket, setDeviceAddressPacket, setDeviceConfigurationPacket, } from './setup.js';
|
||||
// CDC stuff
|
||||
const CDC_REQUEST_SET_CONTROL_LINE_STATE = 0x22;
|
||||
const CDC_DTR = 1 << 0;
|
||||
const CDC_RTS = 1 << 1;
|
||||
const CDC_DATA_CLASS = 10;
|
||||
const ENDPOINT_BULK = 2;
|
||||
const TX_FIFO_SIZE = 512;
|
||||
const ENDPOINT_ZERO = 0;
|
||||
const CONFIGURATION_DESCRIPTOR_SIZE = 9;
|
||||
export function extractEndpointNumbers(descriptors) {
|
||||
let index = 0;
|
||||
let foundInterface = false;
|
||||
const result = {
|
||||
in: -1,
|
||||
out: -1,
|
||||
};
|
||||
while (index < descriptors.length) {
|
||||
const len = descriptors[index];
|
||||
if (len < 2 || descriptors.length < index + len) {
|
||||
break;
|
||||
}
|
||||
const type = descriptors[index + 1];
|
||||
if (type === DescriptorType.Interface && len === 9) {
|
||||
const numEndpoints = descriptors[index + 4];
|
||||
const interfaceClass = descriptors[index + 5];
|
||||
foundInterface = numEndpoints === 2 && interfaceClass === CDC_DATA_CLASS;
|
||||
}
|
||||
if (foundInterface && type === DescriptorType.Endpoint && len === 7) {
|
||||
const address = descriptors[index + 2];
|
||||
const attributes = descriptors[index + 3];
|
||||
if ((attributes & 0x3) === ENDPOINT_BULK) {
|
||||
if (address & 0x80) {
|
||||
result.in = address & 0xf;
|
||||
}
|
||||
else {
|
||||
result.out = address & 0xf;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += descriptors[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export class USBCDC {
|
||||
constructor(usb) {
|
||||
this.usb = usb;
|
||||
this.txFIFO = new FIFO(TX_FIFO_SIZE);
|
||||
this.initialized = false;
|
||||
this.descriptorsSize = null;
|
||||
this.descriptors = [];
|
||||
this.outEndpoint = -1;
|
||||
this.inEndpoint = -1;
|
||||
this.usb.onUSBEnabled = () => {
|
||||
this.usb.resetDevice();
|
||||
};
|
||||
this.usb.onResetReceived = () => {
|
||||
this.usb.sendSetupPacket(setDeviceAddressPacket(1));
|
||||
};
|
||||
this.usb.onEndpointWrite = (endpoint, buffer) => {
|
||||
var _a, _b;
|
||||
if (endpoint === ENDPOINT_ZERO && buffer.length === 0) {
|
||||
if (this.descriptorsSize == null) {
|
||||
this.usb.sendSetupPacket(getDescriptorPacket(DescriptorType.Configration, CONFIGURATION_DESCRIPTOR_SIZE));
|
||||
}
|
||||
// Acknowledgement
|
||||
else if (!this.initialized) {
|
||||
this.cdcSetControlLineState();
|
||||
(_a = this.onDeviceConnected) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
}
|
||||
}
|
||||
if (endpoint === ENDPOINT_ZERO && buffer.length > 1) {
|
||||
if (buffer.length === CONFIGURATION_DESCRIPTOR_SIZE &&
|
||||
buffer[1] === DescriptorType.Configration &&
|
||||
this.descriptorsSize == null) {
|
||||
this.descriptorsSize = (buffer[3] << 8) | buffer[2];
|
||||
this.usb.sendSetupPacket(getDescriptorPacket(DescriptorType.Configration, this.descriptorsSize));
|
||||
}
|
||||
else if (this.descriptorsSize != null && this.descriptors.length < this.descriptorsSize) {
|
||||
this.descriptors.push(...buffer);
|
||||
}
|
||||
if (this.descriptorsSize === this.descriptors.length) {
|
||||
const endpoints = extractEndpointNumbers(this.descriptors);
|
||||
this.inEndpoint = endpoints.in;
|
||||
this.outEndpoint = endpoints.out;
|
||||
// Now configure the device
|
||||
this.usb.sendSetupPacket(setDeviceConfigurationPacket(1));
|
||||
}
|
||||
}
|
||||
if (endpoint === this.inEndpoint) {
|
||||
(_b = this.onSerialData) === null || _b === void 0 ? void 0 : _b.call(this, buffer);
|
||||
}
|
||||
};
|
||||
this.usb.onEndpointRead = (endpoint, size) => {
|
||||
if (endpoint === this.outEndpoint) {
|
||||
const buffer = new Uint8Array(Math.min(size, this.txFIFO.itemCount));
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
buffer[i] = this.txFIFO.pull();
|
||||
}
|
||||
this.usb.endpointReadDone(this.outEndpoint, buffer);
|
||||
}
|
||||
};
|
||||
}
|
||||
cdcSetControlLineState(value = CDC_DTR | CDC_RTS, interfaceNumber = 0) {
|
||||
this.usb.sendSetupPacket(createSetupPacket({
|
||||
dataDirection: DataDirection.HostToDevice,
|
||||
type: SetupType.Class,
|
||||
recipient: SetupRecipient.Device,
|
||||
bRequest: CDC_REQUEST_SET_CONTROL_LINE_STATE,
|
||||
wValue: value,
|
||||
wIndex: interfaceNumber,
|
||||
wLength: 0,
|
||||
}));
|
||||
this.initialized = true;
|
||||
}
|
||||
sendSerialByte(data) {
|
||||
this.txFIFO.push(data);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
export var DataDirection;
|
||||
(function (DataDirection) {
|
||||
DataDirection[DataDirection["HostToDevice"] = 0] = "HostToDevice";
|
||||
DataDirection[DataDirection["DeviceToHost"] = 1] = "DeviceToHost";
|
||||
})(DataDirection || (DataDirection = {}));
|
||||
export var SetupType;
|
||||
(function (SetupType) {
|
||||
SetupType[SetupType["Standard"] = 0] = "Standard";
|
||||
SetupType[SetupType["Class"] = 1] = "Class";
|
||||
SetupType[SetupType["Vendor"] = 2] = "Vendor";
|
||||
SetupType[SetupType["Reserved"] = 3] = "Reserved";
|
||||
})(SetupType || (SetupType = {}));
|
||||
export var SetupRecipient;
|
||||
(function (SetupRecipient) {
|
||||
SetupRecipient[SetupRecipient["Device"] = 0] = "Device";
|
||||
SetupRecipient[SetupRecipient["Interface"] = 1] = "Interface";
|
||||
SetupRecipient[SetupRecipient["Endpoint"] = 2] = "Endpoint";
|
||||
SetupRecipient[SetupRecipient["Other"] = 3] = "Other";
|
||||
})(SetupRecipient || (SetupRecipient = {}));
|
||||
export var SetupRequest;
|
||||
(function (SetupRequest) {
|
||||
SetupRequest[SetupRequest["GetStatus"] = 0] = "GetStatus";
|
||||
SetupRequest[SetupRequest["ClearFeature"] = 1] = "ClearFeature";
|
||||
SetupRequest[SetupRequest["Reserved1"] = 2] = "Reserved1";
|
||||
SetupRequest[SetupRequest["SetFeature"] = 3] = "SetFeature";
|
||||
SetupRequest[SetupRequest["Reserved2"] = 4] = "Reserved2";
|
||||
SetupRequest[SetupRequest["SetAddress"] = 5] = "SetAddress";
|
||||
SetupRequest[SetupRequest["GetDescriptor"] = 6] = "GetDescriptor";
|
||||
SetupRequest[SetupRequest["SetDescriptor"] = 7] = "SetDescriptor";
|
||||
SetupRequest[SetupRequest["GetConfiguration"] = 8] = "GetConfiguration";
|
||||
SetupRequest[SetupRequest["SetDeviceConfiguration"] = 9] = "SetDeviceConfiguration";
|
||||
SetupRequest[SetupRequest["GetInterface"] = 10] = "GetInterface";
|
||||
SetupRequest[SetupRequest["SetInterface"] = 11] = "SetInterface";
|
||||
SetupRequest[SetupRequest["SynchFrame"] = 12] = "SynchFrame";
|
||||
})(SetupRequest || (SetupRequest = {}));
|
||||
export var DescriptorType;
|
||||
(function (DescriptorType) {
|
||||
DescriptorType[DescriptorType["Device"] = 1] = "Device";
|
||||
DescriptorType[DescriptorType["Configration"] = 2] = "Configration";
|
||||
DescriptorType[DescriptorType["String"] = 3] = "String";
|
||||
DescriptorType[DescriptorType["Interface"] = 4] = "Interface";
|
||||
DescriptorType[DescriptorType["Endpoint"] = 5] = "Endpoint";
|
||||
})(DescriptorType || (DescriptorType = {}));
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
import { DataDirection, SetupRecipient, SetupRequest, SetupType, } from './interfaces.js';
|
||||
export function createSetupPacket(params) {
|
||||
const setupPacket = new Uint8Array(8);
|
||||
setupPacket[0] = (params.dataDirection << 7) | (params.type << 5) | params.recipient;
|
||||
setupPacket[1] = params.bRequest;
|
||||
setupPacket[2] = params.wValue & 0xff;
|
||||
setupPacket[3] = (params.wValue >> 8) & 0xff;
|
||||
setupPacket[4] = params.wIndex & 0xff;
|
||||
setupPacket[5] = (params.wIndex >> 8) & 0xff;
|
||||
setupPacket[6] = params.wLength & 0xff;
|
||||
setupPacket[7] = (params.wLength >> 8) & 0xff;
|
||||
return setupPacket;
|
||||
}
|
||||
export function setDeviceAddressPacket(address) {
|
||||
return createSetupPacket({
|
||||
dataDirection: DataDirection.HostToDevice,
|
||||
type: SetupType.Standard,
|
||||
recipient: SetupRecipient.Device,
|
||||
bRequest: SetupRequest.SetAddress,
|
||||
wValue: address,
|
||||
wIndex: 0,
|
||||
wLength: 0,
|
||||
});
|
||||
}
|
||||
export function getDescriptorPacket(type, length, index = 0) {
|
||||
return createSetupPacket({
|
||||
dataDirection: DataDirection.DeviceToHost,
|
||||
type: SetupType.Standard,
|
||||
recipient: SetupRecipient.Device,
|
||||
bRequest: SetupRequest.GetDescriptor,
|
||||
wValue: type << 8,
|
||||
wIndex: index,
|
||||
wLength: length,
|
||||
});
|
||||
}
|
||||
export function setDeviceConfigurationPacket(configurationNumber) {
|
||||
return createSetupPacket({
|
||||
dataDirection: DataDirection.HostToDevice,
|
||||
type: SetupType.Standard,
|
||||
recipient: SetupRecipient.Device,
|
||||
bRequest: SetupRequest.SetDeviceConfiguration,
|
||||
wValue: configurationNumber,
|
||||
wIndex: 0,
|
||||
wLength: 0,
|
||||
});
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Interface for simulated USB devices that can be connected to the RP2040 USB host controller.
|
||||
*/
|
||||
// USB Setup packet fields - bmRequestType bit masks
|
||||
// Direction (bit 7)
|
||||
export const USB_DIR_OUT = 0x00;
|
||||
export const USB_DIR_IN = 0x80;
|
||||
// Type (bits 6:5)
|
||||
export const USB_TYPE_STANDARD = 0x00;
|
||||
export const USB_TYPE_CLASS = 0x20;
|
||||
export const USB_TYPE_VENDOR = 0x40;
|
||||
// Recipient (bits 4:0)
|
||||
export const USB_RECIP_DEVICE = 0x00;
|
||||
export const USB_RECIP_INTERFACE = 0x01;
|
||||
export const USB_RECIP_ENDPOINT = 0x02;
|
||||
export const USB_RECIP_OTHER = 0x03;
|
||||
export function parseSetupPacket(setup) {
|
||||
return {
|
||||
bmRequestType: setup[0],
|
||||
bRequest: setup[1],
|
||||
wValue: setup[2] | (setup[3] << 8),
|
||||
wIndex: setup[4] | (setup[5] << 8),
|
||||
wLength: setup[6] | (setup[7] << 8),
|
||||
// Helpers
|
||||
direction: setup[0] & 0x80 ? 'in' : 'out',
|
||||
type: (setup[0] >> 5) & 0x03, // 0=Standard, 1=Class, 2=Vendor
|
||||
recipient: setup[0] & 0x1f, // 0=Device, 1=Interface, 2=Endpoint, 3=Other
|
||||
};
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
export function opcodeADCS(Rdn, Rm) {
|
||||
return (0b0100000101 << 6) | ((Rm & 7) << 3) | (Rdn & 7);
|
||||
}
|
||||
export function opcodeADDS1(Rd, Rn, imm3) {
|
||||
return (0b0001110 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeADDS2(Rdn, imm8) {
|
||||
return (0b00110 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff);
|
||||
}
|
||||
export function opcodeADDspPlusImm(Rd, imm8) {
|
||||
return (0b10101 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff);
|
||||
}
|
||||
export function opcodeADDsp2(imm) {
|
||||
return (0b101100000 << 7) | ((imm >> 2) & 0x7f);
|
||||
}
|
||||
export function opcodeADDSreg(Rd, Rn, Rm) {
|
||||
return (0b0001100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeADDreg(Rdn, Rm) {
|
||||
return (0b01000100 << 8) | ((Rdn & 0x8) << 4) | ((Rm & 0xf) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeADR(Rd, imm8) {
|
||||
return (0b10100 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff);
|
||||
}
|
||||
export function opcodeANDS(Rn, Rm) {
|
||||
return (0b0100000000 << 6) | ((Rm & 7) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodeASRS(Rd, Rm, imm5) {
|
||||
return (0b00010 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeASRSreg(Rdn, Rm) {
|
||||
return (0b0100000100 << 6) | ((Rm & 0x7) << 3) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeBT1(cond, imm8) {
|
||||
return (0b1101 << 12) | ((cond & 0xf) << 8) | ((imm8 >> 1) & 0x1ff);
|
||||
}
|
||||
export function opcodeBT2(imm11) {
|
||||
return (0b11100 << 11) | ((imm11 >> 1) & 0x7ff);
|
||||
}
|
||||
export function opcodeBICS(Rdn, Rm) {
|
||||
return (0b0100001110 << 6) | ((Rm & 7) << 3) | (Rdn & 7);
|
||||
}
|
||||
export function opcodeBL(imm) {
|
||||
const imm11 = (imm >> 1) & 0x7ff;
|
||||
const imm10 = (imm >> 12) & 0x3ff;
|
||||
const s = imm < 0 ? 1 : 0;
|
||||
const j2 = 1 - (((imm >> 22) & 0x1) ^ s);
|
||||
const j1 = 1 - (((imm >> 23) & 0x1) ^ s);
|
||||
const opcode = (0b1101 << 28) | (j1 << 29) | (j2 << 27) | (imm11 << 16) | (0b11110 << 11) | (s << 10) | imm10;
|
||||
return opcode >>> 0;
|
||||
}
|
||||
export function opcodeBLX(Rm) {
|
||||
return (0b010001111 << 7) | (Rm << 3);
|
||||
}
|
||||
export function opcodeBX(Rm) {
|
||||
return (0b010001110 << 7) | (Rm << 3);
|
||||
}
|
||||
export function opcodeCMN(Rn, Rm) {
|
||||
return (0b0100001011 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodeCMPimm(Rn, Imm8) {
|
||||
return (0b00101 << 11) | ((Rn & 0x7) << 8) | (Imm8 & 0xff);
|
||||
}
|
||||
export function opcodeCMPregT1(Rn, Rm) {
|
||||
return (0b0100001010 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodeCMPregT2(Rn, Rm) {
|
||||
return (0b01000101 << 8) | (((Rn >> 3) & 0x1) << 7) | ((Rm & 0xf) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodeDMBSY() {
|
||||
return 0x8f50f3bf;
|
||||
}
|
||||
export function opcodeDSBSY() {
|
||||
return 0x8f4ff3bf;
|
||||
}
|
||||
export function opcodeEORS(Rdn, Rm) {
|
||||
return (0b0100000001 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeISBSY() {
|
||||
return 0x8f6ff3bf;
|
||||
}
|
||||
export function opcodeLDMIA(Rn, registers) {
|
||||
return (0b11001 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff);
|
||||
}
|
||||
export function opcodeLDRreg(Rt, Rn, Rm) {
|
||||
return (0b0101100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRimm(Rt, Rn, imm5) {
|
||||
return (0b01101 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRlit(Rt, imm8) {
|
||||
return (0b01001 << 11) | ((imm8 >> 2) & 0xff) | ((Rt & 0x7) << 8);
|
||||
}
|
||||
export function opcodeLDRB(Rt, Rn, imm5) {
|
||||
return (0b01111 << 11) | ((imm5 & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRsp(Rt, imm8) {
|
||||
return (0b10011 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff);
|
||||
}
|
||||
export function opcodeLDRBreg(Rt, Rn, Rm) {
|
||||
return (0b0101110 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRH(Rt, Rn, imm5) {
|
||||
return (0b10001 << 11) | (((imm5 >> 1) & 0xf) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRHreg(Rt, Rn, Rm) {
|
||||
return (0b0101101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRSB(Rt, Rn, Rm) {
|
||||
return (0b0101011 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLDRSH(Rt, Rn, Rm) {
|
||||
return (0b0101111 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeLSLSreg(Rdn, Rm) {
|
||||
return (0b0100000010 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeLSLSimm(Rd, Rm, Imm5) {
|
||||
return (0b00000 << 11) | ((Imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeLSRS(Rd, Rm, imm5) {
|
||||
return (0b00001 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeLSRSreg(Rdn, Rm) {
|
||||
return (0b0100000011 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeMOV(Rd, Rm) {
|
||||
return (0b01000110 << 8) | ((Rd & 0x8 ? 1 : 0) << 7) | (Rm << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeMOVS(Rd, imm8) {
|
||||
return (0b00100 << 11) | ((Rd & 0x7) << 8) | (imm8 & 0xff);
|
||||
}
|
||||
export function opcodeMOVSreg(Rd, Rm) {
|
||||
return (0b000000000 << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeMRS(Rd, specReg) {
|
||||
return (((0b1000 << 28) | ((Rd & 0xf) << 24) | ((specReg & 0xff) << 16) | 0b1111001111101111) >>> 0);
|
||||
}
|
||||
export function opcodeMSR(specReg, Rn) {
|
||||
return ((0b10001000 << 24) | ((specReg & 0xff) << 16) | (0b111100111000 << 4) | (Rn & 0xf)) >>> 0;
|
||||
}
|
||||
export function opcodeMULS(Rn, Rdm) {
|
||||
return (0b0100001101 << 6) | ((Rn & 7) << 3) | (Rdm & 7);
|
||||
}
|
||||
export function opcodeMVNS(Rd, Rm) {
|
||||
return (0b0100001111 << 6) | ((Rm & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeNOP() {
|
||||
return 0b1011111100000000;
|
||||
}
|
||||
export function opcodeORRS(Rn, Rm) {
|
||||
return (0b0100001100 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodePOP(P, registerList) {
|
||||
return (0b1011110 << 9) | ((P ? 1 : 0) << 8) | registerList;
|
||||
}
|
||||
export function opcodePUSH(M, registerList) {
|
||||
return (0b1011010 << 9) | ((M ? 1 : 0) << 8) | registerList;
|
||||
}
|
||||
export function opcodeREV(Rd, Rn) {
|
||||
return (0b1011101000 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeREV16(Rd, Rn) {
|
||||
return (0b1011101001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeREVSH(Rd, Rn) {
|
||||
return (0b1011101011 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeROR(Rdn, Rm) {
|
||||
return (0b0100000111 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
|
||||
}
|
||||
export function opcodeRSBS(Rd, Rn) {
|
||||
return (0b0100001001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
|
||||
}
|
||||
export function opcodeSBCS(Rn, Rm) {
|
||||
return (0b0100000110 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
|
||||
}
|
||||
export function opcodeSTMIA(Rn, registers) {
|
||||
return (0b11000 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff);
|
||||
}
|
||||
export function opcodeSTR(Rt, Rm, imm5) {
|
||||
return (0b01100 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSTRsp(Rt, imm8) {
|
||||
return (0b10010 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff);
|
||||
}
|
||||
export function opcodeSTRreg(Rt, Rn, Rm) {
|
||||
return (0b0101000 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSTRB(Rt, Rm, imm5) {
|
||||
return (0b01110 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSTRBreg(Rt, Rn, Rm) {
|
||||
return (0b0101010 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSTRH(Rt, Rm, imm5) {
|
||||
return (0b10000 << 11) | (((imm5 >> 1) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSTRHreg(Rt, Rn, Rm) {
|
||||
return (0b0101001 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
|
||||
}
|
||||
export function opcodeSUBS1(Rd, Rn, imm3) {
|
||||
return (0b0001111 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeSUBS2(Rdn, imm8) {
|
||||
return (0b00111 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff);
|
||||
}
|
||||
export function opcodeSUBSreg(Rd, Rn, Rm) {
|
||||
return (0b0001101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeSUBsp(imm) {
|
||||
return (0b101100001 << 7) | ((imm >> 2) & 0x7f);
|
||||
}
|
||||
export function opcodeSVC(imm8) {
|
||||
return (0b11011111 << 8) | (imm8 & 0xff);
|
||||
}
|
||||
export function opcodeSXTB(Rd, Rm) {
|
||||
return (0b1011001001 << 6) | ((Rm & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeSXTH(Rd, Rm) {
|
||||
return (0b1011001000 << 6) | ((Rm & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeTST(Rm, Rn) {
|
||||
return (0b0100001000 << 6) | ((Rn & 7) << 3) | (Rm & 7);
|
||||
}
|
||||
export function opcodeUXTB(Rd, Rm) {
|
||||
return (0b1011001011 << 6) | ((Rm & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeUDF(imm8) {
|
||||
return ((0b11011110 << 8) | (imm8 & 0xff)) >>> 0;
|
||||
}
|
||||
export function opcodeUDF2(imm16) {
|
||||
const imm12 = imm16 & 0xfff;
|
||||
const imm4 = (imm16 >> 12) & 0xf;
|
||||
return ((0b111101111111 << 4) | imm4 | (0b1010 << 28) | (imm12 << 16)) >>> 0;
|
||||
}
|
||||
export function opcodeUXTH(Rd, Rm) {
|
||||
return (0b1011001010 << 6) | ((Rm & 7) << 3) | (Rd & 7);
|
||||
}
|
||||
export function opcodeWFI() {
|
||||
return 0b1011111100110000;
|
||||
}
|
||||
export function opcodeYIELD() {
|
||||
return 0b1011111100010000;
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
export function bit(n) {
|
||||
return 1 << n;
|
||||
}
|
||||
export function s32(n) {
|
||||
return n | 0;
|
||||
}
|
||||
export function u32(n) {
|
||||
return n >>> 0;
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
export class FIFO {
|
||||
constructor(size) {
|
||||
this.start = 0;
|
||||
this.used = 0;
|
||||
this.buffer = new Uint32Array(size);
|
||||
}
|
||||
get size() {
|
||||
return this.buffer.length;
|
||||
}
|
||||
get itemCount() {
|
||||
return this.used;
|
||||
}
|
||||
push(value) {
|
||||
const { length } = this.buffer;
|
||||
const { start, used } = this;
|
||||
if (this.used < length) {
|
||||
this.buffer[(start + used) % length] = value;
|
||||
this.used++;
|
||||
}
|
||||
}
|
||||
pull() {
|
||||
const { start, used } = this;
|
||||
const { length } = this.buffer;
|
||||
if (used) {
|
||||
this.start = (start + 1) % length;
|
||||
this.used--;
|
||||
return this.buffer[start];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
peek() {
|
||||
return this.used ? this.buffer[this.start] : 0;
|
||||
}
|
||||
reset() {
|
||||
this.used = 0;
|
||||
}
|
||||
get empty() {
|
||||
return this.used == 0;
|
||||
}
|
||||
get full() {
|
||||
return this.used === this.buffer.length;
|
||||
}
|
||||
get items() {
|
||||
const { start, used, buffer } = this;
|
||||
const { length } = buffer;
|
||||
const result = [];
|
||||
for (let i = 0; i < used; i++) {
|
||||
result[i] = buffer[(start + i) % length];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { formatTime } from './time.js';
|
||||
export var LogLevel;
|
||||
(function (LogLevel) {
|
||||
LogLevel[LogLevel["Debug"] = 0] = "Debug";
|
||||
LogLevel[LogLevel["Info"] = 1] = "Info";
|
||||
LogLevel[LogLevel["Warn"] = 2] = "Warn";
|
||||
LogLevel[LogLevel["Error"] = 3] = "Error";
|
||||
})(LogLevel || (LogLevel = {}));
|
||||
export class ConsoleLogger {
|
||||
constructor(currentLogLevel, throwOnError = true) {
|
||||
this.currentLogLevel = currentLogLevel;
|
||||
this.throwOnError = throwOnError;
|
||||
}
|
||||
aboveLogLevel(logLevel) {
|
||||
return logLevel >= this.currentLogLevel ? true : false;
|
||||
}
|
||||
formatMessage(componentName, message) {
|
||||
const currentTime = formatTime(new Date());
|
||||
return `${currentTime} [${componentName}] ${message}`;
|
||||
}
|
||||
debug(componetName, message) {
|
||||
if (this.aboveLogLevel(LogLevel.Debug)) {
|
||||
console.debug(this.formatMessage(componetName, message));
|
||||
}
|
||||
}
|
||||
warn(componetName, message) {
|
||||
if (this.aboveLogLevel(LogLevel.Warn)) {
|
||||
console.warn(this.formatMessage(componetName, message));
|
||||
}
|
||||
}
|
||||
error(componentName, message) {
|
||||
if (this.aboveLogLevel(LogLevel.Error)) {
|
||||
console.error(this.formatMessage(componentName, message));
|
||||
if (this.throwOnError) {
|
||||
throw new Error(`[${componentName}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
info(componentName, message) {
|
||||
if (this.aboveLogLevel(LogLevel.Info)) {
|
||||
console.info(this.formatMessage(componentName, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
export const PIO_SRC_PINS = 0;
|
||||
export const PIO_SRC_X = 1;
|
||||
export const PIO_SRC_Y = 2;
|
||||
export const PIO_SRC_NULL = 3;
|
||||
export const PIO_SRC_STATUS = 5;
|
||||
export const PIO_SRC_ISR = 6;
|
||||
export const PIO_SRC_OSR = 7;
|
||||
export const PIO_DEST_PINS = 0;
|
||||
export const PIO_DEST_X = 1;
|
||||
export const PIO_DEST_Y = 2;
|
||||
export const PIO_DEST_NULL = 3;
|
||||
export const PIO_DEST_PINDIRS = 4;
|
||||
export const PIO_DEST_PC = 5;
|
||||
export const PIO_DEST_ISR = 6;
|
||||
export const PIO_DEST_EXEC = 7;
|
||||
export const PIO_MOV_DEST_PINS = 0;
|
||||
export const PIO_MOV_DEST_X = 1;
|
||||
export const PIO_MOV_DEST_Y = 2;
|
||||
export const PIO_MOV_DEST_EXEC = 4;
|
||||
export const PIO_MOV_DEST_PC = 5;
|
||||
export const PIO_MOV_DEST_ISR = 6;
|
||||
export const PIO_MOV_DEST_OSR = 7;
|
||||
export const PIO_OP_NONE = 0;
|
||||
export const PIO_OP_INVERT = 1;
|
||||
export const PIO_OP_BITREV = 2;
|
||||
export const PIO_WAIT_SRC_GPIO = 0;
|
||||
export const PIO_WAIT_SRC_PIN = 1;
|
||||
export const PIO_WAIT_SRC_IRQ = 2;
|
||||
export const PIO_COND_ALWAYS = 0;
|
||||
export const PIO_COND_NOTX = 1;
|
||||
export const PIO_COND_XDEC = 2;
|
||||
export const PIO_COND_NOTY = 3;
|
||||
export const PIO_COND_YDEC = 4;
|
||||
export const PIO_COND_XNEY = 5;
|
||||
export const PIO_COND_PIN = 6;
|
||||
export const PIO_COND_NOTEMPTYOSR = 7;
|
||||
export function pioJMP(cond = 0, address, delay = 0) {
|
||||
return ((delay & 0x1f) << 8) | ((cond & 0x7) << 5) | (address & 0x1f);
|
||||
}
|
||||
export function pioWAIT(polarity, src, index, delay = 0) {
|
||||
return ((1 << 13) |
|
||||
((delay & 0x1f) << 8) |
|
||||
((polarity ? 1 : 0) << 7) |
|
||||
((src & 0x3) << 5) |
|
||||
(index & 0x1f));
|
||||
}
|
||||
export function pioIN(src, bitCount, delay = 0) {
|
||||
return (2 << 13) | ((delay & 0x1f) << 8) | ((src & 0x7) << 5) | (bitCount & 0x1f);
|
||||
}
|
||||
export function pioOUT(Dest, bitCount, delay = 0) {
|
||||
return (3 << 13) | ((delay & 0x1f) << 8) | ((Dest & 0x7) << 5) | (bitCount & 0x1f);
|
||||
}
|
||||
export function pioPUSH(ifFull, noBlock, delay = 0) {
|
||||
return (4 << 13) | ((delay & 0x1f) << 8) | ((ifFull ? 1 : 0) << 6) | ((noBlock ? 1 : 0) << 5);
|
||||
}
|
||||
export function pioPULL(ifEmpty, noBlock, delay = 0) {
|
||||
return ((4 << 13) |
|
||||
((delay & 0x1f) << 8) |
|
||||
(1 << 7) |
|
||||
((ifEmpty ? 1 : 0) << 6) |
|
||||
((noBlock ? 1 : 0) << 5));
|
||||
}
|
||||
export function pioMOV(dest, op = 0, src, delay = 0) {
|
||||
return (5 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | ((op & 0x3) << 3) | (src & 0x7);
|
||||
}
|
||||
export function pioIRQ(clear, wait, index, delay = 0) {
|
||||
return ((6 << 13) |
|
||||
((delay & 0x1f) << 8) |
|
||||
((clear ? 1 : 0) << 6) |
|
||||
((wait ? 1 : 0) << 5) |
|
||||
(index & 0x1f));
|
||||
}
|
||||
export function pioSET(dest, data, delay = 0) {
|
||||
return (7 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | (data & 0x1f);
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
export function getCurrentMicroseconds() {
|
||||
if (typeof performance != 'undefined') {
|
||||
return Math.floor(performance.now() * 1000);
|
||||
}
|
||||
else {
|
||||
return Math.floor(eval('require')('perf_hooks').performance.now() * 1000);
|
||||
}
|
||||
}
|
||||
function leftPad(value, minLength, padChar = ' ') {
|
||||
if (value.length < minLength) {
|
||||
value = padChar + value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function rightPad(value, minLength, padChar = ' ') {
|
||||
if (value.length < minLength) {
|
||||
value += padChar;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
export function formatTime(date) {
|
||||
const hours = leftPad(date.getHours().toString(), 2, '0');
|
||||
const minutes = leftPad(date.getMinutes().toString(), 2, '0');
|
||||
const seconds = leftPad(date.getSeconds().toString(), 2, '0');
|
||||
const milliseconds = rightPad(date.getMilliseconds().toString(), 3);
|
||||
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
export var TimerMode;
|
||||
(function (TimerMode) {
|
||||
TimerMode[TimerMode["Increment"] = 0] = "Increment";
|
||||
TimerMode[TimerMode["Decrement"] = 1] = "Decrement";
|
||||
TimerMode[TimerMode["ZigZag"] = 2] = "ZigZag";
|
||||
})(TimerMode || (TimerMode = {}));
|
||||
export class Timer32 {
|
||||
constructor(clock, baseFreq) {
|
||||
this.clock = clock;
|
||||
this.baseFreq = baseFreq;
|
||||
this.baseValue = 0;
|
||||
this.baseNanos = 0;
|
||||
this.topValue = 0xffffffff;
|
||||
this.prescalerValue = 1;
|
||||
this.timerMode = TimerMode.Increment;
|
||||
this.enabled = true;
|
||||
this.listeners = [];
|
||||
}
|
||||
reset() {
|
||||
this.baseNanos = this.clock.nanos;
|
||||
this.baseValue = 0;
|
||||
this.updated();
|
||||
}
|
||||
set(value, zigZagDown = false) {
|
||||
this.baseValue = zigZagDown ? this.topValue * 2 - value : value;
|
||||
this.baseNanos = this.clock.nanos;
|
||||
this.updated();
|
||||
}
|
||||
/**
|
||||
* Advances the counter by the given amount. Note that this will
|
||||
* decrease the counter if the timer is running in Decrement mode.
|
||||
*
|
||||
* @param delta The value to add to the counter. Can be negative.
|
||||
*/
|
||||
advance(delta) {
|
||||
this.baseValue += delta;
|
||||
}
|
||||
get rawCounter() {
|
||||
const { baseFreq, prescalerValue, baseNanos, baseValue, enabled, timerMode } = this;
|
||||
if (!baseFreq || !prescalerValue || !enabled) {
|
||||
return this.baseValue;
|
||||
}
|
||||
const zigzag = timerMode == TimerMode.ZigZag;
|
||||
const ticks = ((this.clock.nanos - baseNanos) / 1e9) * (baseFreq / prescalerValue);
|
||||
const topModulo = zigzag ? this.topValue * 2 : this.topValue + 1;
|
||||
const delta = timerMode == TimerMode.Decrement ? topModulo - (ticks % topModulo) : ticks;
|
||||
let currentValue = Math.round(baseValue + delta);
|
||||
if (this.topValue != 0xffffffff) {
|
||||
currentValue %= topModulo;
|
||||
}
|
||||
return currentValue;
|
||||
}
|
||||
get counter() {
|
||||
let currentValue = this.rawCounter;
|
||||
if (this.timerMode == TimerMode.ZigZag && currentValue > this.topValue) {
|
||||
currentValue = this.topValue * 2 - currentValue;
|
||||
}
|
||||
return currentValue >>> 0;
|
||||
}
|
||||
get top() {
|
||||
return this.topValue;
|
||||
}
|
||||
set top(value) {
|
||||
const { counter } = this;
|
||||
this.topValue = value;
|
||||
this.set(counter <= this.topValue ? counter : 0);
|
||||
}
|
||||
get frequency() {
|
||||
return this.baseFreq;
|
||||
}
|
||||
set frequency(value) {
|
||||
this.baseValue = this.counter;
|
||||
this.baseNanos = this.clock.nanos;
|
||||
this.baseFreq = value;
|
||||
this.updated();
|
||||
}
|
||||
get prescaler() {
|
||||
return this.prescalerValue;
|
||||
}
|
||||
set prescaler(value) {
|
||||
this.baseValue = this.counter;
|
||||
this.baseNanos = this.clock.nanos;
|
||||
this.enabled = this.prescalerValue !== 0;
|
||||
this.prescalerValue = value;
|
||||
this.updated();
|
||||
}
|
||||
toNanos(cycles) {
|
||||
const { baseFreq, prescalerValue } = this;
|
||||
return (cycles * 1e9) / (baseFreq / prescalerValue);
|
||||
}
|
||||
get enable() {
|
||||
return this.enabled;
|
||||
}
|
||||
set enable(value) {
|
||||
if (value !== this.enabled) {
|
||||
if (value) {
|
||||
this.baseNanos = this.clock.nanos;
|
||||
}
|
||||
else {
|
||||
this.baseValue = this.counter;
|
||||
}
|
||||
this.enabled = value;
|
||||
this.updated();
|
||||
}
|
||||
}
|
||||
get mode() {
|
||||
return this.timerMode;
|
||||
}
|
||||
set mode(value) {
|
||||
if (this.timerMode !== value) {
|
||||
const { counter } = this;
|
||||
this.timerMode = value;
|
||||
this.set(counter);
|
||||
}
|
||||
}
|
||||
updated() {
|
||||
for (const listener of this.listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
}
|
||||
export class Timer32PeriodicAlarm {
|
||||
constructor(timer, callback) {
|
||||
this.timer = timer;
|
||||
this.callback = callback;
|
||||
this.targetValue = 0;
|
||||
this.enabled = false;
|
||||
this.handleAlarm = () => {
|
||||
this.callback();
|
||||
if (this.enabled && this.timer.enable) {
|
||||
this.schedule();
|
||||
}
|
||||
};
|
||||
this.update = () => {
|
||||
this.cancel();
|
||||
if (this.enabled && this.timer.enable) {
|
||||
this.schedule();
|
||||
}
|
||||
};
|
||||
this.clockAlarm = this.timer.clock.createAlarm(this.handleAlarm);
|
||||
timer.listeners.push(this.update);
|
||||
}
|
||||
get enable() {
|
||||
return this.enabled;
|
||||
}
|
||||
set enable(value) {
|
||||
if (value !== this.enabled) {
|
||||
this.enabled = value;
|
||||
if (value && this.timer.enable) {
|
||||
this.schedule();
|
||||
}
|
||||
else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
get target() {
|
||||
return this.targetValue;
|
||||
}
|
||||
set target(value) {
|
||||
if (value === this.targetValue) {
|
||||
return;
|
||||
}
|
||||
this.targetValue = value;
|
||||
if (this.enabled && this.timer.enable) {
|
||||
this.cancel();
|
||||
this.schedule();
|
||||
}
|
||||
}
|
||||
schedule() {
|
||||
const { timer, targetValue } = this;
|
||||
const { top, mode, rawCounter } = timer;
|
||||
let cycleDelta = targetValue - rawCounter;
|
||||
if (mode === TimerMode.ZigZag && cycleDelta < 0) {
|
||||
if (cycleDelta < -top) {
|
||||
cycleDelta += 2 * top;
|
||||
}
|
||||
else {
|
||||
cycleDelta = top * 2 - targetValue - rawCounter;
|
||||
}
|
||||
}
|
||||
if (top != 0xffffffff) {
|
||||
if (cycleDelta <= 0) {
|
||||
cycleDelta += top + 1;
|
||||
}
|
||||
if (targetValue > top) {
|
||||
// Skip alarm
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mode === TimerMode.Decrement) {
|
||||
cycleDelta = top + 1 - cycleDelta;
|
||||
}
|
||||
const cyclesToAlarm = cycleDelta >>> 0;
|
||||
const nanosToAlarm = timer.toNanos(cyclesToAlarm);
|
||||
this.clockAlarm.schedule(nanosToAlarm);
|
||||
}
|
||||
cancel() {
|
||||
this.clockAlarm.cancel();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
+31
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+10
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/apex/apex", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var i=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var d=(e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})},g=(e,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of c(t))!l.call(e,o)&&o!==s&&i(e,o,{get:()=>t[o],enumerable:!(a=r(t,o))||a.enumerable});return e};var p=e=>g(i({},"__esModule",{value:!0}),e);var h={};d(h,{conf:()=>m,language:()=>b});var m={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"},{open:"<",close:">"}],folding:{markers:{start:new RegExp("^\\s*//\\s*(?:(?:#?region\\b)|(?:<editor-fold\\b))"),end:new RegExp("^\\s*//\\s*(?:(?:#?endregion\\b)|(?:</editor-fold>))")}}},u=["abstract","activate","and","any","array","as","asc","assert","autonomous","begin","bigdecimal","blob","boolean","break","bulk","by","case","cast","catch","char","class","collect","commit","const","continue","convertcurrency","decimal","default","delete","desc","do","double","else","end","enum","exception","exit","export","extends","false","final","finally","float","for","from","future","get","global","goto","group","having","hint","if","implements","import","in","inner","insert","instanceof","int","interface","into","join","last_90_days","last_month","last_n_days","last_week","like","limit","list","long","loop","map","merge","native","new","next_90_days","next_month","next_n_days","next_week","not","null","nulls","number","object","of","on","or","outer","override","package","parallel","pragma","private","protected","public","retrieve","return","returning","rollback","savepoint","search","select","set","short","sort","stat","static","strictfp","super","switch","synchronized","system","testmethod","then","this","this_month","this_week","throw","throws","today","tolabel","tomorrow","transaction","transient","trigger","true","try","type","undelete","update","upsert","using","virtual","void","volatile","webservice","when","where","while","yesterday"],f=e=>e.charAt(0).toUpperCase()+e.substr(1),n=[];u.forEach(e=>{n.push(e),n.push(e.toUpperCase()),n.push(f(e))});var b={defaultToken:"",tokenPostfix:".apex",keywords:n,operators:["=",">","<","!","~","?",":","==","<=",">=","!=","&&","||","++","--","+","-","*","/","&","|","^","%","<<",">>",">>>","+=","-=","*=","/=","&=","|=","^=","%=","<<=",">>=",">>>="],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,digits:/\d+(_+\d+)*/,octaldigits:/[0-7]+(_+[0-7]+)*/,binarydigits:/[0-1]+(_+[0-1]+)*/,hexdigits:/[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,tokenizer:{root:[[/[a-z_$][\w$]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],[/[A-Z][\w\$]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"type.identifier"}}],{include:"@whitespace"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/@\s*[a-zA-Z_\$][\w\$]*/,"annotation"],[/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/,"number.float"],[/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/,"number.float"],[/(@digits)[fFdD]/,"number.float"],[/(@digits)[lL]?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string",'@string."'],[/'/,"string","@string.'"],[/'[^\\']'/,"string"],[/(')(@escapes)(')/,["string","string.escape","string"]],[/'/,"string.invalid"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*\*(?!\/)/,"comment.doc","@apexdoc"],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],apexdoc:[[/[^\/*]+/,"comment.doc"],[/\*\//,"comment.doc","@pop"],[/[\/*]/,"comment.doc"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}};return p(h);})();
|
||||
return moduleExports;
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/azcli/azcli", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var c=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},k=(t,e,o,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of r(e))!l.call(t,n)&&n!==o&&s(t,n,{get:()=>e[n],enumerable:!(a=i(e,n))||a.enumerable});return t};var p=t=>k(s({},"__esModule",{value:!0}),t);var d={};c(d,{conf:()=>f,language:()=>g});var f={comments:{lineComment:"#"}},g={defaultToken:"keyword",ignoreCase:!0,tokenPostfix:".azcli",str:/[^#\s]/,tokenizer:{root:[{include:"@comment"},[/\s-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}],[/^-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}]],type:[{include:"@comment"},[/-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":"key.identifier"}}],[/@str+\s*/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}]],comment:[[/#.*$/,{cases:{"@eos":{token:"comment",next:"@popall"}}}]]}};return p(d);})();
|
||||
return moduleExports;
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/bat/bat", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var n=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var i=Object.prototype.hasOwnProperty;var g=(o,e)=>{for(var t in e)n(o,t,{get:e[t],enumerable:!0})},c=(o,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of l(e))!i.call(o,s)&&s!==t&&n(o,s,{get:()=>e[s],enumerable:!(a=r(e,s))||a.enumerable});return o};var p=o=>c(n({},"__esModule",{value:!0}),o);var k={};g(k,{conf:()=>d,language:()=>m});var d={comments:{lineComment:"REM"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*(::\\s*|REM\\s+)#region"),end:new RegExp("^\\s*(::\\s*|REM\\s+)#endregion")}}},m={defaultToken:"",ignoreCase:!0,tokenPostfix:".bat",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"delimiter.square",open:"[",close:"]"}],keywords:/call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/,symbols:/[=><!~?&|+\-*\/\^;\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^(\s*)(rem(?:\s.*|))$/,["","comment"]],[/(\@?)(@keywords)(?!\w)/,[{token:"keyword"},{token:"keyword.$2"}]],[/[ \t\r\n]+/,""],[/setlocal(?!\w)/,"keyword.tag-setlocal"],[/endlocal(?!\w)/,"keyword.tag-setlocal"],[/[a-zA-Z_]\w*/,""],[/:\w*/,"metatag"],[/%[^%]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],string:[[/[^\\"'%]+/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/%[\w ]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/$/,"string","@popall"]]}};return p(k);})();
|
||||
return moduleExports;
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/bicep/bicep", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var a=Object.prototype.hasOwnProperty;var g=(e,n)=>{for(var o in n)r(e,o,{get:n[o],enumerable:!0})},l=(e,n,o,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of c(n))!a.call(e,t)&&t!==o&&r(e,t,{get:()=>n[t],enumerable:!(i=s(n,t))||i.enumerable});return e};var m=e=>l(r({},"__esModule",{value:!0}),e);var y={};g(y,{conf:()=>$,language:()=>w});var p=e=>`\\b${e}\\b`,k="[_a-zA-Z]",x="[_a-zA-Z0-9]",u=p(`${k}${x}*`),d=["targetScope","resource","module","param","var","output","for","in","if","existing"],b=["true","false","null"],f="[ \\t\\r\\n]",C="[0-9]+",$={comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'"},{open:"'''",close:"'''"}],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'",notIn:["string","comment"]},{open:"'''",close:"'''",notIn:["string","comment"]}],autoCloseBefore:`:.,=}])'
|
||||
`,indentationRules:{increaseIndentPattern:new RegExp("^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$"),decreaseIndentPattern:new RegExp("^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$")}},w={defaultToken:"",tokenPostfix:".bicep",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"}],symbols:/[=><!~?:&|+\-*/^%]+/,keywords:d,namedLiterals:b,escapes:"\\\\(u{[0-9A-Fa-f]+}|n|r|t|\\\\|'|\\${)",tokenizer:{root:[{include:"@expression"},{include:"@whitespace"}],stringVerbatim:[{regex:"(|'|'')[^']",action:{token:"string"}},{regex:"'''",action:{token:"string.quote",next:"@pop"}}],stringLiteral:[{regex:"\\${",action:{token:"delimiter.bracket",next:"@bracketCounting"}},{regex:"[^\\\\'$]+",action:{token:"string"}},{regex:"@escapes",action:{token:"string.escape"}},{regex:"\\\\.",action:{token:"string.escape.invalid"}},{regex:"'",action:{token:"string",next:"@pop"}}],bracketCounting:[{regex:"{",action:{token:"delimiter.bracket",next:"@bracketCounting"}},{regex:"}",action:{token:"delimiter.bracket",next:"@pop"}},{include:"expression"}],comment:[{regex:"[^\\*]+",action:{token:"comment"}},{regex:"\\*\\/",action:{token:"comment",next:"@pop"}},{regex:"[\\/*]",action:{token:"comment"}}],whitespace:[{regex:f},{regex:"\\/\\*",action:{token:"comment",next:"@comment"}},{regex:"\\/\\/.*$",action:{token:"comment"}}],expression:[{regex:"'''",action:{token:"string.quote",next:"@stringVerbatim"}},{regex:"'",action:{token:"string.quote",next:"@stringLiteral"}},{regex:C,action:{token:"number"}},{regex:u,action:{cases:{"@keywords":{token:"keyword"},"@namedLiterals":{token:"keyword"},"@default":{token:"identifier"}}}}]}};return m(y);})();
|
||||
return moduleExports;
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/cameligo/cameligo", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var c=(o,e)=>{for(var n in e)s(o,n,{get:e[n],enumerable:!0})},m=(o,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of a(e))!l.call(o,t)&&t!==n&&s(o,t,{get:()=>e[t],enumerable:!(r=i(e,t))||r.enumerable});return o};var p=o=>m(s({},"__esModule",{value:!0}),o);var u={};c(u,{conf:()=>d,language:()=>g});var d={comments:{lineComment:"//",blockComment:["(*","*)"]},brackets:[["{","}"],["[","]"],["(",")"],["<",">"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'},{open:"(*",close:"*)"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'},{open:"(*",close:"*)"}]},g={defaultToken:"",tokenPostfix:".cameligo",ignoreCase:!0,brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"},{open:"<",close:">",token:"delimiter.angle"}],keywords:["abs","assert","block","Bytes","case","Crypto","Current","else","failwith","false","for","fun","if","in","let","let%entry","let%init","List","list","Map","map","match","match%nat","mod","not","operation","Operation","of","record","Set","set","sender","skip","source","String","then","to","true","type","with"],typeKeywords:["int","unit","string","tz","nat","bool"],operators:["=",">","<","<=",">=","<>",":",":=","and","mod","or","+","-","*","/","@","&","^","%","->","<-","&&","||"],symbols:/[=><:@\^&|+\-*\/\^%]+/,tokenizer:{root:[[/[a-zA-Z_][\w]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],{include:"@whitespace"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\$[0-9a-fA-F]{1,16}/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'/,"string","@string"],[/'[^\\']'/,"string"],[/'/,"string.invalid"],[/\#\d+/,"string"]],comment:[[/[^\(\*]+/,"comment"],[/\*\)/,"comment","@pop"],[/\(\*/,"comment"]],string:[[/[^\\']+/,"string"],[/\\./,"string.escape.invalid"],[/'/,{token:"string.quote",bracket:"@close",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,"white"],[/\(\*/,"comment","@comment"],[/\/\/.*$/,"comment"]]}};return p(u);})();
|
||||
return moduleExports;
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/coffee/coffee", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var a=Object.prototype.hasOwnProperty;var l=(n,e)=>{for(var t in e)s(n,t,{get:e[t],enumerable:!0})},p=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of g(e))!a.call(n,r)&&r!==t&&s(n,r,{get:()=>e[r],enumerable:!(o=i(e,r))||o.enumerable});return n};var c=n=>p(s({},"__esModule",{value:!0}),n);var m={};l(m,{conf:()=>d,language:()=>x});var d={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\@\#%\^\&\*\(\)\=\$\-\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{blockComment:["###","###"],lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{markers:{start:new RegExp("^\\s*#region\\b"),end:new RegExp("^\\s*#endregion\\b")}}},x={defaultToken:"",ignoreCase:!0,tokenPostfix:".coffee",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"}],regEx:/\/(?!\/\/)(?:[^\/\\]|\\.)*\/[igm]*/,keywords:["and","or","is","isnt","not","on","yes","@","no","off","true","false","null","this","new","delete","typeof","in","instanceof","return","throw","break","continue","debugger","if","else","switch","for","while","do","try","catch","finally","class","extends","super","undefined","then","unless","until","loop","of","by","when"],symbols:/[=><!~?&%|+\-*\/\^\.,\:]+/,escapes:/\\(?:[abfnrtv\\"'$]|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/\@[a-zA-Z_]\w*/,"variable.predefined"],[/[a-zA-Z_]\w*/,{cases:{this:"variable.predefined","@keywords":{token:"keyword.$0"},"@default":""}}],[/[ \t\r\n]+/,""],[/###/,"comment","@comment"],[/#.*$/,"comment"],["///",{token:"regexp",next:"@hereregexp"}],[/^(\s*)(@regEx)/,["","regexp"]],[/(\()(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\,)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\=)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\:)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\[)(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\!)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\&)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\|)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\?)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\{)(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\;)(\s*)(@regEx)/,["","","regexp"]],[/}/,{cases:{"$S2==interpolatedstring":{token:"string",next:"@pop"},"@default":"@brackets"}}],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d+[eE]([\-+]?\d+)?/,"number.float"],[/\d+\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F]+/,"number.hex"],[/0[0-7]+(?!\d)/,"number.octal"],[/\d+/,"number"],[/[,.]/,"delimiter"],[/"""/,"string",'@herestring."""'],[/'''/,"string","@herestring.'''"],[/"/,{cases:{"@eos":"string","@default":{token:"string",next:'@string."'}}}],[/'/,{cases:{"@eos":"string","@default":{token:"string",next:"@string.'"}}}]],string:[[/[^"'\#\\]+/,"string"],[/@escapes/,"string.escape"],[/\./,"string.escape.invalid"],[/\./,"string.escape.invalid"],[/#{/,{cases:{'$S2=="':{token:"string",next:"root.interpolatedstring"},"@default":"string"}}],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/#/,"string"]],herestring:[[/("""|''')/,{cases:{"$1==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/[^#\\'"]+/,"string"],[/['"]+/,"string"],[/@escapes/,"string.escape"],[/\./,"string.escape.invalid"],[/#{/,{token:"string.quote",next:"root.interpolatedstring"}],[/#/,"string"]],comment:[[/[^#]+/,"comment"],[/###/,"comment","@pop"],[/#/,"comment"]],hereregexp:[[/[^\\\/#]+/,"regexp"],[/\\./,"regexp"],[/#.*$/,"comment"],["///[igm]*",{token:"regexp",next:"@pop"}],[/\//,"regexp"]]}};return c(m);})();
|
||||
return moduleExports;
|
||||
});
|
||||
+10
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/csharp/csharp", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},p=(t,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of a(e))!c.call(t,n)&&n!==o&&s(t,n,{get:()=>e[n],enumerable:!(i=r(e,n))||i.enumerable});return t};var g=t=>p(s({},"__esModule",{value:!0}),t);var u={};l(u,{conf:()=>d,language:()=>m});var d={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'",notIn:["string","comment"]},{open:'"',close:'"',notIn:["string","comment"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*#region\\b"),end:new RegExp("^\\s*#endregion\\b")}}},m={defaultToken:"",tokenPostfix:".cs",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"},{open:"<",close:">",token:"delimiter.angle"}],keywords:["extern","alias","using","bool","decimal","sbyte","byte","short","ushort","int","uint","long","ulong","char","float","double","object","dynamic","string","assembly","is","as","ref","out","this","base","new","typeof","void","checked","unchecked","default","delegate","var","const","if","else","switch","case","while","do","for","foreach","in","break","continue","goto","return","throw","try","catch","finally","lock","yield","from","let","where","join","on","equals","into","orderby","ascending","descending","select","group","by","namespace","partial","class","field","event","method","param","public","protected","internal","private","abstract","sealed","static","struct","readonly","volatile","virtual","override","params","get","set","add","remove","operator","true","false","implicit","explicit","interface","enum","null","async","await","fixed","sizeof","stackalloc","unsafe","nameof","when"],namespaceFollows:["namespace","using"],parenFollows:["if","for","while","switch","foreach","using","catch","when"],operators:["=","??","||","&&","|","^","&","==","!=","<=",">=","<<","+","-","*","/","%","!","~","++","--","+=","-=","*=","/=","%=","&=","|=","^=","<<=",">>=",">>","=>"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/\@?[a-zA-Z_]\w*/,{cases:{"@namespaceFollows":{token:"keyword.$0",next:"@namespace"},"@keywords":{token:"keyword.$0",next:"@qualified"},"@default":{token:"identifier",next:"@qualified"}}}],{include:"@whitespace"},[/}/,{cases:{"$S2==interpolatedstring":{token:"string.quote",next:"@pop"},"$S2==litinterpstring":{token:"string.quote",next:"@pop"},"@default":"@brackets"}}],[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/[0-9_]*\.[0-9_]+([eE][\-+]?\d+)?[fFdD]?/,"number.float"],[/0[xX][0-9a-fA-F_]+/,"number.hex"],[/0[bB][01_]+/,"number.hex"],[/[0-9_]+/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/"/,{token:"string.quote",next:"@string"}],[/\$\@"/,{token:"string.quote",next:"@litinterpstring"}],[/\@"/,{token:"string.quote",next:"@litstring"}],[/\$"/,{token:"string.quote",next:"@interpolatedstring"}],[/'[^\\']'/,"string"],[/(')(@escapes)(')/,["string","string.escape","string"]],[/'/,"string.invalid"]],qualified:[[/[a-zA-Z_][\w]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],[/\./,"delimiter"],["","","@pop"]],namespace:[{include:"@whitespace"},[/[A-Z]\w*/,"namespace"],[/[\.=]/,"delimiter"],["","","@pop"]],comment:[[/[^\/*]+/,"comment"],["\\*/","comment","@pop"],[/[\/*]/,"comment"]],string:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,{token:"string.quote",next:"@pop"}]],litstring:[[/[^"]+/,"string"],[/""/,"string.escape"],[/"/,{token:"string.quote",next:"@pop"}]],litinterpstring:[[/[^"{]+/,"string"],[/""/,"string.escape"],[/{{/,"string.escape"],[/}}/,"string.escape"],[/{/,{token:"string.quote",next:"root.litinterpstring"}],[/"/,{token:"string.quote",next:"@pop"}]],interpolatedstring:[[/[^\\"{]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/{{/,"string.escape"],[/}}/,"string.escape"],[/{/,{token:"string.quote",next:"root.interpolatedstring"}],[/"/,{token:"string.quote",next:"@pop"}]],whitespace:[[/^[ \t\v\f]*#((r)|(load))(?=\s)/,"directive.csx"],[/^[ \t\v\f]*#\w.*$/,"namespace.cpp"],[/[ \t\v\f\r\n]+/,""],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]]}};return g(u);})();
|
||||
return moduleExports;
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*!-----------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
|
||||
* Released under the MIT license
|
||||
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||
*-----------------------------------------------------------------------------*/
|
||||
define("vs/basic-languages/csp/csp", ["require","require"],(require)=>{
|
||||
"use strict";var moduleExports=(()=>{var o=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var a=(r,t)=>{for(var s in t)o(r,s,{get:t[s],enumerable:!0})},c=(r,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of u(t))!g.call(r,e)&&e!==s&&o(r,e,{get:()=>t[e],enumerable:!(n=i(t,e))||n.enumerable});return r};var q=r=>c(o({},"__esModule",{value:!0}),r);var p={};a(p,{conf:()=>f,language:()=>l});var f={brackets:[],autoClosingPairs:[],surroundingPairs:[]},l={keywords:[],typeKeywords:[],tokenPostfix:".csp",operators:[],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/child-src/,"string.quote"],[/connect-src/,"string.quote"],[/default-src/,"string.quote"],[/font-src/,"string.quote"],[/frame-src/,"string.quote"],[/img-src/,"string.quote"],[/manifest-src/,"string.quote"],[/media-src/,"string.quote"],[/object-src/,"string.quote"],[/script-src/,"string.quote"],[/style-src/,"string.quote"],[/worker-src/,"string.quote"],[/base-uri/,"string.quote"],[/plugin-types/,"string.quote"],[/sandbox/,"string.quote"],[/disown-opener/,"string.quote"],[/form-action/,"string.quote"],[/frame-ancestors/,"string.quote"],[/report-uri/,"string.quote"],[/report-to/,"string.quote"],[/upgrade-insecure-requests/,"string.quote"],[/block-all-mixed-content/,"string.quote"],[/require-sri-for/,"string.quote"],[/reflected-xss/,"string.quote"],[/referrer/,"string.quote"],[/policy-uri/,"string.quote"],[/'self'/,"string.quote"],[/'unsafe-inline'/,"string.quote"],[/'unsafe-eval'/,"string.quote"],[/'strict-dynamic'/,"string.quote"],[/'unsafe-hashed-attributes'/,"string.quote"]]}};return q(p);})();
|
||||
return moduleExports;
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user