网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
{
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模式实现方法](https://bbs.youkuaiyun.com/topics/618631832)

#### 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();//取消上次片选
// if(SD_Select())return 0XFF;//片选失效
//发送
SpiFlash_WriteOneByte(cmd | 0x40);//分别写入命令
SpiFlash_WriteOneByte(arg >> 24);
SpiFlash_WriteOneByte(arg >> 16);
SpiFlash_WriteOneByte(arg >> 8);
SpiFlash_WriteOneByte(arg);
SpiFlash_WriteOneByte(crc);
if(cmd==CMD12)SpiFlash_WriteOneByte(0xff);//Skip a stuff byte when stop reading
//等待响应,或超时退出
Retry=0X1F;
do
{
r1=SpiFlash_ReadOneByte();
}while((r1&0X80) && Retry–);
//返回状态值
return r1;
}
u8 SD_Initialize(void)
{
u8 r1; // 存放SD卡的返回值
u16 retry; // 用来进行超时计数
u8 buf[4];
u16 i;
hal\_spi\_init();
for(i=0;i<10;i++)SpiFlash\_WriteOneByte(0XFF);//发送最少74个脉冲
retry=20;
do
{
r1=SD\_SendCmd(CMD0,0,0x95);//进入IDLE状态
}while((r1!=0X01) && retry--);
SD_Type=0;//默认无卡
if(r1==0X01)
{
if(SD\_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
for(i=0;i<4;i++)buf[i]=SpiFlash\_ReadOneByte(); //Get trailing return value of R7 resp
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
{
retry=0XFFFE;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
无卡
if(r1==0X01)
{
if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
for(i=0;i<4;i++)buf[i]=SpiFlash_ReadOneByte(); //Get trailing return value of R7 resp
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
{
retry=0XFFFE;
[外链图片转存中…(img-xKzjceJf-1715811490440)]
[外链图片转存中…(img-PXJrzvad-1715811490441)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!