随着物联网技术的快速发展,高性能、低功耗、多协议的无线通信芯片成为智能设备的核心组件。海思 WS63 芯片(Hi3863V100)作为一款集成了 Wi-Fi 6、星闪 SLE 1.0 和 BLE 5.2 三模通信协议的物联网 SoC 芯片,凭借其出色的性能和丰富的接口资源,为开发者提供了强大的硬件平台。
硬件架构差异与适配要点
Hi3861和WS63虽然都是面向物联网应用的芯片,但硬件能力有代际差异。Hi3861采用32位微处理器架构,主频最高160MHz,内置SRAM和Flash存储,集成Wi-Fi功能。而WS63作为新一代平台,主频提升至240MHz,内置Flash容量扩大到32M bit,SRAM增加至606KB,并额外提供300KB ROM空间。
关键硬件差异对比表:
| 特性 | Hi3861V100 | WS63 |
|---|---|---|
| 主频 | 最高160MHz | 最高240MHz |
| 内置Flash | 2MB | 32M bit |
| SRAM | 352KB | 606KB + 300KB ROM |
| 无线功能 | Wi-Fi | Wi-Fi 6 + 星闪 + 蓝牙 |
| 安全特性 | 基础安全 | 国密算法SM2/SM3/SM4硬件加速 |
一、项目背景
在海思WS63嵌入式平台上,为了实现数据的持久化存储和文件管理功能,需要移植一个轻量级的文件系统。LittleFS是一个专为嵌入式系统设计的轻量级文件系统,具有断电保护、磨损均衡和低内存占用等优点,非常适合在资源受限的嵌入式系统中使用。
虽然WS63的sdk内置了LittleFS文件系统组件,但是它默认使用的是它的内置flash,空间太小。
内置文件系统代码在fbb_ws63/src/open_source/littlefs/v2.5.0/路径下。
本文档详细介绍如何将LittleFS文件系统移植到海思WS63平台,使用外扩的W25Q128 NOR Flash作为存储介质。
在线文档地址 :https://docs.hisilicon.com/repos/fbb_ws63/zh-CN/master/
论坛地址 :https://developers.hisilicon.com/forum/0133146886267870001
SDK代码仓地址 :https://gitcode.com/HiSpark/fbb_ws63


