《架构特别篇二:SYSTEM 层》
架构特别篇二:SYSTEM 层 — 硬件抽象的精髓在一张表里
换芯片、换底板——只改一个文件
cc_h7_def.h。DMA 8 路串口不打架——靠dma.c里的一张映射表。.sct 分散加载——每一块内存放什么,编译期就决定了。这一章讲 SYSTEM 层的三个核心设计。
1. cc_h7_def.h:一张表统治所有硬件
大多数 STM32 项目的引脚分散在十几个文件里——GPIO 初始化在这里、UART 引脚在那边、CAN 引脚又在别处。换一块底板等于在代码库玩"找引脚"游戏。
煜坤的做法:所有硬件定义集中在一个文件。
// cc_h7_def.h — 想知道任何引脚、任何外设配置,只查这里
/* 串口引脚 (需要换底板? 改这里就行) */
#define COM1_TX_PORT GPIOA
#define COM1_TX_PIN GPIO_PIN_9
#define COM1_TX_AF GPIO_AF7_USART1
#define COM1_RX_PORT GPIOA
#define COM1_RX_PIN GPIO_PIN_10
#define COM1_RX_AF GPIO_AF7_USART1
#define COM4_TX_PORT GPIOB // 雷达串口
#define COM4_TX_PIN GPIO_PIN_8
// ... 8 路串口, 全部在这里
/* CAN 引脚 */
#define CAN1_RX_PORT GPIOD
#define CAN1_RX_PIN GPIO_PIN_0
/* I2C 引脚 */
#define I2C_HW_SCL_PORT GPIOF
#define I2C_HW_SCL_PIN GPIO_PIN_14
/* PWM 引脚 */
#define PWM1_CH1_PORT GPIOE
#define PWM1_CH1_PIN GPIO_PIN_9
/* 电源口、继电器口、LED... */
为什么这是设计亮点?
- 换底板:只改这一个文件。CONTROL 层 20 个任务、3 万行代码,一行不动。
- 换芯片:文件头改
#ifdef STM32H743xx→ 条件编译切换。我们从 H723 迁到 H743 用了不到一天。 - 审图:硬件工程师和软件工程师只需要对这张表。不需要翻 10 个文件。
PX4 的做法:board_config.h + .px4board manifest。两个文件。比我们多一层抽象,但也多一层理解成本。
APM 的做法:AP_HAL_ChibiOS/hwdef/ 目录下每个板子一个子目录。灵活性最高,但一个人的项目维护多个板子目录是负担。
2. DMA 映射表:一张表解决 8 路串口冲突
STM32 的 DMA 在 F4 上是固定绑定——UART1_RX 只能用 DMA1_Stream5。H7 有 DMAMUX——灵活路由,但也意味着需要自己分配,分配不好就冲突。
我们的解法:一张编译期常量表。
// dma.c — DMA 通道分配表 (编译期确定, 永不冲突)
const DMA_ChannelConfig_t g_dma_channels[] = {
[COM1] = { DMA1_Stream0, DMAREQ_ID_USART1_RX, 41, DMA1_Stream0_IRQn, 0 },
[COM2] = { DMA1_Stream1, DMAREQ_ID_USART2_RX, 43, DMA1_Stream1_IRQn, 1 },
[COM3] = { DMA1_Stream2, DMAREQ_ID_USART3_RX, 45, DMA1_Stream2_IRQn, 2 },
[COM4] = { DMA1_Stream3, DMAREQ_ID_UART4_RX, 63, DMA1_Stream3_IRQn, 3 },
[COM5] = { DMA1_Stream4, DMAREQ_ID_UART5_RX, 65, DMA1_Stream4_IRQn, 4 },
[COM6] = { DMA1_Stream5, DMAREQ_ID_USART6_RX, 71, DMA1_Stream5_IRQn, 5 },
[COM7] = { DMA1_Stream6, DMAREQ_ID_UART7_RX, 79, DMA1_Stream6_IRQn, 6 },
[COM8] = { DMA1_Stream7, DMAREQ_ID_UART8_RX, 81, DMA1_Stream7_IRQn, 7 },
};
// 用 [COMx] 数组索引 → DMA 初始化时自动查表 → 绑定
- 数组索引是
COMx枚举值 → 编译期检查 → 不会绑错 - 8 个串口各占一个 DMA 流 → 互不冲突
- 加第 9 个串口?表中加一行
3. .sct 分散加载:内存是编译期分配的
大多数嵌入式项目用默认链接脚本,堆和栈混在一起,谁踩了谁不知道。
我们用自定义 .sct 把内存分区:
// NexusMcu.sct — 内存分区表 (编译期确定)
LR_IROM1 0x08000000 0x00200000 { // 2MB Flash
ER_IROM1 0x08000000 0x00200000 { // 代码段
*.o (RESET, +First)
.ANY (+RO) // 所有只读数据
}
RW_DTCM 0x20000000 0x00020000 { // DTCM 128KB
*(.dtcm_data) // 代价地图 + EKF + 任务栈
}
// AXI SRAM 512KB — DMA 缓冲区
RW_AXI_ETH 0x24000000 0x8000 { *(.eth_dma) }
RW_AXI_SPI 0x24008000 0x8000 { *(.spi_dma) }
RW_AXI_USART 0x24010000 0x10000 { *(.usart_dma) }
// FreeRTOS Heap
HEAP_AXI 0x24020000 0x20000 { *(.heap_axi_dummy) }
// 通用数据主区
RW_AXI_GEN 0x24040000 0x40000 { .ANY (+RW +ZI) }
// SRAM1/2/3/4 — 溢出 + DMA 专用区
RW_SRAM1 0x30000000 0x20000 { *(.i2c_dma) }
RW_SRAM2 0x30020000 0x20000 { .ANY (+RW +ZI) }
// ...
}
设计意图:
- DTCM 放最热的数据(代价地图 DWA 每秒查 12 万次)→ 零等待
- AXI SRAM 放 DMA 缓冲(ETH/SPI/USART)→ DMA 可以访问
- SRAM2 放溢出数据(lwIP 协议栈、Modbus 缓冲)→ 低频访问
换芯片需要改什么? H723 的 AXI 128KB → H743 的 AXI 512KB。.sct 里 AXIGEN 的 Size 从 0x10000(64KB) 改成 0x40000(256KB)。一行改动。
4. 为什么这层没有"面向对象"?
PX4/APM 的 HAL 用 C++ 虚函数实现多态:
// APM 风格
class UARTDriver {
virtual void begin(uint32_t baud) = 0;
virtual size_t write(...) = 0;
};
// 然后: AP_HAL_ChibiOS::UARTDriver, AP_HAL_Linux::UARTDriver...
煜坤不这么做,原因:
1. 目前只有一个硬件平台
虚函数的多态价值体现在"多个实现"。只有一个实现时,虚表是纯开销。
2. 纯 C + static inline 更快
// 煜坤风格: 零调用开销
static inline uint16_t comGetBuf(uint8_t port, uint8_t *buf, uint16_t len) {
return ring_buf_read(&g_uart_devices[port].rx_rb, buf, len);
}
编译后直接内联——没有函数调用、没有虚表查表。DMA ISR 里每微秒都要省。
3. C 结构体 + 函数指针同样能实现"多态"
// 如果需要多实现, 可以用函数指针表
typedef struct {
void (*init)(void);
uint16_t (*read)(uint8_t*, uint16_t);
} UART_Ops;
// STM32 实现:
UART_Ops stm32_uart_ops = { stm32_uart_init, stm32_uart_read };
// 未来 GD32 实现:
UART_Ops gd32_uart_ops = { gd32_uart_init, gd32_uart_read };
需要跨平台时再加——而不是一开始就加。
5. SYSTEM 层的"黄金法则"
① 不包含任何业务逻辑
→ 不在这一层判断"这个雷达目标是威胁"
② 不依赖 FreeRTOS
→ 可以在 ISR 中使用,但不创建任务
③ 所有硬件定义集中
→ cc_h7_def.h + .sct + dma.c 三张表
④ static inline 优先
→ 无函数调用开销,适合 ISR 高频场景
⑤ 换芯片只改这一层
→ CONTROL 层零改动
本文是《从零搭建无人船控制系统》架构特别篇。项目地址:[煜坤 YuKun](开源准备中)。
更多推荐
所有评论(0)