|
| 1 | +#+OPTIONS: ^:nil |
| 2 | +#+BEGIN_EXPORT html |
| 3 | +--- |
| 4 | +layout: default |
| 5 | +title: 一种基于ESP32丰富连接能力的移动存储设备 -- 测试SD卡读写 |
| 6 | +tags: [Rust on ESP, ESP32, PCB, SD Card] |
| 7 | +nav_order: {{ page.date }} |
| 8 | +sync_wexin: 1 |
| 9 | +--- |
| 10 | +#+END_EXPORT |
| 11 | + |
| 12 | +* 一种基于ESP32丰富连接能力的移动存储设备 -- 测试SD卡读写 |
| 13 | + |
| 14 | +** 前言 |
| 15 | + |
| 16 | +上一篇文章介绍了我为移动存储设备设计的一块电路板,并且PCB实物也收到了。现在我已经焊接好了电路板,接下来我想先测试SD卡的读写能力是否正常。 |
| 17 | +[[/images/esp32-storage-assembled-pcb.jpg]] |
| 18 | + |
| 19 | +本系列其他文章 |
| 20 | +1. [[https://paul356.github.io/2024/10/31/mobile-storage.html][基于ESP32的移动存储设备]] |
| 21 | +2. [[https://paul356.github.io/2024/12/12/mobile-storage-pcb.html][一种基于ESP32丰富连接能力的移动存储设备 -- 电路设计]] |
| 22 | + |
| 23 | +** 测试SD卡功能 |
| 24 | + |
| 25 | +*** 使用官方例子 |
| 26 | +要快速测试SD卡的功能,我先找了ESP-IDF 5.3中的一个C语言例子,例子在ESP-IDF源码中的这个位置 ~examples/storage/sd_card/sdmmc~ 。例子中的SD卡槽连接的管脚和我的PCB不一样,需要通过 ~idf.py menuconfig~ 修改CLK,CMD等管脚对应的GPIO口编号。修改之后的状态如下。 |
| 27 | +[[/images/sdmmc-menuconfig.png]] |
| 28 | + |
| 29 | +然后使用 ~idf.py build~ 命令编译代码,使用 ~idf.py flash~ 上传固件。插入SD卡,测试很顺利。 |
| 30 | +#+begin_src text |
| 31 | + I (897) example: Reading file /sdcard/foo.txt |
| 32 | + I (897) example: Read from file: 'Hello SL16G!' |
| 33 | + I (897) example: Opening file /sdcard/nihao.txt |
| 34 | + I (1117) example: File written |
| 35 | + I (1117) example: Reading file /sdcard/nihao.txt |
| 36 | + I (1117) example: Read from file: 'Nihao SL16G!' |
| 37 | + I (1117) example: Card unmounted |
| 38 | + I (1127) main_task: Returned from app_main() |
| 39 | +#+end_src |
| 40 | + |
| 41 | +*** 开发Rust代码 |
| 42 | +通过官方例子验证了我的SD卡硬件设计没有问题,下面就让我们通过Rust来访问SD卡。虽然通过搜索网路我找到一个Rust模块embeded-sdmmc,但是这个项目目前只支持通过SPI协议连接的SD卡。因为我为了获取更好的读写速度,选用了SDMMC接口,所以我无法使用这个模块。之后搜索也没有找到其他支持SDMMC的模块。所以只能直接调用ESP-IDF的C接口这个办法了。 |
| 43 | + |
| 44 | +通过查看官方例子的代码,我们了解到关键的接口是 ~esp_vfs_fat_sdmmc_mount~ 函数。 |
| 45 | +#+begin_src c |
| 46 | + /** |
| 47 | + * @brief Convenience function to get FAT filesystem on SD card registered in VFS |
| 48 | + * |
| 49 | + * This is an all-in-one function which does the following: |
| 50 | + * - initializes SDMMC driver or SPI driver with configuration in host_config |
| 51 | + * - initializes SD card with configuration in slot_config |
| 52 | + * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config |
| 53 | + * - registers FATFS library with VFS, with prefix given by base_prefix variable |
| 54 | + * |
| 55 | + * This function is intended to make example code more compact. |
| 56 | + * For real world applications, developers should implement the logic of |
| 57 | + * probing SD card, locating and mounting partition, and registering FATFS in VFS, |
| 58 | + * with proper error checking and handling of exceptional conditions. |
| 59 | + * |
| 60 | + * @note Use this API to mount a card through SDSPI is deprecated. Please call |
| 61 | + * `esp_vfs_fat_sdspi_mount()` instead for that case. |
| 62 | + * |
| 63 | + * @param base_path path where partition should be registered (e.g. "/sdcard") |
| 64 | + * @param host_config Pointer to structure describing SDMMC host. When using |
| 65 | + * SDMMC peripheral, this structure can be initialized using |
| 66 | + * SDMMC_HOST_DEFAULT() macro. When using SPI peripheral, |
| 67 | + * this structure can be initialized using SDSPI_HOST_DEFAULT() |
| 68 | + * macro. |
| 69 | + * @param slot_config Pointer to structure with slot configuration. |
| 70 | + * For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t |
| 71 | + * structure initialized using SDMMC_SLOT_CONFIG_DEFAULT. |
| 72 | + * @param mount_config pointer to structure with extra parameters for mounting FATFS |
| 73 | + * @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument |
| 74 | + * @return |
| 75 | + * - ESP_OK on success |
| 76 | + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called |
| 77 | + * - ESP_ERR_NO_MEM if memory can not be allocated |
| 78 | + * - ESP_FAIL if partition can not be mounted |
| 79 | + * - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers |
| 80 | + */ |
| 81 | + esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, |
| 82 | + const sdmmc_host_t* host_config, |
| 83 | + const void* slot_config, |
| 84 | + const esp_vfs_fat_mount_config_t* mount_config, |
| 85 | + sdmmc_card_t** out_card); |
| 86 | +#+end_src |
| 87 | +这个函数接收一个文件系统路径、SD驱动配置、管脚配置、挂载配置,输出SD卡对象的句柄。我们只要准备这些配置对象,就可以读写SD卡了。由于在官方的C例程中,使用了一些宏来初始化 ~sdmmc_host_t~ 和 ~sdmmc_slot_config_t~ ,这些宏在Rust中访问不到,所以我们需要参照C的源代码来初始化这些结构体。 |
| 88 | + |
| 89 | +通过Rust初始化 ~sdmmc_slot_config_t~ 结构体,管脚的赋值需要依据电路设计。 |
| 90 | +#+begin_src Rust |
| 91 | + fn get_slot_config() -> sdmmc_slot_config_t { |
| 92 | + sdmmc_slot_config_t { |
| 93 | + clk: 7, |
| 94 | + cmd: 6, |
| 95 | + d0: 15, |
| 96 | + d1: 16, |
| 97 | + d2: 4, |
| 98 | + d3: 5, |
| 99 | + d4: -1, |
| 100 | + d5: -1, |
| 101 | + d6: -1, |
| 102 | + d7: -1, |
| 103 | + __bindgen_anon_1: sdmmc_slot_config_t__bindgen_ty_1 { |
| 104 | + cd: 17, |
| 105 | + }, |
| 106 | + __bindgen_anon_2: sdmmc_slot_config_t__bindgen_ty_2 { |
| 107 | + wp: -1, |
| 108 | + }, |
| 109 | + width: 4, |
| 110 | + flags: SDMMC_SLOT_FLAG_INTERNAL_PULLUP, |
| 111 | + } |
| 112 | +} |
| 113 | +#+end_src |
| 114 | + |
| 115 | +构造参数mount_config、mount_point、host_config。 |
| 116 | +#+begin_src Rust |
| 117 | + let sdmmc_mount_config = esp_vfs_fat_sdmmc_mount_config_t { |
| 118 | + format_if_mount_failed: false, |
| 119 | + max_files: 4, |
| 120 | + allocation_unit_size: 16 * 1024, |
| 121 | + disk_status_check_enable: false, |
| 122 | + use_one_fat: false, |
| 123 | + }; |
| 124 | + |
| 125 | + let mount_point = CString::new("/sdcard").unwrap(); |
| 126 | + |
| 127 | + let sd_host = sdmmc_host_t { |
| 128 | + flags: SDMMC_HOST_FLAG_1BIT | SDMMC_HOST_FLAG_4BIT | SDMMC_HOST_FLAG_8BIT | SDMMC_HOST_FLAG_DDR, |
| 129 | + slot: SDMMC_HOST_SLOT_1, |
| 130 | + max_freq_khz: SDMMC_FREQ_DEFAULT, |
| 131 | + io_voltage: 3.3, |
| 132 | + init: Some(sdmmc_host_init), |
| 133 | + set_bus_width: Some(sdmmc_host_set_bus_width), |
| 134 | + get_bus_width: Some(sdmmc_host_get_slot_width), |
| 135 | + set_bus_ddr_mode: Some(sdmmc_host_set_bus_ddr_mode), |
| 136 | + set_card_clk: Some(sdmmc_host_set_card_clk), |
| 137 | + set_cclk_always_on: Some(sdmmc_host_set_cclk_always_on), |
| 138 | + do_transaction: Some(sdmmc_host_do_transaction), |
| 139 | + __bindgen_anon_1: sdmmc_host_t__bindgen_ty_1 { |
| 140 | + deinit: Some(sdmmc_host_deinit) |
| 141 | + }, |
| 142 | + io_int_enable: Some(sdmmc_host_io_int_enable), |
| 143 | + io_int_wait: Some(sdmmc_host_io_int_wait), |
| 144 | + command_timeout_ms: 0, |
| 145 | + get_real_freq: Some(sdmmc_host_get_real_freq), |
| 146 | + input_delay_phase: SDMMC_DELAY_PHASE_0, |
| 147 | + set_input_delay: Some(sdmmc_host_set_input_delay), |
| 148 | + dma_aligned_buffer: std::ptr::null_mut(), |
| 149 | + pwr_ctrl_handle: std::ptr::null_mut(), |
| 150 | + get_dma_info: Some(sdmmc_host_get_dma_info), |
| 151 | + }; |
| 152 | + |
| 153 | + let slot_config = get_slot_config(); |
| 154 | +#+end_src |
| 155 | + |
| 156 | +参数都准备好了,最后调用esp_vfs_fat_sdmmc_mount挂载文件系统。 |
| 157 | +#+begin_src Rust |
| 158 | + let mut card_handle: *mut sdmmc_card_t = std::ptr::null_mut(); |
| 159 | + |
| 160 | + let ret = unsafe { |
| 161 | + esp_vfs_fat_sdmmc_mount( |
| 162 | + mount_point.as_ptr(), |
| 163 | + &sd_host, |
| 164 | + &slot_config as *const sdmmc_slot_config_t as *const c_void, |
| 165 | + &sdmmc_mount_config, |
| 166 | + &mut card_handle, |
| 167 | + ) |
| 168 | + }; |
| 169 | + |
| 170 | + match ret { |
| 171 | + ESP_OK => log::info!("SD Card mounted"), |
| 172 | + _ => { |
| 173 | + log::error!("Failed to mount SD Card"); |
| 174 | + return; |
| 175 | + } |
| 176 | + } |
| 177 | +#+end_src |
| 178 | + |
| 179 | +再测试SD卡的基本文件读写功能。 |
| 180 | +#+begin_src Rust |
| 181 | + let file_res = std::fs::File::create_new("/sdcard/test.txt"); |
| 182 | + let mut file = match file_res { |
| 183 | + Ok(mut file) => { |
| 184 | + file.write_all(b"Hello, world!").unwrap(); |
| 185 | + |
| 186 | + std::fs::File::open("/sdcard/test.txt").unwrap() |
| 187 | + } |
| 188 | + Err(_) => { |
| 189 | + std::fs::File::open("/sdcard/test.txt").unwrap() |
| 190 | + } |
| 191 | + }; |
| 192 | + |
| 193 | + let mut content = String::new(); |
| 194 | + file.read_to_string(&mut content).unwrap(); |
| 195 | + |
| 196 | + log::info!("File content: {}", content); |
| 197 | +#+end_src |
| 198 | + |
| 199 | +日志如下,初步看来SD卡功能正常。 |
| 200 | +#+begin_src text |
| 201 | + I (417) main_task: Started on CPU0 |
| 202 | + I (427) main_task: Calling app_main() |
| 203 | + I (427) sd_card_test: Hello, world! |
| 204 | + I (427) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 205 | + I (437) gpio: GPIO[6]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 206 | + I (447) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 207 | + I (457) gpio: GPIO[16]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 208 | + I (467) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 209 | + I (477) gpio: GPIO[5]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 |
| 210 | + I (517) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 |
| 211 | + I (527) sd_card_test: SD Card mounted |
| 212 | + I (527) sd_card_test: File content: Hello, world! |
| 213 | + I (527) main_task: Returned from app_main() |
| 214 | +#+end_src |
| 215 | + |
| 216 | +以上就是Rust中使用SD卡的关键要点,其实还是比较简单的。 |
| 217 | + |
| 218 | +** 后记 |
| 219 | + |
| 220 | +有兴趣的朋友可以在[[https://github.com/paul356/sd_card_rust][sd_card_rust]]项目中找到完整代码,欢迎大家留言交流。 |
| 221 | + |
| 222 | +** 链接列表 |
| 223 | + |
| 224 | +1. sd_card_rust -- https://github.com/paul356/sd_card_rust |
0 commit comments