【ESP-IDF】ESP32S3用SPI读写 MicroSD/TF卡(一)SD卡初始化

        如何使用外部储存、如何使用标准文件系统读写信息,是比较大的课题,我想用几篇文章的幅度详细介绍自己写驱动程序、移植文件系统库,在不依赖ESP官方库的情况下完成ESP32S3对SD卡的控制,此过程解剖了SD卡操作和文件系统操作的核心。

这是系列文章,链接如下:

【ESP-IDF】ESP32S3用SPI读写 MicroSD/TF卡(二)读写正文数据_spi读写sd卡驱动需要自己写吗-CSDN博客

       【ESP-IDF】ESP32S3用SPI读写 MicroSD/TF卡(三)移植FATFS文件系统-CSDN博客

        此篇先解决物理驱动层,就是如何用SPI协议操控SD卡的寄存器。重点与硬件和SD卡数据手册交涉。

        我用 ESPIDF自带的sdspi库失败,失败定位在SD卡初始化过程,发送多次ACMD41仍无法接收SD卡0x00回复,超时(主程序返回0x107错误),初始化失败。于是自己写了SD卡SPI模式的通讯协议查看究竟。

一、硬件

        SD卡、MicroSD、TF卡都是用一样的代码一样的协议。MMC卡的协议流程略有不同。SD卡里面还有细分为超大容量SDXC、大容量SDHC和小容量SDSC(2GB以下)三种,接线一样,代码上略有区别,而这些区别挺致命的。        

图1:通讯失败的MicrSD卡模块
图2:通讯成功的Microsd卡模块

         一开始用图1的MicroSD卡模块,有弹簧自锁的,还有3.3v稳压器,电路设计比图二稳当,但到了ACMD41初始化时尝试5000次都总是失败,CMD0和CMD8倒是正常。换过其他sd卡试过,耗了我好多天后,终于下单换了图二的模块,简陋一点,但原代码测试ACMD41就通过了,R1返回0x00。

        我用这种256MB的便宜小卡,属于SDSC。 

        SD卡结构分为控制寄存器和储存单元,我们的指令就是操作控制寄存器。

二、SD卡手册关于指令、参数、response的说明

2.1 指令和参数

#define pin_CS 9
#define pin_MOSI 10
#define pin_MISO 12
#define pin_CLK 11

/*CMD type,包含从命令到CRC共48bits*/
#define CMD0 	0x400000000095
#define CMD1	0x4100000000FF
#define CMD8 	0x48000001AA87
#define CMD55 	0x770000000065
#define ACMD41	0x694000000077  
#define CMD58	0x7A00000000FF
#define CMD10	0x4A00000000FF  //接收CID信息
#define CMD12	0x4C00000000FF	//中止读写
#define CMD17	0x5100000000FF  //读1个block 512bytes
#define CMD18   0x5200000000FF  //读多个blocks
#define CMD24   0x5800000000FF  //写1block
#define CMD25   0x5900000000FF	//写多blocks
#define CMD13	0x4D00000000FF	//写存储后查看卡status,R2响应
#define CMD32	0x6000000000FF	//erase start addr
#define CMD33	0x6100000000FF	//erase end addr
#define CMD38	0x6600000000FF	//erase all

/*response type, the figure indicates length in bytes*/
typedef enum _response_t{R1=1,R7=5,R2=17,R3=5}response_t;

        以上就是SD卡常用操作指令,而本篇需要用到的指令到CMD58为止,就能完成SD卡物理初始化过程,指令按出场顺序排好了。       

        说一下指令的数值构成。CMD0的数值就是0,CMD8就是8,CMD10就是十进制的10。以CMD10为例,10转变成十六进制就是0x0A,加上0x40就是0x4A了。所有指令都是从0x40开始加上去,看CMD0就知道了。

        ACMD41也是跟普通CMD命令一样计算。十进制的41就是十六进制的0x29,加到0x40上等于0x69。ACMD41的第三位可以是4也可以是0,小容量卡用0也可,大容量卡用4,其实无论你用什么都不影响SD卡回复你的消息。

        注意主机发出的所有指令,SD卡都会在后面回复一个response。

