鸿 网 互 联 www.68idc.cn

当前位置 : 服务器租用 > 网站制作教程 > flash > >

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

来源:互联网 作者:佚名 时间:2015-08-24 11:33
软件环境: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 done\n", 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 div(".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 div(".rti_fn."level) = fn,其中##连词符,div的定义为:

#define div(x)   __attribute__((div(x)))

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

__attribute__((div("name")))

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

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

类似的这样的方式,Linux也提供了一些借鉴,把一个函数的地址(注意是函数地址,而不是函数本身)输出到一个独立的div中,同时按照一定顺序进行排列,例如:
.rti_fn.0
.rti_fn.1
.rti_fn.2
...
.rti_fn.7
这样几个div(这样几个不同的div也给出了排列的顺序)。同时把.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 div(".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 { div 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 div(".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 failed\n");
    GPIO_SetBits(GPIOC,GPIO_Pin_6); /* SD card power down */
    return 0;
}
//INIT_DEVICE_EXPORT(rt_hw_sdcard_init);
... ...
修改完成后保存,因为还没有sd卡的功能,先注释掉,不让系统调用这个初始化函数。
(2)打开rt_spi_device.c文件,定位到94行附近,修改如下:

... ...
}
INIT_DEVICE_EXPORT(rt_hw_spi_init);
#endif /* RT_USING_SPI */
void rt_spi_device_init(void)
{
#ifdef RT_USING_SPI
    //rt_hw_spi_init();
#endif // RT_USING_SPI

... ...

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

(3)打开application.c,定位到99行附近,找到“rt_platform_init();”修改成如下代码:

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

    rt_spi_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_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 detection\r\n");
            spi_flash_device.geometry.sector_count = 512;
        }
else if(memory_type_capacity == MTC_SST25VF016B)
        {
            FLASH_TRACE("W25Q16DW detection\r\n");
            spi_flash_device.geometry.sector_count = 512;
        }
        else
        {
            FLASH_TRACE("Memory Capacity error!\r\n");
            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_device.c','rt_stm32f10x_spi.c','spi_flash_w25qxx.c']


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

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

【4】编译测试

编译后发现如下信息:

drivers\rt_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 */

... ...

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




网友评论
<