嵌入式_GCC14.2初体验_使用Module特性
哈哈嗨,之前看着GCC14.2的模块特性馋得不行,但GCC14.2只做了上位机的,而交叉编译器的还是13.3。苦等了这么久,总算在前些天出了这个版本,那么废话不多说,我们直接开始↗。
IDE:CLion 可参考博客【嵌入式】CLion & CubeMX
MCU:STM32F407VET6
一、简介
哈哈嗨,之前看着GCC14.2的模块特性馋得不行,但GCC14.2只做了上位机的,而交叉编译器的还是13.3。苦等了这么久,总算在前些天出了这个版本,那么废话不多说,我们直接开始↗
1,下载工具链
先到官网下载
Arm GNU Toolchain Downloads – Arm Developer
我们单片机开发选择第一个即可
下载好之后,找一个你平时放工具链的地方埋上,只要下次能找到就行
2,配置环境变量
按下Win+X,弹出的选项中,选择【系统】,然后进入【高级系统设置】里
然后点击环境变量
找到【系统环境变量】下的【Path】。注意!!不是上面那个用户变量
之后点击【编辑】
这里可以看到我们之前配置的13.3那个版本的工具链,替换掉就行了。当然,如果你之前没有配置过,这里新建一个就行了。
所以我们现在先复制一下前面那个工具链的bin目录的路径,找到并单击路径,然后按下快捷键Ctrl+Shift+C或者右击路径并点击【复制文件路径】
编辑或者新建,去掉首尾的引号就行。之后,一路确定,最后重启!
3,更换编译器路径
重启之后,把这两个编译器的路径替换为你14.2的那个即可
替换后,一般会自动CMake一下。除非之前gcc版本很低,否则替换为14.2一般是不会出现编译报错的
换了之后,不知道是不是我的错觉,好像比之前慢了一点点,而且是一顿一顿的
二、初尝试
1,体验简单的module
由于我的工程是C/C++混编,所以单独添加C++的编译标志为“-fmodules-ts”
# 添加编译器选项,使得支持Module特性 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmodules-ts")
接下来测试一个简单的模块试试,至于复杂的一些用法参考一些教程就可以了
随便建立一个文件,后缀名ixx、cppm等都随意(C++混乱不是一天两天了嘛)
// myModule.ixx export module myModule; export { int hello(int x) { return x+1; } }
在CMakelists添加这个文件,先用file函数包含刚才我们创建的文件,由于我这个文件是放在当前目录下的Module下的src目录,所以写成了下面这个样子。
现在test_modules这个变量就包含了Module/src目录下的所有ixx文件
# -------------------module特性测试----------------------- file(GLOB_RECURSE test_modules "Module/src/*.ixx" )
最后,我们导入这个模块文件,根据需要替换相关变量
【${PROJECT_NAME}.elf】:构建的目标【FILE_SET CXX_MODULES FILES】:与PUBLIC一样,只是个属性(标志),但是模块特性必须添加这个属性# 导入模块 target_sources(${PROJECT_NAME}.elf PUBLIC FILE_SET CXX_MODULES FILES ${test_modules})
不过由于我现在这个工程使用的是静态库编译,所以需要把目标设置为我需要使用模块的库。此处,我在app这个静态库里导入该模块,所以目标就设置为了libapp,不然找不到。
这两种方法的不同如果不甚了解的话,需要自行补充静态库与CMakelists相关的知识,熟悉熟悉即可。
# 导入模块 target_sources(libapp PUBLIC FILE_SET CXX_MODULES FILES ${test_modules})
在一个文件里导入这个模块
import myModule;
然后调用刚才的模块里定义的函数
hello(3);
编译一下,能编译成功即可说明模块特性可以正常使用。
2,可能的bug
file(GLOB_RECURSE cxx_modules "Module/inc/*.ixx" "Module/src/*.cppm" )
有时候在ixx文件里声明了一个模块,但是在写实现模块文件时,编译器可能会发犯傻,会报错“不能找到xxx模块”,即使你已经cmake了。
我个人的解决做法,就是在模块实现文件的开头加上"module;" 。这时只有这句报错其,其下的代码反而不会再报错了,然后把这句删掉所有代码就正常了。主打的就是个莫名其妙,不知道适不适用其他人
// flash_storage.ixx export module flash_storage; import <cstdint>; import <cstddef>; export { template< void (*WriteFunc)(const uint8_t *data, size_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, size_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > class FlashStorage { public: static void writeData(const uint8_t *data, size_t length, uint32_t address); static void readData(uint8_t *data, size_t length, uint32_t address); static void eraseSector(uint32_t sectorAddress); }; } // 私有模块部分 module :private; // 这里可以放置一些辅助函数或其他私有实现细节 void internalHelperFunction() { // ... }
// flash_storage.cppm // module; module flash_storage; // 实现模板成员函数 template< void (*WriteFunc)(const uint8_t *data, size_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, size_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > void FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::writeData(const uint8_t *data, size_t length, uint32_t address) { WriteFunc(data, length, address); } template< void (*WriteFunc)(const uint8_t *data, size_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, size_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > void FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::readData(uint8_t *data, size_t length, uint32_t address) { ReadFunc(data, length, address); } template< void (*WriteFunc)(const uint8_t *data, size_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, size_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > void FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::eraseSector(uint32_t sectorAddress) { EraseSectorFunc(sectorAddress); }
下面是之前没有写完的示例代码,可以用来参考(只是简单使用了关键字,但内部逻辑还没有真正用上module),注意模块声明和实现分开后需要遵循一些规则。需要说明的是import <cstdint>等标准库的导包还不能使用,只能乖乖使用头文件包含。
flash_storage.ixx
export module flash_storage; export { #include <cstdint> template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > class FlashStorage { public: static auto read_isr_ready() -> void; static auto write_isr_ready() -> void; static auto read_isr() -> uint8_t; static auto write_isr(uint8_t byte) -> void; static auto background_processing() -> void; /*还需要提供擦除所有扇区的接口、提供Flash记录地址的接口、修改Flash记录地址的接口*/ private: // 切换缓冲区并重置索引 static auto switch_buffer() -> void { reset_index(); uint8_t *temp = pBackBuffer; pBackBuffer = pForeBuffer; pForeBuffer = temp; } static auto set_read_sign() -> void { status |= static_cast<uint8_t>(Flags::READ_WRITE_FLAG); }// 设置读标志 static auto set_write_sign() -> void { status &= ~static_cast<uint8_t>(Flags::READ_WRITE_FLAG); }// 设置写标志 static auto set_read_full_sign() -> void { status |= static_cast<uint8_t>(Flags::READ_FULL_FLAG); }// 设置读满标志 static auto set_write_full_sign() -> void { status |= static_cast<uint8_t>(Flags::WRITE_FULL_FLAG); }// 设置写满标志 static auto set_read_incomplete_sign() -> void { status |= static_cast<uint8_t>(Flags::READ_INCOMPLETE_FLAG); }// 设置读残缺标志 static auto set_write_incomplete_sign() -> void { status |= static_cast<uint8_t>(Flags::WRITE_INCOMPLETE_FLAG); }// 设置写残缺标志 static auto check_read_sign() -> uint8_t { return status & static_cast<uint8_t>(Flags::READ_WRITE_FLAG); }// 检测读标志 static auto check_write_sign() -> uint8_t { return !(status & static_cast<uint8_t>(Flags::READ_WRITE_FLAG)); }// 检测写标志 static auto check_read_full_sign() -> uint8_t { return status & static_cast<uint8_t>(Flags::READ_FULL_FLAG); }// 检测读满标志 static auto check_write_full_sign() -> uint8_t { return status & static_cast<uint8_t>(Flags::WRITE_FULL_FLAG); }// 检测写满标志 static auto check_read_incomplete_sign() -> uint8_t { return status & static_cast<uint8_t>(Flags::READ_INCOMPLETE_FLAG); }// 检测读残缺标志 static auto check_write_incomplete_sign() -> uint8_t { return status & static_cast<uint8_t>(Flags::WRITE_INCOMPLETE_FLAG); }// 检测写残缺标志 static auto clear_read_full_sign() -> void { status &= ~static_cast<uint8_t>(Flags::READ_FULL_FLAG); }// 清除读满标志 static auto clear_write_full_sign() -> void { status &= ~static_cast<uint8_t>(Flags::WRITE_FULL_FLAG); }// 清除写满标志 static auto clear_read_incomplete_sign() -> void { status &= ~static_cast<uint8_t>(Flags::READ_INCOMPLETE_FLAG); }// 清除读残缺标志 static auto clear_write_incomplete_sign() -> void { status &= ~static_cast<uint8_t>(Flags::WRITE_INCOMPLETE_FLAG); }// 清除写残缺标志 static auto reset_index() -> void { index = 0; }// 重置缓冲区索引 //低耦合高内聚 private: static constexpr const uint16_t page_size = 256;// 页大小,不占内存 static inline uint8_t buffer1[page_size] = {}; static inline uint8_t buffer2[page_size] = {}; static inline uint8_t *pBackBuffer = buffer1;// 后台缓冲区指针 static inline uint8_t *pForeBuffer = buffer2;// 前台缓冲区指针 // FLASH存储地址 鉴于加载缓冲区都依靠该地址,所以该地址表示的是“将要”加载到缓冲区对应的Flash的起始地址 static inline uint32_t addr = 0; static inline uint32_t addr_remaining = 0;// 剩余地址,用于存储初始地址残缺的量 /** * @details 0'b:读写标志,默认0为写 * 1'b:读满标志,默认0为未满 * 2'b:写满标志,默认0为未满 * 3'b:表示读残缺,默认0为无残缺 所谓残缺就是指输入的地址不是Flash的页字节的整数倍 * 4'b:表示写残缺,默认0为无残缺 */ static inline uint8_t status = 0; // 状态,用于在单线程下处理各种“标志” static inline uint16_t index = 0; // 记录缓冲数组的索引,可根据需要改变数据类型,现在留一定余量 // 使用 enum class 定义常量,由于它是强类型,必须使用类型转换static_cast并不会占用内存,与constexpr相一样 enum class Flags : uint8_t { READ_WRITE_FLAG = 1U,// 读写标志,默认0为写标志,这样读取预加载时会少一个分支 READ_FULL_FLAG = 1U << 1, // 读满标志 WRITE_FULL_FLAG = 1U << 2,// 写满标志 READ_INCOMPLETE_FLAG = 1U << 3,// 读残缺标志 WRITE_INCOMPLETE_FLAG = 1U << 4 }; // 起始地址预留1页用于存储一些数据信息,比如上次写入地址等等 enum class Addresses : uint32_t { FLASH_START_ADDR = 0x100, // 256B FLASH_END_ADDR = 0x200000 // 2MB }; }; } //// 私有模块部分 //module : //private;
flash_storage.cppm
// impl这个分区很重要,至于名称可自拟 module flash_storage:impl; // 导包,不然找不到模块需要实现的部分 import flash_storage; /** * @brief 为中断中的读取作预加载 * @details 先判断此前是否处于读的状态,若不是则重置索引并预加载到缓冲区里 * 此外还需要考虑初始addr不是256的整数倍 */ template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > auto FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::read_isr_ready() -> void { if (check_write_sign()) { set_read_sign(); reset_index();// 编译器应该会为我优化的 /*从Flash的首页加载地址*/ // 判断地址是否是页数的整数倍 index = addr % page_size; addr_remaining = page_size - index; if (index != 0) set_read_incomplete_sign(); /* 加载该页数据*/ ReadFunc(pForeBuffer, addr, addr_remaining); /* 加载下一页数据*/ ReadFunc(pBackBuffer, addr - index + page_size, page_size); } } /** * @brief 为中断中的写入作预加载 * @details 先判断此前是否处于写的状态,若不是则重置索引 */ template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > auto FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::write_isr_ready() -> void { if (check_read_sign()) { set_write_sign(); reset_index(); } // 判断地址是否是页数的整数倍 index = addr % page_size; addr_remaining = page_size - index; if (index != 0) set_write_incomplete_sign(); } /** * @brief 读取数据 * @return 返回该字节数据 * @note 本想加个读写标志判断的,但这样太影响效率了。想了想,干脆把它们变成读写的前置操作。 * 这样既提高了效率,又不会与其他模块耦合在一块(提供接口供Player调用) */ template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > auto FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::read_isr() -> uint8_t { if (index == page_size - 1) { uint8_t temp = pForeBuffer[page_size - 1]; switch_buffer(); set_read_full_sign();// 标记为读满 return temp; } else { return pForeBuffer[index++]; } } template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > auto FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::write_isr(uint8_t byte) -> void { if (index == page_size - 1) { pForeBuffer[page_size - 1] = byte; switch_buffer(); set_write_full_sign();// 标记为写满 } else { pForeBuffer[index++] = byte; } } /** * @brief 后台处理函数,用于在主循环中调用 * @note 检测对应标志就进行相应处理,不要做多余的操作 */ template< void (*WriteFunc)(const uint8_t *data, uint32_t length, uint32_t address), void (*ReadFunc)(uint8_t *data, uint32_t length, uint32_t address), void (*EraseSectorFunc)(uint32_t sectorAddress) > auto FlashStorage<WriteFunc, ReadFunc, EraseSectorFunc>::background_processing() -> void { if (check_read_full_sign()) { clear_read_full_sign(); ReadFunc(pBackBuffer, addr, page_size); addr += page_size; } if (check_write_full_sign()) { clear_write_full_sign(); if (check_write_incomplete_sign()) { clear_write_incomplete_sign(); /* 进行残缺的写入页数据*/ WriteFunc(pBackBuffer, addr, addr_remaining); addr += addr_remaining; } } }
三、参考工程
由于本人也是刚上手这个module特性,所以参考工程里main分支里不一定会运用module特性。并且由于是个人独立项目,没什么约束,所以会经常使用一些很新的特性,比如这次的module。不过出于某些考虑,驱动层编写时依旧会使用C接口,除此之外都是使用C++。
gitcode(后者的镜像):STM32F407VET6:stm32f407vet6 - GitCode
github:ichliebedich-DaCapo/STM32F407VET6: stm32f407vet6 (github.com)
更多推荐
所有评论(0)