stm32f4-emu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ // Minimal starter virtual-component library, built on the public
2
+ // pin/register-access API from emulator.js (`emu.pin()`, `emu.watchPin()`,
3
+ // `emu.read32()`, `emu.i2cRegfile()`). No imports: works in Node and the
4
+ // browser, same "import-free" convention as emulator.js.
5
+ //
6
+ // These are templates, not a full component catalog — attach your own
7
+ // devices the same way (see docs/components.md).
8
+
9
+ // LED wired to a GPIO pin the guest drives as output.
10
+ export class LED {
11
+ constructor(emu, port, num, { activeLow = false } = {}) {
12
+ this.emu = emu;
13
+ this.port = port;
14
+ this.num = num;
15
+ this.activeLow = activeLow;
16
+ this._pin = emu.pin(port, num);
17
+ this._unwatch = null;
18
+ }
19
+
20
+ get value() {
21
+ const raw = this._pin.read();
22
+ return this.activeLow ? !raw : raw;
23
+ }
24
+
25
+ watch(callback) {
26
+ this.unwatch();
27
+ this._unwatch = this.emu.watchPin(this.port, this.num, (raw) => {
28
+ callback(this.activeLow ? !raw : raw);
29
+ });
30
+ return this;
31
+ }
32
+
33
+ unwatch() {
34
+ if (this._unwatch) { this._unwatch(); this._unwatch = null; }
35
+ }
36
+ }
37
+
38
+ // Button that drives a GPIO pin as input into the guest (press/release).
39
+ // activeLow=true (default) models a pull-up button: idle=high, pressed=low.
40
+ export class Button {
41
+ constructor(emu, port, num, { activeLow = true } = {}) {
42
+ this.emu = emu;
43
+ this.activeLow = activeLow;
44
+ this._pin = emu.pin(port, num);
45
+ this._pin.write(activeLow); // idle level
46
+ }
47
+
48
+ press() { this._pin.write(!this.activeLow); }
49
+ release() { this._pin.write(this.activeLow); }
50
+ }
51
+
52
+ // STM32F407 general-purpose/advanced timer base addresses (RM0090).
53
+ const TIM_BASE = {
54
+ TIM1: 0x40010000, TIM2: 0x40000000, TIM3: 0x40000400, TIM4: 0x40000800,
55
+ TIM5: 0x40000C00, TIM6: 0x40001000, TIM7: 0x40001400, TIM8: 0x40010400,
56
+ TIM9: 0x40014000, TIM10: 0x40014400, TIM11: 0x40014800,
57
+ TIM12: 0x40001800, TIM13: 0x40001C00, TIM14: 0x40002000,
58
+ };
59
+
60
+ // Default timer clock at the standard 168 MHz F407 configuration: APB2
61
+ // timers (TIM1/8/9/10/11) tick at 168 MHz, APB1 timers at 84 MHz. Pass
62
+ // `clockHz` explicitly if your firmware clocks the buses differently.
63
+ const TIM_APB2 = new Set(['TIM1', 'TIM8', 'TIM9', 'TIM10', 'TIM11']);
64
+ const timDefaultClock = (timer) => (TIM_APB2.has(timer) ? 168e6 : 84e6);
65
+
66
+ // Read-only PWM observer for one output-compare channel (1-4) of a timer:
67
+ // decodes CR1/CCER/PSC/ARR/CCRn via emu.read32, the same approach
68
+ // emulator.js's built-in buzzer device uses internally, generalized to any
69
+ // timer/channel. Base for a servo-angle or PWM-dimmed-LED component — map
70
+ // `.duty` (0-1) to whatever range your virtual device needs.
71
+ export class Pwm {
72
+ constructor(emu, timer, channel = 1, { clockHz } = {}) {
73
+ const base = TIM_BASE[timer];
74
+ if (base === undefined) throw new Error(`Pwm: unknown timer '${timer}'`);
75
+ if (!Number.isInteger(channel) || channel < 1 || channel > 4) {
76
+ throw new Error(`Pwm: channel must be 1-4, got ${channel}`);
77
+ }
78
+ this.emu = emu;
79
+ this.timer = timer;
80
+ this.base = base;
81
+ this.channel = channel;
82
+ this.clockHz = clockHz ?? timDefaultClock(timer);
83
+ }
84
+
85
+ _regs() {
86
+ const r = (off) => this.emu.read32(this.base + off);
87
+ return {
88
+ cr1: r(0x00), ccer: r(0x20), psc: r(0x28), arr: r(0x2C),
89
+ ccr: r(0x34 + (this.channel - 1) * 4),
90
+ };
91
+ }
92
+
93
+ get freq() {
94
+ const { cr1, psc, arr } = this._regs();
95
+ if (!(cr1 & 1) || arr === 0 || arr >= 0xFFFFFF) return 0;
96
+ const div = (psc + 1) * (arr + 1);
97
+ return div > 0 ? this.clockHz / div : 0;
98
+ }
99
+
100
+ get duty() {
101
+ const { cr1, ccer, arr, ccr } = this._regs();
102
+ const ccEnabled = (ccer >> ((this.channel - 1) * 4)) & 1;
103
+ if (!(cr1 & 1) || !ccEnabled || arr === 0) return 0;
104
+ return ccr / (arr + 1);
105
+ }
106
+ }
107
+
108
+ // Wraps an I2C register-file device already registered via the
109
+ // `ext_devices.regfile` construction option (i2c_register_regfile must run
110
+ // before init() — see docs/components.md). `peripheral` must match the
111
+ // config's `peripheral` field. The DS3231 RTC in emulator.js is a built-in
112
+ // example of this same pattern; this is the generic, embedder-usable form.
113
+ export class I2cRegisterDevice {
114
+ constructor(emu, peripheral) {
115
+ this._regs = emu.i2cRegfile(peripheral);
116
+ }
117
+
118
+ get(offset) { return this._regs.get(offset); }
119
+ set(offset, value) { this._regs.set(offset, value); }
120
+ }
121
+
122
+ // Drives an ADC channel's value (0-4095, or a {min,max}-mapped range) via
123
+ // emu.setAdcChannel/clearAdcChannel — live, any time, unlike the SPI/I2C
124
+ // devices above (no "before init()" constraint; see docs/components.md).
125
+ // Without an override the channel falls back to the emulator's synthetic
126
+ // temp/vref/vbat/random defaults.
127
+ export class Potentiometer {
128
+ constructor(emu, peripheral, channel, { min = 0, max = 4095 } = {}) {
129
+ this.emu = emu;
130
+ this.peripheral = peripheral;
131
+ this.channel = channel;
132
+ this.min = min;
133
+ this.max = max;
134
+ this._value = min;
135
+ }
136
+
137
+ get value() { return this._value; }
138
+
139
+ set value(v) {
140
+ this._value = Math.min(this.max, Math.max(this.min, v));
141
+ const raw = Math.round(((this._value - this.min) / (this.max - this.min || 1)) * 4095);
142
+ this.emu.setAdcChannel(this.peripheral, this.channel, raw);
143
+ }
144
+
145
+ release() { this.emu.clearAdcChannel(this.peripheral, this.channel); }
146
+ }