OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 16105|回复: 21

MDK 开发Stm32 上使用 C++ cout 对象的实现

[复制链接]

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
发表于 2012-11-14 12:53:34 | 显示全部楼层 |阅读模式

MDK 开发Stm32 上使用 C++ cout 对象的实现fficeffice" />

--Pony279

1. Retargeting non-semihosting

为了实现 cout,需要加入了一段代码,在这段代码里重新实现了 fputc, ferror, fseek, ftell 等底层函数。在 mdk 的帮助文档的 Libraries and Floating Point Support Guide: Redefining low-level library functions to enable direct use of high-level library functions 一节中,也有类似的代码,读者可以自行参考。

因为这些底层函数原来默认的实现使用了半主机模式(其实我不知道半主机模式是什么),如果在 STM32 上使用了半主机模式的相关指令,就会直接导致死机,为了保证程序没有使用半主机模式,我加入了一句

#pragma import(__use_no_semihosting_swi)

这样,只要程序里使用了任何半主机模式相关的指令,链接器都会产生错误了。这个在 MDK 的帮助文档里面也有相关说明,可以参考 Libraries and Floating Point Support Guide: Using the libraries in a nonsemihosting environment

  以下是需要加入的代码:

//////////////////////////////////////////////////////////////////

// Retargeting cout and printf

#if 1

 

#include <stdio.h>

#include <stdlib.h>

 

#pragma import(__use_no_semihosting_swi)

 

namespace std{

   

    struct __FILE

    {

        int handle;

 

        /* Whatever you require here. If the only file you are using is */

        /* standard output using printf() for debugging, no file handling */

        /* is required. */

    };

 

    FILE __stdout;

    FILE __stdin;

    FILE __stderr;

   

    // You might also have to re-implement fopen() and related functions

    // if you define your own version of __FILE

    // 有兴趣的可以定义自己的 __FILE,实现自己的 fopenfwrite, fread

  // 因为库里面的 fopen 是弱符号,你的定义可以覆盖库的定义

FILE *fopen(const char * __restrict /*filename*/,

                           const char * __restrict /*mode*/)

    {

        usart1<<"\n\r fopen. \n\r";

        return NULL;

    }

 

    //-----------------------------------------------------------

    int fputc(int ch, std::FILE *f) {

        return sendchar(ch);

    }

   

    //-----------------------------------------------------------

    int fgetc(FILE *f) {

        /* Your implementation of fgetc(). */

        usart1<<"\n\r fgetc \n\r";

        return 0;

    }

   

    /*

  检查是流否有错误,如果没有错误,返回 0

    */

    int ferror(FILE *stream)

    {

        /* Your implementation of ferror(). */

        return 0;

    }

 

    long int ftell(FILE *stream){

        /* Your implementation of ftell(). */

        usart1<<"ftell\n\r";

        return 0;

    }

   

    int fclose(FILE *f){

        /* Your implementation of fclose(). */

        usart1<<"\n\r fclose \n\r";

        return 0;

    }

   

    int fseek(FILE *f, long nPos, int nMode){

        /* Your implementation of fseek(). */

        usart1<<"fseek\n\r";

        return 0;

    }

   

    /*

  对于输出流,把缓冲区中的内容全部发送出去

  这里用的是自己定义的 FILE 结构体,没有缓冲区,

  所以什么都不做就行了。

    */

    int fflush(FILE *f){

        /* Your implementation of fflush(). */

        return 0;

    }

 

   

    /*

默认的 _sys_exit 使用了 semihosted calls

  所以必须重新实现它。

  declared in <rt_sys.h>    */

    ARMAPI void  _sys_exit(int) {

        /* declared in <stdlib.h> */

        abort();

        while(1);

    }

 

    /*

这个函数是C/C++标准库用来打印必要的调试信息的

最好重新实现它,把调试信息发送到串口。

  this function is declared in <rt_sys.h>    */

    ARMAPI void _ttywrch(int ch) {

        sendchar(ch);

        return ;

    }

   

}

 

#endif

//end

//////////////////////////////////////////////////////////////////

注:

1. 这里面的函数是在我自己的代码环境下的实现方式,如果你的是C语言环境或者你自己的函数库里没有 usart1<<... 这一类的调用,需要自己做一下修改

2. 有一些函数,如 ftell ,我并没有期望它会被调用,所以我直接在函数里面输出串口信息,以免被错误调用了我还被蒙在鼓里。

 

还有测试代码,相当简单哦

#include <iostream>

 

using std::cout;

using std::endl;

int main(void) {

 

    cout<<"hello world!"<<endl;

   

    while(1);

}

