ESP32的IDF实现C语言和C++语言的混合编译

发布于:2025-02-10 ⋅ 阅读:(19) ⋅ 点赞:(0)

        根据官方的文档中的指导ESP32IDF是支持C/C++的混合编译的,虽然整个的IDF是C语言写的,因此我们可以使用c++以提升开发效率(运行效率还是c语言高),但是c++在实现应用层上面还是有它的优势的。本篇文章就描述一下如何实现C/C++的混合编译。

        我的建议是将函数入口变成c++的文件,首先将自己的主函数入口的文件改为自己想要的以.cpp结尾的文件。例如我的文件改为main_app.cpp,可能以前接触KEIL的同学会疑惑为什么我不用叫main.cpp,其实这是不用的编译链接的过程指定源文件就可以了,ESP32IDF使用的是CMake进行组织编译,因此我们只需要修改CMakeLists.txt文件即可。

 接下来修改main文件夹下的CMakeLists.txt文件

 Cmake的源码如下:(我们只需要修改第一行的 " "中的内容即可) SRCS就是源码位置 INCLUDE_DIRS就是头文件的路径。

idf_component_register(SRCS "main_app.cpp"
                    INCLUDE_DIRS ".")

target_compile_options(${COMPONENT_LIB} PRIVATE -std=gnu++11)

 ok,那我们继续开始下一步操作来到我们的主函数所在的位置:main_app.cpp文件中

此时编译铁铁报错,原因是ESP32IDF的主函数入口是void app_main(void)函数,但是这个函数属于C语言函数必须用c语言编译器进行编译。因此我们需要在前面加上extern "C":

extern "C" void app_main(void)
{


}

那么我们接着引入文件,我们需要创建一个文件夹名字是components,然后在下面创建一个CMakeLists.txt文件然后在文件夹中填写我们的必要的CMake语句:

set(src_dirs
        LED_driver
        LED_demo
   )

set(include_dirs
         LED_driver
         LED_demo
         )

set(requires
            driver)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

我们来解析一下些语句的含义:

set()函数:设置变量名字第一个参数是变量名,以后的每一个参数都是变量的内容以空格间隔

idf_component_register()函数:是ESP32IDF提供的注册函数向编译文件注册SRC_DIRS是源文件所在的文件夹,INCLUDE_DIRS是头文件所在的文件夹,REQUIRES是请求类型参数这三个宏需要传递参数通过${}的形式{}里面放置的就是变量名字,如果编写过Linux的shell脚本的话对这个东西是比较熟悉的。

可以看出我们引入了两个文件夹,一个是LED_driver另一个是LED_demo(请忽略我的其他文件夹是为后续的文章做准备的,嘿嘿)

其中我们的LED_driver是LED的驱动文件,主要是GPIO的驱动,要是不知道怎么写可以参考我们的第一篇文章的GPIO操作:ESP32开发学习记录---》GPIO-CSDN博客

LED_demo是我写的c++的示例方便进行演示。

 gpio驱动的led_dev.c文件

#include"led_dev.h"
void pinConfiguration(gpio_num_t gpio_num)
{
    const gpio_config_t Pin_config={
        .intr_type=GPIO_INTR_DISABLE,
        .mode=GPIO_MODE_OUTPUT,
        .pin_bit_mask=(1ULL<<gpio_num),
        .pull_down_en=GPIO_PULLDOWN_DISABLE,
        .pull_up_en=GPIO_PULLUP_DISABLE
    };

    gpio_config(&Pin_config);
    gpio_set_level(gpio_num, 0);
}

led_dev.h头文件 

#ifndef __LED_DEV_H__
#define __LED_DEV_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "driver/gpio.h"


void pinConfiguration(gpio_num_t gpio_num);

#ifdef __cplusplus
}
#endif

#endif 

其中肯定有小伙伴会疑惑欸?下面这段是啥玩意?别慌我一句一句带你解析

#ifndef __LED_DEV_H__
#define __LED_DEV_H__

#ifdef __cplusplus
extern "C" {
#endif





#ifdef __cplusplus
}
#endif

#endif 

#ifndef __LED_DEV_H__  和#define  __LED_DEV_H__ 加上一个#endif是防止头文件被反复定义的经常写c语言的同学应该都是了解的主要还是下面这段:



#ifdef __cplusplus
extern "C" {
#endif

/*#ifdef __cplusplus就是如果定义了__cplusplus就是extern "C"{下面那段也是如果定义了__cplusplus就是}也就是如果定义了__cplusplus,那么就会有
extern "C"
{
    //中间这个就是c语言的部分

}

*/



#ifdef __cplusplus
}
#endif

通过上面这段就能告诉c++编译器我是c语言的东西用C编译我。

下面我们来写个C++的代码实现相应的应用层代码吧:

led_example.h头文件

#ifndef __LED_EXAMPLE_H__
#define __LED_EXAMPLE_H__

#ifndef __cplusplus
extern "C"
{
#endif

//C interface

#ifndef __cplusplus
}
#endif

//C++ interface
class led_example //声明一个类叫做led_example
{
private:
    /* data */
    enum          //枚举类型枚举GPIO输出状态高或者低
    {
        GPIO_LOW=0,
        GPIO_HIGH=1
    };
    

public:
    led_example();  //类的构造函数类声明对象的时候默认调用
    ~led_example(); //析构函数类自动释放
};

#endif /* __LED_EXAMPLE_H__ */

led_example.cpp代码文件

#include"led_example.h"
#include"driver/gpio.h"

led_example::led_example()
{
    gpio_set_level(GPIO_NUM_4, this->GPIO_HIGH); //使用gpio的设置函数使用this指针输出高
}


led_example::~led_example()
{
    
}

最后我们回到主函数:

#include"led_dev.h"
#include"led_example.h"
#include"main_app.h"

extern "C" void app_main(void)
{
   
    pinConfiguration(GPIO_NUM_4);  //GPIO初始化配置
    led_example led;               //创建对象对象默认调用设置高电平的构造函数

    while(1)
    {
        vTaskDelay(100);  
    }
}

最后看一下实验现象: