在 STM32 上使用 C++
如何使用在搭载了 RT-Thread 系统的 STM32 平台上使用 C++ ,这里介绍了包括 C++ 的配置和应用等。并给出了在意法半导体 STM32F411 nucleo 开发板上验证的代码示例。
硬件平台简介
本文基于意法半导体 STM32F411 nucleo 开发板,给出了 C++ 的具体应用示例代码,由于 RT-Thread 上层应用 API 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。
STM32F411 nucleo 是意法半导体推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 100Mhz ,该开发板具有丰富的板载资源,可以充分发挥 STM32F411RE 的芯片性能。
如何在 STM32 上使用 C++
准备工作:
1. 下载 RT-Thread 源码
2. 下载 ENV 工具
3. 进入 rt-thread\bsp\stm32f411-st-nucleo 目录,检查 BSP rtconfig.py 文件和 SConstruct 文件是否支 持 C++ 配置,如下图所示
检查 SConstruct 文件中对 C++ 的支持
打开 C++ 支持:
1. 打开 Env 工具,在 Env 命令行中输入 menuconfig ,进入配置界面,使用 menuconfig 工具。在 menuconfig 配置界面依次选择 RT-Thread Components ---> C++ features ---> Support C++ features ,如图所示:
编译工程: scons --target=mdk5
1. 生成 mdk5 工程,将示例代码附带的 main.cpp 替换掉 BSP 中的 main.c 并重新加入到工程中,如图所示:
2. 编译,下载程序,在终端输入 help 命令可以看到 test_cpp 已经添加成功了。
3. 运行 C++ 程序:
在终端输入 test_cpp 运行结果如下图所示。
C++ 全局对象构造函数的调用
RT-Thread 中对全局对象构造函数的实现中,以 GNUC 为例,在 rt-thread\components\cplusplus 目录下的 crt_init.c 文件中对 C++ 进行了系统初始化, 在特定的 BSP 目录下,连接脚本文件 link.lds 为 C++ 全局构造函数的代码分配了段,使 C++ 全局对象构造函数链接后能够存放在指定的段中。如下图所示:
1. crt_init.c 文件完成了 C++ 系统的初始化工作
2. C++ 系统初始化部分:
RT_WEAK int cplusplus_system_init(void)
{
typedef void(*pfunc)();
extern pfunc __ctors_start__[];
extern pfunc __ctors_end__[];
pfunc *p;
for (p = __ctors_start__; p < __ctors_end__; p++)
(*p)();
return 0;
}
INIT_COMPONENT_EXPORT(cplusplus_system_init);
在 cplusplus_system_init 函数中,将全局对象的构造函数依次链接到了链接脚本文件中为其分配的段中,并且调用了 RT-Thread 组件自动初始化的宏 INIT_COMPONENT_EXPORT ,所以在链接的时候, C++ 全局对象构造函数所产生的目标文件就被链接到了 __ctors_start__ 和 __ctors_end__ 组成的段中。
3. 链接脚本中为 C++ 全局构造函数分配的段部分:
PROVIDE(__ctors_start__ = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE(__ctors_end__ = .);
__ctors_start__ 分配了 C++ 全局构造函数段的起始地址, __ctors_end__ 分配了 C++ 全局构造函数段的结 束地址,所以全局构造函数在系统初始化的时候,就会被链接到这里分配的段地址中。
RT-Thread C++ 异常说明
同样,在链接脚本文件 link.lds 中,也为 C++ 异常分配了段地址:
__exidx_start = .;
ARM.exidx :
{
*(.ARM.exidx* .gnu.l_sidata = .;
} > CODE
__exidx_end = .;
__exidx_start 分配了 C++ 异常的起始地址, __exidx_end 分配了 C++ 异常的结束地址,当异常产生的时候,就 会被分配到指定的段地址中. 这里以一个 C++ 除零异常的抛出和捕获为例:
#include
#define MIN_VALUE (1e-4)
#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE)
double div_func(double x, double y)
{
if (IS_DOUBLE_ZERO(y))
{
throw y; /* throw exception */
}
return x / y;
}
void throw_exceptions(void *args)
{
try
{
div_func(6, 3);
rt_kprintf("there is no err\n");
div_func(4, 0); /* create exception*/
rt_kprintf("you can run here?\n");
}
catch(double) /* catch exception */
{
rt_kprintf("error of dividing zero\n");
}
}
MSH_CMD_EXPORT(throw_exceptions, throw cpp exceptions);
秀豆麻袋!!!!!
在 MDK 中编译提示错误:support for exception handling is disabled;use --exceptions to enable
那我们就去使能 C++ 的异常:
重新编译没有错误,下载代码,当除零异常发生的时候 div_func 函数会抛出一个异常,在 throw_exceptions 函数中会去捕获这个异常。 并在终端输入 throw_exceptions 运行结果如下图所示。
附录1 main.cpp 程序源码:
/**
* @file main.cpp
* @time 2019-6-8 13:31:39
* @author tyustli
* @platform w10 + mdk5 + rtthread 4.0.0
*/
#include
#include
#include
/* defined the LED0 pin: PA5 */
#define LED0_PIN GET_PIN(A, 5)
int main(void)
{
int count = 1;
/* set LED0 pin mode to output */
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
while (count++)
{
rt_pin_write(LED0_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED0_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
return RT_EOK;
}
using namespace rtthread;
class tran
{
public:
void getnumber(int a, int b)
{
x = a;
y = b;
}
void out(tran & s)
{
rt_kprintf("x = %d, y = %d\n", x, y);
}
private:
int x, y;
};
int test_cpp(void)
{
tran s;
s.getnumber(13, 54);
s.out(s);
return 0;
}
MSH_CMD_EXPORT(test_cpp, test cpp);
#include
#define MIN_VALUE (1e-4)
#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE)
double div_func(double x, double y)
{
if (IS_DOUBLE_ZERO(y))
{
throw y; /* throw exception */
}
return x / y;
}
void throw_exceptions(void *args)
{
try
{
div_func(6, 3);
rt_kprintf("there is no err\n");
div_func(4, 0); /* create exception*/
rt_kprintf("you can run here\n");
}
catch(double) /* catch exception */
{
rt_kprintf("error of dividing zero\n");
}
}
MSH_CMD_EXPORT(throw_exceptions, throw cpp exceptions);
附录2 C++ 线程源码:
/*
* Copyright (c) 2006-2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-6-24 tyustli the first version
*/
#include
#include "Thread.h"
using namespace rtthread;
class Son_Thread: public Thread
{
public:
virtual void run();
void sleep(rt_uint32_t time);
};
static Thread *test_critical;
static Thread *first_tid1;
static Son_Thread *second_tid2;
static rt_uint32_t counter;
void Son_Thread::run()
{
while(1)
{
counter++;
rt_thread_mdelay(5);
}
}
void Son_Thread::sleep(rt_uint32_t time)
{
rt_thread_mdelay(time);
}
static void increment(void *param)
{
while (1)
{
(*(rt_uint32_t *)param)++;
rt_thread_delay(5);
}
}
static void increment_sleep(void *param)
{
while (1)
{
(*(rt_uint32_t *)param)++;
test_critical->sleep(5);
}
}
static void increment_wait(void *param)
{
while (1)
{
(*(rt_uint32_t *)param)++;
rt_thread_delay(5);
}
}
static void test_critical_fun(void (*entry)(void *p),
void *p,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick,
const char *name)
{
if(stack_size == 0 && priority == 0 && tick == 0 && name == RT_NULL)
test_critical = new Thread(entry, p); /* default parameter */
else
test_critical = new Thread(entry, p, stack_size, priority, tick, name);
counter = 0;
test_critical->start();
test_critical->wait(10);
delete test_critical;
uassert_true(counter > 0);
counter = 0;
return ;
}
static void first_constructor_test()
{
second_tid2 = new Son_Thread();
counter = 0;
second_tid2->start();
second_tid2->sleep(10);
delete second_tid2;
uassert_true(counter > 0);
counter = 0;
return ;
}
static void second_constructor_test()
{
first_tid1 = new Thread(increment_sleep, &counter);
counter = 0;
first_tid1->start();
first_tid1->wait(10);
delete first_tid1;
uassert_true(counter > 0);
counter = 0;
return;
}
static void test_function()
{
test_critical_fun(increment, &counter, 0, 0, 0, RT_NULL);
test_critical_fun(increment, &counter, 1024, 10, 5, "test1");
test_critical_fun(increment_sleep, &counter, 1024, 0, 5, "test2");
test_critical_fun(increment_wait, &counter, 1024, 10, 0, "test3");
first_constructor_test();
second_constructor_test();
return;
}
附录三 std::cout
#include
#include
#include
#include
void io_test(void)
{
std::string s = "Test String";
fprintf(stdout, "%s\n", s.c_str());
std::cout << s << std::endl;
}
MSH_CMD_EXPORT(io_test, C++ test I/O);