精华内容
下载资源
问答
  • 两轮平衡车自动跟随小车-两轮平衡车自动跟随小车代码.rar
  • 目的:一种廉价的跟随方案,让大家都能够参与进来,技术难度不大,一些人也能够DIY一些属于自己的“跟随”机器人!...在这我们只介绍自动跟随的方案,牵扯到机器人底盘移动等其他控制部分,在这不做过多赘述了。 ...

    目的:一种廉价的跟随方案,让大家都能够参与进来,技术难度不大,一些人也能够DIY一些属于自己的“跟随”机器人!并不是要做工业应用什么的。只是做出来玩玩~

    / 1 / 介绍

    先看视频,视频中是一个简单的4轮模型的小型机器人,做了一个简单的应用(智能行李车)

    自动跟随机器人视频1

    自动跟随机器人视频2

    在这我们只介绍自动跟随的方案,牵扯到机器人底盘移动等其他控制部分,在这不做过多赘述了。

    如果读者有机器人相关控制基础或者做过智能小车类的项目,很容易处理好。没有也没关系,

    继续关注我的知乎【张巧龙】或者公众号,后面会开源相关机器人的控制方案。

    / 2 / 硬件方案

    选用单发单收超声波模块进行测距,以此来获取人与车的距离,模块可在某宝上购买,也可自己制作。由于有广告嫌疑,我这里不放购买链接。

    (关注微信公号【大鱼机器人】,后台回复【自动跟随】即可获得电路等资料)

    在机器人的两端各安装一个单收超声波,人手持一个单发的超声波模块。

    这样左侧与右侧离人的距离就构成了一个三角形。简单示意图如下图所示:

    当机器人正对着人时,装在机器人上的2个超声波模块,距离A=B,

    当人左拐时,A必定小于B,

    同理当人右拐时,A大于B。

    当人向前走时,A和B的距离必定大于设定距离。我们只需要控制好这几个距离即可。

    由此可获得人与机器人的距离以及人的行走路线,来实现机器人的自动跟随功能。

     

    【注意】如果选用下面这收发集成一体的超声波
    例如这种:



    这种超声波模块也能做跟随,但是非常有限制性,为什么呢?
    这种超声波模块,收发一体,只能测试障碍物的距离。
    也就是说只能测出机器人面前的物体与机器人的距离。
    所以只能做直线跟随!并不能转弯跟随!
    有人会说,和上面一样,一边装一个不就行了吗。
    这样必失败,因为2个超声波都会发出超声波,会互相干扰,距离一个都测不出。

     

    / 3 / 程序设计

    选当获取到A、B距离后,如何控制呢?对距离做一个简易的PID中的P控制即可。

    首先是直线跟随的部分代码:

     

    其次是转弯跟随的部分代码:


    连夜整理出源码以及相关论文,这个小机器人是我之前本科的毕业设计,从思路到设计再到实体最后调试成功,前前后后花了一个多月的时间,有人可能会觉得一个本科的毕业设计也敢拿出来给大家分享嘛,这不吹牛B吗?天都黑了~(把牛都吹上天了~)

    我想说的是,做这个跟随的小机器人,不在于技术含量多高,而是这个点子很有趣,市面上如今有各式各样的跟随方案,计算机视觉跟随、定位模块跟随、电磁波信号跟随等等。

    那么我这个跟随有什么用呢?

    一种廉价的跟随方案,让大家都能够参与进来,技术难度不大,一些人也能够DIY一些属于自己的“跟随”机器人!做不了工业应用,我们可以做玩具嘛~

    附上原贴,相关资料在链接里面

    纯干货 | 自动跟随机器人方案

    展开全文
  • 基于超声波的自动跟随小车

    千次阅读 多人点赞 2019-07-28 21:18:37
    基于超声波的自动跟随小车超声波跟随原理代码1.电机部分2.PWM定义部分3.串口部分4.控制部分主函数 超声波跟随原理 下面介绍的超声波跟随小车,是可以进行直线跟随,可以转弯,可以避障等等的一款小车,接下来主要...

    超声波跟随原理

    下面介绍的超声波跟随小车,是可以进行直线跟随,可以转弯,可以避障等等的一款小车,接下来主要介绍小车的自动跟随部分。

    基本原理:在小车的两端各安装一个超声波(非收发一体),人手持一个超声波模块。这样左侧与右侧离人的距离就构成了一个三角形。

    距离A
    距离B
    左超声波
    右超声波

    当小车正对着人时,装在小车上的2个超声波模块,距离A=B。

    当人左拐时,A必定小于B,同理当人右拐时,A大于B。

    当人向前走时,A和B的距离必定大于设定距离。我们只需要控制好这几个距离即可。

    代码

    话不多说,直接上代码。
    接下来以STM32为例,下面是小车电机部分的代码:
    代码片

    1.电机部分

    #include "motor.h"
    
    void Motor_Init(void)    //电机引脚的初始化
    {
      GPIO_InitTypeDef GPIO_InitStructure;
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);   //使能GPIOB端口时钟
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;    //端口配置
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      //推挽输出
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //50MHZ
      GPIO_Init(GPIOB, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB 
    }
    /**************************************************************************
    函数功能:赋值给PWM寄存器
    入口参数:左轮PWM、右轮PWM
    返回  值:无
    **************************************************************************/
    void Set_Pwm(int moto1,int moto2)
    {
        if(moto1<0)
    	{				
    		AIN2=0;
    		AIN1=1;
    	}
    	else
    	{				
    		AIN2=1;
    		AIN1=0;
    	}
    			
    	PWMA=myabs(moto1);
    			
    	if(moto2<0)	
    	{
    		BIN1=1;
    		BIN2=0;
    	}
    	else
    	{
            BIN1=0;
    		BIN2=1;
    	}
    	PWMB=myabs(moto2);	
    }
    
    /**************************************************************************
    函数功能:绝对值函数
    入口参数:int
    返回  值:unsigned int
    目    的:经过直立环和速度环以及转向环计算出来的PWM有可能为负值
    					而只能赋给定时器PWM寄存器只能是正值。故需要对PWM进行绝对值处理
    **************************************************************************/
    int myabs(int a)
    { 		   
    	  int temp;
    	  if(a<0)  
    	  	temp =- a;  
    	  else 
    	  	temp = a;
    	  return temp;
    }
    

    2.PWM定义部分

    #include "pwm.h"
    
    //PWM输出初始化
    //arr:自动重装值
    //psc:时钟预分频数
    //TIM1_PWM_Init(7199,0);//PWM频率=72000/(7199+1)=10Khz
    
    
    void TIM1_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_8|GPIO_Pin_11; //TIM_CH1 //TIM_CH4
    	GPIO_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_tim
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    
     
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高
    	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
    	TIM_OC4Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
    
      TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能	
    
    	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
    	TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH4预装载使能	 
    	
    	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
    	
    	TIM_Cmd(TIM1, ENABLE);  //使能TIM1
    }
    

    3.串口部分

    void uart2_init(u32 bound)
    {  	 
    	  //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	 
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);	//使能UGPIOA时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);	//使能USART2时钟
    	//USART2_TX  
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2	
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
      GPIO_Init(GPIOA, &GPIO_InitStructure);
       
      //USART2_RX	  
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure);
    
       //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(USART2, &USART_InitStructure);     //初始化串口2
      USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
      USART_Cmd(USART2, ENABLE);                    //使能串口2 
    }
    
    /**************************************************************************
    函数功能:串口3初始化
    入口参数: bound:波特率
    返回  值:无
    **************************************************************************/
    void uart3_init(u32 bound)
    {  	 
    	  //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	 
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//使能GPIOB时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);	//使能USART3时钟
    	//USART3_TX  
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
      GPIO_Init(GPIOB, &GPIO_InitStructure);
       
      //USART3_RX	  
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    
       //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(USART3, &USART_InitStructure);     //初始化串口3
      USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启串口接受中断
      USART_Cmd(USART3, ENABLE);                    //使能串口3 
    }
    
    /**************************************************************************
    函数功能:串口2接收中断
    入口参数:无
    返回  值:无
    **************************************************************************/
    void USART2_IRQHandler(void)
    {	
    	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
    	{	 
            dat_left[num_left]=USART_ReceiveData(USART2); 
    		if(dat_left[0] != 0xA5) 
    			num_left = 0;	
            else 
            	num_left++;//判断超声波串口接收的数据,如果是0XA5就接收,否则不予接收
            if(num_left==3)	//当接收到3个字节数之后,开始对数据进行解算并处理。得出最终的距离。
            {
                num_left = 0;
                distance_left = dat_left[1]<<8 | dat_left[2];
            }
    	}  											 
    } 
    /**************************************************************************
    函数功能:串口3接收中断
    入口参数:无
    返回  值:无
    **************************************************************************/
    void USART3_IRQHandler(void)
    {	
    	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收到数据
    	{	  
            dat_right[num_right]=USART_ReceiveData(USART3); 
    		if(dat_right[0] != 0xA5) 
    			num_right = 0;	
            else 
            	num_right++;//判断超声波串口接收的数据,如果是0XA5就接收,否则不予接收
            if(num_right==3)	//当接收到3个字节数之后,开始对数据进行解算并处理。得出最终的距离。
            {	     
                num_right = 0;
                distance_right = dat_right[1]<<8 | dat_right[2];
            }
    	}  											 
    } 
    
    

    4.控制部分

    控制部分主要包括定时器中断、直线跟随、转向PD控制,很简单接下来的部分就自己写啦。

    #include "control.h"	
    .......
    

    主函数

    
    #include "sys.h"
    int main(void)	
    { 
    	delay_init();	    	       //=====延时函数初始化	
    	uart1_init(128000);	           //=====串口1初始化
    	uart3_init(115200);            //=====通信超声波串口
    	uart2_init(115200);            //=====通信超声波串口
    	NVIC_Configuration();		   //=====中断优先级分组,其中包含了所有的中断优先级的配置,方便管理和一次性修改。
    	TIM3_Int_Init(99,7199); 	   //=====控制周期10ms			 
    	TIM1_PWM_Init(7199,0);		   //=====10Khz PWM
    	Motor_Init();				   //=====初始化与电机连接的硬件IO接口 
      while(1)	
    	{
    	} 	
    }
    
    

    具体的内容也就这些了,如果还有什么技术问题想咨询可以留言,鉴于这是我的第一篇博客,因此有问必回,欢迎留言交流!想要快速回复的话也可以加我的个人微信咨询:supreme__fan
    在这里插入图片描述

    展开全文
  • 自动跟随小车系统由两部分组成:跟随小车和移动目标携带装置。 工作原理: 跟随小车系统通过无线通信模块发送寻找信号,同时超声波接收器开始计时,如果移动目标接收到无线寻找信号,则立即发送超声波信号。这样小车的...
  • 基于单片机的自动跟随小车.pdf
  • 超声波自动跟随小车基于arduino的源码,与三个超声波接收装置和一个发射装置组成定位功能。
  • 基于单片机自动跟随小车的设计与制作.pdf
  • STM32三路超声波自动跟随小车

    千次阅读 2020-01-01 12:48:28
    三路超声波自动跟随小车 我一直都觉得自动跟随小车很酷,所以我的单片机课设就决定做这个了。当时还没学完STM32的时候觉得好难实现,接近期末的时候终于搞出来了,虽然不是很理想,但还是不错滴,看一下效果。 ...

    三路超声波自动跟随小车

    我一直都觉得自动跟随小车很酷,所以我的单片机课设就决定做这个了。其实现在网上有这个的代码不多,反正我怎么找也找不到,当时还没学完STM32的时候觉得好难实现,接近期末的时候终于搞出来了,虽然不是很理想,但还是不错滴,看一下效果。
    在这里插入图片描述
    这个是只有超声波传感器的,看上去有点简约,可以实现基本的自动跟随功能了

    在这里插入图片描述
    这个是接了火焰传感器、烟雾传感器、蜂鸣器、红外避障模块等等,看上去酷多了。

    小车的系统结构图

    在这里插入图片描述

    代码部分

    超声波初始化函数:

    可能因为板子的端口有冲突,没有把三个超声波的引脚定义到同一组GPIO口上,但是这并不影响。

    void CH_SR04_Init(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructer;
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
    
    	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    
    	/*TRIG触发信号*/
    	GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
    	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_8 |GPIO_Pin_6;
    	GPIO_Init(GPIOB, &GPIO_InitStructer);
    
    	/*ECOH回响信号*/
    	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_9 |GPIO_Pin_7;
    	GPIO_Init(GPIOB, &GPIO_InitStructer);
    	
    	
    
    	/*定时器TIM2初始化*/
    	TIM_DeInit(TIM4);
    	TIM_TimeBaseInitStructer.TIM_Period=999;//定时周期为1000
    	TIM_TimeBaseInitStructer.TIM_Prescaler=71; //分频系数72
    	TIM_TimeBaseInitStructer.TIM_ClockDivision=TIM_CKD_DIV1;//不分频
    	TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
    	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructer);
    
    	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//开启更新中断
    	NVIC_Config();
    	TIM_Cmd(TIM4,DISABLE);//关闭定时器使能
    
    }
    
    

    我把右边的超声波分开定义了,你也可以自己修改。

    void right(void){    //右边超声波的引脚声明
    	GPIO_InitTypeDef GPIO_InitStructer;
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    	/*TRIG触发信号*/
    	GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
    	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_3;
    	GPIO_Init(GPIOA, &GPIO_InitStructer);
    
    	/*ECOH回响信号*/
    	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_4;
    	GPIO_Init(GPIOA, &GPIO_InitStructer);	
    
    }
    

    下面这部分就是超声波工作的过程了,也是核心代码。注释也解释得很清楚了,需要注意的是这里的超声波是测量了五次才返回一个值的,这样做的好处是起到一个滤波的作用,使得超声波的值更精确、稳定。但是他也有不好的地方,在小车前进过程可能会识别很慢或者转向失败,因为三个超声波是使用同一个定时器的,分时复用的,也就是同一时刻只能有一个超声波工作,所以如果处理器不够快的话可以把次数降低一点,我就是值检测一次的,效果也还行。左右超声波的函数也是一样的,把函数名改一下就好了。

    //中间超声波的工作过程
    float Senor_Using(void)
    {
    	float length=0,sum=0;
    	u16 tim;
    	uint i=0;
    	/*测5次数据计算一次平均值*/
    	while(i!=5)
    	{
    		PBout(8)=1; //拉高信号,作为触发信号
    		delay_us(25); //高电平信号超过10us
    		PBout(8)=0;
    		/*等待回响信号*/
    		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==RESET);
    		TIM_Cmd(TIM4,ENABLE);//回响信号到来,开启定时器计数
    
    		i+=5; //每收到一次回响信号+1,收到5次就计算均值
    		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET);//回响信号消失
    		TIM_Cmd(TIM4,DISABLE);//关闭定时器
    
    		tim=TIM_GetCounter(TIM4);//获取计TIM4数寄存器中的计数值,一边计算回响信号时间
    
    		length=(tim+overcount*1000)/58.0;//通过回响信号计算距离
    
    		sum=length+sum;
    		TIM4->CNT=0; //将TIM4计数寄存器的计数值清零
    		overcount=0; //中断溢出次数清零
    		delay_ms(10);
    	}
    	length=sum/5;
    	return length;//距离作为函数返回值
    }
    

    这个是定时器的中断服务函数,避免因为距离过长,定时器中的值溢出。

    void TIM4_IRQHandler(void) //中断,当回响信号很长是,计数值溢出后重复计数,用中断来保存溢出次数
    {
    	if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
    	 {
    		TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除中断标志
    		overcount++;
    	 }
    }
    
    

    PWM电机驱动代码:

    #include "moter.h"
    void CarGo(void)
    {
    	TIM_SetCompare1(TIM3 , 300);  //数值越大速度越慢
    	TIM_SetCompare2(TIM3 , 900);
    	TIM_SetCompare3(TIM3 , 300);  
    	TIM_SetCompare4(TIM3 , 900);	
    }
    
    void CarStop(void)
    {
        TIM_SetCompare1(TIM3 , 900);
        TIM_SetCompare2(TIM3 , 900);
    	TIM_SetCompare3(TIM3 , 900);	
    	TIM_SetCompare4(TIM3 , 900);
    }
    
    void CarBack(void)
    {
        TIM_SetCompare1(TIM3 , 900);
        TIM_SetCompare2(TIM3 , 400);
    	TIM_SetCompare3(TIM3 , 900);	
    	TIM_SetCompare4(TIM3 , 400);
    }
    
    void CarLeft(void)
    {
        TIM_SetCompare1(TIM3 , 900);
    	TIM_SetCompare2(TIM3 , 200);
    	TIM_SetCompare3(TIM3 , 200);
    	TIM_SetCompare4(TIM3 , 900);
    }
    
    void CarBigLeft(void)
    {
        TIM_SetCompare1(TIM3 , 900);
    	TIM_SetCompare2(TIM3 , 100);
    	TIM_SetCompare3(TIM3 , 100);
    	TIM_SetCompare4(TIM3 , 900);
    }
    
    void CarRight(void)
    {
        TIM_SetCompare1(TIM3 , 200);
    	TIM_SetCompare2(TIM3 , 900);
    	TIM_SetCompare3(TIM3 , 900);
    	TIM_SetCompare4(TIM3 , 200);
    	
    }
    
    void CarBigRight(void)
    {
        TIM_SetCompare1(TIM3 , 100);
    	TIM_SetCompare2(TIM3 , 900);
    	TIM_SetCompare3(TIM3 , 900);
    	TIM_SetCompare4(TIM3 , 100);
    	
    }
    
    
    void TIM3_PWM_Init(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    	TIM_OCInitTypeDef TIM_OCInitStructure; 
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    	
    	
    	TIM_TimeBaseStructure.TIM_Period = 899;
    	TIM_TimeBaseStructure.TIM_Prescaler = 0;
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    	TIM_TimeBaseInit(TIM3 , &TIM_TimeBaseStructure);
    	
    	//端口复用
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    	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_0 | GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 	
    	GPIO_Init(GPIOB, &GPIO_InitStructure);   
    	
    	//PWM通道1
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OCInitStructure.TIM_Pulse = 900;
    	TIM_OC1Init(TIM3 , &TIM_OCInitStructure);
    	TIM_OC1PreloadConfig(TIM3 , TIM_OCPreload_Enable);
    	
    	//PWM通道2
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OCInitStructure.TIM_Pulse = 900;
    	TIM_OC2Init(TIM3 , &TIM_OCInitStructure);
    	TIM_OC2PreloadConfig(TIM3 , TIM_OCPreload_Enable);
    	
    	//PWM通道3
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OCInitStructure.TIM_Pulse = 900;
    	TIM_OC3Init(TIM3 , &TIM_OCInitStructure);
    	TIM_OC3PreloadConfig(TIM3 , TIM_OCPreload_Enable);
    	
    	//PWM通道4
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OCInitStructure.TIM_Pulse = 900;
    	TIM_OC4Init(TIM3 , &TIM_OCInitStructure);
    	TIM_OC4PreloadConfig(TIM3 , TIM_OCPreload_Enable);
    	
    	TIM_Cmd(TIM3 , ENABLE);
    }
    
    

    主函数

    while(1)
    	{	
    		length_res[0]=Senor_Using();
    		delay_ms(100);
    		if(length_res[0]>100.00)
    		{
    			CarGo();
    		}
    		if(length_res[0]<=40.00)
    		{
    			CarBack();//先后退一段距离
    			delay_ms(200);
    			CarBack();//先后退一段距离
    			delay_ms(200);		
    
    			length_res[0] =Senor_Using();//再次测量前方距离
    			delay_ms(50);
    			length_res[1]=Senor_Using_left();
    			delay_ms(10);
    			if(length_res[1]<=110.00&&length_res[0]>150.00)
    			{			
    					CarLeft();
    					delay_ms(10);
    								
    			}
    		
    			length_res[2]=Senor_right();
    			if(length_res[2]<=110.00&&length_res[0]>150.00)
    			{
    				CarRight();
    				delay_ms(10);
    							
    			}			
    		}
    			
    		length_res[1]=Senor_Using_left();
    		delay_ms(10);
    		if(length_res[1]<=110.00&&length_res[0]>150.00)
    		{			
    				CarLeft();
    				delay_ms(50);
    				CarLeft();
    				delay_ms(50);
    				if(length_res[1]<=30){
    					CarRight();
    					delay_ms(50);
    				}
    		}
    		
    		
    		length_res[2]=Senor_right();
    		if(length_res[2]<=110.00&&length_res[0]>150.00)
    		{
    			CarRight();
    			delay_ms(50);
    			CarRight();
    			delay_ms(50);
    				
    			if(length_res[2]<=30){
    					CarLeft();
    					delay_ms(50);
    				}
    		}	
    	}	
    

    完成的工程代码已上传,有积分的土豪可以直接下载,链接:工程代码点这里

    展开全文
  • 概述:声源自动跟随小车,方案主要是通过采集实时环境声音,计算声音的到达方向,同步小车的角度和声源方向角度并行进,达到声源跟随的目的。主要可以应用在电子宠物上,与人进行互动,也可以判断声源方向用来指示...
  • 工程能直接使用,引脚的接线看.h文件就行了,自动跟随的主函数可以自己编写更好的,全靠自己的算法了!!
  • 超详细,注释全面且清晰
  • 我们最终是通过摄像头来跑深度学习从而识别人体,根据识别到的方框来判断人具体的位置,从而控制小车。 2.关于小车 这里使用的小车是全向三轮车,可以使用串口来进行控制,通过不同的串口指令可以实现前进、后退以及...

    1.树莓派环境搭建

    这里就不详细介绍了,使用的buster系统,python版本为默认的3.7.

    安装好 numpyopencv。如果需要使用 Intel神经棒进行加速,则需要安装 mvnc,可以自行寻找教程。我们最终是通过摄像头来跑深度学习从而识别人体,根据识别到的方框来判断人具体的位置,从而控制小车。

    2.关于小车

    这里使用的小车是全向三轮车,可以使用串口来进行控制,通过不同的串口指令可以实现前进、后退以及顺时针或逆时针旋转等操作。我们需要使树莓派作为控制器向小车发送串口指令。在 python中,只需导入 serial包,就可以通过串口发送数据了。

    3.小车控制部分代码

    def move_forward():
        forwardInput = 'ff fe 01 00 00 00 12 00 00 00'
        ser.write(bytes.fromhex(forwardInput))
        
    def move_backward():
        backwardInput = 'ff fe 01 00 00 00 12 00 00 02'
        ser.write(bytes.fromhex(backwardInput))
    
    def stop_car():
        stopInput = 'ff fe 01 00 00 00 00 00 00 00'
        ser.write(bytes.fromhex(stopInput))
    
    def move_left():
        leftInput = 'ff fe 01 00 12 00 00 00 00 04'
        ser.write(bytes.fromhex(leftInput))
    
    def move_right():
        rightInput = 'ff fe 01 00 12 00 00 00 00 00'
        ser.write(bytes.fromhex(rightInput))
        
    def move_zpositive():
        zpositiveInput = 'ff fe 01 00 00 00 00 00 06 00'
        ser.write(bytes.fromhex(zpositiveInput))
        
    def move_znegetive():
        znegetiveInput = 'ff fe 01 00 00 00 00 00 06 01'
        ser.write(bytes.fromhex(znegetiveInput))
    

    具体的串口命令可以根据你所使用的小车来改变。这里要注意的是,串口命令一般是16进制发送的,而 python中默认是字符串格式,所以需要格式转换。

    4.总体代码

    # USAGE
    # python ncs_realtime_objectdetection.py --graph graphs/mobilenetgraph --display 1
    # python ncs_realtime_objectdetection.py --graph graphs/mobilenetgraph --confidence 0.5 --display 1
    
    # import the necessary packages
    from mvnc import mvncapi as mvnc
    from imutils.video import VideoStream
    from imutils.video import FPS
    import argparse
    import numpy as np
    import time
    import cv2
    import serial
    
    
    # initialize the list of class labels our network was trained to
    # detect, then generate a set of bounding box colors for each class
    CLASSES = ("background", "aeroplane", "bicycle", "bird",
        "boat", "bottle", "bus", "car", "cat", "chair", "cow",
        "diningtable", "dog", "horse", "motorbike", "person",
        "pottedplant", "sheep", "sofa", "train", "tvmonitor")
    COLORS = np.random.uniform(0, 255, size=(len(CLASSES), 3))
    
    # frame dimensions should be sqaure
    PREPROCESS_DIMS = (300, 300)
    DISPLAY_DIMS = (900, 900)
    
    # calculate the multiplier needed to scale the bounding boxes
    DISP_MULTIPLIER = DISPLAY_DIMS[0] // PREPROCESS_DIMS[0]
    
    ser = serial.Serial("/dev/ttyUSB0", 115200, timeout = 0.5)
    
    def move_forward():
        forwardInput = 'ff fe 01 00 00 00 12 00 00 00'
        ser.write(bytes.fromhex(forwardInput))
        
    def move_backward():
        backwardInput = 'ff fe 01 00 00 00 12 00 00 02'
        ser.write(bytes.fromhex(backwardInput))
    
    def stop_car():
        stopInput = 'ff fe 01 00 00 00 00 00 00 00'
        ser.write(bytes.fromhex(stopInput))
    
    def move_left():
        leftInput = 'ff fe 01 00 12 00 00 00 00 04'
        ser.write(bytes.fromhex(leftInput))
    
    def move_right():
        rightInput = 'ff fe 01 00 12 00 00 00 00 00'
        ser.write(bytes.fromhex(rightInput))
        
    def move_zpositive():
        zpositiveInput = 'ff fe 01 00 00 00 00 00 06 00'
        ser.write(bytes.fromhex(zpositiveInput))
        
    def move_znegetive():
        znegetiveInput = 'ff fe 01 00 00 00 00 00 06 01'
        ser.write(bytes.fromhex(znegetiveInput))
    
    def preprocess_image(input_image):
        # preprocess the image
        preprocessed = cv2.resize(input_image, PREPROCESS_DIMS)
        preprocessed = preprocessed - 127.5
        preprocessed = preprocessed * 0.007843
        preprocessed = preprocessed.astype(np.float16)
    
        # return the image to the calling function
        return preprocessed
    
    def predict(image, graph):
        # preprocess the image
        image = preprocess_image(image)
    
        # send the image to the NCS and run a forward pass to grab the
        # network predictions
        graph.LoadTensor(image, None)
        (output, _) = graph.GetResult()
    
        # grab the number of valid object predictions from the output,
        # then initialize the list of predictions
        num_valid_boxes = output[0]
        predictions = []
    
        # loop over results
        for box_index in range(int(num_valid_boxes)):
            # calculate the base index into our array so we can extract
            # bounding box information
            base_index = 7 + box_index * 7
    
            # boxes with non-finite (inf, nan, etc) numbers must be ignored
            if (not np.isfinite(output[base_index]) or
                not np.isfinite(output[base_index + 1]) or
                not np.isfinite(output[base_index + 2]) or
                not np.isfinite(output[base_index + 3]) or
                not np.isfinite(output[base_index + 4]) or
                not np.isfinite(output[base_index + 5]) or
                not np.isfinite(output[base_index + 6])):
                continue
    
            # extract the image width and height and clip the boxes to the
            # image size in case network returns boxes outside of the image
            # boundaries
            (h, w) = image.shape[:2]
            x1 = max(0, int(output[base_index + 3] * w))
            y1 = max(0, int(output[base_index + 4] * h))
            x2 = min(w, int(output[base_index + 5] * w))
            y2 = min(h, int(output[base_index + 6] * h))
    
            # grab the prediction class label, confidence (i.e., probability),
            # and bounding box (x, y)-coordinates
            pred_class = int(output[base_index + 1])
            pred_conf = output[base_index + 2]
            pred_boxpts = ((x1, y1), (x2, y2))
    
            # create prediciton tuple and append the prediction to the
            # predictions list
            prediction = (pred_class, pred_conf, pred_boxpts)
            predictions.append(prediction)
    
        # return the list of predictions to the calling function
        return predictions
    
    # construct the argument parser and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-g", "--graph", required=True,
        help="path to input graph file")
    ap.add_argument("-c", "--confidence", default=.5,
        help="confidence threshold")
    ap.add_argument("-d", "--display", type=int, default=0,
        help="switch to display image on screen")
    args = vars(ap.parse_args())
    
    # grab a list of all NCS devices plugged in to USB
    print("[INFO] finding NCS devices...")
    devices = mvnc.EnumerateDevices()
    
    # if no devices found, exit the script
    if len(devices) == 0:
        print("[INFO] No devices found. Please plug in a NCS")
        quit()
    
    # use the first device since this is a simple test script
    # (you'll want to modify this is using multiple NCS devices)
    print("[INFO] found {} devices. device0 will be used. "
        "opening device0...".format(len(devices)))
    device = mvnc.Device(devices[0])
    device.OpenDevice()
    
    # open the CNN graph file
    print("[INFO] loading the graph file into RPi memory...")
    with open(args["graph"], mode="rb") as f:
        graph_in_memory = f.read()
    
    # load the graph into the NCS
    print("[INFO] allocating the graph on the NCS...")
    graph = device.AllocateGraph(graph_in_memory)
    
    # open a pointer to the video stream thread and allow the buffer to
    # start to fill, then start the FPS counter
    print("[INFO] starting the video stream and FPS counter...")
    vs = VideoStream(usePiCamera=False).start()
    time.sleep(1)
    fps = FPS().start()
    
    # loop over frames from the video file stream
    while True:
        try:
            # grab the frame from the threaded video stream
            # make a copy of the frame and resize it for display/video purposes
            frame = vs.read()
            image_for_result = frame.copy()
            image_for_result = cv2.resize(image_for_result, DISPLAY_DIMS)
    
            # use the NCS to acquire predictions
            predictions = predict(frame, graph)
    
            # loop over our predictions
            for (i, pred) in enumerate(predictions):
                # extract prediction data for readability
                (pred_class, pred_conf, pred_boxpts) = pred
    
                # filter out weak detections by ensuring the `confidence`
                # is greater than the minimum confidence
                #if pred_conf > args["confidence"]:
                if CLASSES[pred_class] == 'person':
                    area = (pred_boxpts[1][0] - pred_boxpts[0][0]) * (pred_boxpts[1][1] - pred_boxpts[0][1])
                    if ((pred_boxpts[0][0] + pred_boxpts[1][0]) // 2) - 150 > 20:
                        move_zpositive()
                    elif ((pred_boxpts[0][0] + pred_boxpts[1][0]) // 2) - 150 < -20:
                        move_znegetive()
                    elif area < 150 * 150:
                        move_forward()
                    elif area > 250 * 250:
                        move_backward()
                    else:
                        stop_car()
                    
                    #print(area)
                    #print(((pred_boxpts[0][0] + pred_boxpts[1][0]) // 2) - 150)
                    # print prediction to terminal
                    #print("[INFO] Prediction #{}: class={}, confidence={}, "
                        #"boxpoints={}".format(i, CLASSES[pred_class], pred_conf,
                        #pred_boxpts))
    
                    # check if we should show the prediction data
                    # on the frame
                    if args["display"] > 0:
                        # build a label consisting of the predicted class and
                        # associated probability
                        label = "{}: {:.2f}%".format(CLASSES[pred_class],
                            pred_conf * 100)
    
                        # extract information from the prediction boxpoints
                        (ptA, ptB) = (pred_boxpts[0], pred_boxpts[1])
                        ptA = (ptA[0] * DISP_MULTIPLIER, ptA[1] * DISP_MULTIPLIER)
                        ptB = (ptB[0] * DISP_MULTIPLIER, ptB[1] * DISP_MULTIPLIER)
                        (startX, startY) = (ptA[0], ptA[1])
                        y = startY - 15 if startY - 15 > 15 else startY + 15
    
                        # display the rectangle and label text
                        cv2.rectangle(image_for_result, ptA, ptB,
                            COLORS[pred_class], 2)
                        cv2.putText(image_for_result, label, (startX, y),
                            cv2.FONT_HERSHEY_SIMPLEX, 1, COLORS[pred_class], 3)
                
                
            # check if we should display the frame on the screen
            # with prediction data (you can achieve faster FPS if you
            # do not output to the screen)
            if args["display"] > 0:
                # display the frame to the screen
                cv2.imshow("Output", image_for_result)
                key = cv2.waitKey(1) & 0xFF
    
                # if the `q` key was pressed, break from the loop
                if key == ord("q"):
                    break
    
            # update the FPS counter
            fps.update()
        
        # if "ctrl+c" is pressed in the terminal, break from the loop
        except KeyboardInterrupt:
            break
    
        # if there's a problem reading a frame, break gracefully
        except AttributeError:
            break
    
    # stop the FPS counter timer
    fps.stop()
    
    # destroy all windows if we are displaying them
    if args["display"] > 0:
        cv2.destroyAllWindows()
    
    # stop the video stream
    vs.stop()
    
    # clean up the graph and device
    graph.DeallocateGraph()
    device.CloseDevice()
    
    # display FPS information
    print("[INFO] elapsed time: {:.2f}".format(fps.elapsed()))
    print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
    

    深度学习算法模型的导入以及调用就不多说了,要达到跟随行人的目的,重点是第186行开始,如果检测到的是类别是 person,那么根据方框 pred_boxpts的相关参数,可以算出水平方向与整个图像中心点的偏差,从而来控制小车的方向;至于前后方向,我们可以根据方框计算其所占的面积大小,如果人体远离摄像头那么整个面积必然会减小,这时前进即可,反之亦然。

    这个方法虽然比较简单,但是实际的效果还是不错的。至于其他部分,注释比较多,就不再细说了。后续会考虑加入避障等更多功能的实现。

    注:这个模型用到了神经棒一代,不用也完全可以,可以直接跑tf-tensor的例程,这里主要说的是如果根据识别到的人体控制小车。
    最后是完整的工程:
    链接:百度网盘
    提取码:1u4r

    展开全文
  • 避障小车的制作方法大概有两种:一个是利用超声波制作,一个是光电开关(避障模块),而跟随小车便一个是利用超声波和光电开关配合制作,一个是光电开关(避障模块)制作。所以本篇博客,最终决定:用超声波模块制作避障...
  • 基于arduino的跟随小车

    2019-04-11 17:22:17
    避障小车的制作方法大概有两种:一个是利用超声波制作,一个是光电开关(避障模块),而跟随小车便一个是利用超声波和光电开关配合制作,一个是光电开关(避障模块)制作。
  • 基于超声波定位的智能跟随小车方案描述,对跟随功能有需要的可以参考一下。
  • 电子设计校内选拔赛,智能随动小车代码 //按下“运动测试”按键,小车能够...小车自动跟随。 (4)小车具有自动寻人跟随功能。当人体(手掌)不在小车前方,小车能自 动原地旋转,直到检测到人体,然后自动跟随人体。
  • 本设计基于单片机的智能小车测距模块、红外遥控模块和小车智能控制模块,通过软硬件设计调试,实现了小车自动跟随状态。主要利用超声波测距模块实现小车与目标之间距离的实时检测,当两者距离较近小车就“缓慢跟随...
  • 基于arduino的智能跟随小车源程序,注释详细,需要辅助硬件:arduino开发板,超声波传感器,红外传感器3个,热释电传感器,步进电机;软件:arduino1.5.6

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 506
精华内容 202
关键字:

自动跟随小车