发作品签到
专业版

下推式磁悬浮月球灯-stm32f10x

工程标签

1.3w
0
0
49

简介

采用单片机PID控制的下推式磁悬浮月球灯。

简介:采用单片机PID控制的下推式磁悬浮月球灯。

开源协议

Public Domain

创建时间:2022-12-18 13:30:50更新时间:2022-12-19 09:35:59

描述

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        {
            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        {
            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        {
            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
    {
        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站,若文件有误请自行修改。

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
1
月球.png
1159
2
14cm月球灯STL.rar
1535
3
STM32f10x源代码.rar
1841
4
磁悬浮月球灯壳体STL(高度20mm).rar
306
克隆工程
添加到专辑
0
0
分享
侵权投诉

评论

全部评论(1
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航