当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]外设配置最怕手写魔法位:0x40011000、(1<<5)、BRR算错一位,上电就异常。模板元编程把基地址、引脚、时钟、波特率、预分频都当非类型模板参数,用constexpr算掩码和分频值,static_assert锁死数据手册边界,编译完直接生成“常量写寄存器”的机器码,运行期零计算。

外设配置最怕手写魔法位:0x40011000、(1<<5)、BRR算错一位,上电就异常。模板元编程把基地址、引脚、时钟、波特率、预分频都当非类型模板参数,用constexpr算掩码和分频值,static_assert锁死数据手册边界,编译完直接生成“常量写寄存器”的机器码,运行期零计算。

一、寄存器基址与字段模板

先封装内存映射和位域,所有偏移、宽度在类型里编码:

template<typename T, uint32_t ADDR>

struct Mmio {

   using Reg = T;

   static constexpr volatile T* ptr() { return reinterpret_cast<volatile T*>(ADDR); }

   static T read() { return *ptr(); }

   static void write(T v) { *ptr() = v; }

   static void set(T m) { *ptr() |= m; }

   static void clr(T m) { *ptr() &= ~m; }

};


template<typename REG, uint8_t OFF, uint8_t WIDTH>

struct Field {

   static_assert(WIDTH>0 && WIDTH<=sizeof(typename REG::Reg)*8, "bad field");

   static constexpr typename REG::Reg mask =

       (WIDTH>=32)?0xFFFFFFFFu:( (typename REG::Reg(1)<<WIDTH)-1 ) << OFF;

   static void write(typename REG::Reg v){

       REG::write((REG::read() & ~mask) | ((v<<OFF) & mask));

   }

   static typename REG::Reg read(){

       return (REG::read() & mask) >> OFF;

   }

};

UART控制寄存器可这样建模:EN占bit0,Mode占bit1~2,BaudDiv占bit8~15,调用方只写字段名,不碰原始移位。

二、UART:波特率在编译期算BRR

把外设基址、PCLK、波特率都做成模板参数,BRR整数分频编译期得出:

template<uint32_t BASE, uint32_t PCLK, uint32_t BAUD>

class Uart {

   using Cr1 = Mmio<uint32_t, BASE+0x0C>;

   using Brr = Mmio<uint32_t, BASE+0x08>;

   using Dr  = Mmio<uint32_t, BASE+0x04>;

   static constexpr uint32_t DIV = (PCLK + BAUD/2) / BAUD;

   static_assert(DIV>=1 && DIV<=0xFFFF, "baud out of range");

public:

   static void init(){

       Cr1::clr(0xFFFFFFFFu);

       Brr::write(DIV);

       Cr1::set((1u<<13)|(1u<<3)|(1u<<2)); // UE,TE,RE

   }

   static void putchar(uint8_t c){

       while(!(Mmio<uint32_t,BASE+0x00>::read() & (1u<<7)));

       Dr::write(c);

   }

};

using Debug = Uart<0x40011000u, 72000000u, 115200u>;

PCLK、BAUD都是常量,DIV在编译期算好,init里只写常量;换芯片只改BASE/PCLK,换速率只改BAUD,越界直接static_assert报错。

三、定时器:PSC/ARR自动出周期

template<uint32_t BASE, uint32_t TIMCLK, uint32_t HZ>

class Timer {

   static constexpr uint32_t psc = (TIMCLK/HZ/65536u) ? (TIMCLK/HZ/65536u) : 1u;

   static constexpr uint32_t arr = (TIMCLK/psc/HZ) - 1u;

   static_assert(arr<65536u, "arr overflow, adjust TIMCLK/HZ");

public:

   static void init(){

       Mmio<uint32_t,BASE+0x28>::write(psc-1); // PSC

       Mmio<uint32_t,BASE+0x2C>::write(arr);   // ARR

       Mmio<uint32_t,BASE+0x00>::set(1u<<0);   // CEN

   }

};

目标周期、时钟源都是模板参数,编译器展开成两个常量写PSC/ARR;多实例Timer<BASE1,..>、Timer<BASE2,..>各自生成代码,无运行期除法。

四、多外设按实例派发时钟

同族MCU不同USART挂在APB1/APB2,用if constexpr或traits选时钟使能,不写#ifdef:

template<int INST>

struct UartTraits;

template<> struct UartTraits<1> { static constexpr uint32_t BASE=0x40011000u; static constexpr uint32_t APB=2; };

template<> struct UartTraits<2> { static constexpr uint32_t BASE=0x40004400u; static constexpr uint32_t APB=1; };


template<int INST, uint32_t PCLK, uint32_t BAUD>

class UartEx {

   static void enable_clock(){

       if constexpr(UartTraits<INST>::APB==2) rcc_apb2_enable();

       else rcc_apb1_enable();

   }

public:

   static void init(){ enable_clock(); Uart<UartTraits<INST>::BASE,PCLK,BAUD>::init(); }

};

实例号确定后只保留对应时钟分支,另一个分支不进二进制。

五、批量生成初始化序列

多个外设要按固定顺序初始化,用integer_sequence展开,避免手列调用:

template<typename... Devs, std::size_t... I>

void init_all(std::index_sequence<I...>){

   (void(I), ..., (std::get<I>(std::tuple<Devs...>{}) , 0));

}

更实用是写tuple<Uart1,GpioA,Tim2>并在init_all里对每个调init(),编译器展开成常数序列,启动代码不跑循环计算。

六、落地边界

寄存器指针必须volatile,否则读状态/清标志会被优化掉;

非类型参数用整型/枚举/指针常量,C++20可扩到部分类对象,但MCU库别搞太复杂;

每个不同模板参数组合都会生成一份代码,GPIO按Port/Pin全展开会涨Flash,公共逻辑抽到非模板helper;

复杂时钟树用traits+constexpr集中算PLL/AHB/APB,APB1频率、PLL输入范围用static_assert卡死;

错误提示要友好:把“波特率超范围”“ARR溢出”“引脚号>=16”都做成static_assert文本,别让客户看模板回溯。

按“Mmio字段模板+非类型参数外设类+constexpr分频+static_assert+traits派发”做,UART、TIM、GPIO、RCC都能在编译期生成最终寄存器值,既去掉运行期配置计算,也把数据手册约束前置到构建阶段。



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