资讯详情

基于STM32F103智能巡线小车

项目描述: 巡线车是我作为新手开始的第一个项目。基本的巡线功能是使用红外传感器跟踪模块来确定转向方向,并控制单片机的配置PWM占空比波控制汽车前进L298N电机模块,前后退,左右转(差速转)巡线。利用HC-使用05蓝牙模块USART串口发送指令,实现小车的遥控功能;利用mpu6050陀螺仪模块感应偏航角和俯仰角,控制车辆转向和加减速; 需求分析: 任务1:能够完成遥控功能 小车可以通过手机或电脑或其他远程控制端按要求通过图1轨道启动,轨道宽度约为1cm制作黑色电工胶。 在这里插入图片描述

       图1 

1.汽车前部放置在A点,远程启动汽车。启动后,汽车以均匀的速度行驶。在行驶过程中,左右边界线不能滚动。s时间误差绝对值不超过1s。 2.汽车前部放置在A点,远程启动汽车。启动后,汽车以均匀的速度行驶。在行驶过程中,左右边界线不能滚动。汽车行驶第10s时间误差的绝对值不超过1s。 3.汽车前部放置在A点,远程启动汽车。启动后,汽车以均匀的速度行驶。在行驶过程中,左右边界线不能滚动。汽车行驶第30s车头停在B点,时间误差绝对值不超过1s,绝对误差值不超过3cm。

任务2:可完成以下巡线功能 可通过手机过手机、电脑或其他远程控制端远程启动,按要求通过图2轨道,轨道宽度约1cm制作黑色电工胶。

       图2 

汽车前部放置在A点,通过远程控制启动汽车巡逻前进。在前进过程中,如果路灯被识别为红色或其他颜色,则在路灯停止线上停止,如果路灯被识别为绿色,则继续前进。当汽车到达B点时,它会自动停止,蜂鸣器会发出声音提示。在整个前进过程中,车辆走重复路段,车辆垂直投影偏离轨道,视为失败。

任务3:可通过远端设备体感控制汽车 能够通过远程控制端倾斜来控制小车运动,远程控制端倾斜方向和程度控制小车运动方向和速度。

        图3 

假设远程控制端是图3中的纸飞机,纸飞机的机头对应远程控制端的正前方。 ?远程控制端沿pitch当方向角度A出现时,汽车后退。 ?远程控制端沿pitch当角度A出现在相反的方向时,移动。 ?远程控制端沿roll当角度B出现在方向时,汽车向右转弯(当它继续转弯时,它不会转向某个角度)。 ?当远程控制端沿roll当角度B反向出现时,汽车向左转弯。 角度A与小车速度成正比,角度A变化范围为10°40°速度变化范围为0300mm/s;角度A变化区间-10°-40°速度变化范围为0-300mm/s 角度B与汽车方向成正比,角度B变化范围为10°40°当时,汽车方向变化范围为90°135°;角B变化区间-10°-40°当时,汽车方向变化范围为90°45°(转向角度参考图4)

       图4 

设计思路:

开发过程: 1.让汽车移动:控制电机旋转 第一步是选择电机。我们选择两个直流无刷电机来控制两个轮胎的旋转,但同时驱动两个电机需要10个V以上电源供电,所以我们外接了12个V电池,但电机可以接受5V电压需要转换12V到5V,网上搜索怎么办:

数据显示,我们有两种方案,但第一种方案发热严重,我们使用第二套方案;电路图如下:

有了5V电源不够,电机只要连接到电源就可以旋转,但速度无法控制,正反转,所以需要:电机驱动TB6612FNG模块: 查询相关数据 TB6612FNG引脚图 TB6612FNG是东芝半导体公司生产的直流电机驱动器,具有大电流MOSFET-H桥梁结构,双通道电路输出,可同时驱动两个电机。 TB6612FNG每个通道最高输出1.2 A连续驱动电流,启动峰值电流达到2A/3.2 A(连续脉冲/单脉冲);四种电机控制模式:正转/反转/制动/停止;PWM支持频率高达100 kHz;待机状态;片内低压检测电路和热停机保护电路;工作温度:-20~85℃;SSOP24小贴片封装。

