STM32Cubmax stm32f103zet6 SPI通讯

一、基本概念

SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola
首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口主要应用在 EEPROM, FLASH,实时时
钟, AD 转换器,还有数字信号处理器和数字信号解码器之间。 SPI,是一种高速的,全双工,
同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局
上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信
协议, STM32 也有 SPI 接口。 下面我们看看 SPI 的内部简明图(图 28.1.1):

SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。
从图中可以看出, 主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器
写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的
移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,
若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI 主要特点有: 可以同时发出和接收串行数据; 可以当作主机或从机工作; 提供频率可
编程时钟; 发送结束中断标志; 写冲突保护; 总线竞争保护等。
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串
行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果
CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电
平。时钟相位( CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果
CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串
行同步时钟的第二个跳变沿(上升或下降)数据被采样。 SPI 主模块和与之通信的外设备时钟
相位和极性应该一致。
 

二、代码实现

w25qxx.c

#include "w25qxx.h"
#include "main.h"
#include "delay.h"
#include "stm32f1xx_hal.h"
uint16_t W25QXX_TYPE=W25Q128;//默认是W25Q128
//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector
//SPI2总线读写一个字节
//参数是写入的字节,返回值是读出的字节
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;//定义一个变量Rxdata
     HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1,1000);//调用固件库函数收发数据
    return Rxdata;//返回收到的数据
}
void W25QXX_CS(uint8_t a)//软件控制函数(0为低电平,其他值为高电平)
{
    if(a==0)HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
    else  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
//初始化SPI FLASH的IO口
uint8_t W25QXX_Init(void)
{
    uint8_t temp;//定义一个变量temp
    W25QXX_CS(1);//0片选开启,1片选关闭
    W25QXX_TYPE = W25QXX_ReadID();//读取FLASH  ID.
    if(W25QXX_TYPE == W25Q256)//SPI FLASH为W25Q256时才用设置为4字节地址模式
    {
       temp = W25QXX_ReadSR(3);//读取状态寄存器3,判断地址模式
       if((temp&0x01)==0)//如果不是4字节地址模式,则进入4字节地址模式
       {
           W25QXX_CS(0);//0片选开启,1片选关闭
           SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令
           W25QXX_CS(1);//0片选开启,1片选关闭
       }
    }
    if(W25QXX_TYPE==W25Q256||W25QXX_TYPE==W25Q128||W25QXX_TYPE==W25Q64
    ||W25QXX_TYPE==W25Q32||W25QXX_TYPE==W25Q16||W25QXX_TYPE==W25Q80)
    return 0; else return 1;//如果读出ID是现有型号列表中的一个,则识别芯片成功!
}
//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
//状态寄存器2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
//状态寄存器3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS (R) (R)
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
uint8_t W25QXX_ReadSR(uint8_t regno)
{
    uint8_t byte=0,command=0;
    switch(regno)
    {
        case 1:
            command=W25X_ReadStatusReg1;//读状态寄存器1指令
            break;
        case 2:
            command=W25X_ReadStatusReg2;//读状态寄存器2指令
            break;
        case 3:
            command=W25X_ReadStatusReg3;//读状态寄存器3指令
            break;
        default:
            command=W25X_ReadStatusReg1;//读状态寄存器1指令
            break;
    }
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(command);//发送读取状态寄存器命令
    byte=SPI2_ReadWriteByte(0Xff);//读取一个字节
    W25QXX_CS(1);//0片选开启,1片选关闭
    return byte;//返回变量byte
}
//写W25QXX状态寄存器
void W25QXX_Write_SR(uint8_t regno,uint8_t  sr)
{
    uint8_t command=0;
    switch(regno)
    {
        case 1:
            command=W25X_WriteStatusReg1;//写状态寄存器1指令
            break;
        case 2:
            command=W25X_WriteStatusReg2;//写状态寄存器2指令
            break;
        case 3:
            command=W25X_WriteStatusReg3;//写状态寄存器3指令
            break;
        default:
            command=W25X_WriteStatusReg1;
            break;
    }
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(command);//发送写取状态寄存器命令
    SPI2_ReadWriteByte(sr);//写入一个字节
    W25QXX_CS(1);//0片选开启,1片选关闭
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_WriteEnable);//发送写使能
    W25QXX_CS(1);//0片选开启,1片选关闭
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_WriteDisable);//发送写禁止指令
    W25QXX_CS(1);//0片选开启,1片选关闭
}
//读取芯片ID
//高8位是厂商代号(本程序不判断厂商代号)
//低8位是容量大小
//0XEF13型号为W25Q80
//0XEF14型号为W25Q16
//0XEF15型号为W25Q32
//0XEF16型号为W25Q64
//0XEF17型号为W25Q128(目前洋桃2号开发板使用128容量芯片)
//0XEF18型号为W25Q256
uint16_t W25QXX_ReadID(void)
{
    uint16_t Temp = 0;
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(0x90);//发送读取ID命令
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    Temp|=SPI2_ReadWriteByte(0xFF)<<8;
    Temp|=SPI2_ReadWriteByte(0xFF);
    W25QXX_CS(1);//0片选开启,1片选关闭
    return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t  ReadAddr,uint16_t NumByteToRead)
{
    uint16_t i;
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_ReadData);//发送读取命令
    if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位
    {
         SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24));
    }
     SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));//发送24bit地址
     SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));
    SPI2_ReadWriteByte((uint8_t)ReadAddr);
    for(i=0;i<NumByteToRead;i++)
    {
         pBuffer[i]=SPI2_ReadWriteByte(0XFF);//循环读数
    }
    W25QXX_CS(1);//0片选开启,1片选关闭
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(uint8_t*  pBuffer,uint32_t WriteAddr,uint16_t  NumByteToWrite)
{
    uint16_t i;
    W25QXX_Write_Enable();//SET WEL
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_PageProgram);//发送写页命令
    if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位
    {
         SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>24));
    }
     SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));//发送24bit地址
     SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));
    SPI2_ReadWriteByte((uint8_t)WriteAddr);
     for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数
    W25QXX_CS(1);//0片选开启,1片选关闭
    W25QXX_Wait_Busy();//等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t*  pBuffer,uint32_t WriteAddr,uint16_t  NumByteToWrite)
{
    uint16_t pageremain;
    pageremain=256-WriteAddr%256; //单页剩余的字节数
    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
    while(1)
    {
       W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
       if(NumByteToWrite==pageremain)break;//写入结束了
        else //NumByteToWrite>pageremain
       {
           pBuffer+=pageremain;
           WriteAddr+=pageremain;
           NumByteToWrite-=pageremain;            //减去已经写入了的字节数
           if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
           else pageremain=NumByteToWrite;     //不够256个字节了
       }
    };
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t* pBuffer,uint32_t  WriteAddr,uint16_t NumByteToWrite)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t* W25QXX_BUF;
    W25QXX_BUF=W25QXX_BUFFER;
    secpos=WriteAddr/4096;//扇区地址
    secoff=WriteAddr%4096;//在扇区内的偏移
    secremain=4096-secoff;//扇区剩余空间大小
    //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
    if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
    while(1)
    {
       W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
       for(i=0;i<secremain;i++)//校验数据
       {
           if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
       }
       if(i<secremain)//需要擦除
       {
           W25QXX_Erase_Sector(secpos);//擦除这个扇区
           for(i=0;i<secremain;i++)//复制
           {
               W25QXX_BUF[i+secoff]=pBuffer[i];
           }
           W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
       }else  W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.
       if(NumByteToWrite==secremain)break;//写入结束了
       else//写入未结束
       {
           secpos++;//扇区地址增1
           secoff=0;//偏移位置为0
           pBuffer+=secremain;  //指针偏移
           WriteAddr+=secremain;//写地址偏移
           NumByteToWrite-=secremain;//字节数递减
           if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完
           else  secremain=NumByteToWrite;//下一个扇区可以写完了
       }
    };
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
    W25QXX_Write_Enable();//SET WEL
    W25QXX_Wait_Busy();//等待忙状态
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_ChipErase);//发送片擦除命令
    W25QXX_CS(1);//0片选开启,1片选关闭
    W25QXX_Wait_Busy();//等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
    Dst_Addr*=4096;
    W25QXX_Write_Enable();//SET WEL
    W25QXX_Wait_Busy();
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_SectorErase);//发送扇区擦除指令
    if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位
    {
         SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>24));
    }
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16));//发送24bit地址
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
    SPI2_ReadWriteByte((uint8_t)Dst_Addr);
    W25QXX_CS(1);//0片选开启,1片选关闭
    W25QXX_Wait_Busy();//等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
    while((W25QXX_ReadSR(1)&0x01)==0x01);//等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_PowerDown);//发送掉电命令 0xB9
    W25QXX_CS(1);//0片选开启,1片选关闭
    delay_us(3);//等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
    W25QXX_CS(0);//0片选开启,1片选关闭
    SPI2_ReadWriteByte(W25X_ReleasePowerDown);//发送电源唤醒指令 0xAB
    W25QXX_CS(1);//0片选开启,1片选关闭
    delay_us(3);//等待TRES1
}


