加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

RT-Thread 学习笔记(七)---开启基于SPI Flash的elmfat文件系统

发布时间:2020-12-15 17:23:53 所属栏目:百科 来源:网络整理
导读:软件环境: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的例程

软件环境: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的例程

【1】RT-Thread 1.2.x中组件初始化代码分析

上篇文章中介绍了spi flash的相关驱动文件添加和相关代码修改,编译虽然能通过但并不代表能够调试通过。因为针对rt-thread-1.2.x版本采用了组件初始化功能,具体是components.c中实现的,请看下面代码:

/**
?* RT-Thread Components Initialization
?*/
void rt_components_init(void)
{
#ifndef _MSC_VER
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;

rt_kprintf("do components intialization.n");
for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
{
rt_kprintf("initialize %s",desc->fn_name);
result = desc->fn();
rt_kprintf(":%d donen",result);
}
#else
? ? const init_fn_t *fn_ptr;
? ? for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
? ? {
? ? ? ? (*fn_ptr)();
? ? }

#endif

上面代码蓝色粗体是组件初始化的入口,是一个函数指针。init_fn_t 的定义在rtdef.h中,如下所示:

/* initialization export */
#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);
... ...
... ...
? ? #define INIT_EXPORT(fn,level) ?
? ? ? ? const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

... ...
... ...
... ...
#endif

其中typdef int (*init_fn_t)(void)的意思是定义init_fn_t为指向函数的指针类型,该函数返回int类型值。这样一来,我们对(*init_fn_t)()的意思就清楚了。

INIT_EXPOT(fn,level) 的表达式是const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn,其中##连词符,SECTION的定义为:

#define SECTION(x) ? __attribute__((section(x)))

RealView 编译工具 编译器参考指南中给出了下面的解释:

__attribute__((section("name")))

通常,ARM 编译器将它生成的对象放在节中,如?data?和?bss。但是,您可能需要使用其他数据节,或者希望变量出现在特殊节中,例如,便于映射到特殊硬件。section?属性指定变量必须放在特定数据节中。如果使用?section?属性,则将只读变量放在 RO 数据节中,而将读写变量放在 RW 数据节中,除非您使用?zero_init?属性。在这种情况下,变量被放在 ZI 节中。

到此,意思已经很明了了,编译器可以根据对section("name")中的name指定,可以将它生成的数据放到特定的数据节中,下面引用一个网友electrlife的看法:

类似的这样的方式,Linux也提供了一些借鉴,把一个函数的地址(注意是函数地址,而不是函数本身)输出到一个独立的section中,同时按照一定顺序进行排列,例如:
.rti_fn.0
.rti_fn.1
.rti_fn.2
...
.rti_fn.7
这样几个section(这样几个不同的section也给出了排列的顺序)。同时把.rti_fn.0和.rti_fn.7保留给系统使用,分别定义出两个桩放置在这两个点上。也可以按照RT-Thread的形式定义简化的宏:
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn,level)? ? ? ?
? ? ? ? const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
#define INIT_BOARD_EXPORT(fn) ? ? ? ? ? ? ? ?INIT_EXPORT(fn,"1")
#define INIT_CPU_EXPORT(fn) ? ? ? ? ? ? ? ? ? ?INIT_EXPORT(fn,"2")
#define INIT_DEVICE_EXPORT(fn) ? ? ? ? ? ? ? ?INIT_EXPORT(fn,"3")
#define INIT_COMPONENT_EXPORT(fn) ? ? ? ? INIT_EXPORT(fn,"4")
#define INIT_FS_EXPORT(fn) ? ? ? ? ? ? ? ? ? ? ?INIT_EXPORT(fn,"5")
#define INIT_APP_EXPORT(fn) ? ? ? ? ? ? ? ? ? ?INIT_EXPORT(fn,"6")
INIT_EXPORT宏用于输出一个函数到初始化序列中,相应的可以定义一些更简化的宏。
这样两个桩可以定义成:
static int rti_start(void)
{
? ? ? ? return 0;
}
INIT_EXPORT(rti_start,"0");
static int rti_end(void)
{
? ? ? ? return 0;
}
INIT_EXPORT(rti_end,"7");
根据这两个桩的位置,简化的rt_components_init()函数就可以变成:
void rt_components_init(void)
{
? ? ? ? const init_fn_t* fn_ptr;
? ? ? ? for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_end; )
? ? ? ? {
? ? ? ? ? ? ? ? (*fn_ptr)();
? ? ? ? ? ? ? ? fn_ptr ++;
? ? ? ? }
}

