RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$

简介: RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$

前面移植了RT-Thread Nano,其实准确来说那不叫移植,那叫做部署,因为移植的工作官方已经帮我们做好了。


文章链接:小熊派移植RT-Thread Nano

1、引发思考-相关资料检索

在之前的文章提到过,RT-Thread已经提前在main函数以前就把跟硬件配置、系统初始化、启动调度器等相关的都做好了,所以我们后来看到的main函数非常简洁,真是让人感觉神清气爽,有继续往下写代码的欲望,如下:


main.c

int main(void)
{
    while(1)
    {
        rt_kprintf("Hello RTT_NANO\n");
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        rt_thread_mdelay(500);
    }
}

那具体RT-Thread又是如何实现在main函数执行之前就把所有初始化硬件、时钟的工作都做了呢?跟随官方文档的RT-Thread代码启动流程:

640.png

跟代码,最后发现如下代码:

/* re-define main function */
int $Sub$$main(void)
{
    rtthread_startup();
    return 0;
}
/* 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) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif
}

平时工作开发中没用到这样的语法,于是只能搜索文档来看看到底是如何实现的,果然在Keil帮助手册中找到了答案:

640.png

从文档中得知,Keil MDK编译器用$Sub$$$Super$$这两个符号来扩展了 main 函数,这使得使用$Sub$$main可以在main函数执行之前就预先执行$Sub$$main函数,所以在$Sub$$main函数里就可以完成一些基本的硬件、时钟初始化功能,做完这些工作以后,还是得跳转到main函数去执行往后逻辑的呀,这就需要通过调用$Super$$main来实现了。(注:在Keil MDK编译器中是这样的情况,但在IAR以及GCC环境下有差别,这里不做分析,等后面用到再说)。


既然main函数之前能这么用,是不是换个函数也能这么用呢?这引发我的好奇,于是继续查找文档,在armlink_user_guide手册中找到:

640.png

接下来开始做实验,然后我用stm32cubeMX生成一个基本裸机工程,下载到小熊派上来验证是否正确。

2、小熊派上进行实践

2.1 基本功能配置

配置外部时钟、调试串口、调试接口以及LED


640.png

640.png

最后生成代码。

2.2 编写代码进行验证

首先添加一个串口重定向函数,后面才能使用printf

int fputc(int ch,FILE *file)
{
  return HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,1000);
}

接下来结合文档模仿RT-Thread写出以下函数:

void $Sub$$main(void)
{
  extern int main(void);
  extern int $Super$$main(void);
  //初始化HAL
  HAL_Init();
  //初始化系统时钟
  SystemClock_Config();
  //初始化GPIO
  MX_GPIO_Init();
  //初始化串口
  MX_USART1_UART_Init();
  printf("初始化已完成\n");
  //点灯
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
  //回到真正的main函数里
  $Super$$main();
}

main函数如下:

int main(void)
{
  //延时2s
  HAL_Delay(2000);
  printf("回到main函数中\n");
  while(1)
  {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    HAL_Delay(500);
  }
}

将程序编译后下载到小熊派开发板中,然后打开串口调试助手可以看到:

640.png

由此可见,这是一个很有逼格的技能,以后可以在支持这种扩展符号的编译器下将这种技能应用起来,从而简化代码,接下来我们再往上面这个程序里添加功能:添加Function函数和在它之前运行的$Sub$$Function,然后在main函数里调用Function函数:

void $Sub$$Function(void)
{
  extern void Function(void);
  extern void $Super$$Function(void);
  printf("在Function函数之前调用$Sub$$Function\n");
  $Super$$Function();
}
void Function(void)
{
  printf("执行Function函数\n");
}
int main(void)
{
  //延时2s
  HAL_Delay(2000);
  printf("回到main函数中\n");
  //调用Function函数
  Function();
  while(1)
  {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    HAL_Delay(500);
  }
}

然后编译后将程序下载到小熊派开发板后,通过串口调试助手看到:

640.png

至此,我们已经完全弄明白RT-Thread是如何实现在main函数执行之前就把初始化硬件、系统初始化、启动调度器等工作都完成了的基本原理。

3、案例下载

公众号后台回复:main扩展 即可获取本节案例的下载链接。

往期精彩

什么?C/C++面试过不了?因为你还没看过这个!


MCU SPI屏也能跑这么炫酷的特效?来,移植起来秀一秀


推荐三个我工作中经常使用的驱动大全wiki(建议收藏并转发让更多人知道!)


会C/C++就可以开发Linux/Android应用程序?替代传统串口屏的Yoxios了解一下!

目录
相关文章
|
2月前
|
Java
【编程基础知识】《Java 基础探秘:return、break、continue、label、switch 与 enum 的深度解析》
本文深入解析了 Java 中的 return、break、continue、label、switch 和 enum 等基础概念,通过代码示例和流程图,帮助读者理解这些控制结构和枚举类型在编程中的应用,提升编程能力。
30 3
|
7月前
|
Rust Java Serverless
函数与方法的区别
函数与方法的区别,当然是有区别。 不管是java、rust还是go,他们都是不一样的。
72 1
|
程序员 调度
多线程的创建,复习匿名内部类,Thread的一些方法,以及lambda的变量捕捉,join用法(二)
多线程的创建,复习匿名内部类,Thread的一些方法,以及lambda的变量捕捉,join用法
|
前端开发 Java 程序员
多线程的创建,复习匿名内部类,Thread的一些方法,以及lambda的变量捕捉,join用法(一)
多线程的创建,复习匿名内部类,Thread的一些方法,以及lambda的变量捕捉,join用法
|
7月前
|
算法 开发者 Python
【Python 基础扫盲 】self参数、__init__方法和.__str__方法的用处和区别?
【Python 基础扫盲 】self参数、__init__方法和.__str__方法的用处和区别?
496 0
|
7月前
|
JavaScript 编译器 API
v-pre的作用、使用场景、示例代码
v-pre 指令在 Vue 中的作用主要是`防止编译器解析某个特定的元素及其内容`。这在你想要展示 Vue 模板语法或者 Mustache 标签(例如 {{message}})而不是让 Vue 将其解析为数据绑定时非常有用。`使用 v-pre 指令的内容将会原样显示在页面上,不会进行数据绑定或插值。
|
7月前
如果我想在`__init__`方法中添加一些初始化逻辑,应该如何实现?
如果我想在`__init__`方法中添加一些初始化逻辑,应该如何实现?
48 0
|
存储 C++ 容器
C++ 第九节——map/set(用法+底层原理+模拟实现)
们需要知道的是,Map和Set的底层都是红黑树。
837 1
C++ 第九节——map/set(用法+底层原理+模拟实现)
|
7月前
|
存储 数据安全/隐私保护 C++
[C++从入门到精通] 1.函数调用、访问权限、类简介(Struct和Class区别)
[C++从入门到精通] 1.函数调用、访问权限、类简介(Struct和Class区别)
127 0
|
网络协议 编译器 测试技术
方法和函数区别说明|学习笔记
快速学习方法和函数区别说明