毕业设计:基于单片机的超声波智能跟随小车 - 物联网 智能小车 嵌入式单片机 stm32 跟随小车


1 项目简介

自动跟随小车系统由两部分组成:跟随小车和移动目标携带装置。 工作原理:跟随小车系统通过无线通信模块发送寻找信号,同时超声波接收器开始计时,如果移动目标接收到无线寻找信号,则立即发送超声波信号。这样小车的三角超声波接收器陆续收到超声波信号,CPU通过每个超声波模块接收到的时间,计算出移动目标到3个超声波接收点的距离,通过三边定位算法即可确定移动目标的位置。如果计算出来的距离大于设定距离,则控制电机向目标方向移动,如果计算出来的距离小于设定距离,则控制电机停止,从而实现小车的自动跟随功能。

2 课题背景

现代社会逐渐注重智能化发展, 近年来快递、 物流行业发展迅速。 如图 1.7 所示,在快递仓库里, 很多 AGV 车在货物搬运方面占据了很大部分。 通过在地上铺设的电磁轨道, AGV 小车能够获取到与自身相关的工作信息, 听从核心计算机的指令在仓库中快速的移动。 这样可以很好的帮助快递行业运输货物从而提高货物搬运的效率, 为其他工作节约时间。 这类小车的工作原理就和寻迹小车大致相同, 但是目前还是具有造价昂贵的缺点。

在这里插入图片描述

学长开发的自动跟随智能小车, 基于使用现代化的智能设备, 解放人类劳动力的理念, 利用当前普及的通信模块通信技术尝试设计出可以自动跟随指定的被跟随物体自动行走的小车。 如果添加载物功能, 便能极大的方便仓库工作人员对于货品的管理甚至是组建运输小车阵列, 这样就能够替代造价高昂的 AGV 小车。 或是在快递派送的时候为快递员减轻负担。 或是改造成无需人力推动的跟随式购物小车, 方便人们在超市里购物。

自动跟随智能小车具有较为广泛的应用前景及商业市场。

学长设计的自动跟随智能小车使用的各种传感器的成本低廉, 使用便宜且简单的各种传感器就可以实现自动跟随智能小车行驶、 避障、 跟随的各种功能。 小车核心控制芯片为 STM32 芯片, 是现在较为普及的单片机芯片, 可编程程度高, 核心控制技术比较的成熟, 被广泛运用于各种商业产品当中。 小车通过无线通信与被跟随物体进行无线数传, 具有较短的通讯延迟时间和较快的数据传输速率。

3 硬件说明

在这里插入图片描述

3.1 小车硬件设计

自动跟随小车硬件模块包括:

  • 控制器模块
  • 无线收发模块
  • 超声波接收模块
  • 电机及电机驱动模块
  • 报警模块
  • 电源模块

由于跟随小车需要进行实时目标位置定位计算、无线信号收发处理、电机管理、电源管理等任务 ,采用普通单片机其资源及速度难以满足使用要求,需要高性能DSP处理器才能够完成,因此选择STM32F103RCT6作为控制器。

3.2 小车底座

用的亚克力板做底座

在这里插入图片描述
底板两测为 2 个电机的安装留有位置。 底板前部为扇形结构, 留有多个 M3 孔洞便于各种传感器和电子元件的安装。 后部为万向轮的安装留有 4 个 M3 孔洞位置。 相比较于驱动小车行驶的大车轮, 万向轮的体积较小。 为了保持小车底板的水平, 在万向轮和小车底板间使用 4 根长度为 10mm 的 M3 铜柱进行连接。 在调试过程做发现, 万向轮会
导致小车实际的行驶路线与预期的行驶路线产生一定的偏差, 尤其是在转向或者后退的时候, 小车会产生非自主性的转向。 于是使用 2 根 M3 铜柱将万向轮的旋转角度固定,限制万向轮的角度转动使其近似的变为定向轮。

3.3 无线收发器

无线收发是用来实现同步,当小车发射无线信号,同时人手携带装置接收到无线信号时,人手携带装置发射超声波。所以本次设计选用NRF2401做为无线收发模块。

NRF2401各引脚功能为:

  • (1)CSN:芯片的片选线,CSN为低电平工作。
  • (2)SCK:芯片控制的时钟线(SPI时钟)。
  • (3)MISO:芯片控制数据线 。
  • (4)IRQ:中断信号,无线通信过程中MCU主要是通过IRQ与NRF2401通信。
  • (5)CE:芯片的模式控制线。
  • (6)MOSI:芯片控制数据线。

在这里插入图片描述

3.4 超声波模块

超声波接收模块是采用具有单独接收功能的模块,如图所示。其中接收模块核心部分是由专用超声波接收集成电路TL852构成的超声波信号检测电路,这部分主要完成的是回波的检测和放大。

在这里插入图片描述

3.5 直流电机和电源

直流电机的控制很简单,性能出众,直流电源也容易实现。这种直流电机的驱动及控制需要电机驱动模块进行驱动,采用L298N电源模块。

系统电源采用7.4V可充电锂电池。7.4V锂电池组属于多串并锂电池组。

在这里插入图片描述

3.6 目标携带装置

由于跟随小车需要进行实时目标位置定位计算、无线信号收发处理、电机管理、电源管理等任务 ,采用普通单片机其资源及速度难以满足使用要求,需要高性能DSP处理器才能够完成,因此选择STM32F103RCT6作为控制器。

无线收发是用来实现同步,当小车发射无线信号,同时人手携带装置接收到无线信号时,人手携带装置发射超声波。所以本次设计选用NRF2401做为无线收发模块。

超声波发射模块是采用具有单独发射功能的模块,如图所示。其中发射模块中的P1 、R4、R5。因为利用了变压器和发射头的谐振,好处是能得到近似正弦波。但附带的问题是:在驱动信号停止后,由于谐振的原因,发射头还会持续较长时间发射,直至能量在变压器的次级线包直流电阻上消耗完,这样就导致在近距离测量时,回波都到了,余波还未结束,导致测量失败。所以设计了一个余波抑制电路,将变压器初级构成回路,利用初级较小的电阻快速消耗掉次级的能量。为此,要多占一个MCU的I/O口。而且,由于驱动电压的原因,必须使用OC(或者开漏)驱动,否则会无法可靠关断P1,导致正常发射不正常。如果测量的距离较远,或者觉得余波不影响测量,则不必接这个信号。如若使用,一定要注意和发射驱动信号的配合,不要两个同时有效,导致发射效率大减。从原理图上看,如果要提高驱动能量,可以适当提高驱动电压,但要要注意MOS管的耐压只有20V,发射头的最高电压是80V。

在这里插入图片描述

3.7 整体电路图

在这里插入图片描述
在这里插入图片描述

4 跟随系统的软件设计

学长想了三种方案

4.1 方案一:

在小车上搭建与设备通信的芯片和检测小车车身姿态的三轴传感器。 小车通过车体前方的超声波测距模块测量出人与小车间的实时距离。 当人手持设备向前行走时, 小车会与人之间保持相对稳定的实时距离。 当人转向时, 通过获取设备的陀螺仪数据确定人的转向角度。 设备通过通信模块串口通信将从陀螺仪获取的转向角度等等数据发送给小车, 小车根据自身的三轴传感器转动相同的角度, 通过单片机的计算和控制, 从而实现小车对于人手持的设备跟随的目的。

4.2 方案二:

在小车上布置安装一个或者两个通信模块信标和通信模块通信芯片。 人手持设备行走。 设备通过对于通信模块信标和通信模块通信芯片进行信号强度的测量, 计算出设备与通信模块信标和通信模块通信芯片的实时距离。 通过三角定位算法算出小车与人手持的设备的相对位置, 利用三轴传感器判断小车需要的转向角度。 通过单片机的计算和控制, 从而达成小车对于人手持的设备跟随的目的。

