STM32 C++编程系列2.5:让Keil MDK工程支持现代C++特性及填坑

一、问题背景

利用STM32CubeMX建立的Keil工程中,默认是使用AC5(Arm Compiler 5)编译器的,该编译器仅支持C99标准和C++98标准,无法支持现代C++(C++11之后由于添加了大量现代编程语言特性而被称作现代C++),且AC5编译速度的慢也是有目共睹的。诸多因素之下,我们选用更新更强大的AC6(Arm Compiler 6)来给我们的开发(折腾)上一个新的台阶。毕竟C++11之后出现的大量新特性还是很香的,不在STM32这个平台上大显身手就可惜了。
比如强大的auto关键字:

auto ff = "hello";

比如lambda表达式:

auto fun =[=](int x,int y)->bool{return x%10<y%10;};

再比如基于范围的(range-based)for循环:

map<int,string> index_map{{1,"hello"},{2,"world"},{3,"!"}};
for(const auto &e : index_map)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)e.second.data() , e.second.length() , 0xff);
}

是不是感觉打开新世界的大门了呢?稍稍配置,STM32与现代C++的结合,体验一种新的开发方式。现在我们就来从STM32进入现代C++的世界吧。

二、开启步骤

1、点击在这里插入图片描述进入工程配置对话框,
在这里插入图片描述
将ARM编译器改为编译器6,如图所示
在这里插入图片描述
这时C/C++选项卡会变成C/C++(AC6)
在这里选择语言特性支持
与AC5不同的是,这里的语言选项支持到C11和C++17,因此现代C++的语言特性大部分都可以在上面得到比较好的支持。如果在cubemx中添加了FreeRTOS支持的同学,到这里应该会收到数百个错误的提示。这时只需要将…Middlewares\Third_Party\FreeRTOS\Source\portable里的文件替换为cubemx的firmware库中GCC版本即可正常编译。
到这里开启工程支持就结束了,很简单是不是?
But!
仅仅这样配置完是运行不起来的,或者说运行起来会有各种问题。首先出现的就是一个大家熟知也困扰已久的问题:ARM的半主机模式。

三、解决半主机模式

STM32玩的比较熟的都知道,如果在工程选项中没有勾选上microlib支持又使用到一些和stdio相关的函数时,就会陷入一个很尴尬的问题,怎么debug一上来就停在一个地方进行不下去了?

BKPT 0xAB

当出现这个的时候,就说明你的CPU就陷入了半主机模式。具体半主机模式是什么,很多地方解释了我也就不再赘述。在一般的C程序中,我们可以用两种方式解决这个问题:
一、使用microlib库
二、重定向stdio.h里的方法,并在工程里加上如下代码:

#pragma import(__use_no_semihosting)  // 确保没有从 C 库链接使用半主机的函数
_sys_exit(int  x) //定义 _sys_exit() 以避免使用半主机模式
{
x = x;
}
struct __FILE  // 标准库需要的支持函数
{
int handle;
};
/* FILE is typedef ’ d in stdio.h. */
FILE __stdout;

在网上的各种资料以及正点原子的例程中,我们都可以看到有这一段代码用来解决半主机模式的问题。在传统的C程序当中,这两招是公认的终极解决办法,然而这两招在AC6与C++上就不好使了,会报各种各样的错:

比如引用microlib后报的错:
在这里插入图片描述
再比如加入重定向代码后出的错(不用试图改错,改好了还会有更多的报错等着):
不用试图改错,改好了还会有更多的报错等着
因此,就只能使用另一种办法来解决该问题:使用keil补丁。

Keil补丁

估计是ARM开发团队也意识到这个问题了吧,在后来的版本中可以自动添加进官方版本的重定向代码,在补丁中可以选定是进入中断、进入错误回调函数还是使用用户自定义实现版本,于是就有了下面的方法:

一、点击在这里插入图片描述按钮进入运行时环境配置界面:

在这里插入图片描述

二、做些配置

依次进入Compiler->I/O,将里面的STDERR、STDIN、STDOUT勾选上,并将variant列依次选择如下图所示:
在这里插入图片描述
选择完毕后点保存,编译。这样就能避免陷入半主机模式的中断当中去了。

四、解决动态分配内存问题

再执行完上面的操作之后,再进行编译,则可以编译通过,运行程序,不使用io相关函数的情况下也不会进入半主机模式,若需要使用io相关函数则对fput等函数进行重定向即可使用(直接使用会导致程序进入Error_Handler)。然而新的问题又会挡在我们面前:动态分配内存问题(malloc和free)
我们知道,C++中new运算符的底层实现原理如下:
1、调用malloc函数开辟内存空间
2、调用构造函数生成对象

而delete函数实现原理与之相反:
1、调用析构函数做各种释放处理
2、执行free函数释放内存空间

因此在C++中,动态分配内存决定了一个对象是否能被靠谱的new出来。
然而经过了前面的配置后,由于我们不再使用microlib,因此开发环境在编译时将会连接标准C库的malloc函数和free函数,受限于单片机小的可怜的RAM空间,且没有MMU的帮助下,标准C库的内存分配函数对于单片机来说比较庞大,若是进行new和delete对象,很容易出现状态异常,进入Error_Handler。所以我们需要重定向new和delete操作符。
通常情况下,有两种方法去实现malloc和free:
1、自己设计实现内存池,并实现malloc和free函数(例如正点原子例程里的mymalloc和myfree)
2、用操作系统自带的内存管理功能实现

在这里为了方便介绍实现重定向动态分配过程,以使用操作系统自带的malloc方式来介绍。
我们使用freertos自带的pvPortMalloc()vPortFree() 来替换C标准库的malloc()和free()函数,这样做的好处是当内存被释放的时候会由操作系统来进行回收,具体的内存位置在操作系统申请的HEAP空间中(configTOTAL_HEAP_SIZE ),分配和回收方式由加载到工程中的heap_x.c确定。
在stm32cubemx中,我们将configTOTAL_HEAP_SIZE根据各芯片的RAM空间设置为合适的大小,并利用pvPortMalloc()vPortFree() 来重载new和delete运算符,代码如下:

#if USE_OS
#include <new>
void *operator new(size_t size) throw(std::bad_alloc)
{
    return pvPortMalloc(size);
}

void operator delete(void *pointer) throw()
{
    vPortFree(pointer);
}
#endif

其中,头文件包含了运算符new和delete的函数声明,我们在自己的程序中利用将new和delete运算符进行重载。至此,在程序中我们可以new和delete对象了。

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值