软件环境: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的相关驱动文件添加和相关代码修改,编译虽然能通过但并不代表能够调试通过。因为针对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 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
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)
根据以上分析,我们需要工程文件做相应的修改:
(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_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();
... ...
修改完成后保存。
开发板上的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_flash_device.c','rt_stm32f10x_spi.c','spi_flash_w25qxx.c']
# add RTC drvers.
if GetDepend('RT_USING_RTC'):
src += ['rtc.c']
这样执行该脚本时,加入驱动文件才能被编译,修改完成后保存。
编译后发现如下信息:
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 (0x2541) /* SST25V016B */
... ...
然后退出调试模式,保存修改,重新编译,下载,运行后,终端信息如下:
可以看到,Flash芯片已经被成功识别,但文件系统还是没有下载成功,因为我们还没有格式化芯片,没有文件系统当然也就识别不到了。下一篇将要研究文件系统相关操作操作。