写使能(Write Enable) (06h)
向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。
扇区擦除指令(Sector Erase) (0x20h)
扇区擦除指令,数据写入前必须擦除对应的存储单元,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。
读命令(Read Data) (03h)
读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
状态读取命令(Read Status Register)
读状态寄存器1(05H),状态寄存器2(35H),状态寄存器3(15H)
写入命令0x05,即可读取状态寄存器的值。
写入命令(Page Program) (02h)
在对W25Q128 FLASH的写入数据的操作中一定要先擦出扇区,在进行写入,否则将会发生数据错误。
W25Q128 FLASH一次性最大写入只有256个字节。
在进行写操作之前,一定要开启写使能(Write Enable)。
当只接收数据时不但能只检测RXNE状态 ,必须同时向发送缓冲区发送数据才能驱动SCK时钟跳变。
基于CubeMx的讲解
1设置RCC时钟
设置高速外部时钟HSE 选择外部时钟源
2 SPI设置
SPI2设置为全双工主模式,硬件NSS关闭,如下图:
模式设置:
- 有主机模式全双工/半双工
- 从机模式全双工/半双工
- 只接收主机模式/只接收从机模式
- 只发送主机模式
因为我们是和W25Q128V芯片闪存芯片进行通信,所以设置为主机全双工
不使能硬件NSS
STM32有硬件NSS(片选信号),可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替
其中SIP1的片选NSS : SPI1_NSS(PA4)
其中SIP2的片选NSS : SPI2_NSS(PB12)
如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选
NSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。
对于NSS的输入,又分为软件输入和硬件输入。
软件输入:
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
硬件输入:
主机接高电平,从机接低电平。
左键对应的软件片选引脚,选择GPIO_Output(输出模式),然后点击GPIO,设置一下备注。
我这里虽然PB12是SPI2的硬件片选NSS,但是我想用软件片选,所以关闭了硬件NSS
SPI配置默认如下:
SPI配置中设置数据长度为8bit,MSB先输出分频为64分频,则波特率为125KBits/s。其他为默认设置。
Motorla格式,CPOL设置为Low,CPHA设置为第一个边沿。不开启CRC检验,NSS为软件控制。
最后记得初始化一下串口,因为需要测试例程,发送数据到上位机。很简单,这里就不再赘述了,不懂得同学请看:
【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解
3时钟源设置
我的是 外部晶振为8MHz
- 1选择外部时钟HSE 8MHz
- 2PLL锁相环倍频9倍
- 3系统时钟来源选择为PLL
- 4设置APB1分频器为 /2
- 5 使能CSS监视时钟
32的时钟树框图 如果不懂的话请看《【STM32】系统时钟RCC详解(超详细,超全面)》
4项目文件设置
- 1 设置项目名称
- 2 设置存储路径
- 3 选择所用IDE
5创建工程文件
然后点击GENERATE CODE 创建工程
配置下载工具
新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行
SPI函数详解
在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。
- 轮询: 最基本的发送接收函数,就是正常的发送数据和接收数据
- 中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能
- DMA: DMA传输SPI数据
利用SPI接口发送和接收数据主要调用以下两个函数:
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);//接收数据
SPI发送数据函数:
HAL\_SPI\_Transmit(SPI_HandleTypeDef \*hspi, uint8_t \*pData, uint16_t Size, uint32_t Timeout);//发送数据
参数:
- *hspi: 选择SPI1/2,比如&hspi1,&hspi2
- *pData : 需要发送的数据,可以为数组
- Size: 发送数据的字节数,1 就是发送一个字节数据
- Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
SPI接收数据函数:
HAL\_SPI\_Receive(SPI_HandleTypeDef \*hspi, uint8_t \*pData, uint16_t Size, uint32_t Timeout);//接收数据
参数:
- *hspi: 选择SPI1/2,比如&hspi1,&hspi2
- *pData : 接收发送过来的数据的数组
- Size: 接收数据的字节数,1 就是接收一个字节数据
- Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数
SPI接收回调函数:
HAL\_SPI\_TransmitReceive\_IT(&hspi1, TXbuf,RXbuf,CommSize);
当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数:
HAL\_SPI\_TxRxCpltCallback(SPI_HandleTypeDef \*hspi)
用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数
片选引脚:
因为我们是软件使能片选,定义片选引脚,CS片选低电平为有效使能, CS片选高电平不使能
这里用两个宏定义来代替
在main.h中有宏定义命名,SPI2_CS_Pin 就是PB12
//以W25Q128为例
#define SPI\_CS\_Enable() HAL\_GPIO\_WritePin(GPIOB, SPI2\_CS\_Pin, GPIO\_PIN\_RESET)
#define SPI\_CS\_Disable() HAL\_GPIO\_WritePin(GPIOB, SPI2\_CS\_Pin, GPIO\_PIN\_SET)
SPI例程详解
因为不同的flash芯片通信协议以及方式都是不同的,所以这里介绍下具体的SPI的发送和接收应该怎么写,具体的请看芯片手册修改下即可,这里提供下W25QXX的驱动文件,以及测试例程,测试是正常没问题
挑几个函数讲解一下:
在w25Qxx.h钟可以修改CS片选引脚,W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平
#define W25Qx\_Enable() HAL\_GPIO\_WritePin(SPI2\_CS\_GPIO\_Port, SPI2\_CS\_Pin, GPIO\_PIN\_RESET)
#define W25Qx\_Disable() HAL\_GPIO\_WritePin(SPI2\_CS\_GPIO\_Port, SPI2\_CS\_Pin, GPIO\_PIN\_SET)
w25Qxx复位函数:
- 函数开始先将要发送的数据(命令(0x66)和地址(0x99))存储在cmd数组中,
- 拉低片选信号,开始SPI通信
- 然后后通过HAL_SPI_Transmit()函数发送出去
- 拉高片选信号,关闭SPI通信
W25Qx_TIMEOUT_VALUE是最大超时时间,在w25Qxx.h中定义为1000,单位为us
/\*\*
\* @brief This function reset the W25Qx.
\* @retval None
\*/
static void BSP\_W25Qx\_Reset(void)
{
uint8_t cmd[2] = {RESET_ENABLE_CMD,RESET_MEMORY_CMD};
W25Qx\_Enable();
/\* Send the reset command \*/
HAL\_SPI\_Transmit(&hspi2, cmd, 2, W25Qx_TIMEOUT_VALUE);
W25Qx\_Disable();
}
W25QXX读函数:
三个参数:
- pData 存放读取到的数据的数组
- ReadAddr 读取数据的地址
- Size 读取数据的大小
…
- 函数开始先将要发送的数据(命令和地址)存储在cmd数组中,
- 拉低片选信号,开始SPI通信
- 然后后通过HAL_SPI_Transmit()函数发送出去,首先发送写命令(0X03),上方有讲解,然后发送三个字节(24 Bit)的地址
- 接着通过HAL_SPI_Receive()接收读取的数据。
- 拉高片选信号,关闭SPI通信
uint8_t BSP\_W25Qx\_Read(uint8_t\* pData, uint32_t ReadAddr, uint32_t Size)
{
uint8_t cmd[4];
/\* Configure the command \*/
cmd[0] = READ_CMD;
cmd[1] = (uint8_t)(ReadAddr >> 16);
cmd[2] = (uint8_t)(ReadAddr >> 8);
cmd[3] = (uint8_t)(ReadAddr);
W25Qx\_Enable();
/\* Send the read ID command \*/
HAL\_SPI\_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE);
/\* Reception of the data \*/
if (HAL\_SPI\_Receive(&hspi2, pData,Size,W25Qx_TIMEOUT_VALUE) != HAL_OK)
{
return W25Qx_ERROR;
}
W25Qx\_Disable();
return W25Qx_OK;
}
写使能(Write Enable) (06h)
向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。
uint8_t BSP\_W25Qx\_WriteEnable(void)
{
uint8_t cmd[] = {WRITE_ENABLE_CMD};
uint32_t tickstart = HAL\_GetTick();
/\*Select the FLASH: Chip Select low \*/
W25Qx\_Enable();
/\* Send the read ID command \*/
HAL\_SPI\_Transmit(&hspi2, cmd, 1, W25Qx_TIMEOUT_VALUE);
/\*Deselect the FLASH: Chip Select high \*/
W25Qx\_Disable();
/\* Wait the end of Flash writing \*/
while(BSP\_W25Qx\_GetStatus() == W25Qx_BUSY);
{
/\* Check for the Timeout \*/
if((HAL\_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE)
{
return W25Qx_TIMEOUT;
}
}
return W25Qx_OK;
}
扇区擦除函数:
扇区擦除指令(Sector Erase) (0x20h)
扇区擦除指令,数据写入前必须擦除对应的存储单元,并且使能写操作,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。判断flash是否为忙状态,如果不为忙则擦除操作完成。
uint8_t BSP\_W25Qx\_Erase\_Block(uint32_t Address)
{
uint8_t cmd[4];
uint32_t tickstart = HAL\_GetTick();
cmd[0] = SECTOR_ERASE_CMD;
cmd[1] = (uint8_t)(Address >> 16);
cmd[2] = (uint8_t)(Address >> 8);
cmd[3] = (uint8_t)(Address);
/\* Enable write operations \*/
BSP\_W25Qx\_WriteEnable();
/\*Select the FLASH: Chip Select low \*/
W25Qx\_Enable();
/\* Send the read ID command \*/
HAL\_SPI\_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE);
/\*Deselect the FLASH: Chip Select high \*/
W25Qx\_Disable();
/\* Wait the end of Flash writing \*/
while(BSP\_W25Qx\_GetStatus() == W25Qx_BUSY);
{
/\* Check for the Timeout \*/
if((HAL\_GetTick() - tickstart) > W25Q128FV_SECTOR_ERASE_MAX_TIME)
{
return W25Qx_TIMEOUT;
}
}
return W25Qx_OK;
}
下载地址:
例程测试
重新定义printf函数
在 stm32f1xx_hal.c中包含#include <stdio.h>
#include "stm32f4xx\_hal.h"
#include <stdio.h>
extern UART_HandleTypeDef huart1; //声明串口
在 stm32f1xx_hal.c 中重写fget和fput函数
/\*\*
\* 函数功能: 重定向c库函数printf到DEBUG\_USARTx
\* 输入参数: 无
\* 返 回 值: 无
\* 说 明:无
\*/
int fputc(int ch, FILE \*f)
{
HAL\_UART\_Transmit(&huart1, (uint8_t \*)&ch, 1, 0xffff);
return ch;
}
/\*\*
\* 函数功能: 重定向c库函数getchar,scanf到DEBUG\_USARTx
\* 输入参数: 无
\* 返 回 值: 无
\* 说 明:无
\*/
int fgetc(FILE \*f)
{
uint8_t ch = 0;
HAL\_UART\_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
main.c
在main.c里添加以下代码:
#include <string.h>
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**


**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**