软件环境:Win7,Keil MDK 4.72a,IAR EWARM 7.2,GCC 4.2,Python 2.7,SCons 2.3.2
硬件环境:Armfly STM32F103ZE-EK v3.0开发板
参考文章:RT-Thread编程指南
[RTthread]新版本RTT中的SPI驱动框架
Github托管的Realtouch分支中examples目录中spi flash的例程
上篇文章中介绍了通过添加SPI Flash驱动,能够成功识别出SPI Flash芯片型号,但还是不能挂载文件系统,无法对文件系统进行相关操作,因此在这篇文章中要研究这问题。
【1】熟悉RT-Thread的内置命令(系统默认是C-Express Style)
RT-Thread 在默认情况下是开启了Shell终端功能的,而且使用的是USART1串口,这一切是我们所需要的而且在当前的这款开发板上不用改动,按默认即可。开发板上已经烧录进了上次编译的程序,这次只需该开发板上电或者复位即可。在串口的Shell终端,我们熟悉下RT-Thread的内置命令,然后我可以按照相同的方法添加我们自定义的命令。
(1)list():显示当前系统中存在的命令及变量
在Finsh提示符下输入list(),结果显示如下:
finsh>>list() ? ? ? ? ? ? ??
--Function List:
led ? ? ? ? ? ? ?-- set led[0 - 1] on[1] or off[0].
list_mem ? ? ? ? -- list memory usage information
mkfs ? ? ? ? ? ? -- make a file system
df ? ? ? ? ? ? ? -- get disk free
ls ? ? ? ? ? ? ? -- list directory contents
rm ? ? ? ? ? ? ? -- remove files or directories
cat ? ? ? ? ? ? ?-- print file
copy ? ? ? ? ? ? -- copy file or dir
mkdir ? ? ? ? ? ?-- create a directory
hello ? ? ? ? ? ?-- say hello world
version ? ? ? ? ?-- show RT-Thread version information
list_thread ? ? ?-- list thread
list_sem ? ? ? ? -- list semaphone in system
list_event ? ? ? -- list event in system
list_mutex ? ? ? -- list mutex in system
list_mailbox ? ? -- list mail box in system
list_msgqueue ? ?-- list message queue in system
list_mempool ? ? -- list memory pool in system
list_timer ? ? ? -- list timer in system
list_device ? ? ?-- list device in system
list ? ? ? ? ? ? -- list all symbol in system
--Variable List:
dummy ? ? ? ? ? ?-- dummy variable for finsh
? ? ? ? 0,0x00000000
finsh>>
这些命令以list开头的在RT-Thread编程指南的finsh shell部分有说明,这里不再赘述。
下面看下led.c中有关led命令的代码:
#ifdef RT_USING_FINSH
#include <finsh.h>
static rt_uint8_t led_inited = 0;
void led(rt_uint32_t led,rt_uint32_t value)
{
? ? /* init led configuration if it's not inited. */
? ? if (!led_inited)
? ? {
? ? ? ? rt_hw_led_init();
? ? ? ? led_inited = 1;
? ? }
? ? if ( led == 0 )
? ? {
? ? ? ? /* set led status */
? ? ? ? switch (value)
? ? ? ? {
? ? ? ? case 0:
? ? ? ? ? ? rt_hw_led_off(0);
? ? ? ? ? ? break;
? ? ? ? case 1:
? ? ? ? ? ? rt_hw_led_on(0);
? ? ? ? ? ? break;
? ? ? ? default:
? ? ? ? ? ? break;
? ? ? ? }
? ? }
? ? if ( led == 1 )
? ? {
? ? ? ? /* set led status */
? ? ? ? switch (value)
? ? ? ? {
? ? ? ? case 0:
? ? ? ? ? ? rt_hw_led_off(1);
? ? ? ? ? ? break;
? ? ? ? case 1:
? ? ? ? ? ? rt_hw_led_on(1);
? ? ? ? ? ? break;
? ? ? ? default:
? ? ? ? ? ? break;
? ? ? ? }
? ? }
}
FINSH_FUNCTION_EXPORT(led,set led[0 - 1] on[1] or off[0].)
#endif
上面开关led的代码很简单,尽管实际操作中写入的值和命令描述的刚好相反,那是因为硬件驱动逻辑问题,不是我想研究问题的关键,关键是上面蓝色粗体部分,是一个宏定义,在finsh.h文件的324行给出的定义如下:
#define FINSH_FUNCTION_EXPORT(name,desc) ?
? ? FINSH_FUNCTION_EXPORT_CMD(name,name,desc)
一看又是一个宏定义,继续往下看,定位到finsh.h文件的231行,代码如下:
#define FINSH_FUNCTION_EXPORT_CMD(name,cmd,desc) ? ? ?
? ? ? ? ? ? ? ? const char __fsym_##cmd##_name[] = #cmd; ? ? ? ? ? ?
? ? ? ? ? ? ? ? const char __fsym_##cmd##_desc[] = #desc;? ? ? ? ?
? ? ? ? ? ? ? ? const struct finsh_syscall __fsym_##cmdSECTION("FSymTab")=
? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? __fsym_##cmd##_name,? ?
? ? ? ? ? ? ? ? ? ? __fsym_##cmd##_desc,? ?
? ? ? ? ? ? ? ? ? ? (syscall_func)&name ? ?
? ? ? ? ? ? ? ? };
在finsh.h文件的116行,我们可以看到上面有关finsh_syscall和syscall_func的定义:
typedef long (*syscall_func)();
/* system call table */
struct finsh_syscall
{
const char* name; /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
const char* desc; /* description of system call */
#endif
syscall_func func;/* the function address of system call */
};
syscall_func是一个函数指针,返回值是long型,是finsh_syscall类中的一个成员,这样一来有关finsh的命令实现就很清楚了,当FINSH_FUNCTION_EXPOR宏被调用时,led函数的入口地址被传给finsh_syscall类的实例化对象__fsym_##cmd,并且被传进来的参数初始化。
具体点讲,led()被传递给了finsh_syscall类对象的一个成员syscall类型的func,并以?(syscall_func)&name的身份来初始化,显然name要被强制转换成syscall_func类型,也就是说led传递给func,返回值类型为long型。finsh_syscall类对象__fsym_##cmd通过SECTION宏指定在一个特定代码段FSymTab,当线程执行着个代码时,这个命令的功能函数也就被调用了。至于cmd,desc参数通过##连词符重新装配和替换则显得相对简单。
【2】用FINSH_FUNCTION_EXPOR宏导入的命令(Finsh终端的命令操作函数)在finsh_thread线程中调用的实现
参考文章:RT-Thread下finsh原理浅析(出处: amoBBS 阿莫电子论坛)
我们全局搜索下finsh_thread,可以定位到shell.c文件的291行void finsh_thread_entry(void* parameter),这个线程shell的命令接口线程,对终端命令的响应就是从这里发出的,如下
... ...
#ifdef RT_USING_CONSOLE
? ? ? ? shell->device = rt_console_get_device();
? ? ? ? RT_ASSERT(shell->device);
? ? ? ? rt_device_open(shell->device,RT_DEVICE_OFLAG_RDWR);
? ? ? ? rt_device_set_rx_indicate(shell->device,finsh_rx_ind);
#else
? ? ? ? RT_ASSERT(shell->device);
#endif
? ? }
? ? while (1)
? ? {
... ...
/* wait receive */
? ? ? ? if (rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER) != RT_EOK) continue;
? ? ? ? /* read one character from device */
? ? ? ? while (rt_device_read(shell->device,&ch,1) == 1)
? ? ? ? {
... ...
如果串口上没有收到任何数据,并且串口缓冲区中也无数据,即shell->rx_sem信号量的值为0,那么rt_sem_take这个信号量函数会使finsh_thread线程休眠,RTT内核会执行其他线程。当串口收到数据,系统调度程序会调用finsh_thread线程在rt_device_set_rx_indicate函数中设置的回调函数finsh_rx_ind,该函数调用rt_sem_release(&shell->rx_sem)来释放信号量,这会唤醒finsh_thread线程,该线程主动调用rt_device_read进行接收数据。在用户命令输入并回车确认后,会执行下面代码中finsh_run_line()函数进行语法解析。
... ...
#ifndef FINSH_USING_MSH_ONLY ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? /* add ';' and run the command line */
? ? ? ? ? ? ? ? ? ? shell->line[shell->line_position] = ';';
? ? ? ? ? ? ? ? ? ? if (shell->line_position != 0) finsh_run_line(&shell->parser,shell->line);
? ? ? ? ? ? ? ? ? ? else rt_kprintf("n");
? ? ? ? ? ? ? ? #endif ??
... ...
然后finsh_run_line()调用?finsh_parser_run(),代码如下:
#ifndef FINSH_USING_MSH_ONLY
void finsh_run_line(struct finsh_parser* parser,const char *line)
{
? ? const char* err_str;
? ? rt_kprintf("n");
? ? finsh_parser_run(parser,(unsigned char*)line);
... ...
上面代码是对输入的命令进行语法解析,先对传入的字符串进行初步语法判断:
/*
expr_postfix -> expr_primary
| expr_postfix INC
| expr_postfix DEC
| expr_postfix '(' param_list ')'
*/
static struct finsh_node* proc_postfix_expr(struct finsh_parser* self)
{
enum finsh_token_type token;
struct finsh_node* postfix;
postfix =proc_primary_expr(self);
如上面代码所示,然后proc_primary_expr(self)调用finsh_node_new_id(id),finsh_node_new_id()代码如下:
... ...
/* then lookup system variable */
symbol = (void*)finsh_sysvar_lookup(id);
if (symbol == NULL)
{
/* then lookup system call */
symbol = (void*)finsh_syscall_lookup(id);
if (symbol != NULL) type = FINSH_IDTYPE_SYSCALL;
}
finsh_syscall_lookup()用来查找系统调用,代码如下:
struct finsh_syscall* finsh_syscall_lookup(const char* name)
{
struct finsh_syscall* index;
struct finsh_syscall_item* item;
for (index =_syscall_table_begin; index <_syscall_table_end; FINSH_NEXT_SYSCALL(index))
if (strcmp(index->name,name) == 0)
return index;
}
我们再看下_syscall_table_begin?和_syscall_table_end 的定义,代码如下:
#ifdef FINSH_USING_SYMTAB
struct finsh_syscall *_syscall_table_begin = NULL;
struct finsh_syscall *_syscall_table_end = NULL;
struct finsh_sysvar *_sysvar_table_begin = NULL;
struct finsh_sysvar *_sysvar_table_end = NULL;
#else
struct finsh_syscall _syscall_table[] =
{
{"hello",hello},{"version",version},{"list",list},{"list_thread",list_thread},#ifdef RT_USING_SEMAPHORE
{"list_sem",list_sem},#endif
#ifdef RT_USING_MUTEX
{"list_mutex",list_mutex},#endif
#ifdef RT_USING_FEVENT
{"list_fevent",list_fevent},#endif
#ifdef RT_USING_EVENT
{"list_event",list_event},#endif
#ifdef RT_USING_MAILBOX
{"list_mb",list_mailbox},#endif
#ifdef RT_USING_MESSAGEQUEUE
{"list_mq",list_msgqueue},#endif
#ifdef RT_USING_MEMPOOL
{"list_memp",list_mempool},#endif
{"list_timer",list_timer},};
struct finsh_syscall *_syscall_table_begin = &_syscall_table[0];
struct finsh_syscall *_syscall_table_end = &_syscall_table[sizeof(_syscall_table) / sizeof(struct finsh_syscall)];
_syscall_table_begin?
和
_syscall_table_end 的类型为finsh_syscall,这个类型前面有介绍过,就是FINSH_FUNCTION_EXPOR宏中用到的那个类型。
也就是用户定义的功能函数,如系统自带的led()函数调用
FINSH_FUNCTION_EXPOR宏导入
。在这里当finsh_thread线程被唤醒后又通过前面对setction(x)汇编指令指定的代码段去查询,如果用户定义的功能函数定义其中自然是能够解析通过,比如finsh_node_new_id(id)中的id就是功能函数led。如果再解析输入的命令语法再没有问题,下面运行编译函数讲语法树上的节点全部编译,之后再虚拟机finsh_vm_run()调用该命令。
? ? /* compile node root */
? ? if (finsh_errno() == 0)
? ? {
? ? ? ? finsh_compiler_run(parser->root);
? ? }
? ? else
? ? {
? ? ? ? err_str = finsh_error_string(finsh_errno());
? ? ? ? rt_kprintf("%sn",err_str);
? ? }
? ? /* run virtual machine */
? ? if (finsh_errno() == 0)
? ? {
? ? ? ? char ch;
? ? ? ? finsh_vm_run();
有了上面的对led命令的认识,下面我们可以看下我们关心的对spi flash文件系统操作的命令mkfs等相关命令了。
【3】文件系统操作命令
(1) mkdfs 命令,在系统里全局搜索下,就可以在dfs_fs.c文件的492行找到可led命令一样的宏:
#ifdef RT_USING_FINSH
#include <finsh.h>
void mkfs(const char *fs_name,const char *device_name)
{
? ? dfs_mkfs(fs_name,device_name);
}
FINSH_FUNCTION_EXPORT(mkfs,make a file system);
同样地,mkdir,copy,rm等命令都是相同的方法导入finsh_thread线程中的。在终端中输入list_device()命令,显示如下:
finsh />list_device()
device ? ?type ? ? ?
-------- ----------?
flash0 ? Block Device?
spi12 ? ?SPI Device?
spi11 ? ?SPI Device?
spi1 ? ? SPI Bus ?
uart3 ? ?Character Device?
uart2 ? ?Character Device?
uart1 ? ?Character Device?
? ? ? ? 0,0x00000000
finsh />
finsh />mkfs("elm","flash0") ??
? ? ? ? 0,0x00000000
finsh />
显然格式化成功,但为什么挂载不成功能呢?当调试追踪到ff.c文件的1986行时,return 2的条件成立,导致系统无法挂载,于是参考网友关于SPI flash的elm文件系统的帖子后突然想到可能是芯片不支持的问题,于是将开发板上的SST25VF016B跟换成25Q64FV,然后将底层驱动代码改为:
/* JEDEC Manufacturer?ˉs ID */
#define MF_ID (0xEF)
... ...
/*else if(memory_type_capacity == MTC_SST25VF016B)
{
FLASH_TRACE("SST25VF016B detectionrn");
spi_flash_device.geometry.sector_count = 512;
}*/
else
{
FLASH_TRACE("Memory Capacity error!rn");
return -RT_ENOSYS;
}
}
然后重新编译下载,第一启动时仍然挂载失败,在终端执行mkfs("elm","flash0") 显示格式化成功,复位后显示挂载成功,显示如下:
? | /
- RT - ? ? Thread Operating System
?/ | ? ? 1.2.2 build Mar 31 2015
?2006 - 2013 Copyright by rt-thread team
W25Q64BV or W25Q64CV detection
finsh />flash0 mount to /.
再次跟踪到ff.c文件的1986行时,return 2的条件时,不成立了。
查看下flash空间
disk free: 8168 KB [ 2042 block,4096 bytes per block ]
? ? ? ? 0,0x00000000
finsh />
【4】文件系统测试
在rt-thread-1.2.2/examples/test目录下可以找到文件fs_test.c,复制到当前分支的drivers目录下,然后加入到MDK 工程的Drivers组中,修改drivers目录下的Sconscript脚本,结果如下:
# add DFS drvers.
if GetDepend('RT_USING_DFS'):
src += ['rt_spi_device.c','rt_stm32f10x_spi.c','spi_flash_w25qxx.c','fs_test.c']
修改后保存。
打开fs_test.c文件,分别将文件中的64行,66行,79行,96行,122行的8000替换成10,读写次数太多,耗费时间。然后在线程while(1)末尾close(fd)下面添加如下代码:
/* close file */
close(fd);
if(round >10) return;
}
分别将文件中的173行,175行,188行,205行,231行的5000替换成10,也同样在
while(1)末尾close(fd)下面添加上面代码。目的是让这两个线程运行10round后退出。然后保存编译下载,重新复位后在终端中用list()命令查看下:
finsh />list()
--Function List:
led -- set led[0 - 1] on[1] or off[0].
fs_test -- file system R/W test. e.g: fs_test(3)
list_mem -- list memory usage information
mkfs -- make a file system
df -- get disk free
ls -- list directory contents
... ...
可以看到fs_test命令出现在列表中,现在可以调用这个命令来测试下:
finsh />fs_test(3)
arg is : 0x03 ? 0,0x00000000
finsh />thread fsrw1 round 1 rd:40000byte/s,wr:5217byte/s
thread fsrw2 round 1 rd:60000byte/s,wr:180000byte/s
thread fsrw1 round 2 rd:40000byte/s,wr:3428byte/s
thread fsrw2 round 2 rd:60000byte/s,wr:5806byte/s
thread fsrw1 round 3 rd:5454byte/s,wr:3333byte/s
thread fsrw2 round 3 rd:7200byte/s,wr:6000byte/s
thread fsrw1 round 4 rd:4615byte/s,wr:60000byte/s
thread fsrw2 round 4 rd:45000byte/s,wr:45000byte/s
thread fsrw1 round 5 rd:5000byte/s,wr:30000byte/s
thread fsrw2 round 5 rd:6923byte/s,wr:45000byte/s
thread fsrw1 round 6 rd:40000byte/s,wr:40000byte/s
thread fsrw2 round 6 rd:7200byte/s,wr:60000byte/s
thread fsrw1 round 7 rd:40000byte/s,wr:40000byte/s
thread fsrw2 round 7 rd:7200byte/s,wr:60000byte/s
thread fsrw1 round 8 rd:40000byte/s,wr:40000byte/s
thread fsrw2 round 8 rd:7200byte/s,wr:60000byte/s
thread fsrw1 round 9 rd:4800byte/s,wr:30000byte/s
thread fsrw2 round 9 rd:7500byte/s,wr:45000byte/s
thread fsrw1 round 10 rd:4800byte/s,wr:30000byte/s
thread fsrw2 round 10 rd:90000byte/s,wr:60000byte/s
finsh />
可以看到文件读写成功。看下根目录下这两个测试文件:
finsh />ls("/")
Directory /:
TEST1.DAT 1200
TEST2.DAT 1800
0,0x00000000
finsh />
到此,我们已经在开发板上成功开启了基于SPI Flash的ELM FATFS文件系统。