QT上位机控制stm32,并利用PID控制编码电机旋转
由于最近在学习电机控制算法之类的东西,看到论文大多使用PID、或以PID衍生的ADRC作为电机的主流控制,于是自己也写了一个stm32控制L298N以驱动直流电机的程序,并用QT做了一个上位机实现了用软件改变PID的参数、电机转速、转向等功能。
一、硬件原理图
实验所用到的硬件有:
带霍尔编码器的直流减速电机;
霍尔编码器具体型号为JGB37-520,12V供电,一分钟旋转110转(这里指的时全速运转下的转速),两端红白两线为电机的电源(0、12V),棕蓝两线为霍尔编码器的电源(0、3.3V),中间黄绿两线为霍尔编码器的信号线(A、B两相)。
L298N电机驱动模块;
L298N电机驱动模块我们用到了其中的IN1、IN2和OUT1、OUT2以及ENA使能端、接入12V电源即可驱动(注意:使能端ENA的跳帽要拔掉,不然就是默认全速运转、拔开跳帽后可以将其接入PWM信号实现电机调速)
STM32C8T6最小系统板;
用最简单的stm32模块即可。
12V稳压电源
这个电源接入电脑的USB接口或其他电源的USB口都可以输出1~24V的可调电压,电压值通过旋钮旋转改变。
二、QT上位机界面
此QT界面可以显示你所设定的目标速度,和当前电机旋转的实际速度,功能区可以实现对目标速度的更改,和对PID的比例、积分、微分的修改,同时还可以实现电机的正反向运转和制动。
STM32端主要程序:
l298n.c
初始化l298n模块,并实现电机 正转、反转、制动功能。
#include "l298n.h"
#include "Delay.h"
#define IN1 GPIO_Pin_4
#define IN2 GPIO_Pin_5
u8 FLAG = 0;
void L298N_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//控制电机正反转
void L298N_Positive_rotation(void)
{
FLAG = 1;
GPIO_SetBits(GPIOA, IN2); //正转
GPIO_ResetBits(GPIOA, IN1);
}
void L298N_Stop(void)
{
FLAG = 0;
GPIO_ResetBits(GPIOA, IN2); //制动
GPIO_ResetBits(GPIOA, IN1);
}
void L298N_Reverse_rotation(void)
{
FLAG = 2;
GPIO_SetBits(GPIOA, IN1); //反转
GPIO_ResetBits(GPIOA, IN2);
}
pid.c
声明pid的结构体,初始化pid各个参数,实现pid控制函数接口。
#include "stm32f10x.h"
#include "pwm.h"
#include "key.h"
struct pidstruct{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
extern int32_t INPUT;
void pid_init() // pid参数初始化
{
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.1;
pid.Ki=0.01;
pid.Kd=0.3;
}
void pid_set(float Kp,float Ki,float Kd) // pid参数设置函数
{
pid.Kp = Kp;
pid.Ki = Ki;
pid.Kd = Kd;
}
float pid_process() // 简易位移式pid
{
pid.SetSpeed=INPUT;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.integral+=pid.err;
pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
Encoder.c
利用TIM_EncoderInterfaceConfig函数直接配置正交编码器,本来之前用的时外部中断,但是由于霍尔编码器旋转时产生的脉冲数非常多,用外部中断的话非常占用程序资源,所以直接利用ARM提供的库函数TIM_EncoderInterfaceConfig,利用硬件资源来代替。
#include "stm32f10x.h"|
extern u8 FLAG;
void Encoder_init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
TIM_Cmd(TIM3,ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
if(FLAG == 1){
Temp = TIM_GetCounter(TIM3);
}
if(FLAG == 2){
Temp = (uint16_t)65535 - TIM_GetCounter(TIM3);
if(TIM_GetCounter(TIM3) == 0){
Temp = 0;
}
}
if(FLAG == 0){
Temp = 0;
}
//Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity)
这个函数是用来配置正交编码器的,可以直接代替外部中断。
第一个参数是所使用的定时器;
第二个参数是编码器模式,参数可以是TIM_EncoderMode_TI1、TIM_EncoderMode_TI2、TIM_EncoderMode_TI12,这个参数的代表在哪个边沿计数还是双边计数;
后两个参数是反相和不反相的功能;
main.c
在定时器中断里获取电机当前速度,通过pid接口函数算出当前应有的实际速度,然后通过PWM调速改变电机速度。
#include "stm32f10x.h" // Device header
#include "timer.h"
#include "led.h"
#include "OLED.h"
#include "l298n.h"
#include "key.h"
#include "pid.h"
#include "pwm.h"
#include "Delay.h"
#include "Encoder.h"
#include "Serial.h"
#include "stdio.h"
extern int32_t INPUT;
extern struct pidstruct{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
int16_t speed = 0;
int main(void)
{
pid_init();
Key_Init();
LED_Init();
uart_init();
Encoder_init();
Timer_Init();
OLED_Init();
L298N_Init();
PWM_Init();
OLED_ShowString(1, 1, "TargetSpeed:");
OLED_ShowString(2, 1, "ActualSpeed:");
while(1)
{
OLED_ShowNum(1, 13, INPUT, 4);
OLED_ShowNum(2, 13, speed, 4);
printf("T:%d A:%d",INPUT,speed);
if(Receive_Flag == 1) //接收数据标志位等于1(接收完毕,停止接收)
Receive_Flag = 0; //接收数据标志位置0(可以开始接收)
}
}
void TIM4_IRQHandler (void)
{
static uint32_t i=0;
float tempx = 0;
if(TIM_GetITStatus(TIM4, TIM_IT_Update)==SET)
{
i++;
if(i>100){
LED = !LED;
i=0;
}
speed = Encoder_Get();
pid.ActualSpeed = speed;
tempx = pid_process();
if(tempx>100)
tempx=100;
if(tempx<=0)
tempx=0;
PWM_SetCompare1(tempx*10);
}
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
speed = Encoder_Get();
pid.ActualSpeed = speed;
这两句代码通过 TIM_EncoderInterfaceConfig 函数获取的当前电机旋转的实际速度。
tempx = pid_process();
PWM_SetCompare1(tempx*10);
这两句代码是将获取的实际速度传给pid接口函数,用tempx来接受通过pid算出来的值,然后将算出的速度,用PWM输出给ENA使能端以驱动电机改变电机旋转速度。文章来源:https://www.toymoban.com/news/detail-407832.html
这是本人的第一篇博客,写的不好的地方大家可以提出来交流讨论,本人也在学习阶段,如果需要程序源码和QT接口的话之后可以上传。stm32程序在这,需要自取。如果觉得这篇文章对你有用请点赞评论一波谢谢。文章来源地址https://www.toymoban.com/news/detail-407832.html
到了这里,关于QT上位机控制stm32,并利用PID控制编码电机旋转的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!