如图1所示,TB6612FNG主要引脚功能:AINl/AIN2、BIN1/BIN2、PWMA/PWMB控制信号输入端;AO1/A02、B01/B2路电机控制输出端02;STBY引脚控制正常工作/待机状态;VM(4.5~15 V)和VCC(2.7~5.5 V)分别是电机驱动电压输入和逻辑电平输入。 TB6612FNG是基于MOSFETH桥集成电路的效率高于晶体管H桥驱动器。相比L293D每通道平均600 mA驱动电流和1.2 A脉冲峰值电流的输出负载能力提高了一倍。相比L298N热耗和外围二极管连续流电路,不需要额外的散热器,外围电路简单,只有外部电源滤波电容器才能直接驱动电机,有利于降低系统尺寸。对于PWM它支持高达1000个信号 kHz与上述两个芯片相比,频率为5 kHz和40 kHz也有很大的优势。

根据网上找到的信息,我们用一个AINl/AIN2和PWMA控制左电机,BIN1/BIN2和PWMB来制右电机,再根据PWMA和PWMB接口需要有时钟复用功能绘制出原理图就行了: (主芯片部分) (电机驱动模块) 这样就能写驱动代码了,初始化IO口(略),配置时钟: //初始化定时器(右轮的) TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler = psc; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseInit(TIM4, & TIM_TimeBaseStructure); //初始化定时器通道 TIMOCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//输出模式 TIMOCInitStructure.TIM_Pulse=0; TIMOCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High ;//极性 TIMOCInitStructure.TIM_OutputState=ENABLE; TIM_OC3Init(TIM4,&TIMOCInitStructure);

//其他配置
TIM_ARRPreloadConfig(TIM4,ENABLE);
TIM_OC3PreloadConfig(TIM4,TIM_OCPreload_Enable);
//启动定时器
TIM_Cmd(TIM4,ENABLE);	

