本文用的单片机是STM32F103C8T6,循迹模块是五路循迹传感器,电机是直流电机,电机驱动模块是L293D

效果展示

这里是没优化完成的效果,后续优化完成会呈上

视频演示:https://www.bilibili.com/video/BV1ka4y197B8/

基于STM32和PID控制算法实现循迹功能

五路循迹传感器

工作原理

五路循迹传感器的核心是五个感光二极管,通过感知地上的黑线,从而实现对小车运动轨迹的控制和调整。就是这么简单!

传感器的工作原理是通过五个感光电二极管来感知地面上的黑线。当小车运动到黑线上方时,这些感光电二极管会接收到反射回来的光线,从而变化电子状态并产生电压信号。这些信号经过放大和滤波处理后,成为高低电平信号,被输入到控制器中,控制器再通过算法来分析这些数据,从而判断小车的位置和方向,并调整运动轨迹,使小车按照预设路径行驶。

硬件

五路循迹模块

 引脚

让我们看一下它的引脚排列。

GND 是连接到STM32的地。

VCC 是传感器的电源,我们连接了5V的供电。

OUT1~OUT5 是5个传感器的引脚,用于发送电平信号。

接线

将五路循迹模快连接到STM32。

五路循迹传感器STM32
VCC5V
GNDGND
OUT1GPIO PA4
OUT2GPIO PA5
OUT3GPIO PA6
OUT4GPIO PA7
OUT5GPIO PB0

L293D电机驱动模块

L293D是一种基本的电机驱动器集成芯片(IC),它能够在任意方向驱动直流电机并控制惦记的速度。L293D是一个16引脚IC,每侧有8个引脚,可以很好的控制电机。

电路图

工作原理

L293D有4个方向控制输入引脚,IC左侧的引脚2、7(A2和A3)和右侧的引脚15、10(A0和A1)。左侧输入引脚调节连接在左端的电机旋转,右侧输入引脚调节右侧的电机。电机根据输入引脚上提供的高电平或低电平信号进行旋转。

例如:

  • 引脚2  = 高,引脚7  = 低 顺时针旋转
  • 引脚2 = 低,引脚7= 高 逆时针旋转
  • 引脚2 = 低,引脚7 = 低 无旋转
  • 引脚2 = 高,引脚7= 高 无旋转

 右侧同理。

接线

本次接线因为有底板设计了电机驱动模快,我们只需将4个输入引脚接在核心板上。

PID控制算法

PID是比例(proportion)、积分(integral)、微分(derivative)是一种闭环控制算法,通过反馈值进行误差计算,然后通过PID算法控制负载设备。

公式

原理

在PID控制器中,比例环节的作用是对偏差瞬间做出反应。偏差一旦产生控制器立即产生控制作用,使控制量向减少偏差方向变化。控制作用的强弱取决于比例系数,比例系数越大,控制作用越强,则过渡越快,控制过程的静态偏差也就越小;但是越大,也越容易产生振荡,破坏系统的稳定性。故而,比例系数选择必须恰当,才能过渡时间短,稳定的效果。

积分,顾名思义就是一个累加的过程,积分调节的输出是与输入、输出反馈的误差的积分成正比关系,所以积分调节器的作用就是用来消除稳态误差。

为了加快调节过程,在偏差出现的瞬间,或在偏差变化的瞬间,不但要对偏差量做出立即响应,而且要根据偏差的变化趋势预先给出适当的纠正。

自动循迹思路

误差分析

约定左右轮速度调整:
left_motor_pwm_speed  =  left_motor_pwm_speed + pid_output
right_motor_pwm_speed =  right_motor_pwm_speed - pid_output

1.异常情况,小车向需要右偏(左轮速度增加,右轮减少)
case 0x19: cur_error = 2; break;   //B11001
case 0x1d: cur_error = 4; break;   //B11101
case 0x1c: cur_error = 6; break;   //B11100
case 0x18: cur_error = 7; break;   //B11000
case 0x1e: cur_error = 8; break;   //B11110

2.正常情况
case 0x0 :                          //B00000
case 0x1b: cur_error = 0;  break;   //B11011

3.异常情况,小车向需要左偏(右轮速度增加,左轮减少)
case 0x13: cur_error = -2;  break;   //B10011
case 0x17: cur_error = -3;  break;   //B10111
case 0x3 : cur_error = -4;  break;   //B00111
case 0x7 :                           //B00111
case 0x1 : cur_error = -7;  break;   //B00001
case 0xf : cur_error = -8;  break;   //B01111

4.极端情况,小车已经冲出赛道,根据冲出之前的误差,将误差调整到最大
case 0x1f: cur_error = pid.error > 0 ? 9 : -9; break;//B11111

五路循迹管电平读取状态代码

 

#define MOTOR_PWM_SPEED 500
#define MOTOR_PWM_MAX_SPEED 700
#define MOTOR_PWM_MIN_SPEED 0

#define PID_KP 25
#define PID_KI 0.8
#define PID_KD 15


typedef struct {
	int8_t error;
	int8_t last_error;
	int8_t kp;
	float ki;
	int8_t kd;
} pid_t;

bool pid_contorl = true;
pid_t pid;

int32_t left_motor_pwm_speed = MOTOR_PWM_SPEED;
int32_t right_motor_pwm_speed = MOTOR_PWM_SPEED;

//读取状态
uint8_t read_irs_state( void )
{
	int i;
	uint8_t status  = 0;
	uint8_t state[5];

	state[0]  = HAL_GPIO_ReadPin( GPIOA,GPIO_PIN_4 );
	state[1]  = HAL_GPIO_ReadPin( GPIOA,GPIO_PIN_5 );
	state[2]  = HAL_GPIO_ReadPin( GPIOA,GPIO_PIN_6 );
	state[3]  = HAL_GPIO_ReadPin( GPIOA,GPIO_PIN_7 );
	state[4]  = HAL_GPIO_ReadPin( GPIOB,GPIO_PIN_0 );
	

	for( i = 0; i < 5; i++ ) {
		status |=( state[i] << i );
	}

	return status;
}


采用中值滤波算法获取传感器状态

uint8_t get_middle_filter_irs_state( void )
{
	int i;
	uint8_t state[9];

	for( i =0; i<9; i++ ) {
		state[i] = read_irs_state();
	}

	printf( "irs:" );

	for( i =4; i>=0; i-- ) {
		if( state[4] & ( 1 << i ) ) {
			printf( "1" );

		} else {
			printf( "0" );
		}
	}

	printf( "\r\n" );

	return state[4];

}

根据传感器状态获取误差值

uint8_t calc_error_by_irs( uint8_t state )
{ 
  int8_t cur_error = pid.error;
	
	  switch( state ) {
	    
	    case 0x19: cur_error = 2; break;   //B11001
			case 0x1d: cur_error = 4; break;   //B11101
			case 0x1c: cur_error = 6; break;   //B11100
			//case 0x18: cur_error = 6; break;   //B11000
			case 0x18: cur_error = 7; break;   //B11000
	    case 0x1e: cur_error = 8; break;   //B11110
			case 0x0 :                          //B00000
			case 0x1b: cur_error = 0;  break;   //B11011
			case 0x13: cur_error = -2;  break;   //B10011
			case 0x17: cur_error = -4;  break;   //B10111
			//case 0x7 : cur_error = -5;  break;   //B00111
			case 0x3 : cur_error = -6;  break;   //B00111
			case 0x7 :                           //B00111
			case 0x1 : cur_error = -7;  break;   //B00001
			case 0xf : cur_error = -8;  break;   //B01111
	    case 0x1f: cur_error = pid.error > 0 ? 9 : -9; break;//B11111
	  }
  return cur_error;
}

采用平均数求和滤波算法获取当前的误差值

int8_t get_current_irs_error( void )
{
	int i;
	int sum =0;

	for( i = 0; i < 10; i++ ) {
		int8_t state = get_middle_filter_irs_state();
		int8_t error = calc_error_by_irs( state );
		sum += error;
	}

	return sum/10;
}

根据误差值用PID算法算出输出的控制量调整左右轮速度

int32_t pid_calc_output( void )
{
	int32_t P;
	static int32_t I;
	int32_t D;
	int32_t pid_output;

	pid.error = get_current_irs_error();
	P = pid.kp * pid.error;
	I +=pid.ki * pid.error;
	D = pid.kd * ( pid.error -pid.last_error );
	pid.last_error = pid.error;

	pid_output = P + I + D;
	return pid_output;

}

void update_motor_speed_by_pid( int32_t pid_output )
{
  if(!pid.error){
    left_motor_pwm_speed  = MOTOR_PWM_SPEED;
    right_motor_pwm_speed = MOTOR_PWM_SPEED;
    return;
  }
  
	left_motor_pwm_speed =  left_motor_pwm_speed + pid_output;
	if( left_motor_pwm_speed <=MOTOR_PWM_MIN_SPEED ) {
		left_motor_pwm_speed = MOTOR_PWM_MIN_SPEED;

	} else if( left_motor_pwm_speed >= MOTOR_PWM_MAX_SPEED ) {
		left_motor_pwm_speed = MOTOR_PWM_MAX_SPEED;
	}

	right_motor_pwm_speed =  right_motor_pwm_speed - pid_output;

	if( right_motor_pwm_speed <=MOTOR_PWM_MIN_SPEED ) {
		right_motor_pwm_speed = MOTOR_PWM_MIN_SPEED;

	} else if( right_motor_pwm_speed >= MOTOR_PWM_MAX_SPEED ) {
		right_motor_pwm_speed = MOTOR_PWM_MAX_SPEED;
	}

	return;
}

PID参数初始化

#define PID_KP 5
#define PID_KI 0.01
#define PID_KD 10

void pid_init( void )
{
	pid.error = 0;
	pid.last_error = 0;
	pid.kp = PID_KP;
	pid.ki = PID_KI;
	pid.kd = PID_KD;

	return;
}

根据PID算法控制小车

#define MOTOR_PWM_SPEED 500
#define MOTOR_PWM_MAX_SPEED 700
#define MOTOR_PWM_MIN_SPEED 0

void pid_control_motor( void )
{
  if(pid.error >= 7 && pid.error <= 9){
    car_turn_right(MOTOR_PWM_MAX_SPEED);
    return;
  }else if(pid.error >= -9 && pid.error <= -7){
    car_turn_left(MOTOR_PWM_MAX_SPEED);
    return;
  }
	left_motor_forward( left_motor_pwm_speed );
	right_motor_forward( right_motor_pwm_speed );
}

void pid_control_car( void )
{
	int32_t pid_output;
	pid_init();

	while( pid_contorl ) {
		pid_output = pid_calc_output();
		update_motor_speed_by_pid( pid_output );
		pid_control_motor();
		HAL_Delay( 10 );
	}

	return;
}

总结

通过这次功能的实现,我清楚的了解循迹传感器、驱动电机、PID算法控制的原理和使用,可以实现智能巡线、物品跟随等功能,同时也了解自己的不足,还是有很大的提升空间,加油!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