BSP 实现总览:10 个驱动模块的设计模式

Babel36acl 嵌入式实战 无~ 13 次阅读 预计阅读时间: 14 分钟 发布于 1 天前 最后更新于 2 小时前 3184 字


嵌入式实战 BSP设计

BSP 实现总览:10 个驱动模块的设计模式

BSP 层 10 个 .c 文件,每个遵循相同的接口模式。本文统一展示它们的共性和差异。

BSP 模块清单

模块 硬件 核心接口 模式
bsp_stepper TIM4 四轴 PWM start / stop / is_done / task_once 硬件映射表 + 共享ARR + 抛物线起步
bsp_dc_motor TIM1+TIM8 PWM+IR2104 start / stop / set_dir / stop_all 硬件映射表 + 逐周期超时
bsp_actuator GPIO 继电器/指示灯 init / write / read 直接 GPIO 封装
bsp_uart USART1/2/3 + DMA port_init / transmit / receive 端口枚举 + StreamBuffer
bsp_log 格式化输出 log_inf / log_err / set_dispatch 纯格式化,无 I/O(回调输出)
bsp_adc ADC1_IN4 DMA init / get_raw / get_voltage DMA 双缓冲 + 回调通知
bsp_gpio 各 GPIO 引脚 init / read / write / toggle 直接 HAL 封装
bsp_eeprom AT24C02 软件 I2C init / read / write / deinit 三层隔离(时序+芯片+接口)
bsp_at24cxx AT24C 系列通用 通用驱动回调注册 回调函数表(I2C 时序)
bsp_watchdog IWDG init / kick 直接寄存器操作

模式一:硬件映射表(bsp_stepper, bsp_dc_motor)

多实例硬件(四轴步进、两路直流)通过查找表将逻辑编号映射到物理资源:

// bsp_stepper.c — 四轴硬件映射
typedef struct {
    GPIO_TypeDef *en_port;
    uint16_t      en_pin;
    GPIO_TypeDef *dir_port;
    uint16_t      dir_pin;
    uint32_t      tim_channel;   // TIM4 CH1~CH4
} bsp_stepper_hw_map_t;

// 硬编码映射表,编译器优化后无运行时开销
static bsp_stepper_hw_map_t bsp_stepper_get_axis_map(bsp_stepper_axis_t axis) {
    static const bsp_stepper_hw_map_t map[] = {
        {GPIOA, GPIO_PIN_8,  GPIOB, GPIO_PIN_12, TIM_CHANNEL_1},
        {GPIOA, GPIO_PIN_15, GPIOB, GPIO_PIN_13, TIM_CHANNEL_2},
        {GPIOB, GPIO_PIN_3,  GPIOB, GPIO_PIN_14, TIM_CHANNEL_3},
        {GPIOB, GPIO_PIN_4,  GPIOB, GPIO_PIN_15, TIM_CHANNEL_4},
    };
    return (axis < BSP_STEPPER_AXIS_COUNT) ? map[axis] : map[0];
}

同理,TIM4 通道中断信息也建表:

static const bsp_stepper_channel_info_t s_channel_lut[] = {
    {TIM_IT_CC1, TIM_FLAG_CC1, HAL_TIM_ACTIVE_CHANNEL_1},
    {TIM_IT_CC2, TIM_FLAG_CC2, HAL_TIM_ACTIVE_CHANNEL_2},
    {TIM_IT_CC3, TIM_FLAG_CC3, HAL_TIM_ACTIVE_CHANNEL_3},
    {TIM_IT_CC4, TIM_FLAG_CC4, HAL_TIM_ACTIVE_CHANNEL_4},
};

四轴共享同一个 TIM4 的 ARR。启动新轴时必须在临界区中比较各轴需求,选择最高频率作为共享 ARR。这是这个硬件方案最核心的约束。

模式二:错误码枚举 + 状态映射(bsp_uart → packer_screen)

BSP 层定义自己的错误码枚举,上层使用状态映射函数做跨层转换:

// bsp_uart 错误码
typedef enum {
    BSP_UART_STATUS_OK = 0,
    BSP_UART_STATUS_BUSY,
    BSP_UART_STATUS_TIMEOUT,
    BSP_UART_STATUS_ERROR
} bsp_uart_status_t;

// Service 层映射为 DGUS 状态码
static packer_screen_dgus_status_t
packer_screen_dgus_from_uart(bsp_uart_status_t status) {
    switch (status) {
    case BSP_UART_STATUS_OK:      return PACKER_SCREEN_DGUS_STATUS_OK;
    case BSP_UART_STATUS_BUSY:    return PACKER_SCREEN_DGUS_STATUS_BUSY;
    case BSP_UART_STATUS_TIMEOUT: return PACKER_SCREEN_DGUS_STATUS_TIMEOUT;
    default:                      return PACKER_SCREEN_DGUS_STATUS_ERROR;
    }
}

