ODrive FW v0.5.6 — Board文件夹源码解析
ODrive Fireware v0.5.6 — Board文件夹源码解析
解析范围:
ODrive-Fireware-v0.5.6/Firmware/Board/v3/以及同层的board.cpp / board.h。目标:帮助你快速理解 ODrive v3.x 在 STM32F4 平台上的 硬件抽象层(Board Support Layer) 是如何完成外设初始化、PWM/ADC 同步采样、控制回路中断调度、以及不同硬件小版本(v3.1~v3.6)兼容的。
0. 目录结构总览
Firmware/Board/v3/ 基本是 CubeMX 生成的工程骨架 + ODrive 做的少量改造。
Firmware/Board/v3/
├─ board.cpp # ODrive 的“板级聚合层”:外设对象/中断控制回路
├─ Inc/
│ ├─ board.h # ODrive v3.x 板级配置宏/外设句柄/全局对象声明
│ ├─ main.h # CubeMX 生成的 GPIO 引脚宏定义(*_Pin / *_GPIO_Port)
│ ├─ mxconstants.h # 工程常量(PWM 频率、死区、TIM 时钟等)
│ ├─ adc.h / gpio.h / tim.h ...# 各外设 HAL 初始化头文件
│ ├─ stm32f4xx_it.h # 中断声明
│ ├─ FreeRTOSConfig.h # FreeRTOS 配置
│ └─ prev_board_ver/ # 旧版本(3.2/3.4)的 GPIO/ADC 初始化代码
└─ Src/
├─ main.c # CubeMX 的入口(SystemClock_Config 等)
├─ adc.c / tim.c / spi.c ... # HAL 外设初始化实现
├─ freertos.c # 默认 RTOS 任务(基本只启动 USB)
├─ stm32f4xx_it.c # 默认中断入口(HardFault 等)
├─ stm32f4xx_hal_timebase_TIM.c # HAL tick 的 TIM 实现(替代 SysTick)
├─ usb_device.c + usbd_* # USB CDC 设备栈
└─ prev_board_ver/ # 旧版本(3.2/3.4)的 GPIO/ADC 初始化代码
需要关心的“Board 层逻辑”主要集中在两块:
- board.h / board.cpp(ODrive 自己写的板级聚合层)
- tim.c / adc.c(PWM—ADC 同步采样链路的 CubeMX 配置)
1. 启动链路(系统初始化 → 外设初始化 → 控制回路运行)
ODrive 固件的总体启动顺序可以理解为:
- system_init():HAL & 时钟 & OTP 硬件版本检查
- board_init():CubeMX 外设初始化 + 中断优先级配置 + 驱动芯片复位
- start_timers():TIM1/TIM8/TIM13 同步启动,打开 ADC 触发
- 控制回路中断链:
TIM8_UP_TIM13_IRQHandler()(PWM 更新事件)- 软件触发
ControlLoop_IRQHandler()(实际 FOC/控制计算)
在 ODrive v3 中,实时闭环控制不依赖 FreeRTOS 的普通任务调度,而是依赖**“PWM定时器更新中断 + 软件中断触发”**的硬实时链路。
2. board.h(板级配置与全局对象声明)
文件:Firmware/Board/v3/Inc/board.h
2.1 关键宏与参数
(1) 硬件参数:分流电阻
#if HW_VERSION_MINOR <= 3
#define SHUNT_RESISTANCE (675e-6f)
#else
#define SHUNT_RESISTANCE (500e-6f)
#endif
- ODrive 通过
HW_VERSION_MINOR(板子小版本)对硬件参数做兼容。 - 这个电阻会影响电流换算(ADC → 电流)。
(2) GPIO 计数与编号
#define AXIS_COUNT (2)
#define GPIO_COUNT (17)
- ODrive v3.x 双轴(M0/M1)。
- GPIO_COUNT=17 包含:GPIO1~GPIO8、编码器相关引脚、CAN 引脚,以及 GPIO0 dummy。
(3) 默认 GPIO 模式
DEFAULT_GPIO_MODES 直接给出 ODrive 的默认 GPIO 功能映射(数字/串口/模拟输入/编码器/CAN 等)。
(4) 控制周期与 TIM 关联
#define TIM_TIME_BASE TIM14
#define CONTROL_TIMER_PERIOD_TICKS (2 * TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1))
#define TIM1_INIT_COUNT (TIM_1_8_PERIOD_CLOCKS / 2 - 1 * 128)
TIM1/TIM8:中心对齐 PWM + 触发 ADC。TIM13:用于和 TIM1/TIM8 同步(辅助定时/校准采样节奏)。TIM14:被用作 HAL tick 的 timebase(见stm32f4xx_hal_timebase_TIM.c)。
(5) 电压采样分压比
#if HW_VERSION_VOLTAGE >= 48
#define VBUS_S_DIVIDER_RATIO 19.0f
#elif HW_VERSION_VOLTAGE == 24
#define VBUS_S_DIVIDER_RATIO 11.0f
#endif
用于 VBUS 电压转换:ADC 采样电压 × 分压比 → 母线真实电压。
2.2 关键全局对象(C++ 区域)
board.h 将驱动层对象与控制对象进行声明,供 board.cpp 定义。
axes:两轴 Axis 对象数组(Axis0/Axis1)motors[]:Motor0/Motor1encoders[]:Encoder0/Encoder1gpios[]:GPIO 索引到具体端口/引脚alternate_functions[][]:GPIO 复用功能表(UART/PWM/I2C/CAN/ENC)usb_dev_handle:USB CDC 设备句柄ext_spi_arbiter:SPI3 仲裁器(允许多个外设共享 SPI)uart_a / uart_b / uart_c:串口对象指针pwm0_input:PWM 输入捕获(典型用于 RC PWM 控制信号)
3. board.cpp(板级聚合层:对象实例化 + 控制回路中断)
文件:Firmware/Board/v3/board.cpp
这是 Board 文件夹中 最核心的“业务逻辑文件”,可以分为 4 类内容:
- 全局外设对象与驱动对象实例化
- system_init() / board_init() / start_timers()
- ADC 采样读取与复位逻辑
- 中断回调:PWM 更新中断 + 控制回路软中断 + SPI DMA 回调
3.1 全局对象:SPI 仲裁器与驱动芯片
(1) SPI3 仲裁器
Stm32SpiArbiter spi3_arbiter{&hspi3};
Stm32SpiArbiter& ext_spi_arbiter = spi3_arbiter;
- ODrive 把外部 SPI 设备统一挂在 SPI3。
Stm32SpiArbiter用于“多设备共享 SPI 总线”的冲突管理。
(2) 门极驱动芯片:两颗 DRV8301
Drv8301 m0_gate_driver{ ... M0_nCS ... nFAULT };
Drv8301 m1_gate_driver{ ... M1_nCS ... nFAULT };
- 两轴各一颗 DRV8301。
- EN 引脚是共享的,因此 EN 不在 drv8301 内部控制,而在
board_init()中统一拉高拉低。
3.2 温度限流:板载热敏电阻
OnboardThermistorCurrentLimiter fet_thermistors[AXIS_COUNT] = {...};
OffboardThermistorCurrentLimiter motor_thermistors[AXIS_COUNT];
fet_thermistors:测 MOSFET 温度,用于热保护/限流。- ADC 通道会随硬件小版本变化(
HW_VERSION_MINOR >= 3时 M1 的通道不同)。
3.3 Motor / Encoder / Axis 对象绑定
(1) Motor
Motor motors[2] = {
{ &htim1, mask, 1/SHUNT, m0_gate_driver, ... },
{ &htim8, mask, 1/SHUNT, m1_gate_driver, ... }
};
- 轴0 用 TIM1 作为 PWM 定时器
- 轴1 用 TIM8 作为 PWM 定时器
(2) Encoder
Encoder encoders[2] = {
{ &htim3, ... &spi3_arbiter },
{ &htim4, ... &spi3_arbiter }
};
- TIM3/TIM4 配置为 Encoder Mode(见
tim.c)。 - SPI 仲裁器用于 SPI 绝对值编码器/磁编码器类设备。
(3) Axis 聚合
axes 把控制器、估计器、轨迹等与 motor/encoder 绑定。
- 注意:Axis1 的 step/dir GPIO 引脚编号会在
HW_VERSION_MINOR >= 5时发生变化(兼容不同 PCB)。
3.4 GPIO 映射表与复用功能表
(1) gpios[]:软件 GPIO 编号 → MCU 端口引脚
不同硬件小版本(v3.1~v3.6)GPIO 排针复用布局不同,所以这里用 #if HW_VERSION_MINOR 分段定义。
典型结构:
Stm32Gpio gpios[GPIO_COUNT] = {
{nullptr,0}, // GPIO0 dummy
{GPIOA, GPIO_PIN_0}, // GPIO1
...
{GPIOB, GPIO_PIN_8}, // CAN_R
{GPIOB, GPIO_PIN_9}, // CAN_D
};
(2) alternate_functions[]:GPIO 模式 → AF 复用号
std::array<GpioFunction, 3> alternate_functions[GPIO_COUNT] = {
/* GPIO1 */ { UART4_AF8, TIM5_AF2 },
/* ENC0_A */ { TIM3_AF2 },
/* CAN_R */ { CAN1_AF9, I2C1_AF4 },
};
这个表让 ODrive 在运行时能够“根据配置把 GPIO 切换成 UART/PWM/I2C/CAN/ENC”等。
4. system_init() / board_init() / start_timers()
4.1 system_init()
职责:
HAL_Init()SystemClock_Config()(CubeMX 生成在main.c)- OTP 硬件版本校验
关键点:固件编译时的硬件版本必须与 OTP 中记录的一致,否则死循环停机。
实现逻辑:
- 若 OTP 首字节为
0xFF(未烧录),则用.testdata段的fake_otp(用于生产测试)。 - 用
check_board_version()对比HW_VERSION_MAJOR/MINOR/VOLTAGE。
这一步保证:
- 不会把 24V 固件烧到 48V 板子上
- 不会在硬件 pinout 变化的版本上跑错配置
4.2 board_init()
职责:完成外设初始化、中断优先级配置、可选接口初始化、DRV8301 复位。
(1) CubeMX 外设初始化顺序
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_TIM1_Init();
MX_TIM8_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_SPI3_Init();
MX_ADC3_Init();
MX_TIM2_Init();
MX_TIM5_Init();
MX_TIM13_Init();
这里的顺序体现了依赖关系:
- GPIO/DMA 先初始化
- ADC 与 TIM(触发源)必须准备好
- SPI/UART/I2C/CAN 视配置启用
(2) EXTI 中断线统一开启
EXTI0/1/2/3/4/9_5/15_10 全部启用,真正每根线的用途与模式在 stm32_gpio.cpp 才会具体配置。
(3) 控制回路中断
HAL_NVIC_SetPriority(ControlLoop_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(ControlLoop_IRQn);
HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
TIM8_UP_TIM13_IRQn优先级最高(0):硬实时定时基准。ControlLoop_IRQn优先级稍低(5):控制计算。
注意:这里
ControlLoop_IRQn被宏重命名为OTG_HS_IRQn,即 复用一个 USB HS 的 IRQ 号作为“软件中断入口”。
(4) UART A / UART B 条件初始化
只有当 odrv.config_.enable_uart_* 为 true 才会设置波特率并调用 MX_UART4_Init() / MX_USART2_UART_Init()。
(5) I2C 地址由 GPIO 组合决定
当启用 I2C 时,固件会读取 GPIO3/4/5 三个输入脚的电平,拼出 I2C 地址:
i2c_stats_.addr = (0xD << 3);
addr |= gpio3?0x1:0;
addr |= gpio4?0x2:0;
addr |= gpio5?0x4:0;
MX_I2C1_Init(addr);
这相当于板上用 3bit 拨码/跳线决定从机地址。
(6) CAN 引脚模式保护
ODrive 要求 CAN 模式不被后续 GPIO 初始化覆盖,因此会检查配置是否把 CAN_R/CAN_D 设成 CAN。
(7) Debug freeze
为了防止调试暂停时 PWM 还在跑:
__HAL_DBGMCU_FREEZE_TIM1();
__HAL_DBGMCU_FREEZE_TIM8();
__HAL_DBGMCU_FREEZE_TIM13();
(8) DRV8301 全局复位
EN_GATE 既控制驱动使能也控制 SPI 接口上电,因此必须复位:
drv_enable_gpio.write(false);
delay_us(40);
drv_enable_gpio.write(true);
delay_us(20000);
4.3 start_timers():同步启动 TIM1 / TIM8 / TIM13
这部分是 ODrive 控制链路的关键。
(1) 临时关闭 ADC 外部触发
防止启动定时器时产生意外触发:
hadc1.CR2 &= ~JEXTEN;
hadc2.CR2 &= ~(EXTEN | JEXTEN);
hadc3.CR2 &= ~(EXTEN | JEXTEN);
(2) 三个定时器同步启动 + 相位关系
Stm32Timer::start_synchronously<3>(
{&htim1, &htim8, &htim13},
{TIM1_INIT_COUNT, 0, TIM1_INIT_COUNT/2}
);
解释:
- TIM1 与 TIM8 的中心对齐三角波之间引入固定相位差(文中注释说 TIM1 领先 TIM8 90°)。
- TIM13 reload 与 TIM1 的某个 update 对齐,用于辅助控制/校准节拍。
(3) 重新打开 ADC 外部触发并清标志
开启 ADC 外触发后,会清 JEOC/EOC/OVR,确保第一拍采样是干净的。
(4) 使能 TIM8 Update 中断
__HAL_TIM_CLEAR_IT(&htim8, TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim8, TIM_IT_UPDATE);
TIM8 update 是整个控制循环的“心跳”。
5. PWM—ADC—控制回路的中断链(最重要部分)
5.1 fetch_and_reset_adcs():读取 3 路 ADC 并清状态
函数签名:
static bool fetch_and_reset_adcs(
std::optional<Iph_ABC_t>* current0,
std::optional<Iph_ABC_t>* current1);
职责:
- 判断 ADC1/2/3 是否全部完成采样(EOC/JEOC)
- 读取 Vbus 与两轴电流 ADC 值
- 将 ADC 原始值转换成相电流(phB/phC,然后合成 phA=-B-C)
- 清 ADC SR 标志,准备下一次采样
关键点:
- M0 电流来自
ADC2->JDR1和ADC3->JDR1(注入通道) - M1 电流来自
ADC2->DR和ADC3->DR(规则通道) - Vbus 来自
ADC1->JDR1
这体现出:两个电机在同一时刻采样,但通过“注入/规则组”分离通道,减少 ADC 资源冲突。
5.2 TIM8_UP_TIM13_IRQHandler():PWM 更新中断(硬实时)
触发源:TIM8 Update Event(中心对齐 PWM 的更新点)。
核心逻辑:
- 清 TIM8 Update 中断标志
- 判断当前计数方向(上数/下数)
- 维护
timestamp_(时间戳) - 在“上数半周期”执行采样回调并触发控制软中断
- 在“下数半周期”预设 PWM 为 50%,防止 deadline missed
关键代码行为解释:
(1) 计数方向用于区分“真实电流采样”和“零矢量采样”
bool counting_down = TIM8->CR1 & TIM_CR1_DIR;
- 上数:通常对应 SVM 矢量区间内的“真实导通”采样
- 下数:通常对应零矢量,电流接近 0,用于 offset/dc 校准
(2) timer_update_missed 检测
bool timer_update_missed = (counting_down_ == counting_down);
如果连续两次进入中断都处于同一方向,说明漏了一次 update 事件,属于严重时序错误:
Motor::ERROR_TIMER_UPDATE_MISSED
(3) 上数时:采样 + 触发控制软中断
odrv.sampling_cb();
NVIC->STIR = ControlLoop_IRQn;
sampling_cb():通常处理 DMA/测量等采样相关逻辑STIR:软件触发中断,相当于“立即调度控制回路计算”
(4) 下数时:设置 PWM 为 50%(安全默认值)
TIM1->CCR1 = ... = TIM_1_8_PERIOD_CLOCKS/2;
TIM8->CCR1 = ... = TIM_1_8_PERIOD_CLOCKS/2;
含义:
- 如果控制计算没赶上截止时间,下一个周期 PWM 就会被强制拉回“中性占空比”,避免异常输出。
5.3 ControlLoop_IRQHandler():控制回路软中断(核心控制计算)
触发方式:由 TIM8_UP_TIM13_IRQHandler 写 NVIC->STIR 触发。
控制回路步骤可以拆成 6 个阶段:
Stage A:确保 ADC 采样完成
调用 fetch_and_reset_adcs(),如果 ADC 未完成,则报时序错误:
Motor::ERROR_BAD_TIMING
Stage B:若 PWM 未使能,则电流置零
当 MOE 没开(电机未臂动),电流测量无意义:
if (!(TIM1->BDTR & TIM_BDTR_MOE_Msk)) current0 = {0,0,0};
Stage C:电流测量回调
motors[0].current_meas_cb(timestamp - TIM1_INIT_COUNT, current0);
motors[1].current_meas_cb(timestamp, current1);
注意 M0 的时间戳带 -TIM1_INIT_COUNT 偏移,说明 M0/M1 的采样相位不同(与 start_timers 同步策略一致)。
Stage D:主控制回调
odrv.control_loop_cb(timestamp);
这是整个 ODrive 的核心闭环计算入口(FOC、电流环、速度环、位置环等)。
Stage E:等待 ADC 进入下一次采样(DC 校准)
while (!(ADC2->SR & ADC_SR_EOC));
然后再次 fetch_and_reset_adcs() 获取下一组数据,用于:
dc_calib_cb():零电流 offset 校准
Stage F:PWM 更新回调 + deadline 检查
motors[x].pwm_update_cb(...);
最后检查 timestamp_ 是否严格前进一个周期,若不满足,说明控制计算超时:
Motor::ERROR_CONTROL_DEADLINE_MISSED
这套结构体现了 ODrive 的硬实时设计目标:
- TIM8 update 是硬时钟
- ControlLoop 是软触发中断
- 两者之间用 timestamp 检测“漏拍/超时”
5.4 SPI DMA 完成回调(仲裁器解锁)
当 SPI3 DMA Tx/Rx 完成:
spi3_arbiter.on_complete();
这用于通知上层“本次 SPI 事务结束”,释放总线给下一设备。
5.5 TIM5_IRQHandler():PWM 输入捕获
pwm0_input.on_capture();
TIM5 被配置为输入捕获/测量 PWM 脉宽(例如 RC PWM)。
6. CubeMX 外设初始化模块(Inc/Src)
这部分代码多为“外设寄存器配置”,但 ODrive 有一些关键点值得强调。
6.1 gpio.c / gpio.h(基础 GPIO 配置)
文件:Src/gpio.c
这里只配置了最基础且“必须在早期生效”的引脚:
M0_nCS / M1_nCS:默认拉高(不选中 SPI 从设备)EN_GATE:默认拉低(关闭驱动)nFAULT:输入上拉
另外它会根据硬件版本包含旧版本配置:
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 || 2
#include "prev_board_ver/gpio_V3_2.c"
#elif HW_VERSION_MINOR == 3 || 4
#include "prev_board_ver/gpio_V3_4.c"
#endif
6.2 dma.c / dma.h(DMA 流配置)
主要是开启 DMA 控制器时钟、设置中断优先级。
在 ODrive 中 DMA 主要服务于:
- SPI3 Tx/Rx
- UART4/USART2 Rx/Tx
- I2C1 Rx/Tx
特别注意 SPI3:
- TX DMA 优先级更高于 RX(避免时序被打断)。
6.3 adc.c / adc.h(ADC1/2/3:电流与母线电压采样)
ODrive 使用 3 路 ADC:
- ADC1:VBUS(注入通道为主)
- ADC2:M0/M1 相电流采样(注入 + 规则)
- ADC3:M0/M1 相电流采样(注入 + 规则)
adc.c 同样带硬件版本兼容:
#if HW_VERSION_MINOR == 1 || 2
#include "prev_board_ver/adc_V3_2.c"
#elif HW_VERSION_MINOR == 3 || 4
#include "prev_board_ver/adc_V3_4.c"
#endif
设计要点
- 中心对齐 PWM + ADC 外部触发:保证采样落在电流波形最稳定的位置。
- 注入组 + 规则组:在同一 ADC 上交替服务两台电机,降低资源占用。
6.4 tim.c / tim.h(定时器:PWM/编码器/PWM输入/同步)
ODrive v3 中定时器分工非常清晰:
(1) TIM1:M0 三相互补 PWM(中心对齐)
CounterMode = CENTERALIGNED3PWM2输出模式DeadTime = TIM_1_8_DEADTIME_CLOCKSTRGO_UPDATE:作为 ADC 触发源之一
(2) TIM8:M1 三相互补 PWM(中心对齐)
与 TIM1 类似,是第二个电机的 PWM 定时器。
(3) TIM13:同步辅助定时器
Period 由公式计算,确保与 TIM1/TIM8 的周期关系满足同步。
(4) TIM3 / TIM4:编码器接口
HAL_TIM_Encoder_Init()EncoderMode = TI12IC Filter = 4
(5) TIM5:PWM 输入捕获
TIM5 使能中断 TIM5_IRQn,由 TIM5_IRQHandler() 调用 pwm0_input.on_capture()。
6.5 spi.c / spi.h(SPI3:16-bit + DMA)
SPI3 配置特点:
- 16-bit 数据帧(适配编码器/驱动寄存器读写)
CLKPhase = 2EDGE- MOSI/SCK 默认下拉(空闲为 0)
- MISO 上拉(用于断线检测/偶校验设备稳定电平)
DMA:
- TX:
DMA1_Stream7 - RX:
DMA1_Stream0
完成中断由 HAL_SPI_TxRxCpltCallback() 进入仲裁器 on_complete()。
6.6 usart.c / usart.h(UART4/USART2:DMA + 环形接收)
- UART4:通常映射为 UART_A
- USART2:作为 UART_B(注释中提到未来板子版本可支持更多)
DMA RX 采用 DMA_CIRCULAR(典型串口持续接收方案)。
中断优先级设置为 10(相对控制回路更低)。
6.7 i2c.c / i2c.h(I2C1:地址可配置 + DMA)
MX_I2C1_Init(uint8_t addr):与常规 CubeMX 不同之处在于:
- OwnAddress1 由外部传入,用于 I2C 从机模式
DMA:
- RX:
DMA1_Stream0circular - TX:
DMA1_Stream6normal
6.8 can.c / can.h(CAN1:2Mbps 配置)
CAN 初始化采用 hcan1 直接静态初始化结构体(而不是 MX_CAN1_Init())。
典型参数:
- Prescaler=8
- SJW=4TQ
- BS1=16TQ
- BS2=4TQ
并开启 CAN1_TX/RX0/RX1/SCE 中断。
6.9 usb_device.c + usbd_*(USB CDC 虚拟串口)
这部分是 STM32 USB Device Library 的标准实现:
usb_device.c:初始化入口usbd_cdc_if.c:CDC 类接口(收发回调)usbd_conf.c:PCD 底层(中断/端点)usbd_desc.c:设备描述符
FreeRTOS 默认任务 StartDefaultTask() 里只做一件事:
MX_USB_DEVICE_Init();
ODrive 其它任务/线程通常由 odrive_main 自己创建与调度。
7. stm32f4xx_it.c(异常与中断入口)
这里主要关注两个点:
7.1 HardFault 捕获寄存器 + 立刻关 PWM
HardFault_Handler() 用 naked 汇编拿到堆栈指针,跳转 get_regs()。
get_regs() 做了两件非常关键的安全动作:
- 关闭 TIM1/TIM8 的 MOE(停止 PWM)
- 把 r0/r1/r2/r3/pc/lr/psr/cfsr 等寄存器读出来(便于调试)
并通过 while(stay_looping); 卡死,方便你接 JTAG 查看现场。
7.2 统计 IRQ 调用次数
多处看到:
COUNT_IRQ(xxx_IRQn);
这通常用于在线监测中断频率/丢拍(ODrive 的实时性诊断手段)。
8. prev_board_ver(旧板兼容)
目录:
Inc/prev_board_ver/Src/prev_board_ver/
包含:
adc_V3_2.c / adc_V3_4.cgpio_V3_2.c / gpio_V3_4.c
CubeMX 生成的 adc.c/gpio.c 在文件顶部用 #include 直接把旧版本实现“编译进来”。
优点:
- 同一份固件代码可以通过编译宏覆盖不同的 pinout/ADC 方案。
缺点:
- 代码组织不够模块化,但对固件团队维护“版本矩阵”很高效。
9. 你在阅读/移植时最需要关注的 10 个关键点
- TIM1/TIM8 必须是中心对齐 PWM,并且死区、重复计数器等参数必须匹配
mxconstants.h。 - ADC 外部触发与采样点 决定电流测量质量,这是 FOC 成败关键。
- TIM8_UP_TIM13_IRQHandler 是硬实时心跳,优先级应最高。
- ControlLoop_IRQHandler 的 deadline 检测 是 ODrive 安全策略的一部分。
- SPI3 DMA 的优先级与 on_complete() 决定多设备 SPI 的稳定性。
- EN_GATE 同时影响驱动功率级与 SPI 接口,复位时序必须满足 datasheet。
- I2C 地址由 GPIO 组合决定,如果你外接设备,记得避免地址冲突。
- 硬件版本 OTP 校验 能防止“固件与板子不匹配”导致烧毁风险。
- HardFault 一进来就关 PWM 是安全底线,移植时务必保留。
gpios[]与alternate_functions[]决定了 ODrive 的 GPIO 模式动态切换能力。
附录:文件逐个“模块-函数”速查表
A. 板级聚合层
board.h
system_init():系统初始化、OTP 版本校验board_init():外设初始化与中断配置start_timers():同步启动 TIM1/TIM8/TIM13
board.cpp
check_board_version():OTP 版本比对system_init():HAL_Init + 时钟 + OTP 校验board_init():外设 init + EXTI/控制中断 + UART/I2C + DRV resetstart_timers():同步启动三 TIM,开 ADC 触发fetch_and_reset_adcs():读取电流/Vbus 并清 ADC 状态TIM8_UP_TIM13_IRQHandler():PWM update 心跳 + 触发控制软中断ControlLoop_IRQHandler():闭环计算 + DC 校准 + PWM 更新HAL_SPI_TxRxCpltCallback():SPI3 arbiter 完成通知TIM5_IRQHandler():PWM 输入捕获
B. CubeMX 外设初始化层
main.c
SystemClock_Config():系统时钟树配置Error_Handler():错误处理
gpio.c
MX_GPIO_Init():最基础 GPIO 初始化(CS/EN/nFAULT 等)
dma.c
MX_DMA_Init():DMA 时钟与中断
adc.c
MX_ADC1_Init()/MX_ADC2_Init()/MX_ADC3_Init():ADC 配置HAL_ADC_MspInit():GPIO/时钟/DMA(如有)
tim.c
MX_TIM1_Init():M0 PWMMX_TIM8_Init():M1 PWMMX_TIM13_Init():同步辅助MX_TIM3_Init()/MX_TIM4_Init():编码器MX_TIM5_Init():PWM 输入捕获
spi.c
MX_SPI3_Init():SPI3 + DMAHAL_SPI_MspInit():引脚/DMA 绑定
usart.c
MX_UART4_Init():UART_AMX_USART2_UART_Init():UART_BHAL_UART_MspInit():DMA、IRQ
i2c.c
MX_I2C1_Init(uint8_t addr):I2C1,从机地址由外部传入
can.c
HAL_CAN_MspInit():CAN1 引脚/IRQ(Init 结构体写死在 hcan1)
freertos.c
MX_FREERTOS_Init():创建 defaultTaskStartDefaultTask():只初始化 USB
stm32f4xx_it.c
HardFault_Handler()/get_regs():关 PWM + 现场寄存器- 多个 DMA/UART/CAN/USB IRQ 入口
stm32f4xx_hal_timebase_TIM.c
- HAL tick 替代 SysTick 的实现(基于 TIM14)
usb_device.c + usbd_*.c
- USB CDC 虚拟串口设备栈初始化与回调
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)