基于STM32F4实现步进电机闭环控制实现(无PID)

文章目录

    • 概要
    • 整体流程
    • 代码实现
      • TIM8 PWM控制
      • TIM5 编码器计数
      • TIM13 闭环控制
    • 效果展示
    • 小结

概要

因客户外部负载较大,步进电机出现丢步现象,所以需要进行闭环控制,保证最后走到相应的位置即可,所以我采用的是电机停止后与编码器值(转化实际脉冲数 相比较 误差在 256(256细分对应1.8°) 以外 再次运动差值。

整体流程

这个实现较为简单,没啥复杂的思路

  • TIM5采用编码器模式 累计编码器的值
  • TIM8输出PWM控制电机转动
  • TIM13 间隔1ms读取编码器的值,判定是否需要进行补偿

代码实现

TIM8 PWM控制

/***********************************************
TIM8_CH2(PC7) 单脉冲输出+重复计数功能初始化
TIM8 时钟频率 84*2=168MHz
arr:自动重装值
psc:时钟预分频数
************************************************/
void TIM8_OPM_RCR_Init(u16 arr,u16 psc)
{		 					 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);  	//TIM8时钟使能    
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); 	//使能PORTC时钟	                                                                     	
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOC7复用为定时器8
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;           //GPIOC7
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;      //下拉
	GPIO_Init(GPIOC,&GPIO_InitStructure);               //初始化PF9

	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值   
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
	TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	TIM_ClearITPendingBit(TIM8,TIM_IT_Update);

	TIM_UpdateRequestConfig(TIM8,TIM_UpdateSource_Regular); /********* 设置只有计数溢出作为更新中断 ********/
	TIM_SelectOnePulseMode(TIM8,TIM_OPMode_Single);/******* 单脉冲模式 **********/
 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出2使能
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; /****** 比较输出2N失能 *******/
	TIM_OCInitStructure.TIM_Pulse = arr>>1; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM8, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

	TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable);  //CH2预装载使能	 
	TIM_ARRPreloadConfig(TIM8, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_ClearITPendingBit(TIM8,TIM_IT_Update);
	TIM_ITConfig(TIM8, TIM_IT_Update ,ENABLE);  //TIM8   使能或者失能指定的TIM中断
 
	NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;  //TIM8中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
	
	TIM_ClearITPendingBit(TIM8, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源
	TIM_Cmd(TIM8, ENABLE);  //使能TIM8											    
}

/******* TIM8更新中断服务程序 *********/
void TIM8_UP_TIM13_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM8,TIM_FLAG_Update)!=RESET)//更新中断
	{
		TIM_ClearITPendingBit(TIM8,TIM_FLAG_Update);//清除更新中断标志位		
		if(MT8.is_rcr_finish==0)//重复计数器未设置完成
		{
			if(MT8.rcr_integer!=0) //整数部分脉冲还未发送完成
			{
				TIM8->RCR=RCR_VAL8;//设置重复计数值
				MT8.rcr_integer--;//减少RCR_VAL8+1个脉冲				
			}else if(MT8.rcr_remainder!=0)//余数部分脉冲 不位0
			{
				TIM8->RCR=MT8.rcr_remainder-1;//设置余数部分
				MT8.rcr_remainder=0;//清零
				MT8.is_rcr_finish=1;//重复计数器设置完成				
			}else goto out;   //MT8.rcr_remainder=0,直接退出			 
			TIM_GenerateEvent(TIM8,TIM_EventSource_Update);//产生一个更新事件 重新初始化计数器
			TIM_CtrlPWMOutputs(TIM8,ENABLE);	//MOE 主输出使能	
			TIM_Cmd(TIM8, ENABLE);  //使能TIM8	
			if(MT8.motor_dir==CW8) //如果方向为顺时针   
				MT8.current_pos+=(TIM8->RCR+1);//加上重复计数值
			else          //否则方向为逆时针
				MT8.current_pos-=(TIM8->RCR+1);//减去重复计数值							
		}else
		{
out:		MT8.is_rcr_finish=1;//重复计数器设置完成
			TIM_CtrlPWMOutputs(TIM8,DISABLE);	//MOE 主输出关闭
			TIM_Cmd(TIM8, DISABLE);  //关闭TIM8				
		}	
	}
	
