天南地北客
发表于: 2016-11-23 11:55:45 | 显示全部楼层

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烧写算法,如下所示:

烧写工具.jpg

完成上述步骤后点击烧写验证即可。

获得RTT最新版本库

RTT为国产开源的实时嵌入式操作系统,类linux风格并附带有丰富的第三方库支持,从官方的github库中将最新版本克隆到本地。其代码库的组织结构如下所示:

rtt库目录.jpg

其中bsp/下包含了rtt针对各种型号处理器的初始化模版工程,从中找到stm32f7-disco文件目录,可以看到里面包含有一个keil下的模版工程,此工程还不能直接使用,需要按照官方的指导手册来一步步配置,最终通过SCons来调用MDK工具链编译生成新工程project.uvprojx。由于官方文档描述十分详细此处不再赘述。

编译烧写新工程

在完成前两步的基础上,我们已经具备了针对f746的新工程,使用最新的keil ide打开该工程。如果ide中还没有安装stm32f7xx的芯片支持包,建议去keil官网直接下载安装,keil下的自动安装非常慢,而且多半会失败。

在更新了芯片库之后,点击编译会出现 Use MicroLIB的相关错误,解决方式:在工程选项中去除对C库的勾选,同时删除工程中重复添加的main.c及sram.c,如下所示:

工程选项.jpg

完成上述工作之后,工程编译无错误,点击下载可以看到板子上位于复位按钮旁边的led1在闪烁。此处打开串口连接可以看到终端出现类似linux命令行的执行窗口。

命令窗口.jpg

至此,rtt在f746上的初次运行就完成了,此处描述较为简洁,详细的步骤还需要翻阅相关的用户手册及安装配置相关的软件环境。

RTT启动过程介绍

打开一个新工程首先会去查看main.c源文件,让人意外的是工程中的main.c干净的不像话…只有一条语句:return 0;但是板子的灯在闪烁,串口也有命令交互,因此可以肯定的是rtt已经启动了,只不过代码的初始化过程不在main.c中。

使用过IAR的童鞋应该知道IAR编译工程后在进入main.c之前添加了一段函数代码,此处采用的正是此机制,只不过换成了keil。MCU第一次上电运行的运行过程一般为:

    1) 系统上电进入Reset_Handler中断,执行 SystemInit;

    2) SystemInit完成系统时钟源及向量表的配置后,跳转到main函数;

    3) 系统开始运行用户程序;

为了知道在跳转到main函数之前系统做了什么,通过调试开启单步运行可以看到,程序运行到此处:

初始化跳转.jpg

从图中可以看到,不同的编译器在跳转到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();

  1. int rtthread_startup(void)
  2. {
  3.     rt_hw_interrupt_disable();  // 关系统中断

  4.     /* board level initalization
  5.      * NOTE: please initialize heap inside board initialization.
  6.      */
  7.     rt_hw_board_init(); // 板级初始化,系统外设相关初始化,时钟,中断,内存,控制台

  8.     /* show RT-Thread version */
  9.     rt_show_version();

  10.     /* timer system initialization */
  11.     rt_system_timer_init(); // 初始化一组软件定时器

  12.     /* scheduler system initialization */
  13.     rt_system_scheduler_init(); // 系统调度器初始化

  14.     /* create init_thread */
  15.     rt_application_init();  // 创建初始化任务

  16.     /* timer thread initialization */
  17.     rt_system_timer_thread_init();  // 定时器任务,管理所有的定时器

  18.     /* idle thread initialization */
  19.     rt_thread_idle_init();  // 空闲任务

  20.     /* start scheduler */
  21.     rt_system_scheduler_start();    // 启动系统调度器

  22.     /* never reach here */
  23.     return 0;
  24. }
复制代码

rt_hw_board_init();函数中主要实现的是系统时钟及外设的初始化,见如下代码注释:

  1. <p style="line-height: 30px; text-indent: 2em;">/**
  2. * This function will initial STM32 board.
  3. */
  4. void rt_hw_board_init()
  5. {
  6.     /* Configure the MPU attributes as Write Through */
  7.     //mpu_init();   // 没有配置mpu</p><p style="line-height: 30px; text-indent: 2em;">    /* Enable the CPU Cache */
  8.     CPU_CACHE_Enable();</p><p style="line-height: 30px; text-indent: 2em;">    /* STM32F7xx HAL library initialization:
  9.     - Configure the Flash ART accelerator on ITCM interface
  10.     - Configure the Systick to generate an interrupt each 1 msec
  11.     - Set NVIC Group Priority to 4
  12.     - Global MSP (MCU Support Package) initialization
  13.     */
  14.     HAL_Init(); // 硬件抽象层初始化,中断,systick配置
  15.     /* Configure the system clock @ 200 Mhz */
  16.     SystemClock_Config();   // 配置系统时钟
  17.     /* init systick */
  18.     SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);   // 又配置了一遍
  19.     /* set pend exception priority */
  20.     NVIC_SetPriority(PendSV_IRQn, (1 << __NVIC_PRIO_BITS) - 1); // 又配置了一遍</p><p style="line-height: 30px; text-indent: 2em;">#ifdef RT_USING_COMPONENTS_INIT // 已开启
  21.     rt_components_board_init(); // 板载组件初始化
  22. #endif</p><p style="line-height: 30px; text-indent: 2em;">#ifdef RT_USING_EXT_SDRAM   // 已开启
  23.     rt_system_heap_init((void*)EXT_SDRAM_BEGIN, (void*)EXT_SDRAM_END);  // 初始化外部内存
  24.     sram_init();
  25. #else
  26.     rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END);
  27. #endif</p><p style="line-height: 30px; text-indent: 2em;">#ifdef RT_USING_CONSOLE
  28.     rt_console_set_device(RT_CONSOLE_DEVICE_NAME);  // 设置新控制台设备
  29. #endif
  30. }</p>