BSP 层不直接 include 上层的枚举。上层通过映射函数做转换——这是分层边界的一个具体体现。

模式三:DMA + 回调通知(bsp_adc)

ADC 使用 DMA 双缓冲采样,完成时通过注册的回调通知任务:

// BSP 层 — 注册回调函数指针
typedef void (*bsp_adc_notify_cb_t)(void);
void bsp_adc_register_notification_cb(bsp_adc_notify_cb_t cb);

// 回调在 ISR 中调用,通过 vTaskNotifyGiveFromISR 唤醒任务

模式四:纯格式化 + 回调分发(bsp_log)

bsp_log 不包含任何 I/O 操作。它只是一个格式化引擎,输出通过回调分发给注册的后端:

// bsp_log 输出流
void bsp_log_write(const char *text, uint16_t len);
// 内部调用注册的分发函数
// 当前注册:packer_dbus_log_send() → DGUS 5A A5 帧 → USART1

// 调用方式
BSP_LOG_INF("SM_CMD CONTROL stop");
// 内部: vsnprintf → bsp_log_write → 分发回调

模式五:直接寄存器(不绕 HAL)

某些性能敏感的 BSP 操作跳过 HAL,直接操作寄存器。例如关闭单路 TIM4 PWM 通道时,HAL 的 HAL_TIM_PWM_Stop() 会关闭整个定时器,但四轴共享 TIM4——只能手动操作通道寄存器:

static void bsp_stepper_disable_axis_channel(bsp_stepper_axis_t axis,
                                              uint8_t disable_irq) {
    __HAL_TIM_DISABLE_IT(&htim4, info->it_mask);
    __HAL_TIM_CLEAR_FLAG(&htim4, info->flag_mask);
    TIM_CCxChannelCmd(htim4.Instance, channel, TIM_CCx_DISABLE);
    TIM_CHANNEL_STATE_SET(&htim4, channel, HAL_TIM_CHANNEL_STATE_READY);

    if (bsp_stepper_any_channel_enabled() == 0U) {
        __HAL_TIM_DISABLE(&htim4);  // 全部通道关闭后才停 TIM
    }
}

所有 BSP 遵循的共同原则

  1. 命名bsp_<模块>_<动作>,如 bsp_stepper_start()
  2. 错误码:每个模块有自己的枚举,上层做映射转换
  3. 硬件映射表:逻辑编号 → 物理资源,集中一处管理
  4. 不暴露 HAL:上层看不到 HAL_GPIO_WritePin 等调用
  5. 不承载业务:BSP 不知道什么是"出袋"、"封口"
  6. 临界区保护__disable_irq() + 保存恢复,不用 volatile 替代

附录 A:CubeMX 外设资源分配

本文涉及的 10 个 BSP 模块对应的 CubeMX 外设配置如下,供硬件迁移或 .ioc 同步时参考。

时钟配置

参数
HSE 8 MHz 外部晶振
PLL 倍频 ×9
SYSCLK 72 MHz
APB1 36 MHz (max)
APB2 72 MHz (max)

定时器分配

TIM 用途 备注
TIM1 IR2104 两路 PWM (CH1~CH4) 高级定时器,互补输出
TIM2 ST1 步进电机 PUL 专用定时器,独立 ARR
TIM3 NMOS 输出 (Partial Remap) CH1=PA6, CH2=PA7, CH3=PB0, CH4=PB1
TIM4 ST3、ST4 步进电机 PUL 共享 ARR 方案
TIM5 ST2 步进电机 PUL 专用定时器,独立 ARR
TIM6 FreeRTOS timebase 基本定时器,仅中断
TIM8 IR2104 第二组两路 PWM 高级定时器,互补输出

USART 分配

USART 波特率 电平 接驳设备 用途
USART1 9600 RS232 DB9 串口 调试控制台 + 日志输出(dgus_log 后端)
USART2 115200 RS232 DB9 串口 热敏打印机
USART3 9600 RS485 RS485 总线 主站协议通信

ADC

ADC 通道 引脚 采样方式
ADC1 IN4 PA4 DMA 循环 + 双缓冲

GPIO 分类

类别 引脚 方向 说明
隔离输入 IN1~IN9 (PA0~PA3, PA5~PA7, PB0~PB1) 输入 外部传感器/限位开关
步进信号 PUL(PA8/PA15/PB3/PB4), DIR(PB12~PB15), EN(PA11~PA14) 输出 ST1~ST4 控制
IR2104 TIM1/TIM8 CH1~CH4 输出 两路直流电机 H 桥
NMOS PA6, PA7, PB0, PB1 (TIM3 CH1~CH4 Partial Remap) 输出 负载控制
LED 指示灯 PC13 (板载), 其他 GPIO 输出 状态指示
I2C 软件 PB6 (SCL), PB7 (SDA) 开漏输出 AT24C02 EEPROM

