微型四旋翼飞行器的设计与制作
PID控制:
以下是笔者的PID控制代码:

- void
Quadrotor_Control(const float Exp_Pitch, const float Exp_Roll, const float Exp_Yaw) - {
s16 outputPWM_Pitch, outputPWM_Roll, outputPWM_Yaw; // --- 得到当前系统的误差-->利用期望角度减去当前角度 g_Attitude_Error.g_Error_Pitch = Exp_Pitch - g_Pitch; g_Attitude_Error.g_Error_Roll = Exp_Roll - g_Roll; g_Attitude_Error.g_Error_Yaw = Exp_Yaw - g_Yaw; // --- 倾角太大,放弃控制 if (fabs(g_Attitude_Error.g_Error_Pitch) >= 55 || fabs(g_Attitude_Error.g_Error_Roll) >= 55) { PWM2_LED = 0; //蓝灯亮起 PWM_Set(0, 0, 0, 0); //停机 return ; } PWM2_LED = 1; //蓝灯熄灭 // --- 稳定指示灯,黄色.当角度值小于3°时,判定为基本稳定,黄灯亮起 if (fabs(g_Attitude_Error.g_Error_Pitch) <= 3 && fabs(g_Attitude_Error.g_Error_Roll) <= 3) PWM4_LED = 0; else PWM4_LED = 1; // --- 积分运算与积分误差限幅 if (fabs(g_Attitude_Error.g_Error_Pitch) <= 20) //积分分离-->在姿态误差角小于20°时引入积分 { //Pitch //累加误差 g_Attitude_Error.g_ErrorI_Pitch += g_Attitude_Error.g_Error_Pitch; //积分限幅 if (g_Attitude_Error.g_ErrorI_Pitch >= PITCH_I_MAX) g_Attitude_Error.g_ErrorI_Pitch = PITCH_I_MAX; else if (g_Attitude_Error.g_ErrorI_Pitch <= -PITCH_I_MAX) g_Attitude_Error.g_ErrorI_Pitch = -PITCH_I_MAX; } if (fabs(g_Attitude_Error.g_Error_Roll) <= 20) { //Roll //累加误差 g_Attitude_Error.g_ErrorI_Roll += g_Attitude_Error.g_Error_Roll; //积分限幅 if (g_Attitude_Error.g_ErrorI_Roll >= ROLL_I_MAX) g_Attitude_Error.g_ErrorI_Roll = ROLL_I_MAX; else if (g_Attitude_Error.g_ErrorI_Roll <= -ROLL_I_MAX) g_Attitude_Error.g_ErrorI_Roll = -ROLL_I_MAX; } // --- PID运算-->这里的微分D运算并非传统意义上的利用前一次的误差减去上一次的误差得来 // --- 而是直接利用陀螺仪的值来替代微分项,这样的处理非常好,因为巧妙利用了硬件设施,陀螺仪本身就是具有增量的效果 outputPWM_Pitch = (s16)(g_PID_Kp * g_Attitude_Error.g_Error_Pitch + - g_PID_Ki
* g_Attitude_Error.g_ErrorI_Pitch - g_PID_Kd * g_MPU6050Data_Filter.gyro_x_c); outputPWM_Roll = (s16)(g_PID_Kp * g_Attitude_Error.g_Error_Roll + - g_PID_Ki
* g_Attitude_Error.g_ErrorI_Roll - g_PID_Kd * g_MPU6050Data_Filter.gyro_y_c); outputPWM_Yaw = (s16)(g_PID_Yaw_Kp * g_Attitude_Error.g_Error_Yaw); // --- 给出PWM控制量到四个电机-->X模式控制 //特别注意,这里输出反相了,因为误差是反的 g_motor1_PWM = g_BasePWM + outputPWM_Pitch + outputPWM_Roll + outputPWM_Yaw; g_motor2_PWM = g_BasePWM - outputPWM_Pitch + outputPWM_Roll - outputPWM_Yaw; g_motor3_PWM = g_BasePWM - outputPWM_Pitch - outputPWM_Roll + outputPWM_Yaw; g_motor4_PWM = g_BasePWM + outputPWM_Pitch - outputPWM_Roll - outputPWM_Yaw; // --- PWM反向清零,因为没有反转 if (g_motor1_PWM < 0) g_motor1_PWM = 0; if (g_motor2_PWM < 0) g_motor2_PWM = 0; if (g_motor3_PWM < 0) g_motor3_PWM = 0; if (g_motor4_PWM < 0) g_motor4_PWM = 0; // --- PWM限幅 if (g_motor1_PWM >= g_MaxPWM) g_motor1_PWM = g_MaxPWM; if (g_motor2_PWM >= g_MaxPWM) g_motor2_PWM = g_MaxPWM; if (g_motor3_PWM >= g_MaxPWM) g_motor3_PWM = g_MaxPWM; if (g_motor4_PWM >= g_MaxPWM) g_motor4_PWM = g_MaxPWM; if (g_Fly_Enable) //允许起飞,给出PWM PWM_Set(g_motor1_PWM, g_motor2_PWM, g_motor3_PWM, g_motor4_PWM); else PWM_Set(0, 0, 0, 0); //停机 - }
加速度计滤波:
以下是笔者的8深度窗口滑动滤波代码,算法经过优化,减少了数组的移动和求和运算,利用了循环队列的原理避免了求和运算:

- void
IMU_Filter(void) - {
s32 resultx = 0; static s32 s_resulttmpx[ACC_FILTER_DELAY] = {0}; static u8 s_bufferCounterx = 0; static s32 s_totalx = 0; s32 resulty = 0; static s32 s_resulttmpy[ACC_FILTER_DELAY] = {0}; static u8 s_bufferCountery = 0; static s32 s_totaly = 0; s32 resultz = 0; static s32 s_resulttmpz[ACC_FILTER_DELAY] = {0}; static u8 s_bufferCounterz = 0; static s32 s_totalz = 0; //加速度计滤波 s_totalx -= s_resulttmpx[s_bufferCounterx]; //从总和中删除头部元素的值,履行头部指针职责 s_resulttmpx[s_bufferCounterx] = g_MPU6050Data.accel_x; //将采样值放到尾部指针处,履行尾部指针职责 s_totalx += g_MPU6050Data.accel_x; //更新总和 resultx = s_totalx / ACC_FILTER_DELAY; //计算平均值,并输入到一个固定变量中 s_bufferCounterx++; //更新指针 if (s_bufferCounterx == ACC_FILTER_DELAY) //到达队列边界 s_bufferCounterx = 0; g_MPU6050Data_Filter.accel_x_f = resultx; s_totaly -= s_resulttmpy[s_bufferCountery]; s_resulttmpy[s_bufferCountery] = g_MPU6050Data.accel_y; s_totaly += g_MPU6050Data.accel_y; resulty = s_totaly / ACC_FILTER_DELAY; s_bufferCountery++; if (s_bufferCountery == ACC_FILTER_DELAY) s_bufferCountery = 0; g_MPU6050Data_Filter.accel_y_f = resulty; s_totalz -= s_resulttmpz[s_bufferCounterz]; s_resulttmpz[s_bufferCounterz] = g_MPU6050Data.accel_z; s_totalz += g_MPU6050Data.accel_z; resultz = s_totalz / ACC_FILTER_DELAY; s_bufferCounterz++; if (s_bufferCounterz == ACC_FILTER_DELAY) s_bufferCounterz = 0; g_MPU6050Data_Filter.accel_z_f = resultz; - }
基于NRF24L01的Bootloader:
以下为Bootloader中的APP函数跳转关键代码:

- void
IAP_Load_App(u32 appxaddr) - {
if(((*(vu32*)appxaddr) & 0x2FFE0000) == 0x20000000) //检查栈顶地址是否合法 { Jump_To_App = (IAP_FunEntrance)*(vu32*)(appxaddr + 4); //用户代码区第二个字为程序开始地址(复位地址)-->详见startup.s Line61 //(vu32*)(appxaddr + 4) --> 将FLASH的首地址强制转换为vu32的指针 //*(vu32*)(appxaddr + 4) --> 解引用该地址上存放的APP跳转地址,即main函数入口 //(IAP_FunEntrance)*(vu32*)(appxaddr + 4) --> 将main函数入口地址强制转换为指向函数的指针给Jump_To_App MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) Jump_To_App(); //跳转到APP,执行APP } - }

- typedef
void (*IAP_FunEntrance)(void); //定义一个指向函数的指针 - IAP_FunEntrance
Jump_To_App;
评论