复制代码

上述代码中调用了板级组件初始化函数rt_components_board_init,该函数体的代码如下所示:

  1. /**
  2. * RT-Thread Components Initialization for board
  3. */
  4. void rt_components_board_init(void)
  5. {
  6. #if RT_DEBUG_INIT   // 未开启
  7.     int result;
  8.     const struct rt_init_desc *desc;
  9.     for (desc = &__rt_init_desc_rti_start; desc < &__rt_init_desc_rti_board_end; desc ++)
  10.     {
  11.         rt_kprintf("initialize %s", desc->fn_name);
  12.         result = desc->fn();
  13.         rt_kprintf(":%d done\n", result);
  14.     }
  15. #else
  16.     const init_fn_t *fn_ptr;

  17.     // led mpu sdram uart
  18.     for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)   
  19.     {
  20.         (*fn_ptr)();
  21.     }
  22. #endif
  23. }
复制代码

从代码上直观理解,fn_ptr为一个函数指针,__rt_init_rti_start及__rt_init_rti_board_end为函数指针的起始及结束地址,通过for循环来调用这一系列的指针函数来完成板级组件的初始化。

而fn_ptr所指向的函数究竟是什么,有两种方式可以探别,一种是在调试中的(*fn_ptr)();语句处打断点,每次运行到此处时进入函数内部执行,则可以看到当前for循环所调用的指针函数;另一种方式则是直观的看代码,查找这两个起始及结束地址的定义,出现如下代码:

  1. static int rti_start(void)
复制代码

INIT_EXPORT的宏定义如下:

  1. #define INIT_EXPORT(fn, level)  \
  2.         const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
复制代码

SECTION的宏定义如下:

  1. #define SECTION(x)                  __attribute__((section(x)))
复制代码

__attribute__为ARM编译器的扩展属性,realview编译工具手册中对其描述如下:

section属性.jpg

将 INIT_EXPORT(rti_start, "0");宏展开,得到的函数语句如下:

  1. <p style="line-height: 30px; text-indent: 2em;">const init_fn_t __rt_init_rti_start __attribute__((section(".rti_fn.0"))) = rti_start;</p>
复制代码

因此__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,可看到如下宏定义:

  1. /* board init routines will be called in board_init() function */        
复制代码

从宏定义中可以看到,根据编译器生成的段排列顺序,所有使用INIT_EXPORT(fn, "1")宏指定的指针函数都将介于.rti_fn.0段与.rti_fn.1.end段之间,即调用INIT_BOARD_EXPORT导出函数的语句处都会生成一个函数指针存放在这两个段之间。全局搜索INIT_BOARD_EXPORT,可以得知rt_components_board_init函数中的for循环调用的是以下函数:

board导出函数.jpg

其中stm32_hw_usart_init初始化并注册usart1设备,剩下的三个函数则是sdram、mpu及led引脚的初始化。

为了验证上述结论的正确性,打开工程生成的.map文件,全局搜索关键字.rti_fn.可以看到编译器放置在该段范围内的函数指针:

map信息.jpg

解决了stm32_hw_usart_init函数,剩下的rt_application_init则简单许多,该函数创建了一个主任务main_thread_entry,任务回调函数如下:


  1. /* the system main thread */ // 初始化的任务主体
  2. void main_thread_entry(void *parameter)
  3. {
  4.     extern int main(void);
  5.     extern int $Super$main(void);

  6.     /* RT-Thread components initialization */
  7.     rt_components_init();

  8.     /* invoke system main function */
  9. #if defined (__CC_ARM)
  10.     $Super$main(); /* for ARMCC. */ // 此时才会去调用main函数
  11. #elif defined(__ICCARM__) || defined(__GNUC__)
  12.     main();
  13. #endif
  14. }
复制代码

该任务调用了rtt的组件初始化函数rt_components_init,该函数完成了一些系统的初始化服务,包括i2c、finsh、led_task等,分析过程同rt_components_board_init函数,不再赘述。任务的尾部才跳回到main函数中去处理用户代码。初始化的最后回到rtthread_startup函数中完成rtt的任务调度及启动。


原文地址:STM32F7-Disco首次运行RTT


跳转到指定楼层
huaiqiao
发表于: 2016-11-24 09:18:45 | 显示全部楼层

感谢楼主分享。长见识了。哈哈

想请问下,楼主如下这个图片是来自什么文档,能分享下,这个文档么?

115646smrsttyh98rhm287.jpg
回复

使用道具 举报

天南地北客
发表于: 2016-11-24 09:49:29 | 显示全部楼层

huaiqiao 发表于 2016-11-24 09:18
感谢楼主分享。长见识了。哈哈

想请问下,楼主如下这个图片是来自什么文档,能分享下,这个文档么?

这个是ARM官方的《RealView® 编译工具 编译器参考指南 4.0 版》,很多ARM官方的资料都是非常不错的。
PDF可以在官网下载:RealView 编译工具 编译器参考指南



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 11 | 回复: 49



手机版|

GMT+8, 2024-4-19 07:12 , Processed in 0.132575 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表