当前位置: 首页 > news >正文

电子商务网站设计分析怎么做马云做一网站 只作一次

电子商务网站设计分析怎么做,马云做一网站 只作一次,springcloud项目搭建,站酷设计网站官网入口下载本文目录 一、知识点1. PID是什么#xff1f;2. 积分限幅--用于限制无限累加的积分项3. 输出值限幅--用于任何pid的输出4. PID工程 二、各类PID1. 位置式PID#xff08;用于位置环#xff09;#xff08;1#xff09;公式#xff08;2#xff09;代码使用代码 2. 增量式… 本文目录 一、知识点1. PID是什么2. 积分限幅--用于限制无限累加的积分项3. 输出值限幅--用于任何pid的输出4. PID工程 二、各类PID1. 位置式PID用于位置环1公式2代码使用代码 2. 增量式PID用于速度环1公式2代码3使用代码 3. 串级PID1位置环--速度环用于控制电机简易代码2位置环--位置环用于控制舵机 三、调参1. 知识点1纯Kp调节比例2Ki调节积分3Kd调节微分 2. 调参软件--野火多功能调试助手Ⅰ. 传输格式Ⅱ. 协议解析代码1上位机将pid参数发送给下位机2发送实际值、目标值给上位机 注意如果上位机上不显示波形一定要先关闭上位机重新打开才会显示且代码中我们将数据发送到通道4所以我们在上位机上要使用通道4查看波形。 一、知识点 1. PID是什么 在PID控制中P、I、D分别代表比例Proportional、积分Integral、微分Derivative三个部分。它们是PID控制器中的三个调节参数用于调节控制系统的输出以使系统的反馈与期望值更加接近。 P比例部分根据当前偏差的大小来调节输出。当偏差较大时P部分的作用就越强烈输出的变化也就越大。P控制项对应于系统的当前状态它的作用是减小系统对设定值的超调和稳定时间。 I积分部分对偏差的积累进行调节。它的作用是消除稳态误差使系统更快地达到稳定状态。I控制项对应于系统过去的行为它的作用是减小系统对外部干扰的影响。 D微分部分根据偏差变化的速度来调节输出。它的作用是预测系统未来的行为以减小系统的振荡和过冲现象提高系统的响应速度和稳定性。 综合来说PID控制器通过比例、积分、微分三个部分的组合来调节系统的输出以实现对系统的精确控制。 2. 积分限幅–用于限制无限累加的积分项 因为积分系数的Ki是与累计误差相乘的所以效果是累加随着时间的推移积分项的值会升到很高积分本来的作用是用来减小静态误差但积分项过大会引起过大的震荡所以我们可以加一个判断函数if当积分项的值达到一定值后就让积分项保持这个值避免引起更大的震荡。 积分限幅的最大值要根据经验实际多调试调试。 //为了防止积分项过度累积引入积分项的限幅是一种常见的做法。 //限制积分项的幅值可以防止积分项过度增加从而限制了系统的累积误差。这样可以避免系统过度响应或者不稳定。 float abs_limit(float value, float ABS_MAX) //积分限幅设置最大值。 {if(value ABS_MAX)value ABS_MAX;if(value -ABS_MAX)value -ABS_MAX;return value; }3. 输出值限幅–用于任何pid的输出 这个需要查看产生pwm的定时器的计数周期初值设定。如Motor_PWM_Init(7200-1,0);则outputmax就不能大于7200。 //限制输出最大值防止出现突发意外。输出outputmax的最大值if(pid-output pid-outputmax ) pid-output pid-outputmax; if(pid-output - pid-outputmax ) pid-output -pid-outputmax4. PID工程 1定时器1产生pwm tim1.c #include tim1.hvoid Motor_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外设时钟使能//设置该引脚为复用输出功能,输出TIM1 CH1 CH4的PWM脉冲波形GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; //TIM_CH1 //TIM_CH4GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);TIM_TimeBaseStructure.TIM_Period arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频TIM_TimeBaseStructure.TIM_ClockDivision 0; //设置时钟分割:TDTS Tck_timTIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_Pulse 0; //设置待装入捕获比较寄存器的脉冲值TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC4Init(TIM1, TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMxTIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能 TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH4预装载使能 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器TIM_Cmd(TIM1, ENABLE); //使能TIM1 } tim1.h #ifndef __TIM1_H #define __TIM1_H#include sys.h #define PWMB TIM1-CCR4 //PA11 void Motor_PWM_Init(u16 arr,u16 psc);#endif 2定时器2定时 #include tim2.h #include led.h #include usart.h #include sys.hvoid MotorControl(void) {Encoder_Posion Read_Position();//1.获取定时器3的编码器数值SpeedPosionPID_realize(PosionPID,Encoder_Posion);//2.输入位置式PID计算Set_Pwm(Speed); //3.PWM输出给电机 //指令/通道/发送数据/个数set_computer_value(SEND_FACT_CMD, CURVES_CH2, Encoder_Posion, 1); /*4.给上位机通道2发送实际的电机速度值,详情看下面内容*/ }void Time2_Init(u16 arr,u16 psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period arr; //电机PWM频率要和定时器采样频率一致TIM_TimeBaseInitStructure.TIM_Prescaler psc;TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0;TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2, TIM_FLAG_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority 1;NVIC_Init(NVIC_InitStructure);TIM_Cmd(TIM2, ENABLE); }void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){MotorControl();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} } 3定时器4编码器 #include stm32f10x.h // Device headervoid Encoder_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_ICInitTypeDef TIM_ICInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIO_InitStructure);TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period 65536 - 1; //ARRTIM_TimeBaseInitStructure.TIM_Prescaler 1 - 1; //PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter 0;TIM_TimeBaseInit(TIM4, TIM_TimeBaseInitStructure);TIM_ICStructInit(TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel TIM_Channel_1;TIM_ICInitStructure.TIM_ICFilter 0xF;TIM_ICInit(TIM4, TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel TIM_Channel_2;TIM_ICInitStructure.TIM_ICFilter 0xF;TIM_ICInit(TIM4, TIM_ICInitStructure);/*TI1和TI2都计数上升沿计数*/TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);TIM_Cmd(TIM4, ENABLE); }int16_t Read_Position(void) {int16_t Temp;Temp TIM_GetCounter(TIM4); //获取定时器计数值TIM_SetCounter(TIM4, 0); return Temp; } 4串口1 usart.c #include sys.h #include usart.h #if SYSTEM_SUPPORT_OS #include includes.h //ucos 使用 #endif#if 1 #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 void _sys_exit(int x) { x x; } //重定义fputc函数 int fputc(int ch, FILE *f) { while((USART1-SR0X40)0);//循环发送,直到发送完毕 USART1-DR (u8) ch; return ch; } #endif //串口1中断服务程序 //注意,读取USARTx-SR能避免莫名其妙的错误 u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节. //接收状态 //bit15 接收完成标志 //bit14 接收到0x0d //bit13~0 接收到的有效字节数目 u16 USART_RX_STA0; //接收状态标记 void uart_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1GPIOA时钟//USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority 3; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //IRQ通道使能NVIC_Init(NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate bound;//串口波特率USART_InitStructure.USART_WordLength USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; //收发模式USART_Init(USART1, USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART1, ENABLE); //使能串口1 }void USART1_IRQHandler(void)//串口中断服务函数 {u8 Res;if(USART_GetITStatus(USART1, USART_IT_RXNE) SET ) //产生了接收中断{USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除接收中断标志位ResUSART_ReceiveData(USART1);protocol_data_recv(Res,1);} }void usart1_send(u8*data, u8 len) //发送数据函数 {u8 i;for(i0;ilen;i){while(USART_GetFlagStatus(USART1,USART_FLAG_TC)RESET); USART_SendData(USART1,data[i]); } } usart.h #ifndef __USART_H #define __USART_H #include stdio.h #include sys.h #define USART_REC_LEN 200 //定义最大接收字节数 200 #define EN_USART1_RX 1 //使能1/禁止0串口1接收extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 extern u16 USART_RX_STA; //接收状态标记 void uart_init(u32 bound); void usart1_send(u8*data, u8 len); #endif 二、各类PID 1. 位置式PID用于位置环 测量位置就是通过stm32去采集编码器的脉冲数据通过脉冲计算出位置角度。目标位置和测量位置之间做差这个就是目前系统的偏差。送入 PID 控制器进行计算输出然后再经过电机驱动的功率放大控制电机的转动去减小偏差 最终达到目标位置的过程。 1公式 2代码 pid.c typedef struct PID {float Kp; // Proportional Const P系数float Ki; // Integral Const I系数float Kd; // Derivative Const D系数float PrevError ; // Error[-2] float LastError; // Error[-1] float Error; // Error[0 ] float DError; //pid-Error - pid-LastError float SumError; // Sums of Errors float output;float Integralmax; //积分项的最大值float outputmax; //输出项的最大值 } PID;//为了防止积分项过度累积引入积分项的限幅是一种常见的做法。 //限制积分项的幅值可以防止积分项过度增加从而限制了系统的累积误差。这样可以避免系统过度响应或者不稳定。 float abs_limit(float value, float ABS_MAX) //积分限幅设置最大值。 {if(value ABS_MAX)value ABS_MAX;if(value -ABS_MAX)value -ABS_MAX;return value; }//函数里传入指针修改时会修改指针里的值。 float PID_Position_Calc(PID *pid, float Target_val, float Actual_val) //位置式PID { pid-Error Target_val - Actual_val; //与pid P系数相乘。比例误差值 当前差值目标值-实际值pid-SumError pid-Error; //与pid I系数相乘。稳态误差值 误差相加作为误差总和给积分项pid-DError pid-Error - pid-LastError; //与pid D系数相乘。 微分项-消除震荡pid-output pid-Kp* pid-Error abs_limit( pid-Ki* pid-SumError, pid-Integralmax ) pid-Kd* pid-DError ; pid-LastError pid-Error; //更新误差//限制输出最大值防止出现突发意外。输出outputmax的最大值if(pid-output pid-outputmax ) pid-output pid-outputmax; if(pid-output - pid-outputmax ) pid-output -pid-outputmax;return pid-output ; //输出为pwm值 }//PID初始化 void PID_Init(PID *pid, float Kp , float Ki , float Kd , float Limit_value) { pid-Kp Kp;pid-Ki Ki;pid-Kd Kd;pid-PrevError pid-LastError pid-Error pid-SumError pid-output 0; pid-Integralmax pid-outputmax Limit_value; } 使用代码 #include sys.hPID postion_pid; float Encoder_Speed 0; float Position 0; float Speed0; float Target_val 500;int main() {Time2_Init(10000-1,7200-1); //定时器2用于定时 10000*7200/72 1sEncoder_Init(); //定时器4的编码器Motor_PWM_Init(7200-1,0); //定时器1初始化pwm输出PID_Init(postion_pid, 1.0, 0, 1.0, 7000);while(1){} }//---- 获得电机的脉冲 int16_t Encoder_Get(void) {int16_t Temp;Temp TIM_GetCounter(TIM4); //获取编码器当前值TIM_SetCounter(TIM4, 0); //将编码器计数器清0return Temp; }//设置pwm void Set_Pwm(int motor_pwm) {TIM_SetCompare4(TIM1, motor_pwm); }void MotorControl(void) {Encoder_Speed Encoder_Get();//1.获取电机1s的脉冲数。即1s获取的脉冲数。即速度Position Encoder_Speed ; //累计实际脉冲数。与时间无关。即总路程SpeedPID_Position_Calc(postion_pid, Target_val , Position);//2.输入增量式PID计算Set_Pwm(Speed); //3.PWM输出给电机//set_computer_value(SEND_FACT_CMD, CURVES_CH2, Encoder_Speed, 1); /*4.给上位机通道2发送实际的电机速度值*/ }void TIM2_IRQHandler(void) //定时器中断函数1s进一次中断 {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){MotorControl();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} } 2. 增量式PID用于速度环 增量式PID也称速度环PID速度闭环控制就是根据单位时间获取的脉冲数测量电机的速度信息并与目标值进行比较得到控制偏差然后通过对偏差的比例、积分、微分进行控制使偏差趋向于零的过程。 1公式 2代码 typedef struct PID {float Kp; // Proportional Const P系数float Ki; // Integral Const I系数float Kd; // Derivative Const D系数float PrevError ; // Error[-2] float LastError; // Error[-1] float Error; // Error[0 ] float DError; //pid-Error - pid-LastError float SumError; // Sums of Errors float output;float Integralmax; //积分项的最大值float outputmax; //输出项的最大值 } PID;float PID_Incremental_Calc(PID *pid, float Target_val, float Actual_val) { pid-Error Target_val- Actual_val; pid-output pid-Kp* ( pid-Error - pid-LastError ) pid-Ki* pid-Error pid-Kd* ( pid-Error pid-PrevError - 2*pid-LastError); pid-PrevError pid-LastError; pid-LastError pid-Error;if(pid-output pid-outputmax ) pid-output pid-outputmax;if(pid-output - pid-outputmax ) pid-output -pid-outputmax;return pid-output ; //输出为pwm值 }//PID初始化 void PID_Init(PID *pid, float Kp , float Ki , float Kd , float Limit_value) { pid-Kp Kp;pid-Ki Ki;pid-Kd Kd;pid-PrevError pid-LastError pid-Error pid-SumError pid-output 0; pid-Integralmax pid-outputmax Limit_value; } 3使用代码 #include sys.hPID speedpid; float Encoder_Speed 0; float Target_val 500; //目标1s的脉冲数 float Speed0;//实际速度int main() {Time2_Init(10000-1,7200-1); //定时器2用于定时 10000*7200/72 1sEncoder_Init(); //定时器4的编码器Motor_PWM_Init(7200-1,0); //定时器1初始化pwm输出PID_Init(speedpid, 1.0, 0, 1.0, 7000);while(1){} }//获得电机的脉冲 int16_t Encoder_Get(void) {int16_t Temp;Temp TIM_GetCounter(TIM4); //获取编码器当前值TIM_SetCounter(TIM4, 0); //将编码器计数器清0return Temp; }//设置pwm void Set_Pwm(int motor_pwm) {TIM_SetCompare4(TIM1, motor_pwm); }void MotorControl(void) {Encoder_Speed Encoder_Get();//1.获取电机1s的脉冲数。即1s获取的脉冲数。即速度。SpeedPID_Incremental_Calc(speedpid,Target_val ,Encoder_Speed);//2.输入增量式PID计算Set_Pwm(Speed); //3.PWM输出给电机//set_computer_value(SEND_FACT_CMD, CURVES_CH2, Encoder_Speed, 1); /*4.给上位机通道2发送实际的电机速度值*/ }void TIM2_IRQHandler(void) //定时器中断函数1s进一次中断 {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){MotorControl();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} } 3. 串级PID 1位置环–速度环用于控制电机 利用位置式pid的方法将位置环和速度环组合在一起使用。位置环的输出作为速度环的输入。位置环的输出作为速度环的目标期望值。这意味着位置环的输出被视为速度环应该追踪的目标位置。速度环的任务是根据当前位置和目标位置之间的偏差来生成控制输出使系统尽可能快地接近目标位置。速度环将根据当前速度和目标速度之间的差异来调整电机的输出以便使实际速度接近目标速度。 简易代码 将目标位置和实际位置传入位置环PID中计算出期望转速。然后通过期望转速与实际转速传入速度环PID中计算出对应的pwm然后通过pwm去控制电机。 #include stdio.hPID postion_pid; PID speed_pid;float Encoder_Speed 0; float Target_val 500; //目标总的脉冲数 float Speed0;//实际速度 float Position 0;int main(void) {Time2_Init(10000-1,7200-1); //定时器2用于定时 10000*7200/72 1s如果觉得时间太长可以缩短一些Encoder_Init(); //定时器4的编码器Motor_PWM_Init(7200-1,0); //定时器1初始化pwm输出// 初始化PID控制器PID_Init(postion_pid, 1.0, 0.1, 0.01, 300); // PID参数根据实际情况调整PID_Init(speed_pid, 1.0, 0.1, 0.01, 300); // PID参数根据实际情况调整while (1){} }//获得电机的脉冲 int16_t Encoder_Get(void) {int16_t Temp;Temp TIM_GetCounter(TIM4); //获取编码器当前值TIM_SetCounter(TIM4, 0); //将编码器计数器清0return Temp; }//设置pwm void Set_Pwm(int motor_pwm) {TIM_SetCompare4(TIM1, motor_pwm); }void MotorControl(void) {Encoder_Speed Encoder_Get(); //1.获取电机1s的脉冲数。即1s获取的脉冲数。即速度Position Encoder_Speed ; //累计实际脉冲数。与时间无关。即总路程SpeedPID_Position_Calc(postion_pid, Target_val , Position);//2.输入位置式PID计算SpeedPID_Incremental_Calc(speedpid,Speed, Encoder_Speed);//2.输入增量式PID计算Set_Pwm(Speed); //3.PWM输出给电机//set_computer_value(SEND_FACT_CMD, CURVES_CH2, Encoder_Speed, 1); /*4.给上位机通道2发送实际的电机速度值*/ }void TIM2_IRQHandler(void) //定时器中断函数1s进一次中断 {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){MotorControl();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} } 2位置环–位置环用于控制舵机 因为舵机没有编码器无法获取实际速度所以我们可以使用两个位置环来进行串级pid的使用这样更加精准。两个位置环的实际值输入都为距离值。第一个位置环的输出作为第二个位置环的目标值输入。   实际举例假设我们使用舵机来进行目标追踪。则第一个位置环的实际值输入当前坐标-上次坐标的差值目标值为0。将这两个值传入位置环计算的输出作为第二个位置环的目标值第二个位置环的实际值可以传入当前位置和摄像头中心点位置的差值。计算第二个位置环的输出。将其作为pwm值输入定时器通道去控制舵机。 三、调参 讲述Kp、Ki、Kd的作用。 P增加快速性过大会引起震荡和超调P单独作用会一直有静态误差。 I减少静态误差过大会引起震荡。 D减小超调过大会使响应速度变慢。 1. 知识点 1纯Kp调节比例 假设有一个高为10m的水桶需要灌满水这里我们假设Kp0.2每次灌水量为剩余灌水量的0.2倍。 第一次灌水10×0.2 剩余810-10×0.2。 第二次灌水 8×0.2 剩余6.48-8×0.2。 第三次灌水6.4×0.2 剩余5.12。 …   这里我们发现当我们设置Kp后一直会慢慢接近目标值但是永远不会到达目标值这也就是会一直有静态误差。当Kp设置过小时消耗的时间也就会更多。这里我们可以适当的调大Kp使得更快的接近目标值。但是当Kp大于某个定值时就会出现抖动如下假设Kp1.5。 则第一次灌水10×1.5剩余 -5。 第二次灌水-5×1.5剩余2.5-5 - (-5×1.5)。 第三次灌水2.5×1.5剩余 -1.25。 … 所以要根据实际适当调整p值不要使得Kp过大而出现抖动。 2Ki调节积分 作用积分时间用于解决系统的稳态误差问题即系统无法完全到达期望值的情况。当存在稳态误差时积分项会不断积累偏差并且在一段时间内持续作用于控制器的输出直到系统到达期望状态为止。   水桶例子假设你在使用一个PID控制系统来控制一个水桶的水位。如果水桶的出水口略微大于水龙头的流量那么水位就会慢慢下降形成一个稳态偏差。积分时间就像是一个将稳态偏差中的水慢慢积累起来直到水桶完全满了。如果积分时间设置得太大可能会导致水桶溢出而设置得太小则可能导致水桶永远无法完全填满。 3Kd调节微分 作用微分时间用于减小系统的超调和提高系统的稳定性。它通过监测偏差的变化速率来预测系统未来的行为并相应地调整控制器的输出以减少振荡和过冲现象。   水桶例子继续以水桶控制系统为例微分时间就像是观察水流速度的变化。如果你突然关闭水龙头但是水桶的水位仍然在上升那么微分项会告诉你要逐渐减小输出以避免水位超过期望值。如果微分时间设置得太大可能会导致系统对外部干扰过于敏感反而引起不稳定性而设置得太小则可能无法有效地抑制超调和振荡。 2. 调参软件–野火多功能调试助手 注意 在串级PID控制中上位机下传的PID参数通常应该是位置式的PID参数。因为在串级控制中位置PID控制器的输出作为速度PID控制器的输入。因此上位机通常会调节位置PID控制器的参数以影响整个串级PID系统的行为。   当上位机调节位置PID参数时它会直接影响到位置PID控制器的输出从而间接地影响到速度PID控制器的输入进而影响到整个系统的运行状态。因此在串级PID控制中上位机通常下传的是位置式的PID参数。 这个软件需要使用串口进行通信调参下面是通信代码。 Ⅰ. 传输格式 Ⅱ. 协议解析代码 只需要先将protocol.c和protocol.h添加到工程中然后使用相应的函数即可。切记该代码需要和串口1代码搭配使用因为使用了串口1的发送函数见上面PID工程。 protocol.c /********************************************************************************* file protocol.c* brief 野火PID调试助手通讯协议解析*******************************************************************************/ #include protocol.h #include string.h #include pid.h #include timer.h/*协议帧解析结构体*/ struct prot_frame_parser_t {uint8_t *recv_ptr; /*数据接收数组*/uint16_t r_oft; /*读偏移*/uint16_t w_oft; /*写偏移*/uint16_t frame_len; /*帧长度*/uint16_t found_frame_head; };/*定义一个协议帧解析结构体*/ static struct prot_frame_parser_t parser; /*定义一个接收缓冲区*/ static uint8_t recv_buf[PROT_FRAME_LEN_RECV];/*** brief 初始化接收协议* param void* return 初始化结果.*/ int32_t protocol_init(void) {/*全局变量parser清空*/memset(parser, 0, sizeof(struct prot_frame_parser_t));/* 初始化分配数据接收与解析缓冲区*/parser.recv_ptr recv_buf;return 0; }/*** brief 计算校验和* param ptr需要计算的数据* param len需要计算的长度* retval 校验和*/ uint8_t check_sum(uint8_t init, uint8_t *ptr, uint8_t len ) {/*校验和的计算结果*/uint8_t sum init;while(len--){sum *ptr;/*依次累加各个数据的值*/ptr;}return sum; }/*** brief 获取帧类型帧命令* param *buf: 数据缓冲区* param head_oft: 帧头的偏移位置* return 帧类型帧命令*/ static uint8_t get_frame_type(uint8_t *buf, uint16_t head_oft) {/*计算“帧命令”在帧数据中的位置*/uint16_t cmdIndex head_oft CMD_INDEX_VAL;return (buf[cmdIndex % PROT_FRAME_LEN_RECV] 0xFF); }/*** brief 获取帧长度* param *buf: 数据缓冲区* param head_oft: 帧头的偏移位置* return 帧长度.*/ static uint16_t get_frame_len(uint8_t *buf, uint16_t head_oft) {/*计算“帧长度”在帧数据中的位置*/uint16_t lenIndex head_oft LEN_INDEX_VAL;return ((buf[(lenIndex 0) % PROT_FRAME_LEN_RECV] 0) |(buf[(lenIndex 1) % PROT_FRAME_LEN_RECV] 8) |(buf[(lenIndex 2) % PROT_FRAME_LEN_RECV] 16) |(buf[(lenIndex 3) % PROT_FRAME_LEN_RECV] 24)); // 合成帧长度 }/*** brief 获取crc-16校验值* param *buf: 数据缓冲区.* param head_oft: 帧头的偏移位置* param frame_len: 帧长* return 校验值*/ static uint8_t get_frame_checksum(uint8_t *buf, uint16_t head_oft, uint16_t frame_len) {/*计算“校验和”在帧数据中的位置*/uint16_t crcIndex head_oft frame_len - 1;return (buf[crcIndex % PROT_FRAME_LEN_RECV]); }/*** brief 查找帧头* param *buf: 数据缓冲区.* param ring_buf_len: 缓冲区大小常量如128* param start: 起始位置读偏移* param len: 需要查找的长度* return -1没有找到帧头其他值帧头的位置.*/ static int32_t recvbuf_find_header(uint8_t *buf, const uint16_t ring_buf_len, uint16_t start, uint16_t len) {uint16_t i 0;/*帧头是4字节从0查找到len-4逐个比对*/for (i 0; i (len - 3); i){if (((buf[(start i 0) % ring_buf_len] 0) |(buf[(start i 1) % ring_buf_len] 8) |(buf[(start i 2) % ring_buf_len] 16) |(buf[(start i 3) % ring_buf_len] 24)) FRAME_HEADER) /*0x59485A53*/{return ((start i) % ring_buf_len);}} return -1; }/*** brief 计算未解析的数据的长度* param frame_len: 帧长度数据中记录的帧长度* param ring_buf_len: 缓冲区大小常量如128* param start: 起始位置读偏移* param end: 结束位置写偏移* return 未解析的数据长度*/ static int32_t recvbuf_get_len_to_parse(uint16_t frame_len, const uint16_t ring_buf_len,uint16_t start, uint16_t end) {uint16_t unparsed_data_len 0; /*未解析的数据长度*//*读偏移写偏移说明数据在环形缓存区中是连续存储的*/if (start end){unparsed_data_len end - start;}/*否则数据被分成了两部分一部分在缓冲区结尾一部分在缓冲区开头*/else{/*缓冲区结尾处的长度 缓冲区开头处处的长度*/unparsed_data_len (ring_buf_len - start) end;}if (frame_len unparsed_data_len){/*数据中记录的帧长度 未解析的数据长度*/return 0;}else{return unparsed_data_len;} }/*** brief 接收数据写入缓冲区* param *buf: 数据缓冲区.* param ring_buf_len: 缓冲区大小常量如128* param w_oft: 写偏移* param *data: 需要写入的数据* param data_len: 需要写入数据的长度* return void.*/void recvbuf_put_data(uint8_t *buf, const uint16_t ring_buf_len, uint16_t w_oft, uint8_t *data, uint16_t data_len) {/*要写入的数据超过了缓冲区尾*/if ((w_oft data_len) ring_buf_len) {/*计算缓冲区剩余长度*/uint16_t data_len_part ring_buf_len - w_oft; /*数据分两段写入缓冲区*/memcpy((buf w_oft), data, data_len_part); /*先将一部分写入缓冲区尾*/memcpy(buf, (data data_len_part), (data_len - data_len_part));/*再将剩下的覆盖写入缓冲区头*/}else{memcpy(buf w_oft, data, data_len);/*直接将整个数据写入缓冲区*/} }/*** brief 协议帧解析* param *data: 返回解析出的帧数据* param *data_len: 返回帧数据的大小* return 帧类型命令*/uint8_t protocol_frame_parse(uint8_t *data, uint16_t *data_len) {uint8_t frame_type CMD_NONE; /*帧类型*/uint16_t need_to_parse_len 0; /*需要解析的原始数据的长度*/uint8_t checksum 0; /*校验和*//*计算未解析的数据的长度*/need_to_parse_len recvbuf_get_len_to_parse(parser.frame_len, PROT_FRAME_LEN_RECV, parser.r_oft, parser.w_oft); if (need_to_parse_len 9) {/*数据太少肯定还不能同时找到帧头和帧长度*/return frame_type;}/*还未找到帧头需要进行查找*/if (0 parser.found_frame_head){int16_t header_oft -1; /*帧头偏移*//* 同步头为四字节可能存在未解析的数据中最后一个字节刚好为同步头第一个字节的情况因此查找同步头时最后一个字节将不解析也不会被丢弃*/header_oft recvbuf_find_header(parser.recv_ptr, PROT_FRAME_LEN_RECV, parser.r_oft, need_to_parse_len);if (0 header_oft){/* 已找到帧头*/parser.found_frame_head 1;parser.r_oft header_oft;/* 确认是否可以计算帧长*/if (recvbuf_get_len_to_parse(parser.frame_len, PROT_FRAME_LEN_RECV, parser.r_oft, parser.w_oft) 9){return frame_type;}}else {/* 未解析的数据中依然未找到帧头丢掉此次解析过的所有数据*/parser.r_oft ((parser.r_oft need_to_parse_len - 3) % PROT_FRAME_LEN_RECV);return frame_type;}}/* 计算帧长并确定是否可以进行数据解析*/if (0 parser.frame_len) {parser.frame_len get_frame_len(parser.recv_ptr, parser.r_oft);if(need_to_parse_len parser.frame_len){return frame_type;}}/* 帧头位置确认且未解析的数据超过帧长可以计算校验和*/if ((parser.frame_len parser.r_oft - PROT_FRAME_LEN_CHECKSUM) PROT_FRAME_LEN_RECV){/* 数据帧被分为两部分一部分在缓冲区尾一部分在缓冲区头 */checksum check_sum(checksum, parser.recv_ptr parser.r_oft, PROT_FRAME_LEN_RECV - parser.r_oft);checksum check_sum(checksum, parser.recv_ptr, parser.frame_len - PROT_FRAME_LEN_CHECKSUM parser.r_oft - PROT_FRAME_LEN_RECV);}else {/* 数据帧可以一次性取完*/checksum check_sum(checksum, parser.recv_ptr parser.r_oft, parser.frame_len - PROT_FRAME_LEN_CHECKSUM);}if (checksum get_frame_checksum(parser.recv_ptr, parser.r_oft, parser.frame_len)){/* 校验成功拷贝整帧数据 */if ((parser.r_oft parser.frame_len) PROT_FRAME_LEN_RECV) {/* 数据帧被分为两部分一部分在缓冲区尾一部分在缓冲区头*/uint16_t data_len_part PROT_FRAME_LEN_RECV - parser.r_oft;memcpy(data, parser.recv_ptr parser.r_oft, data_len_part);memcpy(data data_len_part, parser.recv_ptr, parser.frame_len - data_len_part);}else {/* 数据帧可以一次性取完*/memcpy(data, parser.recv_ptr parser.r_oft, parser.frame_len);}*data_len parser.frame_len;frame_type get_frame_type(parser.recv_ptr, parser.r_oft);/* 丢弃缓冲区中的命令帧*/parser.r_oft (parser.r_oft parser.frame_len) % PROT_FRAME_LEN_RECV;}else{/* 校验错误说明之前找到的帧头只是偶然出现的废数据*/parser.r_oft (parser.r_oft 1) % PROT_FRAME_LEN_RECV;}parser.frame_len 0;parser.found_frame_head 0;return frame_type; }/*** brief 接收到的数据写入缓冲区* param *data: 接收到的数据的数组.* param data_len: 接收到的数据的大小* return void.*/ void protocol_data_recv(uint8_t *data, uint16_t data_len) {/*数据写入缓冲区*/recvbuf_put_data(parser.recv_ptr, PROT_FRAME_LEN_RECV, parser.w_oft, data, data_len); /*计算写偏移*/parser.w_oft (parser.w_oft data_len) % PROT_FRAME_LEN_RECV; }/*** brief 设置上位机的值* param cmd命令* param ch: 曲线通道* param data参数指针* param num参数个数* retval 无*/ void set_computer_value(uint8_t cmd, uint8_t ch, void *data, uint8_t num) {static packet_head_t set_packet;uint8_t sum 0; // 校验和num * 4; // 一个参数 4 个字节set_packet.head FRAME_HEADER; // 包头 0x59485A53set_packet.ch ch; // 设置通道set_packet.len 0x0B num; // 包长set_packet.cmd cmd; // 设置命令sum check_sum(0, (uint8_t *)set_packet, sizeof(set_packet)); // 计算包头校验和sum check_sum(sum, (uint8_t *)data, num); // 计算参数校验和usart1_send((uint8_t *)set_packet, sizeof(set_packet)); // 发送数据头usart1_send((uint8_t *)data, num); // 发送参数usart1_send((uint8_t *)sum, sizeof(sum)); // 发送校验和 }/**********************************************************************************************/ protocol.h #ifndef __PROTOCOL_H__ #define __PROTOCOL_H__/*****************************************************************************/ /* Includes */ /*****************************************************************************/ #include sys.h #include usart.h#ifdef _cplusplus extern C { #endif /* 数据接收缓冲区大小 */ #define PROT_FRAME_LEN_RECV 128/* 校验数据的长度 */ #define PROT_FRAME_LEN_CHECKSUM 1/* 数据头结构体 */ typedef __packed struct {uint32_t head; // 包头uint8_t ch; // 通道uint32_t len; // 包长度uint8_t cmd; // 命令 }packet_head_t;#define FRAME_HEADER 0x59485A53 // 帧头/* 通道宏定义 */ #define CURVES_CH1 0x01 #define CURVES_CH2 0x02 #define CURVES_CH3 0x03 #define CURVES_CH4 0x04 #define CURVES_CH5 0x05/* 指令(下位机 - 上位机) */ #define SEND_TARGET_CMD 0x01 // 发送上位机通道的目标值 #define SEND_FACT_CMD 0x02 // 发送通道实际值 #define SEND_P_I_D_CMD 0x03 // 发送 PID 值同步上位机显示的值 #define SEND_START_CMD 0x04 // 发送启动指令同步上位机按钮状态 #define SEND_STOP_CMD 0x05 // 发送停止指令同步上位机按钮状态 #define SEND_PERIOD_CMD 0x06 // 发送周期同步上位机显示的值/* 指令(上位机 - 下位机) */ #define SET_P_I_D_CMD 0x10 // 设置 PID 值 #define SET_TARGET_CMD 0x11 // 设置目标值 #define START_CMD 0x12 // 启动指令 #define STOP_CMD 0x13 // 停止指令 #define RESET_CMD 0x14 // 复位指令 #define SET_PERIOD_CMD 0x15 // 设置周期/* 空指令 */ #define CMD_NONE 0xFF // 空指令/********************************************************************************************* 协议数据示例1.下发目标值55|----包头----|通道|---包长度---|命令|----参数---|校验|| 0 1 2 3 | 4 | 5 6 7 8| 9 |10 11 12 13| 14 | -索引|53 5A 48 59 | 01 | 0F 00 00 00| 11 |37 00 00 00| A6 | -协议帧数2.下发PIDP1 I2 D3|----包头----|通道|---包长度---|命令|---参数P---|---参数I---|---参数D---|校验|| 0 1 2 3 | 4 | 5 6 7 8| 9 |10 11 12 13|14 15 15 17|18 19 20 21| 22 | -索引|53 5A 48 59 | 01 | 17 00 00 00| 10 |00 00 80 3F|00 00 00 40|00 00 40 40| F5 | -协议帧数**********************************************************************************************//* 索引值宏定义 */ #define HEAD_INDEX_VAL 0x3u // 包头索引值4字节 #define CHX_INDEX_VAL 0x4u // 通道索引值1字节 #define LEN_INDEX_VAL 0x5u // 包长索引值4字节 #define CMD_INDEX_VAL 0x9u // 命令索引值1字节/* 交换高低字节未用到 */ #define EXCHANGE_H_L_BIT(data) ((((data) 24) 0xFF000000) |\(((data) 8) 0x00FF0000) |\(((data) 8) 0x0000FF00) |\(((data) 24) 0x000000FF)) /* 合成为一个字 */ #define COMPOUND_32BIT(data) (((*(data-0) 24) 0xFF000000) |\((*(data-1) 16) 0x00FF0000) |\((*(data-2) 8) 0x0000FF00) |\((*(data-3) 0) 0x000000FF)) /*** brief 接收数据处理* param *data: 要计算的数据的数组.* param data_len: 数据的大小* return void.*/ void protocol_data_recv(uint8_t *data, uint16_t data_len);/*** brief 初始化接收协议* param void* return 初始化结果.*/ int32_t protocol_init(void);/*** brief 设置上位机的值* param cmd命令* param ch: 曲线通道* param data参数指针* param num参数个数* retval 无*/ void set_computer_value(uint8_t cmd, uint8_t ch, void *data, uint8_t num);uint8_t protocol_frame_parse(uint8_t *data, uint16_t *data_len); #ifdef _cplusplus } #endif #endif1上位机将pid参数发送给下位机 上位机通过串口发送设置的pid参数信息我们通过串口接收并解析出这些信息然后设置到我们的pid上。   我们在对pid调参时如果我们使用的串级pid我们只需要调外层的pid参数即可因为内层的目标值是外层的输出。所以调外层的pid就可以影响整个系统。假如我们有x的内外层pid和y的内外层pid时我们应该先调一个如先调x。当把x层的参数调好后y的pid直接使用x一样的参数即可。如下所示:    注意为了全局代码的一致性我们不使用上位机调整目标值如果需要修改目标值我们直接在代码中修改即可。此文我们只使用上位机调整pid参数外层–位置层 /* #define SET_P_I_D_CMD 0x10 // 设置 PID 值 #define SET_TARGET_CMD 0x11 // 设置目标值 #define START_CMD 0x12 // 启动指令 #define STOP_CMD 0x13 // 停止指令 #define RESET_CMD 0x14 // 复位指令 #define SET_PERIOD_CMD 0x15 // 设置周期 */ PID PosionPID; PID SpeedPID;//该代码为串口接收上位机pid信息解析代码直接复制使用即可。 void receiving_process(void) {uint8_t frame_data[128]; // 要能放下最长的帧uint16_t frame_len 0; // 帧长度uint8_t cmd_type CMD_NONE; // 命令类型/*解析指令类型*/cmd_type protocol_frame_parse(frame_data, frame_len);switch (cmd_type){/*空指令*/case CMD_NONE:{break;}/***************设置PID***************/case SET_P_I_D_CMD:{/* 接收的4bytes的float型的PID数据合成为一个字 */uint32_t temp0 COMPOUND_32BIT(frame_data[13]);uint32_t temp1 COMPOUND_32BIT(frame_data[17]);uint32_t temp2 COMPOUND_32BIT(frame_data[21]);/*uint32_t强制转换为float*/float p_temp, i_temp, d_temp;p_temp *(float *)temp0;i_temp *(float *)temp1;d_temp *(float *)temp2;/*设置PID*/set_PID(p_temp, i_temp, d_temp); }break;/**************设置目标值***************/case SET_TARGET_CMD:{/* 接收的4bytes的int型的数据合成为一个字 */int actual_temp COMPOUND_32BIT(frame_data[13]); /*设置目标值*/set_PID_target((float)actual_temp); }break;/******************启动*****************/case START_CMD:{/*开启pid运算*/TIM_Cmd(TIM2,ENABLE); //使能定时器2}break;/******************停止*****************/case STOP_CMD:{/*停止pid运算*/Set_Pwm(0);TIM_Cmd(TIM2,DISABLE); //关闭定时器2}break;case RESET_CMD:{NVIC_SystemReset(); // 复位系统}break;} }//设置外层位置层的pid参数 void set_PID(float p, float i, float d) {PosionPID.Kp p; // 设置比例系数 PPosionPID.Ki i; // 设置积分系数 IPosionPID.Kd d; // 设置微分系数 D }//设置目标值 void set_PID_target(float temp_val) { postion_outerx.Target_val temp_val; // 设置当前的目标值 }//获取目标值 float get_pid_target(PID *pid) {return pid-Target_val; // 获取当前的目标值 }void USART1_IRQHandler(void)//串口中断服务函数 {u8 Res;if(USART_GetITStatus(USART1, USART_IT_RXNE) SET ) //产生了接收中断{USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除接收中断标志位ResUSART_ReceiveData(USART1);protocol_data_recv(Res,1); //该函数的定义在protocol.c里面。} }//-------------------------放到主函数的while里。int main() {protocol_init(); //该函数的定义在protocol.c里面。while(1){receiving_process(); //一直解析处理接收到的数据。}}2发送实际值、目标值给上位机 发送目标值与实际值。这里的目标值和实际值是外层pid位置层的目标值和实际值。 /* #define SEND_TARGET_CMD 0x01 // 发送上位机通道的目标值 #define SEND_FACT_CMD 0x02 // 发送通道实际值 #define SEND_P_I_D_CMD 0x03 // 发送 PID 值同步上位机显示的值 #define SEND_START_CMD 0x04 // 发送启动指令同步上位机按钮状态 #define SEND_STOP_CMD 0x05 // 发送停止指令同步上位机按钮状态 #define SEND_PERIOD_CMD 0x06 // 发送周期同步上位机显示的值 #define CURVES_CH1 0x01 #define CURVES_CH2 0x02 #define CURVES_CH3 0x03 #define CURVES_CH4 0x04 #define CURVES_CH5 0x05 */PID PosionPID; PID SpeedPID;int16_t Encoder_Speed 0; int16_t Position 0; int16_t Speed;//实际速度 int Target_val500; void MotorControl(void) {Encoder_Speed Read_Position();//1.获取定时器3的编码器数值PositionEncoder_Speed; //2.速度积分得到位置SpeedPID_Position_Calc(PosionPID, Target_val, Position);//3.输入位置式PID计算Speed PID_Incremental_Calc(SpeedPID, Speed, Encoder_Speed);//4.输入速度式PID计算Set_Pwm(Speed); //4.PWM输出给电机//指令/通道/发送数据/个数set_computer_value(SEND_FACT_CMD, CURVES_CH4, Position, 1); /*5.给上位机通道2发送实际的电机速度值*/set_computer_value(SEND_TARGET_CMD, CURVES_CH4, Target_val, 1); //发送目标值 }void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){MotorControl();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} }int main() {PID_Init(PosionPID, 1.0, 1.0, 1.0, 500);PID_Init(SpeedPID,1.0, 1.0, 1.0, 500);protocol_init(); //该函数的定义在protocol.c里面。while(1){} }注意如果上位机上不显示波形一定要先关闭上位机重新打开才会显示且代码中我们将数据发送到通道4所以我们在上位机上要使用通道4查看波形。
http://www.w-s-a.com/news/88894/

