RT-Thread 快速入门实例教程

一般嵌入式操作系统因为它的特殊性,往往和硬件平台密切相关连,具体的嵌入式操作系统往往只能在特定的硬件上运行。对于刚接触 RT-Thread 操作系统的读者并不容易马上就获得一个和 RT-Thread 操作系统相配套的硬件模块,但随着计算机技术的发展,我们可以采用软件方式来模拟一个能够运行 RT-Thread 操作系统的硬件模块,这就是 ARM 公司的 MDK-ARM 仿真模拟环境。

MDK-ARM(MDK-ARM Microcontroller Development Kit)软件是一套完整的集成开发环境(IDE),它出自 ARM 公司,包括了针对 ARM 芯片(ARM7,ARM9,Cortex-M 系列,Cortex-R 系列等)的高效 C/C++ 编译器;针对各类 ARM 设备、评估板的工程向导,工程管理;用于软件模拟运行硬件平台的模拟器;以及与市面上常见的如 ST-Link,JLink 等在线仿真器相连接以配合调试目标板的调试器。MDK-ARM 软件中的软件仿真模拟器,采用完全软件模拟方式解释执行 ARM 的机器指令,并实现外围的一些外设逻辑,从而构成一套完整的虚拟硬件环境,使得用户能够不借助真实的硬件平台就能够在电脑上执行相应的目标程序。

MDK-ARM 集成开发环境因为其完全的 STM32F103 软件仿真环境,也让我们有机会在不使用真实硬件环境的情况下直接在电脑上运行目标代码。这套软件仿真模拟器能够完整地虚拟出 ARM Cortex-M3 的各种运行模式、外设,如中断异常,时钟定时器,串口等,这几乎和真实的硬件环境完全一致。实践也证明,本文使用到的这份 RT-Thread 入门例程,在编译成二进制代码后,不仅能够在模拟器上软件模拟运行,也能够不需要修改地在真实硬件平台上正常运行。

下面我们将选择 MDK-ARM 集成开发环境作为目标硬件平台来观察 RT-Thread 操作系统是如何运行的。

准备环境

