下推式磁悬浮月球灯-stm32f10x
简介
采用单片机PID控制的下推式磁悬浮月球灯。
简介:采用单片机PID控制的下推式磁悬浮月球灯。开源协议
:Public Domain
描述
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站,若文件有误请自行修改。
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程知识产权声明&复刻说明
本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。
请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。


评论