当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]嵌入式常量别再靠#define硬算。宏没有类型、没有作用域,复杂分频/CRC/查表容易写错;constexpr用普通C++函数让编译器在构建期算出结果,生成后直接进只读段,上电零初始化、不占RAM、不跑除法。


嵌入式常量别再靠#define硬算。宏没有类型、没有作用域,复杂分频/CRC/查表容易写错;constexpr用普通C++函数让编译器在构建期算出结果,生成后直接进只读段,上电零初始化、不占RAM、不跑除法。

一、时钟分频与寄存器参数

UART、定时器分频若入参都是编译期常量,直接写成constexpr函数:

constexpr uint32_t uart_divisor(uint32_t pclk, uint32_t baud){

   return (pclk + baud/2u) / baud;          // 四舍五入,避免宏截断

}

constexpr uint32_t tim_presc(uint32_t timclk, uint32_t tick_hz){

   uint32_t div = timclk / tick_hz;

   return (div == 0u) ? 0u : (div - 1u);

}

constexpr auto USART_DIV = uart_divisor(72'000'000u, 115200u);

constexpr auto TICK_PSC  = tim_presc(72'000'000u, 10'000u);

static_assert(USART_DIV >= 1u, "baud too high for current PCLK");

赋值给constexpr变量会强制编译期求值;结果在固件里就是常量,启动代码不再做除法。 多平台用if constexpr按芯片族走不同分频公式,未选中分支不进二进制。

二、CRC/校验表全编译期生成

通信协议最适合。CRC8多项式0x07、MSB优先,256项表编译期展开:

#include <array>

#include <cstdint>


constexpr std::array<uint8_t,256> make_crc8_table(){

   std::array<uint8_t,256> t{};

   for(uint16_t i=0;i<256;++i){

       uint8_t c = static_cast<uint8_t>(i);

       for(int k=0;k<8;++k){

           c = (c & 0x80) ? static_cast<uint8_t>((c<<1) ^ 0x07)

                           : static_cast<uint8_t>(c<<1);

       }

       t[i] = c;

   }

   return t;

}

constexpr auto CRC8_TAB = make_crc8_table();

static_assert(CRC8_TAB[0]==0x00 && CRC8_TAB[0x07]==0x07, "crc8 table error");


constexpr uint8_t crc8(const uint8_t* d, uint16_t n){

   uint8_t r=0xFF;

   for(uint16_t i=0;i<n;++i) r = CRC8_TAB[(r^d[i]) & 0xFF];

   return r;

}

表进.rodata/Flash,运行时只查不算是常见做法; 固定固件版本号可在编译期直接出CRC,static_assert比对预期值,错一个字节构建就失败。

三、协议偏移、数组尺寸、配置结构体

Modbus、CAN、私有串口帧的字段偏移用constexpr算,避免手填魔术数:

constexpr uint16_t HDR_LEN   = 4;

constexpr uint16_t PAYLOAD   = 32;

constexpr uint16_t FRAME_LEN = HDR_LEN + PAYLOAD + 2;   // +CRC

constexpr uint16_t PAYLOAD_OFF = HDR_LEN;


std::array<uint8_t,FRAME_LEN> tx_buf{};

static_assert(FRAME_LEN % 4u == 0u, "align frame to 4 bytes");

结构体配置也可用constexpr构造,全部字面量成员、无虚函数、无动态分配,结果可直接固化:

struct SensorCfg {

   uint8_t  id;

   uint16_t fs;       // 满量程

   int16_t  offset;

   constexpr SensorCfg(uint8_t i,uint16_t f,int16_t o):

       id(i),fs(f),offset(o){}

};

constexpr SensorCfg temp_cfg(0x21, 3300, -12);

编译期布局后配合alignas可控制对齐,只读对象不占RAM。

四、C++20 consteval强制不落地运行时

若担心被人传变量退化成运行时,用立即函数:

consteval uint32_t pwm_arr(uint32_t clk, uint32_t freq){

   return (clk / freq) - 1u;       // 仅编译期,传变量直接报编错

}

constexpr auto ARR_1K = pwm_arr(72'000'000u, 1000u);

consteval比constexpr更硬:不是“可以编译期”,而是“必须编译期”,适合安全参数、校验值、硬件寄存器初值。

五、落地与避坑

constexpr函数被变量/模板/数组尺寸引用才会强制编译期;只普通调用且入参运行时,仍可生成运行代码,别误以为一定零开销。

大表、深循环会拖编译时间,GCC可用-fconstexpr-loop-limit调整;表生成先用运行版单测,再加constexpr和static_assert。

浮点近似(正弦表、PID系数)在M0无FPU要谨慎,编译期算好定点或Q格式再存表,运行期不碰float。

确认存放位置:用size/map看是否进.rodata或内联;某些工具链会把“可能被修改”的全局复制到RAM,关键表可加const、必要时用section属性锁到Flash。

替代宏不是全替代:寄存器和位掩码用constexpr类型安全,条件编译平台差异用if constexpr比#ifdef更利于静态检查。

把分频、CRC、帧格式、标定系数、查找表全部改constexpr/consteval,配合static_assert,MCU固件启动更短、RAM更省、协议参数错在编译阶段而不是现场出怪问题。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