4.3 方案三:

在小车上布置安装两个超声波接收装置, 组装一个人手持的超声波发射装置。 小车通过超声波接收装置接收到超声波后计算得出实时的距离。 如果人手持着超声波发射装置转向后, 安装在小车两侧的两个接收装置的到信号会产生不同。 通过对于超声波发射装置和两个超声波接收装置的距离不同, 获取小车应该向哪个方向转向的信息。 通过单片机的计算和控制, 从而实现小车对于人手持的设备跟随的目的。

从理论的角度上来说, 以上 3 个方案都是可以实现的。 但是由于本人能力有限, 只实现了第三个方案的内容。 如图 3.3 所示为小车超声波自动跟随的流程图。 小车通过蓝牙串口接收到自动跟随的指令。 打开超声波发射端电源, 超声波发射端持续发射超声波。小车车前方的 2 个超声波接收端持续接收超声波, 将接收到的超声波数据经过一定的处理后可以转换为距离信息。 通过对左右两端的距离信息进行比较, 可以判断出小车需要进行的动作。 当小车左端接收到的距离数据大于小车右端接收到的距离数据时, 可以判断出超声波的发射位置在小车的偏右侧, 所以小车应该向右转向直到小车两接收端接收到的距离数据相等。 当小车左端接收到的距离数据小于小车右端接收到的距离数据时,可以判断出超声波的发射位置在小车的偏左侧, 所以小车应该向左转向直到小车两接收端接收到的距离数据相等。 当小车两接收端接收到的数据相等时, 通过比较距离数据与预先设定的跟随阈值的大小, 判断小车应该前进还是后退。

最终设计流程如下
在这里插入图片描述

5 相关代码

#include "stm32f4xx.h"
#include "sys.h"
#include <stdio.h>
static EXTI_InitTypeDef EXTI_InitStructure;
static GPIO_InitTypeDef GPIO_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
static USART_InitTypeDef USART_InitStructure;
static uint8_t k;
static uint8_t num1=0;
static uint8_t num2=0;
static uint8_t dat_right[2];
static uint8_t dat_left[2];
static uint16_t distance_right;
static uint16_t distance_left;
#pragma import(__use_no_semihosting_swi)
struct __FILE {
    
