程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

STM32F103直流有刷电机速度闭环控制

在《基于直流有刷电机的速度闭环控制以及matlab仿真》我们介绍了速度闭环控制的实现,其采用的是PID控制算法,本节我们就基于STM32F103来实现直流电机的增量式PID速度闭环控制。

一、软件实现

我们需要将PID控制器应用于STM32F103的速度闭环电机控制,以下是接下来的步骤:

  1. 电机速度测量:使用编码器来测量电机的实际转速,需要配置定时器的编码器模式来读取编码器脉冲,从而计算速度;具体参考《STM32F103霍尔编码器测速》;
  2. 电机驱动:通常使用PWM驱动电机,需要配置定时器的PWM输出模式,并设置占空比来控制电机速度;具体参考《基于直流有刷电机的开环调速控制以及matlab仿真》;
  3. PID周期调控:PID计算需要定期执行,因此需要配置一个定时器中断,在中断服务函数中执行PID计算和更新PWM输出;
  4. 主程序初始化:初始化所有外设(比如电机初始化、编码器初始化、中断定时器等)和PID参数;
  5. 主循环:通常主循环可以处理一些非实时任务,比如接收用户指令(例如通过串口改变目标速度)等。

1.1 PID算法实现

1.1.1 自定义PID结构体

control目录下新建pid.h文件,自定义PID结构体;

/******************************************************************************************************/
#ifndef _STM32f10x_PID__H
#define _STM32f10x_PID__H

#include "encoder.h"
#include <stdio.h>

#define MAX_HISTORY 300  // 最多保存300次数据

typedef struct {
    float Kp, Ki, Kd;      // 比例、积分、微分系数
    float Ts;              // 调控周期 (秒)
    float prev_error;      // e(k-1)
    float prev_error2;     // e(k-2)
    float out_max;         // 输出的最大值(占空比)
    float out_min;         // 输出的最小值(占空比)
    float output;          // 当前总输出 u(k)(占空比) 
    float target_val;      // 目标转速值 (RPM)
	
    // 历史记录
    float measure_history[MAX_HISTORY];  // 测量转速记录
    float output_history[MAX_HISTORY];   // 输出占空比记录
    int record_count;                    // 已记录次数
    int is_full;                         // 是否已存满
    int has_output;  				     // 标记是否已输出过
    float raw_measure;                   // 当前测量转速值 r(k)  
} PID;
 
extern PID g_pid;                    // 全局PID参数
extern void pid_param_init(void);    // 初始化PID参数
extern void set_pid_params(float kp, float ki, float kd, int ts, int target_val); // 设置PID相关参数
extern float pid_compute(void);          // PID计算
extern void print_measure_record(void);  // 输出测量转速记录

#endif

PID结构体中除了PID算法用到的一些参数外,我们还存储了当前测量的转速值、PID输出的占空比记录值。

1.1.2 PID参数初始化

control目录下新建pid.c文件。pid_param_init函数用于初始化g_pid参数,比如:

  • 将目标值g_pid.target_val设置为1500.00
  • 将实际值、上一次偏差值和上上次偏差值等初始化为0

此外,我们将PID算法调控周期设置为10ms,这个在后面我们会单独介绍。

/*****************************************************************************/
#include "pid.h"

PID g_pid;     // 全局pid参数

/*****************************************************************************
 *
 *		 Description:   初始化PID参数
 *       
********************************************************************************/
void pid_param_init()
{
     int i = 0;
     g_pid.target_val = 1500.0f; 
     g_pid.Ts = 0.01f;
     g_pid.output = 0.0f;
     g_pid.prev_error = 0.0f;
     g_pid.prev_error2 = 0.0f;
     g_pid.out_max = 100.0f;
     g_pid.out_min = 0.0f;
     g_pid.Kp = 0.01f;
     g_pid.Ki = 0.01f;
     g_pid.Kd = 0.01f;
	
     // 历史记录初始化
     g_pid.record_count = 0;
     g_pid.is_full = 0;
     g_pid.has_output = 0; 
     g_pid.raw_measure = 0.0f;
    
     // 清空历史记录
     for(i = 0; i < MAX_HISTORY; i++) {
		g_pid.measure_history[i] = 0;
		g_pid.output_history[i] = 0;
     }
}

