正点原子讲解SPI学习,驱动编程NOR FLASH实战
配置SPI传输速度时,需要先失能SPI,__HAL_SPI_DISABLE,然后操作SPI_CR1中的波特率设置位,再使能SPI,
NM25Q128驱动步骤
myspi.c
#include "./BSP/MYSPI/myspi.h"SPI_HandleTypeDef g_spi1_handler; /* SPI句柄 */void spi1_init(void)
{g_spi1_handler.Instance = SPI1_SPI; //SPI1基地址g_spi1_handler.Init.Mode = SPI_MODE_MASTER; //设置SPI模式g_spi1_handler.Init.Direction = SPI_DIRECTION_2LINES; //spi读写g_spi1_handler.Init.DataSize = SPI_DATASIZE_8BIT; //g_spi1_handler.Init.CLKPolarity = SPI_POLARITY_HIGH; //时钟相位CPOL为1g_spi1_handler.Init.CLKPhase = SPI_PHASE_2EDGE; //时钟极性CPLA为1g_spi1_handler.Init.NSS = SPI_NSS_SOFT; //NSS管理是软件g_spi1_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; //波特率分频最大g_spi1_handler.Init.FirstBit = SPI_FIRSTBIT_MSB; //发送高位在前g_spi1_handler.Init.TIMode = SPI_TIMODE_DISABLE; //帧格式失能g_spi1_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; //CRC校验失能g_spi1_handler.Init.CRCPolynomial = 7; //设置CRC校验多项式(0~65535),默认值7HAL_SPI_Init(&g_spi1_handler);
}//SPI2底层驱动,时钟使能,引脚配置
//此函数会被HAL_SPI_Init()调用
void HAL_SPI_Msp_Init(SPI_HandleTypeDef *hspi)
{GPIO_InitTypeDef gpio_init_struct;if(hspi->Instance == SPI1_SPI) //基地址正确{SPI1_SCK_GPIO_CLK_ENABLE(); /* SPI1_SCK脚时钟使能 */SPI1_MISO_GPIO_CLK_ENABLE(); /* SPI1_MISO脚时钟使能 */SPI1_MOSI_GPIO_CLK_ENABLE(); /* SPI1_MOSI脚时钟使能 *//*SCK引脚模式设置(复用输出)*/gpio_init_struct.Pin = SPI1_SCK_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_AF_PP; //复用推挽输出gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(SPI1_SCK_GPIO_PORT, &gpio_init_struct);/*MISO引脚模式设置(复用输出)*/gpio_init_struct.Pin = SPI1_MISO_GPIO_PIN;HAL_GPIO_Init(SPI1_MISO_GPIO_PORT, &gpio_init_struct);/*MOSI引脚模式配置(复用输出)*/gpio_init_struct.Pin = SPI1_MOSI_GPIO_PIN;HAL_GPIO_Init(SPI1_MISO_GPIO_PORT, &gpio_init_struct);}
}//spi发送接收函数
uint8_t spi1_read_write_byte(uint8_t data)
{uint8_t rec_data = 0;//发送data,接收到rec_data,HAL_SPI_TransmitReceive(&g_spi1_handler, &data, &rec_data, 1, 1000);return rec_data;
}//SPI1速度设置函数
void spi1_set_speed(uint8_t speed)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(speed)); /* 判断有效性 */__HAL_SPI_DISABLE(&g_spi1_handler); /*关闭SPI*/g_spi1_handler.Instance->CR1 &= 0xFFC7; /* 位3-5清零,用来设置1波特率 */g_spi1_handler.Instance->CR1 |= speed << 3; /* 设置SPI速度 */__HAL_SPI_ENABLE(&g_spi1_handler); /*打开SPI*/
}
myspi.h
#ifndef __MYSPI_H
#define __MYSPI_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* SPI2 引脚 定义 */#define SPI1_SCK_GPIO_PORT GPIOB
#define SPI1_SCK_GPIO_PIN GPIO_PIN_10
#define SPI1_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define SPI1_MISO_GPIO_PORT GPIOB
#define SPI1_MISO_GPIO_PIN GPIO_PIN_11
#define SPI1_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define SPI1_MOSI_GPIO_PORT GPIOB
#define SPI1_MOSI_GPIO_PIN GPIO_PIN_12
#define SPI1_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//* SPI2相关定义 */
#define SPI1_SPI SPI1
#define SPI1_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI1_CLK_ENABLE(); }while(0) /* SPI2时钟使能 *//******************************************************************************************//* SPI总线速度设置 */
#define SPI_SPEED_2 0
#define SPI_SPEED_4 1
#define SPI_SPEED_8 2
#define SPI_SPEED_16 3
#define SPI_SPEED_32 4
#define SPI_SPEED_64 5
#define SPI_SPEED_128 6
#define SPI_SPEED_256 7void spi1_init(void);
void spi1_set_speed(uint8_t speed);
uint8_t spi1_read_write_byte(uint8_t txdata);
void spi1_set_speed(uint8_t speed);#endif
norflash.c 跟着原子教学写的
#include "./BSP/MYSPI/norflash.h"
#include "./BSP/MYSPI/myspi.h"void norflash_init(void)
{NORFLASH_CS_GPIO_CLK_ENABLE(); /*NOR flash时钟使能*/GPIO_InitTypeDef gpio_init_struct;gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct);spi1_init(); //spi1初始化,SCK,MOSI,MISO引脚配置时钟使能spi1_read_write_byte(0xFF); //发送0xFF,清除DR的作用NORFLASH_CS(1); //拉高片选
}//读nm25q128状态寄存器1
//读nm25q128状态寄存器BUSY位,SR1的第一位是BUSY,WEL,写使能锁存是第二位
//busy位=0是空闲状态,=1是忙状态
//WEL位=1,是可以写,=0是禁止写
uint8_t norflash_rd_sr1(void)
{uint8_t rec_data = 0;NORFLASH_CS(0);rec_data = spi1_read_write_byte(0x05); /*读状态寄存器1*/NORFLASH_CS(1); //拉高片选 return rec_data;
}/*读flash指定地址一字节数据*/
uint8_t norflash_read_data(uint32_t addr)
{uint8_t rec_data = 0;//拉低片选NORFLASH_CS(0);/*发送读命令*/spi1_read_write_byte(0x03); //NM25Q128发送读命令指令/*发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr);/*接收数据*/rec_data = spi1_read_write_byte(0xFF); //发送0xFF过去,交换数据NORFLASH_CS(1); //拉高片选return rec_data; //返回读取的一字节数据
}//擦除扇区
void norflash_erase_sector(uint32_t addr)
{//1.写使能,执行页写,扇区擦除,块擦除,片擦除都需要写使能NORFLASH_CS(0);spi1_read_write_byte(0x06); //主机向NM25Q128写使能指令NORFLASH_CS(1); //拉高片选//2.等待空闲while(norflash_rd_sr1() & 0x01); //检测写使能是否完成//3.发送擦除扇区指令NORFLASH_CS(0);spi1_read_write_byte(0x20);/*4.发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr); //会自动将该地址的扇区擦除/*5.等待空闲*/while(norflash_rd_sr1() & 0x01); //检测擦除完成
}//norflash页写
void norflash_write_page(uint8_t data, uint32_t addr)
{/*1.擦除扇区*/norflash_erase_sector(addr);/*2. 写使能*/NORFLASH_CS(0);spi1_read_write_byte(0x06); //主机向NM25Q128写使能指令NORFLASH_CS(1); //拉高片选/*3.发送页写指令*/NORFLASH_CS(0);spi1_read_write_byte(0x02);/*4.发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr); /*5.要写入的数据*/spi1_read_write_byte(data);NORFLASH_CS(1); //拉高片选/*6.等待写入完成,等待空闲*/while(norflash_rd_sr1() & 0x01); //等待空闲
}
norflash.h 跟着自己开发板改的引脚,跟着原子教学写的
#ifndef __NORFLASH_H
#define __NORFLASH_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */#define NORFLASH_CS_GPIO_PORT GPIOB
#define NORFLASH_CS_GPIO_PIN GPIO_PIN_14
#define NORFLASH_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//******************************************************************************************//* NORFLASH 片选信号 */
#define NORFLASH_CS(x) do{ x ? \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \}while(0)/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID *//* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFFextern uint16_t norflash_TYPE; /* 定义FLASH芯片型号 *//* 普通函数 */void norflash_init(void); /* 初始化25QXX */
void norflash_erase_sector(uint32_t addr); /* 扇区擦除 */
uint8_t norflash_rd_sr1(void); /*读状态寄存器1*/
uint8_t norflash_read_data(uint32_t addr); /*读flash指定地址一字节数据*/
void norflash_write_page(uint8_t data, uint32_t addr); //写页#endif
另外,有几个norflash写的函数,当写入字节超过剩余页字节容量
下面是正点例程处理写入超过当前页剩余字节数,超过扇区剩余字节数,扇区内有字节已经被写1,只能整块擦除,的norflash.c代码,我已经加了注释,非常巧妙
/******************************************************************************************************* @file norflash.c* @author 正点原子团队(ALIENTEK)* @version V1.0* @date 2021-10-23* @brief NOR FLASH(25QXX) 驱动代码* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 探索者 F407开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com** 修改说明* V1.0 20211023* 第一次发布******************************************************************************************************/#include "./BSP/SPI/spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"uint16_t g_norflash_type = W25Q128; /* 默认是NM25Q128 *//*** @brief 初始化SPI NOR FLASH* @param 无* @retval 无*/
void norflash_init(void)
{uint8_t temp;NORFLASH_CS_GPIO_CLK_ENABLE(); /* NORFLASH CS脚 时钟使能 */GPIO_InitTypeDef gpio_init_struct;gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct); /* CS引脚模式设置(复用输出) */NORFLASH_CS(1); /* 取消片选 */spi1_init(); /* 初始化SPI1 */spi1_set_speed(SPI_SPEED_4); /* SPI1 切换到高速状态 21Mhz */g_norflash_type = norflash_read_id(); /* 读取FLASH ID. */if (g_norflash_type == W25Q256) /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */{temp = norflash_read_sr(3); /* 读取状态寄存器3,判断地址模式 */if ((temp & 0X01) == 0) /* 如果不是4字节地址模式,则进入4字节地址模式 */{norflash_write_enable(); /* 写使能 */temp |= 1 << 1; /* ADP=1, 上电4位地址模式 */norflash_write_sr(3, temp); /* 写SR3 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_Enable4ByteAddr); /* 使能4字节地址指令 */NORFLASH_CS(1);}}//printf("ID:%x\r\n", g_norflash_type);
}/*** @brief 等待空闲* @param 无* @retval 无*/
static void norflash_wait_busy(void)
{while ((norflash_read_sr(1) & 0x01) == 0x01); /* 等待BUSY位清空 */
}/*** @brief 25QXX写使能* @note 将S1寄存器的WEL置位* @param 无* @retval 无*/
void norflash_write_enable(void)
{NORFLASH_CS(0);spi1_read_write_byte(FLASH_WriteEnable); /* 发送写使能 */NORFLASH_CS(1);
}/*** @brief 25QXX发送地址* @note 根据芯片型号的不同, 发送24ibt / 32bit地址* @param address : 要发送的地址* @retval 无*/
static void norflash_send_address(uint32_t address)
{if (g_norflash_type == W25Q256) /* 只有W25Q256支持4字节地址模式 */{spi1_read_write_byte((uint8_t)((address)>>24)); /* 发送 bit31 ~ bit24 地址 */} spi1_read_write_byte((uint8_t)((address)>>16)); /* 发送 bit23 ~ bit16 地址 */spi1_read_write_byte((uint8_t)((address)>>8)); /* 发送 bit15 ~ bit8 地址 */spi1_read_write_byte((uint8_t)address); /* 发送 bit7 ~ bit0 地址 */
}/*** @brief 读取25QXX的状态寄存器,25QXX一共有3个状态寄存器* @note 状态寄存器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 ADP ADS** @param regno: 状态寄存器号,范:1~3* @retval 状态寄存器值*/
uint8_t norflash_read_sr(uint8_t regno)
{uint8_t byte = 0, command = 0;switch (regno){case 1:command = FLASH_ReadStatusReg1; /* 读状态寄存器1指令 */break;case 2:command = FLASH_ReadStatusReg2; /* 读状态寄存器2指令 */break;case 3:command = FLASH_ReadStatusReg3; /* 读状态寄存器3指令 */break;default:command = FLASH_ReadStatusReg1;break;}NORFLASH_CS(0);spi1_read_write_byte(command); /* 发送读寄存器命令 */byte = spi1_read_write_byte(0Xff); /* 读取一个字节 */NORFLASH_CS(1);return byte;
}/*** @brief 写25QXX状态寄存器* @note 寄存器说明见norflash_read_sr函数说明* @param regno: 状态寄存器号,范:1~3* @param sr : 要写入状态寄存器的值* @retval 无*/
void norflash_write_sr(uint8_t regno, uint8_t sr)
{uint8_t command = 0;switch (regno){case 1:command = FLASH_WriteStatusReg1; /* 写状态寄存器1指令 */break;case 2:command = FLASH_WriteStatusReg2; /* 写状态寄存器2指令 */break;case 3:command = FLASH_WriteStatusReg3; /* 写状态寄存器3指令 */break;default:command = FLASH_WriteStatusReg1;break;}NORFLASH_CS(0);spi1_read_write_byte(command); /* 发送读寄存器命令 */spi1_read_write_byte(sr); /* 写入一个字节 */NORFLASH_CS(1);
}/*** @brief 读取芯片ID* @param 无* @retval FLASH芯片ID* @note 芯片ID列表见: norflash.h, 芯片列表部分*/
uint16_t norflash_read_id(void)
{uint16_t deviceid;NORFLASH_CS(0);spi1_read_write_byte(FLASH_ManufactDeviceID); /* 发送读 ID 命令 */spi1_read_write_byte(0); /* 写入一个字节 */spi1_read_write_byte(0);spi1_read_write_byte(0);deviceid = spi1_read_write_byte(0xFF) << 8; /* 读取高8位字节 */deviceid |= spi1_read_write_byte(0xFF); /* 读取低8位字节 */NORFLASH_CS(1);return deviceid;
}/*** @brief 读取SPI FLASH* @note 在指定地址开始读取指定长度的数据* @param pbuf : 数据存储区* @param addr : 开始读取的地址(最大32bit)* @param datalen : 要读取的字节数(最大65535)* @retval 无*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;NORFLASH_CS(0);spi1_read_write_byte(FLASH_ReadData); /* 发送读取命令 */norflash_send_address(addr); /* 发送地址 */for (i = 0; i < datalen; i++){pbuf[i] = spi1_read_write_byte(0XFF); /* 循环读取 */}NORFLASH_CS(1);
}/*** @brief SPI在一页(0~65535)内写入少于256个字节的数据* @note 在指定地址开始写入最大256字节的数据* @param pbuf : 数据存储区* @param addr : 开始写入的地址(最大32bit)* @param datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!* @retval 无*/
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;norflash_write_enable(); /* 写使能 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_PageProgram); /* 发送写页命令 */norflash_send_address(addr); /* 发送地址 */for (i = 0; i < datalen; i++){spi1_read_write_byte(pbuf[i]); /* 循环读取 */}NORFLASH_CS(1);norflash_wait_busy(); /* 等待写入结束 */
}/*** @brief 无检验写SPI FLASH* @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!* 具有自动换页功能* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param pbuf : 数据存储区* @param addr : 开始写入的地址(最大32bit)* @param datalen : 要写入的字节数(最大65535)* @retval 无*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t pageremain;pageremain = 256 - addr % 256; /* 单页剩余的字节数 */if (datalen <= pageremain) /* 不大于256个字节 */{
//1,和2两个操作都是为了让写入256个字节和写入小于当前页剩余字节数的函数3为一个函数,所以1,2做了这样处理pageremain = datalen; //1}while (1){/* 当写入字节比页内剩余地址还少的时候, 一次性写完* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理*///先写满首地址剩余字节,当写入字节小于剩余当前字节页字节时norflash_write_page(pbuf, addr, pageremain); //3if (datalen == pageremain) /* 写入结束了 */{break;}else /* datalen > pageremain */{pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */{pageremain = 256; /* 一次可以写入256个字节 */}else /* 剩余数据小于一页,可以一次写完 */{//2pageremain = datalen; /* 不够256个字节了 */ }}}
}/*** @brief 写SPI FLASH* @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!* SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block* 擦除的最小单位为Sector.** @param pbuf : 数据存储区* @param addr : 开始写入的地址(最大32bit)* @param datalen : 要写入的字节数(最大65535)* @retval 无*/
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *norflash_buf;norflash_buf = g_norflash_buf;secpos = addr / 4096; /* 扇区地址 */secoff = addr % 4096; /* 在扇区内的偏移 */secremain = 4096 - secoff; /* 扇区剩余空间大小 *///printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */if (datalen <= secremain){secremain = datalen; /* 不大于4096个字节 */}while (1){norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++) /* 校验数据 */{if (norflash_buf[secoff + i] != 0XFF){break; /* 需要擦除, 直接退出for循环 */}}//上面if执行break话,就是扇区剩余扇区不需要擦除,执行完后i==secremainif (i < secremain) /* 需要擦除 */{norflash_erase_sector(secpos); /* 擦除这个扇区 */for (i = 0; i < secremain; i++) /* 复制 */{norflash_buf[i + secoff] = pbuf[i];}norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 写入整个扇区 */}else /* 写已经擦除了的,直接写入扇区剩余区间. */{norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */}if (datalen == secremain){break; /* 写入结束了 */}else /* 写入未结束 */{secpos++; /* 扇区地址增1 */secoff = 0; /* 偏移位置为0 */pbuf += secremain; /* 指针偏移 */addr += secremain; /* 写地址偏移 */datalen -= secremain; /* 字节数递减 */if (datalen > 4096){secremain = 4096; /* 下一个扇区还是写不完 */}else{secremain = datalen;/* 下一个扇区可以写完了 */}}}
}/*** @brief 擦除整个芯片* @note 等待时间超长...* @param 无* @retval 无*/
void norflash_erase_chip(void)
{norflash_write_enable(); /* 写使能 */norflash_wait_busy(); /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_ChipErase); /* 发送读寄存器命令 */ NORFLASH_CS(1);norflash_wait_busy(); /* 等待芯片擦除结束 */
}/*** @brief 擦除一个扇区* @note 注意,这里是扇区地址,不是字节地址!!* 擦除一个扇区的最少时间:150ms* * @param saddr : 扇区地址 根据实际容量设置* @retval 无*/
void norflash_erase_sector(uint32_t saddr)
{//printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */saddr *= 4096;norflash_write_enable(); /* 写使能 */norflash_wait_busy(); /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_SectorErase); /* 发送写页命令 */norflash_send_address(saddr); /* 发送地址 */NORFLASH_CS(1);norflash_wait_busy(); /* 等待扇区擦除完成 */
}
下面是norflash.h
/******************************************************************************************************* @file norflash.h* @author 正点原子团队(ALIENTEK)* @version V1.0* @date 2021-10-23* @brief NOR FLASH(25QXX) 驱动代码* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 探索者 F407开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com** 修改说明* V1.0 20211023* 第一次发布******************************************************************************************************/#ifndef __norflash_H
#define __norflash_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */#define NORFLASH_CS_GPIO_PORT GPIOB
#define NORFLASH_CS_GPIO_PIN GPIO_PIN_14
#define NORFLASH_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//******************************************************************************************//* NORFLASH 片选信号 */
#define NORFLASH_CS(x) do{ x ? \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \}while(0)/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID *//* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFFextern uint16_t norflash_TYPE; /* 定义FLASH芯片型号 *//* 静态函数 */
static void norflash_wait_busy(void); /* 等待空闲 */
static void norflash_send_address(uint32_t address);/* 发送地址 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入page */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 *//* 普通函数 */
void norflash_init(void); /* 初始化25QXX */
uint16_t norflash_read_id(void); /* 读取FLASH ID */
void norflash_write_enable(void); /* 写使能 */
uint8_t norflash_read_sr(uint8_t regno); /* 读取状态寄存器 */
void norflash_write_sr(uint8_t regno,uint8_t sr); /* 写状态寄存器 */void norflash_erase_chip(void); /* 整片擦除 */
void norflash_erase_sector(uint32_t saddr); /* 扇区擦除 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 读取flash */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入flash */#endif
页写入无校验就是写入地址全是0xFF的情况和写入地址带校验有的不是0xFF带擦除的两个函数非常有逻辑性,单独列出来
,后面跟上擦除整个扇区和擦除整个芯片的操作,因为norflash最小擦除单位是扇区,nandflash最小擦除单位是块,1个扇区4096byte,一个页256Byte,一个块65536字节
扇区擦除函数不同与教学的是传入扇区地址,在函数乘以了4096恢复了直接地址
/*** @brief 无检验写SPI FLASH* @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!* 具有自动换页功能* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param pbuf : 数据存储区* @param addr : 开始写入的地址(最大32bit)* @param datalen : 要写入的字节数(最大65535)* @retval 无*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t pageremain;pageremain = 256 - addr % 256; /* 单页剩余的字节数 */if (datalen <= pageremain) /* 不大于256个字节 */{
//1,和2两个操作都是为了让写入256个字节和写入小于当前页剩余字节数的函数3为一个函数,所以1,2做了这样处理pageremain = datalen; //1}while (1){/* 当写入字节比页内剩余地址还少的时候, 一次性写完* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理*///先写满首地址剩余字节,当写入字节小于剩余当前字节页字节时norflash_write_page(pbuf, addr, pageremain); //3if (datalen == pageremain) /* 写入结束了 */{break;}else /* datalen > pageremain */{pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */{pageremain = 256; /* 一次可以写入256个字节 */}else /* 剩余数据小于一页,可以一次写完 */{//2pageremain = datalen; /* 不够256个字节了 */ }}}
}/*** @brief 写SPI FLASH* @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!* SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block* 擦除的最小单位为Sector.** @param pbuf : 数据存储区* @param addr : 开始写入的地址(最大32bit)* @param datalen : 要写入的字节数(最大65535)* @retval 无*/
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *norflash_buf;norflash_buf = g_norflash_buf;secpos = addr / 4096; /* 扇区地址 */secoff = addr % 4096; /* 在扇区内的偏移 */secremain = 4096 - secoff; /* 扇区剩余空间大小 *///printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */if (datalen <= secremain){secremain = datalen; /* 不大于4096个字节 */}while (1){norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++) /* 校验数据 */{if (norflash_buf[secoff + i] != 0XFF){break; /* 需要擦除, 直接退出for循环 */}}//上面if执行break话,就是扇区剩余扇区不需要擦除,执行完后i==secremainif (i < secremain) /* 需要擦除 */{norflash_erase_sector(secpos); /* 擦除这个扇区 */for (i = 0; i < secremain; i++) /* 复制 */{norflash_buf[i + secoff] = pbuf[i];}norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 写入整个扇区 */}else /* 写已经擦除了的,直接写入扇区剩余区间. */{norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */}if (datalen == secremain){break; /* 写入结束了 */}else /* 写入未结束 */{secpos++; /* 扇区地址增1 */secoff = 0; /* 偏移位置为0 */pbuf += secremain; /* 指针偏移 */addr += secremain; /* 写地址偏移 */datalen -= secremain; /* 字节数递减 */if (datalen > 4096){secremain = 4096; /* 下一个扇区还是写不完 */}else{secremain = datalen;/* 下一个扇区可以写完了 */}}}
}/*** @brief 擦除整个芯片* @note 等待时间超长...* @param 无* @retval 无*/
void norflash_erase_chip(void)
{norflash_write_enable(); /* 写使能 */norflash_wait_busy(); /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_ChipErase); /* 发送读寄存器命令 */ NORFLASH_CS(1);norflash_wait_busy(); /* 等待芯片擦除结束 */
}/*** @brief 擦除一个扇区* @note 注意,这里是扇区地址,不是字节地址!!* 擦除一个扇区的最少时间:150ms* * @param saddr : 扇区地址 根据实际容量设置* @retval 无*/
void norflash_erase_sector(uint32_t saddr)
{//printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */saddr *= 4096;norflash_write_enable(); /* 写使能 */norflash_wait_busy(); /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_SectorErase); /* 发送写页命令 */norflash_send_address(saddr); /* 发送地址 */NORFLASH_CS(1);norflash_wait_busy(); /* 等待扇区擦除完成 */
}
SPI总结
时钟CPOL,CPHA
NSS就是片选的意思,代码中是软件片选,CS引脚给从设备低电平,即代表开始与从设备通信
SPI相关寄存器,h7有所不同,SPI_DR是由硬件操作
SPI相关HAL库驱动
NMq25q128简介,支持SPI工作模式0和工作模式3
简介
读,擦除,写
NORflash驱动步骤
相关文章:

正点原子讲解SPI学习,驱动编程NOR FLASH实战
配置SPI传输速度时,需要先失能SPI,__HAL_SPI_DISABLE,然后操作SPI_CR1中的波特率设置位,再使能SPI, NM25Q128驱动步骤 myspi.c #include "./BSP/MYSPI/myspi.h"SPI_HandleTypeDef g_spi1_handler; /* SPI句柄 */void spi1_init(void) {g_spi…...

低代码开发助力中小企业数字化转型难度持续降低
随着信息技术的飞速发展,数字化转型已成为企业持续发展的关键驱动力。对于中小企业而言,数字化转型不仅意味着提升效率、降低成本,更是实现业务模式创新和市场竞争力提升的重要途径。然而,传统软件开发模式的高成本、长周期和复杂…...

【Linux】:线程控制
朋友们、伙计们,我们又见面了,本期来给大家带来线程控制相关代码和知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入门到精通 数…...

大数据-174 Elasticsearch Query DSL - 全文检索 full-text query 匹配、短语、多字段 详细操作
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

Spring Boot视频网站:构建可扩展的视频服务平台
6系统测试 为了保证所开发出来的系统质量过关,让所开发出来的系统具备可靠性并能够投入运行使用,这就需要进行系统开发的最后一个关键步骤,那就是系统测试。可以说系统测试就是对系统开发前面的步骤,比如系统分析与设计等进行复查…...

