1 引言
本博客是为了记录自己在无人机行业中,所涉及的技术成长路线。既是记录自己的成长轨迹,亦是总结所学,便于后续持续学习进步。
本blog记录了使用STM32F407 + EG2133三相独立半桥驱动芯片 + MT6701磁性角度编码器芯片 + 无刷电机(10对极)无刷电机,使用六步换向驱动电机转动实验过程。记录可能理解浅薄,请多包涵。
2 实验过程
2.1 机械角度和电角度
机械角度:
转子实际旋转的角度。
如:转子正方向旋转一圈,那么机械角度是360°;正方向旋转两圈,则机械角度是720°。
若:转子反方向旋转一圈,那么机械角度是-360°;反方向旋转两圈,则机械角度是-720°。
电角度:
电角度是描述转子与磁场相关的角度。
(1)转子在磁场中旋转会产生正弦交变电流,一个完整的正弦波周期对应 360° 电角度。
(2)电角度本质上衡量的是“时间”和“相位”,而不是“空间位置”。
电角度和机械角度关系:
电角度 = 机械角度 x 电机磁极对数。
若电机有N对磁极,转子旋转一圈,机械角度=360°,电角度=360° x 磁极对数N。

2.2 死区产生
来自deepseek回答:只要无刷电机(BLDC/PMSM)使用“三相全桥逆变器”驱动,且采用“PWM脉宽调制”方式,那么在每一个PWM周期中,都必然存在死区时间。
物理原因:MOSFET功率管的开通速度永远比关断速度快
如果没有死区时间,那么当程序命令“上桥开通”的同时命令“下桥关断”,由于下桥关断有延迟,而上桥瞬间就通了,这会导致上下桥臂瞬间直通(贯穿),相当于把电源正负极直接短路。结果就是:巨大的尖峰电流烧毁MOS管。
死区时间是为了规避功率开关管固有的‘开通延迟小于关断延迟’特性,通过插入一段两管均截至的空白区间,以防止上下桥臂发生贯穿性短路。
死区时间中,下桥臂的驱动信号全部被硬件强制拉低,保证上桥完全关闭后,再开通下桥。属于偷走共有的电压输出时间。
死区时间是插入在PWM周期内部的,如下图示。