接着我们提供了set_pid_params函数修改PID参数;

/*****************************************************************************
 *
 *		 Description:   设置PID相关参数
 *       Parameter  :    kp:比例环节系数
 *                       ki:积分环节系数
 *                       kd: 微分环节系数
 *                       ts: PID算法调控周期 单位ms
 *                       target_val: 目标值
 *       
********************************************************************************/
void set_pid_params(float kp, float ki, float kd, int ts, int target_val)
{
     g_pid.Kp = kp;
     g_pid.Ki = ki;
     g_pid.Kd = kd;
     g_pid.target_val = target_val; 
     g_pid.Ts = ts/1000.0f;
}
1.1.3 PID算法实现

增量式PID算法:

\[Δu(k)=K_p[e(k)-e(k-1)]+K_iT_se(k)+\frac{K_d}{T_s}[e(k)-2e(k-1)+e(k-2)] \]

\[u(k)=u(k-1)+Δu(k) \]

增量式PID算法实现:

/**************************************************************************************************************
 *
 *		 Description: PID控制算法
 *       Return     :  PID输出值
 *
**************************************************************************************************************/
float pid_compute() {

    // 1. 获取编码器速度
	float raw_measure = get_encoder_speed(g_pid.Ts*1000);

    // 2. 计算误差
    float error = g_pid.target_val - raw_measure;

    // 3. 增量式PID计算
    float delta_u = g_pid.Kp * (error - g_pid.prev_error)
                  + g_pid.Ki * g_pid.Ts * error
                  + (g_pid.Kd / g_pid.Ts) * (error - 2.0f * g_pid.prev_error + g_pid.prev_error2);

	// 4. 更新总输出
    g_pid.output = g_pid.output + delta_u;
		
    // 5. 输出限幅
    if (g_pid.output > g_pid.out_max) 
        g_pid.output = g_pid.out_max;
    if (g_pid.output < g_pid.out_min) 
        g_pid.output = g_pid.out_min;
    
    // 6. 更新误差历史
    g_pid.prev_error2 = g_pid.prev_error;
    g_pid.prev_error = error;
		
    // 7. 记录数据用于调试
    if(!g_pid.is_full){
        g_pid.measure_history[g_pid.record_count] = raw_measure;
        g_pid.output_history[g_pid.record_count] = g_pid.output;
        g_pid.record_count++;
        
        // 检查是否已满
        if(g_pid.record_count == MAX_HISTORY) {
            g_pid.is_full = 1;
        }
    }
    g_pid.raw_measure = raw_measure;

    return g_pid.output;
}
1.1.4 存储转速数据

我们在进行PID参数调试的时候,通常会开发相应的上位机软件和下位机程序,但是这样周期就会变得很长,那有没有简单的方式呢?

这里我们将下位机测量的电机转速数据存储起来,然后通过串口发送到我们的串口助手里,有了这些数据后,我们就可以通过工具去分析电机对于目标转速的跟踪情况。

/**************************************************************************************************************
 *
 *		 Description: 输出测量记录
 *
**************************************************************************************************************/
void print_measure_record() {
	int i = 0;
	// 检查是否已经输出过
    if(g_pid.has_output) {
		return;
	}
		
    if(g_pid.is_full == 0) {
    	printf("转速数据未保存完毕!\n");
    	return;
    }
    
    printf("\n========== 转速历史数据 ==========\n");
    printf("总记录数: %d\n", g_pid.record_count);
    
    // 简单输出,每行一个转速值
    for(i = 0; i < g_pid.record_count; i++) {
        printf("%.1f\n", g_pid.measure_history[i]);
    }
    
    printf("========== 输出完成 ==========\n");
		
	// 标记为已输出
    g_pid.has_output = 1;
}

1.2 PID周期调控

1.2.1 调控周期

离散PID程序实现的第一步就是,确定一个调控周期T ,每隔时间T, 程序执行一次PID调控。

至于调控周期设置多大比较好呢,这也是有说法的,调控周期一般取决于被控对象变化的快慢;比如倒立摆平衡车、四轴飞行器等,这些对象变化的很快,你总不能说我一秒才调控一次吧,要是等一秒才调控一次,那倒立摆、平衡车早就倒了,四轴飞行器早就掉下来了,所以这些对象调控一定要快,一般20 ms 10 ms5ms甚至1ms就要进行一次调控。

调控的越快一般来说对象就会越稳定,但是调控周期也不能无限制的快,因为会受硬件传感器等设备分辨率限制,比如姿态传感器每隔5 ms才能更新一下数据,那么你PID 1ms就调控一次也没意义。

而且,对于电机速度控制来说,调控周期不需要特别快,一般20100 ms调控一次就可以,电机控速调控也不能过快,因为会受编码器分辨率限制,调控周期越快,编码器测速的分辨率就越低,过快的调控周期,编码器测速根本不准确,这时再快的调控也没有意义。

最后,对于某些很大的被控对象,比如给一个很大的锅炉加热,或者给一个游泳池加热,这些对象的变化非常缓慢,PID调控周期也得慢下来,调快了没意义,比如几百毫秒、几秒甚至几十s调控一次都没问题.

因此,调控周期T到底确定为多久,需要根据被控对象变化的速度来决定,没有一个固定的值,需要靠我们的实践经验来反复调节尝试。

1.2.2 实现方案

那在程序中,如何实现每隔时间T执行一次调控呢?下面给出了三种实现方案:

方案 实现逻辑 优点 缺点
delay延时 主循环中执行PID后延时t 实现最简单 周期不精确,阻塞主程序
定时器中断 中断中直接执行PID调控 周期精确,不阻塞主程序 如果在主程序和中断中都对同一硬件进行数据的读取,可能引发硬件资源访问冲突
定时器 + 标志位 中断置标志位,主循环检测标志位执行PID 无资源冲突 主程序阻塞时周期不精确

这里我们采用定时器中断方式,因此需要配置一个定时器中断,在中断服务函数中执行PID计算和更新PWM输出。

1.2.3 实现代码

开启定时中断,这里使用的定时器6,在定时器溢出中断中执行PID运算。修改time.c文件:

#include "pid.h"
#include "motor.h"

/*******************************************************************************
 * Function Name  : TIM6_IRQHandler
 * Description    : This function handles TIM6 global interrupt request.
 * Input          : None
 * Output         : None
 * Return         : None
 *******************************************************************************/
void TIM6_IRQHandler(void)
{
	//**********************自定义用户任务****************************//
	int res_pwm = 0;    /*PWM值(PID输出)*/

    // 1. 进行PID运算,得到PWM输出值
    res_pwm = pid_compute();

    // 2. 更新PWM输出
    motor_set_duty(res_pwm);

	//*****************************************************************//
	TIM6->SR &= ~(1 << 0); // 清中断标志
}

其中motor_set_duty定义在motor.c文件中;

/**************************************************************************************************
*
*    Function  :  设置电机PWM占空比
*    Parameter :  duty:PWM占空比
*
**************************************************************************************************/
void  motor_set_duty(int duty)
{
    g_motor_pwm = duty;
	if(g_motor_pwm < 0)
	{
		g_motor_pwm = 0;
	}
	if(g_motor_pwm >100)
	{
		g_motor_pwm = 100;
	}
	motor_update_speed();
}

1.3 主程序

在主程序中初始化所有外设(比如电机初始化、编码器初始化、中断定时器等)和PID参数;

#include "common.h"
#include "stdio.h"