2.2 CRC计算

        从CMD0至ACMD41都要强调CRC计算准确,ACMD41之后的指令随意填个CRC就可,SD卡手册这样说的。因为SPI模式下,SD卡不检查CRC。但是ACMD41之前卡片都没完成初始化,SD卡不清楚主机采用哪种方式来沟通,因此要正确的CRC。        

        计算CRC的工具在这个网址:

CRC(循环冗余校验)在线计算_ip33.com

        command用CRC7的模式,计算工具使用过程如下:

        我这是用CMD55、0x770000000065作为例子。输入7700000000,选择CRC7模型,按计算按钮,在计算结果Bin栏看到结果是7bits,自己在最右边补一个1就是完整8位,补上1后,就是0x65了,所以CMD55的命令CRC应该是65。末尾补1是SD卡协议的要求,命令传输结束时末尾置1。

2.3 SD卡手册

        去SD卡联盟下载simplified版本的SD卡手册(我文章有提供下载,但可能被CSDN弄成付费资源)。几百页很繁琐,我提几个注意点。这个手册包含了几种传输协议:SDIO协议、SPI协议、UHS-II协议、PCIe协议,不要看花眼了,我一开始看了SDIO的协议,连寄存器和指令的操作都不太一样,response也不一样,浪费了时间。

        SD卡版本的话,我们知道近10年的卡都是2.0版本之后的就行了。

        SD卡分容量,SDSC是2GB以下,SDHC是大杯,SDXC是超大杯,SDUC是Ultra杯。

        SD卡状态的话,主要分为idle、ready、busy这几种重要状态,CMD8命令之前的阶段response返回0x01(idle)表示通过,ACMD41阶段要返回0x00才表示通过,不是所有阶段都是0x01,要看SD卡进入到哪个状态来判断。        

非SPI模式
SPI模式的ACMD41

        左图是SDIO模式的初始化指令ACMD41,右图是SPI模式的ACMD41指令。SPI环节都讲得很简略,可以看到右图ACMD41参数0至31bits大部分都是置0,而左图的参数包含了FB、XPC、OCR等要设置成有用的数值。

        左图的Response是R3类型,48bits,右图的response类型只是R1,7bits。所以差别很大,不要看花眼啦。我们只看SPI有关的。

        除此外,网上有很多文章提到的指令如CMD3用来切换到data transfer阶段,实际上是不能用在SPI模式的,张冠李戴。又比如CMD7对RCA(相对偏移地址)设置,SPI模式是不用设置这个的。看下图第2列,不是所有指令都适合在SPI模式使用的,第二列写NO的不适合SPI模式。

        

        网上也有不少文章提到R1的格式是48bits,实际上SPI不需要校验CRC,SPI模式的R1只有8bits。看下图。

        

        很多人文章都是复制粘贴过来,鱼龙混杂。

三、ESP-IDF的SPI如何配置

        先列一下参数,我使用的是四线硬件SPI,一个时钟周期传输1bit,全双工,开DMA。SD卡的SPI接口也只有四线的形式,要更快的话可能要用SDIO传输协议?四线指CS、CLK、MOSI、MISO,更多线的SPI会体现在多几条数据传输线,可以一次传好几bit。

3.1 主机SPI接口

        ESP32S3配备了4个SPI外设硬件,我们能用的只有SPI2和SPI3,选channel时记得在这两个channel里面选。要更快的话就选SPI2,能直接连接IOMUX,但要特定GPIO引脚;SPI3只能通过GPIO MATRIX传输,慢一点,但可随意指定GPIO引脚,其实速度也有20Mhz,对SD卡来讲足够快了。

