完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战——文件系统。本文将从 SPI Flash 和 SD Card 两方面给大家讲解如何使用文件系统,以及针对本次 DIY 做出的一些优化,会大大增强系统性能,一起来看看吧~
1.第三周任务概览 我们来看一下第三周的任务: 了解 RT-Thread 文件系统,在接收节点中使用文件系统,存放来自发送节点发送过来的数据。 上述任务比较单一,只是文件系统而已。不过,能巧妙灵活的把文件系统用好用对,可不是一件轻松的事情。 2.RT-Thread 文件系统简要介绍 DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格。 RT-Thread DFS 组件的主要功能特点有:
DFS 的更多内容,请在 RT-Thread 文档中心中查看:https://www.rt-thread.org/document/site/ (由于微信无法插入外部链接,请将以上链接复制至外部浏览器打开) 3. 在 SPI Flash 上使用文件系统 3.1 准备工作 以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SPI Flash 上使用文件系统。 值得一提的是,RT-Thread 已经将 libc 那套文件系统接口对接到 DSF 上了,在 env 工具中开启 libc 和 DFS 即可,本次教程使用 libc 的那套接口进行文件的打开/关闭、读取/写入。 在 menuconfig 中开启 libc: 1RT-Thread Components ---> 2 POSIX layer and C standard library ---> 3 在 meunconfig 中开启 DFS,本教程使用 elmfatfs 文件系统,需要将 elmfatfs 挂载到 RT-Thread 的 DFS 上,所以 elmfatfs 也要开启: 1RT-Thread Components ---> 2 Device virtual file system ---> 3 4 当然,不要忘记在 meunconfig 中开启 SPI Flash: 1Hardware Drivers Config ---> 2 Onboard Peripheral Drivers ---> 3 潘多拉开发板上的 SPI Flash 使用的是 QSPI 接口,还需要在 meunconfig 中把 QSPI 接口开启: 1Hardware Drivers Config ---> 2 On-chip Peripheral Drivers ---> 3 退出 menuconfig 后需要输入 “scons --target=mdk5” 更新工程。 3.2 文件系统的挂载 本次 DIY 使用的文件系统是 elmfatfs,elmfatfs 需要在块设备上才能进行文件操作。潘多拉板子上的 SPI Flash 是 W25Q128,我们需要将 W25Q128 注册成块设备,才能使用 elmfatfs 进行文件操作。如下示例代码: 1static int rt_hw_qspi_flash_with_sfud_init(void) 2{ 3 stm32_qspi_bus_attach_device("qspi1", "qspi10", RT_NULL, 4, w25qxx_enter_qspi_mode, RT_NULL); 4 if (RT_NULL == rt_sfud_flash_probe("W25Q128", "qspi10")) 5 return -RT_ERROR; 6 return RT_EOK; 7} 8INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init); 在 FinSH 中输入 “list_decive”,即可看到 W25Q128 注册成了块设备了,并挂载在 QSPI 上: W25Q128 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码: 1dfs_mount("W25Q128", "/", "elm", 0, 0) 3.3 文件操作 到此为止,我们就可以使用 libc 的接口进行文件操作了,将接收到的数据以文件方式存放到 W25Q128 里面去,举个简单的例子,如下示例代码: 1FILE *recvdata_p0; 2recvdata_p0 = fopen("recvdata_p0.csv", "a+"); 3if (recvdata_p0 != RT_NULL) 4{ 5 fputs((char *)RxBuf_P0, recvdata_p0); 6 fputs("n", recvdata_p0); 7 fclose(recvdata_p0); 8} 在 Finsh 中输入 “ls” 可以查看当前文件系统中的文件目录,如下图: 输入 “cat XXX” 可以查看文件内容,如下图: 简单的几步就可以进行文件操作了,RT-Thread 的文件系统还是相当易用的。 4. 在 SD Card 上使用文件系统 4.1 准备工作 以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SD Card 上使用文件系统。 和上面的 SPI Flash 一样,在 menuconfig 中开启相关选项:SD Card,SPI(潘多拉板子的 SD 卡是用 SPI 驱动的而不是 SDIO),libc,DFS,elmfatfs。 4.2 文件系统的挂载 与 SPI Flash 一样,需要将 SD Card 注册成块设备,才能挂载文件系统。如下示例代码: 1static int rt_hw_spi1_tfcard(void) 2{ 3 __HAL_RCC_GPIOC_CLK_ENABLE(); 4 rt_hw_spi_device_attach("spi1", "spi10", GPIOC, GPIO_PIN_3); 5 return msd_init("sd0", "spi10"); 6} 7INIT_DEVICE_EXPORT(rt_hw_spi1_tfcard); 在 FinSH 中输入 “list_decive”,即可看到 SD Card 注册成了块设备了,并挂载在 SPI 上: SD Card 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码: 1dfs_mount("sd0", "/", "elm", 0, 0) 需要注意的是,如果大家手头的板子是使用 SDIO 接口来驱动 SD Card 的,那么将 SD Card 注册成块设备将不用我们操心,RT-Thread 源码中的 “...rt-threadcomponentsdriverssdioblock_dev.c” 文件中,会将 SD Card 注册成块设备的。当然,文件系统的挂载还是需要我们手动敲代码去实现的。 4.3 文件操作 与 SPI Flash 一样,可以直接使用 libc 的接口进行文件操作,如下示例代码: 1FILE *recvdata_p0; 2recvdata_p0 = fopen("recvdata_p0.csv", "a+"); 3if (recvdata_p0 != RT_NULL) 4{ 5 fputs((char *)RxBuf_P0, recvdata_p0); 6 fputs("n", recvdata_p0); 7 fclose(recvdata_p0); 8} 5. 针对本次 DIY 的一些优化 5.1 优化1 将文件系统用起来,进行文件操作,是一件相对比较容易的事情。不过当将文件系统运用到实际项目中的时候,往往会因为一些需求或者说是其他因素,导致事情不那么好办。就拿这个 DIY 来说,如果就像上面的示例代码这么用文件系统,虽然系统能正常工作,但是会带来一些问题: 这里使用 ringbuffer 来避免这个问题。 ringbuffer 是一种先进先出的 FIFO 环形缓冲区,DIY 的接收节点工程中,我们创建了两个线程去工作,一个是 nrf24l01_thread 线程,用于接收来自发送节点的数据,另一个是 DFS_thread 线程,用于利用文件系统保存数据的。并且创建一个 4KB 大小的一个 ringbuffer: 1static struct rt_ringbuffer *recvdatabuf; 2recvdatabuf = rt_ringbuffer_create(4069); /* ringbuffer的大小是4KB */ 每当 nrf24l01_thread 线程接收到一条数据,就存放到 ringbuffer 中去: 1rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data)); 在DFS_thread 线程中,我们设置一个 ringbuffer 的阈值,这里我将阈值设置成了 ringbuffer 大小的一半,当写入的数据达到了 ringbuffer 的阈值之后,就将 ringbuffer 中所有的数据统统写入文件中去: 1/* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */ 1/* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */ 2if (rt_ringbuffer_data_len(recvdatabuf) > (4096 / 2)) 3{ 4 /* 到阈值就直接写数据 */ 5 recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); 6 if (recvdatafile_p0 != RT_NULL) 7 { 8 while(rt_ringbuffer_data_len(recvdatabuf)) 9 { 10 size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, (4096 / 2)); 11 fwrite(writebuffer, 1, size, recvdatafile_p0); 12 } 13 fclose(recvdatafile_p0); 14 } 15} 这么做,就可以尽可能的减少了文件的操作,提高了系统的性能,同时又保证每一条数据都不会丢失。 5.2 优化2 但是,还有一个问题: 当然,掉电丢数据这种情况是不可以避免的,但是我们可以通过一些算法优化(姑且叫它算法吧),尽可能的减少丢失数据的可能。 解决思路是:定个固定时间,计时,如果时间一到,此时数据还没写满 ringbuffer 的阈值,这时候就不管数据到没到阈值了,直接将 ringbuffer 里的数据全部写入文件中去。要实现这个思路需要搭配事件集 (event) 使用。 在 nrf24l01_thread 线程中,每收到一个数据,就发送一个事件: 1while (1) 2{ 3 if (!rx_pipe_num_choose()) 4 { 5 /* 通过sscnaf解析收到的数据 */ 6 if(sscanf((char *)RxBuf_P0, "%d,+%f", &buf.timestamp, &buf.temperature) != 2) 7 { 8 /* 通过sscnaf解析收到的数据 */ 9 if(sscanf((char *)RxBuf_P0, "%d,-%f", &buf.timestamp, &buf.temperature) != 2) 10 { 11 continue; 12 } 13 buf.temperature = -buf.temperature; 14 } 15 sprintf(str_data, "%d,%fn", buf.timestamp, buf.temperature); 16 /* 将数据存放到ringbuffer里 */ 17 rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data)); 18 /* 收到数据,并将数据存放到ringbuffer里后,才发送事件 */ 19 rt_event_send(recvdata_event, WRITE_EVENT); 20 } 21 rt_thread_mdelay(30); 22} 在DFS_thread 线程中,通过接收两次事件,并设置接收事件的超时时间,达到计时的目的: 1while (1) 2{ 3 /* 接收感兴趣的事件WRITE_EVENT,以永久等待方式去接收 */ 4 if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &set) != RT_EOK) 5 continue; 6 do 7 { 8 /* 接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */ 9 if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(1000), &set) == RT_EOK) 10 { 11 /* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */ 12 if (rt_ringbuffer_data_len(recvdatabuf) > THRESHOLD) 13 { 14 /* 到阈值就直接写数据 */ 15 recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); 16 if (recvdatafile_p0 != RT_NULL) 17 { 18 while(rt_ringbuffer_data_len(recvdatabuf)) 19 { 20 size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD); 21 fwrite(writebuffer, 1, size, recvdatafile_p0); 22 } 23 fclose(recvdatafile_p0); 24 } 25 } 26 /* 阈值没到就继续接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */ 27 continue; 28 } 29 /* 1000ms到了,还没有收到感兴趣的事件,这时候不管到没到阈值,直接写 */ 30 recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); 31 if (recvdatafile_p0 != RT_NULL) 32 { 33 while(rt_ringbuffer_data_len(recvdatabuf)) 34 { 35 size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD); 36 fwrite(writebuffer, 1, size, recvdatafile_p0); 37 } 38 fclose(recvdatafile_p0); 39 } 40 } while(0); 41} 这样就尽最大力度的解决了掉电丢失数据的可能了。当然,第二次接收事件的超时时间可以根据自己需求设定长短。 6. 开源代码 为了更进一步便于大家学习,第三周任务的代码已经开源啦~ 项目地址:https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread(由于微信无法插入外部链接,请将以上链接复制至外部浏览器打开) 7. 注意事项 SD Card 的 sector 大小为 512 字节,需要在 menuconfig 中修改: |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1907 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1678 浏览 1 评论
1171 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
770 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1730 浏览 2 评论
1970浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
806浏览 4评论
stm32f4下spi+dma读取数据不对是什么原因导致的?
254浏览 3评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
623浏览 3评论
634浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-1-23 08:03 , Processed in 0.648185 second(s), Total 44, Slave 39 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号