STM32——HAL库开发笔记16(SPI外部flash实验2)(参考来源:b站铁头山羊)
本次实验承接上一节实验,对后续代码进行编写,重点介绍SPI的数据读写。
四、编程接口
1、 HAL StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi ,
uint8_t *pData ,
uint16_t Size ,
uint32_t Timeout)
作用:发送数据
参数说明:hspi :填写SPI句柄的指针 将句柄取地址&hspi1
pData : 填写要发送的数据
Size :填写要发送的数据的数量,以字节为单位。
Timeout :填写超时时间,单位是ms。
填写HAL_MAX_DELAY :表示无限长超时时间
例:主机向从机1发送 0x5a , 0x33
step1: 申明一个数组存取要发送的数据
uint8_t dataToSend[] = {0x5a , 0x33};
step2:第二边沿采集 相对应的NSS引脚写入低电压
HAL_GPIO_WritePin( ,GPIO_PIN_RESET);
step3:发送数据
HAL_SPI_Transmit(&hspi1 , dataToSend , 2 , HAL_MAX_DELAY);
step4:写入完成,将对应NSS电平高
HAL_GPIO_WritePin( ,GPIO_PIN_SET);
-----------------------------------------------------------------------------------------
2、HAL StatusTypeDef HAL SPI Receive(SPI_HandleTypeDef *hspi ,
uint8_t *pData ,
uint16_t Size ,
uint32_t Timeout)
作用:接收数据
参数说明:hspi :填写SPI句柄的指针 将句柄取地址&hspi1
pData : 填写接收缓冲区指针
Size :填写要接收的数据的数量,以字节为单位。
Timeout :填写超时时间,单位是ms。
填写HAL_MAX_DELAY :表示无限长超时时间
例:从从机1接收两个字节数据
step1: 申明一个数组作为接收缓冲区,赋初始值0xff,发送高电压,不赋初值会赋一些随机值。
uint8_t dataRcvd[] = {0xff , 0xff};
step2:第二边沿采集 相对应的NSS引脚写入低电压
HAL_GPIO_WritePin( ,GPIO_PIN_RESET);
step3:发送数据
HAL_SPI_Receive(&hspi1 , dataRcvd , 2 , HAL_MAX_DELAY);
step4:写入完成,将对应NSS电平高
HAL_GPIO_WritePin( ,GPIO_PIN_SET);
-----------------------------------------------------------------------------------------
3、HAL StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi ,
uint8_t *pTxData ,
uint8_t *pRxData ,
uint16_t Size ,
uint32_t Timeout)
作用:发送数据的同时接收数据
参数说明:hspi :填写SPI句柄的指针 将句柄取地址&hspi1
pTxData :填写要发送的数据
pRxData :填写要接收的数据
Size :填写要收发的数据的数量。收数量=发数量,以字节为单位。
Timeout :填写超时时间,单位是ms。
填写HAL_MAX_DELAY :表示无限长超时时间
例:发送{0x5a , 0x33},接收2字节数据
step1: 申明两个数组存储发送数据和作为接收缓冲区。
uint8_t txData[] = {0x5a , 0x33};
uint8_t rxData[] = {};
step2:第二边沿采集 相对应的NSS引脚写入低电压
HAL_GPIO_WritePin( ,GPIO_PIN_RESET);
step3:发送接收数据
HAL_SPI_TransmitReceive(&hspi1 , txData , rxData , 2 , HAL_MAX_DELAY);
step4:写入完成,将对应NSS电平高
HAL_GPIO_WritePin( ,GPIO_PIN_SET);
五、flash模块
我们的flash模块型号为W25Q64,其中64代表其容量为64Mbit,也就是8M字节。详情见下图
flash的写入过程
在写入数据之前我们需要对写入数据的位置进行擦除。flash擦除的最小单元是扇区,也就是4K字节。擦除需要延迟一定的时间,如100ms。然后就是写入,数据写入的最小单元是页,也就是256字节,即页编程,也需要延迟,如10ms。在写入和擦除之前,我们需要对此芯片进行解锁,即写使能。
六、编程思路
1、声明一个函数用来保存LED的亮灭状态
static void SaveLEDState(uint8_t ledstate);
声明需要写在下图所示位置
2、实现函数
static void SaveLEDState(uint8_t ledstate)
{
// 1 .写使能
查找芯片手册第20页: 发送数据0x06(参考编程接口)
// 2 .扇区擦除
手册35页:发送数据0x20(参考编程接口)
uint8_t sectorEraseCmd[] = {0x20 , 0x00 , 0x00 , 0x00}
0x20:指令码 , 后面3个0x00为24位扇区首地址
// 3 .延迟100ms
// 4 .写使能
// 5 .页编程
手册33页,指令码为0x02
uint8_t pageProgCmd[5] ;
pageProgCmd[0] = 0x02 ; //指令码
//24位地址
pageProgCmd[1] = pageProgCmd[2] = pageProgCmd[3] = 0 ;
//写入LED亮灭状态
pageProgCmd[4] = ledstate;
再进行发送。
// 6 .延迟10ms
}
以上过程需要写在下图所示位置
3、调用SaveLEDState 保存LED当前状态
4、加载LED状态
static uint8_t LoadLEDState(void); //申明加载状态函数,写在代码54行即上次声明的函数下面。
//实现函数:手册23页
读取数据的指令码为0x03
static uint8_t LoadLEDState(void)
{
uint8_t readDataCmd[]={0x03,0x00,0x00,0x00};
uint8_t ledstate= 0xff;//用来接收读出的数据,即LED状态
//拉低NSS
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , readDataCmd , 4 , HAL_MAX_DELAY); //发送数据
HAL_SPI_Transmit(&hspi1 , &ledstate , 1 , HAL_MAX_DELAY);
//拉高NSS
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
return ledstate;// 返回读取到的数据
}
5、程序开始运行之前
ledstate = LoadLEDState(); //从flash里面加载LED状态
if (ledstate == 1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET); //点亮LED
}
else {
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET); //熄灭LED
}
七、代码(main.c)
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
static void SaveLEDState(uint8_t ledstate);
static uint8_t LoadLEDState(void); //申明加载状态函数
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static uint8_t LoadLEDState(void); //申明加载状态函数,写在代码54行即上次声明的函数下面。
//实现函数:手册23页
static uint8_t LoadLEDState(void)
{
uint8_t readDataCmd[]={0x03,0x00,0x00,0x00};
uint8_t ledstate= 0xff;//用来接收读出的数据,即LED状态
//拉低NSS
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , readDataCmd , 4 , HAL_MAX_DELAY); //发送数据
HAL_SPI_Transmit(&hspi1 , &ledstate , 1 , HAL_MAX_DELAY);
//拉高NSS
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
return ledstate;// 返回读取到的数据
}
static void SaveLEDState(uint8_t ledstate)
{
// 1 .写使能
uint8_t writeEnableCmd[] = {0x06};
//发送0x06
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , writeEnableCmd , 1 , HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
// 2 .扇区擦除
uint8_t sectorEraseCmd[] = {0x20,0x00,0x00,0x00};
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , sectorEraseCmd , 4 , HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
// 3 .延迟100ms
HAL_Delay(100);
// 4 .写使能
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , writeEnableCmd , 1 , HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
// 5 .页编程
uint8_t pageProgCmd[5] ;
pageProgCmd[0] = 0x02 ; //指令码
//24位地址
pageProgCmd[1] =0x00 ;
pageProgCmd[2] =0x00 ;
pageProgCmd[3] = 0x00 ;
//写入LED亮灭状态
pageProgCmd[4] = ledstate;
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1 , pageProgCmd, 5 , HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA , GPIO_PIN_4 , GPIO_PIN_SET);
// 6 .延迟10ms
HAL_Delay(10);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 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_SPI1_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t pre = 1,cur = 1;
uint8_t ledstate = 0;
ledstate = LoadLEDState();
if (ledstate == 1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
}
else {
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}
while (1)
{
pre = cur ;
if( HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_SET)
{
cur = 1;
}
else {
cur = 0;
}
if (pre != cur)
{
HAL_Delay(10);
if (cur == 0)
{
}
else
{
if(ledstate == 1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
ledstate = 0;
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
ledstate = 1;
}
SaveLEDState(ledstate); //保存亮灭状态
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
最终通过断电上电和按键控制LED状态即可验证实验结果。