注:测试代码虽然简单,但是这这些代码执行之前需要做一些基本的硬件初始化,如系统时钟,串口等。(如果你使用了 ST 公司提供的 STM32 启动代码 V3.5 版本)这部分初始化可以在 SystemInit 函数中完成,SystemInit Stm32 刚启动时,在调用 __main 之前所调用的硬件初始化函数。

 

代码编译通过,但是在程序运行的时候打印出

SIGABRT: Abnormal termination

然后就死机了,(不过不要太悲伤,幸好自己重实现了 __ttywrch 这个错误信息输出函数!)我并不了解SIGABRT 是什么,但在 mdk 的帮助文档是找到相关的说明:


也许是堆的大小不够吧,原来的定义是 0x400,那么把大小改成了 0x800 试试看:


编译,然后仿真运行正常了!到这里,基本工作已经完成了。在接下来的两节将描述我遇到的特殊情况,感兴趣的同学可以继续看下去。

2.下载后不能正常运行的奇怪现象

我把代码下载到开发板上运行,结果还是死机,而且是和之前一样的信息!做到这里我不得不吐槽 ARM 写的C/C++底层库了!“SIGABRT: Abnormal termination”算是什么!尼玛谁会看不出来系统死机了!!!尼玛就不能输出点有用的信息么!

唉,还是再看看帮助文档吧,我找到了

现在的代码里暂时还没有使用到 Exception Handliing 这个诱人的特性,所以是否重新实现 abort() 都无所谓的。还是写一个吧,随便输出点调试信息也好。

extern "C"

__attribute((weak))

 void  abort(){

    output<<"abort()"<<endl;

    while(1);

}

这样做完之后没什么特别的事情发生,只不过原来输出的错误信息 “SIGABRT: Abnormal termination” 现在变成了自己写的 abort 函数输出的 “abort()”然后再死机而已。

我在继续编译下载调试的过程中,发现代码有那么一两次是可以正常运行的,不过后来我又发现,当我断电,再上电后,又不正常了!后来我一直看文档,把大部分底层函数都重实现了,结果还是不行。想上 MDK 论坛去发帖请教,结果大神都没有回复,唉,看来还是只能靠自己啊。

这个现象说明软件仿真和实际运行一定存在着一些区别,虽然目前的代码没有使用多少硬件资源。经过思考和猜测,最后终于发现,原来是因为,我在工程里设置了 noinit(以前手贱设置的= =),所以上电时在 SRAM 中的那些没有声明初始值的全局变量不会被清零(刚上电时 SRAM 中的数据都是不确定的)。而软件仿真时,模拟的 SRAM 中的所有内容都已经事先被软件清零了。(下图中IRAM1右边的 NoInit 对应的那个框框本来有个我打的勾勾的,现在我把它去掉了)


去掉 noinit
,再编译下载,cout 成功执行了!!!也许这应该归为 MDK 使用的 C/C++ 库的 BUG 吧,为啥一定要初始化清零呢?何必呢?就算这个问题不归为 C/C++ 库的 BUG,那么就应该归为 MDK 软件仿真的 BUG 了。

 

3. 其他问题

其实到这里,主要问题都已经解决了。不过我还遇到了其他问题,想和大家分享一下经验技巧,继续看吧

1)部分 flash 写坏了 L

我继续下载调试程序的时候发现编程软件提示我 flash 45k的时候写坏了(在调试过程中我把优化改到了最低,所以代码膨胀到 45K以上了,使用 C++ 的库对 STM32 来说还是挺臃肿的啊)。不过换芯片神马的会比较苦逼,最关键的是现在是凌晨 2 L,上哪找 STM32去。于是我找到了一个比较可行的办法,让链接器在放代码时避开那个位置,还好能用 Y^_^Y


2)
隐藏基本的软硬件初始化

另外,作为一个工程模板,我希望一些基本的硬件初始化和软件初始化代码应该在用户的程序执行之前进行。这样做可以事先建立一个基本的执行环境,对用户隐藏一些不必要的细节,例如我的测试代码里面,主函数就一句输出 “hello world!”,没有任何硬件初始化的代码。这样就方便了用户代码的编写,也让程序的结构变得更加直观。但是这样做就需要保证 __main 中进行 C/C++ 运行时库初始化的时候不可以把我已经初始化过一些全局变量清零。由于又不能 noinit,所以我选择了在 _clock_init 里进行初始化。_clock_init C语言标准库里的函数,它是在清零全局变量之后,初始化用户定义的全局变量之前进行的,而且这个函数允许用户对其进行重新实现。其实我只是借个地方初始化而已。

/*由于不能使用 no_init,而 sys 对象又必须在所有对象之前初始化

  _clock_init 这里是个初始化的好地方    */

ARMAPI void _clock_init(){

    sys.Init();

    return ;

}

 

