Skip to content

Commit b75c3f3

Browse files
committed
fkclaude: prepare two more upstream PRs, neither submitted
Both are staged for review before anything is sent. ci-node24-actions, for oltaco's bootloader repo: the four-line workflow bump onto actions that declare node24. Already proven by running a release on the fork - 14 boards, 42 assets, and the "Node 20 is deprecated" annotation gone from every job. adafruit-touch2400-uf2, for adafruit/Adafruit_TinyUSB_Arduino: rather than changing what touch 1200 means, this adds touch 2400 as a request for the bootloader's mass storage mode, with a weak default so no port is obliged to implement it and touch 1200 is untouched everywhere. The description argues the case in full, including why flipping TinyUSB_Port_EnterDFU() was tried and rejected, the measured fact that MSC does not interfere with a serial DFU transfer, and the one hazard of the chosen baud rate with two alternatives offered. It states plainly that the 2400 path itself has not yet been run on hardware.
1 parent 2490e19 commit b75c3f3

4 files changed

Lines changed: 206 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
diff --git a/src/arduino/Adafruit_TinyUSB_API.cpp b/src/arduino/Adafruit_TinyUSB_API.cpp
2+
index 2f27632..f8c7d72 100644
3+
--- a/src/arduino/Adafruit_TinyUSB_API.cpp
4+
+++ b/src/arduino/Adafruit_TinyUSB_API.cpp
5+
@@ -33,6 +33,12 @@ extern "C" {
6+
7+
uint32_t tusb_time_millis_api(void) { return millis(); }
8+
9+
+// Ports whose bootloader has no separate mass storage mode fall back to the plain DFU
10+
+// entry, so touch 2400 degrades to exactly what touch 1200 already does there.
11+
+__attribute__((weak)) void TinyUSB_Port_EnterMassStorageDFU(void) {
12+
+ TinyUSB_Port_EnterDFU();
13+
+}
14+
+
15+
//--------------------------------------------------------------------+
16+
// Device
17+
//--------------------------------------------------------------------+
18+
diff --git a/src/arduino/Adafruit_TinyUSB_API.h b/src/arduino/Adafruit_TinyUSB_API.h
19+
index 77826a6..f3bf6b4 100644
20+
--- a/src/arduino/Adafruit_TinyUSB_API.h
21+
+++ b/src/arduino/Adafruit_TinyUSB_API.h
22+
@@ -82,6 +82,13 @@ void TinyUSB_Device_FlushCDC(void) __attribute__((weak));
23+
// usually when host disconnects cdc at baud 1200 (touch 1200)
24+
void TinyUSB_Port_EnterDFU(void);
25+
26+
+// To enter/reboot to the bootloader's mass storage (UF2) mode, for ports whose
27+
+// bootloader has one distinct from the plain DFU entry above.
28+
+// Invoked when host disconnects cdc at baud 2400 (touch 2400).
29+
+// Optional: a weak default forwards to TinyUSB_Port_EnterDFU(), so a port without a
30+
+// separate mass storage mode behaves exactly as it does today and need not implement it.
31+
+void TinyUSB_Port_EnterMassStorageDFU(void);
32+
+
33+
// Init device hardware.
34+
// Called by TinyUSB_Device_Init()
35+
void TinyUSB_Port_InitDevice(uint8_t rhport);
36+
diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
37+
index 11c5673..0fb8cc2 100644
38+
--- a/src/arduino/Adafruit_USBD_CDC.cpp
39+
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
40+
@@ -280,6 +280,8 @@ void tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) {
41+
42+
if (coding.bit_rate == 1200) {
43+
TinyUSB_Port_EnterDFU();
44+
+ } else if (coding.bit_rate == 2400) {
45+
+ TinyUSB_Port_EnterMassStorageDFU();
46+
}
47+
}
48+
}
49+
diff --git a/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp b/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp
50+
index a5558b8..83a779f 100644
51+
--- a/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp
52+
+++ b/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp
53+
@@ -97,6 +97,12 @@ void TinyUSB_Port_EnterDFU(void) {
54+
enterSerialDfu();
55+
}
56+
57+
+void TinyUSB_Port_EnterMassStorageDFU(void) {
58+
+ // Reset to the bootloader with mass storage exposed, so a host can drop in a .uf2.
59+
+ // Both modes keep CDC, so serial DFU still works either way - this only adds the drive.
60+
+ enterUf2Dfu();
61+
+}
62+
+
63+
uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) {
64+
uint32_t *serial_32 = (uint32_t *)serial_id;
65+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Add touch 2400 to request the bootloader's mass storage (UF2) mode
2+
3+
**Target:** `adafruit/Adafruit_TinyUSB_Arduino`, base `master`
4+
**Branch:** `feature/touch2400-enter-uf2-dfu` — 4 files, +21/−0, no deletions
5+
**Status:** prepared, not submitted. Not yet verified on hardware (see the last section).
6+
7+
## The problem
8+
9+
A host that cannot reach the board physically has no way to ask for the mass storage side of the
10+
bootloader. Touch 1200 is the only remote trigger this library exposes, and on nRF52 it lands in
11+
serial-only DFU (CDC) — never the UF2 drive (CDC + MSC).
12+
13+
That gap has a concrete consequence: **UF2 is the only route that can carry a bootloader update.** A
14+
DFU zip cannot, because that path requires an application manifest. So a remotely deployed nRF52
15+
node — in our case reached over a USB/IP bridge, with nobody near the board — can have its
16+
application updated but never its bootloader, purely because the mass storage mode is unreachable
17+
without a physical double tap.
18+
19+
## What this changes
20+
21+
Three small additions and one nRF52 implementation:
22+
23+
- `TinyUSB_Port_EnterMassStorageDFU()`, a new **optional** port hook, invoked when the host
24+
disconnects CDC at **2400** baud.
25+
- A **weak default** forwarding it to `TinyUSB_Port_EnterDFU()`. This follows the pattern already
26+
used in the same header for `TinyUSB_Device_Init`, `TinyUSB_Device_Task` and
27+
`TinyUSB_Device_FlushCDC`, so it is not a new mechanism here.
28+
- The nRF52 port implements it with `enterUf2Dfu()`, which the core already provides right next to
29+
`enterSerialDfu()` in `wiring.h`. Nothing new is invented; an existing entry point is exposed.
30+
31+
**Touch 1200 is not modified on any port.** Existing tools, the Arduino/PlatformIO upload path, and
32+
every board's current behaviour are untouched. Ports whose bootloader has no distinct mass storage
33+
mode need no change at all — the weak default makes touch 2400 degrade to exactly what touch 1200
34+
already does there. Of the five ports in the tree, only `nrf` gains a distinct behaviour:
35+
36+
| port | `EnterDFU` | overrides the new hook |
37+
|---|---|---|
38+
| nrf | yes | **yes**`enterUf2Dfu()` |
39+
| rp2040, samd, ch32, esp32 | yes | no — falls back via the weak default |
40+
41+
## Why not simply change what touch 1200 does
42+
43+
That was the first thing we tried, as a local patch: make `TinyUSB_Port_EnterDFU()` call
44+
`enterUf2Dfu()`. It works, and we rejected it for two reasons.
45+
46+
**It changes the normal upload path for everybody.** The 1200-baud touch *is* how the IDE uploads, so
47+
every upload on every sketch built with the core would start mounting a removable drive. On Windows
48+
that means a drive letter, an Explorer window, and an indexer or antivirus scanning a freshly
49+
appeared FAT volume — while a serial DFU transfer is in flight. Nuisance at best, and not something
50+
to inflict on users who never asked for it.
51+
52+
**It removes the choice.** With a second baud rate the *host* decides, per touch, which mode it wants.
53+
Nothing has to be rebuilt to switch intent, and a tool that only knows 1200 keeps working.
54+
55+
For completeness, we also measured whether the mass storage interface interferes with a serial DFU
56+
transfer, since that would have been the stronger objection to either approach: it does not. A full
57+
serial DFU flash completed normally with MSC mounted — 28.33 s, 928 progress marks, `Device
58+
programmed.` on an nRF52840 already in UF2 mode. So the argument against changing the default is
59+
about nuisance, not breakage.
60+
61+
## The choice of 2400, and its one hazard
62+
63+
2400 is proposed because it is archaic enough to be unused for real data, which is the same reason
64+
1200 was chosen. The hazard is the same one 1200 already has: **open a serial monitor at 2400 baud
65+
and close it, and the device reboots into UF2 mode.** Recoverable (a reset returns to the
66+
application) but surprising.
67+
68+
If you would rather avoid that, the patch is trivially adaptable and we have no attachment to the
69+
value:
70+
71+
- a different rate, or a deliberately odd one such as 1201, which no terminal offers by default;
72+
- or gate the whole thing behind a build flag, so only firmware that wants it responds at all.
73+
74+
Say which you prefer and it will be changed — the mechanism is the point, not the number.
75+
76+
## What is and is not verified
77+
78+
Verified: the patch applies cleanly to `master`, the weak-default pattern matches existing usage in
79+
the same header, and `enterUf2Dfu()` is already declared in the nRF52 core's public `wiring.h`
80+
alongside `enterSerialDfu()`, so the nRF52 implementation compiles against what the core already
81+
exposes.
82+
83+
The 2400 path has **not** yet been exercised on hardware. What has been measured, on an nRF52840
84+
ProMicro and a XIAO nRF52840 Sense, is the behaviour on both sides of it: a 1200-baud touch reaching
85+
serial-only DFU (CDC, no mass storage), and a device in UF2 mode (CDC + MSC) accepting a full serial
86+
DFU flash. Happy to run the 2400 path on both boards before you spend time on this — say so and the
87+
result will be posted here.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
diff --git a/.github/workflows/githubci.yml b/.github/workflows/githubci.yml
2+
index c65812b..ff60858 100644
3+
--- a/.github/workflows/githubci.yml
4+
+++ b/.github/workflows/githubci.yml
5+
@@ -15,7 +15,7 @@ jobs:
6+
matrix: ${{ steps.set-matrix.outputs.matrix }}
7+
steps:
8+
- name: Checkout Code
9+
- uses: actions/checkout@v4
10+
+ uses: actions/checkout@v7
11+
12+
- name: Set matrix
13+
id: set-matrix
14+
@@ -34,7 +34,7 @@ jobs:
15+
board: ${{ fromJSON(needs.set-matrix.outputs.matrix) }}
16+
steps:
17+
- name: Checkout Code
18+
- uses: actions/checkout@v4
19+
+ uses: actions/checkout@v7
20+
with:
21+
fetch-depth: 0
22+
submodules: true
23+
@@ -53,13 +53,13 @@ jobs:
24+
make BOARD=${{ matrix.board }} all
25+
make BOARD=${{ matrix.board }} copy-artifact
26+
27+
- - uses: actions/upload-artifact@v4
28+
+ - uses: actions/upload-artifact@v7
29+
with:
30+
name: ${{ matrix.board }}
31+
path: _bin/${{ matrix.board }}
32+
33+
- name: Upload Release Asset
34+
- uses: softprops/action-gh-release@v1
35+
+ uses: softprops/action-gh-release@v3
36+
if: ${{ github.event_name == 'release' }}
37+
with:
38+
files: |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ci: move the release build onto actions that target Node 24
2+
3+
Every job in the last release build carries the same warning: checkout@v4 and upload-artifact@v4
4+
declare node20 and action-gh-release@v1 declares node16, so the runner is forcing all three onto
5+
Node 24. That fallback works today, but when it is withdrawn - as it was for Node 12 and Node 16 -
6+
the steps will not start at all. Since the push and pull_request triggers are commented out, the
7+
first sign of it would be a release that produces no assets.
8+
9+
Bumped to the current majors that declare node24: checkout v7, upload-artifact v7,
10+
action-gh-release v3. Worth knowing that the Node version does not track the major cleanly -
11+
upload-artifact v5 is still node20, so v6 is the first that helps. Every input this workflow relies
12+
on (name, path, fetch-depth, submodules, files) exists unchanged in those majors.
13+
14+
Tested by running a release on a fork: all 14 boards built and all 42 assets were attached, and the
15+
"Node 20 is deprecated" annotation is gone from every job.
16+

0 commit comments

Comments
 (0)