1. Working Principle
This scheme adopts the push-down magnetic suspension control principle of single-chip microcomputer PID regulation. The strong magnets around generate upward thrust, and the coil is energized to generate a suction magnetic field, so that the float reaches dynamic balance and thus floats stably.
2. Hardware Introduction
Three linear Hall sensors are also used for float movement position detection. After amplification by the subtractor, the data is collected through the ADC channel of the single-chip microcomputer, as shown in Figure 1.

Figure 1
According to the ADC collection data, the 4 groups of PWM channels of the single-chip microcomputer generate drive control signals to drive 2 DRV8870s for the change of the magnetic field force and direction of the coils of the X-axis and Y-axis, as shown in Figure 2.

Figure 2
Touch switch, wireless transmission, and Z-axis drive power switch are not introduced in detail, as shown in Figure 3 and Figure 4.

Figure 4
3. Software Design
The single-chip microcomputer project file is generated by ST cubemx, and the main configuration is shown in the figure below.
a. Clock configuration
b. ADC channel configuration, and enable the ADC channel DMA function
c. Open the FreeRTOS operating system and create 3 tasks, which are respectively used as Z-axis detection (including operation indication), X-axis processing and Y-axis processing tasks. It is recommended to select the timer clock as the system basic clock.
e. Timer PWM configuration, frequency 20kHz, PWM 0~100 for duty cycle 0~100%.
4. Software code
a. Default task processing, doing Z-axis switch, PWM clear 0 and run indication operations.
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
1300) //Z-axis magnet is close to { HAL_GPIO_WritePin(PA7_PW_SWITCH_GPIO_Port, PA7_PW_SWITCH_Pin, GPIO_PIN_SET); // Z axis User_PAR.PW_ON_flag=SET; } else { HAL_GPIO_WritePin(PA7_PW_SWITCH_GPIO_Port, PA7_PW_SWITCH_Pin, GPIO_PIN_RESET); //Z axis 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. __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 */}
bX-axis task, ADC data collection and PID call.
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; err_last=0; XPID.Kp=0.50f; XPID.Ki=0.03f; ) ; // __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,50);// __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,0); /* Infinite loop */ for(;;) { for(uint16_t i;i
c. Y-axis task, ADC data collection and PID call are performed.
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; ) ; // __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
d. Sliding filter and PID adjustment,
typedef struct{
#define Buf_windows 20 uint16_t ADC1_Value[Buf_windows*3]; uint16_t PAR_X_ADC_value; //X-axis ADC data uint16_t PAR_Y_ADC_value; //X-axis ADC data uint16_t PAR_Z_ADC_value; //X-axis ADC data uint16_t X_PWM_CCR; //X-axis duty cycle uint8_t X_IS_N; //X Axis N bias uint16_t Y_PWM_CCR; //Y axis duty cycle uint8_t Y_IS_N; //Y axis N bias uint8_t PW_ON_flag; //Power 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;
/*********************************************************************************Function name: uint16_t Moving_average_filter(uint16_t *table_value,uint16_t length)Function: Sliding mean filter@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=Value_MAX) Value_MAX =table_value[i];else{/*No operation*/} if(table_value[i]<=Value_Min) Value_ Min =table_value[i];else{/*No operation*/} Temp_value+=table_value[i]; //Sum } Return_value=(Temp_value-Value_MAX-Value_Min)/(length-2);//Average return (uint16_t)Return_value;}
/*******************************************************************************Function name: void PID_CALC(PID_TypeDef *PID,uint16_t ADC_value,uint8_t IS_X)Function: PID adjustment @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) //Slow down the differential adjustment speed to prevent oscillation amplification { PID->err_last=PID->err; PID->Kd_cnt=0; } else; if(IS_X)//X axis { if(PID->pid_Voltage>0) / /N-axis bias { 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-axis bias { User_PAR.X_IS_N=RESET; User_PAR.X_PWM_CCR=(uint16_t)(fabs(PID->pid_Voltage)) ; if(User_PAR. 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-axis { if(PID->pid_Voltage>0) //N-direction bias set { 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-bias{ 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. Software debugging a
. Confirm the center value of X-axis and Y-axis, place the float at the center position at a suitable height, and the compiler observes the X value and Y value online and records them;
b. First adjust the proportion P in PID, hold the float in one hand, On the one hand, use the compiler SW online setting to adjust from large to small, and it will be fine when the pull of the float changes from a large force to a weak force;
c. Adjust the differential D in PID. The differential adjustment data is pre-judged. The parameters are the easiest to adjust. Remember to slow down the differential adjustment speed in the code to prevent the oscillation from intensifying. Otherwise, the debugging cannot be successful.
d.The integral in the PID code must be limited, otherwise it is easy to overshoot and cause instability;
e. PID debugging requires patience. After adjusting to a more appropriate parameter, the float vibration will be significantly reduced.
6. Test results
The actual test results are shown in the figure below.

7. Summary
a. The PID adjustment of the single-chip microcomputer is obviously much more efficient than the hardware transistor drive, the power consumption is significantly reduced, and the coil basically does not heat up;
b. Under the condition of 12V/2A, the floating height of the float is higher than 2cm, and the load is related to the bottom magnet (a ring of 100*60*10mm or a group of 3 circular strong magnets of 15*5, a total of 8 groups can be used). The float can be 15*5mm, 1 30*5mm, and 2 40*5mm, stacked from small to large (can stably load about 100g). You can also buy special floats online (500g version, can stably load about 130g);
c. The touch switch contact coil and wireless LED coil can be manually wound with enameled wire;
d. The attachment provides source code, moon map and magnetic levitation base shell stl file. The moon lamp can be printed with white PLA material. For specific production, please refer to B station. If the file is incorrect, please modify it yourself.