前言
这是STM32下推式磁悬浮装置的第三篇文章,也是这个项目的最后一篇文章。前面两篇文章介绍了硬件部分,本文介绍软件部分。主要内容是源码分析和PID调试经验。源码部分基于前面两篇文章。代码开源,文末有下载地址。 STM32下推式磁悬浮装置(一)原理分析与元件清单 STM32下推式磁悬浮装置(二)原理图设计思路
以下是本篇文章的正文内容
一、源码分析
1.工程驱动
先简单介绍下工程最基本的组成成分,也就是浮子悬浮起来需要什么驱动。首先需要ADC驱动,浮子的位置信息由霍尔元件提供,霍尔元件输出的是电压值,需要使用ADC进行采集。由于我用到X、Y、Z三轴,所以需要三路ADC。
__IO uint16_t ADC_ConvertedValue[3] = {0,0,0};
static void ADC1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
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;
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);
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_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;
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_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC4Init(TIM1, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM1,ENABLE);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
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;
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_ClearITPendingBit(TIM3, TIM_IT_Update);
ADC_VALUE[0] = ADC_ConvertedValue[0];
ADC_VALUE[1] = ADC_ConvertedValue[1];
ADC_VALUE[2] = ADC_ConvertedValue[2];
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;
}
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 本文如果有什么不对的或者需要改进的地方欢迎指出。
|