STM32 C++编程系列三:重定向标准输出流std::cout

一、问题背景

学过C++的同学往往都是从这么一段代码开始的:
STM32 C++编程系列三:重定向标准输出流std::cout_第1张图片
在Windows或者Linux平台,不需要任何配置即可直接在屏幕上显示出"Hello World!"一行字。原因是std::cout映射到标准输出流stdout文件上,在PC机上,标准输出流默认连接到调用的终端上,因此运行程序时所有经std::cout输出的字符串均显示到屏幕上。而在keil工程上,对stdout并没有直接定义,因此,在我们的keil工程中,如果直接使用cout,会出现这个现象:
在这里插入图片描述
运行到此处进入断点。在之前的文章里提过,显示BKPT 0xAB即为进入到半主机模式,当时的做法是将选项选择成ITM模式来屏蔽该断点。然而,现在我们希望从单片机的通信口将映射为标准输入输出流,因此就得直面该问题,实现stdout功能。实现方法如下。

二、实现方法

1、将输出流设置成User。在Linux中,stderr和stdout的区别在于是否有缓冲区。这里我们将stderr和stdout都勾选上,并选择User。
STM32 C++编程系列三:重定向标准输出流std::cout_第2张图片
此时再编译工程,会发现工程中多了一个retarget_io.c文件,并且在链接的时候会报错:

在这里插入图片描述
产生该错误的原因很简单:勾选了User代表用户需要自行重定义两个流,所以下一步就是编写代码实现这两个函数。

二、实现标准流

话不多说,在这里贴上实现代码:

array<uint8_t, 1024> arrayBuffer;
int bufNum = 0;
//标准输出流
int stdout_putchar(int ch)
{
    uint8_t res = *(uint8_t *)&ch;
    if (bufNum >= arrayBuffer.size())
    {
        bufNum = 0;
    }
    arrayBuffer[bufNum] = res;
    bufNum++;
    if (res != 0x0A && bufNum < arrayBuffer.size())
    {
        return ch;
    }
    vPortEnterCritical();
    sendto(0, arrayBuffer.data(), bufNum, UPLOAD_IP, SHELL_PORT);//发送函数,可自行替换
    vPortExitCritical();
    bufNum = 0;
    return ch;
}

//标准错误流
int stderr_putchar(int ch)
{
    vPortEnterCritical();
    sendto(0, (uint8_t *)&ch, 1, UPLOAD_IP, SHELL_PORT);//发送函数,可自行替换
    vPortExitCritical();
    return ch;
}

由于我们是在.cpp文件里实现这两个函数的,因此还需再.h文件中添加导出:

#ifdef __cplusplus
extern "C"
{
#endif
    int stdout_putchar(int ch);
    int stderr_putchar(int ch);
#ifdef __cplusplus
}
#endif

代码解释:
由于开发时使用的UDP进行输出,而使用UDP进行单字节发送对于单片机还是以太网而言过于浪费,因此在此处直接设计了1024个字节的缓冲区来接收std::cout传来的字符。当调用std::cout时,准备输出的字符存入缓冲区中,当检测到std::endl(0x0A)或接收到的字符数量大于等于1024时,将缓冲区中的字节一并发送出去;stderr作为标准错误流,当出现异常时发送,因此不设计缓冲区提高响应速度。

三、测试代码如下

STM32 C++编程系列三:重定向标准输出流std::cout_第3张图片

实际效果如下:
STM32 C++编程系列三:重定向标准输出流std::cout_第4张图片

四、性能负担

由于keil中的microlib库不支持C++,在使用这个方法的时候不能勾选使用microlib,因此工程中实际使用的是未针对单片机优化过的标准流模块。正常的标准流模块对单片机工程的代码尺寸影响相对比较大,因此不建议在flash和RAM比较小的芯片上使用std::cout函数。在我的工程中可以看到代码尺寸变化如下:

使用std::cout之前STM32 C++编程系列三:重定向标准输出流std::cout_第5张图片

使用std::cout之后
在这里插入图片描述
可以看到代码段大小急剧地变大了,且调用过程中需要更大的函数栈来完成操作,因此仅适合大容量芯片调用。

五、最后

没有std::cout的C++编程体验是不完整的,加入了即可方便快捷地输出各种格式化后的字符串至调试助手,并可以使用来控制输出格式达到美观的效果,因此,std::cout作为设备调试记录输出还是有一定适用性的。然而庞大的代码体积对于小存储尺寸的单片机来说是个灾难,因此还需要开发者自行判断,酌情使用。

std::cin?

由于标准输入流需要阻塞来完成单字节输入,因而对于单片机系统来说,无论是使用串口或是网口,实现起来都比较困难,所以这里暂不考虑std::cin的实现方法。如果有比较好的标准输入流的实现方法,欢迎留言告诉我,不胜感激。

你可能感兴趣的:(STM32,C++,stm32,单片机,c++)