STM32F103直流有刷电机速度闭环控制
在《基于直流有刷电机的速度闭环控制以及matlab仿真》我们介绍了速度闭环控制的实现,其采用的是PID控制算法,本节我们就基于STM32F103来实现直流电机的增量式PID速度闭环控制。
一、软件实现
我们需要将PID控制器应用于STM32F103的速度闭环电机控制,以下是接下来的步骤:
- 电机速度测量:使用编码器来测量电机的实际转速,需要配置定时器的编码器模式来读取编码器脉冲,从而计算速度;具体参考《
STM32F103霍尔编码器测速》; - 电机驱动:通常使用
PWM驱动电机,需要配置定时器的PWM输出模式,并设置占空比来控制电机速度;具体参考《基于直流有刷电机的开环调速控制以及matlab仿真》; PID周期调控:PID计算需要定期执行,因此需要配置一个定时器中断,在中断服务函数中执行PID计算和更新PWM输出;- 主程序初始化:初始化所有外设(比如电机初始化、编码器初始化、中断定时器等)和
PID参数; - 主循环:通常主循环可以处理一些非实时任务,比如接收用户指令(例如通过串口改变目标速度)等。
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算法:
增量式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 ms 、5ms甚至1ms就要进行一次调控。
调控的越快一般来说对象就会越稳定,但是调控周期也不能无限制的快,因为会受硬件传感器等设备分辨率限制,比如姿态传感器每隔5 ms才能更新一下数据,那么你PID 1ms就调控一次也没意义。
而且,对于电机速度控制来说,调控周期不需要特别快,一般20到100 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 PPR(Pulses Per Revolution); 4倍频后:64计数/转;
如果我们将目标转速设定为1500 rpm,采样周期设定为10ms;
但如果测量误差1个脉冲:
1个脉冲误差将会造成94 rpm误差!因此可以看出霍尔编码器775-P16在测量低速的时候存在脉冲少,测速不准的问题。那我们怎么解决这个问题呢?
- 更换高分辨率编码器,比如使用
500线光电编码器; - 增加采样周期;
这里我们选择第二种方式。
2.2.1.1 提高采样周期
将采样周期设为20ms(与PID调控周期一致),此时1500 rpm对应的脉冲数:
1个脉冲误差将会造成1500 / 32 ≈ 47 rpm误差!
将采样周期设为100ms(与PID调控周期一致),此时1500 rpm对应的脉冲数:
1个脉冲误差将会造成1500 / 160 ≈ 9 rpm误差!
2.2.1.2 提高目标转速
如果我们将目标设定为9000 rpm,对应的脉冲数:
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.04,I使用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.04,I保持0.004,D使用0.002,从速度曲线上,出现了震荡。
| P | I | D |
|---|---|---|
| 0.04 | 0.004 | 0.002 |
P保持0.04,I保持0.004,减小D,D使用0.0002,从速度曲线上,和PI控制相比好像看不出明显的变化。
因此可以考虑只用PI,不要D,此外在实际项目中,一定要选择测量精度较高的光电编码器。
三、源码下载
源码下载路径:stm32f103。
参考文章
[2] STM32F103霍尔编码器测速

浙公网安备 33010602011771号