3.2 SPI传输协议

        传输格式为command+address+dummy+data四块,实际上对硬件来说无区别,都是发送0101这些bit,因此这四块都不是必选,我的话前三块都不用,只发送data,把SD卡的指令、参数、crc都编成一条data发出。区别就是在传输函数内自己写多几行简单代码处理一下data的先后顺序。

        SPI传输很简单,把CS线拉低电平后就可以开始传输正文data内容,要结束的话拉高CS。如要接收8bits内容的话,主机也是做发送操作,发送内容为8个0xFF字节,就是8个dummy,然后在接收数组上取出内容即可。发的同时立马就收,发1bit就自动同步收1bit。

        SPI传输内容长度无限制。    

3.3 SPI代码配置

        下文有完整代码,这儿先分析一下如何配置SPI。

        要配置3块。分别是主机SPI bus配置、从机device特性配置、每次数据传输交易的配置。

        第一块是spi bus。如下代码,就是选择引脚号码、选择主机或从机身份、选择GPIO MATRIX或IOMUX方式。调用initialize()函数选择channel 2。

spi_bus_config_t buscnf = {
      .mosi_io_num = pin_MOSI,
      .miso_io_num = pin_MISO,
      .sclk_io_num = pin_CLK,
      .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS,
      };
spi_bus_initialize(SPI2_HOST,&buscnf,SPI_DMA_CH_AUTO);

        第二块是spi device。不同从机设备的通讯要求都是有些不同的,所以要配置一下。比如命令格式是多少位、参数格式是多少位、通讯时钟速率等,参考SD卡的数据手册来定义。由于CS选择线我是手动上下拉,所以这儿将.spics_io_num配置为-1,不需要SPI硬件自动帮我处理cs了。另外我将command、address长度设为0,因为我自己来编制指令,全部塞在data段里发送,不需要SPI设备操心。

        注意device_handledevicecnf,作为全局变量,因为这个handle到后面发生数据传输时要用起来。

spi_device_handle_t device_handle;    
    spi_device_interface_config_t devicecnf={
        .command_bits=0,
        .address_bits=0,  
        .spics_io_num=-1,
		.clock_speed_hz=400000,
		.queue_size=1,
    };
    spi_bus_add_device(SPI2_HOST,&devicecnf,&device_handle);

        前两块都是一次过配置好,后面基本就不会动的了。

        第三块有点特殊,是transaction_t,每次发生数据传输都要再配置一次。下面用发送一个dummy(0xFF)为例。定义一个spi_transaction_t结构体,里面的属性就是关于本次要传输交易的数据特性。发送长度是1byte即8bits,length设为8。tx_buffer指向要发送的内容。.rx_buffer指定用哪个数组来接收内容。

        配置好这三块后,就可以用spi_device_polling_transmit()函数将内容发出去啦。其实写和读都是要调用spi_device_polling_transmit这个传输函数,区别在于读内容时发送的是dummy字节,写内容时不关心rx_buff接收什么信息,聚焦点不同。

	uint8_t dummy=0xFF;
	uint8_t re_buff[1];
	spi_transaction_t transcnf={
		.length=8*1,
		.tx_buffer=&dummy,
		.rx_buffer=re_buff,
	};

spi_device_polling_transmit(device_handle,&transcnf);

        这儿要提到ESP32的SPI的两种传输机制,一种是轮询Polling,一种是中断。我用的是轮询,就是MCU要一直等到数据传输完毕才切换去别的任务,专一。如果是中断方式传输,就调用spi_device_transmit()函数,会将传输内容和任务放在Freertos里面排队,数据传输过程MCU允许去干别的任务,传输结束时触发中断提醒你处理。轮询方式实时性强能优先处理SPI传输任务。中断方式的配置要麻烦一些,要在transaction_t里面配置回调函数。

        按我以上的配置,用示波器检查过,spi_device_polling_transmit()只会发送我要他发的字节,不会拉高拉低cs线。考虑到SD卡上电时序等非常规操作,所以我选择手动控制CS线。