到这里,使用 cout 的全部工作就完成了!!!

4. 测试结果

最后程序输出的信息很简单,就是 Hello world


注:前三行是自己在系统初始化时加的调试信息,且系统时延迟 1s 启动的,让开发板上 PA8 对应的红色 LED 也亮 1s, 以示意系统启动,这样在下载完程序后串口调试助手才有足够的时间打开串口

 

又让我回想起当初入门 C++ 的感觉了呢 Y^_^Y

 

5. 继续拓展?

  既然 cout 能实现了,那么 cin 当然也是能够实现的,只要你实现好 fgetc 这个函数就行了。而且,有兴趣的同学还可以把 Fatfs 中的文件系统函数和 C 语言标准库的函数结合起来,还有还有,可以把 RTC <time.h> 的相关函数结合起来,在 STM32 上完善 C/C++ 标准库的使用!还等什么?有兴趣就自己写代码试试吧!

 

 

stm32_cppTest-20121116 - C++ cout 的实现测试.zip

2.28 MB, 下载次数: 1603

MDK 开发Stm32 上使用 C++ cout 对象的实现.pdf

221.51 KB, 下载次数: 1355

https://github.com/roxma
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 13:04:11 | 显示全部楼层
ARM 公司写的底层库真有点让人恼火,虽然上面的问题解决了,但是有些只是碰巧怀疑到了,比如之间堆空间不足的,一点提示都没有,只在输出了个 SIGABRT: Abnormal termination 这样的信息,而且修改了堆空间后下载到板子上,还是一样的问题。。。
真是无语,丫的谁看不出来程序死机了!就输出这点调试信息,太坑爹了!我昨晚可是调到 3 点的还要带着遗憾入睡的啊!!!
https://github.com/roxma
回复 支持 1 反对 0

使用道具 举报

46

主题

329

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1544
金钱
1544
注册时间
2012-4-9
在线时间
80 小时
发表于 2012-11-14 13:07:19 | 显示全部楼层
顶楼主的研究精神!
STM32
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 13:25:26 | 显示全部楼层
回复【3楼】 jj123paopao :
---------------------------------
谢谢支持

------------------------------------------------------------------------
原来已经有回复提示了,论坛一直在努力啊
https://github.com/roxma
回复 支持 反对

使用道具 举报

38

主题

527

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1424
金钱
1424
注册时间
2011-11-27
在线时间
122 小时
发表于 2012-11-14 13:53:17 | 显示全部楼层
楼主加油!!
永远保持一颗学习的心态。
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 14:04:05 | 显示全部楼层
囧,又有问题了。。。好像意外修改了一段代码,又回到了只能软件仿真,板子上运行不正常的状态了。。。忘记备份了。。。惨。。。
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 14:31:09 | 显示全部楼层
不知怎么又正常了
去掉了上面说的

EXPORT __user_setup_stackheap
__user_setup_stackheap

这两行,发现也可以用,干脆就去掉吧,只能作为参考了,也许真的存在其他问题自己都没搞清楚有人知道的给我指点一下

stm32_cppTest.zip

2.32 MB, 下载次数: 396

https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 15:14:22 | 显示全部楼层
下载下去正常,断电,重启,然后就不正常了。。。。
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-14 18:31:07 | 显示全部楼层

折腾了这么久,还是没解决

就是软件仿真正常,有时候下载进去也正常,然后断电再上电,就跳进 abort 函数里了(在还没进入 main 函数的C/C++ 语言环境初始化的过程中)。。。真不知该怎么解释这种神奇的现象了,最关键的是没有任何相关的调试信息,
真受不了了,下午翘了三节课,明天早上还要交两科的作业,下午还有个实验。。。真没那么多时间来折腾,又得搁置一段时间了

搜到一本 STM32 的书上讲 cout 的,不过好像用的不是 MDK 。。。

https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-16 15:04:07 | 显示全部楼层
问题解决!
花了两个多小时整理了文档献给大家,放在楼主了
https://github.com/roxma
回复 支持 反对

使用道具 举报

108

主题

1433

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2234
金钱
2234
注册时间
2012-4-30
在线时间
7 小时
发表于 2012-11-16 15:43:35 | 显示全部楼层
楼主的东西是好东西啊,但是下载的人好少。
肯定是很多人都不知道楼主在说些什么~~~~包括我。

楼主威武,在下只能为你加油助威了。
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-16 18:00:05 | 显示全部楼层
回复【11楼】lsj9383:
---------------------------------

其实这个和 C 环境下的 printf 的实现差不多的,只是还有两点需要注意的问题罢了

以前一直郁闷百度不到 STM32 用 c++ 的 cout 的,只好自己动手了。

