nRF52832 SPI驱动Micro SD卡,移植正点原子代码时遇到的坑(附完整工程)
2026/4/21 20:31:52 网站建设 项目流程

nRF52832 SPI驱动Micro SD卡移植实战:从STM32到Nordic的避坑指南

在嵌入式开发中,存储扩展是常见需求,而Micro SD卡因其体积小、容量大、价格低廉成为首选方案。本文将分享如何将正点原子STM32平台的SD卡驱动移植到nRF52832平台,重点解析SPI模式下的关键差异点和实际移植中遇到的"坑"。

1. 硬件平台差异与基础配置

nRF52832与STM32在SPI外设设计上存在显著差异,这些差异直接影响SD卡驱动的移植工作:

时钟配置对比

参数nRF52832STM32F1系列典型配置
最大时钟频率8MHz(SPIM模式)18MHz
时钟极性模式3(CPOL=1, CPHA=1)模式3
分频控制预定义频率等级自由分频
DMA支持EasyDMA(需使用SPIM实例)标准DMA控制器

nRF52832的SPI初始化需要特别注意:

nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG; spi_config.sck_pin = 29; // SCK引脚 spi_config.mosi_pin = 25; // MOSI引脚 spi_config.miso_pin = 28; // MISO引脚 spi_config.ss_pin = NRF_DRV_SPI_PIN_NOT_USED; // 手动控制CS spi_config.frequency = NRF_DRV_SPI_FREQ_4M; spi_config.mode = NRF_DRV_SPI_MODE_3; // SD卡必需的模式

关键提示:nRF52832的SPI0与TWI0共享资源,如果同时使用I2C和SPI,必须选择不同的外设实例。

引脚控制特殊要求

  • CS引脚需要手动控制,不能依赖硬件NSS
  • 上电时需要发送至少74个时钟脉冲(发送空字节0xFF)
  • 切换时钟频率需要先uninit再重新初始化

2. 移植过程中的典型问题与解决方案

2.1 容量读取异常问题

在测试8GB SD卡时,读取到的容量显示为3290MB,远小于实际容量。这个问题源于CSD寄存器解析逻辑:

// 修正后的容量计算逻辑(针对SDHC/SDXC卡) if((csd[0]&0xC0)==0x40) { // SDHC/SDXC卡 csize = csd[9] + ((u32)csd[8] << 8) + 1; Capacity = (u32)csize << 10; // 注意使用32位运算 } else { // 标准容量卡 // ...原有计算逻辑 }

问题根源

  1. 变量类型未统一使用32位导致运算溢出
  2. 未考虑SDXC卡的特殊处理(容量≥32GB)
  3. CSD版本判断条件不够严谨

2.2 SPI时序兼容性问题

SD卡对SPI时序有严格要求,移植时发现以下关键点:

  1. 模式3必须严格配置

    • CPOL=1(空闲时时钟高电平)
    • CPHA=1(第二个边沿采样)
  2. 速度切换时机

    • 初始化阶段必须使用低速(≤400kHz)
    • 初始化完成后才能切换到高速模式
    • 需要动态重配置SPI外设:
void spi_set_speed(u32 speed) { nrf_drv_spi_uninit(&spi); // 必须先解除初始化 if(speed > 400000) { spi_config.frequency = NRF_DRV_SPI_FREQ_4M; } else { spi_config.frequency = NRF_DRV_SPI_FREQ_250K; } APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL)); }

2.3 多卡兼容性处理

不同品牌/容量的SD卡可能存在以下差异:

  1. 初始化流程差异

    • SDv1.x卡:需要CMD1初始化
    • SDv2卡:需要CMD8+CMD55+CMD41组合
    • MMC卡:需要CMD1初始化
  2. 超时处理优化

u8 SD_GetResponse(u8 Response) { u32 Count = 100000; // 增大超时计数范围 while((SpiFlash_ReadOneByte()!=Response) && Count--) { __NOP(); // 插入空指令确保时序 } return (Count==0) ? MSD_RESPONSE_FAILURE : MSD_RESPONSE_NO_ERROR; }

3. 完整工程关键代码解析

3.1 驱动层适配代码

SPI读写基础函数

// 带超时检测的字节写入 void SD_WriteByte(u8 data, u32 timeout) { spi_tx_buf[0] = data; spi_xfer_done = false; APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, spi_tx_buf, 1, spi_rx_buf, 1)); u32 start = nrfx_get_us(); while(!spi_xfer_done) { if((nrfx_get_us() - start) > timeout) { break; // 超时处理 } } } // 多字节连续读取 void SD_ReadMultiBytes(u8 *buf, u16 len) { memset(spi_tx_buf, 0xFF, len); // 发送0xFF触发时钟 APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, spi_tx_buf, len, buf, len)); while(!spi_xfer_done); }

3.2 命令发送优化实现

u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc) { u8 frame[6]; frame[0] = cmd | 0x40; frame[1] = (u8)(arg >> 24); frame[2] = (u8)(arg >> 16); frame[3] = (u8)(arg >> 8); frame[4] = (u8)arg; frame[5] = crc; // 使用DMA传输提高效率 spi_xfer_done = false; APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, frame, 6, spi_rx_buf, 0)); u32 timeout = 10000; // 10ms超时 while(!spi_xfer_done && timeout--); // 等待响应 u8 retry = 20; u8 r1; do { r1 = SpiFlash_ReadOneByte(); } while((r1&0x80) && retry--); return r1; }

4. 性能优化与实测数据

通过优化SPI传输效率,我们得到以下实测数据:

不同时钟频率下的读写速度

时钟频率读取速度 (KB/s)写入速度 (KB/s)稳定性
250kHz45.238.7★★★★★
1MHz152.4128.6★★★★☆
4MHz412.8387.5★★★☆☆
8MHz不稳定不稳定★★☆☆☆

实际测试建议:4MHz时钟在大多数情况下提供最佳性价比,若需要更高速度可尝试6MHz但需严格验证信号完整性

优化技巧

  1. 使用乒乓缓冲区减少传输间隔
  2. 合理设置SPI FIFO阈值
  3. 关键路径禁用中断
  4. 采用DMA传输解放CPU资源
// DMA传输示例(需使用SPIM实例) nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX(tx_buf, tx_len, rx_buf, rx_len); nrfx_spim_xfer(&spim, &xfer, 0);

移植完成后工程实测可稳定支持以下操作:

  • 创建/删除文件
  • 连续读写测试(>1MB数据)
  • 长时间稳定性测试(72小时连续运行)
  • 多种品牌SD卡兼容(SanDisk、Kingston、Samsung等)

在完成移植后,发现nRF52832的SPI驱动在4MHz时钟下即可满足大多数应用需求,过高频率反而可能导致信号完整性问题。实际项目中建议根据PCB布局和线长谨慎选择最高工作频率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询