//void delay_us(uint32_t us) //利用CPU循环实现的非精准应用的微秒延时函数
//{
//    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000 * us); //使用HAL_RCC_GetHCLKFreq()函数获取主频值,经算法得到1微秒的循环次数
//    while (delay--); //循环delay次,达到1微秒延时
//}

w25qxx.h

#ifndef W25Q128_W25QXX_H_
#define W25Q128_W25QXX_H_
 
#include "stm32f1xx_hal.h" //HAL库文件声明

 
//25系列FLASH芯片厂商与容量代号(厂商代号EF)
#define W25Q80    0XEF13
#define W25Q16    0XEF14
#define W25Q32    0XEF15
#define W25Q64    0XEF16
#define W25Q128   0XEF17
#define W25Q256 0XEF18
#define EX_FLASH_ADD 0x000000 //W25Q128的地址是24位宽
extern uint16_t W25QXX_TYPE;//定义W25QXX芯片型号
extern SPI_HandleTypeDef hspi2;
//
//指令表
#define W25X_WriteEnable             0x06
#define W25X_WriteDisable            0x04
#define W25X_ReadStatusReg1      0x05
#define W25X_ReadStatusReg2      0x35
#define W25X_ReadStatusReg3      0x15
#define W25X_WriteStatusReg1         0x01
#define W25X_WriteStatusReg2         0x31
#define W25X_WriteStatusReg3     0x11
#define W25X_ReadData             0x03
#define W25X_FastReadData         0x0B
#define W25X_FastReadDual         0x3B
#define W25X_PageProgram          0x02
#define W25X_BlockErase              0xD8
#define W25X_SectorErase          0x20
#define W25X_ChipErase            0xC7
#define W25X_PowerDown            0xB9
#define W25X_ReleasePowerDown    0xAB
#define W25X_DeviceID             0xAB
#define W25X_ManufactDeviceID    0x90
#define W25X_JedecDeviceID           0x9F
#define W25X_Enable4ByteAddr         0xB7
#define W25X_Exit4ByteAddr        0xE9
uint8_t SPI2_ReadWriteByte(uint8_t  TxData);//SPI2总线底层读写
void W25QXX_CS(uint8_t a);//W25QXX片选引脚控制
uint8_t W25QXX_Init(void);//初始化W25QXX函数
uint16_t  W25QXX_ReadID(void);//读取FLASH ID
uint8_t W25QXX_ReadSR(uint8_t regno);//读取状态寄存器
void W25QXX_4ByteAddr_Enable(void);//使能4字节地址模式
void W25QXX_Write_SR(uint8_t regno,uint8_t  sr);//写状态寄存器
void W25QXX_Write_Enable(void);//写使能
void W25QXX_Write_Disable(void);//写保护
void W25QXX_Write_NoCheck(uint8_t*  pBuffer,uint32_t WriteAddr,uint16_t  NumByteToWrite);//无检验写SPI FLASH
void W25QXX_Read(uint8_t* pBuffer,uint32_t  ReadAddr,uint16_t NumByteToRead);//读取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t  WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);//整片擦除
void W25QXX_Erase_Sector(uint32_t  Dst_Addr);//扇区擦除
void W25QXX_Wait_Busy(void);//等待空闲
void W25QXX_PowerDown(void);//进入掉电模式
void W25QXX_WAKEUP(void);//唤醒
//void delay_us(uint32_t us); //C文件中的函数声明
#endif /* W25Q128_W25QXX_H_ */

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 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 "usart.h"
#include "gpio.h"
#include "w25qxx.h"
#include <stdio.h>
#include "delay.h"
	uint8_t EX_FLASH_BUF[1];

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
		switch(GPIO_Pin) {
			case GPIO_PIN_3:
				EX_FLASH_BUF[0]=W25QXX_ReadID();
				printf	("芯片ID, %x \r\n",	EX_FLASH_BUF[0]);
			
				printf("3 pressed! \r\n");
				break;
			case GPIO_PIN_4:
				W25QXX_Read(EX_FLASH_BUF,EX_FLASH_ADD,1);
				EX_FLASH_BUF[0]++;
				if(EX_FLASH_BUF[0]>200)
					EX_FLASH_BUF[0]=0;
					W25QXX_Write(EX_FLASH_BUF,EX_FLASH_ADD,1);
				 printf("读出0x00地址数据:%d \n\r",EX_FLASH_BUF[0]);
			
				printf("4 pressed! \r\n");
				break;
		
		}
}
/* 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 */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* 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_SPI2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	W25QXX_Init();
	printf("=============== \r\n");
  /* USER CODE END 2 */
	
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* 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_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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 */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/376859.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GLSL ES 1.0