//定时器13中断
	if(TIM_GetITStatus(TIM13,TIM_IT_Update)==SET) //溢出中断
	{
		EncPos2 = circlecnt2*(divider2) + TIM5->CNT+ 10000 ;                // + 10000;  
		MT8.FeedbackLocation = ((EncPos2  - divider2/2 ) * 12.8) ;   //(+10000是防止MT8.FeedbackLocation溢出)
		//Axis_2_loop();
		Axis_2_lnc();//闭环补偿
		TIM_ClearITPendingBit(TIM13,TIM_IT_Update);  //清除中断标志位
	}		
	
}

TIM5 编码器计数

//用于编码器
void TIM5_Int_Init()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_ICInitTypeDef        TIM_ICInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0| GPIO_Pin_1; 
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP ;
	GPIO_Init(GPIOA,&GPIO_InitStructure); 
	TIM_DeInit(TIM5);	
	
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM5);

	TIM_TimeBaseStructure.TIM_Period = divider2; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); 
	
	//设置定时器.为编码器模式  
	TIM_EncoderInterfaceConfig(TIM5, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 0x00;
	TIM_ICInit(TIM5, &TIM_ICInitStructure);
	
	TIM_ClearFlag(TIM5, TIM_FLAG_Update);  //清除所有标志位
	TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE); //允许中断更新

	TIM5->CNT = divider2/2; //初始值设置为50%Max 避免负数
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn; //定时器3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM5, ENABLE);  //使能TIM1
	
}
u32 divider2= 60000000 ;//作为中间值,不出现负数
extern int circlecnt2;//圈数累计

void TIM5_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
	{
		if(TIM5->CNT<divider2/2) 
		{ 
			circlecnt2++;
		}
		else
		{ 
			circlecnt2--;
		}
		TIM_ClearITPendingBit(TIM5,TIM_IT_Update);  //清除中断标志位
	}
}

TIM13 闭环控制

void TIM13_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);  ///使能TIM13时钟  APB1 84M时钟
	
	TIM_TimeBaseInitStructure.TIM_Period = arr; 	//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM13,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM13,TIM_IT_Update,ENABLE); //允许定时器13更新中断
	TIM_Cmd(TIM13,ENABLE); //使能定时器13
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM8_UP_TIM13_IRQn; //定时器13中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级1
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
}

中断在TIM8那边

u8   Motor8_3f_flag=0;//调用单独轴控制 再去判断位置跟随
//闭环判断
void Axis_2_lnc(void)
{	
	Axis_2_Pos=MT8.target_pos + 128000;//脉冲值
	if(((TIM8->CR1 & (uint16_t)TIM_CR1_CEN)) || !Motor8_3f_flag) //2轴电机未停止 返回
	{
		return;
	}
	if(MT8.FeedbackLocation<Axis_2_Pos)
	{
		Num2=(Axis_2_Pos - MT8.FeedbackLocation);//误差脉冲(未到位)
		if(Num2>=256)
		{
			Locate8_INC(Num2,50000,1);
		}
		else{
			Motor8_3f_flag=0;
		}
	}

	if(MT8.FeedbackLocation>Axis_2_Pos)
	{
		Num2=(MT8.FeedbackLocation - Axis_2_Pos);//过冲
		if(Num2>=256)
		{
			Locate8_INC(Num2,50000,0);
		}
		else{
			Motor8_3f_flag=0;
		}
	}
	else{
			Motor8_3f_flag=0;
		}	
}

接口函数

void Axis_2(u32 num,u32 fre,u32 dir)
{
	DIR8=dir;
	Locate8_Rle(num,fre,dir);
}

/********************************************
//相对定位函数 
//num 0~2147483647
//frequency: 20Hz~100KHz
//dir: CW(顺时针方向)  CCW(逆时针方向)
*********************************************/
void Locate8_Rle(long num,u32 frequency,DIR8_Type dir) //相对定位函数
{
	if(num<=0) //数值小等于0 则直接返回
	{		
		return;
	}
	if(TIM8->CR1&0x01)//上一次脉冲还未发送完成  直接返回
	{
		return;
	}
	if((frequency<20)||(frequency>100000))//脉冲频率不在范围内 直接返回
	{
		return;
	}
	MT8.motor_dir=DIR8;//得到方向	
#if 0
	if(MT8.motor_dir==1)//顺时针
		MT8.target_pos=MT8.current_pos+num;//目标位置
	else if(MT8.motor_dir==0)//逆时针
		MT8.target_pos=MT8.current_pos-num;//目标位置
#else
	if(MT8.motor_dir==1)//顺时针
		MT8.target_pos=MT8.target_pos+num;//目标位置
	else if(MT8.motor_dir==0)//逆时针
		MT8.target_pos=MT8.target_pos-num;//目标位置
#endif

	MT8.rcr_integer=num/(RCR_VAL8+1);//重复计数整数部分
	MT8.rcr_remainder=num%(RCR_VAL8+1);//重复计数余数部分
	MT8.is_rcr_finish=0;//重复计数器未设置完成
	TIM8_Startup(frequency);//开启TIM8
	TIM_Cmd(TIM5, ENABLE);//开启计数
}

