当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]换MCU最怕业务层直接调厂商HAL:stm32f1xx_hal.c里写HAL_UART_Transmit,gd32f10x.c里又要改usart_data_transmit,换一次改一次。稳定做法是“应用只依赖自有HAL接口,厂商库关在platform实现层”,STM32/GD32/RISC-V都按同一头文件出实现,构建宏切换目录即可。


换MCU最怕业务层直接调厂商HAL:stm32f1xx_hal.c里写HAL_UART_Transmit,gd32f10x.c里又要改usart_data_transmit,换一次改一次。稳定做法是“应用只依赖自有HAL接口,厂商库关在platform实现层”,STM32/GD32/RISC-V都按同一头文件出实现,构建宏切换目录即可。

一、接口层只定义行为,不碰寄存器

统一头文件放hal/,不包含任何芯片头:

namespace hal {

enum class GpioMode { InFloating, InPullup, OutPushPull, AltPushPull };

struct GpioPin { uint8_t port; uint8_t pin; };


class IGpio {

public:

   virtual void config(const GpioPin&, GpioMode) = 0;

   virtual void set(const GpioPin&, bool) = 0;

   virtual bool get(const GpioPin&) = 0;

   virtual ~IGpio() = default;

};


class IUart {

public:

   virtual void init(uint32_t baud) = 0;

   virtual int  send(const uint8_t*, uint16_t) = 0;

   virtual void irq_rx_callback(void(*cb)(uint8_t)) = 0;

   virtual ~IUart() = default;

};

}

应用、协议栈、传感器驱动只包含hal/igpio.h、hal/iuart.h,不出现STM32的HAL_GPIO_WritePin,也不出现GD32的gpio_bit_set。

二、STM32与GD32分别实现,差异收敛在底层

STM32用Cube注册、GD32用标准库/官方HAL,都包成同一个IUart:

// hal_stm32/uart_impl.cpp

#include "hal/iuart.h"

#include "stm32f4xx_hal.h"

static UART_HandleTypeDef huart1;

static void(*rx_cb)(uint8_t) = nullptr;

class StmUart : public hal::IUart {

public:

   void init(uint32_t baud) override {

       huart1.Instance=USART1; huart1.Init.BaudRate=baud;

       huart1.Init.WordLength=UART_WORDLENGTH_8B;

       huart1.Init.StopBits=UART_STOPBITS_1;

       huart1.Init.Parity=UART_PARITY_NONE;

       huart1.Init.Mode=UART_MODE_TX_RX;

       HAL_UART_Init(&huart1);

   }

   int send(const uint8_t* d,uint16_t n) override {

       return HAL_UART_Transmit(&huart1,d,n,100)==HAL_OK?(int)n:-1;

   }

   void irq_rx_callback(void(*cb)(uint8_t)) override { rx_cb=cb; }

};

extern "C" void USART1_IRQHandler(void){

   HAL_UART_IRQHandler(&huart1);

   if(rx_cb){uint8_t b; if(HAL_UART_Receive(&huart1,&b,1,0)==HAL_OK) rx_cb(b);}

}

hal::IUart* uart_open(){ static StmUart u; return &u; }

// hal_gd32/uart_impl.cpp

#include "hal/iuart.h"

#include "gd32f10x.h"

static void(*rx_cb)(uint8_t) = nullptr;

class GdUart : public hal::IUart {

public:

   void init(uint32_t baud) override {

       usart_deinit(USART0);

       usart_baudrate_set(USART0, baud);

       usart_word_length_set(USART0, USART_WL_8BIT);

       usart_stop_bit_set(USART0, USART_STB_1BIT);

       usart_parity_config(USART0, USART_PM_NONE);

       usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

       usart_receive_config(USART0, USART_RECEIVE_ENABLE);

       usart_enable(USART0);

   }

   int send(const uint8_t* d,uint16_t n) override {

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

           usart_data_transmit(USART0,d[i]);

           while(RESET==usart_flag_get(USART0,USART_FLAG_TBE));

       }

       return (int)n;

   }

   void irq_rx_callback(void(*cb)(uint8_t)) override { rx_cb=cb; }

};

extern "C" void USART0_IRQHandler(void){

   if(usart_interrupt_flag_get(USART0,USART_INT_FLAG_RBNE) && rx_cb){

       rx_cb(usart_data_receive(USART0));

   }

}

hal::IUart* uart_open(){ static GdUart u; return &u; }

GPIO同理:STM32配MODER/OSPEEDR/PUPDR,GD32配gpio_init,都收敛到config/set/get。应用拿hal::IGpio*,不关心谁在底层。

三、编译期切换,不堆#ifdef

CMake/Make按MCU宏只编一个实现目录:

if(PLATFORM STREQUAL "stm32f4")

 add_definitions(-DPLATFORM_STM32)

 target_sources(app PRIVATE hal_stm32/gpio_impl.cpp hal_stm32/uart_impl.cpp)

elseif(PLATFORM STREQUAL "gd32f10")

 add_definitions(-DPLATFORM_GD32)

 target_sources(app PRIVATE hal_gd32/gpio_impl.cpp hal_gd32/uart_impl.cpp)

endif()

引脚映射再单独建board_xxx.cpp,LED、RS485_DE、传感器CS都写成hal::GpioPin{1,5}这类表,换板子只改板级文件,不动车型协议。

四、多系列必须单独校的差异

STM32与GD32同Cortex-M也不等于完全等价:RCC_PLL配置、Flash等待周期、AHB/APB分频要按目标重算;GD32部分系列使能外设时钟后需总线下个周期再写寄存器,STM32无此要求。 USART使能顺序、标志位(TBE/RBNE vs TXE/RXNE)不同; TIM预分频位宽、ARR重装、高级定时器刹车/死区要按手册核对; 中断号差异大,F1系EXTI向量与GD32偏移不同,启动文件别混用; ADC采样时序、RTC预分频、DMA通道映射也要逐外设对照。 抽象层把这些“行为差异”封进platform,应用层只看到统一波特率、统一ms延时、统一接收回调。

五、轻量替代:模板零开销HAL

对主频敏感、不想虚表开销,可用模板把基地址当参数,编译期生成:

template<uint32_t BASE>

class RegUart {

public:

   void send(uint8_t c){

       volatile uint32_t* sr=(volatile uint32_t*)(BASE+0x00);

       volatile uint32_t* dr=(volatile uint32_t*)(BASE+0x04);

       while(!(*sr & 0x80)); *dr=c;

   }

};

using DebugUart=RegUart<0x40011000>; // STM32 USART1/GD映射按手册改

缺点是多平台基地址、标志位仍要特化,适合驱动库内部;业务层仍建议用IGpio/IUart接口,兼顾可读与可测。

按“接口层—platform实现层—board映射层”三层切,STM32/GD32切换只换实现目录与启动文件,业务代码、Modbus、传感器解析、低功耗策略基本不动;新系列接入只需补hal_xxx实现并跑GPIO/UART/TIM/ADC自测,避免一换料全量返工。

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