蓝牙超声波循迹小车制作
蓝牙超声波循迹小车制作
材料准备:
-
stm32F103C8t6主控板
-
超声波传感器模块
-
小车底盘
-
直流电机驱动模块tb6612一个
-
直流电机四个(买小车会送)
-
杜邦线
-
电池盒及电池
-
蓝牙HC-04模块
-
面包板洞洞板
-
反射式红外模块
-
热熔胶
步骤:
1.搭建小车底盘和电机:
将四个直流电机安装在小车底盘上,并连接到直流电机驱动模块。
单片机输出一个pwm信号,提供给电机驱动的两个端口,AIN BIN 控制一侧电机的转向;
PWM代码如下:
#include "stm32f10x.h" // Device header
void PWM1_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}
2.电机转向模块 Motor
在Motor_Init函数中,首先使能了GPIOA外设的时钟,并配置了GPIOA的几个引脚为推挽输出模式,最大输出频率为50MHz。然后调用了PWM1_Init函数进行PWM模块的初始化。
接下来的Motor_SetLeftSpeed函数和Motor_SetRightSpeed函数分别用于设置左电机和右电机的速度。这两个函数接受一个表示速度的参数Speed。
如果Speed大于0,表示正向转动,则相关GPIO引脚设置为相应状态,同时通过PWM_SetCompare3函数设置PWM的占空比为Speed。
如果Speed等于0,表示停止转动,那么GPIO引脚都设置为高电平,同时通过PWM_SetCompare3函数设置PWM的占空比为Speed。
如果Speed小于0,表示反向转动,相关GPIO引脚设置为相应状态,然后通过PWM_SetCompare3函数设置PWM的占空比为-Speed。
#include "stm32f10x.h" // Device header
#include "PWM1.h"
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PWM1_Init();
}
void Motor_SetLeftSpeed(int8_t Speed)
{
if (Speed >0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare3(Speed);
}
else if(Speed==0){
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare3(Speed);
}else{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare3(-Speed);
}
}
void Motor_SetRightSpeed(int8_t Speed)
{
if (Speed >0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
PWM_SetCompare3(Speed);
}
else if(Speed==0){
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_SetBits(GPIOA, GPIO_Pin_7);
PWM_SetCompare3(Speed);
}else{
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
GPIO_SetBits(GPIOA, GPIO_Pin_7);
PWM_SetCompare3(-Speed);
}
}
3.小车运行状态模块 Car
Car_Init() 函数用于初始化电机,它调用了 Motor_Init() 函数。
Go_Ahead() 函数用于让车辆前进,通过调用 Motor_SetLeftSpeed() 和 Motor_SetRightSpeed() 函数将左右电机速度都设置为 Speed(75)。
Go_Back() 函数用于让车辆后退,与 Go_Ahead() 函数类似,只不过将左右电机速度设置为 -Speed(负的 Speed)。
Turn_Left() 函数用于让车辆向左转弯。它将左电机速度设置为 0,以停止旋转,然后将右电机速度设置为 Speed,使其正向旋转。
Turn_Right() 函数用于让车辆向右转弯。与 Turn_Left() 函数类似,只不过将右电机速度设置为 0,然后将左电机速度设置为 Speed。
Self_Left() 函数与之前提到的不同,它用于车辆自旋向左转。它将左电机速度设置为 -Speed,使其反向旋转,而右电机速度仍然设置为 Speed,使其正向旋转。
Self_Right() 函数用于车辆自旋向右转,它将左电机速度设置为 Speed,右电机速度设置为 -Speed,与 Self_Left() 函数相反。
Car_Stop() 函数用于使车辆停止,将左右电机速度都设置为 0。
#include "stm32f10x.h" // Device header
#include "Motor.h"
#include "Delay.h"
#define Speed 75
void Car_Init(){
Motor_Init();
}
void Go_Ahead(){
Motor_SetLeftSpeed(Speed);
Motor_SetRightSpeed(Speed);
}
void Go_Back(){
Motor_SetLeftSpeed(-Speed);
Motor_SetRightSpeed(-Speed);
}
void Turn_Left(){
Motor_SetLeftSpeed(0);
Motor_SetRightSpeed(Speed);
}
void Turn_Right(){
Motor_SetRightSpeed(0);
Motor_SetLeftSpeed(Speed);
}
void Self_Left(){
Motor_SetLeftSpeed(-Speed);
Motor_SetRightSpeed(Speed);
}
void Self_Right(){
Motor_SetLeftSpeed(Speed);
Motor_SetRightSpeed(-Speed);
}
void Car_Stop(){
Motor_SetLeftSpeed(0);
Motor_SetRightSpeed(0);
}
3.蓝牙模块就利用串口和单片机通信
本代码由B站UP江科大提供
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_RxFlag;
uint8_t Serial_RxData;
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 选择中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 选择 开启
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1,ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
}
void Serial_SendArry(uint8_t *Arry, uint16_t Lenth)
{
uint16_t i;
for(i = 0; i < Lenth; i++)
{
Serial_SendByte(Arry[i]);
}
}
void Serial_SendString(char *String)
{
uint16_t i;
for(i = 0; String[i] != '\0'; i++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format,...)
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String,format,arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Get_Serial_RxData(void)
{
return Serial_RxData;
}
uint8_t Get_Serial_RxFlag(void)
{
uint8_t temp = Serial_RxFlag;
Serial_RxFlag = 0;
return temp;
}
void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
蓝牙模块的 TX和RX分别对应连接 PA10 和 PA9
手机上通过APP给蓝牙发送指令,控制小车运动状态
main.c中蓝牙控制部分
if(Get_Serial_RxFlag())
{
Data1= Get_Serial_RxData();
Delay_us(10);
Serial_SendByte(Data1);
if(Data1==0x30)Car_Stop();
if(Data1==0x31)Go_Ahead();
if(Data1==0x33)Turn_Left();
if(Data1==0x34)Turn_Right();
if(Data1==0x35)Self_Left();
if(Data1==0x36)Self_Right();
if(Data1==0x37)Servo_SetAngle(0);
}
4.超声波测距模块
用热熔胶把超声波模块粘在车头
首先我们要了解超声波测距模块饿原理
工作原理
Step1:通过IO口给Trig接口周期不小于10us的脉冲信号。 Step2:HC-SR04接收到单片机发来的脉冲信号后自动发送8个频率为40KHz的方波,自动检测是否有信号返回。 Step3:若有信号返回,则通过Echo接口向单片机相连的IO口发送一个高电平,高电平持续时间就是超声波从发射到返回的总时间。 距离计算:假设高电平持续时间为T,声速为V(一般为340m/s),时间T是从发射到返回的时间,超声波测距模块到障碍物时间为T的一半。
距离S=(T*V)/2。
所以我们先给模块一个10us以上的触发信号,再用定时器捕获回响信号的次数
注意超声波模块新版跟旧版有区别,供电3.3v-5v 和 5v
代码部分:
1.定时器部分
#include "stm32f10x.h" // Device header
#include "Timer.h"
uint16_t Time;
void Timer_Init()
{
Time = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //选择APB1总线下的定时器Timer2
TIM_InternalClockConfig(TIM3); //TIM2使用内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式,此处为向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //高级计时器特有,重复计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //使能中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //中断通道选择
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //优先级,同上
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3, ENABLE); //打开定时器
}
void TIM3_IRQHandler() //定时器3的中断函数,不懂直接套用
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) // 0.0001秒就进一次中断 1秒就计数 10000
{
if (GPIO_ReadInputDataBit(Echo_Port, Echo_Pin) == 1) //捕获回响
{
Time ++;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清空标志位
}
}
2.超声波部分
#include "stm32f10x.h" // Device header
#include "HCSR04.h"
#include "Timer.h"
#include "Delay.h"
void HCSR04_Init()
{
RCC_APB2PeriphClockCmd(Trig_RCC, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = Trig_Pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Trig_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; //下拉输出
GPIO_InitStruct.GPIO_Pin = Echo_Pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Echo_Port, &GPIO_InitStruct);
GPIO_ResetBits(Trig_Port, Trig_Pin);
}
void HCSR04_Start()
{
GPIO_SetBits(Trig_Port, Trig_Pin);
Delay_us(45);
GPIO_ResetBits(Trig_Port, Trig_Pin);
Timer_Init();
}
uint16_t HCSR04_GetValue()
{
HCSR04_Start();
Delay_ms(100);
return ((Time * 0.0001) * 34000) / 2;
}
超声波避障模式
当超声波感应到设置的距离后做出决策,这个可以根据自己的想法设计决策。
以下是我的代码部分,蓝牙发送 0x40 开启
if(Data1 == 0x40)
{
// 如果蓝牙模块返回的数据为0x40,则执行以下操作
Go_Ahead(); // 前进
uint32_t T1 = HCSR04_GetValue(); // 获取超声波传感器的数值
Serial_SendNumber(T1,3); // 将获取的数值通过串口发送出去
if(T1<8)
{
// 如果距离小于8,则执行以下操作
Car_Stop(); // 停止车辆
Go_Back(); // 后退
Delay_ms(1000); // 延时1秒
if(Flag == 0)
{
// 如果Flag标志为0,则执行以下操作
Turn_Left(); // 左转
Flag=1;
}
else
{
// 如果Flag标志不为0,则执行以下操作
Turn_Left(); // 左转
Flag=0;
}
Delay_ms(Turning_time); // 延时一段时间
Go_Ahead(); // 前进
Serial_SendNumber(T1,3); // 将获取的数值通过串口发送出去
}
else if(T1<distance)
{
// 如果距离小于设定的distance,则执行以下操作
Car_Stop(); // 停止车辆
Delay_ms(500); // 延时500毫秒
if(Flag == 0)
{
// 如果Flag标志为0,则执行以下操作
Self_Left(); // 车辆自旋左转
Flag=1;
}
else
{
// 如果Flag标志不为0,则执行以下操作
Self_Right(); // 车辆自旋右转
Flag=0;
}
Delay_ms(Turning_time); // 延时一段时间
Go_Ahead(); // 前进
Serial_SendNumber(T1,3); // 将获取的数值通过串口发送出去
}
}
5.红外循迹模块
粘在车头左右分别两个
介绍:红外循迹模块是一种传感器模块,用于检测地面上的黑线或白线。它由红外发射管和红外接收管组成。发射管发出红外光照射在地面上,接收管接收地面上反射回来的红外光。当传感器位于黑线上时,反射回来的红外光较少;而当传感器位于白线上时,反射回来的红外光较多。通过检测反射回来的红外光信号强度,红外循迹模块可以判断当前位置是黑线还是白线。根据检测结果,可以控制机器人或小车的运动方向,实现自动循迹。
当碰到黑线就输出高电平
初始化代码如下:
#include "stm32f10x.h" // Device header
void Infrared_Init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_6| GPIO_Pin_7| GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
红外循迹模式
和超声波测距避障的决策类似
if(Data1 == 0x41)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0)
{
Go_Ahead();
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==1)
{
Car_Stop();
Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==1)
{
Self_Right();
Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0)
{
Turn_Right();Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==1)
{
Turn_Right();Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0)
{
Turn_Left();Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0)
{
Self_Left();Delay_ms(Turning_time2);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0)
{
Turn_Left();Delay_ms(Turning_time2);
}
}
总结:
-
硬件组装和连接:学会了如何正确连接电机、传感器、主控板和其他组件。这需要仔细阅读电路图并确保连接正确无误。
-
传感器的应用和数据处理:了解了如何使用超声波传感器和红外循迹传感器,并获取它们返回的数据。学习了如何根据传感器数据来做出决策,比如避开障碍物或跟踪线路。
-
编程控制:通过编写代码,能够实现小车的自动避障和循迹功能。学到了如何处理传感器数据、控制电机的运动以及与蓝牙模块进行通信。
-
测试和调试:在完成硬件组装和编写代码后,进行了测试和调试,以确保小车的功能正常。我发现了一些问题并进行了修复,提高了小车的性能和稳定性。
-
创新和改进:一旦基本功能实现,还可以进行创新和改进。例如,可以添加其他传感器或功能,探索更多有趣的应用。还可以优化代码和算法,提高小车的性能和响应速度。
代码和软件:
链接:百度网盘 请输入提取码 提取码:6666

浙公网安备 33010602011771号