当前位置:网站首页>RT-Thread 学习笔记(七)---开启基于SPI Flash的elmfat文件系统(中)
RT-Thread 学习笔记(七)---开启基于SPI Flash的elmfat文件系统(中)
2022-07-26 10:38:00 【aping_cs_dn】
软件环境: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编程指南
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 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 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();
... ...
修改完成后保存。
【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_flash_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 (0x2541) /* SST25V016B */
... ...
然后退出调试模式,保存修改,重新编译,下载,运行后,终端信息如下:
边栏推荐
猜你喜欢
putty的使用教程
Issue 7: how do you choose between curling up and lying flat
Application of.Net open source framework in industrial production
【机器学习小记】【风格迁移】deeplearning.ai course4 4th week programming(tensorflow2)
Navicat15 MySQL (centos7) connected to local virtual machine
英语基础句型结构------起源
第5期:大学生入职必备技能之二
Zongzi battle - guess who can win
Analysis of the transaction problem of chained method call
Phase 4: one of College Students' vocational skills preparation in advance
随机推荐
多目标优化系列1---NSGA2的非支配排序函数的讲解
【机器学习小记】【人脸识别】deeplearning.ai course4 4th week programming
[leetcode每日一题2021/8/31]1109. 航班预订统计【中等】差分数组
Okaleido ecological core equity Oka, all in fusion mining mode
C语言回调函数
Centos8 (liunx) deploying WTM (asp.net 5) using PgSQL
MD5 encryption
Redis特殊数据类型使用场景
[leetcode每日一题2021/4/29]403. 青蛙过河
剑指Offer(五十三):表示数值的字符串
[leetcode每日一题2021/5/8]1723. 完成所有工作的最短时间
Some web APIs you don't know
putty的使用教程
10 令 operator= 返回一个 reference to *this
[leetcode每日一题2021/2/13]448. 找到所有数组中消失的数字
algorithm
Okaleido生态核心权益OKA,尽在聚变Mining模式
异常的概念与处理
Oracle create index
[leetcode每日一题2021/4/23]368. 最大整除子集