看到网上很多做磁悬浮的,但大多是Arduino的资料,刚好手头上有一块stm32最小系统,索性就想用stm32做一个。但实际上手才发现坑是真的多,在参考了动力老男孩的博客以及其他的相关资料才得以实现。
基本原理
原理就是用四个线圈对浮子在前后左右的位置进行调整,浮子的位置用线性霍尔元件进行检测,转换为电压后由单片机进行ADC采集
单片机将采集的位置数据分别进行x,y轴的pid算法处理,输出pwm控制电磁线圈的磁力从而控制浮子的位置。
霍尔输出处理
线性霍尔元件用的a3144,输出只有几百mv需要用运放将其放大,运放接5v话其最大输出有4v左右,需要用两个电阻将其限制在3.3v以下保护单片机,也可以用两个二极管组成钳位电路进行限制。原先看网上的资料说不需要用放大电路直接用Ah49e线性霍尔元件就可以,实际操作的时候发现霍尔传感器自身的噪声就掩盖了浮子的位置信息,好像也有成功的就要看自己的技术了。建议一定要用线性霍尔元件,并且要接放大电路将霍尔元件的输出放大,还要对放大后的输出限制以免烧坏stm32单片机。
将霍尔元件,运放,以及电位限制电路连接好后,需要调节电位器使输出电压稳定在1.7v附近
pid算法及调节
pid算法网上有很多这里就不解释了,贴几个连接https://blog.csdn.net/qq_25352981/article/details/81007075 https://blog.csdn.net/sdkdlwk/article/details/107759435
以下运用的是位置pid算法,pid参数调整时可以x,y轴一起调整,先调整kp也就是比例系数,再调整kd微分系数。
也可以参考https://blog.csdn.net/qq_39200996/article/details/81477223
#include<stdio.h>
#include<math.h
#include<stdlib.h>
#define Max_Cycle 290//限制幅度
#define Min_Cycle -290
typedef struct//结构体
{
int targetValue;//设定值
int Error;//误差
int prevError;//上次误差
int Integral;//误差积分
float Kp;//比例系数
float Ki;//没用
float Kd;//微分系数
}PID;
PID xPID,yPID;//X和Y的结构体
intCalc_PID(PID*pid,intPos)//位置PID
{
intoutput;
intDifferential;//微分变量
pid->Error=(pid->targetValue-Pos); //误差有正负
pid->Integral+=pid->Error; // 误差累计
Differential=pid->Error-pid->prevError;//微分部分本次误差减去上次误差
output=(pid->Kp*pid->Error+pid->Integral*pid->Ki+pid->Kd*Differential);//通过比例积分微分参数算出输出控制量可省略积分部分
pid->prevError=pid->Error; //本次误差赋值为上次
if(output>Max_Cycle)//超过上限则输出恒定值
output=Max_Cycle;
if(output<Min_Cycle)
output=Min_Cycle;
returnoutput;
}
voidInit_PID_Parameter(void)
{
xPID.targetValue=2300;//
yPID.targetValue=2300;
xPID.Error=0;
yPID.Error=0;
xPID.prevError=0;
yPID.prevError=0;
xPID.Ki=0.0f;
yPID.Ki=0.0f;
xPID.Kp=0.3f;
yPID.Kp=0.3f;
xPID.Kd=0.60f;
yPID.Kd=0.60f;
}
pwm主要就是控制电磁线圈的的磁力大小,从而控制浮子位置。将pid算法的输出作为pwm的输入,控制4路pwm的通断
l298n接受单片机的pwm输出,再输出电压到电磁线圈。
voidTIM3_GPIO_Config(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
/*设置TIM3CLK为72MHZ*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
/*GPIOAandGPIOBclockenable*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
/*GPIOAConfiguration:TIM3channel1and2asalternatefunctionpush-pull*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*GPIOBConfiguration:TIM3channel3and4asalternatefunctionpush-pull*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
voidTIM3_Mode_Config(void)
{
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
TIM_OCInitTypeDefTIM_OCInitStructure;
TIM_TimeBaseStructure.TIM_Period=299;//当定时器从0计数到999,即为1000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler=11; //设置预分频:不预分频,即为72MHz
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
/*PWM1Modeconfiguration:Channel1*/
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//当定时器计数值小于CCR1_Val时为高电平
TIM_OCInitStructure.TIM_Pulse=10; //设置跳变值,当计数器计数到这个值时,电平发生跳变可不设置
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //使能通道1
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);
/*PWM1Modeconfiguration:Channel2*/
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=10; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC2Init(TIM3,&TIM_OCInitStructure); //使能通道2
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
/*TIM3的通道3*/
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=10; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC3Init(TIM3,&TIM_OCInitStructure); //使能通道2
TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);
/*TIM通道4*/
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=10; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC4Init(TIM3,&TIM_OCInitStructure); //使能通道2
TIM_OC4PreloadConfig(TIM3,TIM_OCPreload_Enable);
/*TIM3enablecounter*/
TIM_Cmd(TIM3,ENABLE);//使能定时器3
}
voidTIM3_PWM_Init(void)
{
TIM3_GPIO_Config();
TIM3_Mode_Config();
}
//x轴pwm通断函数,读取pid的返回值
voidTIM3_OutSequence1zhuoyou(intShuzhi){
if(Shuzhi>0){
TIM_SetCompare1(TIM3,0);
TIM_SetCompare2(TIM3,Shuzhi);
}
else{
TIM_SetCompare1(TIM3,Shuzhi*(-1));
TIM_SetCompare2(TIM3,0);
}
}
//y轴pwm通断函数
voidTIM3_OutSequence1shangxia(intShuzhi){
if(Shuzhi>0){
TIM_SetCompare3(TIM3,0);
TIM_SetCompare4(TIM3,Shuzhi);
}
else{
TIM_SetCompare3(TIM3,Shuzhi*(-1));
TIM_SetCompare4(TIM3,0);
}
}
当所有电路连接好后,将强力磁铁放在四个线圈的中间,再左右前后移动如感觉有斥力将浮子限制在中间则线路连接正确,否则要调整电路连接。
大磁铁(为浮子提供主要支持力)的位置放置很重要,将浮子放在大磁铁中间上方要感觉是有排斥力才行。大磁铁最好放在板子也就是电磁线圈的下方,否者电磁线圈的控制力不足以控制浮子的姿态。或者加大输入电磁线圈的电压也行
最终效果
keil5工程文件,电路原理图,及物料清单
https://download.csdn.net/download/sijianwuxian/14975830