DMA 分配

外设 DMA 流 方向 模式
USART1_TX DMA1_Channel4 内存→外设 Normal
USART2_TX DMA1_Channel7 内存→外设 Normal
USART3_TX DMA1_Channel2 内存→外设 Normal
ADC1 DMA1_Channel1 外设→内存 Circular (双缓冲)

附录 B:板级外设接线与引脚冲突 v1.1

以下记录 BSP 驱动层对应的物理引脚映射,以及在布线调试过程中已解决的引脚冲突。

隔离输入接线

逻辑编号 功能 MCU 引脚 备注
INPUT_1 入袋限位 PA0
INPUT_2 封口限位 PA1
INPUT_3 打印就绪 PA2
INPUT_4 应急停止 PA3
INPUT_5 袋检测 PA5
INPUT_6 封口温度 PA6 v1.0 原为 PB0→v1.1 改至 PA6 (见冲突说明)
INPUT_7 步进复位 PA7
INPUT_8 预留 A PB0 v1.1 复用 NMOS CH3
INPUT_9 预留 B PB1 v1.1 复用 NMOS CH4

步进电机接线 (ST1~ST4)

定时器 PUL DIR EN
ST1 TIM2 PA8 PB12 PA11
ST2 TIM5 PA15 PB13 PA12
ST3 TIM4 CH1 PB3 PB14 PA13
ST4 TIM4 CH2 PB4 PB15 PA14

NMOS 输出接线 (TIM3 Partial Remap)

通道 TIM3 输出 MCU 引脚 负载
CH1 TIM3_CH1 PA6 封口加热
CH2 TIM3_CH2 PA7 气泵
CH3 TIM3_CH3 PB0 预留
CH4 TIM3_CH4 PB1 预留

注意:TIM3 配置为 Partial Remap,将默认的 CH3(PC6)、CH4(PC7) 重映射至 PB0、PB1,避免与 USART3(PC10/PC11) 冲突。

已解决的引脚冲突

  1. INPUT_6 迁移(PB0 → PA6):v1.0 将 INPUT_6 分配在 PB0,但 PB0 同时是 TIM3_CH3 (Partial Remap) 的 NMOS 输出引脚,造成输入/输出冲突。v1.1 将 INPUT_6 移到了 PA6,PB0 释放给 NMOS CH3 使用。NMOS CH1(PA6) 被 INPUT_6 占据的问题通过将 TIM3 Partial Remap 的 CH1 共用同一 PA6 来解决——NPWM 和 GPIO 输入在 v1.1 的电气设计中通过外部分时/跳线处理。
  2. PB3 / PB4 / PA15 复用作 SWD 与 JTAG:这三个引脚默认被 Debug 接口占用的(PA15=JTDI, PB3=JTDO/Traceswo, PB4=JNTRST)。CubeMX 中已配置为 Serial Wire (SWD) 模式禁用 JTAG,释放 PA15/PB3/PB4 给步进 PUL 使用。
  3. TIM3 Partial Remap 必要性:TIM3 默认 CH3(PC6)、CH4(PC7) 与 USART3(PC10/PC11) 无冲突,但 PC6/PC7 在 PCB 上已被其他外设占用。启用 Partial Remap 后将 TIM3_CH3→PB0、TIM3_CH4→PB1,规避了 PCB 走线冲突。
  4. ST1 / ST2 从 TIM4 拆分:v1.0 原型将 ST1~ST4 全部挂于 TIM4 四通道,共享 ARR 限制导致 ST1/ST2 频率受限。v1.1 将 ST1 独立至 TIM2、ST2 独立至 TIM5,ST3/ST4 仍共享 TIM4。这样 ST1/ST2 可获得独立 ARR,不受四轴最高频率约束。

固件兼容性说明

注意:当前的 bsp_stepper 固件实现(代码块中的映射表)仍然基于 v1.0 的硬件方案——四轴全部挂载在 TIM4 上。这意味着本文列出的 v1.1 接线表是硬件文档和 .ioc 文件中的真实配置,但固件尚未同步更新。.ioc 文件和硬件接线表是唯一正确的物理拓扑依据,固件将在下一迭代中根据此处记录的 TIM2/TIM5/TIM4 分组重新生成硬件映射表。此文档优先于固件实现。

此作者没有提供个人介绍。
最后更新于 2026-05-30