目录

一、定时器外设初始化和中断

1.1 初始化

1.2 电流环中断函数

1.2.1 注入组原理

1.2.2 CURRENT_LOOP_IRQ_TASK()

1.2.3 计时器

1.3.4 第一次启动

二、电流环逻辑

2.1 电流环函数

2.2 获取三项电流

2.3 调用 Simulink 模型

2.3.1 CurrentCtlInput

2.3.2 CurrentCtlOutput

2.3.3 CurrentCtlConfig

2.4 设置三项电压输出

三、电流环 Simulink 模型

3.1 电流环模型概览

3.2 current_ctl_loop 内部结构

3.2.1 current_ctl_pi

3.2.2 svpwm

3.2.3 deadband_comp_uc


一、定时器外设初始化和中断

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 值计算出电流的公式如下:

K = (\frac{V_{ref}}{Resolution})\cdot \frac{1}{Gain}\cdot \frac{1}{R_{shunt}}

  • 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 框架:

  1. 读取三相电流
  2. 变 d/q 轴
  3. 与目标电流求误差做 PI 控制
  4. 算出 d/q 轴目标电压
  5. 反变换回三相电压指令

在 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;

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