jjzjj

QT上位机控制stm32,并利用PID控制编码电机旋转

不知名的菜 2024-07-22 原文

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使能端以驱动电机改变电机旋转速度。

       这是本人的第一篇博客,写的不好的地方大家可以提出来交流讨论,本人也在学习阶段,如果需要程序源码和QT接口的话之后可以上传。stm32程序在这,需要自取如果觉得这篇文章对你有用请点赞评论一波谢谢。

有关QT上位机控制stm32,并利用PID控制编码电机旋转的更多相关文章

  1. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  2. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  3. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  4. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  5. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  6. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  7. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  8. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  9. ruby-on-rails - 在 Rails 控制台中使用 asset_path - 2

    在我的Character模型中,我添加了:字符.rbbefore_savedoself.profile_picture_url=asset_path('icon.png')end但是,对于数据库中已存在的所有角色,它们的profile_picture_url为nil。因此,我想进入控制台并遍历所有这些并进行设置。在我试过的控制台中:Character.find_eachdo|c|c.profile_picture_url=asset_path('icon.png')end但这给出了错误:NoMethodError:undefinedmethod`asset_path'formain:O

  10. ruby-on-rails - 带有 Pry 的 Rails 控制台 - 2

    当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question

随机推荐