YuanHub 源码分析【三】电流环的初始化、应用和 Simulink 建模
目录
一、定时器外设初始化和中断
1.1 初始化
外设由 CubeMX 初始化:

其中电机的 PWM 外设由 TIM1 进行初始化:
int main(void)
{
...
MX_TIM1_Init();
...
}
下面是 TIM1 的初始化代码:
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIMEx_BreakInputConfigTypeDef sBreakInputConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
htim1.Init.Period = 6000;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC4REF;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sBreakInputConfig.Source = TIM_BREAKINPUTSOURCE_BKIN;
sBreakInputConfig.Enable = TIM_BREAKINPUTSOURCE_ENABLE;
sBreakInputConfig.Polarity = TIM_BREAKINPUTSOURCE_POLARITY_HIGH;
if (HAL_TIMEx_ConfigBreakInput(&htim1, TIM_BREAKINPUT_BRK, &sBreakInputConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM2;
sConfigOC.Pulse = 3000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
sConfigOC.Pulse = 1;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 25;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
sBreakDeadTimeConfig.BreakFilter = 8;
sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
配置如下:
| 分类 | 参数项目 | 配置值 | 功能说明 |
| 计数器 | 计数模式 | 中心对齐模式 1 | 产生对称 PWM,有利于电机控制减小谐波 |
| (Time Base) | 周期 (Period) | 6000 | 决定 PWM 频率,中心对齐下周期翻倍 |
| 预分频 (Prescaler) | 0 | 定时器时钟不分频,保持最高精度 | |
| 通道输出 | PWM 模式 | PWM 模式 2 | 通道 1/2/3 设为 50% 占空比 (Pulse=3000) |
| (OC 1-3) | 空闲状态 | 低电平 (Reset) | 停止运行时确保输出安全电平 |
| 触发同步 | 主模式 (TRGO) | OC4REF | 将通道 4 的比较信号作为触发源(常用于 ADC) |
| (Master) | 通道 4 脉宽 | 1 | 极小脉宽,精准触发采样点(避开开关噪声) |
| 硬件保护 | 死区时间 | 25 | 防止功率管(半桥)上下臂同时导通短路 |
| (Break/DT) | 刹车输入 | 使能 (BKIN) | 低电平触发强制关闭输出,保护硬件 |
| 过滤器 | 8 | 滤除刹车引脚的电磁干扰噪声 |
1.2 电流环中断函数
1.2.1 注入组原理
因为我们配置的是注入组中断,所以中断函数也是注入组触发,其在每一个 cnt 上升沿信号等于TIM1 通道 4 的 OC4 输出比较通道即会被触发。
如果我们的:
sConfigOC.Pulse = 1; //通道4
那么在 CNT 从 0 跳变到 1 的上升沿即会触发中断。
下面是触发注入组中断的函数:
1.2.2 CURRENT_LOOP_IRQ_TASK()
#define CURRENT_LOOP_IRQ_TASK HAL_ADCEx_InjectedConvCpltCallback
// 电流环中断任务 典型频率 20KHZ
void CURRENT_LOOP_IRQ_TASK(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == UVW_CURRENT_U_HANDLE.Instance)
{
bsp_set_timer_record_stop(SYS_TIMER_RECORD_CURRENT_LOOP_CYCLE_INDEX); // 测量电流环周期
bsp_set_timer_record_start(SYS_TIMER_RECORD_CURRENT_LOOP_CYCLE_INDEX);
bsp_set_timer_record_start(SYS_TIMER_RECORD_CURRENT_LOOP_TIME_INDEX); // 测量电流环运行时间
// 硬件自检完毕且电流校准通过,运行电流环
if (sys_get_hardware_self_test_status() == true &&
sys_get_current_calibration_status() == CURRENT_CALIBRATION_STATUS_OK)
{
CurrentLoopCtrl();
}
#ifdef VIRTUAL_MOTOR_MODEL
SimPlantStep();
#endif
bsp_set_timer_record_stop(SYS_TIMER_RECORD_CURRENT_LOOP_TIME_INDEX);
// 第一次运行完电流环后启动常规非同步周期位置环
if (kBspData.pl_start_state == false)
{
kBspData.pl_start_state = true;
// 1. 重置计数器
POSITION_LOOP_TIM_HANDLE.Instance->CNT = 0;
// 2. 使能中断和计数器
POSITION_LOOP_TIM_HANDLE.Instance->DIER |= TIM_DIER_UIE;
POSITION_LOOP_TIM_HANDLE.Instance->CR1 |= TIM_CR1_CEN;
// 3. 生成更新事件
POSITION_LOOP_TIM_HANDLE.Instance->EGR |= TIM_EGR_UG;
}
}
}
1.2.3 计时器
其使用了 DWT 时钟周期计数器来记录时间,共有
- sys_timer_init()
- set_timer_record_start()
- set_timer_record_stop()
- get_timer_duration_records_us()
四个函数。
下面是 CURRENT_LOOP_IRQ_TASK() 所使用的计时器代码:
SysTimer_t sys_timer = {
.sys_freq_Mhz = 0,
.sys_freq_Mhz_inv = 0,
.now_records = {0},
.duration_records = {0},
};
void sys_timer_init(void)
{
if (!((DEM_CR & DEM_CR_TRCENA) && DWT_CR & DWT_CR_CYCCNTENA))
{
DEM_CR |= DEM_CR_TRCENA;//第一步使能DWT外设
DWT_LAR |= DWT_LAR_UNLOCK;
DWT_CYCCNT = 0u;//第二步在使能DWT计数前先清零
DWT_CR |= DWT_CR_CYCCNTENA;//第三步使能DWT计数
}
sys_timer.sys_freq_Mhz = HAL_RCC_GetSysClockFreq() / 1000000;
sys_timer.sys_freq_Mhz_inv = 1.0f / (float)sys_timer.sys_freq_Mhz;
}
void set_timer_record_start(SYS_TIMER_RECORD_INDEX index)
{
sys_timer.now_records[index] = DWT_CYCCNT;
}
void set_timer_record_stop(SYS_TIMER_RECORD_INDEX index)
{
sys_timer.duration_records[index] = DWT_CYCCNT - sys_timer.now_records[index];
}
float get_timer_duration_records_us(SYS_TIMER_RECORD_INDEX index)
{
return sys_timer.sys_freq_Mhz_inv * sys_timer.duration_records[index];
}
1.3.4 第一次启动
电流环需要先于位置环执行一次,因为位置环 PosSpeedLoopCtrl() 的输出最终是电流指令,会喂给电流环去执行。如果位置环先跑起来,而电流环还没开始工作,位置环产生的电流指令就没人处理——这会导致控制状态不一致。
// 第一次运行完电流环后启动常规非同步周期位置环
if (kBspData.pl_start_state == false)
{
kBspData.pl_start_state = true;
// 1. 重置计数器
POSITION_LOOP_TIM_HANDLE.Instance->CNT = 0;
// 2. 使能中断和计数器
POSITION_LOOP_TIM_HANDLE.Instance->DIER |= TIM_DIER_UIE;
POSITION_LOOP_TIM_HANDLE.Instance->CR1 |= TIM_CR1_CEN;
// 3. 生成更新事件
POSITION_LOOP_TIM_HANDLE.Instance->EGR |= TIM_EGR_UG;
}
二、电流环逻辑
2.1 电流环函数
/**
* @brief 电流环中断循环
*/
void CurrentLoopCtrl(void)
{
// 电流环系统输入
// 1. 读取当前实际三相电流
bsp_get_phase_current(kAxis.current_ctl_input.iabc_now_A);
// 2. 执行电流环控制算法
CurrentCtlLoopTask(&kAxis, &kAxisDw);
// 3. 输出电机控制电压
bsp_set_phase_voltage(kAxis.current_ctl_output.uabc_tar_comp_V);
}
2.2 获取三项电流
从 ADC 值计算出电流的公式如下:
- ADC = ADC 最大量程 (V)
- Resolution = ADC 分辨率 (12位就是4096)
- Gain = 放大倍数
- Rshunt = 采样电阻值
- K = 电流采样ADC转化系数
下面就是计算 K 的分辨率:
#define UVW_CURRENT_SAMP_ADC_K (ADC_REFERENCE_V / ADC_RESOLTION /(CURRENT_SAMP_OPAMP_VOLTAGE_GAIN * UVW_CURRENT_SAMP_RES)) //UVW电流采样ADC转化系数
除此之外,还需要减去电流运放零点,全部代码如下:
/**
* @brief 获取UVW三相电流
* @param[out] piabc UVW三相电流数组,单位A
*/
void bsp_get_phase_current(float piabc[3])
{
#ifdef VIRTUAL_MOTOR_MODEL
piabc[0] = kAxis.sim_plant_output.iabc_now_A[0];
piabc[1] = kAxis.sim_plant_output.iabc_now_A[1];
piabc[2] = kAxis.sim_plant_output.iabc_now_A[2];
#else
uint16_t drift[3] = {0};
if (false == bsp_get_pwm_state()) // PWM未准备好,电流采样回读值强制为0
{
kBspData.uvw_current[0] = 0.0f;
kBspData.uvw_current[1] = 0.0f;
kBspData.uvw_current[2] = 0.0f;
}
else
{
sys_get_current_calibration_drift(drift);
kBspData.uvw_current[0] = UVW_CURRENT_DIRECTION *
(float)(int32_t)(UVW_CURRENT_U_CHANNEL - drift[0]) * UVW_CURRENT_SAMP_ADC_K;
kBspData.uvw_current[1] = UVW_CURRENT_DIRECTION *
(float)(int32_t)(UVW_CURRENT_V_CHANNEL - drift[1]) * UVW_CURRENT_SAMP_ADC_K;
kBspData.uvw_current[2] = UVW_CURRENT_DIRECTION *
(float)(int32_t)(UVW_CURRENT_W_CHANNEL - drift[2]) * UVW_CURRENT_SAMP_ADC_K;
}
piabc[0] = kBspData.uvw_current[0];
piabc[1] = kBspData.uvw_current[1];
piabc[2] = kBspData.uvw_current[2];
#endif
}
2.3 调用 Simulink 模型
在下面的函数调用 Simulink 的 current_ctl_loop_task 模型代码:
// 电流环中断调用
void CurrentCtlLoopTask(Axis *const axis, AxisDw *const axis_dw)
{
current_ctl_loop_task(&axis->current_ctl_input, &axis->current_ctl_config,
&axis->current_ctl_output,
&(axis_dw->current_ctl_loop_task_InstanceData.rtdw));
}
其中输入的结构如下:
2.3.1 CurrentCtlInput
typedef struct
{
/* 目标id iq 电流 */
real32_T idq_tar_A[2];
/* iabc 三相当前电流反馈 */
real32_T iabc_now_A[3];
/* 当前电角度 */
real32_T elec_theta_rad;
/* 当前母线电压 */
real32_T dc_bus_now_V;
/* 电流环控制模式 */
uint8_T mode;
/* 三相目标电压 */
real32_T uabc_tar_V[3];
/* 驱动器当前温度 */
real32_T driver_temp;
/* 电角速度 */
real32_T elec_angle_speed_rad_s;
/* iq 目标电流偏置 */
real32_T iq_offset_A;
}
CurrentCtlInput;
2.3.2 CurrentCtlOutput
typedef struct
{
/* 死区补偿后目标三相电压 */
real32_T uabc_tar_comp_V[3];
/* 当前dq轴电流 */
real32_T idq_now_A[2];
/* 补偿前目标三相电压 */
real32_T uabc_tar_org_V[3];
}
CurrentCtlOutput;
2.3.3 CurrentCtlConfig
typedef struct
{
/* 电流采样噪声绝对值 */
real32_T i_noise_A;
/* 死区补偿电压大小 */
real32_T comp_du_V;
/* 电流环目标带宽 % */
real32_T bandwidth_percentage;
/* 电流环运行周期 */
real32_T dt_s;
/* 磁链系数 */
real32_T flux_wb;
/* d轴 kp控制增益 */
real32_T kp_ld;
/* d轴 ki控制增益 */
real32_T ki_ld;
/* q轴 kp控制增益 */
real32_T kp_lq;
/* q轴 ki控制增益 */
real32_T ki_lq;
/* pwm 占空比最大值 */
real32_T pwm_duty_cycle_max;
/* 电角度补偿系数 */
real32_T elec_angle_compensation;
/* UVW相序 */
int8_T phase_dir;
}
CurrentCtlConfig;
2.4 设置三项电压输出
在 CurrentLoopCtrl() 函数中,将 current_ctl_loop_task 模型计算出的电压值输入到 bsp_set_phase_voltage() 函数中,其值输出电压单位是伏特 (V) 。
bsp_set_phase_voltage(kAxis.current_ctl_output.uabc_tar_comp_V);
下面是 bsp_set_phase_voltage() 代码:
/**
* @brief 设置相电压
* @param[in] voltage UVW相电压数组(单位V)
*/
void bsp_set_phase_voltage(const float voltage[3])
{
#ifdef VIRTUAL_MOTOR_MODEL
kAxis.sim_plant_input.uabc_tar_V[0] = voltage[0];
kAxis.sim_plant_input.uabc_tar_V[1] = voltage[1];
kAxis.sim_plant_input.uabc_tar_V[2] = voltage[2];
#else
uint32_t uabc_tar[3] = {0};
if (kBspData.dc_bus_voltage_val < 8.0f)
{
return;
}
kBspData.uvw_target_voltage[0] = voltage[0];
kBspData.uvw_target_voltage[1] = voltage[1];
kBspData.uvw_target_voltage[2] = voltage[2];
for (uint8_t i = 0; i < 3; i++)
{
uabc_tar[i] = voltage[i] * ((float)PWM_TIM_ARR / kBspData.dc_bus_voltage_val) + PWM_TIM_ARR_LIMIT_HALF;
if (uabc_tar[i] > PWM_TIM_ARR_P_LIMIT)
{
uabc_tar[i] = PWM_TIM_ARR_P_LIMIT;
}
if (uabc_tar[i] < PWM_TIM_ARR_N_LIMIT)
{
uabc_tar[i] = PWM_TIM_ARR_N_LIMIT;
}
uabc_tar[i] = PWM_TIM_ARR - uabc_tar[i]; // CCR值越大,占空比越小
}
PWM_TIM_U_CCR_VAL = uabc_tar[0];
PWM_TIM_V_CCR_VAL = uabc_tar[1];
PWM_TIM_W_CCR_VAL = uabc_tar[2];
#endif
}
uabc_tar[i] = voltage[i] * ((float)PWM_TIM_ARR / kBspData.dc_bus_voltage_val) + PWM_TIM_ARR_LIMIT_HALF;
三、电流环 Simulink 模型
3.1 电流环模型概览
输入、配置和输出关系如下:


3.2 current_ctl_loop 内部结构
下图红框内从左到右分别是
- 电流环 PI 模块
- SVPWM 模块
- 死区补偿模块

3.2.1 current_ctl_pi
固定的 FOC 框架:
- 读取三相电流
- 变 d/q 轴
- 与目标电流求误差做 PI 控制
- 算出 d/q 轴目标电压
- 反变换回三相电压指令

在 current_ctl_pi 中,使用母线的 2/3 作为电压极限圆的上线,规定了:
- limit_up
- limit_low
两个变量,这是逆变器物理调制出相电压的极限。
其他则实现了 q 轴和 d 轴的 PI 调节:

如果 pi 过大 udq_max_lim 将做电压矢量限幅,并且采用了d 轴优先的限幅策略。
逆变器能输出的最大电压受限于当前的直流母线电压。输出的合成电压矢量必须落在一个极限圆内 半径即为传入的 limit_up 参数:
function [ud, uq] = udq_max_lim(ud_tar, uq_tar, v_out_limit)
uq_temp = single(v_out_limit^2-ud_tar^2);
if(uq_temp >= 0)
uq_max = single(sqrt(uq_temp));
else
uq_max = single(0);
end
if(uq_tar > uq_max)
uq_tar = uq_max;
elseif(uq_tar < -uq_max)
uq_tar = -uq_max;
end
ud = single(ud_tar);
uq = single(uq_tar);
3.2.2 svpwm
3.2.3 deadband_comp_uc
三项分别做死区补偿:

内部定义如下:

deadband_comp 的内部的精巧之处在于,以噪音 i_noise 为范围,在电流过零点的时候增大电压。其增大的电压以计算出的 k 乘当前电流,也就是电流越大,补偿越多。其电流补偿符号也是基于当前电流方向。
代码如下:
function u_tar_comp = deadband_comp(u_tar, i_now, i_noise, comp_du)
if i_noise < 0.00001
u_tar_comp = single(0);
return; %入参 i_noise 必须 > 0.00001 是正数
end
du = single(0); %补偿电压大小
line_range = i_noise * single(2); %线性区域范围
k = comp_du / line_range; %线性补偿斜率
if i_now <= line_range && i_now >= -line_range % 在线性补偿范围内
du = k * i_now;
else %在非线性范围
if i_now > single(0)
du = comp_du;
else
du = -comp_du;
end
end
u_tar_comp = u_tar + du;
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)