/---------------------------------------------------------------------------------------------/ 这的话我们给定时器4周期设置为5ms,根据TIM4挂载APB1总线,时钟频率72MHz,由公式0.005=arr*psc/72,000,000,我们假定arr为1000,速度0-1000就对应arr的0-1000(方便不用计算),所以psc为2,在初始化时配置即可;左边轮子的电机同理; 这样我们就能在单片机的IO口上配置高低电平来控制转动了,比如AIN1接PB1,AIN2接PB0,PB1置高,PB0置低就能正转,反之反转; 同时加上我们控制TIM4产生的PWM波,设置一个期望速度来设置高电平占空比: void Moter_RightControl(int speed){ int temp =speed; //限幅 if(temp<-1000) temp=-1000; if(temp> 1000) temp= 1000; if(temp<0) { AIN1=1; AIN2=0; temp=-temp; } else if(temp>0){ AIN1=0; AIN2=1; } else{ AIN1=0; AIN2=0; } TIM_SetCompare3(TIM4,(u16)temp); } 这样我们调用这个函数参数为速度就能控制电机速度和转向了;

2.能够转向:控制舵机转动 查询MG995舵机工作原理 1.MG995舵机简介 产品型号 MG995 产品尺寸 40.719.742.9mm 产品重量 55g 工作扭矩 13KG/cm 反应转速 53-62R/M 使用温度 -30~+60° 死区设定 4微秒 插头类型 JR、FUTABA通用 转动角度 最大180度 舵机类型 模拟舵机 工作电流 100mA 使用电压 3-7.2V 结构材质 金属铜齿、空心杯电机、双滚珠轴承 无负载 操作速度 0.17秒/60度(4.8V);0.13秒/60度(6.0V) 附件包含 舵盘、线长 30CM、固定螺钉、减振胶套及铝套等附件 适用范围 1:10和1:8平跑车、越野车、卡车、大脚车、攀爬车、双足机器人、机械手、遥控船,适合50级-90级甲醇固定翼飞机以及26cc-50cc汽油固定翼飞机等模型。

2.舵机接线 舵机上有三根线,分别为VCC、GND、信号线。控制信号一般要求周期为20ms的PWM信号。VCC、GND需要另外接驱动给舵机供电,而且得和开发板共地。

中间的永远是电源正极。

3.控制原理 舵机的控制一般需要一个20ms的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度舵机为例,那么对应的控制关系是这样的: 0.5ms————–0度; 1.0ms————45度; 1.5ms————90度; 2.0ms———–135度; 2.5ms———–180度; ​ 由资料显示,舵机的接线方式很简单,只需要一个周期为20ms的PWM信号接口就行,我们也找一个有定时器复用功能的IO口初始化一下,配置周期20ms:0.02=arrpsc/72,000,000,先设置arr为10000,算出psc;由舵机的对应的控制关系: 0度的占空比为0.5/20=0.025;90度的占空比为1.5/20=0.075; 180度的占空比为2.5/20=0.125;所以0度-180度对应arr的250-1250,其他值不能用(TIM_SetCompare2(TIM2,temp);中的temp不能等于这个范围以外的值)。 //初始化定时器 TIM_InitStructure.TIM_Period = arr-1; TIM_InitStructure.TIM_Prescaler = psc-1; TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2,&TIM_InitStructure); //定时器通道 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = 750; //1.5ms 0-10000 —>250-1250 TIM_OCInitStructure.TIM_OutputState = ENABLE; TIM_OC2Init(TIM2,&TIM_OCInitStructure); //使能预装载 TIM_ARRPreloadConfig(TIM2,ENABLE); //在定时器工作时改变预分频器的值 时基模块 TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable); //通道 //使能定时器 TIM_Cmd(TIM2,ENABLE); } //舵机角度控制 //param : 0~180 void Servo_setAngle(u8 angle) { u16 temp = 0; temp = angle; //限幅 if(temp<0) temp = 0; if(temp>180) temp = 180; temp = (1250-250)/180temp+250; TIM_SetCompare2(TIM2,temp); } /*==========================================================*/

配置好舵机,我们就能使用角度控制函数了。

来到主函数,初始化 Servo_Init(10000,144);//周期20ms//舵机初始化 Moter_RightInit(1000,2);//电机初始化 调用 Servo_setAngle(90); Moter_RightControl(200); Moter_LeftControl(200);小车就能以200的速度在90度方向行驶; 3.让小车自动巡线:解析红外模块数据 选择模块:红外循迹模块有很多种,这里我们选用最多的五路来控制,实物图如下:

参数搜得到:

可以看到他有7个引脚,一个vcc一个gnd,5个传感器,我们只需要在单片机上设置5个IO口读取这五个传感器返回的值即可;选5个IO口为传感器配置引脚为输入模式,读取数据,如果输入为0,则代表识别到黑线,根据这个我们就能写出方向控制函数: u8 direction=GO_STRAIGHT; //返回应该的转弯方向 u8 LineFollow(void){

if((L1==0&&R1==0)||(L2 == 0 || L1 == 0))
{
	direction = GO_LEFT_2;
	return GO_LEFT_2;
}

			else if(R1 == 0 || R2 == 0){
								direction = GO_RIGHT_2;					
							return GO_RIGHT_2;
			}
			
else if(M0 == 0	&&L1==1&&R1==1&&L2==1&&R2==1)
{
	direction=GO_STRAIGHT;
	return GO_STRAIGHT;	
}


			else {
			return direction;
			}

return direction;

} /-=----------------------------------------------------------------/ 在主函数解析返回值,进而设置方向即可; switch(LineFollow()){ case 0: Servo_setAngle(170); break; case 1: Servo_setAngle(150); break; case 2: Servo_setAngle(90); break; case 3: Servo_setAngle(40); break; case 4: Servo_setAngle(30); break;

		}				

	/

