YuanHub 源码分析【三】电流环的初始化、应用和 Simulink 建模

情系淮思:五款车规MCU横向对比与VCU选型 本文对比五款车规MCU中的S32K312与S32K324,聚焦核心架构、存储、通信及安全特性。S32K324为双核160MHz Cortex-M7,内存、外设(如以太网、QuadSPI、SAI)全面升级,支持高实时并行处理与复杂应用;而S32K312为单核120MHz,适用于中端车身控制。两者均满足ASIL B功能安全,具备完整安全引擎。选型建议:中端用S32K312,高端复杂系统优选S32K324。 阅读详情

目录

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

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;

21 硬件工程师笔面试高频考点真题解析——单片机 本文系统介绍了单片机的核心概念与应用知识,包括:单片机定义与选型要点(性能、存储、外设等);核心参数(主频、I/O、通信接口等);最小系统组成(电源、复位、晶振、BOOT);常见外设(GPIO、ADC、定时器等);GPIO工作模式(输入/输出);死机原因分析(硬件干扰、软件错误等);上电故障排查流程(电源、时钟、程序等);与DSP、FPGA的对比(控制vs运算vs可编程);常用通信总线特点(UART、I2C、SPI、CAN);看门狗功能(超时复位防死机)。内容涵盖单片机硬件设计关键要点,适合嵌入式开发者参考 阅读详情

相关推荐

NV MCU与SoC的交互

MCU通过IST_REQ信号触发SoC执行IST,由IST_DONE_N通知完成,SOC_RESET_N由MCU控制。MCU与SoC间通过通信通道(如以太网)传输IST结果。热/电压监测通过I2C实现,阈值由MCU初始设置、SoC动态更新,异常可中止IST。客户应用可在检测严重错误时通过IST管理器中止IST,相关原因与状态将被记录。

liu-yonggang的专栏 55

YuanHub 源码分析【二】全局变量与调试

本文介绍了YuanHub项目中Simulink控制器模型的结构体参数体系,重点解析了电流环、位置速度环等核心控制模块的参数配置。主要内容包括: Simulink模型采用模块化设计,每个功能对应独立模型文件,包含input/config/output三个结构体接口 详细说明了电流环控制结构体(CurrentCtlConfig/Input/Output)的参数定义,包括PI增益、死区补偿等关键参数 介绍了位置速度环、编码器、速度观测器等模块的结构体配置 阐述了伺服系统的参数数据库体系,包含指令层、反馈层、配置层

nullccc的博客 497

Android 开发实战:从零到一集成 espeak-ng 实现中文离线 TTS(无需账号开箱即用)

本文详细介绍了如何在 Android 项目中集成 espeak-ng,实现中文离线语音播报。通过下载 so 库、编写 Java 接口、封装管理类,开发者可快速构建无需账号的 TTS 功能。文章覆盖从零到一的开发步骤、调试技巧及性能优化,适合中高级 Android 开发者参考。

全栈 3320

基本定时器(TIM6/TIM7)