     int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
//printf重定向
int fputc(int ch, FILE *f) {
    
    
USART_SendData(USART3,ch);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
USART_ClearFlag(USART3,USART_FLAG_TXE);
return ch;
}
void _sys_exit(int return_code) {
    
    
}
void delay_us(uint32_t n)
{
    
    
SysTick->CTRL = 0; // Disable SysTick, 关闭系统定时器
SysTick->LOAD = (168*n)-1; // 配置计数值(168*n)-1 ~ 0
SysTick->VAL = 0; // Clear current value as well as count flag
SysTick->CTRL = 5; // Enable SysTick timer with processor clock
while ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set
SysTick->CTRL = 0; // Disable SysTick
}
void delay_ms(uint32_t n)
{
    
    
while(n--)
{
    
    
SysTick->CTRL = 0; // Disable SysTick, 关闭系统定时器
SysTick->LOAD = (168000)-1; // 配置计数值(168000)-1 ~ 0
SysTick->VAL = 0; // Clear current value as well as count flag
SysTick->CTRL = 5; // Enable SysTick timer with processor clock
while ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set
}
SysTick->CTRL = 0; // Disable SysTick
}
void car_work(void);//车轮初始化
void car_stop(void); //停车
void car_go(void); //前进
void car_back(void); //后退
void car_left1(void); //左转 1
void car_right1(void); //右转 1
void car_left2(void); //左转 2
void car_right2(void); //右转 2
void car_auto(void); //自动跟随
void car_auto(void)//自动跟随
{
    
    
while(30 <= distance_left && distance_left < 6800 && 30 <= distance_right
&& distance_right < 6800)
{
    
    
if(distance_left >= distance_right+5)
{
    
    
car_right1();
}
if(distance_right >= distance_left+5)
{
    
    
car_left1();
}
else
{
    
    
if(distance_left > 1200 && distance_right > 1200)
{
    
    
car_go();
}
else
{
    
    
car_stop();
}
}
if(k == 0)
break;
}
car_stop();
}
void car_work(void) //PD0、 PD1、 PD2、 PD3 引脚初始化
{
    
    
//端口 D 硬件时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
//配置 PD0、 PD1、 PD2、 PD3 为输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 |
GPIO_Pin_3;//第 0、 1、 2、 3 根引脚
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT;//输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出, 增加输出电
流能力。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//没有使能上下拉电GPIO_Init(GPIOD,&GPIO_InitStructure);
}
void car_stop(void) //停车
{
    
    
GPIO_ResetBits(GPIOD,GPIO_Pin_0); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_1);
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
}
void car_go(void) //前进
{
    
    
GPIO_SetBits(GPIOD,GPIO_Pin_0); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_1);
GPIO_SetBits(GPIOD,GPIO_Pin_2); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
}
void car_back(void) //后退
{
    
    
GPIO_SetBits(GPIOD,GPIO_Pin_1); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_0);
GPIO_SetBits(GPIOD,GPIO_Pin_3); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
}
void car_left1(void) //左转 1
{
    
    
GPIO_SetBits(GPIOD,GPIO_Pin_0); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_1);
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
}
void car_right1(void) //右转 1
{
    
    
GPIO_ResetBits(GPIOD,GPIO_Pin_0); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_1);
GPIO_SetBits(GPIOD,GPIO_Pin_2); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
}
void car_left2(void) //左转 2
{
    
    
GPIO_SetBits(GPIOD,GPIO_Pin_0); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_1);
GPIO_SetBits(GPIOD,GPIO_Pin_3); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
}
void car_right2(void) //右转 2
{
    
    
GPIO_SetBits(GPIOD,GPIO_Pin_1); //控制右边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
GPIO_SetBits(GPIOD,GPIO_Pin_2); //控制左边轮胎
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
}
//外部中断初始化
void exti0_init(void)
{
    
    
//使能(打开)端口 A 的硬件时钟, 就是对端口 A 供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//使能系统配置时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
//配置 PA0 引脚为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//第 0 根引脚
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN;//输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//没有使能上下拉电GPIO_Init(GPIOA,&GPIO_InitStructure);
//将 PA0 和 EXTI0 连接在一起
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
//外部中断的配置
EXTI_InitStructure.EXTI_Line = EXTI_Line0;//外部中断 0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发, 用于
识别电平的变低
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//外部中断0的请求通
道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能该通道
NVIC_Init(&NVIC_InitStructure);
}
//串口 1 初始化
void usart1_init(uint32_t baud)
{
    
    
//打开 PA 硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//打开串口 1 硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//配置 PA9 和 PA10 为复用功能模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;//第 9 10 根引脚
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//多功能模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出, 增加输出电
流能力。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//没有使能上下拉电GPIO_Init(GPIOA,&GPIO_InitStructure);
//将 PA9 和 PA10 引脚连接到串口 1 的硬件
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//配置串口 1 相关参数: 波特率、 无校验位、 8 位数据位、 1 个停止位......
USART_InitStructure.USART_BaudRate = baud;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8 位
数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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);
//配置串口 1 的中断触发方法: 接收一个字节触发中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//配置串口 1 的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

效果演示

在这里插入图片描述

演示视频

毕业设计:智能跟随小车 - 嵌入式 单片机 物联网

6 最后

技术解答、毕设帮助、开题指导
print("Q 746876041") 

请添加图片描述

Guess you like

Origin blog.csdn.net/huawei123444/article/details/121853726