Skip to content

Commit 3cf0eed

Browse files
authored
fix(teensy): auto-link Teensyduino CMSIS-DSP per-MCU (#300) (#313)
Mirrors PlatformIO+Teensyduino's SCons builder, which auto-appends the right libarm_cortex*_math.a to the link command based on MCU. Without this, FastLED's Ports/PJRCSpectrumAnalyzer (and any Teensy sketch using Audio.h FFT classes) failed at link with undefined references to arm_cfft_radix4_q15 / arm_cfft_radix4_init_q15. Data-driven via a new build.cmsis_dsp_lib field on the board JSONs, surfaced as BoardConfig.cmsis_dsp_lib and threaded into TeensyLinker. The bundled archive ships inside the Teensyduino toolchain (framework-arduinoteensy.../cores/teensy*/), which the linker already gets via -L<core_dir> through LinkerScripts::single — no extra search path or download is needed. Per-MCU mapping populated on the JSONs: MK20DX128/256 (3.0, 3.1, 3.2) -> arm_cortexM4l_math MK64FX512 (3.5), MK66FX1M0 (3.6) -> arm_cortexM4lf_math MKL26Z64 (LC) -> arm_cortexM0l_math IMXRT1062 (4.0, 4.1, MicroMod) -> arm_cortexM7lfsp_math The previously-hardcoded -larm_cortexM7lfsp_math entry in teensy4x.json's mcu config linker_libs is removed; the same library is now contributed via the data-driven path so teensy40/41/mm all share the same mechanism as the 3.x family. Tests: - fbuild-config: every ARM Teensy board carries the expected cmsis_dsp_lib, AVR Teensy 2.0/2pp omit it, non-Teensy boards omit it, env overrides win over the bundled value - fbuild-build: teensy36 build_link_args() includes -larm_cortexM4lf_math and -L<core_dir>; the flag is absent when no lib is configured; mcu_config-level test now asserts each ARM Teensy board declares its expected library Refs #300, #257.
1 parent d587a0c commit 3cf0eed

16 files changed

Lines changed: 276 additions & 28 deletions

File tree

crates/fbuild-build/src/teensy/configs/teensy4x.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
],
3939

4040
"linker_libs": [
41-
"-larm_cortexM7lfsp_math",
4241
"-lgcc",
4342
"-lstdc++",
4443
"-lm",

crates/fbuild-build/src/teensy/mcu_config.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -199,24 +199,39 @@ mod tests {
199199
.contains(&"-mcpu=cortex-m7".to_string()));
200200
}
201201