事实上,aozima做了工程测试得到了验证:

?工程编译后,从map文件找到相关部分内容:
InitFuncSym$$Base? ?? ?? ?? ?? ?? ?? ?? ?0x00000e18? ?Number? ?? ?? ?0init_1.o(InitFuncSym)
? ? __rt_init_init_1? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e18? ?Data? ?? ?? ?4init_1.o(InitFuncSym)
? ? __rt_init_init_2? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e1c? ?Data? ?? ?? ?4init_2.o(InitFuncSym)
? ? __rt_init_init_3? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e20? ?Data? ?? ?? ?4init_3.o(InitFuncSym)
? ? __rt_init_init_4? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e24? ?Data? ?? ?? ?4init_4.o(InitFuncSym)
? ? __rt_init_init_5? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e28? ?Data? ?? ?? ?4init_5.o(InitFuncSym)
? ? __rt_init_init_6? ?? ?? ?? ?? ?? ?? ?? ? 0x00000e2c? ?Data? ?? ?? ?4init_6.o(InitFuncSym)
? ? InitFuncSym$$Limit? ?? ?? ?? ?? ?? ?? ?0x00000e30? ?Number? ?? ?? ?0init_6.o(InitFuncSym)
尽管数据存放在不同的文件,但从这里这可以看到是空间连续分配的
楼主设定的桩
static int rti_start(void)
{
? ? ? ? return 0;
}
INIT_EXPORT(rti_start,"0");

static int rti_end(void)
{
? ? ? ? return 0;
}
INIT_EXPORT(rti_end,"7");
可由InitFuncSym$$Base和InitFuncSym$$Limit 代替
最后,初始化过程可以写成
? ?? ?extern int InitFuncSym$$Base;
? ? ? ? extern int InitFuncSym$$Limit;
? ? ? ? init_fn_t* fn;
? ? ? ? for (fn = (init_fn_t *)&InitFuncSym$$Base; fn < (init_fn_t *)&InitFuncSym$$Limit; fn ++ ) {
? ? ? ? ? ? ? ? (*fn)();
? ? ? ? }

同样的,ffxz也有下面的测试:

参考RTT下的IAR工程,在ICF文件增加一行
keep { section InitFuncSym };
这行关系到以下信息是否生成.
在Debug/List下的map中有这样的描述
.text? ?? ?? ?? ?ro code0x00000658? ?0x16xprout.o?
InitFuncSym? ?? ?? ?? ?? ?0x00000670? ?0x14<Block>
? ? InitFuncSym? ?? ?const? ? 0x00000670? ? 0x4init_1.o?
? ? InitFuncSym? ?? ?const? ? 0x00000674? ? 0x4init_2.o?
? ? InitFuncSym? ?? ?const? ? 0x00000678? ? 0x4init_4.o?
? ? InitFuncSym? ?? ?const? ? 0x0000067c? ? 0x4init_5.o?
? ? InitFuncSym? ?? ?const? ? 0x00000680? ? 0x4init_6.o?
.rodata? ?? ?? ?? ?const? ? 0x00000684? ?0x10init_1.o?
......
InitFuncSym$$Base? ?0x00000670? ?? ?? ?DataGb- Linker created -
InitFuncSym$$Limit? ? 0x00000684? ?? ?? ?DataGb- Linker created -