2.3 死区时间计算
说是不同的芯片,死区的计算公式不一样。这里用了STM32F407单片机,故就一起看看STM32F407 的死区时间计算。
公式:死区时间 = DTG值 × t_DTS
t_DTS = 1 / f_TIM = 1 / 168MHz ≈ 5.95ns
STM32F4 参考手册完整公式(DTG 分段编码):
| DTG[7:5] | 死区时间公式 | DTG 范围 | 死区范围 @168MHz |
| 0xx | DT = DTG[7:0] × t_DTS | 0~127 | 0~756ns |
| 10x | DT = (64 + DTG[5:0]) × 2 × t_DTS | 128~191 | 760ns~1.5µs |
| 110 | DT = (32 + DTG[4:0]) × 8 × t_DTS | 192~223 | 1.5µs~3.0µs |
| 111 | DT = (32 + DTG[4:0]) × 16 × t_DTS | 224~255 | 3.0µs~4.5µs |
定时器设置的DeadTime会直接设置到OTG寄存器,如下示例。
| DeadTime | DTG 二进制 | 死区时间 | 公式 |
|---|---|---|---|
| 17 | 0b00010001 | ~101ns | 17 × 5.95ns |
| 42 | 0b00101010 | ~250ns | 42 × 5.95ns |
| 84 | 0b01010100 | ~500ns | 84 × 5.95ns |
2.4 实验记录
记录1:换相表。
换相表默认按电角度从 0° 到 360° 递增设计,当转子旋转电角度持续增加时,必须顺着步骤 0→1→2… 的换相表顺序切换通电状态,才能产生正向的旋转力矩,持续驱动电子转动的力矩。否则可能会出现转子原地震动等现象。
记录2:无刷电机无法转动且高频震动。
按照换相表,电角度从0到360°方向增长驱动电机旋转时,若磁性编码器读出的电角度是递减的,此时与换相表递增设计是相反的。即故转子在物理上正转,可编码器测量电角度却在减小(反转),按代码实际照换向逻辑,此时会产生来回变化的反向力矩,让电机剧烈“原地”抖动。
记录3:机械角度0°其实等于电角度0°
机械角度0°可通过 磁性传感器z相 输出信号定位。当转子旋转到z相0°时,此时z相输出高电平,单片机通过上升沿捕获,标记此时机械角度0度。
电角度0°是描述电磁起始点。如果无刷电机有多对磁极,那么机械角度0°实际上是对应“其中某一对极”的电角度0°,而对于其它的对极电角度不是0度。
重要!!!
(1)当转子转到机械0度时,如果电机是2极(1对极),此时电角度直接就是0度;
(2)对于多对极电机,一般会将机械角度为0时,将此时的电角度直接定义为0度;
记录4:实际电角度计算时,要“手动” 减去 机械角度偏移,否则电角度计算有误差。
简单测量机械角度的设备一般是霍尔传感器,如项目中使用了MT6701传感器测量转子位置。当转子转到z相输出高电平时,此时机械角度为“0”时(转子稳定位置),但霍尔传感器实测机械角度输出并不是0度,而由于转子稳定时,电角度是可定义成0度的。
根据电角度和机械角度关系式:
理想状态:电角度 = 机械角度 x 电极对数
实际状态:电角度= (实测机械角度 - 偏移角度)x 电极对数
此时,在软件算法中对于电角度的计算,是必须要减去当电角度为0时的机械角度,这样才能使用:电角度 = 机械角度 x 电极对数 这个公式。
电角度0度 = 0 x 电极对数 // 0 = (实测机械角度 - 偏移角度)
3 六步换相代码
如下是六步换相电机控制代码。
/******************************************************************************/
/* file : motorTsk.c */
/* description : motor task */
/* */
/* author : justin */
/* version : 1.0 */
/* date : 2024-01-15 */
/******************************************************************************/
/****************************************************************************/
/* Head files */
/****************************************************************************/
#include <math.h>
#include "gpio.h"
#include "fc_main.h"
#include "fc_public.h"
#include "fc_mt6701.h"
#include "fc_EG2133.h"
#include "sysMngr.h"
#include "keyTsk.h"
#include "motorTsk.h"
/****************************************************************************/
/* Marco Define */
/****************************************************************************/
#define PWM_PERIOD 8399 // TIM1 ARR值
#define PWM_HALF 4199 // 50%占空比
#define PI 3.14159265f
#define PIX2 (2.0f * PI)
/****************************************************************************/
/* Type Definitions */
/****************************************************************************/
/****************************************************************************/
/* Global Variable Declarations */
/****************************************************************************/
uint8_t g_motor_run = 0; // 电机运行标志
float g_motor_speed = 5.0f; // 目标电角度速度 (Hz)
float g_motor_voltage = 0.5f; // 电压幅值 (0.0~1.0)
/****************************************************************************/
/* static Variable Declarations */
/****************************************************************************/
static fc_core_task_id_t s_motorTsk_id = -1;
static uint8_t s_last_step = 0; // 上一次换相步骤
static uint8_t s_motor_pwr_enable = 0; // 0-off 1-enable
static uint8_t s_has_calib = 0; // 0-编码器未校准 1-电角度已标定
static float s_elec_zero_offset_deg = 0.0f; // A相对齐时编码器的机械角度(°),电角度零点偏置
static float s_comm_adv_deg = 0.0f; // 换相提前角(°),需根据实际电机标定
static int8_t s_comm_direction = -1; // +1正向 -1反向,若电机抖动可尝试改为-1
/****************************************************************************/
/* extern Variable Declarations */
/****************************************************************************/
/****************************************************************************/
/* extern functions Declarations */
/****************************************************************************/
/****************************************************************************/
/* Function Declarations */
/****************************************************************************/
/****************************************************************************/
/* */
/* Function : _motorTsk_keyCB_pwrCtl */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
static void _motorTsk_keyCB_pwrCtl
(
KEY_NO_X_VALUE keyValue,
uint32_t occurTick
)
{
s_motor_pwr_enable = (s_motor_pwr_enable==0)?(1):(0);
if(s_motor_pwr_enable == 0)
{
// pwr off
HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, GPIO_PIN_RESET);
}
else
{
// pwr on
HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, GPIO_PIN_SET);
}
return;
}
/****************************************************************************/
/* */
/* Function : _motorTsk_keyCB_callbZero */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
static void _motorTsk_keyCB_callbZero
(
KEY_NO_X_VALUE keyValue,
uint32_t occurTick
)
{
motorTsk_fixAngle();
return;
}
/****************************************************************************/
/* */
/* Function : _motorTsk_keyCB_moterRun */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
static void _motorTsk_keyCB_moterRun
(
KEY_NO_X_VALUE keyValue,
uint32_t occurTick
)
{
motorTsk_run();
return;
}
/****************************************************************************/
/* */
/* Function : _motorTsk_keyCB_moterStop */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
static void _motorTsk_keyCB_moterStop
(
KEY_NO_X_VALUE keyValue,
uint32_t occurTick
)
{
motorTsk_stop();
return;
}
/****************************************************************************/
/* 辅助函数:设置单相的工作状态(使能/浮空)并更新比较值 */
/* phase : 0=A相, 1=B相, 2=C相 */
/* enable: 1=使能输出, 0=浮空(上下管均关断) */
/* ccr : 比较值(当 enable=1 时有效) */
/****************************************************************************/
static void Motor_SetPhaseState(uint8_t phase, uint8_t enable, uint32_t ccr)
{
uint32_t ccer_mask;
switch (phase)
{
case 0: /* A相 : CH1 & CH1N */
ccer_mask = TIM_CCER_CC1E | TIM_CCER_CC1NE;
if (enable)
{
TIM1->CCR1 = ccr; // 先更新比较值
TIM1->CCER |= ccer_mask; // 再使能输出
}
else
{
TIM1->CCER &= ~ccer_mask; // 同时关闭 CH1 和 CH1N
}
break;
case 1: /* B相 : CH2 & CH2N */
ccer_mask = TIM_CCER_CC2E | TIM_CCER_CC2NE;
if (enable)
{
TIM1->CCR2 = ccr;
TIM1->CCER |= ccer_mask;
}
else
{
TIM1->CCER &= ~ccer_mask;
}
break;
case 2: /* C相 : CH3 & CH3N */
ccer_mask = TIM_CCER_CC3E | TIM_CCER_CC3NE;
if (enable)
{
TIM1->CCR3 = ccr;
TIM1->CCER |= ccer_mask;
}
else
{
TIM1->CCER &= ~ccer_mask;
}
break;
default:
break;
}
}
/****************************************************************************/
/* */
/* Function : motorTsk_tim1_update */
/* description : TIM1更新中断回调 - 基于编码器电角度的六步换相 */
/* 使用fcMT6701_GetElcAngle()获取电角度进行闭环换相 */
/* 浮空相通过关闭PWM输出实现 */
/* */
/****************************************************************************/
void motorTsk_tim1_update(void)
{
float elc_angle_deg; // 当前电角度(度),0~360
uint8_t current_step; // 当前换相步骤 (0~5)
static uint8_t s_last_step = 0xFF; // 上一次的步骤,0xFF强制首次更新
if(fcMT6701_GetCalib() == false || s_has_calib == 0)
{
return;
}
// 六步换相表 (index 0~5 对应电角度 0°~60°, 60°~120°, ... 300°~360°)
// 2: 高侧PWM(上管PWM调制,下管互补导通)
// 1: 低侧导通(下管常通,上管关断)
// 0: 浮空(上下管均关断)
const uint8_t commutation_table[6][3] = {
{2, 1, 0}, // 步骤0: A→B ( 0°~ 60°)
{2, 0, 1}, // 步骤1: A→C ( 60°~120°)
{0, 2, 1}, // 步骤2: B→C (120°~180°)
{1, 2, 0}, // 步骤3: B→A (180°~240°)
{1, 0, 2}, // 步骤4: C→A (240°~300°)
{0, 1, 2}, // 步骤5: C→B (300°~360°)
};
// 1. 计算修正后的电角度(°): 机械角度 - 偏置 → 转换为电角度
float mech_deg = fcMT6701_GetAngle() - s_elec_zero_offset_deg;
if (mech_deg < 0.0f) mech_deg += 360.0f;
elc_angle_deg = mech_deg * 10.0f; // MOTOR_POLE_PAIRS = 10
while (elc_angle_deg >= 360.0f) elc_angle_deg -= 360.0f;
// 2. 根据电角度+提前角确定换相步骤, 换相方向可调
current_step = ((uint32_t)((elc_angle_deg + s_comm_adv_deg) / 60.0f)) % 6;
if(s_comm_direction < 0) current_step = 5 - current_step;
// 3. 计算当前电压指令对应的占空比
uint32_t duty_on = (uint32_t)(g_motor_voltage * PWM_HALF);
uint32_t duty_low = 0; // 低侧常通:CCR=0 → 下管常通(上管关断)
// 4. 根据步骤是否发生变化,执行不同的更新策略
if (current_step != s_last_step)
{
/* ---- 发生了换相:重新配置三相的使能状态与占空比 ---- */
s_last_step = current_step;
for (uint8_t phase = 0; phase < 3; phase++)
{
uint8_t mode = commutation_table[current_step][phase];
if (mode == 2) // 高侧PWM
{
Motor_SetPhaseState(phase, 1, duty_on);
}
else if (mode == 1) // 低侧导通
{
Motor_SetPhaseState(phase, 1, duty_low);
}
else // mode == 0 → 浮空
{
Motor_SetPhaseState(phase, 0, 0); // enable=0, ccr值无所谓
}
}
}
else
{
/* ---- 步骤未变:仅更新高侧PWM相的占空比,跟踪电压指令变化 ---- */
for (uint8_t phase = 0; phase < 3; phase++)
{
if (commutation_table[current_step][phase] == 2)
{
// 直接更新该高侧相的CCR,低侧和浮空相保持不变
switch (phase)
{
case 0: TIM1->CCR1 = duty_on; break;
case 1: TIM1->CCR2 = duty_on; break;
case 2: TIM1->CCR3 = duty_on; break;
}
break; // 每步只有一个高侧相
}
}
}
}
/****************************************************************************/
/* */
/* Function : motorTsk_ReTOZero */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_ReTOZero
(
void
)
{
static uint8_t step = 0;
const float voltage = 0.04f; // 固定小电压
// 使用你原换相表
const uint8_t table[6][3] =
{
{2, 1, 0},
{2, 0, 1},
{0, 2, 1},
{1, 2, 0},
{1, 0, 2},
{0, 1, 2}
};
HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, GPIO_PIN_SET);
fc_delay_ms(20);
fcEG2133_enablePWM();
do
{
// 先全部浮空
TIM1->CCER &= ~(TIM_CCER_CC1E | TIM_CCER_CC1NE |
TIM_CCER_CC2E | TIM_CCER_CC2NE |
TIM_CCER_CC3E | TIM_CCER_CC3NE);
uint32_t duty_on = (uint32_t)(PWM_HALF*voltage);
uint32_t duty_low = 0;
// 设置比较值
uint32_t ccr_a = (table[step][0] == 2) ? duty_on : ((table[step][0] == 1) ? duty_low : 0);
uint32_t ccr_b = (table[step][1] == 2) ? duty_on : ((table[step][1] == 1) ? duty_low : 0);
uint32_t ccr_c = (table[step][2] == 2) ? duty_on : ((table[step][2] == 1) ? duty_low : 0);
TIM1->CCR1 = ccr_a;
TIM1->CCR2 = ccr_b;
TIM1->CCR3 = ccr_c;
// 只使能非浮空相
uint32_t ccer = 0;
if (table[step][0] != 0) ccer |= (TIM_CCER_CC1E | TIM_CCER_CC1NE);
if (table[step][1] != 0) ccer |= (TIM_CCER_CC2E | TIM_CCER_CC2NE);
if (table[step][2] != 0) ccer |= (TIM_CCER_CC3E | TIM_CCER_CC3NE);
TIM1->CCER |= ccer;
step = (step + 1) % 6;
fc_delay_ms(100);
}
while(fcMT6701_GetCalib()== false);
// Z相校准完成,关闭PWM,编码器归零
fcEG2133_disablePWM();
HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, GPIO_PIN_RESET);
fcMT6701_SetZero();
printf("step = %d\r\n", step);
return;
}
/****************************************************************************/
/* */
/* Function : motorTsk_fixAnglee */
/* description : module init */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_fixAngle
(
void
)
{
// Step1: 找Z相并归零编码器
s_has_calib = 0;
fcMT6701_ResetCalibFlag();
motorTsk_ReTOZero();
// Step2: 给A相通电,转子自动对齐到A相磁极的电角度0°位置
fcEG2133_setPWM(400, 0, 0);
fcEG2133_enablePWM();
fc_delay_ms(1000);
fcEG2133_disablePWM();
// 此时编码器的读数就是电角度零点的机械角度偏置
s_elec_zero_offset_deg = fcMT6701_GetAngle();
// 标记电角度标定完成,ISR可以开始换相
s_has_calib = 1;
printf("ElecOffset = %.1f\r\n", s_elec_zero_offset_deg);
return;
}
/****************************************************************************/
/* */
/* Function : motorTsk_run */
/* description : module init */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_run
(
void
)
{
if(fcMT6701_GetCalib() == true && s_has_calib == 1)
{
fcEG2133_enablePWM();
}
return;
}
/****************************************************************************/
/* */
/* Function : motorTsk_stop */
/* description : module init */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_stop
(
void
)
{
fcEG2133_disablePWM();
return;
}
/****************************************************************************/
/* */
/* Function : motorTsk_task */
/* description : none */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_task
(
void *user_params
)
{
static uint8_t has_init = 0;
// attention:
// task can not be blocked!!!!!
// this is 100ms task
do
{
if(has_init == 0)
{
has_init = 1;
motorTsk_ReTOZero();
}
// 电机控制任务:可以在这里添加速度调节、模式切换等逻辑
// 打印编码器信息(电角度以°显示,便于调试)
printf("pos:%d ang:%.1f eAng:%.1f off:%.1f adv:%.0f dir:%d cal:%d\r\n",
fcMT6701_GetPosition(), fcMT6701_GetAngle(),
fcMT6701_GetElcAngle() * 180.0f / PI,
s_elec_zero_offset_deg, s_comm_adv_deg,
s_comm_direction, s_has_calib);
} while(0);
return;
}
/****************************************************************************/
/* */
/* Function : motorTsk_Init */
/* description : module init */
/* */
/* Parameters : */
/* void - none */
/* */
/* Returns : none */
/* */
/* Author : justin */
/* Created : 2025-10-25 */
/* Modified : 2025-10-25 */
/* Version : 1.0.0 */
/* */
/****************************************************************************/
void motorTsk_Init
(
void
)
{
keyTsk_register_callback(KEY_NO_1_SHORT_PRESSED, _motorTsk_keyCB_callbZero);
keyTsk_register_callback(KEY_NO_2_SHORT_PRESSED, _motorTsk_keyCB_moterRun);
keyTsk_register_callback(KEY_NO_3_SHORT_PRESSED, _motorTsk_keyCB_moterStop);
keyTsk_register_callback(KEY_NO_4_SHORT_PRESSED, _motorTsk_keyCB_pwrCtl);
// 注册电机任务(100ms周期)
s_motorTsk_id = fc_core_task_register(motorTsk_task, 100, NULL);
s_motorTsk_id = s_motorTsk_id;
return;
}
实验设备图:

over!

333

被折叠的 条评论
为什么被折叠?