二、技术选型
2.1 LittleFS特性
LittleFS是由ARM公司开发的一个轻量级文件系统,具有以下特点:
- 断电保护:支持断电恢复,确保数据完整性
- 磨损均衡:自动管理Flash擦写周期,延长Flash寿命
- 低内存占用:RAM占用极小,适合资源受限系统
- 动态文件大小:支持动态大小的文件
- 目录支持:支持目录结构
- 可移植性强:通过移植层适配不同的存储介质
2.2 W25Q128 NOR Flash特性
W25Q128是Winbond公司生产的一款128Mbit(16MB)容量的串行Flash存储器,具有以下特点:
- 容量:16MB(128Mbit)
- 接口:SPI接口(支持标准SPI、双SPI、四SPI)
- 擦除块大小:4KB、32KB、64KB
- 页大小:256字节
- 擦写寿命:10万次
- 数据保持:20年@25°C
- 工作电压:2.7V-3.6V
三、硬件连接与驱动配置
3.1 硬件连接
在海思WS63平台上,W25Q128通过SPI接口连接,具体连接如下:
| W25Q128引脚 | WS63引脚 | 功能说明 |
|---|---|---|
| CS (Pin 1) | GPIO_XX | 片选信号 |
| CLK (Pin 6) | SPI1_CLK | 时钟信号 |
| MOSI (Pin 5) | SPI1_MOSI | 主出从入 |
| MISO (Pin 2) | SPI1_MISO | 主入从出 |
| VCC (Pin 8) | 3.3V | 电源 |
| GND (Pin 4) | GND | 地 |
3.2 SPI驱动配置
WS63平台使用CMSIS-RTOS2的HAL层驱动SPI接口,配置代码如下:
c
/* board_config.c - SPI配置 */
#include "w25q128.h"
#include "spi.h"
#define W25Q128_CS_PIN GPIO_PIN_XX
#define W25Q128_CS_PORT GPIO_PORT_XX
static void w25q128_cs_low(void)
{
HAL_GPIO_WritePin(W25Q128_CS_PORT, W25Q128_CS_PIN, GPIO_PIN_RESET);
}
static void w25q128_cs_high(void)
{
HAL_GPIO_WritePin(W25Q128_CS_PORT, W25Q128_CS_PIN, GPIO_PIN_SET);
}
int flash_write_read(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length)
{
SPI_HandleTypeDef *hspi = &hspi1;
HAL_StatusTypeDef status;
w25q128_cs_low();
status = HAL_SPI_TransmitReceive(hspi, (uint8_t *)tx_data, rx_data, length, 1000);
w25q128_cs_high();
if (status != HAL_OK) {
elog_e("flash", "SPI transfer failed: %d", status);
return -1;
}
return 0;
}
3.3 W25Q128驱动实现
c
/* w25q128.h */
#ifndef W25Q128_H
#define W25Q128_H
#include <stdint.h>
#include <stdbool.h>
#define W25Q128_PAGE_SIZE 256
#define W25Q128_SECTOR_SIZE 4096
#define W25Q128_BLOCK_SIZE_32K 32768
#define W25Q128_BLOCK_SIZE_64K 65536
#define W25Q128_CHIP_SIZE (16 * 1024 * 1024)
/* W25Q128命令定义 */
#define W25Q128_CMD_WRITE_ENABLE 0x06
#define W25Q128_CMD_WRITE_DISABLE 0x04
#define W25Q128_CMD_READ_STATUS_REG 0x05
#define W25Q128_CMD_WRITE_STATUS_REG 0x01
#define W25Q128_CMD_READ_DATA 0x03
#define W25Q128_CMD_PAGE_PROGRAM 0x02
#define W25Q128_CMD_SECTOR_ERASE 0x20
#define W25Q128_CMD_BLOCK_ERASE_32K 0x52
#define W25Q128_CMD_BLOCK_ERASE_64K 0xD8
#define W25Q128_CMD_CHIP_ERASE 0xC7
#define W25Q128_CMD_READ_JEDEC_ID 0x9F
#define W25Q128_CMD_READ_UNIQUE_ID 0x4B
int w25q128_init(void);
int w25q128_read(uint32_t addr, uint8_t *data, uint32_t len);
int w25q128_write(uint32_t addr, const uint8_t *data, uint32_t len);
int w25q128_erase(uint32_t addr, uint32_t len);
int w25q128_chip_erase(void);
#endif /* W25Q128_H */
c
/* w25q128.c */
#include "w25q128.h"
#include "elog.h"
#include "cmsis_os2.h"
extern int flash_write_read(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
static bool w25q128_is_busy(void)
{
uint8_t tx_buf[2] = {W25Q128_CMD_READ_STATUS_REG, 0xFF};
uint8_t rx_buf[2] = {0};
flash_write_read(tx_buf, rx_buf, 2);
return (rx_buf[1] & 0x01) != 0;
}
static int w25q128_wait_busy(uint32_t timeout_ms)
{
uint32_t start = osKernelGetTickCount();
while (w25q128_is_busy()) {
if ((osKernelGetTickCount() - start) >= timeout_ms) {
elog_e("w25q128", "Wait busy timeout");
return -1;
}
osDelay(1);
}
return 0;
}
static int w25q128_write_enable(void)
{
uint8_t cmd = W25Q128_CMD_WRITE_ENABLE;
return flash_write_read(&cmd, &cmd, 1);
}
int w25q128_init(void)
{
uint8_t tx_buf[4] = {W25Q128_CMD_READ_JEDEC_ID, 0xFF, 0xFF, 0xFF};
uint8_t rx_buf[4] = {0};
elog_i("w25q128", "Initializing W25Q128...");
if (flash_write_read(tx_buf, rx_buf, 4) != 0) {
elog_e("w25q128", "Read JEDEC ID failed");
return -1;
}
uint32_t jedec_id = (rx_buf[1] << 16) | (rx_buf[2] << 8) | rx_buf[3];
elog_i("w25q128", "JEDEC ID: 0x%06X", jedec_id);
if ((rx_buf[1] != 0xEF) || (rx_buf[2] != 0x40)) {
elog_e("w25q128", "Unknown flash device");
return -1;
}
elog_i("w25q128", "W25Q128 initialized successfully");
return 0;
}
int w25q128_read(uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t tx_buf[4];
if ((addr + len) > W25Q128_CHIP_SIZE) {
elog_e("w25q128", "Read address out of range");
return -1;
}
tx_buf[0] = W25Q128_CMD_READ_DATA;
tx_buf[1] = (addr >> 16) & 0xFF;
tx_buf[2] = (addr >> 8) & 0xFF;
tx_buf[3] = addr & 0xFF;
if (flash_write_read(tx_buf, tx_buf, 4) != 0) {
return -1;
}
if (flash_write_read(NULL, data, len) != 0) {
return -1;
}
return 0;
}
int w25q128_write(uint32_t addr, const uint8_t *data, uint32_t len)
{
uint32_t offset = 0;
uint32_t remain = len;
if ((addr + len) > W25Q128_CHIP_SIZE) {
elog_e("w25q128", "Write address out of range");
return -1;
}
while (remain > 0) {
uint32_t page_offset = addr % W25Q128_PAGE_SIZE;
uint32_t page_remain = W25Q128_PAGE_SIZE - page_offset;
uint32_t write_len = (remain < page_remain) ? remain : page_remain;
if (w25q128_write_enable() != 0) {
return -1;
}
uint8_t tx_buf[4 + W25Q128_PAGE_SIZE];
tx_buf[0] = W25Q128_CMD_PAGE_PROGRAM;
tx_buf[1] = (addr >> 16) & 0xFF;
tx_buf[2] = (addr >> 8) & 0xFF;
tx_buf[3] = addr & 0xFF;
memcpy(&tx_buf[4], &data[offset], write_len);
if (flash_write_read(tx_buf, NULL, 4 + write_len) != 0) {
return -1;
}
if (w25q128_wait_busy(100) != 0) {
return -1;
}
addr += write_len;
offset += write_len;
remain -= write_len;
}
return 0;
}
int w25q128_erase(uint32_t addr, uint32_t len)
{
uint32_t sector_start = (addr / W25Q128_SECTOR_SIZE) * W25Q128_SECTOR_SIZE;
uint32_t sector_end = ((addr + len - 1) / W25Q128_SECTOR_SIZE + 1) * W25Q128_SECTOR_SIZE;
if (sector_end > W25Q128_CHIP_SIZE) {
sector_end = W25Q128_CHIP_SIZE;
}
elog_d("w25q128", "Erase from 0x%08X to 0x%08X", sector_start, sector_end);
for (uint32_t sector = sector_start; sector < sector_end; sector += W25Q128_SECTOR_SIZE) {
if (w25q128_write_enable() != 0) {
return -1;
}
uint8_t tx_buf[4];
tx_buf[0] = W25Q128_CMD_SECTOR_ERASE;
tx_buf[1] = (sector >> 16) & 0xFF;
tx_buf[2] = (sector >> 8) & 0xFF;
tx_buf[3] = sector & 0xFF;
if (flash_write_read(tx_buf, NULL, 4) != 0) {
return -1;
}
if (w25q128_wait_busy(500) != 0) {
return -1;
}
}
return 0;
}
int w25q128_chip_erase(void)
{
elog_w("w25q128", "Chip erase started");
if (w25q128_write_enable() != 0) {
return -1;
}
uint8_t cmd = W25Q128_CMD_CHIP_ERASE;
if (flash_write_read(&cmd, NULL, 1) != 0) {
return -1;
}
if (w25q128_wait_busy(10000) != 0) {
return -1;
}
elog_i("w25q128", "Chip erase completed");
return 0;
}
四、LittleFS源码获取与移植
4.1 获取LittleFS源码
LittleFS的源码可以从GitHub官方仓库获取:
bash
git clone https://github.com/littlefs-project/littlefs.git
或者直接下载最新版本的源码包。
4.2 LittleFS移植层实现
LittleFS通过移植层与底层存储介质交互,需要实现以下接口:
c
/* littlefs_port.h */
#ifndef LITTLEFS_PORT_H
#define LITTLEFS_PORT_H
#include "lfs.h"
#include "w25q128.h"
#include <stdint.h>
#define LFS_READ_SIZE 256
#define LFS_PROG_SIZE 256
#define LFS_BLOCK_SIZE 4096
#define LFS_BLOCK_COUNT (W25Q128_CHIP_SIZE / LFS_BLOCK_SIZE)
#define LFS_BLOCK_CYCLES 100
#define LFS_CACHE_SIZE 256
#define LFS_LOOKAHEAD_SIZE 16
int littlefs_init(void);
int littlefs_format(void);
int littlefs_mount(void);
int littlefs_unmount(void);
#endif /* LITTLEFS_PORT_H */
c
/* littlefs_port.c */
#include "littlefs_port.h"
#include "elog.h"
#include <string.h>
static lfs_t lfs;
static lfs_config_t cfg;
static int lfs_read(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size)
{
uint32_t addr = block * c->block_size + off;
return w25q128_read(addr, (uint8_t *)buffer, size);
}
static int lfs_prog(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size)
{
uint32_t addr = block * c->block_size + off;
return w25q128_write(addr, (const uint8_t *)buffer, size);
}
static int lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
uint32_t addr = block * c->block_size;
return w25q128_erase(addr, c->block_size);
}
static int lfs_sync(const struct lfs_config *c)
{
return 0;
}
int littlefs_init(void)
{
elog_i("littlefs", "Initializing LittleFS...");
memset(&cfg, 0, sizeof(cfg));
cfg.read = lfs_read;
cfg.prog = lfs_prog;
cfg.erase = lfs_erase;
cfg.sync = lfs_sync;
cfg.read_size = LFS_READ_SIZE;
cfg.prog_size = LFS_PROG_SIZE;
cfg.block_size = LFS_BLOCK_SIZE;
cfg.block_count = LFS_BLOCK_COUNT;
cfg.block_cycles = LFS_BLOCK_CYCLES;
cfg.cache_size = LFS_CACHE_SIZE;
cfg.lookahead_size = LFS_LOOKAHEAD_SIZE;
elog_i("littlefs", "Block size: %d, Block count: %d, Total size: %d bytes",
cfg.block_size, cfg.block_count, cfg.block_size * cfg.block_count);
return 0;
}
int littlefs_format(void)
{
elog_w("littlefs", "Formatting filesystem...");
int err = lfs_format(&lfs, &cfg);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Format failed: %d", err);
return -1;
}
elog_i("littlefs", "Format completed");
return 0;
}
int littlefs_mount(void)
{
elog_i("littlefs", "Mounting filesystem...");
int err = lfs_mount(&lfs, &cfg);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Mount failed: %d", err);
return -1;
}
elog_i("littlefs", "Filesystem mounted successfully");
return 0;
}
int littlefs_unmount(void)
{
elog_i("littlefs", "Unmounting filesystem...");
int err = lfs_unmount(&lfs);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Unmount failed: %d", err);
return -1;
}
elog_i("littlefs", "Filesystem unmounted");
return 0;
}
五、文件系统操作API封装
为了方便使用,我们对LittleFS的底层API进行封装:
c
/* littlefs_api.h */
#ifndef LITTLEFS_API_H
#define LITTLEFS_API_H
#include <stdint.h>
#include <stdbool.h>
#define LFS_MAX_PATH_LEN 256
#define LFS_MAX_FILENAME 64
typedef struct {
char name[LFS_MAX_FILENAME];
uint32_t size;
bool is_dir;
} lfs_file_info_t;
int lfs_mkdir(const char *path);
int lfs_remove(const char *path);
int lfs_rename(const char *old_path, const char *new_path);
int lfs_stat(const char *path, lfs_file_info_t *info);
int lfs_list_dir(const char *path, lfs_file_info_t *files, uint32_t *count);
uint32_t lfs_get_free_space(void);
int lfs_file_read_all(const char *path, uint8_t *buffer, uint32_t size);
int lfs_file_write_all(const char *path, const uint8_t *data, uint32_t size);
#endif /* LITTLEFS_API_H */
c
/* littlefs_api.c */
#include "littlefs_api.h"
#include "littlefs_port.h"
#include "elog.h"
#include <string.h>
extern lfs_t lfs;
int lfs_mkdir(const char *path)
{
elog_i("littlefs", "Create directory: %s", path);
int err = lfs_mkdir(&lfs, path);
if (err != LFS_ERR_OK && err != LFS_ERR_EXIST) {
elog_e("littlefs", "Create directory failed: %d", err);
return -1;
}
return 0;
}
int lfs_remove(const char *path)
{
elog_i("littlefs", "Remove: %s", path);
int err = lfs_remove(&lfs, path);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Remove failed: %d", err);
return -1;
}
return 0;
}
int lfs_rename(const char *old_path, const char *new_path)
{
elog_i("littlefs", "Rename: %s -> %s", old_path, new_path);
int err = lfs_rename(&lfs, old_path, new_path);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Rename failed: %d", err);
return -1;
}
return 0;
}
int lfs_stat(const char *path, lfs_file_info_t *info)
{
struct lfs_info lfs_info;
int err = lfs_stat(&lfs, path, &lfs_info);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Stat failed: %d", err);
return -1;
}
strncpy(info->name, lfs_info.name, LFS_MAX_FILENAME - 1);
info->name[LFS_MAX_FILENAME - 1] = '\0';
info->size = lfs_info.size;
info->is_dir = (lfs_info.type == LFS_TYPE_DIR);
return 0;
}
int lfs_list_dir(const char *path, lfs_file_info_t *files, uint32_t *count)
{
lfs_dir_t dir;
struct lfs_info lfs_info;
uint32_t index = 0;
int err = lfs_dir_open(&lfs, &dir, path);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Open directory failed: %d", err);
return -1;
}
while (lfs_dir_read(&lfs, &dir, &lfs_info)) {
if (strcmp(lfs_info.name, ".") == 0 || strcmp(lfs_info.name, "..") == 0) {
continue;
}
if (index < *count) {
strncpy(files[index].name, lfs_info.name, LFS_MAX_FILENAME - 1);
files[index].name[LFS_MAX_FILENAME - 1] = '\0';
files[index].size = lfs_info.size;
files[index].is_dir = (lfs_info.type == LFS_TYPE_DIR);
index++;
} else {
break;
}
}
lfs_dir_close(&lfs, &dir);
*count = index;
return 0;
}
uint32_t lfs_get_free_space(void)
{
lfs_ssize_t used = lfs_fs_size(&lfs);
if (used < 0) {
return 0;
}
return (LFS_BLOCK_COUNT - used) * LFS_BLOCK_SIZE;
}
int lfs_file_read_all(const char *path, uint8_t *buffer, uint32_t size)
{
lfs_file_t file;
lfs_ssize_t read_size;
int err = lfs_file_open(&lfs, &file, path, LFS_O_RDONLY);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Open file failed: %d", err);
return -1;
}
read_size = lfs_file_read(&lfs, &file, buffer, size);
lfs_file_close(&lfs, &file);
if (read_size < 0) {
elog_e("littlefs", "Read file failed: %d", (int)read_size);
return -1;
}
return (int)read_size;
}
int lfs_file_write_all(const char *path, const uint8_t *data, uint32_t size)
{
lfs_file_t file;
lfs_ssize_t write_size;
int err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
if (err != LFS_ERR_OK) {
elog_e("littlefs", "Open file failed: %d", err);
return -1;
}
write_size = lfs_file_write(&lfs, &file, data, size);
lfs_file_close(&lfs, &file);
if (write_size < 0) {
elog_e("littlefs", "Write file failed: %d", (int)write_size);
return -1;
}
return (int)write_size;
}
六、应用示例
6.1 文件系统初始化
c
/* main.c - 文件系统初始化 */
#include "littlefs_port.h"
#include "littlefs_api.h"
#include "w25q128.h"
#include "elog.h"
void filesystem_init(void)
{
elog_i("app", "Initializing filesystem...");
if (w25q128_init() != 0) {
elog_e("app", "W25Q128 initialization failed");
return;
}
if (littlefs_init() != 0) {
elog_e("app", "LittleFS initialization failed");
return;
}
if (littlefs_mount() != 0) {
elog_w("app", "Mount failed, trying to format...");
if (littlefs_format() != 0) {
elog_e("app", "Format failed");
return;
}
if (littlefs_mount() != 0) {
elog_e("app", "Mount after format failed");
return;
}
}
elog_i("app", "Filesystem initialized successfully");
elog_i("app", "Free space: %d bytes", lfs_get_free_space());
}
6.2 文件读写示例
c
/* file_operations.c - 文件操作示例 */
#include "littlefs_api.h"
#include "elog.h"
#include <string.h>
void file_write_read_test(void)
{
const char *test_file = "/test/data.txt";
const char *test_data = "Hello, LittleFS on WS63!";
uint8_t read_buffer[256];
int ret;
elog_i("test", "=== File Write/Read Test ===");
ret = lfs_file_write_all(test_file, (const uint8_t *)test_data, strlen(test_data));
if (ret < 0) {
elog_e("test", "Write file failed");
return;
}
elog_i("test", "Write %d bytes to %s", ret, test_file);
ret = lfs_file_read_all(test_file, read_buffer, sizeof(read_buffer));
if (ret < 0) {
elog_e("test", "Read file failed");
return;
}
read_buffer[ret] = '\0';
elog_i("test", "Read %d bytes from %s: %s", ret, test_file, read_buffer);
}
6.3 目录操作示例
c
/* dir_operations.c - 目录操作示例 */
#include "littlefs_api.h"
#include "elog.h"
void directory_operations_test(void)
{
lfs_file_info_t files[32];
uint32_t count = 32;
elog_i("test", "=== Directory Operations Test ===");
lfs_mkdir("/config");
lfs_mkdir("/data");
lfs_mkdir("/log");
lfs_file_write_all("/config/settings.ini", (const uint8_t *)"version=1.0", 12);
lfs_file_write_all("/data/sensor.txt", (const uint8_t *)"temp=25.5", 10);
elog_i("test", "Listing root directory:");
count = 32;
lfs_list_dir("/", files, &count);
for (uint32_t i = 0; i < count; i++) {
elog_i("test", " %s [%s] %d bytes",
files[i].name,
files[i].is_dir ? "DIR" : "FILE",
files[i].size);
}
elog_i("test", "Free space: %d bytes", lfs_get_free_space());
}
6.4 配置文件存储示例
c
/* config_storage.c - 配置文件存储 */
#include "littlefs_api.h"
#include "elog.h"
#include <stdio.h>
#include <string.h>
typedef struct {
char wifi_ssid[32];
char wifi_password[64];
int log_level;
int sample_interval;
} app_config_t;
int config_save(const app_config_t *config)
{
char buffer[256];
int len;
len = snprintf(buffer, sizeof(buffer),
"wifi_ssid=%s\n"
"wifi_password=%s\n"
"log_level=%d\n"
"sample_interval=%d\n",
config->wifi_ssid,
config->wifi_password,
config->log_level,
config->sample_interval);
if (lfs_file_write_all("/config/app.cfg", (const uint8_t *)buffer, len) < 0) {
elog_e("config", "Save config failed");
return -1;
}
elog_i("config", "Config saved successfully");
return 0;
}
int config_load(app_config_t *config)
{
char buffer[256];
int len;
char *line;
len = lfs_file_read_all("/config/app.cfg", (uint8_t *)buffer, sizeof(buffer) - 1);
if (len < 0) {
elog_e("config", "Load config failed");
return -1;
}
buffer[len] = '\0';
line = strtok(buffer, "\n");
while (line != NULL) {
if (strncmp(line, "wifi_ssid=", 10) == 0) {
strncpy(config->wifi_ssid, line + 10, sizeof(config->wifi_ssid) - 1);
} else if (strncmp(line, "wifi_password=", 14) == 0) {
strncpy(config->wifi_password, line + 14, sizeof(config->wifi_password) - 1);
} else if (strncmp(line, "log_level=", 10) == 0) {
config->log_level = atoi(line + 10);
} else if (strncmp(line, "sample_interval=", 16) == 0) {
config->sample_interval = atoi(line + 16);
}
line = strtok(NULL, "\n");
}
elog_i("config", "Config loaded: SSID=%s, log_level=%d",
config->wifi_ssid, config->log_level);
return 0;
}
七、测试验证
7.1 功能测试
c
/* filesystem_test.c - 文件系统测试 */
#include "littlefs_api.h"
#include "elog.h"
#include <string.h>
void filesystem_test(void)
{
elog_i("test", "========== Filesystem Test Start ==========");
file_write_read_test();
directory_operations_test();
elog_i("test", "Stress test: Write/Read 1000 times");
const char *stress_file = "/test/stress.dat";
uint8_t write_buf[256];
uint8_t read_buf[256];
for (int i = 0; i < 1000; i++) {
memset(write_buf, i % 256, sizeof(write_buf));
if (lfs_file_write_all(stress_file, write_buf, sizeof(write_buf)) < 0) {
elog_e("test", "Stress test write failed at iteration %d", i);
break;
}
if (lfs_file_read_all(stress_file, read_buf, sizeof(read_buf)) < 0) {
elog_e("test", "Stress test read failed at iteration %d", i);
break;
}
if (memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
elog_e("test", "Stress test data mismatch at iteration %d", i);
break;
}
if ((i + 1) % 100 == 0) {
elog_i("test", "Completed %d iterations", i + 1);
}
}
elog_i("test", "Free space after test: %d bytes", lfs_get_free_space());
elog_i("test", "========== Filesystem Test End ==========");
}
7.2 性能测试
c
/* performance_test.c - 性能测试 */
#include "littlefs_api.h"
#include "elog.h"
#include "cmsis_os2.h"
#include <string.h>
void performance_test(void)
{
elog_i("perf", "========== Performance Test ==========");
const char *perf_file = "/test/perf.dat";
uint8_t *buffer;
uint32_t test_sizes[] = {256, 1024, 4096, 16384};
buffer = malloc(16384);
if (buffer == NULL) {
elog_e("perf", "Memory allocation failed");
return;
}
memset(buffer, 0xAA, 16384);
for (int i = 0; i < sizeof(test_sizes) / sizeof(test_sizes[0]); i++) {
uint32_t size = test_sizes[i];
uint32_t iterations = 100;
uint32_t start, elapsed;
float throughput;
start = osKernelGetTickCount();
for (int j = 0; j < iterations; j++) {
lfs_file_write_all(perf_file, buffer, size);
}
elapsed = osKernelGetTickCount() - start;
throughput = (float)(size * iterations) / elapsed * 1000.0f / 1024.0f;
elog_i("perf", "Write %d bytes x %d: %d ms, %.2f KB/s",
size, iterations, elapsed, throughput);
start = osKernelGetTickCount();
for (int j = 0; j < iterations; j++) {
lfs_file_read_all(perf_file, buffer, size);
}
elapsed = osKernelGetTickCount() - start;
throughput = (float)(size * iterations) / elapsed * 1000.0f / 1024.0f;
elog_i("perf", "Read %d bytes x %d: %d ms, %.2f KB/s",
size, iterations, elapsed, throughput);
}
free(buffer);
elog_i("perf", "========== Performance Test End ==========");
}
7.3 断电保护测试
c
/* power_cycle_test.c - 断电保护测试 */
#include "littlefs_api.h"
#include "elog.h"
#include <string.h>
void power_cycle_test(void)
{
elog_i("test", "========== Power Cycle Test ==========");
const char *test_file = "/test/power.dat";
const char *test_data = "Power cycle test data";
uint8_t read_buffer[256];
if (lfs_file_read_all(test_file, read_buffer, sizeof(read_buffer)) > 0) {
read_buffer[sizeof(test_data)] = '\0';
if (strcmp((char *)read_buffer, test_data) == 0) {
elog_i("test", "Power cycle test: Data integrity verified!");
} else {
elog_e("test", "Power cycle test: Data corrupted!");
}
} else {
elog_i("test", "Power cycle test: No existing data, writing test data");
lfs_file_write_all(test_file, (const uint8_t *)test_data, strlen(test_data));
elog_i("test", "Power cycle test: Test data written, please power cycle now");
}
elog_i("test", "========== Power Cycle Test End ==========");
}
八、常见问题与解决方案
8.1 挂载失败
问题:文件系统挂载失败,返回LFS_ERR_CORRUPT错误。
原因:Flash数据损坏或首次使用未格式化。
解决方案:
c
if (littlefs_mount() != 0) {
elog_w("app", "Mount failed, formatting...");
if (littlefs_format() == 0) {
littlefs_mount();
}
}
8.2 写入失败
问题:文件写入失败,返回LFS_ERR_NOSPC错误。
原因:存储空间不足。
解决方案:
c
uint32_t free_space = lfs_get_free_space();
if (free_space < required_size) {
elog_w("app", "Not enough space: %d bytes required, %d bytes available",
required_size, free_space);
lfs_remove("/log/old.log");
}
8.3 SPI通信错误
问题:Flash读写操作频繁失败。
原因:SPI时钟频率过高或片选信号时序问题。
解决方案:
c
/* 降低SPI时钟频率 */
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
HAL_SPI_Init(&hspi1);
/* 增加片选信号切换延迟 */
static void w25q128_cs_low(void)
{
HAL_GPIO_WritePin(W25Q128_CS_PORT, W25Q128_CS_PIN, GPIO_PIN_RESET);
osDelay(1);
}
static void w25q128_cs_high(void)
{
osDelay(1);
HAL_GPIO_WritePin(W25Q128_CS_PORT, W25Q128_CS_PIN, GPIO_PIN_SET);
}
8.4 文件系统性能优化
问题:文件读写速度较慢。
解决方案:
c
/* 增加缓存大小 */
#define LFS_CACHE_SIZE 1024
#define LFS_LOOKAHEAD_SIZE 32
/* 使用更大的读写块大小 */
#define LFS_READ_SIZE 512
#define LFS_PROG_SIZE 512
九、总结
本文档详细介绍了将LittleFS文件系统移植到海思WS63平台的完整过程,包括:
- 硬件配置:W25Q128 NOR Flash与WS63平台的SPI连接
- 驱动实现:W25Q128底层驱动和LittleFS移植层
- API封装:提供简洁易用的文件操作接口
- 应用示例:文件读写、目录操作、配置存储等实用示例
- 测试验证:功能测试、性能测试、断电保护测试
- 问题解决:常见问题的诊断和解决方案
通过本次移植,WS63平台获得了可靠的文件系统支持,可以方便地进行数据持久化存储和文件管理。LittleFS的断电保护和磨损均衡特性,确保了在嵌入式环境下的数据安全性和Flash寿命。
十、参考资料
- LittleFS官方文档:https://github.com/littlefs-project/littlefs
- W25Q128数据手册:https://www.winbond.com/resource-files/w25q128fv rev.i 06272017.pdf
- 海思WS63开发指南
- CMSIS-RTOS2 API文档