第一章节:w25q64使用fal组件进行外部flash分区,并完成相关测试
第二章节:w25q64挂载文件系统配置,进行相关测试
第三章节:将日志信息存储于文件中,文件以年月日进行命名,每天生成一个日志
第四章节:在第三章基础上增加easyflash软件包实现键值对形式的数据存储和读取(分区一挂载文件系统存储日志信息,分区二通过键值对形式实现参数的存储)
第五章节:在第四章基础上增加agile_ftp软件包结合FileZilla软件将开发板日志信息导出到电脑(该方式更好用)
第六章节:在第四章接基础上增加netutils软件包软件包结合Tftpd64软件将日志导出文件到电脑
文章目录
- 一、W25Q64+FAL进行W25Q64(外部flash)分区
- 二、W25Q64-虚拟文件系统挂载文件
- 三、开启ulog组件:将日志信息存储到挂载的文件系统
- 四、W25Q64-FAL-DFS-easyflash
- 五、agile_ftp软件包将开发板日志文件导出到电脑
-
- (一)rw007-wifi软件包(板子上自带的wifi模块)
- (二)agile_ftp软件包
- [(三)方法一:电脑使用WIN+E进入 WINDOWS 资源管理器地址栏输入IP地址获取开发板日志文件](#(三)方法一:电脑使用WIN+E进入 WINDOWS 资源管理器地址栏输入IP地址获取开发板日志文件)
- (四)方法二:使用FileZilla软件进行操作,将所需日志文件下载到电脑
- 六、使用netutils软件包操作导入导出文件
一、W25Q64+FAL进行W25Q64(外部flash)分区
SPI-W25Q64配置及使用SFUD组件进行数据读写测试参考下文
RT-Thread studio SPI-W25Q64 SPI+SFUD+W25Q64
021基础上修改021-spak-device-spi-w25q64
通过 RT-Thread 提供的 FAL 组件对 Flash 进行分区管理操作,通过调用
FAL 接口完成了对指定分区的测试工作,完成了对 Flash 读、写、擦除测试。

(一)FAL组件+Error解决
1、使能fal组件
打开"rtthread studio studio"按照如图所示使能FAL组件,并且修改FAL组件设备名字"W25Q64"


保存编译程序后发现有错误
3、解决第一个报错:头文件缺少路径-需要手动增加

打开头文件在左上角可以看到头文件路径,增加对应的头文件路径即可

重新编译报错减少

4、解决第二个报错,将内部flash相关部分删除掉

删除内部flash之后如下所示:

5、再次编译程序 "error" 已经解决完毕

(二)根据开发板修改FAL代码
1、修改fal_cfg.h头文件
(1)修改外部flash分区表(分区表的名字可以随意命名,和后续使用保持一致即可)

(2)宏定义名修改为W25Q64

(3)fal_cfg.h头文件修改完毕后最终代码

2、修改W25Q64初始化函数
SPI片选和探测W25Q64设备成功红后,增加FAL组件初始化函数fal_init();
代码如下所示
#include"rtthread.h"
#include"rtdevice.h"
#include"drv_spi.h"
#include"spi_flash_sfud.h"
#include"fal.h"
int w25q_sfud_test()
{
//片选挂载设备
rt_hw_spi_device_attach("spi2", "spi20", GPIOB, GPIO_PIN_12);
//探测设备
//使用sfud探测spi20设备,并将spi20连接的flash初始化为块设备,名称w25q64
if(rt_sfud_flash_probe("W25Q64", "spi20")==RT_NULL)
{
rt_kprintf("w25q64 prob failed...\n");
return -RT_ERROR;
}
fal_init();//W25Q64分区增加的fal初始化
return RT_EOK;
}
MSH_CMD_EXPORT(w25q_sfud_test,sfub test);
编译程序下载程序:输入w25q_sfud_test 指令调用函数进行W25Q64初始化及分区初始化。

运行结果如下所示,可以看到W25Q64已经按照设置的分区表被分为两个区域:
(1)分区一"param",分区2"filesystm"
(2)后续操作中分区一按照地址进行读写,用于存储参数;分区二挂载文件系统存储日志信息。

3、MSH测试指令

注意:在使用 fal 测试命令的时候,请先使用 fal probe 命令选择一个 Flash 分区
(1)输入"fal probe"可以查看W25Q64的分区情况

(2)使用 fal probe dev_name\|part_name 命令探测指定的 Flash 设备或者 Flash 分区,当探测到指定的 Flash 设备或分区后,会显示其属性信息。
输入"fal probe 分区名"可以查看该该分区的分区名,属于的flsah设备,偏移地址,分区大小;

(3)使用擦除命令时, addr 为相应探测 Flash 分区的偏移地址, size 为不超过该分区的值;
从地址0开始擦除4096个字节数据:输入"fal erase 0 4096"擦除一个扇区4096字节数据,擦除成功后相关位置被写为0XFF
(4)读取和写入测试
输入 fal read ,后面跟着待读取数据的起始地址以及长度,从 0 地址开始读取 10字节数据。
输入"fal read 0 10"表示从地址0开始读取10个数据。因为执行了擦除操作,所以数据值均为0XFF

输入"fal write 0 1 2 3 10 11 12"从地址0开始依次写入数据
输入"fal read 0 10"查看从地址0开始读取10个数据均可以发现相关位置数据已经被重新写入

(三)W25Q64分区+读写测试程序
修改代码如下所示:在fal初始化后增加读写测试函数
#include"rtthread.h"
#include"rtdevice.h"
#include"drv_spi.h"
#include"spi_flash_sfud.h"
#include"fal.h"
int w25q_sfud_test()
{
//增加写入数据和读取缓冲数组
const uint8_t test_data[]={0x0b,0x0c,0x12,0x13,0x0e,0x11,0x02};
uint8_t read_data[50]={0};
//片选挂载设备
rt_hw_spi_device_attach("spi2", "spi20", GPIOB, GPIO_PIN_12);
//探测设备
//使用sfud探测spi20设备,并将spi20连接的flash初始化为块设备,名称w25q64
if(rt_sfud_flash_probe("W25Q64", "spi20")==RT_NULL)
{
rt_kprintf("w25q64 prob failed...\n");
return -RT_ERROR;
}
fal_init();//W25Q64分区增加的fal初始化
//增加读写操作
// 查找param分区-进行数据读写测试
const struct fal_partition *part = fal_partition_find("param");
if (part)
{
fal_partition_erase(part, 0, 4096);
// 直接读写,无需创建块设备
if( fal_partition_write(part, 0, test_data, sizeof(test_data))<0)
{
rt_kprintf("write error\n");
}
else {
rt_kprintf("write success\n");
}
if(fal_partition_read(part, 0, read_data, sizeof(test_data))<0)
{
rt_kprintf("read error\n");
}
else {
rt_kprintf("read success \n");
for(int i=0;i<sizeof(test_data);i++)
rt_kprintf("%#x ",read_data[i]);
}
}
return RT_EOK;
}
MSH_CMD_EXPORT(w25q_sfud_test,sfub test);
测试效果如下所示:输入"w25q_sfud_test"调用函数

二、W25Q64-虚拟文件系统挂载文件


(一)开启虚拟文件系统-设置DFS

(二)修改W25Q64初始化程序+挂载文件系统
注意:挂载文件系统前必须创建为块设备,要不然挂载失败,踩雷点!!!
在fal初始化函数后增加创建块设备+挂载文件系统
fal_blk_device_create("filesystem");//创建块设备(挂载文件系统必备)
//格式化设备
dfs_mkfs("elm","filesystem");
// 直接挂载到根目录 /,而非 /spi
if (dfs_mount("filesystem", "/", "elm", 0, 0) == 0)
{
rt_kprintf("W25Q64 filesystem mount to / successful!\n");
}
else {
rt_kprintf("W25Q64 filesystem mount retry\n");
//格式化设备
dfs_mkfs("elm","filesystem");
if (dfs_mount("filesystem", "/", "elm", 0, 0) == 0)
rt_kprintf("W25Q64 filesystem mount to / successful!\n");
else
rt_kprintf("W25Q64 filesystem mount to / failed!\n");
}
运行程序发现提示缺少RTC设备:开启模拟RTC即可


重新编译下载:filesystem分区文件系统挂载成功,且param分区数据读写测试成功

对于挂载的文件系统进行msh指令测试:

新建目录,输出数据到文件,查看文件内容,删除文件,查看当前目录下文件

三、开启ulog组件:将日志信息存储到挂载的文件系统
(一)开启ulog组件


(二)增加RTC代码
#include"rtthread.h"
#include"rtdevice.h"
#include"time.h"
#include <rtdbg.h>
#define RTC_NAME "rtc"
#define time_set 1001
rt_device_t rtc_device=RT_NULL;
rt_thread_t rtc_th;
struct tm *p;
void rtc_thread_fun(void *parameter)
{
rt_err_t ret;
// time_t now_time;
time_t timep;
//1-查找设备
rtc_device=rt_device_find(RTC_NAME);
if(rtc_device==RT_NULL)
{
rt_kprintf("rtc device find failed..\n");
}
else {
rt_kprintf("rtc device find success...\n");
}
//2-初始化RTC设备
ret=rt_device_open(rtc_device, 0);
if(ret!=RT_EOK)
{
rt_kprintf("rtc_device open failed..\n");
}
else {
rt_kprintf("rtc device open success...\n");
}
if(time_set!=1000)
{
//3-设置日期
ret=set_date(2024, 01, 31);
if(ret!=RT_EOK)
{
rt_kprintf("RTC set date fail...\n");
}
else
{
rt_kprintf("RTC set date ok...\n");
}
//4-设置时间
ret=set_time(13, 37, 13);
if(ret!=RT_EOK)
{
rt_kprintf("RTC set time fail...\n");
}
else
{
rt_kprintf("RTC set time ok...\n");
}
}
while(1)
{
//
// now_time=time(RT_NULL);
// // /* 打 印 输 出 时 间 信 息 */
// rt_kprintf("%s ", ctime(&now_time));
time(&timep);
p = localtime(&timep);
// printf("%d-%d-%d %d:%d:%d\n", (1900 + p->tm_year), ( 1 + p->tm_mon), p->tm_mday,
// (p->tm_hour+12 ), p->tm_min, p->tm_sec);
rt_thread_mdelay(1000);//延时1s
}
}
int rtc_thread_test(void)
{
rt_err_t ret;
rtc_th=rt_thread_create("RTC_thread",rtc_thread_fun,RT_NULL,1024,6,5);
if(rtc_th==RT_NULL)
{
rt_kprintf("rtc_th create failed..\n");
}
else
{
rt_kprintf("rtc_th create success..\n");
}
ret=rt_thread_startup(rtc_th);//开启任务调度
if(ret==RT_EOK)
{
rt_kprintf("rtc_th start success..\n");
}
return RT_EOK;
}
INIT_APP_EXPORT(rtc_thread_test);
(三)增加ulog存储日志到文件代码
#include <rtthread.h>
#include <ulog_be.h>
#include <rtdevice.h>
#include <ulog_be.h>
#include "dfs_fs.h"
#include "ulog_def.h"
//新建一个注册表,把目录,文件名,大小,数量写入表内
// 后端注册表
struct _log_file
{
const char *prefix; // 文件前缀(替代原固定name)
ulog_backend_t backend;
struct ulog_file_be *file_be;
const char *dir_path; // 保存路径
rt_size_t max_num; // 保存最大文件数量
rt_size_t max_size; // 保存最大文件大小
rt_size_t buf_size; // 文件保存缓存大小
};
/*
* 文件后端标识
*/
typedef enum
{
sys_id = 0,
dht_id = 1,
}ulog_file_be_name;
#define ROOT_PATH "/day" // 设置保存的路径
#define FILE_SIZE 1024 * 1024 // 单个文件大小(可根据需求调整)
//BUFF_SIZE代表多少大小再进行一次写入,缓冲区太大,会很久才更改一次;
//太小会写入太频繁,导致ULOG异步线程堆栈不够溢出
#define BUFF_SIZE 20 // 设备缓存区大小
static struct ulog_backend sys_log_backend;
static struct ulog_file_be sys_log_file;
static struct ulog_backend dht_log_backend;
static struct ulog_file_be dht_log_file;
// 修改table:name改为prefix(文件前缀)
static struct _log_file table[] =
{
{"" ,&sys_log_backend,&sys_log_file,ROOT_PATH,10,FILE_SIZE,BUFF_SIZE},
};
extern struct tm *p;//时间结构体
/**
* @brief 获取当前日期并生成带日期的文件名
* @param prefix: 文件前缀(如sys、dht、ulog等)
* @param buf: 存储生成的文件名缓冲区
* @param buf_len: 缓冲区长度
* @retval RT_EOK: 成功,-RT_ERROR: 失败
*/
//修正文件名生成函数20210225010305
static int get_date_filename(const char *prefix, char *buf, rt_size_t buf_len)
{
rt_snprintf(buf, buf_len, "%s%04d%02d%02d%02d%02d%02d",
prefix,
1900 + p->tm_year, // tm_year 是 1900 年至今的年数
p->tm_mon + 1,
p->tm_mday,p->tm_hour,p->tm_min,p->tm_sec);
return RT_EOK;
}
/**
* @brief 初始化日志文件后端(带日期文件名)
* @param id: 日志标识(sys_id/dht_id)
* @retval RT_EOK: 成功,其他: 失败
*/
static int log_file_backend_init_with_date(ulog_file_be_name id)
{
char filename[18] = {0}; // 存储带日期的文件名
struct _log_file *log_cfg = &table[id];
struct ulog_file_be *file_be = log_cfg->file_be;
int ret;
// 生成带日期的文件名
ret = get_date_filename(log_cfg->prefix, filename, sizeof(filename));
if (ret != RT_EOK)
{
return ret;
}
// 设置父后端
file_be->parent = *log_cfg->backend;
// 初始化文件后端(使用动态生成的日期文件名)
ulog_file_backend_init(file_be,
filename, // 带日期的文件名
log_cfg->dir_path, // 保存路径
log_cfg->max_num,
log_cfg->max_size,
log_cfg->buf_size);
// 使能文件后端(init后一定要enbale,不然没有运行)
ulog_file_backend_enable(file_be);
rt_kprintf("Log backend %s init success, file: %s/%s\n",
log_cfg->prefix, log_cfg->dir_path, filename);
return RT_EOK;
}
/**
* @brief 系统日志文件后端初始化(带日期文件名)
* @retval RT_EOK: 成功
*/
int sys_log(void)
{
return log_file_backend_init_with_date(sys_id);
}
MSH_CMD_EXPORT(sys_log, sys log init with date filename)
//在重新初始化前,必须先销毁旧的日志后端,随后创建新的!!!!!
static void log_date_check_thread(void *param)
{
rt_uint8_t last_day = p->tm_mday;
while (1)
{
if (p->tm_mday != last_day)
{
rt_kprintf("Date changed, reinit log backend...\n");
// 先销毁旧的后端(关键修复)
ulog_file_backend_disable(&sys_log_file);
ulog_file_backend_deinit(&sys_log_file);
// 再初始化新的后端
sys_log();
last_day = p->tm_mday;
}
rt_thread_mdelay(4 * 1000); // 1分钟或更长时间检查一次更稳妥(为测试方便修改为4s检查一次)
}
}
/**
* @brief 启动日志日期检查线程(可选)--判断是否需要开启新的日志
*/
int log_date_check_init(void)
{
rt_thread_t tid;
tid = rt_thread_create("log_date",
log_date_check_thread,
RT_NULL,
1024, // 栈大小
25, // 优先级
10); // 时间片
if (tid != RT_NULL)
{
rt_thread_startup(tid);
rt_kprintf("Log date check thread start success!\n");
return RT_EOK;
}
return -RT_ERROR;
}
// 自动初始化日志检查线程(可选,开机启动)
INIT_APP_EXPORT(log_date_check_init);
在main函数或者其他地方开启打印

(四)编译运行程序
1、输入指令调用初始化程序:w25q_sfud_test

2、ls查看当前路径并无文件

3、运行"sys_log"之后再"ls"查看当前目录(此时会将日志信息存储于文件中)

4、"cd day" 进入目录使用"ls指令查看",可以看到已经生成了日志文件

5、但是文件命名不太对!!!
经过多次修改排查发现是设置中对象名称最大长度默认为8导致的(踩雷点),将其改为20保存编译重新下载。


6、使用"date"日期时间 指令修改一个不一样的日期
(1)例如设置2022年2月5日一个时间点:
date 2022 02 05 13 25 12

(2)再次修改为date 2022 02 05 23 59 50
设置一个2022年2月5日23:59:50的时间,使其自动跳转到新的一天,则会生成一个2022年2月6日的日志(代码中在重新初始化前,先销毁旧的日志后端,随后创建新的!!!!!)

(3)可以输入cat 20220206000001.log查看该天的日志信息

最终代码021-01-spak-device-spi-w25q64-addFAL-filesystem-logfile-ok
四、W25Q64-FAL-DFS-easyflash
在第二章基础上增加一个分区,用于easyflash软件包的操作,通过键值对的方式进行写入数据,读取数据,修改数据操作
(一)新增easyflash软件包

(二)修改easyflash代码
1、fal_cfg.h文件flash分区表中新增easyflash分区

2、easyflash软件包代码
easyflash软件包将ef_fal_port.c从ports文件夹中移动到src文件夹,并且修改宏定义"FAL_EF_PART_NAME"的值修改为"easyflash",宏定义值只要修改为分区表中的分区名一致

3、修改w25q64初始化函数代码
在fal初始化函数之后调用easyflash初始化函数:easyflash_init()
w25q64文件修改如下所示:
#include"rtthread.h"
#include"rtdevice.h"
#include"drv_spi.h"
#include"spi_flash_sfud.h"
#include"fal.h"
#include"easyflash.h"
static void easyflash_test_env(void)
{
uint32_t i_boot_times = 0;
char *c_old_boot_times, c_new_boot_times[11] = {0},mode_valve[2]={0};
/* 从环境变量中获取启动次数 */
c_old_boot_times = ef_get_env("boot_times");
/* 获取启动次数是否失败 */
if (c_old_boot_times == RT_NULL)
c_old_boot_times[0] = '0';
i_boot_times = atol(c_old_boot_times);
/* 启动次数加 1 */
i_boot_times++;
LOG_I("===============================================");
LOG_I("The system now boot %ld times", i_boot_times);
LOG_I("===============================================");
/* 数字转字符串 */
sprintf(c_new_boot_times, "%ld", i_boot_times);
/* 保存开机次数的值 */
ef_set_env("boot_times", c_new_boot_times);
ef_save_env();
/* 新增-保存mode_value数值*/
sprintf(mode_valve, "%d", 2);
ef_set_and_save_env("mode_valve", mode_valve);
LOG_I("mode_valve=%d\n", atol(ef_get_env("mode_valve")));
sprintf(mode_valve, "%d", 5);//修改数值重新打印
ef_set_and_save_env("mode_valve", mode_valve);
LOG_I("mode_valve=%d\n", atol(ef_get_env("mode_valve")));
}
int w25q_sfud_test()
{
/*************************SPI-W25Q64探测***********************************/
//增加写入数据和读取缓冲数组
const uint8_t test_data[]={0x0b,0x0c,0x12,0x13,0x0e,0x11,0x02};
uint8_t read_data[50]={0};
//片选挂载设备
rt_hw_spi_device_attach("spi2", "spi20", GPIOB, GPIO_PIN_12);
//探测设备
//使用sfud探测spi20设备,并将spi20连接的flash初始化为块设备,名称w25q64
if(rt_sfud_flash_probe("W25Q64", "spi20")==RT_NULL)
{
rt_kprintf("w25q64 prob failed...\n");
return -RT_ERROR;
}
/***************************W25Q64-FAL分区初始化***********************************/
fal_init();//W25Q64分区增加的fal初始化
/******************FAL分区初始化后创建块设备-挂载文件系统***************************/
fal_blk_device_create("filesystem");//创建块设备(挂载文件系统必备)
//格式化设备
dfs_mkfs("elm","filesystem");
// 直接挂载到根目录 /,而非 /spi
if (dfs_mount("filesystem", "/", "elm", 0, 0) == 0)
{
rt_kprintf("W25Q64 filesystem mount to / successful!\n");
}
else {
rt_kprintf("W25Q64 filesystem mount retry\n");
//格式化设备
dfs_mkfs("elm","filesystem");
if (dfs_mount("filesystem", "/", "elm", 0, 0) == 0)
rt_kprintf("W25Q64 filesystem mount to / successful!\n");
else
rt_kprintf("W25Q64 filesystem mount to / failed!\n");
}
/******************FAL分区初始化后查找分区,进行擦除读写测试--调用的FAL接口函数***********************************/
//增加分区后的读写操作
// 查找param分区-进行数据读写测试
const struct fal_partition *part = fal_partition_find("param");
if (part)
{
fal_partition_erase(part, 0 , 4096);
// 直接读写,无需创建块设备
if( fal_partition_write(part, 0 , test_data, sizeof(test_data))<0)
{
rt_kprintf("write error\n");
}
else {
rt_kprintf("write success\n");
}
if(fal_partition_read(part, 0 , read_data, sizeof(test_data))<0)
{
rt_kprintf("read error\n");
}
else {
rt_kprintf("read success \n");
for(int i=0;i<sizeof(test_data);i++)
rt_kprintf("%#x ",read_data[i]);
}
}
/*****************************FAL分区后使用easyflash软件包根据键值对进行数据写入和读取********************************/
if(easyflash_init()==EF_NO_ERR)
{
easyflash_test_env();//测试函数
}
return RT_EOK;
}
MSH_CMD_EXPORT(w25q_sfud_test,sfub test);
输入w25q_sfud_test调用函数:


五、agile_ftp软件包将开发板日志文件导出到电脑
021-03-spak-device-spi-w25q64-addFAL-filesystem-logfile-easyflashkey2-ftp
(一)rw007-wifi软件包(板子上自带的wifi模块)

(二)agile_ftp软件包

1、使能agile_ftp软件包

2、增加对应头文件+调用初始化函数
#include "ftp.h"
ftp_init(2048, 27, 100);
3、代码中-默认端口:21

4、登录用户名和密码默认都是"loogg"

(三)方法一:电脑使用WIN+E进入 WINDOWS 资源管理器地址栏输入IP地址获取开发板日志文件
1、输入对应的IP地址回车
输入:ftp://192.168.52.148回车
IP地址是开发板实际ip地址(WIFI连接手机开的热点后会自动分配一个IP),电脑连的手机热点,和开发板IP是同一个网段。

2、回车之后即可看到开发板中的目录文件,右击登录输入账号和密码,账号和密码都是"loogg"


3、找到需要查看的日志文件将其选中复制到电脑上即可

(四)方法二:使用FileZilla软件进行操作,将所需日志文件下载到电脑
1、打开软件输入账号和密码
打开软件输入主机服务器地址(开发板的IP地址),输入账号(默认"loogg")和密码默认"loogg"以及端口(默认21),点击连接即可连接成功,即可通过鼠标进行控制

2、将设备中的文件导出到电脑,在远程端选择要导出的文件右击"下载即可"

3、将电脑上的文件上传到设备目录,选中文件右击上传即可将其传送到设备目录中

在下方可以看到传输的进度,传输进度为100%即为传输完成

4、传输完毕之后在设备端可以看到该文件(传输完毕未显示可以右击刷新)

在开发板串口调试中也可以输入"ls"指令查看当前目录下是否有该文件

六、使用netutils软件包操作导入导出文件

该软件包可以实现功能如下:
1、将文件从开发板中导出到电脑
2、将电脑文件导入到开发板
3、需要Tftpd64软件协助进行(设置好参数后即可使用);串口貌似不支持linux一样输入指令主动推送文件(尝试多次未成功),总体而言不如"agile_ftp软件包+FileZilla软件"操作方便。

netutils软件包其他功能参考下面文章第二章部分
RT-Thread studio STM32F407+RW007模块/物联网软件包/阿里云