int main()
{
    int duty;
    int ts = 100;         // PID算法调控周期 
    STM32_Clock_Init(9); // 系统时钟初始化

    // 串口初始化
    STM32_NVIC_Init(2, USART1_IRQn, 0, 1); // 串口中断优先级初始化,其中包括中断使能
    usart_init(USART_1, 115200);           // 串口1初始化,波特率115200 映射到PA9 PA10

    // 按键KEY初始化
    gpio_init(PC5, GPI_UP, HIGH);             // PC5接按键KEY0
    gpio_init(PA15, GPI_UP, HIGH);            // PA15接按键KEY1
    Ex_NVIC_Congig(PC5, FALLING);             // 按键KEY0按下触发 高电平->低电平
    Ex_NVIC_Congig(PA15, FALLING);            // 按键KEY1按下触发 高电平->低电平
    STM32_NVIC_Init(2, EXTI9_5_IRQn, 2, 2);   // EXTI线[9:5]中断优先级初始化,其中包括中断使能
    STM32_NVIC_Init(2, EXTI15_10_IRQn, 2, 2); // EXTI线[15:10]中断优先级初始化,其中包括中断使能

    motor_init(); // 电机初始化,使用的定时器2,PA0/PA1

    encoder_init();    // 编码器初始化,使用的定时器4,PB6/PB7
	
	pid_param_init();  // 初始化PID参数 
    set_pid_params(0.01f, 0.0f, 0.0f, ts, 1500.0f);
    
    STM32_NVIC_Init(2, TIM6_IRQn, 0, 0); // TIM6溢出中端使能
    TIM_Init_MS(TIMER6, ts);             // TIM6计数到ts ms发生中断,进行PID周期调控
    while (1)
    {
        printf("Output: %.2f, Speed: %.2f RPM, Error: %.2f\n",  g_pid.output, g_pid.raw_measure, g_pid.prev_error); 
        print_measure_record();
        delay_ms(1000);
    }
}

二、 测试

2.1 硬件接线

硬件接线参考《基于直流有刷电机的开环调速控制以及matlab仿真》。

2.2 软件设定

2.2.1 采样周期和目标转速设定

我们测速使用的775-P16霍尔编码器是低精度编码器;

  • 脉冲数:16 PPRPulses Per Revolution);
  • 4倍频后:64计数/转;

如果我们将目标转速设定为1500 rpm,采样周期设定为10ms

\[计数 = 1500 / 60 × 64 × 0.01 = 16个脉冲 \]

但如果测量误差1个脉冲:

\[测速 = (15 / 64) / 0.01 × 60 ≈ 1406 rpm \]

\[测速 = (17 / 64) / 0.01 × 60 ≈ 1594 rpm \]

1个脉冲误差将会造成94 rpm误差!因此可以看出霍尔编码器775-P16在测量低速的时候存在脉冲少,测速不准的问题。那我们怎么解决这个问题呢?

  • 更换高分辨率编码器,比如使用500线光电编码器;
  • 增加采样周期;

这里我们选择第二种方式。

2.2.1.1 提高采样周期

将采样周期设为20ms(与PID调控周期一致),此时1500 rpm对应的脉冲数:

\[(1500×64×0.02)/60=32个脉冲 \]

1个脉冲误差将会造成1500 / 32 ≈ 47 rpm误差!

将采样周期设为100ms(与PID调控周期一致),此时1500 rpm对应的脉冲数:

\[(1500×64×0.1)/60=160个脉冲 \]

1个脉冲误差将会造成1500 / 160 ≈ 9 rpm误差!

2.2.1.2 提高目标转速

如果我们将目标设定为9000 rpm,对应的脉冲数:

\[(9000×64×0.02)/60=192个脉冲 \]

1个脉冲误差将会造成9000 / 192 ≈ 47 rpm误差!可以看到提高并没有降低误差带来的影响。

2.2.2 烧录测试

