Skip to content

Commit 78f41fd

Browse files
tacertainclaude
andcommitted
Add USDHC1 SD card support for Teensy 4.1
Integrate the imxrt-usdhc driver crate into the BSP: - Configure USDHC1 root clock (PLL2_PFD2 / 2 = 198 MHz) - Add USDHC1 instance to board Resources - Add usdhc1 module with pin config and init_usdhc1() convenience fn - Re-export embedded_sdmmc, Usdhc, SdPins, etc. from board module - Add rtic_sd_info example (card init + FAT32 root dir listing) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c4f94a5 commit 78f41fd

6 files changed

Lines changed: 222 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ optional = true
5151
workspace = true
5252
optional = true
5353

54+
[dependencies.imxrt-usdhc]
55+
path = "../imxrt-usdhc"
56+
5457
[dependencies.teensy4-pins]
5558
version = "0.3.1"
5659
path = "teensy4-pins"
@@ -151,3 +154,8 @@ required-features = ["rt"]
151154
[[example]]
152155
name = "rtic_usb_log"
153156
required-features = ["rt"]
157+
158+
[[example]]
159+
name = "rtic_sd_info"
160+
required-features = ["rt"]
161+

examples/rtic_sd_info.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//! Demonstrates SD card initialization on the Teensy 4.1.
2+
//!
3+
//! Insert a FAT32-formatted SD card, flash this example, and connect to the
4+
//! USB serial port to see card info and a root directory listing.
5+
//!
6+
//! This example requires a Teensy 4.1 (which has the built-in micro-SD slot).
7+
8+
#![no_std]
9+
#![no_main]
10+
11+
use teensy4_panic as _;
12+
13+
#[rtic::app(device = teensy4_bsp, peripherals = true, dispatchers = [KPP])]
14+
mod app {
15+
use bsp::board;
16+
use teensy4_bsp as bsp;
17+
18+
use board::embedded_sdmmc;
19+
use imxrt_log as logging;
20+
use rtic_monotonics::systick::*;
21+
22+
/// Dummy time source for FAT timestamps.
23+
struct FakeTime;
24+
25+
impl embedded_sdmmc::TimeSource for FakeTime {
26+
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
27+
embedded_sdmmc::Timestamp {
28+
year_since_1970: 56, // 2026
29+
zero_indexed_month: 2,
30+
zero_indexed_day: 29,
31+
hours: 0,
32+
minutes: 0,
33+
seconds: 0,
34+
}
35+
}
36+
}
37+
38+
#[local]
39+
struct Local {
40+
poller: logging::Poller,
41+
led: board::Led,
42+
sd: Option<board::Usdhc>,
43+
}
44+
45+
#[shared]
46+
struct Shared {}
47+
48+
#[init]
49+
fn init(cx: init::Context) -> (Shared, Local) {
50+
let board::Resources {
51+
usb,
52+
pins,
53+
mut gpio2,
54+
usdhc1,
55+
..
56+
} = board::t41(cx.device);
57+
let led = board::led(&mut gpio2, pins.p13);
58+
59+
let poller = logging::log::usbd(usb, logging::Interrupts::Enabled).unwrap();
60+
61+
Systick::start(
62+
cx.core.SYST,
63+
board::ARM_FREQUENCY,
64+
rtic_monotonics::create_systick_token!(),
65+
);
66+
67+
// Initialize SD card during init (blocking, before USB is up).
68+
// The result is passed to the sd_info task for logging once
69+
// the USB host has had time to connect.
70+
let sd_pins = board::SdPins {
71+
cmd: pins.p45,
72+
clk: pins.p44,
73+
data0: pins.p43,
74+
data1: pins.p42,
75+
data2: pins.p47,
76+
data3: pins.p46,
77+
};
78+
79+
let sd = match board::init_usdhc1(usdhc1, sd_pins) {
80+
Ok(sd) => Some(sd),
81+
Err(e) => {
82+
// Can't log yet — USB isn't enumerated. The sd_info task
83+
// will detect None and report the failure.
84+
let _ = e;
85+
None
86+
}
87+
};
88+
89+
sd_info::spawn().unwrap();
90+
led.set();
91+
92+
(Shared {}, Local { poller, led, sd })
93+
}
94+
95+
/// Wait for the USB host to connect, then log SD card info.
96+
#[task(local = [sd])]
97+
async fn sd_info(cx: sd_info::Context) {
98+
// Give the host time to enumerate the USB device and open the serial port.
99+
Systick::delay(2000.millis()).await;
100+
101+
let Some(sd) = cx.local.sd.take() else {
102+
log::error!("SD card not initialized (no card or init failed)");
103+
return;
104+
};
105+
106+
log::info!(
107+
"SD card: {:?}, {} MB ({} blocks)",
108+
sd.card_type(),
109+
sd.capacity_mb(),
110+
sd.block_count(),
111+
);
112+
113+
let volume_mgr = embedded_sdmmc::VolumeManager::new(sd, FakeTime);
114+
115+
match volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(0)) {
116+
Ok(volume) => {
117+
match volume_mgr.open_root_dir(volume) {
118+
Ok(root_dir) => {
119+
log::info!("Root directory:");
120+
let _ = volume_mgr.iterate_dir(root_dir, |entry| {
121+
log::info!(" {}", entry.name);
122+
});
123+
let _ = volume_mgr.close_dir(root_dir);
124+
}
125+
Err(e) => log::error!("open root dir: {:?}", e),
126+
}
127+
let _ = volume_mgr.close_volume(volume);
128+
}
129+
Err(e) => log::error!("open volume: {:?}", e),
130+
}
131+
132+
log::info!("Done.");
133+
}
134+
135+
#[task(local = [led])]
136+
async fn blink(cx: blink::Context) {
137+
loop {
138+
cx.local.led.toggle();
139+
Systick::delay(500.millis()).await;
140+
}
141+
}
142+
143+
#[task(binds = USB_OTG1, local = [poller])]
144+
fn usb_interrupt(cx: usb_interrupt::Context) {
145+
cx.local.poller.poll();
146+
}
147+
}

