nRF52832学习记录(十二、SPI接口的应用 Micro SD卡读写测试)_nrf52832 spi(2)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事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)卡的引脚说明:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/13e1193eff3e473895447ec4ac4a7ac3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)  
 Micro SD卡只有8个引脚是比SD卡少了一个Vss。可以买个SD卡套套在Micro SD卡上,这样一来大小就和SD卡一样大,这时候卡套上的9个引脚就和SD卡一样了,可以完全当做SD卡来操作。  
 在《SD卡接口规范》文档中,有SD卡工作在SPI模式的引脚定义:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/9228fcf389d04b9188b1fdba8663c741.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)SD卡和Micro SD卡的SPI操作方式是一样的。


SD卡的 SPI 时钟空闲时为高电平,在时钟的第二个边沿,也就是时钟线的电平由低变高时 采集数据,所以配置 SPI 的极性和相位: CPOL = 1, CPHA = 1。由上面第一章的内容可知 **读取SD卡 nRF52832工作在模式 3**。


下面的图片参考博文:[MicroSD卡(TF卡)SPI模式实现方法](https://bbs.youkuaiyun.com/topics/618631832)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/92749f70d052401e92f7126bf3c39fd4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/3a2d8cd25b484a718ddaafcdd102a99c.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/c77f8c25455b454085a2339b3a4b29d8.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)


#### 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;

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值