目标转速设置为1500 rpm,调控周期设置为100 ms,编译程序并下载测试,可以通过串口查看当前输出的占空比以及对应的转速;

2.3 PID参数整定

为了进行PID参数整定,我们通过串口输出了前300次的转速值,其采样周期为100ms

我们接下来使用python脚本这些采样值进行可视化分析;

import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple, Dict, Any


# ==================== 配置部分 ====================
def setup_matplotlib_config():
    """设置matplotlib全局配置"""
    plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
    plt.rcParams['axes.unicode_minus'] = False
    plt.rcParams['figure.autolayout'] = True


# ==================== 数据准备部分 ====================
def generate_sample_data(sample_period: float = 0.01) -> Tuple[np.ndarray, np.ndarray]:
    """生成模拟数据用于测试"""
    # 模拟PID响应:从0到超调再到稳定
    num_points = 300

    # 生成更真实的PID响应曲线
    time_array = np.arange(0, num_points * sample_period, sample_period)

    # 创建典型的二阶系统响应
    t = time_array
    # 响应模型:快速上升 -> 超调 -> 衰减振荡 -> 稳定
    speed_array = 1500 * (1 - np.exp(-3 * t))  # 指数上升
    speed_array += 300 * np.sin(8 * t) * np.exp(-1.5 * t)  # 衰减振荡
    speed_array += np.random.normal(0, 20, len(t))  # 添加噪声

    # 确保数据维度正确
    return speed_array, time_array


def load_real_data(file, sample_period: float = 0.01) -> Tuple[np.ndarray, np.ndarray]:
    """加载真实数据"""
    speed_array = np.loadtxt(file)
    time_array = np.arange(0, len(speed_array) * sample_period, sample_period)

    # 使用模拟生成的数据
    # return generate_sample_data(sample_period)
    return speed_array, time_array


# ==================== 分析计算部分 ====================
def calculate_pid_performance(speed_data: np.ndarray, target_rpm: float) -> Dict[str, Any]:
    """计算PID性能指标"""

    # 稳态分析(取后25%的数据)
    steady_start = int(len(speed_data) * 0.75)
    steady_data = speed_data[steady_start:]

    steady_mean = np.mean(steady_data)
    steady_std = np.std(steady_data)
    steady_error = steady_mean - target_rpm

    # 动态性能
    max_value = np.max(speed_data)
    min_value = np.min(speed_data)
    overshoot = max(max_value - target_rpm, 0)
    undershoot = max(target_rpm - min_value, 0)

    # 上升时间(到达目标值90%的时间)
    ninety_percent = target_rpm * 0.9
    if np.any(speed_data >= ninety_percent):
        rise_time_idx = np.where(speed_data >= ninety_percent)[0][0]
    else:
        rise_time_idx = 0

    return {
        'steady_mean': steady_mean,
        'steady_std': steady_std,
        'steady_error': steady_error,
        'max_value': max_value,
        'min_value': min_value,
        'overshoot': overshoot,
        'undershoot': undershoot,
        'rise_time_idx': rise_time_idx,
        'peak_time_idx': np.argmax(speed_data) if max_value > target_rpm else None
    }