总之,通过定义

#define INIT_EXPORT(fn,level)? ? ? ?
? ? ? ? const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

可以系统各部分的组件通过INIT_EXPORT(fn,level)放到一个特定代码段当中,简言之,当我们要初始化某个组件时,定义完这个初始化函数后,根据上面宏定义的注释,在其下面接着放一条INIT_XXX_EXPORT(fn)就可以了。相当于一个指定到特定代码段的隐形调用,而且要清楚这个段中是不同组件初始化函数的入口地址,例如:

int my_init_fun(void)

{

... ...

}

INIT_XXX_EXPORT(my_init_fun)

根据以上分析,我们需要工程文件做相应的修改:

【2】工程文件修改

(1)打开sdcard.c文件,定位到3241行附近,修改如下:

int rt_hw_sdcard_init(void)
{
? ? /* SDIO POWER */

... ...

__return:
? ? rt_kprintf("sdcard init failedn");
? ? GPIO_SetBits(GPIOC,GPIO_Pin_6); /* SD card power down */
? ? return 0;
}
//INIT_DEVICE_EXPORT(rt_hw_sdcard_init);
... ...
修改完成后保存,因为还没有sd卡的功能,先注释掉,不让系统调用这个初始化函数。
(2)打开rt_spi_flash_device.c文件,定位到94行附近,修改如下:

... ...
}
INIT_DEVICE_EXPORT(rt_hw_spi_init);
#endif /* RT_USING_SPI */
void rt_spi_flash_device_init(void)
{
#if defined(RT_USING_DFS) && defined(RT_USING_DFS_ELMFAT)

w25qxx_init("flash0","spi11");

#endif /* RT_USING_DFS && RT_USING_DFS_ELMFAT */

通过在rt_hw_spi_init()下面加上宏语句“INIT_DEVICE_EXPORT(rt_hw_spi_init);”让系统隐含调用它。当然也就不需要在rt_spi_device_init()函数调用它了。

(3)打开application.c,定位到99行附近,确认已经修改成如下代码:

void rt_init_thread_entry(void* parameter)
{
#ifdef RT_USING_COMPONENTS_INIT
? ? /* initialization RT-Thread Components */
? ? rt_components_init();
#endif

? ? rt_spi_flash_device_init();


#ifdef ?RT_USING_FINSH
? ? finsh_set_device(RT_CONSOLE_DEVICE_NAME);
#endif ?/* RT_USING_FINSH */

然后定位到文件开头45行附近,加入外部声明,如下:

#include "led.h"

extern void rt_spi_flash_device_init(void);

ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t led_stack[ 512 ];
static struct rt_thread led_thread;
static void led_thread_entry(void* parameter)
{
? ? unsigned int count=0;


? ? rt_hw_led_init();

... ...

修改完成后保存。

【3】确认Flash底层驱动的芯片型号识别部分代码是否和开发板上硬件对应上

开发板上的SPI Flash芯片型号SST25VF016B,打开数据手册,定位到第19页,可以看到下图所示定义:


制造商ID号0xBF,器件ID是0x4125,打开spi_flash_w25qxx.c,定位到37行附近,修改如下:

/* JEDEC Manufacturer?ˉs ID */
#define MF_ID ? ? ? ? ? (0xBF) /*(0xEF)*/
/* JEDEC Device ID: Memory type and Capacity */
#define MTC_W25Q16_BV_CL_CV ? (0x4015) /* W25Q16BV W25Q16CL W25Q16CV ?*/
#define MTC_W25Q16_DW ? ? ? ? (0x6015) /* W25Q16DW ?*/
#define MTC_W25Q32_BV ? ? ? ? (0x4016) /* W25Q32BV */
#define MTC_W25Q32_DW ? ? ? ? (0x6016) /* W25Q32DW */
#define MTC_W25Q64_BV_CV ? ? ?(0x4017) /* W25Q64BV W25Q64CV */
#define MTC_W25Q64_DW ? ? ? ? (0x4017) /* W25Q64DW */
#define MTC_W25Q128_BV ? ? ? ?(0x4018) /* W25Q128BV */
#define MTC_W25Q256_FV ? ? ? ?(TBD) ? ?/* W25Q256FV */
#define MTC_SST25VF016B ? ? ? (0x4125) /* SST25V016B */

然后定位到378行附近,修改如下:

... ...

? ? ? ? else if(memory_type_capacity == MTC_W25Q16_DW)
? ? ? ? {
? ? ? ? ? ? FLASH_TRACE("W25Q16DW detectionrn");
? ? ? ? ? ? spi_flash_device.geometry.sector_count = 512;
? ? ? ? }
else if(memory_type_capacity == MTC_SST25VF016B)
? ? ? ? {
? ? ? ? ? ? FLASH_TRACE("W25Q16DW detectionrn");
? ? ? ? ? ? spi_flash_device.geometry.sector_count = 512;
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? FLASH_TRACE("Memory Capacity error!rn");
? ? ? ? ? ? return -RT_ENOSYS;
? ? ? ? }

... ...

修改完成后保存。

(4) 使用Notepad++打开driver目录下的Sconscript,定位到23行附近,把前面添加到driver目录的rt_spi_device.c,rt_stm32f10x_spi.c,spi_flash_w25qxx.c添加到Depend()中,修改如下:

# add DFS drvers.
if GetDepend('RT_USING_DFS'):
? ? src += ['rt_spi_flash_device.c','rt_stm32f10x_spi.c','spi_flash_w25qxx.c']


# add RTC drvers.
if GetDepend('RT_USING_RTC'):
? ? src += ['rtc.c']

这样执行该脚本时,加入驱动文件才能被编译,修改完成后保存。

【4】编译测试

编译后发现如下信息:

driversrt_spi_device.c(86): warning: ?#144-D: a value of type "void (*)(void)" cannot be used to initialize an entity of type "const init_fn_t"

出现告警:原因是rt_hw_spi_init(void)函数出来问题,这个函数应该定义成int 类型而不是void类型,现在改过来,打开rt_spi_device.c定位到21行附近,修改如下:


#ifdef RT_USING_SPI
static int rt_hw_spi_init(void)
{


? ? /* register spi bus */
? ? {

... ...

return 0;


}
INIT_DEVICE_EXPORT(rt_hw_spi_init);
#endif /* RT_USING_SPI */


修改完成后保存,重新编译,OK,编译通过,没有告警。

下载到开发板上运行,结果如下:


Memory 容量错误,挂载失败。

进入跟踪调试模式,打开spi_flash_w25qxx.c,定位到378行,在出现“Memory Capacity error!”地方这个断点,然后点击运行,停到断点处,得到memory_type_capacity的跟踪值如下图所示:


显然是下图中的MTC_SST25VF016B的值定义错了,定位到


往上定位到48行附近,原来是芯片型号定义问题

... ...
#define MTC_W25Q128_BV ? ? ? ?(0x4018) /* W25Q128BV */
#define MTC_W25Q256_FV ? ? ? ?(TBD) ? ?/* W25Q256FV */
#define MTC_SST25VF016B ? ? ? (0x4125) /* SST25V016B */
... ...

现在修改成和跟踪到memory_type_capacity值相同:

... ...
#define MTC_SST25VF016B ? ? ??(0x25
41) /* SST25V016B */

... ...

然后退出调试模式,保存修改,重新编译,下载,运行后,终端信息如下:

可以看到,Flash芯片已经被成功识别,但文件系统还是没有下载成功,因为我们还没有格式化芯片,没有文件系统当然也就识别不到了。下一篇将要研究文件系统相关操作操作。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读