文章目录 STM32F7-DISCO评估板极简介 获得RTT最新版本库 编译烧写新工程 RTT启动过程介绍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_startstatic int rti_board_end(
void){
return 0;}INIT_EXPORT(rti_board_end,
"1.end");
// __rt_init_rti_board_endINIT_EXPORT的宏定义如下:
#define INIT_EXPORT(fn, level) \ const init_fn_t __rt_init_
##fn SECTION(".rti_fn."level) = fnSECTION的宏定义如下:
#define SECTION(x) __
attribute__((
section(x)))
__attribute__为ARM编译器的扩展属性,realview编译工具手册中对其描述如下:
将 INIT_EXPORT(rti_start, "0");宏展开,得到的函数语句如下:
const init_fn_t __rt_init_rti_start __attribute__((section(
".rti_fn.0"))) = 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的任务调度及启动。
本文来自论坛,点击查看完整帖子内容。