2023年12月1日发(作者:oppor9手机怎么样)
STM32CubeMX学习笔记(10)——SPI接⼝使⽤(读写SPIFlashW25Q64)
⼀、SPI简介
SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串⾏外围设备接⼝,是⼀种⾼速全双⼯的通信总线。
它被⼴泛地使⽤在 ADC、LCD 等设备与 MCU 间,要求通讯速率较⾼的场合。
芯⽚的管脚上只占⽤四根线。
MISO: 主器件数据输出,从器件数据输⼊。
MOSI:主器件数据输⼊,从器件数据输出。
SCK: 时钟信号,由主设备控制发出。
NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。
⼆、引脚分布
STM32 芯⽚有多个 SPI 外设,它们的 SPI 通讯信号引出到不同的 GPIO 引脚上,使⽤时必须配置到这些指定的引脚。其中 SPI1 是
APB2 上的设备,最⾼通信速率达 36Mbtis/s,SPI2、SPI3 是 APB1 上的设备,最⾼通信速率为 18Mbits/s。除了通讯速率,在其它
功能上没有差异。
其中 SPI3 ⽤到了下载接⼝的引脚,这⼏个引脚默认功能是下载,第⼆功能才是 IO ⼝,如果想使⽤ SPI3 接⼝,则程序上必须先禁⽤掉这
⼏个 IO ⼝的下载功能。⼀般在资源不是⼗分紧张的情况下,这⼏个 IO ⼝是专门⽤于下载和调试程序,不会复⽤为 SPI3。
三、FLASH芯⽚
开发板中的 FLASH 芯⽚型号:W25Q64。W25Q 系列为台湾华邦公司推出的是⼀种使⽤ SPI 通讯协议的 NOR FLASH 存储器。芯⽚型
主机⾸先通过 MOSI 线向 FLASH 芯⽚发送第⼀个字节数据为“9F h”,当 FLASH 芯⽚收到该数据后,它会解读成主机向它发送
1. 打开 STM32CubeMX 软件,点击“新建⼯程”
3. 配置时钟
4. 配置调试模式
⾮常重要的⼀步,否则会造成第⼀次烧录程序后续⽆法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
五、SPI1
5.1 参数配置
在 中选择 设置,并选择 全双⼯主模式, 即不使⽤硬件⽚选信号
ConnectivitySPI1Full-Duplex Master不开启 NSS
原理图中虽然将 CS ⽚选接到了硬件 SPI1 的 NSS 引脚,因为硬件 NSS 使⽤⽐较⿇烦,所以后⾯直接把 PA4 配置为普通 GPIO,
⼿动控制⽚选信号。
在右边图中找到 SPI1 NSS 对应引脚,选择 。
GPIO_Output纠正:野⽕STM32F103指南者开发板SPI1 NSS须配置为PC0
修改输出⾼电平 ,标签为 。
HighW25Q64_CHIP_SELECT
SPI 为默认设置不作修改。只需注意⼀下, 分频系数最低为 ,波特率 (Baud Rate) 为 。这⾥被限制了,SPI1 最
Prescaler418.0 MBits/s
⾼通信速率可达 36Mbtis/s。
Clock Polarity(CPOL):SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为⾼电平时 SCK 的状
态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
Clock Phase(CPHA):指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边
沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
根据 FLASH 芯⽚的说明,它⽀持 SPI 及 ,⽀持双线全双⼯,使⽤ MSB 先⾏模式,数据帧长度为 8 位。
模式0模式 3
所以这⾥配置 CPOL 为 ,CPHA 为 即 SPI 。
Low1 Edge模式0
5.2 ⽣成代码
输⼊项⽬名和项⽬路径
选择应⽤的 IDE 开发环境 MDK-ARM V5
每个外设⽣成独⽴的 ⽂件
点击 GENERATE CODE ⽣成代码
5.3 封装SPI Flash(W25Q64)的命令和底层函数
向 SPI Flash 发送数据的函数
/**
* @brief SPI
发送指定长度的数据
* @param buf ——
发送数据缓冲区⾸地址
* @param size ——
要发送数据的字节数
* @retval HAL_OK
成功返回
*/
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{
return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);
}
从 SPI Flash 接收数据的函数
/**
* @brief SPI
接收指定长度的数据
* @param buf ——
接收数据缓冲区⾸地址
* @param size ——
要接收数据的字节数
* @retval HAL_OK
成功返回
*/
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);
}
发送数据的同时读取数据的函数
/**
* @brief SPI
在发送数据的同时接收指定长度的数据
* @param send_buf ——
接收数据缓冲区⾸地址
* @param recv_buf ——
接收数据缓冲区⾸地址
* @param size —— /
要发送接收数据的字节数
* @retval HAL_OK
成功返回
*/
static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);
}
5.4 编写W25Q64的驱动程序
5.4.1 读取 Manufacture ID 和 Device ID
读取 Flash 内部这两个 ID 有两个作⽤:
检测 SPI Flash 是否存在
可以根据 ID 判断 Flash 具体型号
/**
* @brief FlashID
读取内部的
* @param none
* @retval device_id
成功返回
*/
uint16_t W25QXX_ReadID(void)
{
uint8_t recv_buf[2] = {0}; //recv_buf[0]Manufacture ID, recv_buf[1]Device ID
存放存放
uint16_t device_id = 0;
uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00}; //+
待发送数据,命令地址
/* */
使能⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
/* */
发送并读取数据
if (HAL_OK == SPI_Transmit(send_data, 4))
{
if (HAL_OK == SPI_Receive(recv_buf, 2))
{
device_id = (recv_buf[0] << 8) | recv_buf[1];
}
}
/* */
取消⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return device_id;
}
5.4.2 读取状态寄存器数据并判断Flash是否忙碌
SPI Flash 的所有操作都是靠发送命令完成的,但是 Flash 接收到命令后,需要⼀段时间去执⾏该操作,这段时间内 Flash 处于“忙”状
态,MCU 发送的命令⽆效,不能执⾏,在 Flash 内部有 2-3 个状态寄存器,指⽰出 Flash 当前的状态,有趣的⼀点是:
当 Flash 内部在执⾏命令时,不能再执⾏ MCU 发来的命令,但是 MCU 可以⼀直读取状态寄存器,这下就很好办了,MCU可以⼀直读
/**
* @brief W25QXXW25Q642
读取的状态寄存器,⼀共有个状态寄存器
* @param reg —— (1~2)
状态寄存器编号
* @retval
状态寄存器的值
*/
static uint8_t W25QXX_ReadSR(uint8_t reg)
{
uint8_t result = 0;
uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};
switch(reg)
{
case 1:
send_buf[0] = READ_STATU_REGISTER_1;
case 2:
send_buf[0] = READ_STATU_REGISTER_2;
case 0:
default:
send_buf[0] = READ_STATU_REGISTER_1;
}
/* */
使能⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
if (HAL_OK == SPI_Transmit(send_buf, 4))
{
if (HAL_OK == SPI_Receive(&result, 1))
{
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return result;
}
}
/* */
取消⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return 0;
}
然后编写阻塞判断 Flash 是否忙碌的函数:
/**
* @brief Flash
阻塞等待处于空闲状态
* @param none
* @retval none
*/
static void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR(1) & 0x01) == 0x01); // BUSY
等待位清空
}
5.4.3 读取数据
SPI Flash 读取数据可以任意地址(地址长度32bit)读任意长度数据(最⼤ 65535 Byte),没有任何限制。
/**
* @brief SPI FLASH
读取数据
* @param buffer ——
数据存储区
* @param start_addr —— (32bit)
开始读取的地址最⼤
* @param nbytes —— (65535)
要读取的字节数最⼤
* @retval 0-1
成功返回,失败返回
*/
int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{
uint8_t cmd = READ_DATA_CMD;
start_addr = start_addr << 8;
W25QXX_Wait_Busy();
/* */
使能⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
if (HAL_OK == SPI_Transmit((uint8_t*)&start_addr, 3))
{
if (HAL_OK == SPI_Receive(buffer, nbytes))
{
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return 0;
}
}
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return -1;
}
5.4.4 写使能/禁⽌
Flash 芯⽚默认禁⽌写数据,所以在向 Flash 写数据之前,必须发送命令开启写使能。
/**
* @brief W25QXX,S1WEL
写使能将寄存器的置位
* @param none
* @retval
*/
void W25QXX_Write_Enable(void)
{
uint8_t cmd= WRITE_ENABLE_CMD;
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
/**
* @brief W25QXX,WEL
写禁⽌将清零
* @param none
* @retval none
*/
void W25QXX_Write_Disable(void)
{
uint8_t cmd = WRITE_DISABLE_CMD;
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
5.4.5 擦除扇区
SPI Flash有个特性:
数据位可以由1变为0,但是不能由0变为1。
所以在向 Flash 写数据之前,必须要先进⾏擦除操作,并且 Flash 最⼩只能擦除⼀个扇区,擦除之后该扇区所有的数据变为 (即全
0xFF
为1)。
/**
* @brief W25QXX
擦除⼀个扇区
* @param sector_addr ——
扇区地址根据实际容量设置
* @retval none
* @note
阻塞操作
*/
void W25QXX_Erase_Sector(uint32_t sector_addr)
{
uint8_t cmd = SECTOR_ERASE_CMD;
sector_addr *= 4096; //164KB
每个块有个扇区,每个扇区的⼤⼩是,需要换算为实际地址
sector_addr <<= 8;
W25QXX_Write_Enable(); //0xFF
擦除操作即写⼊,需要开启写使能
W25QXX_Wait_Busy(); //
等待写使能完成
/* */
使能⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
SPI_Transmit((uint8_t*)§or_addr, 3);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy(); //
等待扇区擦除完成
}
5.4.6 页写⼊操作
向 Flash 芯⽚写数据的时候,因为 Flash 内部的构造,可以按页写⼊。
/**
* @brief
页写⼊操作
* @param dat ——
要写⼊的数据缓冲区⾸地址
* @param WriteAddr ——
要写⼊的地址
* @param byte_to_write —— 0-256
要写⼊的字节数()
* @retval none
*/
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{
uint8_t cmd = PAGE_PROGRAM_CMD;
WriteAddr <<= 8;
W25QXX_Write_Enable();
/* */
使能⽚选
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
SPI_Transmit((uint8_t*)&WriteAddr, 3);
SPI_Transmit(dat, nbytes);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
5.5 添加宏定义和全局变量
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ManufactDeviceID_CMD 0x90
#define READ_STATU_REGISTER_1 0x05
#define READ_STATU_REGISTER_2 0x35
#define READ_DATA_CMD 0x03
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
#define SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xc7
#define PAGE_PROGRAM_CMD 0x02
/* USER CODE END PD */
/* Private user code ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
uint16_t device_id;
uint8_t read_buf[10] = {0};
uint8_t write_buf[10] = {0};
int i;
/* USER CODE END PV */
5.6 添加测试函数
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
/* USER CODE BEGIN 2 */
device_id = W25QXX_ReadID();
printf("W25Q64 Device ID is 0x%04xrn", device_id);
/* */
为了验证,⾸先读取要写⼊地址处的数据
printf("-------- read data before write -----------rn");
W25QXX_Read(read_buf, 0, 10);
for(i = 0; i < 10; i++)
{
printf("[0x%08x]:0x%02xrn", i, *(read_buf+i));
}
/* */
擦除该扇区
printf("-------- erase sector 0 -----------rn");
W25QXX_Erase_Sector(0);
/* */
再次读数据
printf("-------- read data after erase -----------rn");
W25QXX_Read(read_buf, 0, 10);
for(i = 0; i < 10; i++)
{
printf("[0x%08x]:0x%02xrn", i, *(read_buf+i));
}
/* */
写数据
printf("-------- write data -----------rn");
for(i = 0; i < 10; i++)
{
write_buf[i] = i;
}
W25QXX_Page_Program(write_buf, 0, 10);
/* */
再次读数据
printf("-------- read data after write -----------rn");
W25QXX_Read(read_buf, 0, 10);
for(i = 0; i < 10; i++)
{
printf("[0x%08x]:0x%02xrn", i, *(read_buf+i));
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
5.7 查看打印
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : W25Q64_CHIP_SELECT_Pin */
GPIO_InitStruct.Pin = W25Q64_CHIP_SELECT_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(W25Q64_CHIP_SELECT_GPIO_Port, &GPIO_InitStruct);
}
/**
* @brief SPI1 Initialization Function
* @param None
* @retval None
*/
static void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
uint32_t Timeout);
使⽤ STM32 标准库的代码:
/**
* @brief SPI_FLASH
初始化
* @param
⽆
* @retval
⽆
*/
void SPI_FLASH_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* SPI */
使能时钟
FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
/* SPI */
使能引脚相关的时钟
FLASH_SPI_CS_APBxClock_FUN (FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );
/* SPI CSIO */
配置的引脚,普通即可
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);
/* SPI SCK*/
配置的引脚
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
/* SPI MISO*/
配置的引脚
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
/* SPI MOSI*/
配置的引脚
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
/* FLASH: CS*/
停⽌信号引脚⾼电平
SPI_FLASH_CS_HIGH();
/* SPI */
模式配置
// FLASH SPI03CPOL CPHA
芯⽚⽀持模式及模式,据此设置
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(FLASH_SPIx , &SPI_InitStructure);
/* SPI */
使能
SPI_Cmd(FLASH_SPIx , ENABLE);
}
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
六、注意事项
⽤户代码要加在 和 之间,否则下次使⽤ STM32CubeMX 重新⽣成代码后,会被删除。
USER CODE BEGIN NUSER CODE END N
· 由 写于 2021 年 1 ⽉ 27 ⽇
· 参考:
发布者:admin,转转请注明出处:http://www.yc00.com/num/1701382058a1075485.html
评论列表(0条)