相关文章:

  • 爱站数据官网纯静态网站挂马
  • 网站建设公司未来方向3d设计网站
  • 建设部网站 干部学院 一级注册建筑师培训 2014年做网站开发的提成多少钱
  • 网上请人做软件的网站铝合金型材外发加工网
  • 手机网站建设万网山东省作风建设网站
  • 网站策划专员招聘50万县城做地方网站
  • 网站开发公司+重庆wordpress自定义搜索界面
  • 梅州南站学校官网
  • 网站变灰代码 所有浏览器企业邮箱域名怎么填写
  • 网站建设哪好旅行社网站模板
  • 网站开发发展存在的问题交换链接营销的经典案例
  • 烟台高端网站建设公司福田市网站建设推广
  • 做网站如何保证询盘数量智慧城市
  • 大连网站平台研发wordpress更改地址
  • 做标书要不要做网站南昌网站排名优化费用
  • 网站内容如何自动关联新浪微博万网域名信息
  • 网站出售网络推广服务费计入什么科目
  • 宁波咨询网站设计西安网站制作开发
  • 深圳市专注网站建设全网营销网络推广
  • 如何快速建设网站虚拟空间软件
  • 一个虚拟主机可以做几个网站免费软件下载中心
  • 美工培训网站中国建筑网官网手机版
  • 创建网站花钱吗谁能给个网址免费的
  • 宁波教育学会网站建设网站建设价格由什么决定
  • 北京定制网站价格wordpress上传pdf文档
  • 网站建设费税率dz论坛seo设置
  • 推销网站话术商业网站开发与设计
  • 金华网站建设哪个网站做欧洲旅行比较好
  • 东莞市住房和城乡建设局网站trswcm网站建设
  • 郑州做网站企业h5编辑器免费版