# ==================== 绘图部分 ====================
def create_pid_plot(speed_array: np.ndarray, time_array: np.ndarray,
                    target_rpm: float = 1500) -> plt.Figure:
    """创建PID响应曲线图"""
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10),
                                   gridspec_kw={'height_ratios': [3, 1]})

    perf = calculate_pid_performance(speed_array, target_rpm)

    # ===== 主图:转速曲线 =====
    # 绘制主曲线
    ax1.plot(time_array, speed_array, 'b-', linewidth=2.5,
             label=f'实际转速 (n={len(speed_array)})', alpha=0.8)

    # 目标线和稳定带
    ax1.axhline(y=target_rpm, color='r', linestyle='--',
                linewidth=2, label=f'目标: {target_rpm} RPM')

    stable_upper = target_rpm * 1.02
    stable_lower = target_rpm * 0.98
    ax1.fill_between(time_array, stable_lower, stable_upper,
                     alpha=0.1, color='green', label='±2%稳定带')

    # ====== 修改这里:智能调整Y轴范围 ======
    # 计算实际数据的范围
    actual_min = np.min(speed_array)
    actual_max = np.max(speed_array)
    actual_range = actual_max - actual_min

    # 确保目标值在可视范围内(如果目标值很远)
    view_min = min(actual_min, target_rpm * 0.9)
    view_max = max(actual_max, target_rpm * 1.1)

    # 添加适当的边距(基于实际数据范围)
    if actual_range > 0:
        margin = actual_range * 0.15  # 15%的边距
    else:
        margin = 100  # 默认边距

    # 设置Y轴范围
    ax1.set_ylim(view_min - margin, view_max + margin)

    # ===== 子图:误差曲线 =====
    error_percent = (speed_array - target_rpm) / target_rpm * 100
    ax2.plot(time_array, error_percent, 'gray', linewidth=1.5, alpha=0.7)
    ax2.fill_between(time_array, 0, error_percent,
                     where=(error_percent >= 0), alpha=0.2, color='red')
    ax2.fill_between(time_array, 0, error_percent,
                     where=(error_percent < 0), alpha=0.2, color='blue')

    ax2.axhline(y=0, color='black', linewidth=0.8, alpha=0.5)
    ax2.axhline(y=2, color='green', linestyle=':', alpha=0.5)
    ax2.axhline(y=-2, color='green', linestyle=':', alpha=0.5)

    ax2.set_xlabel('时间 (秒)', fontsize=12)
    ax2.set_ylabel('误差 (%)', fontsize=12)
    ax2.grid(True, alpha=0.2, linestyle=':')
    ax2.set_ylim(-10, 10)

    # ===== 添加统计信息 =====
    stats_text = (
        f'稳态均值: {perf["steady_mean"]:.1f} RPM\n'
        f'稳态误差: {perf["steady_error"]:.1f} RPM ({perf["steady_error"] / target_rpm * 100:.2f}%)\n'
        f'稳态波动: ±{perf["steady_std"]:.1f} RPM\n'
        f'超调量: {perf["overshoot"]:.1f} RPM ({perf["overshoot"] / target_rpm * 100:.1f}%)\n'
        f'调节范围: {perf["max_value"] - perf["min_value"]:.1f} RPM'
    )

    fig.text(0.02, 0.02, stats_text, fontsize=10,
             bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.9))

    plt.tight_layout()
    return fig


# ==================== 报告输出部分 ====================
def print_performance_report(perf: Dict[str, Any], target_rpm: float):
    """打印性能分析报告"""
    print("=" * 60)
    print("PID控制性能分析报告".center(60))
    print("=" * 60)

    metrics = [
        ("目标转速", f"{target_rpm} RPM"),
        ("稳态转速", f"{perf['steady_mean']:.1f} RPM"),
        ("稳态误差", f"{perf['steady_error']:.1f} RPM ({perf['steady_error'] / target_rpm * 100:.2f}%)"),
        ("稳态标准差", f"{perf['steady_std']:.1f} RPM"),
        ("最大转速", f"{perf['max_value']:.1f} RPM"),
        ("最小转速", f"{perf['min_value']:.1f} RPM"),
        ("超调量", f"{perf['overshoot']:.1f} RPM ({perf['overshoot'] / target_rpm * 100:.2f}%)"),
        ("调节范围", f"{perf['max_value'] - perf['min_value']:.1f} RPM"),
        ("控制精度", f"{perf['steady_std'] / target_rpm * 100:.2f}%")
    ]

    for label, value in metrics:
        print(f"{label:>15}: {value}")

    print("=" * 60)

    # 性能评估
    if abs(perf['steady_error']) < target_rpm * 0.01:
        print("✓ 稳态精度优秀 (<1%)")
    elif abs(perf['steady_error']) < target_rpm * 0.02:
        print("✓ 稳态精度良好 (<2%)")
    else:
        print("⚠ 稳态精度有待提高")

    if perf['overshoot'] < target_rpm * 0.1:
        print("✓ 超调控制优秀 (<5%)")
    elif perf['overshoot'] < target_rpm * 0.1:
        print("✓ 超调控制良好 (<10%)")

    if perf['steady_std'] < target_rpm * 0.01:
        print("✓ 稳定性优秀 (波动<1%)")


