1.工作原理
本方案中采用单片机PID调节的下推式磁悬浮控制原理,四周强磁铁产生向上推力,线圈通电产生吸力磁场,使浮子达到动态平衡,从而稳定漂浮。
2.硬件介绍
同样采用了3颗线性霍尔传感器,用于浮子移动位置检测,减法器放大后通过单片机ADC通道采集,如图1所示。
图1
根据ADC采集数据单片机4组PWM通道产生驱动控制信号,驱动2片DRV8870,用于X轴和Y轴的线圈磁场力和方向改变,如图2所示。
图2
触摸开关、无线发射、Z轴驱动电源开关不多作介绍,如图3和图4所示。
图3
图4
3.软件设计
单片机工程文件采用ST cubemx生成,主要配置如下图所示。
a. 时钟配置
b. ADC通道配置,并启用ADC通道DMA功能
c.开启FreeRTOS操作系统,并创建3个任务,分别作为Z轴检测(含运行指示)、X轴处理和Y轴处理任务。系统基本时钟建议选择定时器时钟。
e.定时器PWM配置,频率20kHz,PWM 0~100对于占空比0~100%。
4.软件代码
a.默认任务处理,做了Z轴开关,PWM清0和运行指示的操作。
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
uint8_t BTH_cnt=0;
uint16_t Z_ADC_table[Buf_windows];
/* Infinite loop */
for(;;)
{
for(uint16_t i;i<Buf_windows;i++)
{
Z_ADC_table[i]=User_PAR.ADC1_Value[i*3+2];
}
User_PAR.PAR_Z_ADC_value=Moving_average_filter(Z_ADC_table,Buf_windows);
if(User_PAR.PAR_Z_ADC_value>1300) //Z轴磁铁靠近
{
HAL_GPIO_WritePin(PA7_PW_SWITCH_GPIO_Port, PA7_PW_SWITCH_Pin, GPIO_PIN_SET); //Z轴
User_PAR.PW_ON_flag=SET;
}
else
{
HAL_GPIO_WritePin(PA7_PW_SWITCH_GPIO_Port, PA7_PW_SWITCH_Pin, GPIO_PIN_RESET); //Z轴
User_PAR.PW_ON_flag=RESET;
User_PAR.X_PWM_CCR=0;
User_PAR.Y_PWM_CCR=0;
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,User_PAR.X_PWM_CCR);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,User_PAR.X_PWM_CCR);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_3,User_PAR.Y_PWM_CCR);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_4,User_PAR.Y_PWM_CCR);
}
HAL_GPIO_TogglePin(PA11_RUN_LED_GPIO_Port, PA11_RUN_LED_Pin);
if(BTH_cnt++/4==1)
{
HAL_GPIO_TogglePin(PA8_BTH_LED_CTR_GPIO_Port, PA8_BTH_LED_CTR_Pin);
BTH_cnt=0;
}
osDelay(500);
}
/* USER CODE END StartDefaultTask */
}
b.X轴任务,做了ADC数据采集和PID调用。
void XPID_Task(void const * argument)
{
/* USER CODE BEGIN XPID_Task */
// uint16_t X_PWM_CCR=50;
uint16_t X_ADC_table[Buf_windows];
XPID.set_adc_value=SET_X_Value;
XPID.actual_adc_value=0;
XPID.err=0;
XPID.err_last=0;
XPID.Kp=0.50f;
XPID.Ki=0.03f;
XPID.Kd=1.0f;
XPID.sum_err=0;
XPID.PWM_duty=0;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
// __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,50);
// __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,0);
/* Infinite loop */
for(;;)
{
for(uint16_t i;i<Buf_windows;i++)
{
X_ADC_table[i]=User_PAR.ADC1_Value[i*3];
}
User_PAR.PAR_X_ADC_value=Moving_average_filter(X_ADC_table,Buf_windows);
if(User_PAR.PW_ON_flag)
PID_CALC(&XPID,User_PAR.PAR_X_ADC_value,1);
else
{
XPID.actual_adc_value=0;
XPID.err=0;
XPID.err_last=0;
XPID.sum_err=0;
XPID.PWM_duty=0;
XPID.pid_Voltage=0;
}
osDelay(1);
}
/* USER CODE END XPID_Task */
}
c. Y轴任务,,做了ADC数据采集和PID调用。
void YPID_Task(void const * argument)
{
/* USER CODE BEGIN YPID_Task */
// uint16_t Y_PWM_CCR=50;
uint16_t Y_ADC_table[Buf_windows];
YPID.set_adc_value=SET_Y_Value;
YPID.actual_adc_value=0;
YPID.err=0;
YPID.err_last=0;
YPID.Kp=0.50f;
YPID.Ki=0.03f;
YPID.Kd=1.0f;
YPID.sum_err=0;
YPID.PWM_duty=0;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4);
// __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_3,Y_PWM_CCR);
// __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_4,Y_PWM_CCR);
/* Infinite loop */
for(;;)
{
for(uint16_t i;i<Buf_windows;i++)
{
Y_ADC_table[i]=User_PAR.ADC1_Value[i*3+1];
}
User_PAR.PAR_Y_ADC_value=Moving_average_filter(Y_ADC_table,Buf_windows);
if(User_PAR.PW_ON_flag)
PID_CALC(&YPID,User_PAR.PAR_Y_ADC_value,0);
else
{
YPID.actual_adc_value=0;
YPID.err=0;
YPID.err_last=0;
YPID.sum_err=0;
YPID.PWM_duty=0;
YPID.pid_Voltage=0;
}
osDelay(1);
}
/* USER CODE END YPID_Task */
}
d. 滑动滤波以及PID调节,
typedef struct
{
#define Buf_windows 20
uint16_t ADC1_Value[Buf_windows*3];
uint16_t PAR_X_ADC_value; //X轴ADC数据
uint16_t PAR_Y_ADC_value; //X轴ADC数据
uint16_t PAR_Z_ADC_value; //X轴ADC数据
uint16_t X_PWM_CCR; //X轴占空比
uint8_t X_IS_N; //X轴N偏
uint16_t Y_PWM_CCR; //Y轴占空比
uint8_t Y_IS_N; //Y轴N偏
uint8_t PW_ON_flag; //电源打开标志
}User_TypeDef_PAR;
typedef struct
{
uint16_t set_adc_value; //
uint16_t actual_adc_value; //
float err; //
float err_last; //
float Kp,Ki,Kd; //
float sum_err; //
float pid_Voltage; //
uint16_t PWM_duty; //
uint8_t Kd_cnt;
}PID_TypeDef;
/*****************************************************************************
函数名称: uint16_t Moving_average_filter(uint16_t *table_value,uint16_t length)
函数功能: 滑动均值滤波
@param :table_value ,length
@retval :uint16_t
******************************************************************************/
uint16_t Moving_average_filter(uint16_t *table_value,uint16_t length)
{
uint16_t Value_MAX,Value_Min;
uint32_t Temp_value=0,Return_value;
Value_MAX=-0;
Value_Min= 65535;
for(uint16_t i=0;i<length;i++)
{
if(table_value[i]>=Value_MAX)
Value_MAX =table_value[i];else{/*无操作*/}
if(table_value[i]<=Value_Min)
Value_Min =table_value[i];else{/*无操作*/}
Temp_value+=table_value[i]; //求和
}
Return_value=(Temp_value-Value_MAX-Value_Min)/(length-2);//求平均
return (uint16_t)Return_value;
}
/*****************************************************************************
函数名称: void PID_CALC(PID_TypeDef *PID,uint16_t ADC_value,uint8_t IS_X)
函数功能: PID调节
@param :PID_TypeDef *PID,uint16_t ADC_value,uint8_t IS_X
@retval :NULL
******************************************************************************/
void PID_CALC(PID_TypeDef *PID,uint16_t ADC_value,uint8_t IS_X)
{
PID->actual_adc_value=ADC_value;
PID->err=PID->actual_adc_value-PID->set_adc_value;
PID->sum_err+=PID->err;
if(PID->sum_err>PID_i_MAX)
PID->sum_err=PID_i_MAX;
else if(PID->sum_err<-PID_i_MAX)
PID->sum_err=-PID_i_MAX;
else;
PID->pid_Voltage=PID->Kp*PID->err+PID->Ki*PID->sum_err+PID->Kd*(PID->err-PID->err_last);
PID->pid_Voltage=PID->pid_Voltage*1.0f;
if(PID->Kd_cnt++>=5) //减缓微分调节速度,防止震荡放大
{
PID->err_last=PID->err;
PID->Kd_cnt=0;
}
else;
if(IS_X)//X轴
{
if(PID->pid_Voltage>0) //N向偏置
{
User_PAR.X_IS_N=SET;
User_PAR.X_PWM_CCR=(uint16_t)PID->pid_Voltage;
if(User_PAR.X_PWM_CCR>PWM_Duty_Value)
User_PAR.X_PWM_CCR=PWM_Duty_Value;
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,User_PAR.X_PWM_CCR);
}
else if(PID->pid_Voltage<0) //S向偏置
{
User_PAR.X_IS_N=RESET;
User_PAR.X_PWM_CCR=(uint16_t)(fabs(PID->pid_Voltage)) ;
if(User_PAR.X_PWM_CCR>PWM_Duty_Value)
User_PAR.X_PWM_CCR=PWM_Duty_Value;
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,User_PAR.X_PWM_CCR);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,0);
}
else
{
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,0);
}
}
else //Y轴
{
if(PID->pid_Voltage>0) //N向偏置
{
User_PAR.Y_IS_N=SET;
User_PAR.Y_PWM_CCR=(uint16_t)PID->pid_Voltage;
if(User_PAR.Y_PWM_CCR>PWM_Duty_Value)
User_PAR.Y_PWM_CCR=PWM_Duty_Value;
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_3,0);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_4,User_PAR.Y_PWM_CCR);
}
else if(PID->pid_Voltage<0) //S向偏置
{
User_PAR.Y_IS_N=RESET;
User_PAR.Y_PWM_CCR=(uint16_t)(fabs(PID->pid_Voltage)) ;
if(User_PAR.Y_PWM_CCR>PWM_Duty_Value)
User_PAR.Y_PWM_CCR=PWM_Duty_Value;
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_3,User_PAR.Y_PWM_CCR);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_4,0);
}
else
{
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_3,0);
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_4,0);
}
}
}
5.软件调试
a.X轴和Y轴中心值的确认,将浮子放置在中心位置距离合适高度,编译器在线观察X值和Y值并记录;
b.先调PID中的比例P,一手拿浮子,一手通过编译器SW在线设置,从大往小调,当浮子从很大拉扯力变得微弱就行;
c.调节PID中的微分D,微分调节数据预判,参数最容易调节,切记代码中需减缓微分调节速度防止震荡加剧,否则无法调试成功;
d.PID代码中的积分必须限幅,否则容易超调,导致不稳;
e.PID调试需要耐心,调节到一个比较合适的参数后,浮子振动会明显变小。
6.测试效果
实测效果如下图所示。
7.总结
a.单片机PID调节明显比硬件三极管驱动效率高很多,功耗明显降低,线圈基本不发热;
b. 12V/2A条件下,浮子悬浮高度高于2cm,载重与底部磁铁有关(可采用环形100*60*10mm或1组圆形强磁15*5 3只共计8组),浮子可以采用15*5mm 1只、30*5mm 1只、40*5mm 2只,从小到大叠放(可稳定载重100g左右)。也可以网上购买专用浮子(500g版本,可稳定载重130g左右);
c.触摸开关接触线圈和无线LED线圈可以漆包线手工绕制;
d.附件中提供源代码、月球贴图和磁悬浮底座外壳stl文件,月球灯可以采用白色PLA材料打印,具体制作可以参考B站,若文件有误请自行修改。
!注意:请使用浏览器自带下载,迅雷等下载软件可能无法下载到有效资源。
欢迎加入EEWorld参考设计群,也许能碰到搞同一个设计的小伙伴,群聊设计经验和难点。 入群方式:微信搜索“helloeeworld”或者扫描二维码,备注:参考设计,即可被拉入群。 另外,如您在下载此设计遇到问题,也可以微信添加“helloeeworld”及时沟通。
EEWorld Datasheet 技术支持