上海备案证查询网站,南宁seo建站,苏宁电器网上商城,建设部网站关于公租房目录 SPISPI介绍idf配置初始化配置通信 驱动代码 SPI
SPI介绍
详细SPI介绍内容参考我之前写的内容【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】
idf配置
初始化配置
spi_bus_initialize() 参数1 #xff1a;spi几#xff0c;例如spi2,spi3
参数2#xff1a;… 目录 SPISPI介绍idf配置初始化配置通信 驱动代码 SPI
SPI介绍
详细SPI介绍内容参考我之前写的内容【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】
idf配置
初始化配置
spi_bus_initialize() 参数1 spi几例如spi2,spi3
参数2指向spi_bus_config_t的结构体该结构体有较多值取自己用的就行 参数3是否使用DMA
对于参数2有如下成员变量
typedef struct {int miso_io_num; // MISO引脚号int mosi_io_num; // MOSI引脚号int sclk_io_num; // 时钟引脚号int quadwp_io_num; // 用于Quad模式的WP引脚号未使用时设置为-1int quadhd_io_num; // 用于Quad模式的HD引脚号未使用时设置为-1int max_transfer_sz; // 最大传输大小
} spi_bus_config_t;设备配置 spi_bus_add_device() 参数1和上面的一样选择spi几 参数2指向设备的结构体 参数3返回该设备的句柄
其中参数2成员变量如下
参数二的结构体的成员变量也很多我们一样是挑着用上的配置。 spi_device_handle_t dev_handle;spi_device_interface_config_t device_initer{.command_bits0,//指令的位数.address_bits0,//地址的位数.mode0,//模式0spi四种模式.spics_io_num15,//ss/cs片选引脚号.clock_speed_hz1000*1000//时钟通信频率};if(spi_bus_add_device(SPI2_HOST,dev_handle,dev_handle)!ESP_OK) printf(add device success\r\n);通信
spi_device_transmit() 参数二的结构体成员变量也不少但是我们配置好要发送的数据和长度以及接收数据的地方和长度即可。 举例
uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle)
{//00 定义错误标志esp_err_t e;//01 接收数据的时候发送的空指令uint8_t data[3];data[0] 0xff;data[1] 0xff;data[2] 0xff;//02 定义用于返回的数据uint32_t Temp;//03 定义数据发送接收结构体spi_transaction_ext_t ext; //因为读取设备ID的指令结构与前面定义的默认的不一样所以需要更改位长memset(ext, 0, sizeof(ext)); //初始化结构体ext.command_bits 8; //指令位长度为8ext.address_bits 0; //地址位长度为0ext.base.cmd W25X_JedecDeviceID; //设备IDext.base.length 3 * 8; //要发送数据的长度ext.base.tx_buffer data; //要发送数据的内容ext.base.rx_buffer NULL; //接收数据buffer使用结构体内部带的ext.base.flags SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA; //04 数据收发espi_device_polling_transmit(handle, ext.base);if (e ! ESP_OK){printf(get ID error!\n);return 0;}//05 返回获取的数据IDuint8_t temp0 ext.base.rx_data[0];uint8_t temp1 ext.base.rx_data[1];uint8_t temp2 ext.base.rx_data[2];Temp (temp0 16) | (temp1 8) | temp2;
驱动代码
w25q64.c
#include W25Q64.h/*********************************************************** 函 数 名 称w25q64_init_config* 函 数 功 能w25q64初始化* 传 入 参 数无* 函 数 返 回无* 备 注无
**********************************************************/
esp_err_t w25q64_init_config(spi_device_handle_t* handle)
{ //00 定义错误标志esp_err_t e;//01 配置总线初始化结构体static spi_bus_config_t bus_cfg; //总线配置结构体bus_cfg.miso_io_num Flash_SPI_MISO; //misobus_cfg.mosi_io_num Flash_SPI_MOSI; //mosibus_cfg.sclk_io_num Flash_SPI_SCLK; //sclkbus_cfg.quadhd_io_num Flash_SPI_HD; // HDbus_cfg.quadwp_io_num Flash_SPI_WP; // WPbus_cfg.max_transfer_sz 4092; //非DMA最大64bytes,DMA最大4092bytes//bus_cfg.intr_flags 0; //这个用于设置中断优先级的0是默认bus_cfg.flags SPICOMMON_BUSFLAG_MASTER;//这个用于设置初始化的时候要检测哪些选项。比如这里设置的是spi初始化为主机模式是否成功。检测结果通过spi_bus_initialize函数的//返回值进行返回。如果初始化为主机模式成功就会返回esp_ok//02 初始化总线配置结构体e spi_bus_initialize(Flash_SPI, bus_cfg, SPI_DMA_CH_AUTO);if (e ! ESP_OK){printf(bus initialize failed!\n);return e;}//03 配置设备结构体static spi_device_interface_config_t interface_cfg; //设备配置结构体interface_cfg.address_bits Flash_Address_Bits; //配置地址位长度//(1)如果设置为0在通讯的时候就不会发送地址位。//(2)如果设置了非零值就会在spi通讯的地址发送阶段发送指定长度的address数据。//如果设置了非零值并且在后面数据发送结构体中没有定义addr的值会默认发送指定长度0值//(3)我们后面发送数据会使用到spi_transaction_t结构体这个结构体会使用spi_device_interface_config_t中定义好address、command和dummy的长度//如果想使用非固定长度就要使用spi_transaction_ext_t结构体了。这个结构体包括了四个部分包含了一个spi_transaction_t和address、command、dummy的长度。//我们要做的就是在spi_transaction_ext_t.base.flags中设置SPI_TRANS_VARIABLE_ADDR/CMD/DUMMY//然后定义好这三部分数据的长度然后用spi_transaction_ext_t.base的指针代替spi_transaction_t的指针即可interface_cfg.command_bits Flash_Command_Bits; //配置命令位长度//与address_bits是一样的interface_cfg.dummy_bits Flash_Dummy_Bits; //配置dummy长度//这里的配置方法与address_bits是一样的。但是要着重说一下这个配置的意义后面会再说一遍//(1)dummy_bits是用来用来补偿输入延迟。//(2)在read phase开始阶段之前被插入进去。在dummy_bits的时钟下并不进行数据读取的工作//相当于这段时间发送的clock都是虚拟的时钟并没有功能。在输入延迟最大允许时间不够的时候可以通过这种方法进行配置从而//能够使得系统工作在更高的时钟频率下。//(3)如果主机设备只进行write操作可以在flags中设置SPI_DEVICE_NO_DUMMY关闭dummy bits的发送。只有写操作的话即使使用了gpio交换矩阵,时钟周期也可以工作在80MHZ//interface_cfg.input_delay_ns 0; //配置输入延时的允许范围//时钟发出信号到miso进行输入直接会有延迟这个参数就是配置这个允许的最大延迟时间。//如果主机接收到从机时钟但是超过这个时间没有收到miso发来的输入信号就会返回通讯失败。//这个时间即使设置为0也能正常工作但是最好通过手册或逻辑分析仪进行估算。能够实现更好的通讯。//超过8M的通讯都应该认真设置这个数字interface_cfg.clock_speed_hz Flash_CLK_SPEED; //配置时钟频率//配置通讯的时钟频率。//这个频率受到io_mux和input_delay_ns限制。//如果是io直连的时钟上限是80MHZ如果是gpio交换矩阵连接进来的,时钟上限是40MHZ。//如果是全双工时钟上限是26MHZ。并且还要考虑输入延时。在相同输入延时的条件下使用gpio交换矩阵会比使用io mux最大允许的时钟频率小。可以通过//spi_get_freq_limit()来计算能够允许的最大时钟频率是多少//有关SPI通讯时钟极限和配置的问题后面会详细说一下。interface_cfg.mode 0; //设置SPI通讯的相位特性和采样边沿。包括了mode0-3四种。要看从设备能够使用哪种模式interface_cfg.spics_io_num Flash_SPI_CS; //配置片选线interface_cfg.duty_cycle_pos 0; //配置占空比//设置时钟的占空比比例是 pos*1/256,默认为0也就是50%占空比//interface_cfg.cs_ena_pretrans; //在传输之前片选线应该保持激活状态多少个时钟只有全双工的时候才需要配置//interface_cfg.cs_ena_posttrans; //在传输之后片选线应该保持激活状态多少个时钟只有全双工的时候才需要配置interface_cfg.queue_size 6; //传输队列的长度表示可以在通讯的时候挂起多少个spi通讯。在中断通讯模式的时候会把当前spi通讯进程挂起到队列中//interface_cfg.flags; //配置与从机有关的一些参数比如MSB还是LSB使不使用三线SPI//interface_cfg.pre_cb; //配置通讯前中断。比如不在这里配置cs片选线把片选线作为自行控制的线把片选线拉低放在通讯前中断中//interface_cfg.post_cb;//配置通讯后中断。比如不在这里配置cs片选线把片选线作为自行控制的线把片选线拉高放在通讯前中断中//04 设备初始化e spi_bus_add_device(Flash_SPI, interface_cfg, handle);if (e ! ESP_OK){printf(device config error\n);return e;}return ESP_OK;
}uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle)
{//00 定义错误标志esp_err_t e;//01 接收数据的时候发送的空指令uint8_t data[3];data[0] Dummy_Byte;data[1] Dummy_Byte;data[2] Dummy_Byte;//02 定义用于返回的数据uint32_t Temp;//03 定义数据发送接收结构体spi_transaction_ext_t ext; //因为读取设备ID的指令结构与前面定义的默认的不一样所以需要更改位长memset(ext, 0, sizeof(ext)); //初始化结构体ext.command_bits 8; //指令位长度为8ext.address_bits 0; //地址位长度为0ext.base.cmd W25X_JedecDeviceID; //设备IDext.base.length 3 * 8; //要发送数据的长度ext.base.tx_buffer data; //要发送数据的内容ext.base.rx_buffer NULL; //接收数据buffer使用结构体内部带的ext.base.flags SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA; //04 数据收发espi_device_polling_transmit(handle, ext.base);if (e ! ESP_OK){printf(get ID error!\n);return 0;}//05 返回获取的数据IDuint8_t temp0 ext.base.rx_data[0];uint8_t temp1 ext.base.rx_data[1];uint8_t temp2 ext.base.rx_data[2];Temp (temp0 16) | (temp1 8) | temp2;return Temp;
}/**
* breif flash写使能。在执行页写入和擦除命令之前都必须执行一次页写入
* param[in] handle: 提供SPI的操作句柄
* retval 无
**/void bsp_spi_flash_WriteEnable(spi_device_handle_t handle)
{esp_err_t e; //错误标志位// 定义数据发送接收结构体spi_transaction_ext_t ext; //写使能的长度与默认的不同需要修改memset(ext, 0, sizeof(ext)); //初始化结构体ext.command_bits 8; //指令位长度为8ext.address_bits 0; //地址位长度为0ext.base.cmd W25X_WriteEnable; //写使能ext.base.length 0; //要发送数据的长度这里不需要发送数据ext.base.flags SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;//发送指令e spi_device_polling_transmit(handle, ext.base);if (e ! ESP_OK){printf(write enable failed!\n);}}/**
* breif 等待flash完成当前操作
* param[in] handle: 提供SPI的操作句柄
* retval 无
**/void bsp_spi_flash_WaitForWriteEnd(spi_device_handle_t handle)
{// 定义数据发送接收结构体spi_transaction_ext_t ext; //写使能的长度与默认的不同需要修改memset(ext, 0, sizeof(ext)); //初始化结构体ext.command_bits 8; //指令位长度为8ext.address_bits 0; //地址位长度为 0ext.base.cmd W25X_ReadStatusReg; //读取状态寄存器ext.base.length 1 * 8; //要发送数据的长度这里不需要发送数据ext.base.rx_buffer NULL; //不使用外部数据ext.base.tx_buffer NULL; //不使用外部数据ext.base.tx_data[0] Dummy_Byte; //发送数据ext.base.flags SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;do{//发送指令spi_device_polling_transmit(handle, ext.base);}while ( (ext.base.rx_data[0] WIP_Flag ) WIP_SET);
}/**
* breif 扇区擦除
* param[in] handle: 提供SPI的操作句柄
* param[in] SectorAddr: 要擦除的起始扇区地址
* retval 无
**/void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr)
{bsp_spi_flash_WriteEnable(handle);bsp_spi_flash_WaitForWriteEnd(handle);// 定义数据发送接收结构体spi_transaction_t t; //配置位与默认一致不需要修改memset(t, 0, sizeof(t)); //初始化结构体t.cmd W25X_SectorErase; //擦除指令t.addr SectorAddr; //擦除地址t.length 0; //不需要额外数据了//发送指令spi_device_polling_transmit(handle, t);//等待擦除完毕bsp_spi_flash_WaitForWriteEnd(handle);
}/**
* breif 页写入
* param[in] handle: 提供SPI的操作句柄
* param[in] pBuffer:要写入的数据地址
* param[in] WriteAddr:要写入的地址
* param[in] NumByteToWrite: 要写入的长度
* retval 无
**/void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{bsp_spi_flash_WriteEnable(handle);// 定义数据发送接收结构体spi_transaction_t t; //配置位与默认一致不需要修改memset(t, 0, sizeof(t)); //初始化结构体t.cmd W25X_PageProgram; //页写入t.addr WriteAddr; //擦除地址t.length 8*NumByteToWrite; //写入长度t.tx_buffer pBuffer; //写入的数据t.rx_buffer NULL; //不需要读取数据if (NumByteToWrite SPI_Flash_PageSize){printf(length is too long!\n);return ;}//发送指令spi_device_polling_transmit(handle, t);//等待擦除完毕bsp_spi_flash_WaitForWriteEnd(handle);}/**
* breif 不定量数据写入
* param[in] handle: 提供SPI的操作句柄
* param[in] pBuffer:要写入的数据地址
* param[in] WriteAddr:要写入的地址
* param[in] NumByteToWrite: 要写入的长度
* retval 无
**/void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint8_t NumOfPage 0, NumOfSingle 0, Addr 0, count 0, temp 0;//进行取余运算查看是否进行了页对齐Addr WriteAddr % SPI_Flash_PageSize;//差count个数据值可以进行页对齐count SPI_Flash_PageSize - Addr;//计算要写多少个完整的页NumOfPage NumByteToWrite / SPI_Flash_PageSize;//计算剩余多少字节不满1页NumOfSingle NumByteToWrite % SPI_Flash_PageSize;//如果Addr0也就是进行了页对齐if (Addr 0){//如果写不满1页if (NumOfPage 0){bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);}else{//如果超过1页先把满的写了while (NumOfPage--){bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);WriteAddr SPI_Flash_PageSize;pBuffer SPI_Flash_PageSize;}//不满的1页再写bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);}}//如果没有进行页对齐else{if (NumOfPage 0){//如果当前页剩下的count个位置比NumOfSingle小1页写不完if (NumOfSingle count){//先把这页剩下的写了temp NumOfSingle - count;bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, count);WriteAddr count;pBuffer count;//再把多了的写了bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, temp);}else{//如果剩下的空间足够大就直接写bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);}}//如果不止1页else{//先把对不齐的字节写了NumByteToWrite - count;NumOfPage NumByteToWrite / SPI_Flash_PageSize;NumOfSingle NumByteToWrite % SPI_Flash_PageSize;bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr,count);//重复地址对齐的情况WriteAddr count;pBuffer count;while (NumOfPage--){bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);pBuffer SPI_Flash_PageSize;WriteAddr SPI_Flash_PageSize;}if (NumOfSingle ! 0){bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);}}}}/**
* breif 数据读取
* param[in] handle: 提供SPI的操作句柄
* param[out] pBuffer:要读取的数据buffer地址
* param[in] WriteAddr:要写入的地址
* param[in] NumByteToWrite: 要写入的长度
* retval 无
**/void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{bsp_spi_flash_WriteEnable(handle);// 定义数据发送接收结构体spi_transaction_t t; //配置位与默认一致不需要修改memset(t, 0, sizeof(t)); //初始化结构体t.cmd W25X_ReadData; //读取数据t.addr WriteAddr; //擦除地址t.length 8 * NumByteToWrite; //读取长度t.tx_buffer NULL; //不需要写入数据t.rx_buffer pBuffer; //读取数据//发送指令spi_device_polling_transmit(handle, t);//等待擦除完毕bsp_spi_flash_WaitForWriteEnd(handle);}
w25q64.h
/** Author: i want to 舞动乾坤* Date: 2024-07-27 09:26:08* LastEditors: i want to 舞动乾坤* LastEditTime: 2024-07-27 17:21:08* FilePath: \spi_hardware_driver_w25q64\main\W25Q64.h* Description: * * Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved. */
#ifndef __W25Q64_H__
#define __W25Q64_H__#include driver/spi_master.h
#include driver/spi_common.h
#include hal/gpio_types.h
#include string.h //定义Flash实验所需要的引脚
#define Flash_SPI SPI3_HOST
#define Flash_SPI_MISO GPIO_NUM_19
#define Flash_SPI_MOSI GPIO_NUM_23
#define Flash_SPI_SCLK GPIO_NUM_18
#define Flash_SPI_CS GPIO_NUM_5
#define Flash_SPI_WP -1
#define Flash_SPI_HD -1
#define Flash_SPI_DMA SPI_DMA_CH1//定义设备参数
#define Flash_CLK_SPEED 6 * 1000 * 1000 //6M的时钟
#define Flash_Address_Bits 3*8 //地址位长度
#define Flash_Command_Bits 1*8 //命令位长度
#define Flash_Dummy_Bits 0*8 //dummy位长度
#define SPI_Flash_PageSize 256 //页写入最大值//定义命令指令
#define W25X_JedecDeviceID 0x9F //获取flashID的指令
#define W25X_WriteEnable 0x06 //写入使能
#define W25X_WriteDisable 0x04 //禁止写入
#define W25X_ReadStatusReg 0x05 //读取状态寄存器
#define W25X_SectorErase 0x20 //扇区擦除
#define W25X_BlockErase 0xD8 //块擦除
#define W25X_ChipErase 0xC7 //芯片擦除
#define W25X_PageProgram 0x02 //页写入
#define W25X_ReadData 0x03 //数据读取#define Dummy_Byte 0xFF //空指令用于填充发送缓冲区
#define WIP_Flag 0x01 //flash忙碌标志位
#define WIP_SET 1 esp_err_t w25q64_init_config(spi_device_handle_t* handle);
uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle);void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr);
void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);#endif
main.c
/** Author: i want to 舞动乾坤* Date: 2024-07-27 09:14:50* LastEditors: i want to 舞动乾坤* LastEditTime: 2024-07-27 17:16:23* FilePath: \spi_hardware_driver_w25q64\main\main.c* Description: * * Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved. */#include stdio.h
#include inttypes.h
#include sdkconfig.h
#include freertos/FreeRTOS.h
#include freertos/task.h
#include esp_chip_info.h
#include esp_flash.h
#include W25Q64.h
#include esp_log.hspi_device_handle_t spi2_handle;
static const char * TAG Task;void app_main(void)
{uint8_t pBuffer[11] { 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0x00 };uint8_t rBuffer[11] {0};int id0;//配置W25Q64w25q64_init_config(spi2_handle);//读取器件IDidbsp_spi_flash_ReadID(spi2_handle);ESP_LOGI(TAG,id %X\r\n,id);//向W25Q64的地址0写入10个数据bsp_spi_flash_SectorErase(spi2_handle, 0);ESP_LOGI(TAG,Sector erase successfully\r\n);bsp_spi_flash_BufferWrite(spi2_handle, pBuffer, 0, 10);ESP_LOGI(TAG,Write successfully\r\n);//向W25Q64的地址0读取10个数据bsp_spi_flash_BufferRead(spi2_handle, rBuffer, 0, 10);for (int i 0; i 10; i){ESP_LOGI(TAG,0x%x , rBuffer[i]);}ESP_LOGI(TAG,.);while(1){ESP_LOGI(TAG,.);vTaskDelay(1000/portTICK_PERIOD_MS);}
}
项目结构
调试