-
Notifications
You must be signed in to change notification settings - Fork 89
/
pico_spi_sd_card.rs
341 lines (286 loc) · 11.1 KB
/
pico_spi_sd_card.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
//! # Pico SD Card Example
//!
//! Reads and writes a file from/to the SD Card that is formatted in FAT32.
//! This example uses the SPI0 device of the Raspberry Pi Pico on the
//! pins 4,5,6 and 7. If you don't use an external 3.3V power source,
//! you can connect the +3.3V output on pin 36 to the SD card.
//!
//! SD Cards up to 2TB are supported by the `embedded_sdmmc` crate.
//! I've tested this with a 64GB micro SD card.
//!
//! You need to format the card with an regular old FAT32 filesystem
//! and also make sure the first partition has the right type. This is how your
//! `fdisk` output should look like:
//!
//! ```text
//! fdisk /dev/sdj
//!
//! Welcome to fdisk (util-linux 2.34).
//! Changes will remain in memory only, until you decide to write them.
//! Be careful before using the write command.
//!
//! Command (m for help): Disk /dev/sdj:
//! 59,49 GiB, 63864569856 bytes, 124735488 sectors
//! Disk model: SD/MMC/MS/MSPRO
//! Units: sectors of 1 * 512 = 512 bytes
//! Sector size (logical/physical): 512 bytes / 512 bytes
//! I/O size (minimum/optimal): 512 bytes / 512 bytes
//! Disklabel type: dos
//! Disk identifier: 0x00000000
//!
//! Device Boot Start End Sectors Size Id Type
//! /dev/sdj1 2048 124735487 124733440 59,5G c W95 FAT32 (LBA)
//! ```
//!
//! The important bit here is the _Type_ with `W95 FAT32 (LBA)`, other types
//! are rejected by the `embedded_sdmmc` filesystem implementation.
//!
//! Formatting the partition can be done using `mkfs.fat`:
//!
//! $ mkfs.fat /dev/sdj1
//!
//! The example can either be used with a probe to receive debug output
//! and also the LED is used as status output. There are different blinking
//! patterns.
//!
//! For every successful stage in the example the LED will blink long once.
//! If everything is successful (9 long blink signals), the example will go
//! into a loop and either blink in a _"short long"_ or _"short short long"_ pattern.
//!
//! If there are 4 different error patterns, all with short blinking pulses:
//!
//! - **3 short blink (in a loop)**: Card size could not be retrieved.
//! - **4 short blink (in a loop)**: Error getting volume/partition 0.
//! - **5 short blink (in a loop)**: Error opening root directory.
//! - **6 short blink (in a loop)**: Could not open file 'O.TST'.
//!
//! See the `Cargo.toml` file for Copyright and license details.
#![no_std]
#![no_main]
// The macro for our start-up function
use rp_pico::entry;
// info!() and error!() macros for printing information to the debug output
use defmt::*;
use defmt_rtt as _;
// Ensure we halt the program on panic (if we don't mention this crate it won't
// be linked)
use panic_halt as _;
// Pull in any important traits
use rp_pico::hal::prelude::*;
// Embed the `Hz` function/trait:
use fugit::RateExtU32;
// A shorter alias for the Peripheral Access Crate, which provides low-level
// register access
use rp_pico::hal::pac;
// Import the SPI abstraction:
use rp_pico::hal::spi;
// Import the GPIO abstraction:
use rp_pico::hal::gpio;
// A shorter alias for the Hardware Abstraction Layer, which provides
// higher-level drivers.
use rp_pico::hal;
// Link in the embedded_sdmmc crate.
// The `SdMmcSpi` is used for block level access to the card.
// And the `VolumeManager` gives access to the FAT filesystem functions.
use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager};
// Get the file open mode enum:
use embedded_sdmmc::filesystem::Mode;
use embedded_hal::delay::DelayNs;
use rp2040_hal::Timer;
/// A dummy timesource, which is mostly important for creating files.
#[derive(Default)]
pub struct DummyTimesource();
impl TimeSource for DummyTimesource {
// In theory you could use the RTC of the rp2040 here, if you had
// any external time synchronizing device.
fn get_timestamp(&self) -> Timestamp {
Timestamp {
year_since_1970: 0,
zero_indexed_month: 0,
zero_indexed_day: 0,
hours: 0,
minutes: 0,
seconds: 0,
}
}
}
// Setup some blinking codes:
const BLINK_OK_LONG: [u8; 1] = [8u8];
const BLINK_OK_SHORT_LONG: [u8; 4] = [1u8, 0u8, 6u8, 0u8];
const BLINK_OK_SHORT_SHORT_LONG: [u8; 6] = [1u8, 0u8, 1u8, 0u8, 6u8, 0u8];
const BLINK_ERR_3_SHORT: [u8; 6] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8];
const BLINK_ERR_4_SHORT: [u8; 8] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8];
const BLINK_ERR_5_SHORT: [u8; 10] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8];
const BLINK_ERR_6_SHORT: [u8; 12] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8];
fn blink_signals(
pin: &mut dyn embedded_hal::digital::OutputPin<Error = core::convert::Infallible>,
delay: &mut dyn DelayNs,
sig: &[u8],
) {
for bit in sig {
if *bit != 0 {
pin.set_high().unwrap();
} else {
pin.set_low().unwrap();
}
let length = if *bit > 0 { *bit } else { 1 };
for _ in 0..length {
delay.delay_ms(100);
}
}
pin.set_low().unwrap();
delay.delay_ms(500);
}
fn blink_signals_loop(
pin: &mut dyn embedded_hal::digital::OutputPin<Error = core::convert::Infallible>,
delay: &mut dyn DelayNs,
sig: &[u8],
) -> ! {
loop {
blink_signals(pin, delay, sig);
delay.delay_ms(1000);
}
}
#[entry]
fn main() -> ! {
info!("Program start");
// Grab our singleton objects
let mut pac = pac::Peripherals::take().unwrap();
let _core = pac::CorePeripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// Configure the clocks
//
// The default is to generate a 125 MHz system clock
let clocks = hal::clocks::init_clocks_and_plls(
rp_pico::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO);
// Set the pins up according to their function on this particular board
let pins = rp_pico::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// Set the LED to be an output
let mut led_pin = pins.led.into_push_pull_output();
// Set up our SPI pins into the correct mode
let spi_sclk: gpio::Pin<_, gpio::FunctionSpi, gpio::PullNone> = pins.gpio2.reconfigure();
let spi_mosi: gpio::Pin<_, gpio::FunctionSpi, gpio::PullNone> = pins.gpio3.reconfigure();
let spi_miso: gpio::Pin<_, gpio::FunctionSpi, gpio::PullUp> = pins.gpio4.reconfigure();
let spi_cs = pins.gpio5.into_push_pull_output();
// Create the SPI driver instance for the SPI0 device
let spi = spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));
// Exchange the uninitialised SPI driver for an initialised one
let spi = spi.init(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
400.kHz(), // card initialization happens at low baud rate
embedded_hal::spi::MODE_0,
);
let mut delay = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
info!("Initialize SPI SD/MMC data structures...");
let sdcard = SdCard::new(spi, spi_cs, delay);
let mut volume_mgr = VolumeManager::new(sdcard, DummyTimesource::default());
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
info!("Init SD card controller and retrieve card size...");
match volume_mgr.device().num_bytes() {
Ok(size) => info!("card size is {} bytes", size),
Err(e) => {
error!("Error retrieving card size: {}", defmt::Debug2Format(&e));
blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_3_SHORT);
}
}
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
// Now that the card is initialized, clock can go faster
volume_mgr
.device()
.spi(|spi| spi.set_baudrate(clocks.peripheral_clock.freq(), 16.MHz()));
info!("Getting Volume 0...");
let mut volume = match volume_mgr.get_volume(VolumeIdx(0)) {
Ok(v) => v,
Err(e) => {
error!("Error getting volume 0: {}", defmt::Debug2Format(&e));
blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_4_SHORT);
}
};
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
// After we have the volume (partition) of the drive we got to open the
// root directory:
let dir = match volume_mgr.open_root_dir(&volume) {
Ok(dir) => dir,
Err(e) => {
error!("Error opening root dir: {}", defmt::Debug2Format(&e));
blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_5_SHORT);
}
};
info!("Root directory opened!");
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
// This shows how to iterate through the directory and how
// to get the file names (and print them in hope they are UTF-8 compatible):
volume_mgr
.iterate_dir(&volume, &dir, |ent| {
info!(
"/{}.{}",
core::str::from_utf8(ent.name.base_name()).unwrap(),
core::str::from_utf8(ent.name.extension()).unwrap()
);
})
.unwrap();
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
let mut successful_read = false;
// Next we going to read a file from the SD card:
if let Ok(mut file) = volume_mgr.open_file_in_dir(&mut volume, &dir, "O.TST", Mode::ReadOnly) {
let mut buf = [0u8; 32];
let read_count = volume_mgr.read(&volume, &mut file, &mut buf).unwrap();
volume_mgr.close_file(&volume, file).unwrap();
if read_count >= 2 {
info!("READ {} bytes: {}", read_count, buf);
// If we read what we wrote before the last reset,
// we set a flag so that the success blinking at the end
// changes it's pattern.
if buf[0] == 0x42 && buf[1] == 0x1E {
successful_read = true;
}
}
}
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
match volume_mgr.open_file_in_dir(&mut volume, &dir, "O.TST", Mode::ReadWriteCreateOrTruncate) {
Ok(mut file) => {
volume_mgr
.write(&mut volume, &mut file, b"\x42\x1E")
.unwrap();
volume_mgr.close_file(&volume, file).unwrap();
}
Err(e) => {
error!("Error opening file 'O.TST': {}", defmt::Debug2Format(&e));
blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_6_SHORT);
}
}
volume_mgr.free();
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG);
if successful_read {
info!("Successfully read previously written file 'O.TST'");
} else {
info!("Could not read file, which is ok for the first run.");
info!("Reboot the pico!");
}
loop {
if successful_read {
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_SHORT_SHORT_LONG);
} else {
blink_signals(&mut led_pin, &mut delay, &BLINK_OK_SHORT_LONG);
}
delay.delay_ms(1000);
}
}