嵌入式实战 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 遵循的共同原则
- 命名:
bsp_<模块>_<动作>,如bsp_stepper_start() - 错误码:每个模块有自己的枚举,上层做映射转换
- 硬件映射表:逻辑编号 → 物理资源,集中一处管理
- 不暴露 HAL:上层看不到
HAL_GPIO_WritePin等调用 - 不承载业务:BSP 不知道什么是"出袋"、"封口"
- 临界区保护:
__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) 冲突。
已解决的引脚冲突
- 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 的电气设计中通过外部分时/跳线处理。
- PB3 / PB4 / PA15 复用作 SWD 与 JTAG:这三个引脚默认被 Debug 接口占用的(PA15=JTDI, PB3=JTDO/Traceswo, PB4=JNTRST)。CubeMX 中已配置为
Serial Wire (SWD)模式禁用 JTAG,释放 PA15/PB3/PB4 给步进 PUL 使用。 - TIM3 Partial Remap 必要性:TIM3 默认 CH3(PC6)、CH4(PC7) 与 USART3(PC10/PC11) 无冲突,但 PC6/PC7 在 PCB 上已被其他外设占用。启用 Partial Remap 后将 TIM3_CH3→PB0、TIM3_CH4→PB1,规避了 PCB 走线冲突。
- 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 分组重新生成硬件映射表。此文档优先于固件实现。
Comments NOTHING