【STM32 基于PID的闭环电机控制系统】

embedded/2025/3/6 2:46:36/

STM32 基于PID的闭环电机控制系统

目录

  • STM32 基于PID的闭环电机控制系统
    • 一、PID算法在STM32F103C8T6中的实现思路
    • 二、代码实现与解释
    • 三、PID算法的调试与优化
    • 四、总结

一、PID算法在STM32F103C8T6中的实现思路

  1. 基本概念
    • 目标 :通过PID算法调节电机的转速,使其保持恒定或按照给定的曲线变化。
    • 硬件配置 :
    ○ STM32F103C8T6作为主控制器。
    ○ 电机驱动模块(如L298N、 DRV8825 等)。
    ○ 用于测量电机转速的编码器或霍尔传感器。
    ○ 可选:用于显示目标值和实际值的LCD或LED指示灯。
  2. STM32F103C8T6的功能模块
    • PWM输出 :通过PWM信号控制电机的转速(占空比调节)。
    • ADC输入 :采集编码器或传感器的反馈信号(如转速反馈)。
    • 定时器中断 :用于定期采样系统的反馈信号,并执行PID计算。
  3. PID算法实现的步骤
    1. 初始化STM32的PWM、ADC和定时器模块。
    2. 采集当前系统的状态(如转速)。
    3. 计算PID输出(控制量)。
    4. 根据PID输出调节PWM信号的占空比,控制电机转速。
    5. 重复上述过程,形成闭环控制。

二、代码实现与解释

  1. 头文件与配置 (main.h)
#ifndef __MAIN_H
#define __MAIN_H#include "stm32f10x.h"// 定义PWM输出引脚
#define PWM	GPIO_Pin_6  // PB6用于PWM输出// 定义ADC输入引脚
#define ADC_CHANNEL ADC_Channel_0  // 通道0用于转速反馈// 定义PID参数
#define KP 2.0f    // 比例系数
#define KI 1.0f    // 积分系数
#define KD 0.5f    // 微分系数// 定义系统参数
#define SAMPLE_TIME 20  // 采样时间(ms)
#define SYSTEM_FREQUENCY 1000  // 系统频率(Hz)#endif
  1. PID控制函数 (pid.h 和 pid.c)
    头文件 (pid.h) :
#ifndef __PID_H
#define __PID_H
#include <stdint.h>
#include <math.h>typedef struct {float setpoint;         // 目标值float processVariable;  // 实际值float output;           // 输出值float integ;            // 积分项float lastError;        // 上次误差float kp;               // 比例系数float ki;               // 积分系数float kd;               // 微分系数
} PID_HandleTypeDef;void PID_Init(PID_HandleTypeDef *pid, float setpoint, float kp, float ki, float kd);
void PID_Update(PID_HandleTypeDef *pid, float processVariable);
float PID_GetOutput(PID_HandleTypeDef *pid);
#endif

实现文件 (pid.c) :

#include "pid.h"void PID_Init(PID_HandleTypeDef *pid, float setpoint, float kp, float ki, float kd) {pid->setpoint = setpoint;pid->processVariable = 0;pid->output = 0;pid->integ = 0;pid->lastError = 0;pid->kp = kp;pid->ki = ki;pid->kd = kd;
}void PID_Update(PID_HandleTypeDef *pid, float processVariable) {float error = pid->setpoint - processVariable;float deltaError = error - pid->lastError;// 计算积分项(避免积分饱和)pid->integ += error;if (pid->integ > 1000) pid->integ = 1000;if (pid->integ < -1000) pid->integ = -1000;// 计算PID输出pid->output = pid->kp * error + pid->ki * pid->integ + pid->kd * deltaError;// 限制输出范围if (pid->output > 100) pid->output = 100;if (pid->output < 0) pid->output = 0;pid->lastError = error;
}float PID_GetOutput(PID_HandleTypeDef *pid) {return pid->output;
}
  1. 主程序 (main.c)
    代码实现 :
#include "main.h"
#include "pid.h"// 初始化PWM和ADC
void System_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;ADC_InitTypeDef ADC_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruct;NVIC_InitTypeDef NVIC_InitStructure;// 使能外设时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph(TIM2), ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置PWM引脚(PB6)GPIO_InitStructure.GPIO_Pin = PWM;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置ADC引脚(PB0)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置ADCADC_InitStructure.ADC_Mode = ADC_Mode_Independent;ADC_InitStructure.ADC_ScanConvMode = ADC_ScanConvMode_Disable;ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable;ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC2;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;ADC_InitStructure.ADC_NbrOfChannel = 1;ADC_Init(ADC1, &ADC_InitStructure);ADC_ChannelConfig(ADC1, ADC_CHANNEL, ADC_SampleTime_13Cycles5);ADC_Cmd(ADC1, ENABLE);// 配置定时器 TIM2,用于PWM和PID周期TIM_TimeBaseInitStruct.TIM_Period = 10000; // 10 kHzTIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; // 72 MHz / 72 = 1 MHzTIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD不分频;TIM_TimeBase_Init(TIM2, &TIM_TimeBaseInitStruct);TIM_Cmd(TIM2, ENABLE);// 配置PWM输出TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStruct.TIM_Pulse = 0;TIM_OC_InitStruct TIM_Channel_1, &TIM_OCInitStruct);// 配置定时器中断(用于采样和PID计算)TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStructure);
}// 定时器中断服务程序
void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {// 清除中断标志TIM_ClearITPendingBit(TIM2, TIM_IT_Update);// 采集ADC值(模拟电机转速)ADC_SoftwareStartConv(ADC1);while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);float processVariable = ADC_GetConversionValue(ADC1);// PID计算PID_Update(&pidHandle, processVariable);float output = PID_GetOutput(&pidHandle);// 更新PWM占空比TIM_SetCompare1(TIM2, output * 100);  // 100%对应10000}
}int main(void) {// 初始化系统System_Init();// 初始化PIDPID_Init(&pidHandle, 50.0f, KP, KI, KD);  // 目标值为50pidHandle.integ = 0;pidHandle.lastError = 0;// 进入主循环while (1) {// 可选:更新目标值或显示当前状态// 这里为空循环,由中断服务程序处理PID控制}
}