效果展示

人为堵转,编码器的值通过了校准。
单次50000个脉冲
在这里插入图片描述
在这里插入图片描述

小结

实现方式较为简单逻辑上没啥难点,定时器配置好,直接控制即可,至于编码器相关计算请跳到这一篇观看。
传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/929591.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第4章:颜色和背景 --[CSS零基础入门]

在 CSS 中&#xff0c;颜色和背景属性是用于美化网页元素的重要工具。你可以通过多种方式定义颜色&#xff0c;并且可以设置元素的背景颜色、图像、渐变等。以下是关于如何在 CSS 中使用颜色和背景的一些关键点和示例。 1.颜色表示法 当然&#xff01;以下是使用不同颜色表示…

二叉树概述

目录 一、二叉树的基本结构 二、二叉树的遍历 1.前序 2.中序 3.后序 4.层序遍历 三.计算二叉树的相关参数 1.计算节点总个数 2.计算叶子节点的个数 3.计算树的高度 4.计算第k层的子树个数 5.查找树中val为x的节点 四.刷题 1.单值二叉树 2.检查两棵树是否相同 3.一…

04 创建一个属于爬虫的主虚拟环境

文章目录 回顾conda常用指令创建一个爬虫虚拟主环境Win R 调出终端查看当前conda的虚拟环境创建 spider_base 的虚拟环境安装完成查看环境是否存在 为 pycharm 配置创建的爬虫主虚拟环境选一个盘符来存储之后学习所写的爬虫文件用 pycharm 打开创建的文件夹pycharm 配置解释器…

weblogic开启https

JSK证书生成 生成密钥库和证书 使用Java的keytool命令来生成一个Java密钥库&#xff08;Keystore&#xff09;和证书。keytool是Java开发工具包&#xff08;JDK&#xff09;中用于管理密钥库和证书的命令行工具。 #创建证书存放目录 [weblogicosb1 jksHL]$ mkdir -p /home/w…

学习记录,正则表达式, 隐式转换

正则表达式 \\&#xff1a;表示正则表达式 W: 表示一个非字&#xff08;不是一个字&#xff0c;例如&#xff1a;空格&#xff0c;逗号&#xff0c;句号&#xff09; W: 多个非字 基本组成部分 1.字符字面量&#xff1a; 普通字符&#xff1a;在正则表达式中&#xff0c;大…

防火墙有什么作用

防火墙的作用&#xff1a;1. 提供网络安全防护&#xff1b;2. 实施访问控制和流量过滤&#xff1b;3. 检测和阻止恶意攻击&#xff1b;4. 保护内部网络免受未经授权的访问&#xff1b;5. 监控网络流量和安全事件&#xff1b;6. 支持虚拟专用网络&#xff08;VPN&#xff09;。防…

linux中启动oracle19c操作过程及详解

1.登录Oracle用户 su - oracle2.启动监听程序 监听器&#xff08;Listener&#xff09;是Oracle数据库与客户端通信的桥梁。使用以下命令启动监听器&#xff1a; lsnrctl start如图情况监控程序启动成功。 3.启动数据库实例 使用 sqlplus 工具以 SYSDBA 权限连接到数据库&a…

ainiworth 在分布式目标的方程中 与正常互易性可以形成的方程不同 多引入了协方差元素未知 但可解,因为此时只有一个串扰参数且已经解出来了

这个散射互易性&#xff0c;在不考虑AB时 方程应该只剩两个即 HVHV VHVH 和VHHV相位(虚部) 0 但是这一组方程却可以解4个参数未知数。C元素是观测的已知。 β表示真实协方差矩阵&#xff0c;Σ是恢复的协方差&#xff08;也可以认为是真实协方差元素&#xff09; 1、首先把方…

10a大电流稳压芯片_24v转3.3v稳压芯片,高效率DC-DC变换器10A输出电流芯片-AH1514