护眼台灯横评:书客、柏曼、明基哪款使用体验好,又能护眼?
如果你使用过护眼台灯,就太能理解为什么护眼台灯会诞生了。护眼台灯确实有一定的护眼作用,光线柔和不刺眼,许多护眼台灯还有智能调光、定时休息等人性化功能。在当今这个数字化时代,长时间面对电脑屏幕或埋头于书本已成为许多人的…...

RDMA笔记
目录 1. RDMA简介1.1. 比较Socket与RDMA的通信1.2. RDMA优势1.3. RDMA 2. RDMA基本元素2.1. QPSQ, SQE & RQ, RQEQPNQPC 2.2. CQ2.3. MR2.4. PD 3. RDMA基本操作3.1. Send & Receive3.2. RDMA Write3.3. RDMA Read 阅读RDMA相关资料,从硬件开发角度对RDMA作…...

Collection 单列集合 List Set
集合概念 集合是一种特殊类 ,这些类可以存储任意类对象,并且长度可变, 这些集合类都位于java.util中,使用的话必须导包 按照存储结构可以分为两大类 单列集合 Collection 双列集合 Map 两种 区别如下 Collection 单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两…...

LabVIEW提高开发效率技巧----跨平台开发
在如今的多平台环境下,开发者常常面临不同操作系统的需求,如Windows、Linux和RT(实时)系统等。而LabVIEW作为一种强大的开发工具,提供了支持跨平台开发的能力,但要使其无缝迁移,开发者需要掌握一…...

