IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32下推式磁悬浮装置(三)PID调试与源码分析 -> 正文阅读

[嵌入式]STM32下推式磁悬浮装置(三)PID调试与源码分析



前言

这是STM32下推式磁悬浮装置的第三篇文章,也是这个项目的最后一篇文章。前面两篇文章介绍了硬件部分,本文介绍软件部分。主要内容是源码分析和PID调试经验。源码部分基于前面两篇文章。代码开源,文末有下载地址。
STM32下推式磁悬浮装置(一)原理分析与元件清单
STM32下推式磁悬浮装置(二)原理图设计思路


以下是本篇文章的正文内容

一、源码分析

1.工程驱动

先简单介绍下工程最基本的组成成分,也就是浮子悬浮起来需要什么驱动。首先需要ADC驱动,浮子的位置信息由霍尔元件提供,霍尔元件输出的是电压值,需要使用ADC进行采集。由于我用到X、Y、Z三轴,所以需要三路ADC。

__IO uint16_t ADC_ConvertedValue[3] = {0,0,0};


/**
  * @brief  ADC1初始化
  * @param  无
  * @retval 无
  */
static void ADC1_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
	
	//PA1 PA4 PA5 作为模拟通道输入引脚                       
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
}		


static void ADC1_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	DMA_InitStructure.DMA_BufferSize = 3;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_M2M = DISABLE;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) ADC_ConvertedValue; //将要使用的存储器地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //16位
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC1 -> DR)); //外设地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	DMA_Cmd(DMA1_Channel1, ENABLE);
}


static void ADC1_Mode_Config(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续采集
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //关闭外部触发
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
	ADC_InitStructure.ADC_NbrOfChannel = 3; //通道数目
	ADC_InitStructure.ADC_ScanConvMode = ENABLE; //设置扫描模式
	
	ADC_Init(ADC1, &ADC_InitStructure);
	
	//配置ADC时钟
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	//配置通道转换顺序和采集时间
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 2, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 3, ADC_SampleTime_55Cycles5);
	
	ADC_Cmd(ADC1, ENABLE);

	//ADC DMA 请求
	ADC_DMACmd(ADC1, ENABLE);
	//校准
	ADC_StartCalibration(ADC1);
	//等待校准完成
	while( ADC_GetCalibrationStatus(ADC1) );
	//软件触发
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
}

void ADC1_Init(void)
{
	ADC1_GPIO_Config();
	ADC1_DMA_Config();
	ADC1_Mode_Config();
}

初始化后直接使用数组ADC_ConvertedValue就可以了,这里我用的是原始值。使用原始值的精度更高,没必要转化成电压值。多路ADC采集推荐使用DMA传输,不会占用CPU。

线圈需要控制浮子的位置,有时候浮子偏移大,有时候偏移小。所以线圈需要用PWM控制。总共需要两路PWM,虽然有四个线圈,X+、X-、Y+、Y-,但是在原理图中X+与X-在原理图中连接在一起,所以它们是控制X轴的一个整体,Y轴同理。

void coil_PWM_init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM1_CH1  TIM1_CH4
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseStructure.TIM_Period = arr;
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //默认不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟不分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //TIM输出比较极性高
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);
	TIM_OC4Init(TIM1, &TIM_OCInitStructure);
	
	TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能

	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
	TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH4预装载使能
	
	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_Cmd(TIM1, ENABLE);
}

在main函数中调用函数coil_PWM_init(3599, 0)即可。这个我把线圈频率设成20KHZ,频率20K以上人耳几乎就听不到线圈的噪音了。可以把频率设成10K听听线圈的噪音…
使用电机驱动还需要配置IO口进行翻转,其他的串口、显示器、LED之类的简单配置就不贴代码了。ADC和PWM的配置是重点。
在第一篇文章里提到了霍尔元件的安装位置,霍尔元件芯片部分的中心点需要安装在线圈高度的中点位置,这个安装位置非常重要,直接决定整个系统的性能。中点位置线圈磁感线与霍尔元件相切,即线圈磁场不会影响霍尔元件的采集。理想状态是改变线圈PWM大小不会影响到霍尔元件的输出值,也就是霍尔元件输出值完全却决于浮子位置。

2.PID代码

PID我只用了单环PD控制,整体效果在下文展示。对于这个系统单环PD控制足够使浮子稳定。

int x_pid_calc(u16 magnetic)
{
	float magnetic_bias=0;
	static float last_magnetic_bias=0, magnetic_integral=0;
	float x_pid=0;
	
	magnetic_bias = magnetic - pid.x_target;
//	magnetic_integral *= 0.7;
//	magnetic_integral += magnetic_bias;
	
	x_pid = pid.x_kp * magnetic_bias + 
		pid.x_kd * (magnetic_bias - last_magnetic_bias) + pid.x_ki * magnetic_integral;
	
	last_magnetic_bias = magnetic_bias;
	
	return x_pid;
}

Y轴的PID函数也是跟X轴一样。形参magnetic就是ADC采样值。目标值pid.x_target的经验值是2100,2100对应电压值大约是1.7V,1.7V是霍尔元件临界值,这时浮子刚好居中,功耗也最低。在上一篇文章中提到运放电路原理图,
在这里插入图片描述
运放电路上有一个滑动变阻器,滑动变阻器的作用就是调节输出的偏移量,霍尔元件临界值是1.7V,所以需要在无浮子的情况下调节滑动变阻器使输出是1.7V。可以用串口打印ADC值,一边调滑动变阻器一边观察串口助手。

3.控制函数

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//检查指定的TIM中断发生与否:TIM 中断源 
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除中断
		
		//传递ADC值
		ADC_VALUE[0] = ADC_ConvertedValue[0];
		ADC_VALUE[1] = ADC_ConvertedValue[1];
		ADC_VALUE[2] = ADC_ConvertedValue[2];
		
		//PID计算PWM
		COIL_X = x_pid_calc(ADC_VALUE[0]);
		COIL_Y = y_pid_calc(ADC_VALUE[1]);
		limiting_PWM(); //限幅
		
		//没有浮子时关闭线圈
		if(ADC_VALUE[2] > 3660)
		{
			COIL_X = 0;
			COIL_Y = 0;
		}
		
		//设置线圈PWM
		set_PWM(COIL_X, COIL_Y);
	}
}

该函数用于控制PID周期和进行PID运算

二、PID调试

调PID前要确定浮子的正反面,由于使用环形磁铁,如果浮子在中间时斥力在四周是吸引力,那么这个摆放方向是正确的,可以在浮子上方做个标记。浮子需要一定的重量。然后要调节运放电路的滑动变阻器到1.7V附近。

PID调试时先调P再调D,调参数前先确定极性。由于有X轴和Y轴,所以需要调两组PD,实际上两个轴的参数差不多。下面以调X轴为例,Y轴同理。
调X轴时要防止浮子向Y轴运动,浮子放在X轴上,如果左右移动浮子都是斥力则P极性正确,P从0.01开始调,0.01时没什么力增大到0.1,0.1时也没什么力增大到1,1时感受到斥力了就以1-10作为区间调。D也是这样从小到大调。逐渐增大P到浮子出现轻微抖动,这时就可以调D了,如果D能消抖则极性正确。用手挡住Y方向,浮子能稳定悬浮就可以调Y轴了。

调试过程中可以使用上位机观察波形,如果PID调了很久都没有效果,那么就是硬件或者代码的问题了。

三、整体效果

在这里插入图片描述
磁铁可以在无干扰状态下长时间保持稳定悬浮,线圈基本不发热。

结语

那么以上就是本篇文章的所有内容了。该项目的原理图、PCB和源码都上传了,源码是开源的不需要积分。
STM32下推式磁悬浮PCB.rar
STM32下推式磁悬浮装置代码.rar
本文如果有什么不对的或者需要改进的地方欢迎指出。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 09:31:07  更:2021-08-29 09:32:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:51:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码