STM32 NOR FLASH(SPI FLASH)驱动移植(2)

2)FLASH 读取函数

c 复制代码
/*
* @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);
 spi5_read_write_byte(FLASH_ReadData); /* 发送读取命令 */
 norflash_send_address(addr); /* 发送地址 */
 
 for (i = 0; i < datalen; i++)
 {
	 pbuf[i] = spi5_read_write_byte(0XFF); /* 循环读取 */
 }
 NORFLASH_CS(1);
}

3)FLASH写函数

c 复制代码
/**
* @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 (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;/* 下一个扇区可以写完了 */
 		}
 	}
 }
}

该函数可以在 NOR FLASH 的任意地址开始写入任意长度(必须不超过 NOR FLASH 的容量)的数据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个 g_norflash_buf 的全局变量,用于擦除时缓存扇区内的数据。

3)简单介绍一下写函数的实质调用,它用到的是通过无检验写 SPI_FLASH 函数实现的,而最终是用到页写函数 norflash_write_page,在前面也对页写时序进行了分析,现在看一下代码:

c 复制代码
/**
* @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);
	 spi5_read_write_byte(FLASH_PageProgram); /* 发送写页命令 */
	 norflash_send_address(addr); /* 发送地址 */
	 for (i = 0; i < datalen; i++)
	 {
	 	spi5_read_write_byte(pbuf[i]); /* 循环读取 */
	 }
	 NORFLASH_CS(1);
	 norflash_wait_busy(); /* 等待写入结束 */
}

在页写功能的代码中,先发送写使能命令,才发送页写命令,然后发送写入的地址,再把写入的内容通过一个 for 循环写入,发送完后拉高片选 CS 引脚结束通信,等待 flash 内部写入结束。检测 flash 内部的状态可以通过查看 25Q256 状态寄存器 1 的位 0。我们也定义了一个函数 norflash_read_sr,去读取 25Q256 状态寄存器的值,这里就不列出来了,主要实现的方式也是老套路:根据传参判断需要获取的是哪个状态寄存器,然后拉低片选线,调用 spi5_read_write_byte函数发送该寄存器的命令,然后通过发送一字节空数据获取读取到的数据,最后拉高片选线,函数返回读取到的值。

在 norflash_write_page 函数的基础上,增加了 norflash_write_nocheck 函数进行封装解决写入字节可能大于该页剩下的字节数问题,方便解决写入错误问题,其代码如下:

c 复制代码
/**
* @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 个字节 */
 {
 pageremain = datalen;
 }
 while (1)
 {
 /* 当写入字节比页内剩余地址还少的时候, 一次性写完
 * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 
 * 然后根据剩余长度进行不同处理 */
 norflash_write_page(pbuf, addr, pageremain);
 if (datalen == pageremain) /* 写入结束了 */
 {
 break;
 }
 else /* datalen > pageremain */
 {
 pbuf += pageremain; /* pbuf 指针地址偏移,前面已经写了 pageremain 字节 */
 addr += pageremain; /* 写地址偏移,前面已经写了 pageremain 字节 */
 datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
 if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */
 {
 pageremain = 256; /* 一次可以写入 256 个字节 */
 }
 else /* 剩余数据小于一页,可以一次写完 */
 {
 pageremain = datalen; /* 不够 256 个字节了 */
 }
 }
 }
}

上面函数的实现主要是逻辑处理,通过判断传参中的写入字节的长度与单页剩余的字节数,来决定是否是需要在新页写入剩下的字节。这里需要大家自行理解一下。通过调用该函数实现了 norflash_write 的功能。下面简单介绍一下擦除函数 norflash_erase_sector,前面工作时序中也有对此描述,现在就来看一下代码:

c 复制代码
/**
* @brief 擦除一个扇区
* @note 注意,这里是扇区地址,不是字节地址!!
* 擦除一个扇区的最少时间:150ms
*
* @param saddr : 扇区地址 根据实际容量设置
* @retval 无
*/
void norflash_erase_sector(uint32_t saddr)
{
 saddr *= 4096;
 norflash_write_enable(); /* 写使能 */
 norflash_wait_busy(); /* 等待空闲 */
 NORFLASH_CS(0);
 spi5_read_write_byte(FLASH_SectorErase); /* 发送写页命令 */
 norflash_send_address(saddr); /* 发送地址 */
 NORFLASH_CS(1);
 norflash_wait_busy(); /* 等待扇区擦除完成 */
}

该代码也是老套路,通过发送擦除指令实现擦除功能,要注意的是使用扇区擦除指令前,

需要先发送写使能指令,拉低片选线,发送扇区擦除指令之后,发送擦除的扇区地址,实现擦

除,最后拉高片选线结束通信。在函数最后通过读取寄存器状态的函数,等待扇区擦除完成。

相关推荐
阳光宅男@李光熠2 小时前
【电子通识】PWM驱动让有刷直流电机恒流工作
单片机·嵌入式硬件
半个番茄3 小时前
STM32 : 奈奎斯特-香农采样定理
网络·stm32·单片机
IT信息技术学习圈4 小时前
CSP初赛知识学习计划(第二天)
单片机·嵌入式硬件·学习
硬件技术我知道8 小时前
产品 防尘防水IP等级 划分与实验方法
网络·人工智能·嵌入式硬件·物联网·计算机视觉·硬件工程·智慧城市
JoneMaster9 小时前
[读书日志]从零开始学习Chisel 第三篇:Scala面向对象编程——类和对象(敏捷硬件开发语言Chisel与数字系统设计)
开发语言·嵌入式硬件·学习·硬件架构·scala
qq_5719572610 小时前
通过串口通信控制led灯的亮灭
c语言·stm32·单片机
hfffhfh11 小时前
STM32CUBEMX+PLS_D1000激光测距模块+MT6701角度传感器,获取三角形第三边角度
stm32·单片机·嵌入式硬件
1101 110112 小时前
STM32-笔记34-4G遥控灯
嵌入式硬件
亿道电子Emdoor15 小时前
【ARM】Keil恢复默认设置
arm开发·stm32·单片机
时光の尘18 小时前
嵌入式Linux(二)·配置VMware使用USB网卡连接STM32MP157实现Windows、Ubuntu以及开发板之间的通信
linux·服务器·c语言·windows·stm32·ubuntu