3.4 关于SPI读信息

        MOSI发送dummy,MISO接收要读的信息,这是SPI读信息的过程。我测试了以下几种情况分享一下:

(1)如下代码,若transaction_t里的tx_buffer指向1字节的dummy,而想接收10字节信息,结果是会接收到一些奇怪的信息,不完全是我们想要的。

	uint8_t dummy=0xFF;
	uint8_t re_buff[10];
	spi_transaction_t transcnf={
		.length=80,
		.tx_buffer=&dummy,
		.rx_buffer=re_buff,
	};

(2)如下代码,若transaction_t里根本没定义tx_buffer,想要接收10字节信息,结果会是空,什么信息都接收不到。

	uint8_t re_buff[10];
	spi_transaction_t transcnf={
		.length=80,
		.rx_buffer=re_buff,
	};

        结论是,dummy也要谨慎处理,最正确就是一致长度的0xFF dummy。

四、SD卡操作时序

4.1 初始化程序的时序

        2.0版本以上的SD卡可以概括为以下初始化时序:

        上电:CS保持拉高,主机发送74个bits的数据,最好是15个dummy即15个0xFF,帮助SD卡完成上电电压爬升阶段,因为是同步通讯,一般从机设备都需要主机发时钟过去协调其内部程序执行。我发现发多一些dummy会帮助下一阶段稳定获得正常response。

        复位命令:CS拉低,主机发送CMD0,0x400000000095,CS拉高。这阶段有可能主机连着很多张SD卡,所以这个CMD0是个广播命令,让所有SD卡复位,进入idle状态。同时这个操作高速SD卡接下来是用SPI模式。R1值是0x01表示成功。

        电压检查:CS拉低,主机发送CMD8,0x48000001AA87,CS拉高。CMD8又叫SEND_IF_COND,IF代表interface,就是双方的接口检查,主要是电压对接。这儿也是一条广播命令。01代表主机告诉从机我支持3.3V电压。AA只是一个check pattern,相当于暗号,设置成别的也行,但设别的话,CRC也要记得改,这个AA暗号作用就是检查SPI通讯是否准确,若正常,response R3也会带着AA暗号。R3的最高一位字节也是R1,最高位返回0x01表示成功。如果返回其他值,可以重复发CMD8,如果也不行,考虑是MMC卡或1.0版本的卡。

        卡片初始化:CS拉低,主机发送CMD55,0x770000000065,CS拉高;CS拉低,主机发送ACMD41,0x694000000077,CS拉高。因为发任何ACMD命令前都要发CMD55。期望ACMD41返回的response R1是0x00,代表脱离了idle阶段进入ready阶段。如果ACMD41一直不返回0x00,而是0x01或其他值,就要重复这个CMD55+ACMD41的发送操作。我这边重复到第3次就成功收到0x00了。注意初始化不是格式化,卡片的内容不会被清除。这一步前,都要注意CRC计算准确。初始化是主机跟某张SD卡建立一对一专线通讯过程的操作。

        检查初始化是否成功:CS拉低,主机发送CMD58,0x7A00000000FF,CS拉高。这一步不是必须的,只是个检查。接收R3,0x00 80 FF 80 00表示成功。R3里面除了最高一位是R1,剩余就是卡片的OCR寄存器的信息,读OCR我们主要看两个参数。第30位CCS,SDSC低容量卡片就是0,SDHC以上卡片(2GB)就是1。第31位power up status位,如果ACMD41步骤不成功,这个位就是0,如果初始化成功这个位就是1。我的实操返回结果是0x00 80 FF 80 00。OCR寄存器看下图:

        到此为止,卡片初始化就完成,主机完成了从多张SD卡里面选择出这一张SD卡的程序。后面的通讯就不再是广播了,都是针对这张卡地址的通讯,包括读SD卡容量出厂信息、读写数据等,都是直接操作读写命令即可,不用再做上述初始化步骤。       

