前文回顾
《物联网系统RT-Thread学习—开发环境搭建》
《物联网系统RT-Thread学习—内核学习》
《物联网系统RT-Thread学习—设备和驱动学习》
物联网系统RT-Thread学习—组件使用(一)
本集预告
学习RTT到现在,缺少一些实际用例,最近购买了flash芯片,来学习一下组件中的文件系统,为了学习,得下本钱啊。
开发过程
硬件连接
用到是标准SPI接口,所以一共SPI线4根,与开发板的SPI1接口相连
STM32F103C8T6的SPI1如下
flash芯片是W25Q64,8Mflash,接线如下
这4根线,很简单。
CS 《----》NSS
CLK《----》SCK
DI 《----》MOSI
DO 《----》MISO
电源2根,别接反了就行。
工程配置
开启SPI总线。
这里我们使用了SFUD,这个能够自动探测flash的参数。
然后配置开发板的SPI部分宏定义开启,这与前面的总线启用是配合关系,负责具体的接口选择
如果这里没开启,那就需要手动增加一些宏定义,但是每次配置就会清空,所以最好的办法,就是在这里开启。前面一章开启IIC的时候,这里就没有配置,还是太年轻啊。
开启虚拟文件系统及对elm格式支持
这里选择的是elm格式,系统能够支持如下几种格式,需要分别开启。根据需求选择,而我不一样,我只是学习一下没有见过的elm文件系统。
这个扇区大小就需要根据flash芯片来决定了
本章重点来咯!!!
W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
我这个STM32103F20K的内存,用起来也很吃力,后面就马上遇到问题了。
代码实现
SPI设备注册
static int rt_hw_spi_flash_init(void)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", "spi10", GPIOA, GPIO_PIN_4);
rt_thread_delay(50);
if (RT_NULL == rt_sfud_flash_probe("W25Q64", "spi10"))
{
return -RT_ERROR;
}
return RT_EOK;
}
/* 导出到自动初始化 */
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);
其中的PA4这个接口是片选引脚
这里就是告诉系统,我们有一个SPI设备接入了,片选IO为PA4。并且挂载为块设备。
块设备注册
然后需要将刚才的SPI设备,挂载到根目录
static int mnt_init(void)
{
//dfs_mkfs("elm","W25Q64");//挂在前需格式化
if(dfs_mount("W25Q64","/","elm",0,0)==0) //挂载文件系统,参数:块设备名称、挂载目录、文件系统类型、读写标志、私有数据0
{
rt_kprintf("挂载成功\n");
}
else
{
rt_kprintf("挂载失败\n");
}
return RT_EOK;
}
INIT_ENV_EXPORT(mnt_init);
如果需要格式化,前面调用
dfs_mkfs("elm","W25Q64");//挂在前需格式化
这个最好不要每次都调用,否则你还存啥啊
测试主函数
int main(void)
{
int file;
int size;
struct statfs elm_stat;
char str[] = "hello world";
char buf[80];
if(statfs("/", &elm_stat) == 0)
{
rt_kprintf("文件系统 block size: %d, total blocks: %d, free blocks: %d.\n", elm_stat.f_bsize, elm_stat.f_blocks, elm_stat.f_bfree);
}
if(mkdir("/testdir", 0x777) == 0)
{
rt_kprintf("创建目录'/testdir'成功\n");
}
else
{
rt_kprintf("创建目录'/testdir'失败\n");
}
/* 以创建和读写模式打开文件,如果该文件不存在则创建该文件*/
file = open("/testdir/test.txt", O_WRONLY | O_CREAT);
if (file >= 0)
{
rt_thread_mdelay(1000);
if(write(file, str, sizeof(str)) == sizeof(str))
{
rt_kprintf("写文件成功\n");
}
else
{
rt_kprintf("写文件失败\n");
}
close(file);
}
else
{
rt_kprintf("写文件打开失败\n");
}
/* 以只读模式打开文件 */
file = open("/testdir/test.txt", O_RDONLY);
if (file >= 0)
{
size = read(file, buf, sizeof(buf));
close(file);
if(size == sizeof(str))
{
rt_kprintf("读取内容成功(size: %d): %s \n", size, buf);
}
else
{
rt_kprintf("读取内容失败(size: %d): %s \n", size, buf);
}
}
else
{
rt_kprintf("读文件打开失败\n");
}
return RT_EOK;
}
功能就是挂载目录,测试创建目录,文件的写入和读出。
,基操勿六,基操勿六。
遇到问题
无法打开文件
调试结果发现
能创建路径,但是无法创建文件
通过调试发现,在函数int dfs_elm_open(struct dfs_fd *file)
中,分配内存出现了失败
这里的结构大小是4000多,这就和前面我们所说的
呼应起来了。
所以这里来看,使用这个文件系统,对内存要求还是挺大,反观创建路径的内存需求为500多个字节,这里还是能够满足的。
所以这里我们需要打开一个宏定义
一旦开启这个,FIL这个结构,就会缩小FF_MAX_SS个字节,即减少一个扇区的大小,4096字节,那么再分配内存,就不会失败了。
修改之后再次测试
OK。
注册函数
RTT提供了一些接口,用来在系统启动时候,注册一些设备,并且根据先后顺序,提供了不同的接口。
/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
我们前面用了
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
分别用来初始化设备和执行挂载操作。并且保证了注册设备在前,挂载在后。
结束语
又到了一年一度的扫福活动了,每次到这时候,才意识到快过年了。今天看到这样一篇文章
《对话“流调中最辛苦的中国人”:来北京找儿子,凌晨打零工补贴家用》
我也不觉得自己可怜。我只是好好干活,我不偷不抢,靠自己的力气,靠自己的双手,挣点钱,挣了钱找孩子。就是为了生活,为了照顾这个家。
我找孩子,到现在花了好几万。打工都是打零工,赚了钱就找孩子,没钱了就打工。我努力,就是为了把孩子找回来。我辛苦一点,就算把命搭到里面,也要把孩子找回来。
致敬一位伟大的父亲。