创建uniCloud新项目并且是新服务空间,运行会报Error: Invalid uni-id config file错误
问题说明 新创建的服务空间,新起的项目,运行查询数据库就会报错,Uncaught (in promise) Error: Invalid uni-id config file,我记得在原来创建项目的时候,是不需要进行配置的,最近创建新项目出现了这个错误…...

七、IPD 方法论框架(IPD的组织架构)
IPD的组织架构 在IPD(集成产品开发)方法论中,组织架构是确保跨职能团队高效协作、快速响应市场需求的关键要素之一。IPD的组织架构通常打破传统的职能部门隔离,倡导跨职能团队和矩阵式管理模式,使各职能部门在项目开发中紧密合作,从而提高开发效率,降低沟通成本。 IPD…...

iPad mini 7惨遭暗砍一刀
大屏是工作,小屏才是生活。 iPad mini系列,一直被誉为最适合普罗大众的平板。热爱学习、工作的卷王不多,沉迷游戏、追剧的俗人不少。 对娱乐场景而言,便携性是核心属性。iPad mini不大不小,只有两台手机的大小&#x…...

【计算机网络 - 基础问题】每日 3 题(三十六)
✍个人博客:https://blog.csdn.net/Newin2020?typeblog 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞…...

Docker镜像
Docker是一个开源的容器化平台,它可以帮助开发人员打包应用程序及其依赖项为轻量级、可移植的容器,以实现快速部署和可扩展性。下面是关于Docker的一些基本概念和优势: 容器:Docker使用容器来封装应用程序和其所有依赖项ÿ…...