4.2 数据传输的时序

        具体一条指令和响应怎样传输和接收?

       注意SD卡的响应不是同步接收的。command和response之间是有空档的,经过实测,间隔为一个dummy字节。发一个指令,接收一个response的时序应该如下:

        (1)CS拉低

        (2)主机MOSI发48bits内容,指令+参数+CRC

        (3) 主机MOSI发一字节dummy即0xFF

        (4)主机MOSI根据response的长度比如5字节就发5字节dummy,同时在.rx_buffer上读取response

        (5)CS拉高

        (6)主机发送一字节dummy协助卡片处理其他事宜,结束。

五、初始化部分的代码

        这是自建的component中的fatfs2.c文件

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "driver/sdspi_host.h"

static spi_device_handle_t device_handle;
static spi_device_interface_config_t devicecnf;
uint8_t tx_dummy[514];

void cs_enable()
{
    gpio_set_level(pin_CS,0);
}
void cs_disable()
{
    gpio_set_level(pin_CS,1);
}

esp_err_t spi_init()
{
    esp_err_t ret;
    gpio_config_t gpiocnf={
		.pull_up_en=GPIO_PULLUP_ENABLE,
        .mode=GPIO_MODE_OUTPUT_OD,
        .pin_bit_mask=1UL << pin_CS,
    };
    gpio_config(&gpiocnf);

    static spi_bus_config_t buscnf = {
        .mosi_io_num = pin_MOSI,
        .miso_io_num = pin_MISO,
        .sclk_io_num = pin_CLK,
        .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS,
    };
    ret=spi_bus_initialize(SPI2_HOST,&buscnf,SPI_DMA_CH_AUTO);
    ESP_ERROR_CHECK(ret);

    static spi_device_interface_config_t devicecnf={
        .command_bits=0,
        .address_bits=0,  
        .spics_io_num=-1,
		.clock_speed_hz=400000,
		.queue_size=1,
    };
    ret=spi_bus_add_device(SPI2_HOST,&devicecnf,&device_handle);

    memset(tx_dummy,0xFF,sizeof(uint8_t)*514);//初始化tx_dummy
    return ret;
}

/*MOSI发len个字节的0xFF,手动控制cs线*/
esp_err_t send_dummy(uint8_t len)
{
	spi_transaction_t transcnf={
		.length=8*len,
		.tx_buffer=&tx_dummy,
	};
	spi_device_polling_transmit(device_handle,&transcnf);
	return(ESP_OK);
}

/*手动控制cs,传入48bits的cmd包含crc*/
esp_err_t write_cmd(uint64_t cmd) 
{
    esp_err_t ret;
	/*cmd转化成6byte数组*/
	uint8_t buff[6];
	for(uint8_t i=0;i<6;i++)
	{
		buff[i]=(uint8_t)(cmd >> (5-i)*8);
	}
	spi_transaction_t transcnf={
		.length=48,
		.tx_buffer=buff,
	};
    ret=spi_device_polling_transmit(device_handle,&transcnf);
    return(ret);
}


/*response type, the figure indicates length in bytes*/
typedef enum _response_t{R1=1,R7=5,R2=2,R3=5}response_t;

/*MOSI发0xFF等候response*/
void wait_response(response_t response)
{
	uint8_t re_buff[response];
	spi_transaction_t transcnf={
		.length=8*response,
		.rx_buffer=re_buff,
		.tx_buffer=&tx_dummy,
	};
	send_dummy(1);  //先发一个dummy
	spi_device_polling_transmit(device_handle,&transcnf);
	printf("Response is:0x");
	for(int i=0;i<response;i++)
	{
		printf("%02X ",re_buff[i]);
	}
	printf("\n");	
}

void speed_change()
{
	devicecnf.clock_speed_hz =20000*1000,
    spi_bus_add_device(SPI2_HOST,&devicecnf,&device_handle);
}

主程序main.c代码如下:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "driver/sdspi_host.h"