# ==================== 主程序 ====================
def main():
    """主程序"""
    # 1. 初始化配置
    setup_matplotlib_config()

    # 2. 加载数据(替换为您的真实数据)
    speed_array, time_array = load_real_data('speed_data.txt', 0.1)

    print(f"数据加载完成: {len(speed_array)} 个点,时长 {time_array[-1]:.1f} 秒")

    # 3. 创建图形
    target_rpm = 1500
    fig = create_pid_plot(speed_array, time_array, target_rpm)

    # 4. 保存图形
    fig.savefig('pid_response_analysis.png', dpi=300, bbox_inches='tight')
    print(f"\n图表已保存: pid_response_analysis.png")

    # 5. 性能分析和报告
    perf = calculate_pid_performance(speed_array, target_rpm)
    print_performance_report(perf, target_rpm)

    # 6. 显示图形
    plt.show()


# ==================== 程序入口 ====================
if __name__ == "__main__":
    main()
2.3.1 调整P

最初我将P设置为0.01,输出的占空比为15%(由PID算法计算得到),由于我将编码器焊接到电机上后,增加了摩擦阻力,此时电机根本不会转动。

因此我们先将P值设置为0.02,然后烧录程序,并将转速的采样值保存到speed_data.txt文件;

P I D
0.02 0 0

运行python分析脚本,看看效果,从速度曲线可以看出,达不到目标速度,且与目标速度相差较大,稳态误差大概在788 rpm

P值加大到0.03,从速度曲线可以看出,与目标转速减小,稳态误差大概在556 rpm

P I D
0.03 0 0

P值加大到0.04,从速度曲线可以看出,与目标转速进一步减小,稳态误差大概在116 rpm,但是出现了超调;

P I D
0.04 0 0

P值加大到0.05,从速度曲线可以看出,与目标转速增大,稳态误差变大,大概在128 rpm,并且出现了震荡;

P I D
0.05 0 0

只使用P,会存在静差,始终达到不了目标值,这时就要使用积分项来消除静差了。

2.3.2 调整I

P保持0.04I使用0.004,从速度曲线可以看出,可以达到目标速度。

P I D
0.04 0.004 0

P保持0.04,加大I,使用0.008,从速度曲线可以看出,同样可以达到目标速度。

P保持0.04,加大I,使用0.01,从速度曲线可以看出,同样可以达到目标速度。

比较上面三个速度曲线,我们发现跟踪的速度均是很快的,I=0.004时稳态波动最小。

对于过冲,可以再加入微分试试,微分D相当于阻力的效果。

2.3.3 调整D

P保持0.04I保持0.004D使用0.002,从速度曲线上,出现了震荡。

P I D
0.04 0.004 0.002

P保持0.04I保持0.004,减小DD使用0.0002,从速度曲线上,和PI控制相比好像看不出明显的变化。

因此可以考虑只用PI,不要D,此外在实际项目中,一定要选择测量精度较高的光电编码器。

三、源码下载

源码下载路径:stm32f103

参考文章

[1] 基于直流有刷电机的开环调速控制以及matlab仿真

[2] STM32F103霍尔编码器测速

[3] STM32单片机开发(7).离散PID的程序实现

[4] PID 算法的原理与应用 (通俗易懂)

posted @ 2025-12-01 21:57  大奥特曼打小怪兽  阅读(33)  评论(0)    收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步