既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
主设备能够控制时钟,因为SPI通信并不像UART或者IIC通信那样有专门的通信周期,起始信号,结束信号;所以SPI协议能够通过控制时钟信号线,当没有数据交流的时候我们的时钟线要么是保持高电平要么是保持低电平。
不同的从设备可能在出厂时由工厂配置为某种通信模式,无法改变,所以我们得根据从设备的通讯模式来设置主设备的模式。
我们还是从基本的SPI设备使用介绍开始学习,以后应用需要使用到EasyDMA再做相应的记录。
nRF52832的SPI模式:
SPI 寄存器
注意 SPI0、SPI1 和 TWI0、TWI1 共有基础地址,两者不能同时使用
SPI 库函数介绍
先熟悉一下需要用到的库函数,SPI的库函数结构 和 I2C 基本上一致
SPI初始化函数
nrf_drv_spi_init
有4个参数:
第一个参数选择使用哪个 SPI 模块(3个) 如果使用 EasyDMA 就是SPIM;
第二个参数是 SPI 的配置;
第三个参数是 自定义的事件处理函数,如果设置为NULL, 则使能 SPI 的阻塞模式;
第四个参数 写 NULL暂时,不太清楚,也是处理函数。
/\*
第一个参数就是定义使用哪一个 SPI 或者是 SPIM(如果使用EasyDMA的话):
typedef struct
{
uint8\_t inst\_idx;
union
{
#ifdef SPIM\_PRESENT
nrfx\_spim\_t spim;
#endif
#ifdef SPI\_PRESENT
nrfx\_spi\_t spi;
#endif
} u;
bool use\_easy\_dma;
} nrf\_drv\_spi\_t;
nrf\_drv\_spi\_t 结构体中的 nrfx\_spi\_t 结构体:
typedef struct
{
NRF\_SPI\_Type \* p\_reg; ///< Pointer to a structure with SPI registers.
uint8\_t drv\_inst\_idx; ///< Driver instance index.
} nrfx\_spi\_t;
nrfx\_spi\_t 结构体中的 NRF\_SPI\_Type 就是对应的 SPI 的每个寄存器
第二个参数定义 SPI 的配置:
typedef struct
{
uint8\_t sck\_pin; ///< SCK pin number.
uint8\_t mosi\_pin; ///< MOSI pin number (optional).
/\*\*< Set to @ref NRF\_DRV\_SPI\_PIN\_NOT\_USED
\* if this signal is not needed. /
uint8\_t miso\_pin; ///< MISO pin number (optional).
/\*\*< Set to @ref NRF\_DRV\_SPI\_PIN\_NOT\_USED
\* if this signal is not needed. /
uint8\_t ss\_pin; ///< Slave Select pin number (optional).
/\*\*< Set to @ref NRF\_DRV\_SPI\_PIN\_NOT\_USED
\* if this signal is not needed. The driver
\* supports only active low for this signal.
\* If the signal should be active high,
\* it must be controlled externally. /
uint8\_t irq\_priority; ///< Interrupt priority.
uint8\_t orc; ///< Over-run character.
/\*\*< This character is used when all bytes from the TX buffer are sent,
but the transfer continues due to RX. /
nrf\_drv\_spi\_frequency\_t frequency; ///< SPI frequency.
nrf\_drv\_spi\_mode\_t mode; ///< SPI mode.
nrf\_drv\_spi\_bit\_order\_t bit\_order; ///< SPI bit order.
} nrf\_drv\_spi\_config\_t;
其中nrf\_drv\_spi\_bit\_order\_t bit\_order; ///< SPI bit order.
表示每个数据是高位先发还是地位先发
第三个参数是 用户提供的事件处理程序,如果为空,使用阻塞模式。
第四个参数是 传递给事件处理的上下文。
整体结构和I2C一样
\*/
ret\_code\_t nrf\_drv\_spi\_init(nrf\_drv\_spi\_t const \* const p_instance,
nrf\_drv\_spi\_config\_t const \* p_config,
nrf\_drv\_spi\_evt\_handler\_t handler,
void \* p_context)
nrf_drv_spi_config_t
SPI默认配置如下:
#define NRF\_DRV\_SPI\_DEFAULT\_CONFIG \
{ \
.sck\_pin = NRF\_DRV\_SPI\_PIN\_NOT\_USED, \
.mosi\_pin = NRF\_DRV\_SPI\_PIN\_NOT\_USED, \
.miso\_pin = NRF\_DRV\_SPI\_PIN\_NOT\_USED, \
.ss\_pin = NRF\_DRV\_SPI\_PIN\_NOT\_USED, \
.irq\_priority = SPI\_DEFAULT\_CONFIG\_IRQ\_PRIORITY, \
.orc = 0xFF, \
.frequency = NRF\_DRV\_SPI\_FREQ\_4M, \
.mode = NRF\_DRV\_SPI\_MODE\_0, \
.bit\_order = NRF\_DRV\_SPI\_BIT\_ORDER\_MSB\_FIRST, \
}
SPI数据传输函数
nrf_drv_spi_transfer有5个参数:
第一个参数,和上面介绍的一样,哪个SPI 模块;
第二个参数 ,指向传输缓冲区的指针;
第三个参数,为 传输缓冲区的长度;
第四个参数,指向接收缓冲区的指针;
第五个参数,为 接收缓冲区的长度;
/\*
第一个参数, 和初始化一样
使用哪一个 SPI 或者是 SPIM(如果使用EasyDMA的话):
第二个参数指向传输缓冲区的指针。没有则可以为空。
第三个数据 为 传输缓冲区的长度
第四个数据 接收缓冲区
5长度
\*/
__STATIC_INLINE
ret\_code\_t nrf\_drv\_spi\_transfer(nrf\_drv\_spi\_t const \* const p_instance,
uint8\_t const \* p_tx_buffer,
uint8\_t tx_buffer_length,
uint8\_t \* p_rx_buffer,
uint8\_t rx_buffer_length)
2、nRF52xx SPI 使用示例
w25qxx SPI Flash读写
这里只放Flash的驱动部分.c文件部分,至于里面的CMD,是在.h文件里面定义的,不同的SPI设备不同,这里是为了给出使用示例,所以不贴全部代码,代码是 清风蓝牙教程的demo:
w25qxx.c 驱动:
#include <string.h>
#include "nrf\_drv\_common.h"
#include "nrf\_drv\_spi.h"
#include "app\_util\_platform.h"
#include "app\_error.h"
#include "w25q16.h"
#include "nrf\_gpio.h"
#include "boards.h"
#include "nrf\_delay.h"
#define SPI\_INSTANCE 0 /\*\*< SPI instance index. \*/
static volatile bool spi_xfer_done; //SPI数据传输完成标志
static const nrf\_drv\_spi\_t spi = NRF\_DRV\_SPI\_INSTANCE(SPI_INSTANCE); /\*\*< SPI instance. \*/
static uint8\_t spi_tx_buf[256]; /\*\*< TX buffer. \*/
static uint8\_t spi_rx_buf[256]; /\*\*< RX buffer. \*/
/\*\*
\* @brief SPI user event handler.
\* @param event
\*/
void spi\_event\_handler(nrf\_drv\_spi\_evt\_t const \* p_event,
void \* p_context)
{
spi_xfer_done = true;
}
void hal\_spi\_init(void)
{
// nrf\_drv\_spi\_config\_t spi\_config = NRF\_DRV\_SPI\_DEFAULT\_CONFIG(SPI\_INSTANCE);
// spi\_config.ss\_pin = SPI\_CS\_PIN;
// APP\_ERROR\_CHECK(nrf\_drv\_spi\_init(&spi, &spi\_config, spi\_event\_handler));
nrf\_drv\_spi\_config\_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
spi_config.ss_pin = SPI_SS_PIN;
spi_config.miso_pin = SPI_MISO_PIN;
spi_config.mosi_pin = SPI_MOSI_PIN;
spi_config.sck_pin = SPI_SCK_PIN;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_init(&spi, &spi_config, spi_event_handler, NULL));
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:读出一个字节
\*\* 入 参:无
\*\* 返回值:读出的数据
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
uint8\_t SpiFlash\_ReadOneByte(void)
{
uint8\_t len = 1;
spi_tx_buf[0] = 0xFF;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
return (spi_rx_buf[0]);
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:写入一个字节
\*\* 入 参:Dat:待写入的数据
\*\* 返回值:无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SpiFlash\_WriteOneByte(uint8\_t Dat)
{
uint8\_t len = 1;
spi_tx_buf[0] = Dat;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:写入命令
\*\* 入 参:\*CMD:指向待写入的命令
\*\* 返回值:RET\_SUCCESS
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
uint8\_t SpiFlash\_Write\_CMD(uint8\_t \*CMD)
{
uint8\_t len = 3;
spi_tx_buf[0] = \*CMD;
spi_tx_buf[1] = \*(CMD+1);
spi_tx_buf[2] = \*(CMD+2);
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
return RET_SUCCESS;
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:写使能
\*\* 入 参:无
\*\* 返回值:无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SpiFlash\_Write\_Enable(void)
{
spi_xfer_done = false;
SpiFlash\_WriteOneByte(SPIFlash_WriteEnable_CMD);
while(!spi_xfer_done);
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:擦除扇区,W25Q128FVSIG最小的擦除单位是扇区
\*\* 入 参:Block\_Num:块号
\*\* Sector\_Number:扇区号
\*\* 返回值:
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SPIFlash\_Erase\_Sector(uint8\_t Block_Num,uint8\_t Sector_Number)
{
SpiFlash\_Write\_Enable();
spi_tx_buf[0] = SPIFlash_SecErase_CMD;
spi_tx_buf[1] = Block_Num;
spi_tx_buf[2] = Sector_Number<<4;
spi_tx_buf[3] = 0x00;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, 4, spi_rx_buf, 4));
while(!spi_xfer_done);
nrf\_delay\_ms(10); //每次擦除数据都要延时等待写入结束
return ;
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:向指定的地址写入数据
\*\* \*pBuffer:指向待写入的数据
\*\* WriteAddr:写入的起始地址
\*\* WriteBytesNum:读出的字节数
\*\* 返回值:RET\_SUCCESS
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
uint8\_t SpiFlash\_Write\_Page(uint8\_t \*pBuffer, uint32\_t WriteAddr, uint32\_t WriteBytesNum)
{
uint8\_t len;
SpiFlash\_Write\_Enable();
spi_tx_buf[0] = SPIFlash_PageProgram_CMD;
spi_tx_buf[1] = (uint8\_t)((WriteAddr&0x00ff0000)>>16);
spi_tx_buf[2] = (uint8\_t)((WriteAddr&0x0000ff00)>>8);
spi_tx_buf[3] = (uint8\_t)WriteAddr;
memcpy(&spi_tx_buf[4],pBuffer,WriteBytesNum);
len = WriteBytesNum + 4;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, 0));
while(!spi_xfer_done);
return RET_SUCCESS;
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:从指定的地址读出指定长度的数据
\*\* 入 参:pBuffer:指向存放读出数据的首地址
\*\* ReadAddr:待读出数据的起始地址
\*\* ReadBytesNum:读出的字节数
\*\* 返回值:
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
uint8\_t SpiFlash\_Read(uint8\_t \*pBuffer,uint32\_t ReadAddr,uint32\_t ReadBytesNum)
{
uint8\_t len;
spi_tx_buf[0] = SPIFlash_ReadData_CMD;
spi_tx_buf[1] = (uint8\_t)((ReadAddr&0x00ff0000)>>16);
spi_tx_buf[2] = (uint8\_t)((ReadAddr&0x0000ff00)>>8);
spi_tx_buf[3] = (uint8\_t)ReadAddr;
len = ReadBytesNum + 4;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
memcpy(pBuffer,&spi_rx_buf[4],ReadBytesNum);
return RET_SUCCESS;
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*END FILE\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
MicroSD卡(TF卡)SPI测试
SD卡往往有多种通讯方式(上电后需要主机告诉SD卡采用什么方式,所以需要SD卡初始化程序):
- 标准的SPI
- SD模式 —— 一根线的SDIO接口
- SD模式 —— 四根线的SDIO接口,一次可以4个数据出,4个数据回
Micro SD卡SPI模式基础知识
Micro SD(TF)卡的引脚说明:
Micro SD卡只有8个引脚是比SD卡少了一个Vss。可以买个SD卡套套在Micro SD卡上,这样一来大小就和SD卡一样大,这时候卡套上的9个引脚就和SD卡一样了,可以完全当做SD卡来操作。
在《SD卡接口规范》文档中,有SD卡工作在SPI模式的引脚定义:
SD卡和Micro SD卡的SPI操作方式是一样的。
SD卡的 SPI 时钟空闲时为高电平,在时钟的第二个边沿,也就是时钟线的电平由低变高时 采集数据,所以配置 SPI 的极性和相位: CPOL = 1, CPHA = 1。由上面第一章的内容可知 读取SD卡 nRF52832工作在模式 3。
下面的图片参考博文:MicroSD卡(TF卡)SPI模式实现方法
Micro SD卡SPI 程序移植测试
研究了这么多,最好还是回到当初STM32正点原子的教程里面有SD的读写,驱动,想着直接移植过来试试,最后应该是成功了,没有做过多的测试读写,只是读了一下扇区大小。
- 程序中主要注意 读取SD卡 nRF52832工作在模式 3;
- 速度设置需要额外更改;
.h部分
//省略
typedef unsigned char u8;
typedef unsigned long int u32;
typedef unsigned int u16;
// SD卡类型定义
#define SD\_TYPE\_ERR 0X00
#define SD\_TYPE\_MMC 0X01
#define SD\_TYPE\_V1 0X02
#define SD\_TYPE\_V2 0X04
#define SD\_TYPE\_V2HC 0X06
// SD卡指令表
#define CMD0 0 //卡复位
#define CMD1 1
#define CMD8 8 //命令8 ,SEND\_IF\_COND
#define CMD9 9 //命令9 ,读CSD数据
#define CMD10 10 //命令10,读CID数据
#define CMD12 12 //命令12,停止数据传输
#define CMD16 16 //命令16,设置SectorSize 应返回0x00
#define CMD17 17 //命令17,读sector
#define CMD18 18 //命令18,读Multi sector
#define CMD23 23 //命令23,设置多sector写入前预先擦除N个block
#define CMD24 24 //命令24,写sector
#define CMD25 25 //命令25,写Multi sector
#define CMD41 41 //命令41,应返回0x00
#define CMD55 55 //命令55,应返回0x01
#define CMD58 58 //命令58,读OCR信息
#define CMD59 59 //命令59,使能/禁止CRC,应返回0x00
//数据写入回应字意义
#define MSD\_DATA\_OK 0x05
#define MSD\_DATA\_CRC\_ERROR 0x0B
#define MSD\_DATA\_WRITE\_ERROR 0x0D
#define MSD\_DATA\_OTHER\_ERROR 0xFF
//SD卡回应标记字
#define MSD\_RESPONSE\_NO\_ERROR 0x00
#define MSD\_IN\_IDLE\_STATE 0x01
#define MSD\_ERASE\_RESET 0x02
#define MSD\_ILLEGAL\_COMMAND 0x04
#define MSD\_COM\_CRC\_ERROR 0x08
#define MSD\_ERASE\_SEQUENCE\_ERROR 0x10
#define MSD\_ADDRESS\_ERROR 0x20
#define MSD\_PARAMETER\_ERROR 0x40
#define MSD\_RESPONSE\_FAILURE 0xFF
void hal\_spi\_init(void);
uint8\_t SpiFlash\_ReadOneByte(void);
void SpiFlash\_WriteOneByte(uint8\_t Dat);
void spi\_set\_highspeed(void);
u8 SD\_SendCmd(u8 cmd, u32 arg, u8 crc);
u8 SD\_Initialize(void);
u8 SD\_GetResponse(u8 Response);
u8 SD\_RecvData(u8\*buf,u16 len);
u8 SD\_GetCSD(u8 \*csd_data);
u32 SD\_GetSectorCount(void);
//省略
.c部分
//省略
#define SPI\_INSTANCE 0 /\*\*< SPI instance index. \*/
#define SDCARD 1
u8 SD_Type=0;//SD卡的类型
static volatile bool spi_xfer_done; //SPI数据传输完成标志
static const nrf\_drv\_spi\_t spi = NRF\_DRV\_SPI\_INSTANCE(SPI_INSTANCE); /\*\*< SPI instance. \*/
static uint8\_t spi_tx_buf[256]; /\*\*< TX buffer. \*/
static uint8\_t spi_rx_buf[256]; /\*\*< RX buffer. \*/
static nrf\_drv\_spi\_config\_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
/\*\*
\* @brief SPI user event handler.
\* @param event
\*/
void spi\_event\_handler(nrf\_drv\_spi\_evt\_t const \* p_event,
void \* p_context)
{
spi_xfer_done = true;
}
void hal\_spi\_init(void)
{
spi_config.ss_pin = SPI_SS_PIN;
spi_config.miso_pin = SPI_MISO_PIN;
spi_config.mosi_pin = SPI_MOSI_PIN;
spi_config.sck_pin = SPI_SCK_PIN;
spi_config.frequency = NRF_DRV_SPI_FREQ_250K;
#ifdef SDCARD
spi_config.mode = NRF_DRV_SPI_MODE_3;
#endif
APP\_ERROR\_CHECK(nrf\_drv\_spi\_init(&spi, &spi_config, spi_event_handler, NULL));
}
void spi\_set\_highspeed(void)
{
nrf\_drv\_spi\_uninit(&spi); //改之前必须uninit
spi_config.frequency = NRF_DRV_SPI_FREQ_4M;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_init(&spi, &spi_config, spi_event_handler, NULL));
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:读出一个字节
\*\* 入 参:无
\*\* 返回值:读出的数据
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
uint8\_t SpiFlash\_ReadOneByte(void)
{
uint8\_t len = 1;
spi_tx_buf[0] = 0xFF;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
return (spi_rx_buf[0]);
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\* 描 述:写入一个字节
\*\* 入 参:Dat:待写入的数据
\*\* 返回值:无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SpiFlash\_WriteOneByte(uint8\_t Dat)
{
uint8\_t len = 1;
spi_tx_buf[0] = Dat;
spi_xfer_done = false;
APP\_ERROR\_CHECK(nrf\_drv\_spi\_transfer(&spi, spi_tx_buf, len, spi_rx_buf, len));
while(!spi_xfer_done);
}
//向SD卡发送一个命令
//输入: u8 cmd 命令
// u32 arg 命令参数
// u8 crc crc校验值
//返回值:SD卡返回的响应
u8 SD\_SendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry=0;
// SD\_DisSelect();//取消上次片选
![img](https://img-blog.csdnimg.cn/img_convert/74ffc54a8f55975bef49e7c12744850a.png)
![img](https://img-blog.csdnimg.cn/img_convert/b632e39bc4abb9ebc2de367adf8635f7.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618631832)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
n, spi_rx_buf, len));
while(!spi_xfer_done);
}
//向SD卡发送一个命令
//输入: u8 cmd 命令
// u32 arg 命令参数
// u8 crc crc校验值
//返回值:SD卡返回的响应
u8 SD\_SendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry=0;
// SD\_DisSelect();//取消上次片选
[外链图片转存中...(img-qqV4ND3x-1715811457299)]
[外链图片转存中...(img-dsvpy5kv-1715811457299)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618631832)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**