void card_init()
{
    spi_init();
	/*模拟sd卡上电*/
	cs_disable();
	send_dummy(20);
	printf("上电发74时钟:\n\n");
	vTaskDelay(5 / portTICK_PERIOD_MS);

	/*发送cmd0,带R1*/
	cs_enable();
	write_cmd(CMD0);
	printf("写入cmd0结果:\n");
	wait_response(R1);
	send_dummy(1);
	cs_disable();
	send_dummy(1);
	printf("\n");
	vTaskDelay(5 / portTICK_PERIOD_MS);

	/*发cmd8识别是否2.0卡*/
	cs_enable();
	write_cmd(CMD8);
	printf("写入cmd8结果:\n");
	wait_response(R7);
	send_dummy(1);
	cs_disable();
	send_dummy(1);
	printf("\n");
	vTaskDelay(5 / portTICK_PERIOD_MS);

	/*发cmd55准备为acmd41做准备*/
	for (uint32_t i = 0; i < 5; i++)
	{

		cs_enable();
		write_cmd(CMD55);
		printf("写入CMD55:\n");
		wait_response(R1);
		cs_disable();
		send_dummy(1);

		/*发ACMD41初始化卡*/
		cs_enable();
		write_cmd(ACMD41);
		printf("写入ACMD41初始化\n");
		wait_response(R1);
		cs_disable();
		send_dummy(1);
		vTaskDelay(1 / portTICK_PERIOD_MS);
	}

	printf("\n");

	/*发cmd58*/
	cs_enable();
	write_cmd(CMD58);
	printf("写入cmd58结果:\n");
	wait_response(R3);
	cs_disable();
	send_dummy(1);
	printf("\n");

	/*传输模式改成高速*/
	printf("传输模式改成高速.\n");
	speed_change();
}

void app_main(void)
{
    card_init();
}

六、输出结果

2024-06-13 16:48:13 上电发74时钟:ESP_OK
2024-06-13 16:48:13
2024-06-13 16:48:14 写入cmd0结果:
2024-06-13 16:48:14 response is:0x01
2024-06-13 16:48:14
2024-06-13 16:48:15 写入cmd8结果:
2024-06-13 16:48:15 response is:0x01 00 00 01 AA
2024-06-13 16:48:15
2024-06-13 16:48:16 写入CMD55:
2024-06-13 16:48:16 response is:0x01
2024-06-13 16:48:16 写入ACMD41初始化
2024-06-13 16:48:16 response is:0x01
2024-06-13 16:48:16 写入CMD55:
2024-06-13 16:48:16 response is:0x01
2024-06-13 16:48:16 写入ACMD41初始化
2024-06-13 16:48:16 response is:0x01
2024-06-13 16:48:16 写入CMD55:
2024-06-13 16:48:16 response is:0x01
2024-06-13 16:48:16 写入ACMD41初始化
2024-06-13 16:48:16 response is:0x00
2024-06-13 16:48:16 写入CMD55:
2024-06-13 16:48:16 response is:0x00 
2024-06-13 16:48:16 写入ACMD41初始化
2024-06-13 16:48:16 response is:0x00
2024-06-13 16:48:16 写入CMD55:
2024-06-13 16:48:16 response is:0x00
2024-06-13 16:48:16 写入ACMD41初始化
2024-06-13 16:48:16 response is:0x00
2024-06-13 16:48:16
2024-06-13 16:48:21 写入cmd58结果:
2024-06-13 16:48:21 response is:0x00 80 FF 80 00
2024-06-13 16:48:21

       

七、故障解读

         若从CMD0开始,response都是接收0xFF的反馈,则可以检查以下情况:

(1)硬件接线是否接错引脚,是否有线虚接,是否正常通电,电压是否正确;

(2)检查dummy是否正常,包括dummy内容是否初始化为0xFF,dummy长度是否正确;