三、PID算法的调试与优化

  1. 参数调整
    • Kp(比例系数) :过大可能导致系统振荡,过小则响应速度慢。
    • Ki(积分系数) :过大可能导致积分饱和,过小则无法消除静差。
    • Kd(微分系数) :过大可能导致系统不稳定,过小则无法有效抑制动态误差。
  2. 调试技巧
    • 从小的Kp值开始,逐步增大,直到系统开始振荡。
    • 适当增加Ki,确保系统在稳态时无静差。
    • 调整Kd,减少系统的超调和振荡。
  3. 实际应用中的考虑
    • 采样时间 :根据系统的动态特性选择合理的采样时间,以确保闭环控制的稳定性。
    • 抗噪声处理 :在ADC采样和PID计算中加入滤波处理,避免噪声干扰。
    • 输出限制 :防止PWM输出超过硬件限制(如0-100%占空比)。

四、总结

通过上述代码示例,可以在STM32F103C8T6微控制器上实现基于PID算法的电机控制。PID算法的核心在于参数的合理选择和系统的稳定性调试。通过不断实验和调整,可以实现高精度的电机控制。后续可以根据具体需求,优化PWM输出策略、加入更加复杂的控制逻辑(如前馈控制)或扩展系统的功能(如多电机同步控制)。


http://www.ppmy.cn/embedded/170364.html

相关文章

2.4GHZ无线跳频算法 C语言

目录 一、概述 二、2.4GHZ无线调频算法C语言代码 关键点说明: 实际应用注意事项: 一、概述 2.4GHz频段常用在蓝牙、Wi-Fi或者Zigbee这些无线技术中,不同的协议可能有不同的跳频机制。比如蓝牙使用的是自适应跳频,而传统的可能用伪随机序列跳频。 用户可能是在开发自己…

rust学习笔记11-集合349. 两个数组的交集

rust除了结构体&#xff0c;还有集合类型&#xff0c;同样也很重要&#xff0c;常见的有数组&#xff08;Array&#xff09;、向量&#xff08;Vector&#xff09;、哈希表&#xff08;HashMap&#xff09; 和 集合&#xff08;HashSet&#xff09;字符串等&#xff0c;好意外呀…

Ubuntu 22.04 启动登录页面显示 IP 地址

Ubuntu 22.04 启动登录页面显示 IP 地址的配置方法 Ubuntu 22.04 默认登录界面不会直接显示 IP 地址&#xff0c;但可通过以下步骤实现开机后登录页面的 IP 展示&#xff1a; ‌方法一&#xff1a;通过修改 /etc/issue 文件显示 IP‌ ‌编辑 /etc/issue 文件‌ 该文件控制登…

Vue 组件通信 - 父传子

Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue 组件通信 - 父传子 目录 组件通信 父传子示例1 封装导航 右侧按钮显示与隐藏 属性验证 父传子示例2 总结 组件通信 父传子示例1 封装导航 组件父传子示例&#xff0c;通过对导航封装为组件来做演示。 首先封装一…

Kafka零拷贝

Kafka为什么适用零拷贝&#xff0c;其他存储结构不适用&#xff1f; Kafka 采用的是日志存储模型&#xff0c;数据通常是顺序写入、顺序读取&#xff0c;并且它的消费模式是 “读完即走”&#xff08;一次性读取并发送给消费者&#xff09;&#xff0c;这与零拷贝的特性完美匹…

Spring Boot 与 MyBatis 版本兼容性

初接触Spring Boot&#xff0c;本次使用Spring Boot版本为3.4.3&#xff0c;mybatis的起步依赖版本为3.0.0&#xff0c;在启动时报错&#xff0c;报错代码如下 org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name userMapper…

flink集成oracle 19c详解

关键注意事项详解&#xff0c;涵盖配置、性能、兼容性等核心问题&#xff1a; 一、驱动与依赖管理 JDBC 驱动版本选择 必须使用 ojdbc8.jar&#xff08;Oracle 19c 官方推荐与 JDK 8 兼容&#xff09;&#xff0c;避免使用 ojdbc10 或更高版本&#xff08;可能因 Flink 生态兼容…

【每日论文】How far can we go with ImageNet for Text-to-Image generation?

下载PDF或查看论文&#xff0c;请点击&#xff1a;LlamaFactory - huggingface daily paper - 每日论文解读 | LlamaFactory | LlamaFactory 摘要 近年来&#xff0c;通过在大规模数据集上训练&#xff0c;文本到图像&#xff08;T2I&#xff09;生成模型已经取得了显著成果&a…