基本定时器最简单,就干一件事:到点了触发中断。定时器本质就是计数器,每个时钟脉冲加1,数到ARR就溢出、产生中断、清0重来。三个核心寄存器:PSC(预分频器):对输入时钟分频CNT(计数器):从0往上数ARR(自动重装载值):数到这个值就溢出,CNT清0,产生更新中断公式:  定时周期 = (PSC+1) × (ARR+1) / 定时器时钟  更新事件频率 = 定时器时钟 / ((PSC+1) × (ARR+1)) 例:TIM6时钟84MHz,要定时0.5秒(2

2401_89890233的博客 260

自学PCB思路 | PCB设计零基础入门-小白适用(附详细入门经验分享)

本人大一在实践课上就听说PCB,被虎的一楞一楞的,不过现在回看,发现其实并没有相信的那么难!想着把自己这一路摸索的学习路线分享一下,当时我是一边摸索一边上手,一把鼻涕一把泪的过来的。如果你想搞硬件或者需要搞硬件,下面是我的入门学习过程,希望可以帮到你。目录前言选择一款设计软件好的入门课程掌握抄板元件采购焊板子烙铁头热风枪自己半设计明确设计需求全设计最后我并非什么大佬,也没法自己去教大家如何画板。

自动化小秋葵的博客 1万+

工业级RFID读写器|识读器CK-LR08-E00通过以太网TCPIP自由协议与汇川PLC控制器联机与通讯实例方案

本文以工业级RFID读写器CK-LR08-E00和汇川PLC控制器H5U是实例介绍工业级RFID读写器CK-LR08-E00通过以太网TCPIP自由协议与汇川PLC控制器联机与通讯方案。

华翔天诚科技 4372

7-2 按键控制LED流水灯(SDCC + Notepad++ + STC-ISP/普中ISP)

本节基于上节定时器T0设计,实现按键控制LED流水灯。通过配置12MHz晶振下16位定时器模式,初值设为64536(0xFC18),每1毫秒溢出一次,替代原有Delay方法。启用定时器中断,设置TR0启动,TF0标志自动置位触发中断,配合外部按键控制流水灯状态,提升系统实时性与效率。

xunyan6234的博客 81

Halcon 20.11在WPF中的交互式绘图:完整实现矩形ROI的创建、拖动与坐标获取

本文详细介绍了如何在WPF框架下利用Halcon 20.11实现交互式矩形ROI的创建、拖动与坐标获取。通过HDrawingObject和HSmartWindowControlWPF控件,开发者可以高效构建机器视觉应用,提升项目实战中的开发效率。文章涵盖环境配置、事件处理、坐标获取及性能优化等关键内容。

weixin_29266007的博客 427

TMS320F28335 学习笔记(二):系统时钟、PLL 与看门狗

本文系统介绍F28335的时钟架构与看门狗应用,涵盖外部晶振、PLL倍频生成系统时钟(SYSCLKOUT)、高速/低速外设时钟分频配置,以及看门狗的启用、关闭与喂狗机制。强调时钟配置准确性对延时、PWM、通信等外设的重要性,指出常见错误如晶振不匹配、未等待PLL锁定、随意修改PLL或喂狗周期过长等问题,并提供初始化框架与验证方法,帮助开发者正确配置时钟系统,确保程序稳定运行。

2403_83044722的博客 367

14、OpenClaw(龙虾助手)Skill 开发完整规范(2026最新版)

OpenClaw Skill 开发指南摘要: Skill 是 OpenClaw AI 助手的核心扩展机制,支持声明式(SKILL.md)和原生代码(JS/TS/Python)两种开发模式。声明式 Skill 通过 Markdown 定义触发条件和执行流程,适合简单业务;原生代码 Skill 提供完整编程能力,适合复杂场景。开发需遵循标准化项目结构,包含 SKILL.md(元数据)、manifest.json(权限声明)等文件,并严格处理错误和多系统适配。新版要求原生 Skill 必须声明文件、网络等权限,确

欢迎光临~知1而N的博客 1243

RK3588网卡出现丢包或者带宽不足的问题验证排查

注意除了有最大的TCP数据接收缓冲,还有/proc/sys/net/core/wmem_max — 最大的TCP数据发送缓冲可以调整,这里暂时没用到,针对具体需要可适当调整。比如把ethx接口中断从默认的cpu0迁移到其它cpu,例如将eth0中断234移到cpu7 (10是16进制, cpu0 =3588的cpu有8个核,4个A55(小核)+4个A76(大核),可以找到对应中断 将中断从默认cpu0核切到cpu7。tcp_rmem:单位是字节,接收缓存区大小,缓存从对端接收的数据,后续会被应用程序读取。

Android/Linux驱动开发传帮带学习,让驱动入门门槛更低! 2030

代码小白笔记:江科大 51 单片机 LCD1602 课程详解

本文为单片机新手梳理江科大51课程中LCD1602的驱动逻辑,聚焦“看懂代码”而非深究寄存器。通过解析接线、底层函数(延时、写命令/数据)、初始化流程及光标定位,详解字符、字符串、数字(十进制、十六进制、二进制)的显示原理,核心在于理解“RS控制指令/数据”、“地址映射规则”及“数字转字符”的位运算技巧,帮助读者快速上手并自定义显示内容。

2301_81429079的博客 338

产品缺陷检测:表面缺陷检测_(1).产品缺陷检测基础理论

确定像素之间的相似性准则,如灰度值的差异。

zhubeibei168的博客 834

MCU 启动流程解析:从 SRAM 到 DDR

MCU 的性能瓶颈通常不在 DDR 带宽,而在访问延迟与确定性。因此,关键代码、中断向量、栈以及功能安全数据应放在片内 SRAM;大块传感器数据缓冲、堆和冷数据则放在 DDR。启动流程的核心可以概括为三步:Boot ROM 搬运、Remap 切换、SRAM 执行。

Where there is life, there is hope. 290

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

XZ6273H 100V低功耗高压LDO硬件设计与实测体验

这款LDO提供SOT23-3、SOT89-3和TO92三种通用封装,不同场景下可以灵活选择:做小型化的PCB节点时用SOT23-3,板卡面积能控制得非常小;常规工业板卡选SOT89-3,贴片焊接方便,自带的焊盘也能提供基础散热能力;从实际落地的情况来看,它最适配两类场景:一类是汽车电子里的休眠值守子模块,车辆熄火后仍能保持极低的待机功耗,不会过度消耗电瓶电量;另一类是户外无人值守的通信远端节点,长期运行下的低功耗表现能大幅降低设备的供电维护成本。

koce13544100678的博客 231

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

MCU的ADC采样怎么配

一块采集板,室温下读电池电压挺准,一上高低温箱就差出0.3V。软件反复查了三遍,没改一行代码,最后发现是参考电压选错了——电源纹波直接灌进了测量基准。ADC这东西,代码就那么几行,坑全在配置里。这篇拿ATmega16当例子,把采样链路从头讲清:参考电压怎么选、时钟为什么卡在50到200kHz、结果为什么要讲究读取顺序。这些规则不只对AVR成立,换成车规MCU,思路一样。

qq_43241255的博客 194

HAL库基本定时器框架分析

这部分是在main.c里面调用的,使能定时器外设和中断位。实时真正干活的操作在中断处理函数里进行。流程1 定时器外设初始化和中断配置。流程2 使能定时器中断相关位。流程3 中断处理函数。

tecoes的博客 22

PHY内部环回通过,能证明网口整条链路正常吗?

PHY内部环回通过,只能证明该测试路径在记录条件下工作正常,不能直接证明变压器、RJ45、网线和对端组成的整条链路正常。采购收到“环回合格”报告时,应先确认数据从哪里发出、在哪里折返、又在哪里检查。

Voohu_Elsa的博客 189

从FT 93k测试机角度看如何测试芯片的VOH/VOL/VIH/VIL,通俗易懂的方式介绍

FT 93k测试中,VIH/VIL与VOH/VOL的核心是验证芯片“听清”与“说清”的能力。VIH/VIL测试芯片输入灵敏度,通过施加边界电压并观察功能响应;VOH/VOL则在负载下测量输出电平,模拟真实工作状态。测试需严格遵循规格书的负载电流条件,确保信号可靠传输,避免灰色地带干扰。本质是精准复现测试条件,实现“听话的对话者”与“挑剔的听众”的双重角色。

Wang20122013的博客 227

Arduino-MEGA-ESP单片机理论课笔记-06温湿度-07时间-延时定时-2026

本文解析了DHT11温湿度传感器的Arduino代码,逐行讲解了库引入、引脚定义、传感器初始化及数据读取流程。重点说明了delay(1000)导致阻塞的问题,并提出使用millis()实现非阻塞定时,将采样间隔改为2秒,提升程序响应性。同时建议增加isnan()错误检测、优化串口输出格式,确保数据稳定可靠。

zhangrelay的专栏 363

认识元器件 · 单片机(MCU/MPU/SOC) · 实战篇16

VBUS检测用于识别USB主机是否存在,自供电设备强制检测,采用PA9引脚(5V耐压)通过分压或MOSFET方案实现。分压电阻取高阻值(如82k+33k),确保分压后电压≤4V,符合GPIO输入电平要求;当GPIO耐压条件不足时,改用N沟道MOSFET(如2N7002)检测,适用于高电压场景(如5–48V)。三电阻加焊桥可覆盖1.8V/3.3V双档,但量产建议使用标准封装。需避免MCU未上电时5V VBUS接入PA9,以防损坏器件。

一只天天被叨叨的小可爱 207

哪些国产单片机厂家提供FAE技术支持?英锐恩等对比

大厂FAE资源通常优先服务大客户,中小批量项目往往只能找代理商或看论坛。如果你需要,推荐:收到请求当日或次日回复,必要项目24小时内工程师到场,还能组织芯片研发工程师一起评审问题。生态成熟、资料丰富;通过代理商体系提供支持。

2601_95079068的博客 226

【电感专题2:电感的感抗、电路的阻抗、电流的滞后】

本文系统阐述了电感的感抗特性及其对交流电路的影响。感抗 随频率升高而增大,阻碍电流变化,导致电流滞后电压90°。在串联RL电路中,总阻决定电流大小,频率或电感增大时阻抗上升,电流减小;反之则电流增大。仿真验证了理论计算与波形特征一致,直观展示了电流滞后于电压的相位关系。

weixin_57246604的博客 330

USB3.0读卡器为何跑不满速?CH379工程实践拆解

规范覆盖上,这颗芯片从eMMC4.4、4.5.1一直到5.0的HS200与HS400都有支持,老库存卡和新采购颗粒都能兼容,这在料源不稳定的时期尤为重要,一颗芯片同时兼容SD物理层1.0、2.0与SD3.0规范,也覆盖了MMC卡这类偏工业的存量介质。调试阶段,TEST引脚每秒输出一帧状态数据,包含USB连接速度、卡的插入与初始化状态以及上下传速率,115200bps的串口格式接一根线就能读取,产线工装可以直接用它做成品测试判断,不必反复插拔试错。这颗决定读卡器性能上限的芯片,值得从工程角度拆开看一遍。

ccbw16的博客 120

飞控主控 MCU 怎么选?STM32H743、STM32F405 与 AT32F421 应用及选型对比

AT32F421 采用 ARM Cortex-M4 内核,主频最高 120MHz,具有较紧凑的封装形式,可以用于空间和成本受到限制的电调产品。当飞控需要增加更多传感器、提高 IMU 采样频率,或者同时运行导航、通信和数据处理任务时,较高的 CPU 性能可以减少任务之间的资源竞争。对于开发团队来说,满足当前功能、保留合理的性能余量,同时控制整体 BOM 成本,才是更实际的选型思路。例如,在需要进行多路电流采样、PWM 控制和电机状态监测的设备中,G473 的模拟外设资源可能比单纯提高 CPU 主频更加重要。

Cheryl13824368625 139
上一篇: YuanHub 源码分析【一】FlashDB 初始化与项目应用
下一篇: YuanHub 源码分析【二】全局变量与调试
崔佳宇_
崔佳宇_ 优质创作者: 嵌入式与硬件开发技术领域 优质创作者: 嵌入式与硬件开发技术领域
博客等级 码龄10年 1万+粉丝 173原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值