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

发布者:MindfulYogi最新更新时间:2018-10-13 来源: eefocus关键字:MDK开发  Stm32  C++开发 手机看文章 扫描二维码
随时随地手机看文章

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,实现自己的 fopen,fwrite, 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  

        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!"<

   

    while(1);

}

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

 

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

SIGABRT: Abnormal termination

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

 

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

 


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

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

 


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

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

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

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

 

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

 

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

extern "C"

__attribute((weak))

 void  abort(){

    output<<"abort()"<

    while(1);

}

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

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

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

 

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


去掉 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

 

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

 


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 啦

 

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

 


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

 

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

 

5. 继续拓展?

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

 


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

using namespace std;
int main(void) {

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

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

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




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

 

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

 



 

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

==============================================================

Keil for ARM与C++

1. 如果你的程序中使用了C++全局变量,那么*不要*使用MicroLIB,否则Keil会说某某Symbol找不到

2. 不使用MicroLIB带来的一个问题是KEIL会使用semihosting SWI完成sys_io(例如printf的时候),我们需要一个retarget.c来禁止semihosting。KEIL提供该文件的模版(包括最小版和完全版,见下文),改改就是了

3. retarget.c也有最小版和完全版;最小版除实现fputc及辅助函数用于printf外,只实现了sys_io中的_sys_close;完全版还实现了_sys_open,_sys_read,_sys_write,等等。如果实现了sys_io中除_sys_close以外的任意一个,那么就必须同时实现其他函数。即,要么最小版,要么完全版,不存在中间版。当使用C++标准库时可能需要完全版:例如使用complex template时就必须使用完全版的retarget.c,因为complex class实现了“<<”和“>>”运算符重载,需要_sys_open等函数。当然我们一般不需要完整的函数内容,只要让编译器看到函数定义就行了。

4. 使用new和delete:参考帮助文件的Libraries and floating point support guide -> The ARM C and C++ libraries -> Stack pointer initialization and heap bounds

    一般来说KEIL或厂家(如ST)提供的启动文件已包含该项支持;将汇编启动文件中的heap size项改改就好了。

5. 顺便说下如何动态控制printf的精度(同样用于snprintf,etc):使用%*控制。例如printf("%.*f ", 2, 1.234)

6. 科学记数法打印:%e

==============================================================

如何在MDK中使用C++,整理的经验

一:C++引用C文件

         注意:C++文件能引用C文件,但是C文件不能引用C++文件

         1:在C头文件中加上extern修饰符:

1.  #ifdef __cplusplus

2.  extern "C"  {

3.  #endif

4.   

5.   

6.  。。。。。。。。。。。。。这里写c语言代码

7.   

8.   

9.  #ifdef __cplusplus

10. }

11. #endif


         这样在使用C++调用时就使用C++编译器编译,c语言调用时就是用c语言方式编译

         2:编写一个C++风格的头文件,在这里添加extern修饰符:

                   (一般用在调用已经封装好的库文件或者无法或不想修改.c文件所引用的头文件时)

1.  // CStack.h  

2.  extern "C" {  

3.  #include "Stack.h";  

4.  }  


或者是直接在需要引用c头文件的cpp文件中

1.  // .cpp

2.  extern "C" {  

3.  #include "delay.h";  

4.  }  


二:在新建cpp文件之后,MDK可能会把它识别为image文件

  现象如下:

此处这个iic.cpp便被识别成了image文件,main.cpp则是正常的。对它单机右键,选择option for file ”iic.cpp”即可查看。

         解决:将File Type 修改为C++ sourcefile 即可。

三:C++中相对于C独有的new以及堆地址设置及内存分配问题。

                            (当然,如果不必使用new功能,则可以不分配堆空间)

         这个new 就是从堆里取一块内存空间,并执行类的构造函数。

     那么这里就要涉及到堆的问题,你必须得告诉程序一个问题:

                            (1)堆在哪里?(2)堆有多大?

       MDK中主要使用_init_alloc( startAddr, endAddr  );(rt_heap.h中声明)来设置堆的地址

1:定义数值作为堆空间

              Eg:

1.           #defineHEAP_SIZE   (0x3000)   

2.       u32 heap_zone[HEAP_SIZE] = { 0 };


              然后初始化时调用  

3.       _init_alloc((u32)heap_zone,(u32)&heap_zone[HEAP_SIZE - 1]);


    来指定堆的起始和结束地址。

2:扩展SRAM空间来作为堆空间

4.  #define HEAP_BASE  Bank1_SRAM3_ADDR  

5.  #define HEAP_TOP   (Bank1_SRAM3_ADDR+0x100000)  


然后初始化

1.  FSMC_SRAM_Init();  //初始化对SRAM的访问  

2.  _init_alloc(HEAP_BASE, HEAP_TOP);    // 设置堆空间


注: MDK 版本:      keil μvision5.11.1.0

         编写时使用系统: Windows 10

         测试时使用芯片: STMA32f103ZET6

整理者:Neucrack

参考资料:

1:在MDK上建立一个C++的STM32开发工程


2:C++调用C函数

=============================================================

使用C++封装库在MDK上建STM32开发工程

直接上一些片上硬件、一些常用器件的封装库GitHub链接: 
STM32F4:https://github.com/InfiniteYuan1/STM32F407DriverLib 
STM32F1:https://github.com/InfiniteYuan1/STM32f103DriverLib

用C++进行嵌入式程序开发,甚至是裸系统程序。开发起来比C语言更方便架构搭建与程序管理。

  1. 简单的一个例子使用USART

#include "USART.h"
USART com(1,115200);void main()
{
    com<<"test string\r\n";    while(1)
    {

    }
}
  1. 使用片外资源的一个例子

#include "USART.h"#include "DPPrint.h"int main(void)
{    USART com(USART1, 115200);
    DPPrint DP(com);
    DP.InitializePrint();
    DP.PrintPageStart(0,0,384,640,0);
    DP.rectangleDraw(0,0,384,576,5,1);    while(1)
    {

    }
}
extern "C"{    void USART1_IRQHandler(void)    //----USART1 IRQ----//
    {    #ifdef USE_GPS
        if(pGPS1){
            pGPS1->GPS_IRQHandler();            return ;
        }    #endif
    #ifdef USE_USART1
        pCOM1->IRQ();    #endif
    #ifdef USE_USART1_DMA
        pCOM1->IRQ();    #endif
    }
}
  • 首先声明,这个main()所在的文件必须是以.cpp文件,不然编译当成C语言进行编译。C++可以调用C,C不能调用C++,这点要记住。

  • extern “C” { } 用于告诉编译器,大括号以内的东西以C的方式进行编译。因为原来.C的头文件被.CPP文件包含了,那么编译器便按C++的方式进行编译,导致与原来.C文件中定义的不一致,编译报错。


关键字:MDK开发  Stm32  C++开发 引用地址:MDK开发Stm32上使用C++开发的实现

上一篇:Tiny210裸机之UART串口操作
下一篇:stm32如何改变PC(R15)的值?

推荐阅读最新更新时间:2024-03-16 16:16

解决STM32 不能读RAM 下载的问题
制作了两个ARM-OB下载器,原以为会很快搞定,谁知道,在最后一步出现了如下的问题: ERROR: RAM check failed @ address 0x20000000. - ERROR: Write: 0xE7FEBE00 E07CE062 - ERROR: Read: 0x0000000 000000000 - ERROR: (0 bytes of RAM have been checked successfully) - ERROR: Failed to read back target memory 我感觉好奇怪啊,三个月前我制作ARM-OB下载器的时候还没有这个问题出现,为什么呢? 百度该问
[单片机]
STM32之DRV8834电机驱动(PWM方式)
一、简介 本文介绍如何使用STM32通过DRV8834控制步进电机,使用PWM方式控制转动。 二、实验平台 库版本:STM32F10x_StdPeriph_Lib_V3.5.0 编译软件:MDK4.53 硬件平台:STM32开发板(主芯片stm32f103c8t6) 仿真器:JLINK 三、版权声明 四、实验前提 1、在进行本文步骤前,请先阅读以下博文: 1)《STM32之DRV8834电机驱动(IO方式)》:http://blog.csdn.net/feilusia/article/details/52853951 2、在进行本文步骤前,请先实现以下博文: 1)《STM32之系统滴答定时器》:
[单片机]
<font color='red'>STM32</font>之DRV8834电机驱动(PWM方式)
STM32 之二 HAL库详解 及 手动移植
HAL库结构   说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!首先看一下,官方给出的HAL库的包含结构: - **stm32f2xx.h**主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件: #if defined(STM32F205xx) #include stm32f205xx.h #elif defined(STM32F215xx) #include stm32f215xx
[单片机]
基于STM32与机智云的智能蜡疗机
摘要: 随着物理疗法在慢病治疗中的效果获得认可, 传统的石蜡疗法被广泛应用在医院的理疗科。目前, 蜡疗机的制蜡方法虽已趋于成熟, 但仍存在制蜡效率低下、功耗大、不够智能等问题, 因此有必要对蜡疗机的制蜡效率及智能化程度进行研究。以STM32微控制器为核心, 结合G510通信模块, 对蜡疗机的硬件系统进行优化设计;通过机智云物联网开发技术, 对蜡疗机进行控制和通信。 试验结果表明, 与现有产品相比, 该系统减少了制蜡时间、降低了蜡疗机的功耗, 实现了蜡疗机的物联网控制。该研究为进一步完善蜡疗机的功效提供了设备基础, 对蜡疗机的广泛应用具有积极促进作用, 对今后利用石蜡疗法进行理疗大数据的挖掘与应用具有重要意义。 0 引言 经
[单片机]
基于<font color='red'>STM32</font>与机智云的智能蜡疗机
实用STM32的串口控制平台的实现
1. 前言 玩过Linux的朋友, 是不是对Linux无所不能的串口Shell命令控制台羡慕不已, 要是自己做的STM32F系列低档次的MCU也有这种控制交互能力, 会给调试/维护和配置省下多少麻烦事呀, 比如启动/关闭调试或自检模式, 打印调试信息, 配置系统参数, 传输文件等等, 也有相当多的朋友凭借自己出色的编程能力可以实现这些功能, 这里提出我的这个解决方案, 以作交流. 本平台(xc_shell)具备以下性能特点: 1) 大量主要代码, 和具体硬件无关, 移植性强,代码文件少. 2) 只有在处理用户的输入命令时, 才占用CPU资源, 且代码可裁剪到1KB SRAM和4KB Flash; 3) 用户可以非常灵活的添加按
[单片机]
实用<font color='red'>STM32</font>的串口控制平台的实现
STM32的复用功能——时钟输出(MCO)
STM32的PA.8引脚具有复用功能——时钟输出(MCO), 该功能能将STM32内部的时钟通过PA.8输出. 操作流程: 1)、设置PA.8为复用Push-Pull模式。 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); 2)、选择输出时钟源。 时钟的选择由时钟配置寄存器(RCC_CFGR)中的MC
[单片机]
STM32高级开发(17)-使用DFU方案
什么是 DFU DFU全称为Device Firmware update,是ST官方推出的一个通过USB接口进行IAP升级的方案,同串口ISP一样,他们都集成在了芯片内部的Bootloader区段,可以通过配置boot引脚来启动。(具体可参照ST文档:AN2606)。不过内置DFU的芯片大部分型号都比较新,如果你用的型号没有内置DFU程序,没关系我们也可以通过CubeMX来快速生成和移植一个DFU功能程序到你的Flash中来使用。 DFU方案完整的组件包括单片机DFU Demo代码、PC端升级程序、PC端Demo代码以及相关资料手册等。通过使用DFU方案,我们可以快速的集成升级功能到开发的产品中,同时还能够快速的开发与之配套的
[单片机]
<font color='red'>STM32</font>高级<font color='red'>开发</font>(17)-使用DFU方案
关于STM32的USART_GetFlagStatus和USART_GetITStatus解析
前言 STM32固件库中提供了串口收发的标志位函数,包括USART_GetFlagStatus(…,…);和USART_GetITStatus(…,…);,两者容易混淆,重点区别就在于:前者返回值是中断标志位状态(读SR寄存器),后者返回值是中断发生与否的判断(读CR寄存器),以下主要对这两个函数进行分析。 一、USART_GETFlagStatus(…,…) /** * @brief Checks whether the specified USART flag is set or not. * @param USARTx: Select the USART or the UART peripheral. * Th
[单片机]
关于<font color='red'>STM32</font>的USART_GetFlagStatus和USART_GetITStatus解析
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved