智能小车项目(七)通过PID实现给定和实际速度值计算PWM输出

这里记住一点我给定给的是速度,不是PWM,至于PWM怎么计算得到是,由实际速度与给定速度之间PID关系得到的,当前PWM与上一次PWM有关累计产生的
我们先看大脑(上位机nano)
在这里插入图片描述
keybord_ctrl节点发布’cmd_vel’消息消息类型为Twist队列大小为1

pub = rospy.Publisher('cmd_vel', Twist, queue_size=1)
if not stop: pub.publish(twist)

driver_node订阅这个消息 当有消息时cmd_vel_callback回掉函数处理消息调用set_car_motion处理消息

 rospy.init_node("driver_node", anonymous=False) 
    def cmd_vel_callback(self, msg):
        # 小车运动控制,订阅者回调函数
        # Car motion control, subscriber callback function
        if not isinstance(msg, Twist): return
        # 下发线速度和角速度
        # Issue linear vel and angular vel
        vx = msg.linear.x
        vy = msg.linear.y
        angular = msg.angular.z
        # 小车运动控制,vel: ±1, angular: ±5
        # Trolley motion control,vel=[-1, 1], angular=[-5, 5]
        # rospy.loginfo("cmd_velx: {}, cmd_vely: {}, cmd_ang: {}".format(vx, vy, angular))
        self.car.set_car_motion(vx, vy, angular)
    def set_car_motion(self, v_x, v_y, v_z):
        try:
            vx_parms = bytearray(struct.pack('h', int(v_x*1000)))
            vy_parms = bytearray(struct.pack('h', int(v_y*1000)))
            vz_parms = bytearray(struct.pack('h', int(v_z*1000)))
            cmd = [self.__HEAD, self.__DEVICE_ID, 0x00, self.FUNC_MOTION, self.__CAR_TYPE, \
                vx_parms[0], vx_parms[1], vy_parms[0], vy_parms[1], vz_parms[0], vz_parms[1]]
            cmd[2] = len(cmd) - 1
            checksum = sum(cmd, self.__COMPLEMENT) & 0xff
            cmd.append(checksum)
            self.ser.write(cmd)
            if self.__debug:
                print("motion:", cmd)
            time.sleep(self.__delay_time)
        except:
            print('---set_car_motion error!---')
            pass

再看小脑(下位机32)
下位机接到串口消息进行速度处理Motion_Ctrl

	case FUNC_MOTION:
	{
		uint8_t parm = (uint8_t) *(data_buf + 4);
		int16_t Vx_recv = *(data_buf + 6) << 8 | *(data_buf + 5);
		int16_t Vy_recv = *(data_buf + 8) << 8 | *(data_buf + 7);
		int16_t Vz_recv = *(data_buf + 10) << 8 | *(data_buf + 9);
		uint8_t adjust = parm & 0x80;
		DEBUG("motion: 0x%02X, %d, %d, %d\n", parm, Vx_recv, Vy_recv, Vz_recv);

		if (Vx_recv == 0 && Vy_recv == 0 && Vz_recv == 0)
		{
			Motion_Stop(STOP_BRAKE);
		}
		else
		{
			Motion_Ctrl(Vx_recv, Vy_recv, Vz_recv, (adjust==0?0:1));
		}
		break;
	}

通过公式计算每个轮子的速度调用Mecanum_Ctrl函数处理速度

// 控制小车运动
void Motion_Ctrl(int16_t V_x, int16_t V_y, int16_t V_z, uint8_t adjust)
{
    switch (g_car_type)
    {
    case CAR_MECANUM:
    {
        Mecanum_Ctrl(V_x, V_y, V_z, adjust);
        break;
    }
    case CAR_MECANUM_MAX:
    {
        if (V_x > CAR_X3_PLUS_MAX_SPEED)  V_x = CAR_X3_PLUS_MAX_SPEED;
        if (V_x < -CAR_X3_PLUS_MAX_SPEED) V_x = -CAR_X3_PLUS_MAX_SPEED;
        if (V_y > CAR_X3_PLUS_MAX_SPEED)  V_y = CAR_X3_PLUS_MAX_SPEED;
        if (V_y < -CAR_X3_PLUS_MAX_SPEED) V_y = -CAR_X3_PLUS_MAX_SPEED;
        Mecanum_Ctrl(V_x, V_y, V_z, adjust);
        break;
    }
    case CAR_FOURWHEEL:
    {
        Fourwheel_Ctrl(V_x, V_y, V_z, adjust);
        break;
    }
    case CAR_ACKERMAN:
    {
        Ackerman_Ctrl(V_x, V_y, V_z, adjust);
        break;
    }
    default:
        break;
    }
}

调用Motion_Set_Speed设置给定速度到motor_data结构体中

typedef struct _motor_data_t
{
    float speed_mm_s[4];        // 输入值,编码器计算速度
    float speed_pwm[4];         // 输出值,PID计算出PWM值
    int16_t speed_set[4];       // 速度设置值
} motor_data_t;
void Motion_Set_Speed(int16_t speed_m1, int16_t speed_m2, int16_t speed_m3, int16_t speed_m4)
{
    g_start_ctrl = 1;
    motor_data.speed_set[0] = speed_m1;
    motor_data.speed_set[1] = speed_m2;
    motor_data.speed_set[2] = speed_m3;
    motor_data.speed_set[3] = speed_m4;
    for (uint8_t i = 0; i < MAX_MOTOR; i++)
    {
        PID_Set_Motor_Target(i, motor_data.speed_set[i]*1.0);
    }
}

将目标速度给入pid_motor[i].target_val中

typedef struct _pid
{
    float target_val;               //目标值
    float output_val;               //输出值
    float pwm_output;        		//PWM输出值
    float Kp,Ki,Kd;          		//定义比例、积分、微分系数
    float err;             			//定义偏差值
    float err_last;          		//定义上一个偏差值

    float err_next;                 //定义下一个偏差值, 增量式
    float integral;          		//定义积分值,位置式
} pid_t;

void PID_Set_Motor_Target(uint8_t motor_id, float target)
{
    if (motor_id > MAX_MOTOR) return;

    if (motor_id == MAX_MOTOR)
    {
        for (int i = 0; i < MAX_MOTOR; i++)
        {
            pid_motor[i].target_val = target;
        }
    }
    else
    {
        pid_motor[motor_id].target_val = target;
    }
}

到这我们完成给定输入

然后我们看实际和给定之间的计算

void Motion_Handle(void)
{
    Motion_Get_Speed(&car_data);

    if (g_start_ctrl)
    {
        Motion_Set_Pwm(motor_data.speed_pwm[0], motor_data.speed_pwm[1], motor_data.speed_pwm[2], motor_data.speed_pwm[3]);
    }
}

Motion_Get_Speed(&car_data);

首先得到编码器的当前值,编码器的值如何传过来的后面再说
通过该值计算当前各轮速度
通过各轮速度由公式计算当前小车xyz速度

 
// 从编码器读取当前各轮子速度,单位mm/s
void Motion_Get_Speed(car_data_t* car)
{
    int i = 0;
    float speed_mm[MAX_MOTOR] = {0};
    float circle_mm = Motion_Get_Circle_MM();
    float circle_pulse = Motion_Get_Circle_Pulse();
    float robot_APB = Motion_Get_APB();

    Motion_Get_Encoder();

    // 计算轮子速度,单位mm/s。
    for (i = 0; i < 4; i++)
    {
        speed_mm[i] = (g_Encoder_All_Offset[i]) * 100 * circle_mm / circle_pulse;
				//printf("speed%d : %f",i,speed_mm[i]);
    }
    switch (g_car_type)
    {
    case CAR_MECANUM:
    {
        car->Vx = (speed_mm[0] + speed_mm[1] + speed_mm[2] + speed_mm[3]) / 4;
        car->Vy = -(speed_mm[0] - speed_mm[1] - speed_mm[2] + speed_mm[3]) / 4;
        car->Vz = -(speed_mm[0] + speed_mm[1] - speed_mm[2] - speed_mm[3]) / 4.0f / robot_APB * 1000;
        break;
    }
    case CAR_MECANUM_MAX:
    {
        car->Vx = (speed_mm[0] + speed_mm[1] + speed_mm[2] + speed_mm[3]) / 4;
        car->Vy = -(speed_mm[0] - speed_mm[1] - speed_mm[2] + speed_mm[3]) / 4;
        car->Vz = -(speed_mm[0] + speed_mm[1] - speed_mm[2] - speed_mm[3]) / 4.0f / robot_APB * 1000;
//				printf("linear_x:%d\n",car->Vx);
//				printf("linear_y:%d\n",car->Vy);
//				printf("linear_z:%f\n",car->Vz);
        break;
    }
    case CAR_ACKERMAN:
    {
        car->Vx = (speed_mm[1] + speed_mm[3]) / 2;
        car->Vy = Ackerman_Get_Steer_Angle();
        car->Vz = -(speed_mm[1] - speed_mm[3]) * 1000 / robot_APB;
        break;
    }    
    
    default:
        break;
    }

    if (g_start_ctrl)
    {
        for (i = 0; i < MAX_MOTOR; i++)
        {
            motor_data.speed_mm_s[i] = speed_mm[i];
        }
        
        #if ENABLE_YAW_ADJUST//这个宏关的
        if (g_yaw_adjust)
        {
            #if ENABLE_INV_MEMS
            Mecanum_Yaw_Calc(ICM20948_Get_Yaw_Now());
            #elif ENABLE_MPU9250
            Mecanum_Yaw_Calc(MPU_Get_Yaw_Now());
            #endif
        }
        #endif
        PID_Calc_Motor(&motor_data);

        #if PID_ASSISTANT_EN//这个宏关的
        if (start_tool())
        {
            int32_t speed_send = car->Vx;
            // int32_t speed_send = (int32_t)speed_m1;
            set_computer_value(SEND_FACT_CMD, CURVES_CH1, &speed_send, 1);
        }
        #endif
    }
}

调用这个函数计算值
PID_Calc_Motor(&motor_data);

void PID_Calc_Motor(motor_data_t* motor)
{
    int i;
    // float pid_out[4] = {0};
    // for (i = 0; i < MAX_MOTOR; i++)
    // {
    //     pid_out[i] = PID_Location_Calc(&pid_motor[i], 0);
    //     PID_Set_Motor_Target(i, pid_out[i]);
    // }
    
    for (i = 0; i < MAX_MOTOR; i++)
    {
        motor->speed_pwm[i] = PID_Incre_Calc(&pid_motor[i], motor->speed_mm_s[i]);
    }
}

调用PID_Incre_Calc这个函数完成PID计算计算出的值给到 motor->speed_pwm中

typedef struct _pid
{
    float target_val;               //目标值
    float output_val;               //输出值
    float pwm_output;        		//PWM输出值
    float Kp,Ki,Kd;          		//定义比例、积分、微分系数
    float err;             			//定义偏差值
    float err_last;          		//定义上两个个偏差值

    float err_next;                 //定义上一个偏差值, 增量式
    float integral;          		//定义积分值,位置式
} pid_t;

u(k)=Kp * e(k-1)+Ki *e(k) +Kd *(e(k)-2e(k-1)+e(k-2))+u(k-1);