以前本来想自己实现的,但有时候觉得自己写代码苦太繁琐了,真没那么多时间,能搬标准库里的现成的东西来用就最好了,代码的可移植性也会好很多,而且也不用读代码的人重新去了解一个库。
https://github.com/roxma
回复 支持 反对

使用道具 举报

108

主题

1433

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2234
金钱
2234
注册时间
2012-4-30
在线时间
7 小时
发表于 2012-11-16 18:32:44 | 显示全部楼层
回复【12楼】Pony279:
---------------------------------
师兄,我真心感觉你编程很厉害。

我才接触编程1年的时间...╮(╯▽╰)╭ 还有好长的路要走~
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-16 22:01:30 | 显示全部楼层

再发个使用 cin 和 string 测试的例子。
// 测试代码
#include <iostream>
#include <string.h>

using namespace std;
int main(void) {

    cout<<"hello world!"<<endl;
    std::string s;
 int i;
 while(1){
  cout<<"please enter a character shtring :";
  cin>>s;
  cout<<endl
   <<"received :"<<s<<endl;
  
  cout<<"please enter an integer :";
  cin>>i;
  cout<<endl
   <<"The integer is "<<dec<<i
   <<"    hex form :"<<hex<<i;
  
  cout<<endl<<endl;
 }
}

直接用串口调试助手发送数据就可以,但是需要在后面发多一个空格或者回车例如




    以前用过 SecureCRT,有点类似串口调试助手,它可以直接在编辑框上键入字符,软件会自动把数据从串口发送出去。但是我在 Mini  板上用 SecureCRT 的时候发现它会对自动下载电路产生影响。
    然后改了一下我在一年前写超简陋的串口调试助手的代码,发现可以用,不过存在的问题比较多,连中文都不能显示,使用不方便代码也比较丑陋。。。当时只是写来练手的。。。
    代码里连删除这一类的按键也没有做特殊处理,不过改下代码拉出来体验一下命令行的感觉还是可以的



 

Serial.zip

2.11 MB, 下载次数: 512

stm32_cppTest - 20121116 - cin cout.zip

2.34 MB, 下载次数: 597

https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-17 01:10:28 | 显示全部楼层
"也许这应该归为 MDK 使用的 C/C++ 库的 BUG 吧,为啥一定要初始化清零呢?何必呢?就算这个问题不归为 C/C++ 库的 BUG,那么就应该归为 MDK 软件仿真的 BUG 了。"

这个还是应该算是 "NoInit" 的设置错了,毕竟 C++ 标准上有说到 "Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place" ...
库本身的编写是符合标准的,不过 "NoInit" 这个设置却破坏了标准的前提条件,所以导致了 C/C++ 库后来的初始化的失败。
https://github.com/roxma
回复 支持 反对

使用道具 举报

1

主题

25

帖子

0

精华

新手上路

积分
49
金钱
49
注册时间
2012-9-21
在线时间
0 小时
发表于 2012-11-17 13:35:23 | 显示全部楼层
Pony279 的C++系列真心不错 ,感激不尽呀。有很大的研究意义
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-11-17 14:29:56 | 显示全部楼层
回复【16楼】绝无神:
---------------------------------
谢谢支持!
https://github.com/roxma
回复 支持 反对

使用道具 举报

0

主题

17

帖子

0

精华

新手上路

积分
37
金钱
37
注册时间
2014-3-18
在线时间
0 小时
发表于 2014-3-18 10:42:44 | 显示全部楼层
这个有用
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
21
金钱
21
注册时间
2015-2-7
在线时间
0 小时
发表于 2015-2-7 22:48:11 | 显示全部楼层

厉害

不过试了下,好像cout,cin空间占用很大,

半主机模式不太懂,不知道它里面到底加了什么料,

正考虑自己实现printf()函数

http://blog.csdn.net/wisepragma
回复 支持 反对

使用道具 举报

2

主题

6

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2016-2-13
在线时间
4 小时
发表于 2016-2-18 07:57:35 | 显示全部楼层
顶起!学习了!
回复 支持 反对

使用道具 举报

30

主题

1170

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1993
金钱
1993
注册时间
2016-2-16
在线时间
527 小时
发表于 2016-11-7 15:55:00 | 显示全部楼层
楼主威武霸气。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
发表于 2018-10-5 21:00:37 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-10-5 21:02 编辑

谢谢楼主多年前的分享,我在多年后都能收到楼主的阳光。
我刚想有必要在keil中使用C++编程,结果出现了很多问题,也看了很多帖子,直到接受到楼主的分享才往前拱了一步。
楼主使用的是103RB,我使用的是103ZE,结果可用啊。
我学着把Heap_Size 设置为0x1000后__main可以执行了,就是现在还不能实现入楼主一般的cout和cin.

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-7-8 00:35

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表