GLSL ES 概述 写在前面 程序是大小写敏感的每一个语句都应该以英文分号结束一个shader必须包含一个main函数&#xff0c;该函数不接受任何参数&#xff0c;并且返回voidvoid main() { }数据值类型 GLSL支持三种数据类型&#xff1a; 整型浮点型&#xff1a;必须包含小数点&…

eclipse使用google的Java代码格式

插件下载地址 1.下载eclipse的插件 2.下载的jar包放到eclipse安装目录的dropins文件夹 D:\install_package\STS\sts-4.10.0.RELEASE\dropins&#xff13;.重启后设置 eclipse - windows - preference - java - code style - formatter -

Excel——合并计算

1.表格的合并计算&#xff08;单张表格/多个表格&#xff09; Q&#xff1a;请统计两个表格中各商品的总销量和总销售额&#xff0c;将结果放置在下方任意位置。 A&#xff1a;选择一个需要将合并计算数据放置区域的空白单元格 选择【数据】——【合并计算】&#xff0c;【函…

Linux安装Java

yum安装 下面命令直接复制粘贴一件安装java17 yum list installed | grep java #查看已经安装的javayum remove java* -y #移除现在系统已经安装的javayum list | grep java-17 #查看安装java17yum install -y java-17-openjdk #安装java17此处可…

