|
| 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 | +} |
0 commit comments