配置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驱动步骤