2025年stm32cubemx软件库_STM32cube

stm32cubemx软件库_STM32cubeSPI 是一种高速的 全双工 同步的通信总线 并且在芯片的管脚上只占用四根线 节约了芯片的管脚 同时为 PCB 的布局上节省空间 提供方便 主要应用在 EEPROM FLASH 实时时钟 AD 转换器 还有数字信号处理器和数字信号解码器之间 SPI 主从模式 SPI 分为主 从两种模式 一个 SPI 通讯系统需要包含一个 且只能是一个 主设备 一个或多个从设备 提供时钟的为主设备 Master

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI主从模式

SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

SPI信号线

SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

SCLK:串行时钟信号,由主设备产生。

CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

硬件上为4根线。

SPI一对一

SPI一对多

SPI数据发送接收

SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。

首先拉低对应SS信号线,表示与该设备进行通信

主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍

主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。

从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。


SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

SPI工作模式

根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。
时钟极性(CPOL)定义了时钟空闲状态电平:

CPOL=0为时钟空闲时为低电平

CPOL=1为时钟空闲时为高电平

时钟相位(CPHA)定义数据的采集时间。

CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。

CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。

关于SPI简单介绍到这里,详细讲解请参看:

《SPI原理超详细讲解—值得一看》

W25Q128 FLASH芯片介绍

W25Q128是一款SPI通信的FLASH芯片,可以通过标准/两线/四线SPI控制,其FLASH的大小为16M,分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为 16个扇区(Sector),每个扇区 4K 个字节。通过SPI通信协议即可实现MCU(STM32)和 W25Q128 之间的通信。实现W25Q128的控制需要通过SPI协议发送相应的控制指令,并满足一定的时序。

原理图连接


常用指令:

写使能(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;
}

下载地址:

w25Qxx.zip

例程测试

重新定义printf函数
在 stm32f1xx_hal.c中包含#include

#include "stm32f4xx_hal.h"
#include
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 
#include "W25QXX.h"
uint8_t wData[0x100];
uint8_t rData[0x100];
uint32_t i;
uint8_t ID[2];
printf("\r\n SPI-W25Qxxx Example \r\n\r\n");
/*##-1- Read the device ID ########################*/
BSP_W25Qx_Init();
BSP_W25Qx_Read_ID(ID);
printf(" W25Qxxx ID is : 0x%02X 0x%02X \r\n\r\n",ID[0],ID[1]);
/*##-2- Erase Block ##################################*/
if(BSP_W25Qx_Erase_Block(0) == W25Qx_OK)
printf(" SPI Erase Block ok\r\n");
else
Error_Handler();
/*##-3- Written to the flash ########################*/
/* fill buffer */
for(i =0;i<0x100;i ++)
{

wData[i] = i;
rData[i] = 0;
}
if(BSP_W25Qx_Write(wData,0x00,0x100)== W25Qx_OK)
printf(" SPI Write ok\r\n");
else
Error_Handler();
/*##-4- Read the flash ########################*/
if(BSP_W25Qx_Read(rData,0x00,0x100)== W25Qx_OK)
printf(" SPI Read ok\r\n\r\n");
else
Error_Handler();
printf("SPI Read Data : \r\n");
for(i =0;i<0x100;i++)
printf("0x%02X ",rData[i]);
printf("\r\n\r\n");
/*##-5- check date ########################*/
if(memcmp(wData,rData,0x100) == 0 )
printf(" W25Q128FV SPI Test OK\r\n");
else
printf(" W25Q128FV SPI Test False\r\n");

STM32F103测试正常:

编程小号
上一篇 2025-03-01 13:40
下一篇 2025-02-13 15:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/111026.html