本帖最后由 liuluqqzj 于 2015-10-30 15:13 编辑
STM32F7-DISCO评估板极简介STM32F7-DISCO评估板基于STM32F746NG微控制器,板载资源丰富。板子的供电接口有四种选择:外部5v供电、5v st-link供电、USB-FS及USB-HS供电。通过反面的JP1跳帽选择供电方式,此处选择5v st-link即可。
烧写官方demo
从官网下载最新的
st-link烧写工具,安装之后打开烧写工具,打开要烧写的hex文件(位于官方cube库的STM32Cube_FW_F7_V1.1.0\Projects\STM32746G-Discovery\Demonstration下),连接开发板的同时选择外部flash烧写算法,如下所示:
完成上述步骤后点击烧写验证即可。
获得RTT最新版本库RTT为国产开源的实时嵌入式操作系统,类linux风格并附带有丰富的第三方库支持,从官方的
github库中将最新版本克隆到本地。其代码库的组织结构如下所示:
其中bsp/下包含了rtt针对各种型号处理器的初始化模版工程,从中找到stm32f7-disco文件目录,可以看到里面包含有一个keil下的模版工程,此工程还不能直接使用,需要按照
官方的指导手册来一步步配置,最终通过SCons来调用MDK工具链编译生成新工程project.uvprojx。由于官方文档描述十分详细此处不再赘述。
编译烧写新工程在完成前两步的基础上,我们已经具备了针对f746的新工程,使用最新的keil ide打开该工程。如果ide中还没有安装stm32f7xx的芯片支持包,建议去keil官网直接下载安装,keil下的自动安装非常慢,而且多半会失败。
在更新了芯片库之后,点击编译会出现 Use MicroLIB的相关错误,解决方式:在工程选项中去除对C库的勾选,同时删除工程中重复添加的main.c及sram.c,如下所示:
完成上述工作之后,工程编译无错误,点击下载可以看到板子上位于复位按钮旁边的led1在闪烁。此处打开串口连接可以看到终端出现类似linux命令行的执行窗口。
至此,rtt在f746上的初次运行就完成了,此处描述较为简洁,详细的步骤还需要翻阅相关的用户手册及安装配置相关的软件环境。
RTT启动过程介绍打开一个新工程首先会去查看main.c源文件,让人意外的是工程中的main.c干净的不像话…只有一条语句:return 0;但是板子的灯在闪烁,串口也有命令交互,因此可以肯定的是rtt已经启动了,只不过代码的初始化过程不在main.c中。
使用过IAR的童鞋应该知道IAR编译工程后在进入main.c之前添加了一段函数代码,此处采用的正是此机制,只不过换成了keil。MCU第一次上电运行的运行过程一般为:
- 系统上电进入Reset_Handler中断,执行 SystemInit;
- SystemInit完成系统时钟源及向量表的配置后,跳转到main函数;
- 系统开始运行用户程序;
为了知道在跳转到main函数之前系统做了什么,通过调试开启单步运行可以看到,程序运行到此处:
从图中可以看到,不同的编译器在跳转到main函数之前执行所替代的函数各不相同,对于当前的keil工程,通过int $Sub$$main(void)来取代main函数的跳转,再通过int $Super$$main(void);跳回到main函数中。而系统的初始化过程则在int $Sub$$main(void);中完成。
在int rtthread_startup(void)中有一系列的rtt初始化函数,重点关注函数rt_hw_board_init();及rt_application_init();
- int rtthread_startup(void)
-
- {
-
- rt_hw_interrupt_disable(); // 关系统中断
-
-
-
- /* board level initalization
-
- * NOTE: please initialize heap inside board initialization.
-
- */
-
- rt_hw_board_init(); // 板级初始化,系统外设相关初始化,时钟,中断,内存,控制台
-
-
-
- /* show RT-Thread version */
-
- rt_show_version();
-
-
-
- /* timer system initialization */
-
- rt_system_timer_init(); // 初始化一组软件定时器
-
-
-
- /* scheduler system initialization */
-
- rt_system_scheduler_init(); // 系统调度器初始化
-
-
-
- /* create init_thread */
-
- rt_application_init(); // 创建初始化任务
-
-
-
- /* timer thread initialization */
-
- rt_system_timer_thread_init(); // 定时器任务,管理所有的定时器
-
-
-
- /* idle thread initialization */
-
- rt_thread_idle_init(); // 空闲任务
-
-
-
- /* start scheduler */
-
- rt_system_scheduler_start(); // 启动系统调度器
-
-
-
- /* never reach here */
-
- return 0;
-
- }
复制代码rt_hw_board_init();函数中主要实现的是系统时钟及外设的初始化,见如下代码注释:
- /**
-
- * This function will initial STM32 board.
-
- */
-
- void rt_hw_board_init()
-
- {
-
- /* Configure the MPU attributes as Write Through */
-
- //mpu_init(); // 没有配置mpu
-
-
-
- /* Enable the CPU Cache */
-
- CPU_CACHE_Enable();
-
-
-
- /* STM32F7xx HAL library initialization:
-
- - Configure the Flash ART accelerator on ITCM interface
-
- - Configure the Systick to generate an interrupt each 1 msec
-
- - Set NVIC Group Priority to 4
-
- - Global MSP (MCU Support Package) initialization
-
- */
-
- HAL_Init(); // 硬件抽象层初始化,中断,systick配置
-
- /* Configure the system clock @ 200 Mhz */
-
- SystemClock_Config(); // 配置系统时钟
-
- /* init systick */
-
- SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); // 又配置了一遍
-
- /* set pend exception priority */
-
- NVIC_SetPriority(PendSV_IRQn, (1 << __NVIC_PRIO_BITS) - 1); // 又配置了一遍
-
-
-
- #ifdef RT_USING_COMPONENTS_INIT // 已开启
-
- rt_components_board_init(); // 板载组件初始化
-
- #endif
-
-
-
- #ifdef RT_USING_EXT_SDRAM // 已开启
-
- rt_system_heap_init((void*)EXT_SDRAM_BEGIN, (void*)EXT_SDRAM_END); // 初始化外部内存
-
- sram_init();
-
- #else
-
- rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END);
-
- #endif
-
-
-
- #ifdef RT_USING_CONSOLE
-
- rt_console_set_device(RT_CONSOLE_DEVICE_NAME); // 设置新控制台设备
-
- #endif
-
- }
复制代码
上述代码中调用了板级组件初始化函数rt_components_board_init,该函数体的代码如下所示:
- /**
-
- * RT-Thread Components Initialization for board
-
- */
-
- void rt_components_board_init(void)
-
- {
-
- #if RT_DEBUG_INIT // 未开启
-
- int result;
-
- const struct rt_init_desc *desc;
-
- for (desc = &__rt_init_desc_rti_start; desc < &__rt_init_desc_rti_board_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;
-
-
-
- // led mpu sdram uart
-
- for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
-
- {
-
- (*fn_ptr)();
-
- }
-
- #endif
-
- }
复制代码
从代码上直观理解,fn_ptr为一个函数指针,__rt_init_rti_start及__rt_init_rti_board_end为函数指针的起始及结束地址,通过for循环来调用这一系列的指针函数来完成班级组件的初始化。
而fn_ptr所指向的函数究竟是什么,有两种方式可以探别,一种是在调试中的(*fn_ptr)();语句处打断点,每次运行到此处时进入函数内部执行,则可以看到当前for循环所调用的指针函数;另一种方式则是直观的看代码,查找这两个起始及结束地址的定义,出现如下代码:
- static int rti_start(void)
-
- {
-
- return 0;
-
- }
-
- INIT_EXPORT(rti_start, "0");// __rt_init_rti_start
-
-
-
- static int rti_board_end(void)
-
- {
-
- return 0;
-
- }
-
- INIT_EXPORT(rti_board_end, "1.end");// __rt_init_rti_board_end
复制代码INIT_EXPORT的宏定义如下:
- <font color="rgb(222, 147, 95)"><font face="inherit">#define INIT_EXPORT(fn, level) \</font></font> <font color="rgb(178, 148, 187)"><font face="inherit">const</font></font> init_fn_t __rt_init_<font color="rgb(222, 147, 95)"><font face="inherit">##fn SECTION(".rti_fn."level) = fn</font></font>
复制代码
SECTION的宏定义如下:
- <font color="rgb(204, 102, 102)"><font face="inherit">#define</font></font> <font color="rgb(129, 162, 190)"><font face="inherit">SECTION</font></font>(x) __<font color="rgb(129, 162, 190)"><font face="inherit">attribute__</font></font>((<font color="rgb(129, 162, 190)"><font face="inherit">section</font></font>(x)))
复制代码
__attribute__为ARM编译器的扩展属性,realview编译工具手册中对其描述如下:
将 INIT_EXPORT(rti_start, "0");宏展开,得到的函数语句如下:
- <font color="rgb(178, 148, 187)"><font face="inherit">const</font></font> init_fn_t __rt_init_rti_start __attribute__((section(<font color="rgb(181, 189, 104)"><font face="inherit">".rti_fn.0"</font></font>))) = rti_start;
复制代码
因此__rt_init_rti_start为一个函数指针,指向rti_start函数,同时__attribute__的section属性将该函数指针放在名为.rti_fn.0的段中。同理__rt_init_rti_board_end也是一个函数指针,指向rti_board_end函数,同时指定该函数指针存放在.rti_fn.1.end段中。
程序分析到这里,已经明白了编译器中关于段的含义,但是.rti_fn.0段与.rti_fn.1.end段之间到底还存在哪些函数指针,则需要进一步分析。工程全局搜索宏INIT_EXPORT,可看到如下宏定义:
- /* board init routines will be called in board_init() function */
-
- #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
-
- /* device/component/fs/app init routines will be called in init_thread */
-
- /* device initialization */
-
- #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "2")
-
- /* components initialization (dfs, lwip, ...) */
-
- #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "3")
-
- /* file system initialization (dfs-elm, dfs-rom, ...) */
-
- #define INIT_FS_EXPORT(fn) INIT_EXPORT(fn, "4")
-
- /* environment initialization (mount disk, ...) */
-
- #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
-
- /* appliation initialization (rtgui application etc ...) */
-
- #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
复制代码从宏定义中可以看到,根据编译器生成的段排列顺序,所有使用INIT_EXPORT(fn, "1")宏指定的指针函数都将介于.rti_fn.0段与.rti_fn.1.end段之间,即调用INIT_BOARD_EXPORT导出函数的语句处都会生成一个函数指针存放在这两个段之间。全局搜索INIT_BOARD_EXPORT,可以得知rt_components_board_init函数中的for循环调用的是以下函数:
其中stm32_hw_usart_init初始化并注册usart1设备,剩下的三个函数则是sdram、mpu及led引脚的初始化。
为了验证上述结论的正确性,打开工程生成的.map文件,全局搜索关键字.rti_fn.可以看到编译器放置在该段范围内的函数指针:
解决了stm32_hw_usart_init函数,剩下的rt_application_init则简单许多,该函数创建了一个主任务main_thread_entry,任务回调函数如下:
- /* the system main thread */ // 初始化的任务主体
-
- void main_thread_entry(void *parameter)
-
- {
-
- extern int main(void);
-
- extern int $Super$main(void);
-
-
-
- /* RT-Thread components initialization */
-
- rt_components_init();
-
-
-
- /* invoke system main function */
-
- #if defined (__CC_ARM)
-
- $Super$main(); /* for ARMCC. */ // 此时才会去调用main函数
-
- #elif defined(__ICCARM__) || defined(__GNUC__)
-
- main();
-
- #endif
-
- }
复制代码
该任务调用了rtt的组件初始化函数rt_components_init,该函数完成了一些系统的初始化服务,包括i2c、finsh、led_task等,分析过程同rt_components_board_init函数,不再赘述。任务的尾部才跳回到main函数中去处理用户代码。初始化的最后回到rtthread_startup函数中完成rtt的任务调度及启动。