flink反压及解决思路和实操

1. 反压原因 反压其实就是 task 处理不过来&#xff0c;算子的 sub-task 需要处理的数据量 > 能够处理的数据量&#xff0c;比如&#xff1a; 当前某个 sub-task 只能处理 1w qps 的数据&#xff0c;但实际上到来 2w qps 的数据&#xff0c;但是实际只能处理 1w 条&#…

JVM 性能调优- 五种内存溢出(5)

在介绍之前先简单介绍下 直接内存(Direct Memory)和堆内存(Heap Memory): 关系: 直接内存并不是Java虚拟机的一部分,它是通过Java的NIO库中的ByteBuffer来分配和管理的。直接内存通常由操作系统的本地内存(Native Memory)提供支持。堆内存是Java虚拟机的一部分,用于存…

裸机开发及开发环境搭建

ARM 的裸机开发&#xff0c;也就是不带操作系统开发&#xff0c;就和我们开发 STM32 一样&#xff0c;如果 有 STM32 开发经验的话学起本篇会很容易 1 、裸机开发是了解所使用的 CPU 最直接、最简单的方法&#xff0c;裸机开发是直接操作 CPU 的寄存器。 Linux 驱动开发…

人工智能 | 深度学习的进展

深度学习的进展 深度学习是人工智能领域的一个重要分支&#xff0c;它利用神经网络模拟人类大脑的学习过程&#xff0c;通过大量数据训练模型&#xff0c;使其能够自动提取特征、识别模式、进行分类和预测等任务。近年来&#xff0c;深度学习在多个领域取得了显著的进展&#…

React+Antd+tree实现树多选功能(选中项受控+支持模糊检索)