src/board.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,13 @@ pub struct Resources<Pins> {
299299
pub sai3: ral::sai::SAI3,
300300
/// The IOMUXC general purpose register block.
301301
pub iomuxc_gpr: ral::iomuxc_gpr::IOMUXC_GPR,
302+
/// The USDHC1 peripheral instance.
303+
///
304+
/// The Teensy 4.1 has a built-in micro-SD card slot connected to
305+
/// USDHC1 (pins p42–p47). Use this with [`init_usdhc1`] to create
306+
/// an SD card driver. See [`USDHC1_FREQUENCY`] for the configured
307+
/// root clock frequency.
308+
pub usdhc1: ral::usdhc::USDHC1,
302309
}
303310

304311
/// The board's dedicated LED.
@@ -689,6 +696,7 @@ fn prepare_resources<Pins>(
689696
sai2: instances.SAI2,
690697
sai3: instances.SAI3,
691698
iomuxc_gpr: instances.IOMUXC_GPR,
699+
usdhc1: instances.USDHC1,
692700
}
693701
}
694702

@@ -715,3 +723,5 @@ pub fn t41(instances: impl Into<Instances>) -> T41Resources {
715723
pub fn tmm(instances: impl Into<Instances>) -> TMMResources {
716724
prepare_resources(instances.into(), pins::tmm::from_pads)
717725
}
726+
727+
pub use crate::usdhc1::{embedded_sdmmc, init_usdhc1, CardType, SdError, SdPins, Usdhc};

src/clock_power.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ const CLOCK_GATES: &[clock_gate::Locator] = &[
267267
clock_gate::sai::<1>(),
268268
clock_gate::sai::<2>(),
269269
clock_gate::sai::<3>(),
270+
clock_gate::usdhc::<1>(),
270271
];
271272

272273
/// Prepare clocks and power for the MCU.
@@ -289,8 +290,25 @@ pub fn prepare_clocks_and_power(
289290
setup_uart_clk(ccm);
290291
setup_audio_pll(ccm_analog);
291292
setup_sai1_clk(ccm);
293+
setup_usdhc1_clk(ccm);
292294

293295
CLOCK_GATES
294296
.iter()
295297
.for_each(|locator| locator.set(ccm, clock_gate::ON));
296298
}
299+
300+
/// USDHC1 root clock frequency (Hz).
301+
///
302+
/// PLL2_PFD2 (396 MHz) / 2 = 198 MHz. The actual SD bus clock is
303+
/// further divided by the USDHC1 peripheral's internal SDCLKFS and
304+
/// DVS dividers during card initialization.
305+
pub const USDHC1_FREQUENCY: u32 = 198_000_000;
306+
307+
/// Configure the USDHC1 clock root for the SD card slot.
308+
///
309+
/// Source: PLL2_PFD2 (396 MHz), divider: /2 → 198 MHz root clock.
310+
fn setup_usdhc1_clk(ccm: &mut ral::ccm::CCM) {
311+
clock_gate::usdhc::<1>().set(ccm, clock_gate::OFF);
312+
ral::modify_reg!(ral::ccm, ccm, CSCMR1, USDHC1_CLK_SEL: 0); // PLL2_PFD2
313+
ral::modify_reg!(ral::ccm, ccm, CSCDR1, USDHC1_PODF: 1); // divide by 2
314+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pub use ral::{interrupt, Interrupt, NVIC_PRIO_BITS};
111111

112112
pub mod board;
113113
mod clock_power;
114+
mod usdhc1;
114115

115116
/// SYSTICK external clock frequency.
116117
///

src/usdhc1.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! BSP-level SD card support for the Teensy 4.1.
2+
//!
3+
//! This module provides pin configuration and a convenience initializer
4+
//! for the USDHC1 peripheral. The driver implementation lives in the
5+
//! [`imxrt_usdhc`] crate.
6+
7+
use crate::{hal, pins, ral};
8+
9+
pub use imxrt_usdhc::{embedded_sdmmc, CardType, SdError, Usdhc};
10+
11+
/// SD card pins for USDHC1 on the Teensy 4.1 (pins p42–p47).
12+
pub struct SdPins {
13+
pub cmd: pins::t41::P45,
14+
pub clk: pins::t41::P44,
15+
pub data0: pins::t41::P43,
16+
pub data1: pins::t41::P42,
17+
pub data2: pins::t41::P47,
18+
pub data3: pins::t41::P46,
19+
}
20+
21+
/// Initialize the USDHC1 peripheral and SD card on the Teensy 4.1.
22+
///
23+
/// Configures the SD card pins and delegates to [`imxrt_usdhc::Usdhc::new`].
24+
/// The USDHC1 clock gate and root clock are already configured by
25+
/// [`t41`](crate::board::t41).
26+
pub fn init_usdhc1(
27+
usdhc1: ral::usdhc::USDHC1,
28+
mut sd_pins: SdPins,
29+
) -> Result<Usdhc, SdError> {
30+
hal::iomuxc::usdhc::prepare(&mut sd_pins.cmd);
31+
hal::iomuxc::usdhc::prepare(&mut sd_pins.clk);
32+
hal::iomuxc::usdhc::prepare(&mut sd_pins.data0);
33+
hal::iomuxc::usdhc::prepare(&mut sd_pins.data1);
34+
hal::iomuxc::usdhc::prepare(&mut sd_pins.data2);
35+
hal::iomuxc::usdhc::prepare(&mut sd_pins.data3);
36+
37+
imxrt_usdhc::Usdhc::new(usdhc1, crate::clock_power::USDHC1_FREQUENCY)
38+
}

0 commit comments

Comments
 (0)