以上,我们已经能够让小车实现最基本的自动巡线。接下来加上附加功能,用串口发送指令,让小车按指令前进。 4.遥控小车:HC-05蓝牙通信技术 同样的网上先看看怎么用的: 一、说明 蓝牙传输模块一般通过串口进行通信,即RS232(设备1)<—>蓝牙模块<—>蓝牙模块<—>RS232(设备2)。因此,使用蓝牙模块需要配置的参数有串口通信参数和蓝牙通信参数。HC05蓝牙模块采用的AT配置命令进行配置。 二、数据格式 HC-05只支持一种数据格式: 数据位8 位,停止位1 位,无校验位,无流控制。波特率要选择正确, 原始模式是38400和正常模式是9600。AT命令后面需要换行,然后点发送命令才有效。 三、AT命令配置方法 按住按键或EN脚拉高,此时灯是慢闪,进入AT命令模式,默认波特率是38400。原始模式下一直处于AT命令模式状态。 四、AT主要的命令AT+RESET HC-05复位 AT+VERSION? 返回HC-05的软件版本号 AT+UART? 返回蓝牙波特率 AT+UART=115200,1,2 设置串口波特率115200,2位停止位,偶校验 AT+NAME=BLUE 修改蓝牙模块的名字为BLUE AT+ORGL 恢复出厂默认设置 AT+NAME? 返回HC-05的名字 AT+PSWD? 查询配对密码 AT+PSWD=”1234” 设置密码1234 AT+ROLE? AT+ROLE=1 ?: 查询主从状态 =1:设置成主 =0:设置成从 =2:设置成回环 波特率设置的规则如下: AT+UART=,, param1: 波特率 param2: 停止位, 0=1位,1=2位 param3: 校验位, 0=无校验,1=奇校验,2=偶校验 默认设置为9600,0,0

可以得知蓝牙通信需要配置串口,将蓝牙TX,RX连接到单片机串口的RX,TX上就可以通信了;在这之前要配置蓝牙: 用下面的ttl_usb转换模块连接上蓝牙,

长按蓝牙上的按钮然后再上电(进入AT模式),打开串口助手,发送指令 主:AT+ROLE=1 AT+PSWD=”xxxx” AT+UART=115200,0,0 从:AT+ROLE=0 AT+PSWD=”xxxx” AT+UART=115200,0,0 (主要配置这三个,其他的配置这里可以不用) 分别配置好一主一从两个蓝牙后,然后给他们配置单片机上的IO口,找到有串口复用功能的引脚,配置USART:

我们选择串口一进行配置: void Usart1_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); //USART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //USART 初始化设置 USART_InitStructure.USART_BaudRate=bound; USART_InitStructure.USART_WordLength=USART_WordLength_8b; 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); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); } / 在这里我们要思考当串口发送什么数据的时候小车才能响应,因此我们要做一个简单的通信协议,如下: 串口接收或发送数据的格式为: CAR:[控制命令?]=[小车模式],[方向],[速度] 声明如下变量: float CarMode =2;//小车模式 0遥控模式 1巡线模式 2定时模式 float Direction =90;//舵机方向 float Speed_USART =0;//串口设置的速度 这样,若要控制小车运动,模式为遥控,方向90,速度100,该指令为: CAR:SPO=0,90,100 (若要控制小车PID,p为3,i为1,d为0,该指令为:CAR:PID=3,1,0 ) 串口通信需要给他配置中断,写出如下中断函数和接收数据的函数:

char USART_ReadBuff[30]; u8 USART_ReadOK=0; char opv[2]; void USART1_IRQHandler(void) //串口1中断服务程序 { u8 temp=0; static u8 count =0; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断 { temp= USART_ReceiveData(USART1); //读取接收到的数据

	 //USART_SendData(USART1,temp);
		
	USART_ReadBuff[count++]=temp;
	USART_ITConfig(USART1, USART_IT_IDLE,ENABLE);
	}  
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)  //空闲帧
	{
	USART_ReadOK=1;
	count=0;
		//USART_SendData(USART1,'A');
	USART_ClearITPendingBit(USART1, USART_IT_IDLE);
	USART_ITConfig(USART1, USART_IT_IDLE,DISABLE);
	}  		

}

u8 USART_GetData(char* cmd,float* D1,float* D2,float* D3){ u8 flag=0; if(USART_ReadOK==1){ if(sscanf(USART_ReadBuff,“CAR:%3s=%f,%f,%f”,cmd,D1,D2,D3)>=2){ flag=1; } USART_ReadOK=0;//清除接收完成标志 memset(USART_ReadBuff,0,sizeof(USART_ReadBuff));//清除数组 } return flag; }

void USART1_SendByte(u8* addr,int size){ while(size–) { while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//等待发送缓存区完成 USART_SendData(USART1,*addr); addr++; } }

/---------------------------------------------------------------------------------------------------/

在主函数判断USART_GetData(buff,&data1,&data2,&data3)==1是否接收到数据,如果=1,解析数据: char buff[6]={0}; float data1,data2,data3; if(USART_GetData(buff,&data1,&data2,&data3)==1){ if(strcmp(buff,“PID”)==0){ … } else if(strcmp(buff,“SPO”)==0){ CarMode=data1; Direction=data2; Speed_USART=data3; } } 做完这些,小车的主要功能就都有了,接下来就是加上辅助性的功能了; 5.小车速度精准控制:编码器数据采集 要想精准控制小车的速度,就必须知道速度的实际值,本小车编码器采用的编码器原理图:

同样网上查询资料编码器怎么用(我随便复制的,肯定还有更合适的): 编码器(encoder)是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。 光电编码器如果按信号原理来分类的话,可以分为增量型编码器和绝对型编码器。旋转编码器是一种光电式旋转测量装置,它将被测的角位移直接转换成数字信号(高速脉冲信号)。因此可将旋转编码器的输出脉冲信号直接输入给PLC,利用PLC的高速计数器对其脉冲信号进行计数,以获得测量结果。   编码器接线原理:   我们通常用的是增量型编码器,可将旋转编码器的输出脉冲信号直接输入给PLC,利用PLC的高速计数器对其脉冲信号进行计数,以获得测量结果。不同型号的旋转编码器,其输出脉冲的相数也不同,有的旋转编码器输出A、B、Z三相脉冲,有的只有A、B相两相,最简单的只有A相。   编码器有5条引线,其中3条是脉冲输出线,1条是COM端线,1条是电源线(OC门输出型)。编码器的电源可以是外接电源,也可直接使用PLC的DC24V电源。电源“-”端要与编码器的COM端连接,“+ ”与编码器的电源端连接。编码器的COM端与PLC输入COM端连接,A、B、Z两相脉冲输出线直接与PLC的输入端连接,A、B为相差90度的脉冲,Z相信号在编码器旋转一圈只有一个脉冲,通常用来做零点的依据,连接时要注意PLC输入的响应时间。旋转编码器还有一条屏蔽线,使用时要将屏蔽线接地,提高抗干扰性。   编码器-----------PLC   A-----------------X0   B-----------------X1   Z------------------X2   +24V------------+24V   COM------------- -24V-----------COM   增量式编码器转轴旋转时,有相应的脉冲输出,其计数起点任意设定,可实现多圈无限累加和测量。编码器轴转一圈会输出固定的脉冲,脉冲数由编码器光栅的线数决定。需要提高分辩率时,可利用 90 度相位差的 A、B 两路信号进行倍频或更换高分辩率编码器。   绝对式光电编码器与单片机怎么接线   绝对式光电编码器有很多种接口,现在比较常见的是串行同步接口,也就是符合RS422电平标准的时钟数据接口,其时钟线通常有+,- 一组,数据线+,- 一组,如与单片机连接的话,最好是选用带有SPI功能的单片机,把单片机的SPI的时钟输出和数据输入分别用422电平转换芯片转换成差分信号后与编码器连接,当然也可以用普通单片机IO口模拟SPI时序,不过这样做的话程序上处理相当麻烦,最好不用。   NPN开路输出,又叫OC输出。   需要在A、B端分别外接一个电阻,电阻上端的电压由你的电路决定:   单片机接5V,PLC接24V,使用就很方便了。   检测A、B信号就是(1)检测脉冲数量;(2)A、B谁在前,谁在后。A相上升沿在前(出现高电平)表示编码器正转;反之B在前,表示反转。   至于45°,就看编码器一周有多少脉冲,自己分配了。   PLC与旋转编码器的接线图   旋转编码器是一种光电式旋转测量装置,它将被测的角位移直接转换成数字信号(高速脉冲信号)。因此可将旋转编码器的输出脉冲信号直接输入给plc,利用PLC的高速计数器对其脉冲信号进行计数,欧姆龙触摸屏,以获得测量结果。

(旋转编码器与plc的链接图)   如图所示是输出两相脉冲的旋转编码器与FX2N系列PLC的连接示意图。   编码器有4条引线,其中2条是脉冲输出线,1条是COM端线,1条是电源线。   编码器的电源可以是外接电源,也可直接使用PLC的DC24V电源。电源“-”端要与编码器的COM端连接,“+ ”与编码器的电源端连接。   编码器的COM端与PLC输入COM端连接,A、B两相脉冲输出线直接与PLC的输入端连接,连接时要注意PLC输入的响应时间。有的旋转编码器还有一条屏蔽线,使用时要将屏蔽线接地。 不同型号的旋转编码器,其输出脉冲的相数也不同,有的旋转编码器输出A、B、Z三相脉冲,有的只有A、B相两相,最简单的只有A相。 / 上面的资料笔者也没有看懂所有,根据本小车需要的,是AB双相脉冲的,当转动轮子时,示波器波形大概如下:

正转 反转 根据正转波形分析,当A相下降沿时,B相是低电平,当A相上升沿时,B相是高电平,两个上升/下降沿刚好隔着一个周期,电机每转一圈转化为A、B相出现一次上升/下降沿。反转正好相反; 因此如果我们规定A相上升沿时,判断B相的电平为高电平还是低电平就能知道轮子是正转还是反转;并且跳变一次就是轮子转一圈,由此还可以获得小车位移的长度(根据轮子周长)。 对单片机来说,每次跳变就要记一次数,所以需要配置中断来计数,初始化如下: void Encode_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure;

	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
													RCC_APB2Periph_GPIOB,ENABLE);	
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

		//编码器AB相

	 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;				 
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 		 
	 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	 GPIO_Init(GPIOA, &GPIO_InitStructure);	
	 GPIO_Init(GPIOB, &GPIO_InitStructure);	

//映射中断线到IO	//左轮A相 PA7     右轮A相 PB6
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource7);

	EXTI_InitStructure.EXTI_Line=EXTI_Line6;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line7;
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI9_5_IRQn ;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
}

int Encode_Right=0;
int Encode_Left=0;

void EXTI9_5_IRQHandler(void){ if(EXTI_GetFlagStatus(EXTI_Line6)!=RESET){ if(PAin(7)==0) Encode_Left–; //反转 else Encode_Left++;//正转

		EXTI_ClearITPendingBit(EXTI_Line6);
	}
	
	if(EXTI_GetFlagStatus(EXTI_Line7)!=RESET){
				if(PBin(6)==0)
					Encode_Right--; 
			else 
				Encode_Right++;
		
		EXTI_ClearITPendingBit(EXTI_Line7);			
	}		
}

获取到编码器记下的圈数后,还需要把他们转换成位移和速度我们才能用: void Encode_GetCode(int *LC,int *RC) { *LC = Encode_Left; *RC = Encode_Right; }

// 知道轮子转一圈的编码值 EX = 15000 // 知道轮子的周长 C 225mm // encode / EX C = position 99999/15000225 void Encode_GetPosition(float *LP,float *RP) { LP = (float)Encode_Left/15000201.061929; // 单位mm RP = (float)Encode_Right/15000201.061929; // 单位mm } 计算速度光有位移还不行,还需要一定时间,所以我们需要一个定时器定时计算速度,这里先作为参数使用上;在配置好后加进去。 void Encode_GetSpeed(float *LS,float *RS,float time){ static float lastleft_s=0,lastright_s=0; float left_s,right_s; Encode_GetPosition(&left_s,&right_s); *LS = (left_s-lastleft_s)/time; // mm/s *RS = (right_s-lastright_s)/time; // mm/s lastleft_s = left_s; lastright_s = right_s; } 现在来配置一个定时器,周期我们也配置成20ms,这样和之前舵机一样不用再计算而且这个周期也很合理;(代码略) 在定时器中断函数中就是需要计算出定时器响应的时候速度值了: void TIM3_IRQHandler(void){ if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET){ //功能函数 Encode_GetSpeed(&Speed_MeasureL,&Speed_MeasureR,0.02); //printf(“Speed_MeasureL=%f,Speed_MeasureR=%f\r\n”,Speed_MeasureL,Speed_MeasureR);

		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	}

} 以上获取了实际的速度。 因为有摩擦力,惯性等因素的存在,实际速度与期望的速度肯定不相同,要想把一个期望速度在实际中体现出来,就需要用到PID算法。 6.小车速度精准控制:PID算法 什是pid算法? PID:输入一个期望值,一个实际测量的值,PID会产生一个控制量让系统达到期望值。输出的值作用到PWM波(对小车来说:pwm→加速度→速度→产生位移)。 由V=V0+(1/2)at*,这里的pwm→加速度 的过程不知道具体关系,所以简化成pwm→速度→产生位移