202-
/// Regression test for issue #257: teensy4x must link
203-
/// `libarm_cortexM7lfsp_math.a` (CMSIS-DSP) so Teensy Audio FFT
204-
/// examples (`Ports/PJRCSpectrumAnalyzer`) resolve symbols like
205-
/// `arm_cfft_radix4_q15`. The library ships in the Teensy 4.x core
206-
/// dir, which the linker already gets as `-L<core_dir>` via
207-
/// `LinkerScripts::single`.
202+
/// Regression test for issues #257 and #300: Teensy boards that ship a
203+
/// Teensyduino-bundled CMSIS-DSP math library must auto-link it so Teensy
204+
/// `Audio.h` FFT examples (`Ports/PJRCSpectrumAnalyzer`) resolve symbols
205+
/// like `arm_cfft_radix4_q15`. After #300 the per-MCU library name is
206+
/// data-driven via `BoardConfig.cmsis_dsp_lib` (populated from board JSON
207+
/// `build.cmsis_dsp_lib`), mirroring PlatformIO+Teensyduino's SCons
208+
/// builder. The library ships in the Teensy core dir, which the linker
209+
/// already gets as `-L<core_dir>` via `LinkerScripts::single`.
208210
#[test]
209-
fn teensy4x_links_cmsis_dsp_math() {
210-
let config = get_teensy_config_for_mcu("imxrt1062").expect("teensy4x config");
211-
assert!(
212-
config
213-
.linker_libs
214-
.contains(&"-larm_cortexM7lfsp_math".to_string()),
215-
"teensy4x linker_libs must include -larm_cortexM7lfsp_math \
216-
so Teensy Audio FFT examples link; see issue #257. \
217-
Actual libs: {:?}",
218-
config.linker_libs
219-
);
211+
fn teensy_boards_carry_cmsis_dsp_lib() {
212+
let expectations: &[(&str, &str)] = &[
213+
("teensy30", "arm_cortexM4l_math"),
214+
("teensy31", "arm_cortexM4l_math"),
215+
("teensy35", "arm_cortexM4lf_math"),
216+
("teensy36", "arm_cortexM4lf_math"),
217+
("teensy40", "arm_cortexM7lfsp_math"),
218+
("teensy41", "arm_cortexM7lfsp_math"),
219+
("teensymm", "arm_cortexM7lfsp_math"),
220+
("teensylc", "arm_cortexM0l_math"),
221+
];
222+
for (board_id, expected) in expectations {
223+
let board = fbuild_config::BoardConfig::from_board_id(board_id, &HashMap::new())
224+
.unwrap_or_else(|_| panic!("BoardConfig should load for {}", board_id));
225+
assert_eq!(
226+
board.cmsis_dsp_lib.as_deref(),
227+
Some(*expected),
228+
"{} must declare build.cmsis_dsp_lib={} so Teensy Audio FFT \
229+
examples link (mirrors PlatformIO+Teensyduino's per-MCU \
230+
auto-link). See FastLED/fbuild#300.",
231+
board_id,
232+
expected,
233+
);
234+
}
220235
}
221236

222237
#[test]

crates/fbuild-build/src/teensy/orchestrator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ impl BuildOrchestrator for TeensyOrchestrator {
261261
params.profile,
262262
ctx.board.max_flash,
263263
ctx.board.max_ram,
264+
ctx.board.cmsis_dsp_lib.clone(),
264265
params.verbose,
265266
);
266267

crates/fbuild-build/src/teensy/teensy_linker.rs

Lines changed: 141 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub struct TeensyLinker {
2222
profile: BuildProfile,
2323
max_flash: Option<u64>,
2424
max_ram: Option<u64>,
25+
/// Bare CMSIS-DSP math library name (e.g. `arm_cortexM4lf_math`) to link
26+
/// via `-l<name>`. Mirrors PlatformIO+Teensyduino's per-MCU auto-link of
27+
/// the appropriate `libarm_cortex*_math.a` so Teensy `Audio.h` FFT classes
28+
/// resolve at link time. See FastLED/fbuild#300.
29+
cmsis_dsp_lib: Option<String>,
2530
verbose: bool,
2631
}
2732

@@ -37,6 +42,7 @@ impl TeensyLinker {
3742
profile: BuildProfile,
3843
max_flash: Option<u64>,
3944
max_ram: Option<u64>,
45+
cmsis_dsp_lib: Option<String>,
4046
verbose: bool,
4147
) -> Self {
4248
Self {
@@ -49,26 +55,26 @@ impl TeensyLinker {
4955
profile,
5056
max_flash,
5157
max_ram,
58+
cmsis_dsp_lib,
5259
verbose,
5360
}
5461
}
55-
}
5662

57-
impl Linker for TeensyLinker {
58-
fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
59-
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "arm-none-eabi-ar")
63+
/// Bare CMSIS-DSP library name configured for this linker, if any.
64+
pub fn cmsis_dsp_lib(&self) -> Option<&str> {
65+
self.cmsis_dsp_lib.as_deref()
6066
}
6167

62-
fn link(
68+
/// Build the full `arm-none-eabi-gcc` link command-line for the given
69+
/// inputs. Exposed for unit-testing the auto-appended CMSIS-DSP lib
70+
/// (FastLED/fbuild#300) without spawning the real linker.
71+
fn build_link_args(
6372
&self,
6473
objects: &[PathBuf],
6574
archives: &[PathBuf],
66-
output_dir: &Path,
75+
elf_path: &Path,
6776
extra: &LinkExtraArgs,
68-
) -> Result<PathBuf> {
69-
std::fs::create_dir_all(output_dir)?;
70-
let elf_path = output_dir.join("firmware.elf");
71-
77+
) -> Vec<String> {
7278
let mut args: Vec<String> = vec![self.gcc_path.to_string_lossy().to_string()];
7379

7480
// Linker flags from config
@@ -99,8 +105,38 @@ impl Linker for TeensyLinker {
99105

100106
// Linker libraries from config
101107
args.extend(self.mcu_config.linker_libs.iter().cloned());
108+
// Per-board CMSIS-DSP math library auto-link (FastLED/fbuild#300).
109+
// Mirrors PlatformIO+Teensyduino's behaviour: when the board defines
110+
// `build.cmsis_dsp_lib`, append `-l<name>` so the linker resolves
111+
// CMSIS-DSP symbols (`arm_cfft_*`, etc.) referenced by `Audio.h` FFT
112+
// classes from `libarm_cortex*_math.a` that ships in the Teensy core
113+
// dir (already on the search path via `-L<core_dir>`).
114+
if let Some(ref lib) = self.cmsis_dsp_lib {
115+
args.push(format!("-l{}", lib));
116+
}
102117
args.extend(extra.libs.iter().cloned());
103118

119+
args
120+
}
121+
}
122+
123+
impl Linker for TeensyLinker {
124+
fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
125+
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "arm-none-eabi-ar")
126+
}
127+
128+
fn link(
129+
&self,
130+
objects: &[PathBuf],
131+
archives: &[PathBuf],
132+
output_dir: &Path,
133+
extra: &LinkExtraArgs,
134+
) -> Result<PathBuf> {
135+
std::fs::create_dir_all(output_dir)?;
136+
let elf_path = output_dir.join("firmware.elf");
137+
138+
let args = self.build_link_args(objects, archives, &elf_path, extra);
139+
104140
if self.verbose {
105141
eprintln!("link: {}", args.join(" "));
106142
tracing::info!("link: {}", args.join(" "));
@@ -198,10 +234,12 @@ mod tests {
198234
BuildProfile::Release,
199235
Some(8126464),
200236
Some(1048576),
237+
None,
201238
false,
202239
);
203240
assert_eq!(linker.max_flash, Some(8126464));
204241
assert_eq!(linker.max_ram, Some(1048576));
242+
assert!(linker.cmsis_dsp_lib().is_none());
205243
}
206244

207245
#[test]
@@ -216,6 +254,7 @@ mod tests {
216254
BuildProfile::Release,
217255
Some(8126464),
218256
Some(1048576),
257+
None,
219258
false,
220259
);
221260
assert!(linker
@@ -224,4 +263,96 @@ mod tests {
224263
.iter()
225264
.any(|s| s.contains("imxrt1062")));
226265
}
266+
267+
/// Regression test for FastLED/fbuild#300: when a CMSIS-DSP library is
268+
/// configured, the linker stores it so the `-l<lib>` flag is appended at
269+
/// link time (mirrors PlatformIO+Teensyduino's auto-link behaviour).
270+
#[test]
271+
fn test_teensy_linker_stores_cmsis_dsp_lib() {
272+
let linker = TeensyLinker::new(
273+
PathBuf::from("/bin/arm-none-eabi-gcc"),
274+
PathBuf::from("/bin/arm-none-eabi-ar"),
275+
PathBuf::from("/bin/arm-none-eabi-objcopy"),
276+
PathBuf::from("/bin/arm-none-eabi-size"),
277+
LinkerScripts::single(PathBuf::from("/teensy3"), "mk66fx1m0.ld"),
278+
crate::teensy::mcu_config::get_teensy_config_for_mcu("mk66fx1m0").unwrap(),
279+
BuildProfile::Release,
280+
Some(1048576),
281+
Some(262144),
282+
Some("arm_cortexM4lf_math".to_string()),
283+
false,
284+
);
285+
assert_eq!(linker.cmsis_dsp_lib(), Some("arm_cortexM4lf_math"));
286+
}
287+
288+
/// Regression test for FastLED/fbuild#300: the constructed link command
289+
/// includes `-larm_cortexM4lf_math` for teensy36 (MK66FX1M0). Mirrors
290+
/// PlatformIO+Teensyduino's per-MCU auto-link so Teensy `Audio.h` FFT
291+
/// classes (e.g. `arm_cfft_radix4_q15`) resolve at link time.
292+
#[test]
293+
fn test_teensy36_link_command_includes_cmsis_dsp_lib() {
294+
let linker = TeensyLinker::new(
295+
PathBuf::from("/bin/arm-none-eabi-gcc"),
296+
PathBuf::from("/bin/arm-none-eabi-ar"),
297+
PathBuf::from("/bin/arm-none-eabi-objcopy"),
298+
PathBuf::from("/bin/arm-none-eabi-size"),
299+
LinkerScripts::single(PathBuf::from("/teensy3"), "mk66fx1m0.ld"),
300+
crate::teensy::mcu_config::get_teensy_config_for_mcu("mk66fx1m0").unwrap(),
301+
BuildProfile::Release,
302+
Some(1048576),
303+
Some(262144),
304+
Some("arm_cortexM4lf_math".to_string()),
305+
false,
306+
);
307+
let args = linker.build_link_args(
308+
&[PathBuf::from("/build/sketch.o")],
309+
&[PathBuf::from("/build/core.o")],
310+
&PathBuf::from("/build/firmware.elf"),
311+
&LinkExtraArgs::default(),
312+
);
313+
assert!(
314+
args.iter().any(|a| a == "-larm_cortexM4lf_math"),
315+
"teensy36 link command must include -larm_cortexM4lf_math \
316+
so Audio.h FFT examples link (see fbuild#300). Args: {:?}",
317+
args
318+
);
319+
// The `-L<core_dir>` flag from LinkerScripts is what lets the linker
320+
// resolve the `-l` to `libarm_cortexM4lf_math.a` inside teensy3/.
321+
assert!(
322+
args.iter().any(|a| a == "-L/teensy3"),
323+
"expected -L/teensy3 for library search, got {:?}",
324+
args
325+
);
326+
}
327+
328+
/// Boards that do not declare a CMSIS-DSP lib (e.g. user override clears
329+
/// it) must not have a spurious `-l` argument appended.
330+
#[test]
331+
fn test_teensy_link_command_omits_cmsis_dsp_lib_when_none() {
332+
let linker = TeensyLinker::new(
333+
PathBuf::from("/bin/arm-none-eabi-gcc"),
334+
PathBuf::from("/bin/arm-none-eabi-ar"),
335+
PathBuf::from("/bin/arm-none-eabi-objcopy"),
336+
PathBuf::from("/bin/arm-none-eabi-size"),
337+
LinkerScripts::single(PathBuf::from("/teensy4"), "imxrt1062_t41.ld"),
338+
crate::teensy::mcu_config::get_teensy_config_for_mcu("imxrt1062").unwrap(),
339+
BuildProfile::Release,
340+
Some(8126464),
341+
Some(524288),
342+
None,
343+
false,
344+
);
345+
let args = linker.build_link_args(
346+
&[],
347+
&[],
348+
&PathBuf::from("/build/firmware.elf"),
349+
&LinkExtraArgs::default(),
350+
);
351+
assert!(
352+
!args.iter().any(|a| a.starts_with("-larm_cortex")),
353+
"no CMSIS-DSP -l flag should be appended when cmsis_dsp_lib is None. \
354+
Args: {:?}",
355+
args
356+
);
357+
}
227358
}

crates/fbuild-config/assets/boards/json/teensy30.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "mk20dx128.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM4l_math",
67
"core": "teensy3",
78
"extra_flags": "-D__MK20DX128__ -DARDUINO_TEENSY30",
89
"f_cpu": "48000000L",

crates/fbuild-config/assets/boards/json/teensy31.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "mk20dx256.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM4l_math",
67
"core": "teensy3",
78
"extra_flags": "-D__MK20DX256__ -DARDUINO_TEENSY31",
89
"f_cpu": "72000000L",

crates/fbuild-config/assets/boards/json/teensy35.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "mk64fx512.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM4lf_math",
67
"core": "teensy3",
78
"extra_flags": "-D__MK64FX512__ -DARDUINO_TEENSY35",
89
"f_cpu": "120000000L",

crates/fbuild-config/assets/boards/json/teensy36.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "mk66fx1m0.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM4lf_math",
67
"core": "teensy3",
78
"extra_flags": "-D__MK66FX1M0__ -DARDUINO_TEENSY36",
89
"f_cpu": "180000000L",

crates/fbuild-config/assets/boards/json/teensy40.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "imxrt1062.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM7lfsp_math",
67
"core": "teensy4",
78
"extra_flags": "-D__IMXRT1062__ -DARDUINO_TEENSY40",
89
"f_cpu": "600000000",

crates/fbuild-config/assets/boards/json/teensy41.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"arduino": {
44
"ldscript": "imxrt1062_t41.ld"
55
},
6+
"cmsis_dsp_lib": "arm_cortexM7lfsp_math",
67
"core": "teensy4",
78
"extra_flags": "-D__IMXRT1062__ -DARDUINO_TEENSY41",
89
"f_cpu": "600000000",

0 commit comments

Comments
 (0)