在运行 RT-Thread 操作系统前,我们需要安装 MDK-ARM 5.24(正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。这里采用了 16k 编译代码限制的评估版 5.24 版本,如果要解除 16k 编译代码限制,请购买 MDK-ARM 正式版。先从 www.keil.com 官方网站下载 MDK-ARM 评估版:http://www.keil.com/download/

在下载时,需要填一些个人基本信息,请填写相应的完整信息,然后开始下载。下载完成后,鼠标双击运行,会出现如图所示的软件安装画面:

第一步

这是 MDK-ARM 的安装说明,点击 “Next>>” 进入下一画面,如图所示。

第二步

在 “I agree to all the terms of the preceding License Agreement” 前的选择框中点击选择 “√”,并点击”Next >>” 进入下一步安装,如图所示:

第三步

点击 “Browse…” 选择 MDK-ARM 的安装目录或者直接在 “Destination Folder” 下的文本框中输入安装路径,这里我们这里我们默认 “C:/Keil” 即可,然后点击”“Next>>”进入下一步安装,如图所示:

第四步

在 “First Name” 后输入您的名字,“Last Name”后输入您的姓,“Company Name”后输入您的公司名称,“E-mail”后输入您的邮箱地址,然后点击 “Next>>” 进行安装,等待一段时间后,安装结束,出现如图所示画面:

第五步

图中的默认选择不需改动,直接点击 “Next” 进入如图所示画面。

完成 MDK-ARM 安装

在这里可以点击 “Finish” 完成整个 MDK-ARM 软件的安装。

有了 MDK-ARM 利器,就可以轻松开始 RT-Thread 操作系统之旅,一起探索实时操作系统的奥秘。

注:MDK-ARM 正式版是收费的,如果您希望能够编译出更大体积的二进制文件,请购买 MDK-ARM 正式版。RT-Thread 操作系统也支持自由软件基金会的 GNU GCC 编译器,这是一款开源的编译器,想要了解如何使用 GNU 的相关工具请参考 RT-Thread 网站上的相关文档。

初识 RT-Thread

作为一个操作系统,RT-Thread 的代码规模怎么样呢?在弄清楚这些之前,我们先要做的就是获得与本文相对应的 RT-Thread 的例子,这份例子可以从以下链接获得:

RT-Thread Simulator 例程

这个例子是一个压缩包文件,将它解压,我们这里解压到 D:/。解压完成后的目录结构如下图所示:

rtthread_simulator_v0.1.0 代码目录

各个目录所包含的文件类型的描述如下表所示:

目录名 描述
applications RT-Thread 应用程序。
rt-thread RT-Thread 的源文件。
- components RT-Thread 的各个组件目录。
- include RT-Thread 内核的头文件。
- libcpu 各类芯片的移植代码,此处包含了 STM32 的移植文件。
- src RT-Thread 内核的源文件。
- tools RT-Thread 命令构建工具的脚本文件。
drivers RT-Thread 的驱动,不同平台的底层驱动具体实现。
Libraries ST 的 STM32 固件库文件。
kernel-sample-0.1.0 RT-Thread 的内核例程。

在目录下,有一个 project.uvprojx 文件,它是本文内容所引述的例程中的一个 MDK5 工程文件,双击 “project.uvprojx” 图标,打开此工程文件:

打开第一个 RT-Thread 工程

在工程主窗口的左侧 “Project” 栏里可以看到该工程的文件列表,这些文件被分别存放到如下几个组内,分别是:

目录组 描述
Applications 对应的目录为 rtthread_simulator_v0.1.0/applications,它用于存放用户应用代码。
Drivers 对应的目录为 rtthread_simulator_v0.1.0/drivers,它用于存放 RT-Thread 底层的驱动代码。
STM32_HAL 对应的目录为 rtthread_simulator_v0.1.0/Libraries/CMSIS/Device/ST/STM32F1xx,它用于存放 STM32 的固件库文件。
kernel-sample 对应的目录为 rtthread_simulator_v0.1.0/kernel-sample-0.1.0,它用于存放 RT-Thread 的内核例程。
Kernel 对应的目录为 rtthread_simulator_v0.1.0/src,它用于存放 RT-Thread 内核核心代码。
CORTEX-M3 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/libcpu,它用于存放 ARM Cortex-M3 移植代码。
DeviceDrivers 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/drivers,它用于存放 RT-Thread 驱动框架源码。
finsh 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/finsh,它用于存放 RT-Thread 命令行 finsh 命令行组件。

现在我们点击一下窗口上方工具栏中的按钮img,对该工程进行编译,如图所示:

编译工程

编译的结果显示在窗口下方的 “Build” 栏中,没什么意外的话,最后一行会显示“0 Error(s), * Warning(s).”,即无任何错误和警告。

在编译完 RT-Thread/STM32 后,我们可以通过 MDK-ARM 的模拟器来仿真运行 RT-Thread。点击窗口右上方的按钮img或直接按 “Ctrl+F5” 进入仿真界面,再按 F5 开始运行,然后点击该图工具栏中的按钮或者选择菜单栏中的 “View→Serial Windows→UART#1”,打开串口 1 窗口,可以看到串口的输出只显示了 RT-Thread 的 LOGO,这是因为用户代码是空的,其模拟运行的结果如图所示:

模拟运行 RT-Thread

我们可以通过输入Tab键或者 help + 回车 输出当前系统所支持的所有命令,如下图所示。

模拟运行 RT-Thread

系统启动代码

一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头,因为 MDK-ARM 的用户程序入口为 main() 函数,所以先看看 main() 函数在哪个文件中。这里的 main() 函数位于 Startup 组的 startup.c 文件中,它位于 STM32 的启动汇编代码后,与 C 代码的入口跳转前。启动汇编在 STM32_HAL 组的 startup_stm32f103xe.s 中。

下面我们来看看 main() 函数中的这段代码:

  1. //components.c 中定义
  2. /* re-define main function */
  3. int $Sub$$main(void)
  4. {
  5. rt_hw_interrupt_disable();
  6. rtthread_startup();
  7. return 0;
  8. }

在这里 $Sub$$main 函数仅仅调用了 rtthread_startup() 函数。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一入口点,所以 $Sub$$main 函数只需调用 rtthread_startup() 函数即可。例如采用 GNU GCC 编译器编译的 RT-Thread,就是直接从汇编启动代码部分跳转到 rtthread_startup() 函数中,并开始第一个 C 代码的执行的。在 components.c 的代码中找到 rtthread_startup() 函数,我们将可以看到 RT-Thread 的启动流程:

  1. int rtthread_startup(void)
  2. {
  3. rt_hw_interrupt_disable();
  4.  
  5. /* board level initalization
  6. * NOTE: please initialize heap inside board initialization.
  7. */
  8. rt_hw_board_init();
  9.  
  10. /* show RT-Thread version */
  11. rt_show_version();
  12.  
  13. /* timer system initialization */
  14. rt_system_timer_init();
  15.  
  16. /* scheduler system initialization */
  17. rt_system_scheduler_init();
  18.  
  19. #ifdef RT_USING_SIGNALS
  20. /* signal system initialization */
  21. rt_system_signal_init();
  22. #endif
  23.  
  24. /* create init_thread */
  25. rt_application_init();
  26.  
  27. /* timer thread initialization */
  28. rt_system_timer_thread_init();
  29.  
  30. /* idle thread initialization */
  31. rt_thread_idle_init();
  32.  
  33. /* start scheduler */
  34. rt_system_scheduler_start();
  35.  
  36. /* never reach here */
  37. return 0;
  38. }
  39. #endif
  40. #endif

这部分启动代码,大致可以分为四个部分 :- 初始化与系统相关的硬件;- 初始化系统内核对象,例如定时器,调度器;- 初始化系统设备,这个主要是为 RT-Thread 的设备框架做的初始化;- 初始化各个应用线程,并启动调度器。

用户入口代码

上面的启动代码基本上可以说都是和 RT-Thread 系统相关的,那么用户如何加入自己的应用程序的初始化代码呢?RT-Thread 将 main 函数作为了用户代码入口,只需要在 main 函数里添加自己的代码即可。

  1. int main(void)
  2. {
  3. /* user app entry */
  4. return 0;
  5. }
为了在进入 main 程序之前,完成系统功能初始化,可以使用 $sub 和 $super 函数标识符在进入主程序之前调用另外一个例程,这样可以让用户不用去管 main() 之前的系统初始化操作。详见 ARM® Compiler v5.06 for µVision® armlink User Guide

跑马灯的例子

对于从事电子方面开发的技术工程师来说,跑马灯大概是最简单的例子,就类似于每种编程语言中程序员接触的第一个程序 Hello World 一样,所以这个例子就从跑马灯开始。让它定时地对 LED 进行更新(关或灭)。

我们 UART#1 中输入 msh 命令:led 然后回车就可以运行起来了,如图所示:

模拟运行跑马灯

跑马灯例子

  1. /*
  2. * 程序清单:跑马灯例程
  3. *
  4. * 跑马灯大概是最简单的例子,就类似于每种编程语言中程序员接触的第一个程序
  5. * Hello World 一样,所以这个例子就从跑马灯开始。创建一个线程,让它定时地对
  6. * LED 进行更新(关或灭)
  7. */
  8.  
  9. int led(void)
  10. {
  11. rt_uint8_t count;
  12.  
  13. rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
  14.  
  15. for(count = 0 ; count < 10 ;count++)
  16. {
  17. rt_pin_write(LED_PIN, PIN_HIGH);
  18. rt_kprintf("led on, count : %d\r\n", count);
  19. rt_thread_mdelay(500);
  20.  
  21. rt_pin_write(LED_PIN, PIN_LOW);
  22. rt_kprintf("led off\r\n");
  23. rt_thread_mdelay(500);
  24. }
  25. return 0;
  26. }
  27. MSH_CMD_EXPORT(led, RT-Thread first led sample);

其他例子

其他更多的内核示例可以从 kernel-sample-0.1.0 目录下找到。

更多内核示例

常见问题

  • 出现如下编译错误
  1. rt-thread\src\kservice.c(823): error: #929: incorrect use of vaarg fieldwidth = aarg(args, int);
  2. rt-thread\src\kservice.c(842): error: #929: incorrect use of vaarg precision = aarg(args, int);
  3. ………

原因:这类问题基本上都是因为安装了 ADS 导致,ADS 与 keil共存,va_start 所在的头文件指向了 ADS 的文件夹。

解决办法:

  • 删除 ADS 环境变量
  • 卸载 ADS 和 keil,重启电脑,重装keil

原文: https://www.rt-thread.org/document/site/rtthread-tutorial/quick-start/quick-start/