### AH1514——高性能的大电流稳压芯片 在现代电子电路设计中&#xff0c;对于能够满足大电流、高效率转换以及稳定电压输出的芯片需求日益增长。AH1514芯片作为一款出色的DC-DC变换器&#xff0c;以其独特的性能特点&#xff0c;在众多应用场景中展现出了卓越的优势. ### 一…

【网络篇】HTTP知识

键入网址到网页显示&#xff0c;期间发生了什么&#xff1f; 浏览器第一步是解析URL&#xff0c;这样就得到了服务器名称和文件的路径名&#xff0c;然后根据这些信息生成http请求&#xff0c;通过DNS查询得到我们要请求的服务器地址&#xff0c;然后添加TCP头、IP头以及MAC头&…

pdf转word/markdown等格式——MinerU的部署:2024最新的智能数据提取工具

一、简介 MinerU是开源、高质量的数据提取工具&#xff0c;支持多源数据、深度挖掘、自定义规则、快速提取等。含数据采集、处理、存储模块及用户界面&#xff0c;适用于学术、商业、金融、法律等多领域&#xff0c;提高数据获取效率。一站式、开源、高质量的数据提取工具&…

github使用SSH进行克隆仓库

SSH 密钥拉取git 查询密钥是否存在 s -al ~/.ssh这个文件夹下 known_hosts 就是存在的密钥文件 创建密钥文件 ssh-keygen -t rsa -b 4096 -C "testtt.com"-t rsa 是 rsa 算法加密 -b 是指定密钥的长度&#xff08;以位为单位&#xff09;。 -C 是用于给密钥添加注…

【MARL】MAT论文阅读笔记

文章目录 前言一、如何产生这个想法(TRPO -> ) PPO -> MAPPO -> HAPPO -> MAT 二、多智能体优势值分解定理三、transformer 在MAT的应用四、伪代码简述五、实验效果 前言 正好有节课让我们调研最新的自己的方向的新论文&#xff0c;找到一篇自己觉得比较可行&…

代码随想录32 动态规划理论基础,509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯。

1.动态规划理论基础 动态规划刷题大纲 什么是动态规划 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的…

基于SpringBoot的社区医院管理系统(代码+论文)

&#x1f389;博主介绍&#xff1a;Java领域优质创作者&#xff0c;阿里云博客专家&#xff0c;计算机毕设实战导师。专注Java项目实战、毕设定制/协助 &#x1f4e2;主要服务内容&#xff1a;选题定题、开题报告、任务书、程序开发、项目定制、论文辅导 &#x1f496;精彩专栏…

Leetcode 每日一题 49.字母异位词分组

目录 问题描述 示例 示例 1 示例 2 示例 3 约束条件 解决方案 思路 算法步骤 过题图片 代码实现 复杂度分析 题目链接 结论 问题描述 给定一个字符串数组&#xff0c;需要将其中的字母异位词分组。字母异位词是指通过重新排列源单词的所有字母得到的新单词。要求…

进程控制(下)

进程控制&#xff08;下&#xff09; 进程程序替换 fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢&#xff1f;进程的程序 替换来完成这个功能&#xff01; 程序替换是通过特定的接⼝&#xff0c;加载磁盘上的⼀个全新的程序(代码和数据)&am…

安全关系型数据库查询新选择:Rust 语言的 rust-query 库深度解析

在当今这个数据驱动的时代&#xff0c;数据库作为信息存储和检索的核心组件&#xff0c;其重要性不言而喻。然而&#xff0c;对于开发者而言&#xff0c;如何在保证数据安全的前提下&#xff0c;高效地进行数据库操作却是一项挑战。传统的 SQL 查询虽然强大&#xff0c;但存在诸…

读取电视剧MP4视频的每一帧,检测出现的每一个人脸并保存

检测效果还不错,就是追踪有点难做 import cv2 import mediapipe as mp import os from collections import defaultdict# pip install msvc-runtime# 初始化OpenCV的MultiTracker # multi_tracker = cv2.MultiTracker_create() # multi_tracker = cv2.legacy.MultiTracker_cre…

【AI系统】Transformer 模型小型化

Transformer 模型小型化 自 Vision Transformer 出现之后&#xff0c;人们发现 Transformer 也可以应用在计算机视觉领域&#xff0c;并且效果还是非常不错的。但是基于 Transformer 的网络模型通常具有数十亿或数百亿个参数&#xff0c;这使得它们的模型文件非常大&#xff0…