1、先上效果 树型控件&#xff0c;选中项形成一棵新的树&#xff0c;若父选中&#xff0c;子自动选中&#xff0c;子取消&#xff0c;父不取消&#xff0c;子选中&#xff0c;所有的父节点自动取消。同时支持模糊检索&#xff0c;会检索出所有包含该内容的关联节点。 2、环境准…

Python数据可视化库之ggplot使用详解

概要 数据可视化是数据分析和数据沟通的关键部分。Python 作为一门强大的数据科学和数据分析工具,提供了多种数据可视化库,其中之一就是 ggplot。ggplot 是一个基于 ggplot2 的 Python 数据可视化库,它可以创建精美且高度可定制的图表,以更好地理解和传达数据。本文将深入…

spring boot整合 cache 以redis服务 处理数据缓存 便捷开发

我们常规开发中 就是程序去数据库取数据 然后返回给客户端 但是 如果有些业务业务量非常庞大 不断访问数据库 性能就会非常糟糕 从而造成不好的用户体验 那么 我们自然就可以将数据查到缓存中 然后 用户访问 从缓存中取 这样就会大大提高用户的访问效率 之前 我的文章 java …

【算法设计与分析】求根节点到叶节点数字之和

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 每条从根节点到叶节点的路径都代表一个数…

【数据结构】二叉树的三种遍历(非递归讲解)

目录 1、前言 2、二叉树的非递归遍历 2.1、先序遍历 2.2、中序遍历 2.3、后序遍历 1、前言 学习二叉树的三种非递归遍历前&#xff0c;首先来了解一下递归序&#xff1a; 递归序就是按照先序遍历的顺序&#xff0c;遇到的所有结点按顺序排列&#xff0c;重复的结点也必须记…

Java排序算法-持续更新中

一、比较排序 1.1 交换排序 数组两元素交换位置 public class ArrayUtil {/*** 交换数组中的两个元素* param array 数组* param ele1Idx 元素1的索引下标* param ele2Idx 元素1的索引下表*/public static void swap(int[] array, int ele1Idx, int ele2Idx) {int tmp arra…

【Linux开发工具】gcc/g++的使用

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.前言2.gcc/g使用方…

初始Ansible自动化运维工具之playbook剧本编写

一、playbook的相关知识 1.1 playbook 的简介 playbook是 一个不同于使用Ansible命令行执行方式的模式&#xff0c;其功能更强大灵活。简单来说&#xff0c;playbook是一个非常简单的配置管理和多主机部署系统&#xff0c;不同于任何已经存在的模式&#xff0c;可作为一个适…

安装PyInstaller的保姆级教程

一、安装PyInstaller之前首先要安装Python&#xff0c;小编这里安装的是Python3.9&#xff0c;目前&#xff08;2024/2/6&#xff09;匹配到的最高版本的PyInstaller的版本为6.3.0。需要安装Python的小伙伴可以去这里安装python详细步骤&#xff08;超详细&#xff0c;保姆级&a…

推荐一款开源的跨平台划词翻译和OCR翻译软件:Pot

Pot简介 一款开源的跨平台划词翻译和OCR翻译软件 下载安装指南 根据你的机器型号下载对应版本&#xff0c;下载完成后双击安装即可。 使用教程 Pot具体功能如下&#xff1a; 划词翻译输入翻译外部调用鼠标选中需要翻译的文本&#xff0c;按下设置的划词翻译快捷键即可按下输…

作业2.7

一、填空题 1、在下列程序的空格处填上适当的字句&#xff0c;使输出为&#xff1a;0&#xff0c;2&#xff0c;10。 #include <iostream> #include <math.h> class Magic {double x; public: Magic(double d0.00):x(fabs(d)) {} Magic operator(__const Magic&…

登录+JS逆向进阶【过咪咕登录】(附带源码)

JS渗透之咪咕登录 每篇前言&#xff1a;咪咕登录参数对比 captcha参数enpassword参数搜索enpassword参数搜索J_RsaPsd参数setPublic函数encrypt加密函数运行时可能会遇到的问题此部分改写的最终形态JS代码&#xff1a;运行结果python编写脚本运行此JS代码&#xff1a;运行结果&…