(绿色部分为按位移算,这里没用到)

Ev :期望速度 Mv:测量速度 Err=Mv-Ev:误差 参数P误差 I 误差的累加 D(误差-上一次误差) 输出值out=P误差 + I 误差的累加 + D(误差-上一次误差)

p与out值成正比,p越大启动越快(太快会震荡),确定一个P值后输出值到一定值会产生稳态误差,比如 当p稳定时,输出值稳定,产生的加速度稳定,理想状态下加速度为零时匀速运动,但摩擦力在所难免,算出的加速度0=a=(ma-f)/m,此时实际速度没有达到期望值也不变。

原理大概如上,可能有些表达不清楚,具体些需要详细学了PID算法才能说明了; 该小车的PID算法如下: typedef struct { float p; float i; float d;

	float ErrLimit;
	float IntegLimit;
	float OutputLimit;

	float ErrInteg;
	float LastErr;

}_PID;

float PID(_PID *pid,float expect,float measure){ float Err=expect-measure; float output=0;

//误差限幅
if(Err < -pid->ErrLimit)
	Err = - pid->ErrLimit;
else if(Err > pid->ErrLimit)
	Err = pid->ErrLimit;

output=pid ->p * Err;//比例相
pid->ErrInteg+=Err;//误差积分
	
		//积分限幅
			if(pid->ErrInteg<-pid->IntegLimit){
				pid->ErrInteg =	-pid->IntegLimit;
			}
			if(pid->ErrInteg>pid->IntegLimit){
				pid->ErrInteg =	pid->IntegLimit;
			}

output+=pid ->i*pid ->ErrInteg;//积分相	
output+=pid ->d*(Err-pid ->LastErr);//微分相	
		
		
			
		//输出限幅
			if(output < -pid->OutputLimit){
				output = -pid ->OutputLimit;
			}
			if(output > pid ->OutputLimit){
				output = pid ->OutputLimit;
			}
			pid->LastErr = Err;//保存误差值,用于下一次计算
return output;

} //由于笔者对PID还是第一次接触,多的还不懂。 // 小尝试一 7.小车遥控升级版:mpu6650陀螺仪 在我们生活中,有一些赛车游戏可以感知手机的俯仰,横移,翻滚来控制游戏里的小车进行转向,加速等操作,他们用到的技术就是陀螺仪,先来了解下:

要做到这个还需要另外一块开发板,将陀螺仪固定在上面,做一个类似的遥控装置,我们还是使用蓝牙通信,先给开发板初始化并配置串口,让他可以发送数据即可;初始化陀螺仪接口…关于IIC协议我只知道一点原理,具体使用在这里并没有体现,使用的正点原子提供的MPU6050整个文件包,哈哈(我不讲了不讲了讲不了)。 不过这个人讲的挺好的:https://blog.csdn.net/muchunpeng/article/details/98311161

小尝试二 8.小车巡线升级版:openmv摄像头识别 我们让小车智能起来,能够识别交通灯,在openmv星瞳科技官网查看openmv相关信息:https://singtown.com/openmv/

这里我们还是初始化小车的开发版的USART3串口,用串口输出来实现。连线如上图。用之前的TTL转USB模块将它连接到电脑,下载它的程序还需要上位机软件OPENMV IDE 同样在上面的网站有,安装后会有很多例程学习可以看(https://book.openmv.cc/quick-starter.html),本次我们就利用颜色识别的例程来实现,他在这里:

点击copy,我们进入IDE把代码放进去,现在只需要开启他的串口并且发送我们小车能够识别的信息(我们自定义的那个协议)就行了。(我是这样整的,其实还可以传别的参数,我为方便解析)。由于这个IDE是python编程写的,所以需要一点相关知识。

我改成了这样: import sensor, image, time, math import json import time

from pyb import UART #导入包 uart = UART(3,115200)#配置uart对象 uart.init(115200, bits=8, parity=None, stop=1) thresholds_red = [(30, 100, 15, 127, 15, 127)] #红色色域识别,这个可以专门测出来实际想要的那个颜色,我这里直接用的例程的红色 thresholds_blue=[(30, 100, -64, -8, -32, 32)] sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time = 2000) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) clock = time.clock() while(True): clock.tick() img = sensor.snapshot() for blob in img.find_blobs(thresholds_red, pixels_threshold=200, area_threshold=200): uart.write(“CAR:OPV=0,90,100”)#直接传一整条指令 time.sleep(1.5)#延时的原因是防止串口一直发送数据,处理数据的串口进不了中断,没法实现。 for blob in img.find_blobs(thresholds_blue, pixels_threshold=200, area_threshold=200): uart.write(“CAR:OPV=1,90,100”) time.sleep(1.5)

在小车单片机上加上解析命令并做出反应的相关代码即可;

这个博客主要还是想理清楚这个小项目的开发思路。提炼出这当中涉及到的技术:

项目经验与收获:

学会了HC-05蓝牙模块的调试/无线遥控技术; 编码器数据采集/编码器数据转换成位移和速度; 学会了红外传感器循迹模块的使用/红外巡线技术; 了解了PID算法实现,调试,舵机控制方法; 了解了mpu6050陀螺仪的使用方法; 理解程序设计技巧思路;

使用的硬件: STM32F103RCT6; 电机驱动:TB6612FNG模块; 舵机:MG995 TTL转USB模块 陀螺仪:MPU6050 GY521 OPENMV 其他:电机,小车模型等

标签: usb连接器180度贴片式180度贴片式连接器11传感器插头mg641234计数盘状贴片电阻电容ic的机器集成电路tc74hc75af贴片电阻1m0

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台