Golang | Leetcode Golang题解之第478题在圆内随机生成点
题目: 题解: type Solution struct {radius, xCenter, yCenter float64 }func Constructor(radius, xCenter, yCenter float64) Solution {return Solution{radius, xCenter, yCenter} }func (s *Solution) RandPoint() []float64 {r : math.Sqrt(rand.…...

菜鸟笔记006 截图识别文字插件 textOCR
随手可得的截图识别文字插件 textOCR,识别出来的文字可直接输入到illustrator的当前文档中: 执行条件 1、需截图软件支持,推荐笔记截图工具 2、截好图片直接拖入面板即可完成识别 ****后期可完成实现在illustrator选择图片对象完成文字识别。…...

MySQL【知识改变命运】07
MySQL 1:Group by 分组查询1.1:语法:1.2:练习 2:having⼦句3回顾:3:内置函数3.1 :⽇期函数 1:Group by 分组查询 可以根据某列,进行分组查询,比如学校里面的…...

Matlab自学笔记三十八:日期时间序列的创建方法
1.概念 时间序列是指,某一时间段的时间,也就是说,一组日期时间数据组成的序列,例如,1.1~1.10,1点~5点等,在Matlab中,使用向量表示这种时间序列,例如[2025.1.1 2025.1.2 …...

fiber的原理
React Fiber 的主要原理包括动态优先级、可中断的工作、增量渲染和协作式多任务 React Fiber 是 React 16 引入的一种新的协调(reconciliation)引擎,它旨在提高 React 应用的性能和响应性。Fiber 的核心原理主要包括以下几个方面:…...

重塑输电线路运维管理,巡检管理系统守护电网稳定运行
在输电线路巡检管理中,一个高效、直接的巡检系统对于确保电力供应的稳定性和安全性至关重要。巡检系统能够直接对接运维需求,减少繁琐流程,并强化数据分析能力,这无疑为输电线路的运维管理带来了诸多优势。以下是对这些优势的具体…...

各种排序方法总结
目录 1. 冒泡排序 (Bubble Sort 2. 选择排序 (Selection Sort) 3. 插入排序 (Insertion Sort) 4. 快速排序 (Quick Sort) 5. 归并排序 (Merge Sort) 6. 堆排序 (Heap Sort) 排序算法 时间复杂度 空间复杂度 备注冒泡排序 最好情况: O(n) 平均情况: O(n^2) 最坏情况: O(n^…...

【工欲善其事】巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号
文章目录 巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号1 问题描述2 解决方案3 具体步骤4 效果测试5 小结与复盘 巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号 1 问题描述 不知各位是否也为复制过来的文本中夹杂的回车换行符抓狂过?就是在复…...

Maven与Gradle的区别
Maven与Gradle是两种流行的构建工具,广泛用于Java项目的管理和构建。以下是它们的对比,包括官网、Windows 11配置环境、在IDEA中的相同点和不同点,以及它们各自的优缺点。 官网 Maven官网: https://maven.apache.orgGradle官网: https://gr…...

【linux 多进程并发】0202 Linux进程fork之后父子进程间的文件操作有着相同的偏移记录,多进程操作文件的方法
0202 Linux进程资源 专栏内容: postgresql使用入门基础手写数据库toadb并发编程 个人主页:我的主页 管理社区:开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 文章目录 020…...

SQLite在安卓中的应用
在 Android 应用程序中,SQLite 是默认的嵌入式数据库解决方案,Android 系统为开发者提供了相应的 API 来管理 SQLite 数据库。通过使用 SQLiteOpenHelper 类和 SQLiteDatabase 类,开发者可以方便地创建、查询、更新和删除数据库中的数据。 以…...

Python数据库操作
前面的章节中学习了使用 Python 读写文件的方法,大家可以用文件方式来存放数据,不过使用文件方式时不容易管理,同时还容易丢失,会带来许多问题。目前主流的方法都是采用数据库软件,通过数据库软件来组织和存放数据&…...

交叉熵损失函数为代表的两层神经网络的反向传播量化求导计算公式
反向传播(back propagation,BP)算法也称误差逆传播,是神经网络训练的核心算法。我们通常说的 BP 神经网络是指应用反向传播算法进行训练的神经网络模型。反向传播算法的工作机制究竟是怎样的呢?我们以一个两层…...

数据结构——八大排序(上)
数据结构中的八大排序算法是计算机科学领域经典的排序方法,它们各自具有不同的特点和适用场景。以下是这八大排序算法的详细介绍: 一、插入排序(Insertion Sort) 核心思想:将数组中的所有元素依次跟前面已经排好的元…...

vxe-table 导入导出功能全解析
一、vxe-table 导入导出功能概述 vxe-table 的导入导出功能在数据处理中具有至关重要的作用。在现代数据管理和处理的场景中,高效地导入和导出数据是提高工作效率的关键。 对于导入功能而言,它允许用户将外部的表格数据,如 Excel 文件&…...

常用STL的操作以及特点
C 标准模板库(STL)提供了很多常用的数据结构和算法,极大简化了开发工作。STL 包括容器(如 vector、list、map 等)、算法(如排序、查找等)以及迭代器。以下是一些常用 STL 容器的操作以及它们的特…...