Skip to content

Commit ce6dcaa

Browse files
committed
docs: design record for the Trigger Oscilloscope on Mazduino boards
Notes on what rusEFI's Trigger Oscilloscope actually does, how it differs from the Primary Tooth and Composite loggers, what it costs in flash/RAM/CPU, and what a board revision would have to provide (an ADC-capable trigger input). Nothing is implemented - this records the findings so the next board revision can be decided with the numbers in hand. References are to ext/rusefi/firmware at the submodule revision in this tree.
1 parent 509e9ea commit ce6dcaa

1 file changed

Lines changed: 272 additions & 0 deletions

File tree

‎docs/trigger-scope.md‎

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Trigger Oscilloscope on Mazduino boards
2+
3+
Implementation notes for bringing rusEFI's Trigger Oscilloscope up on Mazduino
4+
hardware. Nothing here is implemented yet — this is a design record of what the
5+
upstream feature does, what it costs, and what a board revision would have to
6+
provide.
7+
8+
Everything below was read out of `ext/rusefi/firmware` at the submodule revision
9+
in this tree. File and line references are to that copy.
10+
11+
---
12+
13+
## 1. What it is, and why the existing loggers are not it
14+
15+
rusEFI ships three trigger diagnostics. They are usually confused with each
16+
other, so the distinction matters when deciding whether this is worth the board
17+
change:
18+
19+
| Logger | TS name | What it records | Needs |
20+
|---|---|---|---|
21+
| Primary tooth | "Primary Tooth Time Logger" | one timestamp per rising edge | nothing extra |
22+
| Composite | "Composite Logger" | a timestamp per *edge*, plus level/sync/TDC flags | nothing extra |
23+
| **Trigger scope** | "Trigger Oscilloscope" | the **raw analog voltage** on the trigger line, sampled continuously | an ADC-capable input |
24+
25+
The first two are digital: they tell you *when* the ECU's own comparator decided
26+
an edge happened. If the comparator is misbehaving, they tell you nothing about
27+
why — a VR signal that is too weak to cross the threshold at cranking, ringing
28+
on a long unshielded run, or a Hall sensor with a slow rise time all look the
29+
same from a digital logger: a missing or spurious tooth.
30+
31+
The trigger scope samples the conditioned analog waveform, so you see amplitude,
32+
shape and noise. That is what makes it worth the hardware, and also why it
33+
cannot be retrofitted in firmware alone.
34+
35+
---
36+
37+
## 2. Why it does not work on Mazduino today
38+
39+
`trigger_scope.cpp` is wrapped entirely in `#ifdef TRIGGER_SCOPE`
40+
(`firmware/console/binary/trigger_scope.cpp:7`). The define comes from the
41+
board's own makefile — on AlphaX 4chan, `board.mk:24`:
42+
43+
```make
44+
DDEFS += -DTRIGGER_SCOPE
45+
```
46+
47+
No Mazduino board defines it, and none has a `trigger_scope_config.h`. So
48+
`initTriggerScope()` is never compiled in, and the `TS_TRIGGER_SCOPE_ENABLE`
49+
case inside `TS_SET_LOGGER_SWITCH` is compiled out too — the firmware answers
50+
the start command with a NAK.
51+
52+
**The generated INI is misleading here.** `tunerstudio.template.ini` emits the
53+
`loggerDef = triggerScope, "Trigger Oscilloscope", csv` block unconditionally,
54+
so every Mazduino INI advertises a logger the firmware cannot run. TunerStudio
55+
will list it and fail. Consider suppressing that block for boards without the
56+
define when this is picked up.
57+
58+
---
59+
60+
## 3. Protocol, for whoever writes the app side
61+
62+
The scope rides the same `TS_SET_LOGGER_SWITCH` ('l', `0x6C`) command as the
63+
tooth and composite loggers, with a different sub-command byte:
64+
65+
| Action | Bytes | Constant |
66+
|---|---|---|
67+
| Start | `6C 04` | `TS_TRIGGER_SCOPE_ENABLE` |
68+
| Stop | `6C 05` | `TS_TRIGGER_SCOPE_DISABLE` |
69+
| Read | `6C 06` | `TS_TRIGGER_SCOPE_READ` |
70+
71+
Wrapped in the usual TS CRC frame. `dataReadyCondition` is the
72+
`triggerScopeReady` output channel; reading before it is set answers
73+
`TS_RESPONSE_OUT_OF_RANGE` (`0x84`).
74+
75+
The response payload is a flat array of 2-byte records, no header or footer:
76+
77+
```
78+
recordDef = 0, 0, 2
79+
recordField = channel1, <name>, bit 0, 8 bits, scale 6.6/255, "v"
80+
recordField = channel2, <name>, bit 8, 8 bits, scale 6.6/255, "v"
81+
```
82+
83+
So byte 0 is channel 1, byte 1 is channel 2, interleaved for the whole buffer.
84+
Samples are **8-bit** (the ADC runs in 8-bit mode, `ADC_CR1_RES_1`), and the
85+
buffer is `BIG_BUFFER_SIZE` = 8192 bytes → 4096 sample pairs per read.
86+
87+
The channel names come from the board's `prepend.txt`, e.g. AlphaX 4chan:
88+
89+
```c
90+
#define TS_TRIGGER_SCOPE_CHANNEL_1_NAME "C2/C3 Crank VR"
91+
#define TS_TRIGGER_SCOPE_CHANNEL_2_NAME "E5/E6 Cam VR"
92+
```
93+
94+
---
95+
96+
## 4. The hardware requirement
97+
98+
### 4.1 The scale factor tells you the divider
99+
100+
The INI scales a sample by `6.6 / 255`. An 8-bit full-scale reading is 255,
101+
which the ECU reports as 6.6 V, while the pin itself can only see VREF = 3.3 V.
102+
That is a **2:1 divider in front of the ADC pin**: 6.6 V at the input becomes
103+
3.3 V at the pin.
104+
105+
If a Mazduino revision uses a different divider, the `6.6` in the INI has to
106+
change to match, or every reading is wrong by that ratio. It is a per-board
107+
constant baked into the generated INI, so it belongs with the board's other
108+
scaling values.
109+
110+
### 4.2 What has to reach the pin
111+
112+
The scope taps the signal **after** protection and conditioning but **before**
113+
(or in parallel with) the digital comparator. Wanted:
114+
115+
- Divider to put the working range inside 0–3.3 V at the pin (2:1 for the 6.6 V
116+
scale above).
117+
- Clamp diodes to 3.3 V and GND. A VR sensor at high RPM swings far beyond this
118+
and will otherwise destroy the pin.
119+
- Series resistor between the clamp and the pin to limit clamp current.
120+
- Keep the existing conditioning path intact — the scope is a passive observer,
121+
the decoder still runs off the comparator output.
122+
123+
A VR sensor is bipolar and swings negative. The AlphaX inputs are named
124+
`IN_RES*`, which on Hellen boards are biased analog inputs, so the negative half
125+
of the swing is lifted into range by the bias network. A Mazduino tap would need
126+
the same treatment: bias the signal to roughly mid-rail so both halves of the VR
127+
waveform are visible, otherwise the negative half clips to 0 V and the trace is
128+
useless for judging symmetry.
129+
130+
### 4.3 The pin must be on the ADC
131+
132+
On `mazduino-core` the trigger input is `Gpio::C6`
133+
(`boards/mazduino-core/board_configuration.cpp:89`). **PC6 has no ADC channel on
134+
STM32F4** — it is a digital-only pin. So this cannot be done by re-declaring an
135+
existing pin; the conditioned signal has to be routed to a spare ADC-capable pin
136+
as a second net. That is a board respin, not a firmware change.
137+
138+
ADC-capable pins on the STM32F4 in use: PA0–PA7, PB0–PB1, PC0–PC5, and PF3–PF10
139+
on the 144-pin parts.
140+
141+
---
142+
143+
## 5. The blocker: ADC3 is already spoken for
144+
145+
This is the part that decides the design, and it is specific to Mazduino.
146+
147+
Upstream puts the scope on ADC3:
148+
149+
```c
150+
#define TRIGGER_SCOPE_ADC ADCD3 // trigger_scope_config.h
151+
```
152+
153+
Mazduino puts **software knock** on ADC3 (`boards/mazduino-core/knock_config.h`):
154+
155+
```c
156+
#define KNOCK_ADC ADCD3
157+
#define KNOCK_PIN_CH1 Gpio::F4 // ADC_CHANNEL_IN14
158+
#define KNOCK_PIN_CH2 Gpio::F5 // ADC_CHANNEL_IN15
159+
```
160+
161+
Upstream already treats these as mutually exclusive — `startSampling()` and
162+
`initTriggerScope()` both bail out when `enableSoftwareKnock` is set, with the
163+
comment *"Trigger scope and knock currently mutually exclusive"*. They also
164+
compete for the same 8 KB scratch area: `BigBufferUser` has separate
165+
`TriggerScope` and `KnockSpectrogram` entries, and only one holder at a time.
166+
167+
Three ways out, in increasing order of effort:
168+
169+
1. **Accept the exclusivity.** Scope works only with software knock disabled.
170+
Cheapest, and matches upstream. Knock is not something you need while
171+
diagnosing a trigger that will not sync, so in practice the conflict is
172+
mild — but it must be surfaced in the UI, or the feature will read as broken
173+
to anyone with knock enabled.
174+
2. **Put the scope on a different ADC unit** (ADC1/ADC2), if the chosen pin's
175+
channel is reachable from it and the DMA stream is free. Needs care: ADC1 is
176+
busy with the normal sensor scan. Check `STM32_ADC_USE_ADC*` in `mcuconf.h`
177+
and the DMA stream map before committing.
178+
3. **Share ADC3 by time-slicing.** Not worth it — both features want continuous
179+
DMA into a large buffer.
180+
181+
Recommendation: option 1 for a first cut, and pick the scope pin on ADC3
182+
(PF3–PF10 range, next to the existing knock pins) so no ADC rework is needed.
183+
184+
---
185+
186+
## 6. Sample rate and capture window
187+
188+
From `trigger_scope_config.h`, `TRIGGER_SCOPE_SAMPLE_TIME = ADC_SAMPLE_144`, in
189+
8-bit mode, with two channels in the scan group.
190+
191+
On STM32F4, one conversion takes `SMP + resolution` ADC clocks, where the
192+
8-bit resolution costs 8 cycles. So per channel pair:
193+
194+
```
195+
cycles_per_scan = 2 * (144 + 8) = 304
196+
f_scan = f_adc / 304
197+
```
198+
199+
`f_adc` is `PCLK2 / STM32_ADC_ADCPRE`. With PCLK2 = 84 MHz and a /4 prescaler
200+
that is 21 MHz, giving roughly:
201+
202+
```
203+
f_scan ≈ 21e6 / 304 ≈ 69 kSa/s per channel
204+
window = 4096 scans / 69 kSa/s ≈ 59 ms
205+
```
206+
207+
**Verify `STM32_ADC_ADCPRE` for the target board before quoting these numbers** —
208+
they move directly with the prescaler. The knock config in this repo derives its
209+
rate the same way and is a good template:
210+
211+
```c
212+
#define KNOCK_SAMPLE_RATE (STM32_PCLK2 / (4 * (84 + 12)))
213+
```
214+
215+
~59 ms is about 3 crank revolutions at 3000 rpm, or half a revolution at
216+
cranking speed. Enough to see the gap and judge waveform shape; not enough for a
217+
long-term noise hunt. `triggerScopeGetBuffer()` re-arms the next capture 10 ms
218+
after each read, so a continuous read loop gives a repeating snapshot, not a
219+
gapless recording.
220+
221+
---
222+
223+
## 7. Firmware checklist
224+
225+
Once the hardware exists:
226+
227+
1. `boards/<board>/trigger_scope_config.h`:
228+
```c
229+
#define TRIGGER_SCOPE_ADC ADCD3
230+
#define TRIGGER_SCOPE_SAMPLE_TIME ADC_SAMPLE_144
231+
#define TRIGGER_SCOPE_PIN_CH1 Gpio::Fx
232+
#define TRIGGER_SCOPE_ADC_CH1 ADC_CHANNEL_INxx
233+
#define TRIGGER_SCOPE_HAS_CH2 true // cam channel, optional
234+
#define TRIGGER_SCOPE_PIN_CH2 Gpio::Fy
235+
#define TRIGGER_SCOPE_ADC_CH2 ADC_CHANNEL_INyy
236+
```
237+
2. `boards/<board>/board.mk`: `DDEFS += -DTRIGGER_SCOPE`
238+
3. `boards/<board>/prepend.txt`: set `TS_TRIGGER_SCOPE_CHANNEL_1_NAME` and
239+
`..._2_NAME` to the real connector pin labels — these are what the user sees
240+
as the trace legend.
241+
4. If the divider is not 2:1, change the `6.6` scale in the INI template for
242+
this board.
243+
5. Decide and document the knock interaction. `enableSoftwareKnock` silently
244+
disables the scope today; if that stays, the INI should gate the logger on
245+
it so TunerStudio does not offer a dead button.
246+
247+
Note `adcConvGroupCh1` hardcodes two channels in `ADC_SQR3`. A single-channel
248+
board still works but wastes half the buffer on a dead channel — worth a small
249+
patch upstream-side if only one channel is fitted.
250+
251+
---
252+
253+
## 8. App side (MTuneX)
254+
255+
Not implemented, and deliberately so: adding a button that NAKs on every board
256+
currently shipped would only confuse people. When a board gains the hardware:
257+
258+
- The commands are three fixed 2-byte payloads (section 3), so `TsProtocol`
259+
needs one more builder alongside the tooth/composite ones.
260+
- Gate the menu entry on the ECU actually supporting it. The INI's `loggerDef`
261+
is not a reliable signal today (section 2); until that is fixed, gate on the
262+
board name or on a probe that tolerates a NAK.
263+
- The existing Tooth Logger screen already has a mode selector and a raw-data
264+
table; a scope trace is a third mode rather than a new screen.
265+
266+
---
267+
268+
## 9. Speeduino
269+
270+
No equivalent. Speeduino has the tooth logger and three composite variants, all
271+
digital edge logging off the same interrupt. There is no ADC path on the trigger
272+
input and no scope command in its serial protocol.

0 commit comments

Comments
 (0)