Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
STM32F407
libcsv
csv
rtthread操作系统libcsv库的使用
发布于 2021-05-17 20:07:25 浏览:1744
订阅该版
[tocm] # 前言 最近做一个 STM32F4 的项目,需要做本地数据持久化。一开始的策略是,使用数据产生的时间戳作为文件名,保存为json格式的数据文件来进行存储,使用cJSON库进行数据解析,每个文件大小大概为100字节左右。该方法在数据量小的时候很方便,一次展示30条历史数据,从文件系统读取时几乎感觉不到卡顿。但是当数据量大到将近2000个文件时,一次读取30条数据耗时超过30秒,因此该方法不可采用。 通过debug发现,读取30条数据过程中,读取文件内容耗时可以忽略不记,主要是文件打开耗时严重。因此改变策略,将所有数据存储到一个大文件中。 使用 csv格式 代替 json格式 来进行数据存储,使用libcsv库进行数据解析,json只用来做配置文件。 # 开发环境 IDE: rt-thread studio v2.0.1 主芯片:STM32F407VG # 软件包配置 完成基础工程配置后,在软件包中找到 **libcsv** 库,勾选后保存生成代码。(必须先完成文件系统配置,并挂载完成,该步骤不做赘述) ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/c917210ae8ed0d8ef8997a40ce3c2e2dc77d8160.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2d5endfbXg=,size_16,color_FFFFFF,t_70) 打开工程目录 packages/libcsv-v3.0.3/ 下的 libcsv.h ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/b7bb17c686d8bd28b91d86f4be4bb026afc2dced.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2d5endfbXg=,size_16,color_FFFFFF,t_70) 一般情况下,我们只需要用到红框框出的4个函数。 ## csv_init 顾名思义,对 csv_parser 结构体进行初始化。 参数 p,好理解,接收需要初始化的 csv_parser 结构体地址 参数 options 共有如下几种定义: ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/c3b3b5d03dd149aefb7ecedb412c60e0b2c92c13.png) 谷歌翻译一下: ``` #define CSV_STRICT 1 / *启用严格模式* / #define CSV_REPALL_NL 2 / *报告所有未引用的回车和换行* / #define CSV_STRICT_FINI 4 / *如果引用了最后一个字段且不包含结尾的引号,则使csv_fini返回CSV_EPARSE * / #define CSV_APPEND_NULL 8 / *确保所有字段均以空值结尾* / #define CSV_EMPTY_IS_NULL 16 / *当遇到空的,未引用的字段时,将空指针传递给cb1函数* / ``` 返回值: 如果初始化成功,返回 0,初始化失败返回 -1. ``` int csv_init(struct csv_parser *p, unsigned char options) { /* Initialize a csv_parser object returns 0 on success, -1 on error */ if (p == NULL) return -1; p->entry_buf = NULL; p->pstate = ROW_NOT_BEGUN; p->quoted = 0; p->spaces = 0; p->entry_pos = 0; p->entry_size = 0; p->status = 0; p->options = options; p->quote_char = CSV_QUOTE; p->delim_char = CSV_COMMA; p->is_space = NULL; p->is_term = NULL; p->blk_size = MEM_BLK_SIZE; p->malloc_func = NULL; p->realloc_func = realloc; p->free_func = free; return 0; } ``` 查看源码,只要 p 不为空值都可以初始化成功。 ## csv_fini 查看函数注释:完成解析。 例如,当文件不以换行符结尾时需要。 该函数通常在整个文件读取完成后调用一次,防止csv文件最后一行数据末尾未加换行符。 参数 p,csv_parser 结构体地址,不做解释。 后面三个参数,cb1, cb2, data 在后面 csv_parse中含义一致,后面一并解释。 ## csv_free 用于在解析完成后,释放 csv_parser 开辟的堆区空间。 ## csv_parse 解析csv格式的数据。 参数 p , csv_parser 结构体地址 参数 s , csv格式数据地址 参数 len , 本次解析传入数据长度 参数 cb1, 读取到数据列分隔符后调用的回调函数(分隔符默认为英文半角逗号 ‘ **,** ’) 参数 cb2, 读取到数据行分隔符后调用的回调函数(分隔符默认为换行符 ‘ **\n** ’) 参数 data, 传入回调函数 cb1 和 cb2 的最后一个参数 #### 回调函数 cb1 的参数解释: 第一个 (void *) 指该列数据的地址 第二个 size_t 指该列数据的长度 第三个 (void *) 指 csv_parse 最后一个参数 data #### 回调函数 cb2 的参数解释: 第一个参数 int 指当前正在处理的字符 第二个参数 (void *) 指 csv_parse 最后一个参数 data # 文件读取解析流程示例 ``` uint8_t csv_parse_file(const char *file_path, void (*cols_callback)(void *, size_t, void *), void (*rows_callback)(int c, void *), void *data) { struct csv_parser parser; FILE *fp; size_t bytes_read; char buf[128]; size_t retval; size_t pos = 0; uint8_t result = RT_ERROR; if (csv_init(&parser, CSV_STRICT | CSV_STRICT_FINI) != 0) { rt_kprintf("failed to initialize csv parser\n"); return result; } fp = fopen(file_path, "rb+"); if (fp == NULL) { rt_kprintf("Failed to open file %s\n", file_path); csv_free(&parser); return result; } while ((bytes_read = fread(buf, 1, 128, fp)) > 0) { if ((retval = csv_parse(&parser, buf, bytes_read, cols_callback, rows_callback, data)) != bytes_read) { goto end; } pos += 128; } if (csv_fini(&parser, cols_callback, rows_callback, data) != 0) { goto end; } result = RT_EOK; end: fclose(fp); csv_free(&parser); return result; } // 假设文件中存储的是传感器数据,数据产生的时间,传感器数据序号 // 我们定义一个结构体,用来存储读取到的数据 struct SensorData { int id; int temperature; int humidity; char date[20]; } // 因为回调函数只能传入一个参数,所以另外定义一个数据结构,将上述结构体封装在内,方便我们处理数据 struct DataHandle { int col_count; int row_count; SensorData *data; } // 接下来定义列数据处理函数 void csv_cols_callback(void *s, size_t len, void *data) { struct DataHandle *handle = (struct DataHandle *)data; // 比如我们要取第5行的数据 (第一行时,row_count == 0) if (handle->row_count == 5 - 1) { switch (handle ->col_count) { case 0: sscanf(s, "%d", &(handle ->data->id)); break; case 1: sscanf(s, "%d", &(handle ->data->temperature)); break; case 2: sscanf(s, "%d", &(handle ->data->humidity)); break; case 3: strncpy(handle ->data->date, s, 19); break; } } // 每进入该函数一次,列计数 + 1 ++handle->col_count; } // 定义行数据处理函数 void csv_rows_callback(int c, void *data) { struct DataHandle *handle = (struct DataHandle *)data; // 每进入该函数一次,行计数 + 1,列计数 清零 ++handle->row_count; handle->col_count = 0; } // 假设我们需要读取的数据存储在 /sensor1.csv // 定义一个数据结构来存储处理后的结果 struct SensorData data; // 定义一个数据结构方便我们完成数据处理 struct DataHandle handle = {0}; handle.data = &data; // 开始解析 csv_parse_file("/sensor1.csv", csv_cols_callback, csv_rows_callback, &handle); ``` # 文件写入 ``` // 我们定义一个函数将传感器数据转换为csv格式的一行数据字符串 size_t csv_parse_sensordata2string(struct SensorData *data, void *str_buf) { return sprintf(str_buf, "%d,%d,%d,%s\n", data->id, data->temperature, data->humidity, data->date); } // 定义一个文件写入函数 size_t csv_write_file(FILE *fp, void *data, size_t size) { size_t count = 0; char *data_p = data; if (fp == NULL) { return count; } for (size_t i = 0; i < size; i++) { if (fputc(data_p[i], fp) == EOF) { return count; } ++count; } return count; } // 将上面两个函数封装成一个追加数据的函数 uint8_t csv_write_sensordata2file(const char *file_path, struct SensorData *data) { char buf[128]; FILE *fp; size_t len, res = 0; len = csv_parse_sensordata2string(data, buf); if (len == 0) { return RT_ERROR; } fp = fopen(file_path, "ab"); do { res = csv_write_file(fp, buf, len); len -= res; } while (len != 0); fclose(fp); return RT_EOK; } ``` # 后记 使用单一csv大文件存储数据,代替大量json小文件存储数据后,效率有非常明显的提升,测试 一万行数据,读取最后30条数据,解析时间大概为 5秒钟左右。
7
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
gyzw
这家伙很懒,什么也没写!
文章
3
回答
0
被采纳
0
关注TA
发私信
相关文章
1
STM32F407 enable ethernet driver
2
F407使用QEMU调试网络功能报错,附Cm_backtrace信息
3
tm32f407-atk-explorer这个BSP中,1M外置的SRAM
4
串口怎样接收int类型数据?
5
F407无法使用RTC,手册上的RTC实验例程跑不通。请各位不吝赐教!
6
CAN不能推出休眠模式
7
ENV配置Lwip后不能将包跟新到本地?
8
程序跳转出问题,怀疑rtt有指定地址的函数
9
rtthread3.1.2中使用env建立的工程需还需要修改什么东西吗
10
在STM32F407跟着教程制作新bsp后 ,程序运行卡死
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部