(3)SD卡或读卡模块是否物理上已损坏;

        若CMD0~CMD8正确接收0x01的response,从ACMD41开始都是0x01,表示一直在忙,则却确定SPI通讯是正确的,要检查:

(1)SD卡模块引脚是否电阻不对;

(2)SD卡种类是否不对;

(3)由于ACMD41的response是4字节,查看spi_transaction_t定义的数据传输长度和dummy是否设置正确。

        这篇解决了指令操作和SD卡的物理初始化过程,下一篇就讲SD卡读写数据内容。

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 ESP32 上使用 SPI 接口与 ADXL355 加速度计进行通信,您可以按照以下步骤进行操作: 1. 确保您已经正确连接了 ADXL355 加速度计到 ESP32SPI 总线上。这通常包括将 SPI 时钟线(SCK)、主输入/输出线(MISO/MOSI)和片选线(CS)连接到合适的引脚上。 2. 在 ESP32 IDF 中配置 SPI 总线和引脚。您需要使用 `spi_bus_initialize()` 初始化 SPI 总线,并使用 `spi_bus_add_device()` 添加 SPI 设备。在添加设备时,您需要设置 SPI 总线的引脚配置、通信速度等参数。 3. 使用 ESP32 IDF 提供的 SPI 函数与 ADXL355 进行通信。首先,您需要使用 `spi_device_acquire_bus()` 获取 SPI 总线的访问权限。然后,您可以使用 `spi_device_transmit()` 或 `spi_device_polling_transmit()` 函数发送命令和接收数据。 4. 了解 ADXL355 的寄存器映射和通信协议。ADXL355 使用 SPI 接口进行配置和数据读取。您需要根据 ADXL355 的数据手册使用正确的命令和寄存器地址来进行读取和写入。 下面是一个简单的示例代码,演示如何在 ESP32 上通过 SPI 接口读取 ADXL355 的设备 ID 寄存器: ```c #include <stdio.h> #include "driver/spi_master.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define SPI_BUS HSPI_HOST #define SPI_DEV 0 #define CS_PIN 15 spi_device_handle_t spi; void adxl355_init() { spi_bus_config_t bus_cfg = { .mosi_io_num = 13, .miso_io_num = 12, .sclk_io_num = 14, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 0, }; spi_bus_initialize(SPI_BUS, &bus_cfg, SPI_DEV); spi_device_interface_config_t dev_cfg = { .command_bits = 0, .address_bits = 8, .dummy_bits = 0, .mode = 3, .duty_cycle_pos = 128, .cs_ena_pretrans = 0, .cs_ena_posttrans = 0, .clock_speed_hz = 1000000, // 设置 SPI 时钟速度 .input_delay_ns = 0, .spics_io_num = CS_PIN, // 片选线引脚 .flags = 0, .queue_size = 1, .pre_cb = NULL, .post_cb = NULL, }; spi_bus_add_device(SPI_BUS, &dev_cfg, &spi); } void adxl355_read_device_id() { uint8_t cmd_byte = 0x01; // 读设备 ID 的命令字节 spi_transaction_t trans = { .length = 8, // 命令字节长度为 8 .tx_buffer = &cmd_byte, .rx_buffer = NULL, // 在此示例中我们不接收数据,如果需要接收数据,可以提供接收缓冲区 }; spi_device_acquire_bus(spi, portMAX_DELAY); spi_device_transmit(spi, &trans); spi_device_release_bus(spi); printf("Device ID: 0x%02X\n", cmd_byte); } void app_main() { adxl355_init(); adxl355_read_device_id(); vTaskDelay(portMAX_DELAY); } ``` 请注意,这只是一个简单的示例,您可能需要根据 ADXL355 的具体需求进行更多的配置和数据读取操作。同时,确保您已经正确配置了 SPI 总线和引脚,并根据实际情况进行相应的更改。 希望这可以帮助您开始使用 ESP32 和 ADXL355 进行 SPI 通信。如果您有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值