float PID_Incre_Calc(pid_t *pid, float actual_val)
{
    /*计算目标值与实际值的误差*/
    pid->err = pid->target_val - actual_val;
    /*PID算法实现*/
    pid->pwm_output += pid->Kp * (pid->err - pid->err_next) 
                    + pid->Ki * pid->err 
                    + pid->Kd * (pid->err - 2 * pid->err_next + pid->err_last);
    /*传递误差*/
    pid->err_last = pid->err_next;
    pid->err_next = pid->err;
    
    /*返回PWM输出值 这里如果忽略死区将MOTOR_IGNORE_PULSE设置为0*/
    if (pid->pwm_output > (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE))
        pid->pwm_output = (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE);
    if (pid->pwm_output < (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE))
        pid->pwm_output = (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE);
    return pid->pwm_output;

    // // 计算偏差
    // pid->err = pid->target_val - actual_val;
    // //增量式PI控制器
    // pid->pwm_output += pid->Kp * (pid->err - pid->err_last) + pid->Ki * pid->err;
                   
    // if (pid->pwm_output > (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE))
    //     pid->pwm_output = (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE);
    // if (pid->pwm_output < (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE))
    //     pid->pwm_output = (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE);
    // pid->err_last = pid->err;
    // return pid->pwm_output;
}
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 首先,需要了解小车的转向需求,例如左转、右转、直行或停止。然后,需要将这些转向需求转换为PWM输出的占空比和极性。一般来说,PWM输出的占空比越大,电机转速就越快;而极性则决定了电机的转向。 以下是一个简单的例子,假设小车有两个电机(左右轮各一个),可以通过以下步骤计算PWM输出的占空比和极性: 1. 定义PWM的频率,例如500Hz。 2. 根据转向需求和小车的设计规格,确定两个电机的最大转速和最小转速。 3. 根据最大转速和最小转速,计算PWM输出的最大占空比和最小占空比,例如最大占空比为80%,最小占空比为20%。 4. 根据转向需求,确定左右轮的转向方向(正转或反转)。 5. 根据转向需求,计算出左右轮的目标转速(例如左轮目标转速为200RPM,右轮目标转速为250RPM)。 6. 根据最大转速和最小转速,以及左右轮的目标转速,计算出左右轮对应的PWM输出占空比,例如左轮PWM输出占空比为60%,右轮PWM输出占空比为75%。 7. 根据左右轮的转向方向,确定左右轮的PWM输出极性(正向或反向)。 8. 将左右轮的PWM输出占空比和极性发送给电机控制器,控制电机转动。 需要注意的是,小车的具体实现可能有所不同,上述步骤仅供参考。此外,还需要考虑电机的额定电压和电流等参数,以确保电机能够正常运行。 ### 回答2: 编写程序根据小车的转向需求计算PWM输出的占空比和极性需要以下几个步骤: 1. 获取小车的转向需求:程序需要获取小车的转向需求,例如前进、后退、左转、右转等。 2. 设置PWM的频率和周期:根据小车的驱动需求,设置PWM的频率和周期。频率指每秒钟的脉冲数量,周期是指一个完整脉冲的时间。 3. 计算PWM的占空比:根据小车的转向需求和驱动方式,计算PWM的占空比。占空比是指信号的高电平占整个脉冲周期的比例。 4. 设置PWM的极性:根据小车的转向需求和驱动电路的设计,设置PWM的极性。极性指信号的电压变化方向,如正向或负向。 5. 输出PWM信号:根据计算得到的占空比和极性,通过程序将PWM信号输出到小车的驱动电路。可以使用硬件PWM模块或者软件编写PWM输出的相关功能。 通过以上步骤,我们可以编写程序来根据小车的转向需求计算PWM输出的占空比和极性,并实现对小车的准确控制。程序的具体实现方式,可以根据不同的开发平台和编程语言进行选择和调整。 ### 回答3: 要编写程序来根据小车的转向需求计算PWM输出的占空比和极性,我们可以按照以下步骤进行: 1. 首先,确定小车转向的需求。例如,假设需求是向前行驶、向后行驶、左转和右转。 2. 根据不同的转向需求,设定对应的占空比和极性。占空比表示PWM信号高电平时间占总周期的比例,极性表示高电平或低电平对应的操作,通常使用高电平表示向前或向右,低电平表示向后或向左。 3. 编写程序来根据转向需求计算PWM输出。可以使用条件语句(如if-else语句)来判断转向需求,并根据不同的情况设置对应的占空比和极性。 4. 设置PWM输出。根据计算出来的占空比和极性,将其设置为PWM输出的参数。具体的设置方法可以根据使用的硬件平台和编程语言来确定。 5. 测试程序。将小车连接到PWM输出端口,并运行程序进行测试。根据不同的转向需求,观察小车的行驶情况和PWM输出的波形是否符合预期。 总结:通过根据小车的转向需求计算PWM输出的占空比和极性,可以实现对小车的控制。编写程序时,需要确定转向需求,并根据需求设置对应的参数,最后进行测试来验证程序的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值