1.2 W25QXX芯片简介 W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。下表是W25QXX的常用命令表
2.硬件设计 D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
指示灯D1 USART1串口
W25Q64
K_UP和K_DOWN按键
3.软件设计 3.1 STM32CubeMX设置 ➡️ RCC设置外接HSE,时钟设置为72M ➡️ PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平 ➡️ USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位 ➡️ PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式 ➡️ PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚) ➡️ 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM软件编程 ➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
- void MX_SPI2_Init(void){
- hspi2.Instance = SPI2;
- hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
- hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式
- hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
- hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
- hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
- hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
- hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
- hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
- hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
- hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
- hspi2.Init.CRCPolynomial = 10;
- if (HAL_SPI_Init(&hspi2) != HAL_OK){
- Error_Handler();
- }
- }
- void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- if(spiHandle->Instance==SPI2){
- __HAL_RCC_SPI2_CLK_ENABLE();
- __HAL_RCC_GPIOB_CLK_ENABLE();
- /**SPI2 GPIO Configuration
- PB13 ------> SPI2_SCK
- PB14 ------> SPI2_MISO
- PB15 ------> SPI2_MOSI */
- GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- GPIO_InitStruct.Pin = GPIO_PIN_14;
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- }
- }
复制代码
➡️ 创建按键驱动文件key.c 和相关头文件key.h ➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍 - //这里仅介绍几个重要的函数
- void W25QXX_Init(void){
- W25Qx_Disable();
- W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID
- printf("FLASH ID:%X\r\n",W25QXX_TYPE);
- if(W25QXX_TYPE == 0xc816)
- printf("FLASH TYPE:W25Q64\r\n");
- }
- uint16_t W25QXX_ReadID(void){
- uint16_t ID;
- uint8_t id[2]={0};
- uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令
- W25Qx_Enable();//使能器件
- HAL_SPI_Transmit(&hspi2,cmd,4,1000);
- HAL_SPI_Receive(&hspi2,id,2,1000);
- W25Qx_Disable();//取消片选
- ID = (((uint16_t)id[0])<<8)|id[1];
- return ID;
- }
- void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
- uint8_t cmd[4] = {0};
- cmd[0] = W25X_ReadData;//读取命令
- cmd[1] = ((uint8_t)(ReadAddr>>16));
- cmd[2] = ((uint8_t)(ReadAddr>>8));
- cmd[3] = ((uint8_t)ReadAddr);
- W25Qx_Enable();//使能器件
- HAL_SPI_Transmit(&hspi2,cmd,4,1000);
- HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
- W25Qx_Disable();//取消片选
- }
- 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; //扇区地址
- secpos = WriteAddr%4096; //在扇区里的偏移
- secremain = 4096-secoff; //扇区剩余空间大小
- printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
- if(NumByteToWrite <= secremain) //不大于4K字节
- secremain = NumByteToWrite;
- 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;
- }
- 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;//下个扇区可以写完了
- }
- }
- }
复制代码
➡️ 在main.c文件下编写SPI测试代码 - /* USER CODE BEGIN PV */
- uint8_t wData[0x100];
- uint8_t rData[0x100];
- uint32_t i;
- /* USER CODE END PV */
- int main(void){
- /* USER CODE BEGIN 1 */
- uint8_t key;
- /* USER CODE END 1 */
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_SPI2_Init();
- MX_USART1_UART_Init();
- /* USER CODE BEGIN 2 */
- W25QXX_Init();
- for(i=0;i<0x100;i++){
- wData = i;
- rData = 0;
- }
- /* USER CODE END 2 */
- while (1){
- key = KEY_Scan(0);
- if(key == KEY_UP_PRES){
- printf("KEY_UP_PRES write data...\r\n");
- W25QXX_Erase_Sector(0);
- W25QXX_Write(wData,0,256);
- }
- if(key == KEY_DOWN_PRES){
- printf("KEY_DOWN_PRES read data...\r\n");
- W25QXX_Read(rData,0,256);
- for(i=0;i<256;i++){
- printf("0x%02X ",rData);
- }
- }
- HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
- HAL_Delay(200);
- }
- }
复制代码
|