目录
[1. 软硬件环境](#1. 软硬件环境)
[1.1 软件开发环境](#1.1 软件开发环境)
[1.2 硬件环境](#1.2 硬件环境)
[2 Flash操作库函数](#2 Flash操作库函数)
[2.1 nRF52832的Flash](#2.1 nRF52832的Flash)
[2.2 Nordic 特有的 Flash 操作](#2.2 Nordic 特有的 Flash 操作)
[2.2.1 nrfx_nvmc_bytes_write 函数](#2.2.1 nrfx_nvmc_bytes_write 函数)
[2.2.2 nrfx_nvmc_page_erase函数](#2.2.2 nrfx_nvmc_page_erase函数)
[2.2.3 nrfx_nvmc_write_done_check 函数](#2.2.3 nrfx_nvmc_write_done_check 函数)
[3 操作Flash的接口函数](#3 操作Flash的接口函数)
[3.1 接口实现](#3.1 接口实现)
[3.2 函数接口](#3.2 函数接口)
[4 验证](#4 验证)
[4.1 读取Flash的参数](#4.1 读取Flash的参数)
[4.2 大容量数据读写操作](#4.2 大容量数据读写操作)
[5 使用Zephyr的Flash接口实现驱动](#5 使用Zephyr的Flash接口实现驱动)
[5.1 代码实现](#5.1 代码实现)
[5.2 测试代码实现](#5.2 测试代码实现)
[5.3 验证功能](#5.3 验证功能)
概述
本文主要介绍基于 Zephyr RTOS 操作 Nordic 芯片的 Flash 存储器,其主要涉及以下几个方面:Nordic 内部Flash的资源,操作Flash的接口,验证读写数据功能等内容。
1. 软硬件环境
1.1 软件开发环境
nordic提供了基于zephyr平台sdk, 其提供了大量的demo可供开发者参考和使用,同时nordi还提供一个集成的软件库工具,方便开发者安装相应的SDK和编译工具链。集成环境同时包含了其他的一些软件,非常便于进行项目开发。
|---------------------------|----------------------|----------|
| 软件工具 | 功能 | 版本信息 |
| nRF Connect SDK | nordic提供基于zephyr的代码库 | v2.9.0 |
| nRF Connect SDK Toolchain | 代码编译工具 | v2.9.1 |
| VS-CODE | 集成开发环境 | v1.99.3 |
| nRF Connect for Desktop | nordic集成工具链 | v5.1.0 |
| nRF Connect | 手机App | |
手机App下载地址:
bash
https://nav.nordicsemi.com/search?query=nRF%20Connect
1.2 硬件环境
本案例是在nRF52832开发板(nRF52-DK)上实现的,该开发板nRF52832的主要特点如下:
1)板载j-link调试接口
2)引出所有 IO接口,用户可根据实际应用,外载其他设备
3)支持4个LED
4)支持4路Key接口
5)板载UART调试接口,方便打印调试信息

2 Flash操作库函数
2.1 nRF52832的Flash
在 Zephyr 的设备树中配置 Flash如下,本文以nRF52832的设备树为例:

具体设备树代码如下:
cpp
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0xc000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x37000>;
};
slot1_partition: partition@43000 {
label = "image-1";
reg = <0x00043000 0x37000>;
};
storage_partition: partition@7a000 {
label = "storage";
reg = <0x0007a000 0x00006000>;
};
};
2.2 Nordic 特有的 Flash 操作
2.2.1 nrfx_nvmc_bytes_write
函数
nrfx_nvmc_bytes_write
是 Nordic 提供的用于向 Flash 写入数据的底层函数,属于 nrfx 驱动库的一部分。这个函数提供了比 Zephyr 通用 Flash API 更底层的访问方式。
函数原型
cpp
nrfx_err_t nrfx_nvmc_bytes_write(uint32_t addr, const void *p_src, uint32_t num_bytes);
参数说明
参数 | 类型 | 描述 |
---|---|---|
addr |
uint32_t |
Flash 中要写入的目标地址 |
p_src |
const void * |
包含要写入数据的源缓冲区指针 |
num_bytes |
uint32_t |
要写入的字节数 |
返回值
返回
nrfx_err_t
类型,可能的值为:
NRFX_SUCCESS
- 写入成功完成
NRFX_ERROR_INVALID_ADDR
- 提供的地址无效
NRFX_ERROR_INVALID_LENGTH
- 请求的长度无效
重要注意事项
地址对齐:
虽然函数名为
bytes_write
,但实际上写入操作是以32位字为单位进行的地址必须是4字节对齐的(addr % 4 == 0)
长度也必须是4的倍数
Flash状态:
目标区域必须已经被擦除(全为0xFF)
只能将1改为0(不能将0改为1)
中断影响:
Flash写入期间CPU会被暂停
建议在写入关键代码段时禁用中断
2.2.2 nrfx_nvmc_page_erase函数
nrfx_nvmc_page_erase
是 Nordic nRF 系列芯片提供的用于擦除 Flash 页面的底层函数,属于 nrfx 驱动库的一部分。这个函数执行的是对整个 Flash 页的擦除操作。
函数原型
cpp
void nrfx_nvmc_page_erase(uint32_t address);
参数说明
参数 | 类型 | 描述 |
---|---|---|
address |
uint32_t |
要擦除的 Flash 页中的任意地址 |
注意事项:
页面大小:
不同 nRF 芯片的 Flash 页面大小不同
nRF51 系列:1024 字节 (1KB)
nRF52 系列:4096 字节 (4KB)
可以使用
NRF_FICR->CODEPAGESIZE
获取实际的页面大小地址对齐:
地址参数不需要严格对齐到页面起始地址
函数会自动对齐到包含该地址的页面起始地址
擦除效果:
擦除后,整个页面的所有位将被设置为 1 (0xFF)
擦除是写入操作的必要前提
2.2.3 nrfx_nvmc_write_done_check
函数
nrfx_nvmc_write_done_check
是 Nordic nRF 系列芯片提供的用于检查 Flash 写入/擦除操作是否完成的辅助函数,属于 nrfx 驱动库的一部分。
函数原型
cpp
bool nrfx_nvmc_write_done_check(void);
功能说明
主要用途:
检查 NVMC (Non-Volatile Memory Controller) 是否已完成前一次 Flash 编程或擦除操作
提供非阻塞式的操作完成状态检查机制
返回值:
true
:表示所有挂起的 Flash 操作已完成
false
:表示 Flash 操作仍在进行中底层原理:
通过检查 NVMC 的 READY 寄存器位来确定操作状态
对应寄存器位:
NRF_NVMC->READY
3 操作Flash的接口函数
3.1 接口实现
在Zephyr OS框架下使用Flash的相关接口,需要做如下配置:
1)在.conf文件中使能Flash的操作接口
bash
CONFIG_NRFX_NVMC=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y

2)引用相关的头文件
cpp
#include <zephyr/kernel.h>
#include <zephyr/drivers/flash.h>
#include <nrfx_nvmc.h>

3.2 函数接口
1)写操作
cpp
u32 user_flash_write(u32 addr, const u8 *buf, u32 size)
{
nrfx_nvmc_bytes_write(addr, buf, size);
while(nrfx_nvmc_write_done_check() == false)
;
return size;
}
2)读操作
cpp
u32 user_flash_read(u32 addr, u8 *buf, u32 size)
{
u32 i;
const uint8_t *p = (const uint8_t *)addr;
for (i = 0; i < size; i ++) {
*buf ++ = *p ++;
}
return size;
}
3)擦除操作
cpp
s8 user_flash_erase(u32 addr)
{
nrfx_err_t err_code = nrfx_nvmc_page_erase(addr);
if (err_code != NRFX_SUCCESS)
{
LOG_ERR("Erase flash page(0x%08x) err_code(0x%08x)", addr, err_code);
}
while(nrfx_nvmc_write_done_check() == false)
{
// wait for erase to complete
}
return 0;
}
4 验证
4.1 读取Flash的参数
实现函数接口如下:
cpp
void user_drv_flash_msg( void )
{
flash_msg.paga_size = nrfx_nvmc_flash_page_size_get();
flash_msg.total_bytes = nrfx_nvmc_flash_size_get();
flash_msg.total_page = nrfx_nvmc_flash_page_count_get();
printf("Flash page size: %d bytes \r\n", flash_msg.paga_size );
printf("Total flash size: %d bytes \r\n", flash_msg.total_bytes );
printf("Total flash page count: %d \r\n ", flash_msg.total_page);
}
验证结果如下:

4.2 大容量数据读写操作
实现一个写大容量的数据函数:
cpp
#define NOR_FLASH_PAGE_SIZE 4096
u8 wrflash_buff[NOR_FLASH_PAGE_SIZE];
void nor_flash_Write( u32 WriteAddr, u8 *pBuffer, u16 NumByteToWrite)
{
u32 secpos;
u16 secoff;
u16 secremain;
u16 i;
secpos = WriteAddr/NOR_FLASH_PAGE_SIZE;
secoff = WriteAddr%NOR_FLASH_PAGE_SIZE;
secremain = NOR_FLASH_PAGE_SIZE-secoff;
if(NumByteToWrite <= secremain)
secremain = NumByteToWrite;
while(1)
{
user_flash_read(secpos*NOR_FLASH_PAGE_SIZE, wrflash_buff, NOR_FLASH_PAGE_SIZE);
for( i=0; i<secremain; i++)
{
if(wrflash_buff[secoff+i]!=0XFF)
break;
}
if(i<secremain)
{
user_flash_erase(secpos*NOR_FLASH_PAGE_SIZE);
for(i=0;i<secremain;i++)
{
wrflash_buff[i+secoff]=pBuffer[i];
}
user_flash_write(secpos*NOR_FLASH_PAGE_SIZE,wrflash_buff, NOR_FLASH_PAGE_SIZE);
}
else
{
user_flash_write( WriteAddr, pBuffer, secremain);
}
if( NumByteToWrite == secremain)
break;
else
{
secpos++;
secoff=0;
pBuffer += secremain;
WriteAddr += secremain;
NumByteToWrite -= secremain;
if( NumByteToWrite>NOR_FLASH_PAGE_SIZE )
secremain = NOR_FLASH_PAGE_SIZE;
else
secremain = NumByteToWrite;
}
}
}
验证函数:
cpp
u8 write_buff[1024];
u8 read_fuff[1024];
void test_flash()
{
user_drv_flash_msg();
for( int i = 0; i < 1024; i++ )
{
write_buff[i] = 0x5a;
}
u32 address = flash_msg.total_bytes - (flash_msg.total_page-3)*4096 - 12;
nor_flash_Write(address,write_buff, 1024);
// read buff
user_flash_read(address, read_fuff, 1024);
for( int i = 0; i < 1024; i++ )
{
if( read_fuff[i] != write_buff[i])
{
printk(" test_flash: failed \n");
return;
}
}
printk(" test_flash: pass \n");
}
验证结果如下:

5 使用Zephyr的Flash接口实现驱动
5.1 代码实现
使用的主要接口:
cpp
#include <drivers/flash.h>
// 获取 Flash 设备
const struct device *flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller));
// 读取数据
int flash_read(const struct device *dev, off_t offset, void *data, size_t len);
// 写入数据
int flash_write(const struct device *dev, off_t offset, const void *data, size_t len);
// 擦除扇区
int flash_erase(const struct device *dev, off_t offset, size_t size);
5.2 测试代码实现
cpp
u8 write_buff[1024];
u8 read_fuff[1024];
const struct device *flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller));
void test_flash( void )
{
u32 address;
user_drv_flash_msg();
address = flash_msg.total_bytes - (flash_msg.total_page-3)*4096;
for( int i = 0; i < 1024; i++ )
{
write_buff[i] = 0x5a;
}
// 擦除一个页面 (通常4KB)
flash_erase(flash_dev, address, 4096);
// 写入数据
flash_write(flash_dev, address, write_buff, sizeof(write_buff));
// 读取验证
flash_read(flash_dev, address, read_fuff, sizeof(read_fuff));
for( int i = 0; i < 1024; i++ )
{
if( read_fuff[i] != write_buff[i])
{
printk(" test_flash: failed \n");
return;
}
}
printk(" test_flash: pass \n");
}
5.3 验证功能
烧写代码,运行后结果如下:
