基于STM32智能小车

一、前置准备

前置知识:需要学习stm32,建议去b站看江科大的视频,讲的很详细,学完串口那一块就可以制作了,软件用的是Keil5,开发语言C语言,手机连接蓝牙模块软件是蓝牙调试器。

需要准备的器件:小车底盘,面包板,stm32f103c8t6,电机驱动模块(tb6612)、蓝牙模块(hc06)、超声波测距模块(HC-SR04)、红外寻迹模块(TCRT5000)、舵机(sg90)、杜邦线若干、电源模块(可以换成充电宝,用5伏那一口)、黑色胶布(用于制作轨迹)、超声波测距模块云台。

二、演示

寻迹演示

基于STM32制作智能小车

自动避障(忘记录了后续补上)

三、功能实现

1、接线图

 2、引脚定义表

3、PWM驱动电机旋转

3.1、让一个轮子转起来

电机旋转就是我们小车轮子旋转了,我们先看看然后让一个轮子转起来。

电机驱动的PWMA接PA1口,AIN2与AIN1接PA5与PA4口,根据定义表中,我们要使用定时器(TIM) 2的通道(Channel) 2,所以初始化代码是。

3.2、代码实现 

void PWM_Init(void)
{
	/*GPIO重映射*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	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;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;
	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;
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}

设置CCR代码

void PWM_SetCompare(uint8_t Num,uint16_t Compare)
{
	if(Num == 2)
	{
		TIM_SetCompare1(TIM2, Compare); // 使用通道2
	}
	else if(Num == 3)
	{
		TIM_SetCompare2(TIM2, Compare); // 使用通道3
	}else if(Num == 4)
	{
		TIM_SetCompare4(TIM2, Compare); // 使用通道4
	}
}

电机驱动代码

思路:使用PA4,与PA5口,对GPIO进行初始化,对于void Motor_SetSpeed(int8_t Speed),当速度大于0正转,小于0反转,对4口5口分别设置高低电平,并且调用PWM_SetCompare3(),修改CCR的值,也就是转速(TIM_OCInitStructure.TIM_Pulse = 0;)。

void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // GPIO_Mode_Out_PP: GPIO引脚在该模式下可以输出高或低电平,而不会受到其他引脚的影响。
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	PWM_Init();
}

void Motor_SetSpeed(int8_t Speed)
{
	if(Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(Speed);
	}else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(-Speed);
	}
}

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"

int main(void)
{
	Motor_Init();		//直流电机初始化
	Motor_SetSpeed(30);	
	while (1)
	{
	}
}

3.2、让四个轮子转起来

这时,电机就转起来了。

提问:这样就能让一个轮子转起来,那我cv 4份,就能都转起来,即简单又方便,我太聪明了,hhh~

答:这样做肯定不对的,如何实现请看思路。

思路:我们可以使用PWMA控制左边两个轮子,PWMB控制右边两个轮子,当然下图知识作为参考,可以自行脑补。

可以仔细看看下面四图。我们让PWMA控制左边的轮子,可以看看第二张图的接线,将左边的电机正负极接到AO1、AO2上面;PWMB控制右边的轮子,将右边的电机正负极接到BO1、BO2上面。

注意:当正负极接反时,电机会反转,当按下前进按钮,三个轮子前进,一个轮子后退,这个肯定不是我们想要的,所以,这里接线的时候注意下!

这时我们的线就接好了,我们来看看代码是如何实现的。

3.4、代码实现

思路:这里使用pa4、pa5、pa6、pa7 做控制四个电机,根据Aspet的值控制左边与右边的轮子

void Motor_SetSpeed(unsigned char Aspet,int8_t Speed)
{
	if(Aspet == 1)
	{
		if(Speed > 0)
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_6);
			GPIO_ResetBits(GPIOA, GPIO_Pin_7);
			PWM_SetCompare(3,Speed);
		}else
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_6);
			GPIO_SetBits(GPIOA, GPIO_Pin_7);
			PWM_SetCompare(3,-Speed);
		}
	}else if(Aspet == 2)
	{
		if(Speed > 0)
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_4);
			GPIO_ResetBits(GPIOA, GPIO_Pin_5);
			PWM_SetCompare(2,Speed);
		}else
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_4);
			GPIO_SetBits(GPIOA, GPIO_Pin_5);
			PWM_SetCompare(2,-Speed);
		}
	}
}

3.5、前进后退等功能

根据Motor_SetSpeed的Speed参数正负值控制前进与后退、Aspet控制左轮与右轮,新建一个车模块,封装前进、后退、左拐、右拐、后左拐、后右拐等功能。

void Car_Init(void){
	Motor_Init();
}
void Go_Ahead(void){
	Motor_SetSpeed(1,50);
	Motor_SetSpeed(2,50);
}
void Go_Back(void){
	Motor_SetSpeed(1,-50);
	Motor_SetSpeed(2,-50);
}
void Turn_Left(void){
	Motor_SetSpeed(1,0);
	Motor_SetSpeed(2,90);
}
void Turn_Right(void){
	Motor_SetSpeed(2,0);
	Motor_SetSpeed(1,90);
	
}
void Self_Left(void){
	Motor_SetSpeed(1,-90);
	Motor_SetSpeed(2,90);
}
void Self_Right(void){
	Motor_SetSpeed(1,90);
	Motor_SetSpeed(2,-90);
}
void Car_Stop(void){
	Motor_SetSpeed(1,0);
	Motor_SetSpeed(2,0);
}

4、蓝牙模块

串口通信里面,tx发送信息,rx接受信息,这里我使用了pa9口作为tx,pa10作为rx,

注:在蓝牙模块的tx与rx中,要记得反插,也就是蓝牙的tx接pa10,蓝牙的rx接pa9。

4.1、代码实现

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

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; // tx
 	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; // rx
 	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);
	
	// 下面是用中断的方法实现串口发送
	// 使能 USART1 的接收中断,以便在接收到新的数据时触发中断处理程序。
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启串口接收数据的中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置NVIC为分组2
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  // 选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  // 指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  // 将结构体变量交给NVIC_Init,配置NVIC外设
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);	
	
}
// 串口发送一个字节
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 串口发送一个数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

// 串口发送一个字符串
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

// 次方函数(内部使用)
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将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');	//依次调用Serial_SendByte发送每位数字
	}
}

// 使用printf需要重定向的底层函数
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

// 自己封装的prinf函数
void Serial_Printf(char *format, ...)
{
	char String[100];				//定义字符数组
	va_list arg;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_SendString(String);		//串口发送字符数组(字符串)
}

// 获取串口接收标志位
uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

// 获取串口接收的数据
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}

4.2、中断函数

// USART1中断函数
void USART1_IRQHandler(void)
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // 判断是否是USART1的接收事件触发的中断
	{
		// USART_ReceiveData: 将接收到的数据从 USART1 的接收数据寄存器中读取
		Serial_RxData1 = USART_ReceiveData(USART1);
		OLED_ShowHexNum(1,1, Serial_RxData1, 10);
		if(Serial_RxData1 == 0x30) Car_Stop();
		if(Serial_RxData1 == 0x31) Go_Ahead();
		if(Serial_RxData1 == 0x32) Go_Back();
		if(Serial_RxData1 == 0x33) Turn_Left();
		if(Serial_RxData1 == 0x34) Turn_Right();
		if(Serial_RxData1 == 0x35) Self_Left();
		if(Serial_RxData1 == 0x36) Self_Right();
		if(Serial_RxData1 == 0x37)
		{
			Car_Stop(); 
			Servo_SetAngle(0);
		}
		if(Serial_RxData1 == 0x38)
		{
			Car_Stop(); 
			Servo_SetAngle(90);
		}
		if(Serial_RxData1 == 0x39)
		{
			Car_Stop(); 
			Servo_SetAngle(180);
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

5、自动避障功能

舵机与超声波模块联合使用,实现检查前面是否有障碍物,如有,左拐或右拐,如果遇到死胡同(前左右都有障碍物)停下来。

4.1、舵机模块

舵机使用的PB0口,根据引脚定义表进行定时器与通道的初始化,前面有讲,这里就不过多进行复述了。

代码实现
void PWMServo_Init(void)
{


	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	TIM_InternalClockConfig(TIM3);

	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;  
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; 
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &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;
	TIM_OC3Init(TIM3, &TIM_OCInitStructure);

	TIM_Cmd(TIM3, ENABLE);
}

void PWMServo_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM3, Compare); 

}

对舵机初始化封装与转弯角度。

void Servo_Init(void)
{
	PWMServo_Init();
}

void Servo_SetAngle(float Angle)
{
	PWMServo_SetCompare3(Angle / 180 * 2000 + 500);
}

主函数调用,就可以使用了。

4.2、超声波模块

使用的是pb12口,原理可以看看这位大佬的。

uint16_t Cnt;
uint16_t OverCnt;
void Ultrasound_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // trig
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // echo
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM4);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 60000 - 1; // arr
 	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; // psc
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
	
}
float Test_Distance()
{
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
	Delay_us(20);
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	
	while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == RESET){};
	TIM_Cmd(TIM4, ENABLE);
 	while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == SET){};
	TIM_Cmd(TIM4, DISABLE);
		
	Cnt = TIM_GetCounter(TIM4);
	float distance = (Cnt * 1.0 / 10 * 0.34) / 2;
	TIM4->CNT = 0;
	Delay_us(100);
	return distance;
		
}

我们来看看自动寻迹的代码,先让他一直前进,当检测的具体小于15cm时,停下来,舵机左转或右转,然后再进行检测,前进或停下来操作。

void Obstacle_Avoidance()
{
			Go_Ahead();
			uint16_t Distance = Test_Distance();
			if(Distance < 15){
				Car_Stop();
				Servo_SetAngle(0);	
				Delay_ms(1000);
				Distance= Test_Distance();
				if(Distance > 15){
					Delay_ms(1000);
					Turn_Right();
					Delay_ms(1300);
					Servo_SetAngle(90);
					Go_Ahead();
				}
				else 
				{
					Servo_SetAngle(180);
					Delay_ms(1000);
					Distance = Test_Distance();
					if(Distance > 15){	
						Delay_ms(1000);
						Turn_Left();
						Delay_ms(1300);
						Servo_SetAngle(90);
						Go_Ahead();
					}else{
						Servo_SetAngle(90);
						while(1){};
					}
				}
			}
			Delay_ms(100);
		
}

这里代码后续进行优化

4.3、主函数调用

int main(void)
{
	Car_Init();
	Serial_Init();
	Servo_Init();
	Servo_SetAngle(90);
	Ultrasound_Init();
	//Go_Ahead();
	while (1)
	{
		Obstacle_Avoidance(); // 自动规避障碍
		//Auto_Tracing(); // 自动寻迹
	}
}

6、自动寻迹功能

寻迹模块使用的红外,这个原理自行搜索,使用的PB5到PB8口,然后使用GPIO_ReadInputDataBit 读取信号,也就是0或1 ,0代表没有检测到黑线,1代表检测到黑线,下面的代码待优化

6.1、代码实现

// 寻迹模块
void Track_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}
// b6,b5在左边   b7 b8在右边
void Auto_Tracing()
{
	// 没有黑线往前走
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0){
			Go_Ahead();
			}
			// 有黑线往后走
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1){
			Car_Stop();
			}
			// 后右拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1){
			Turn_Left();
			}
			// 右拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==1&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0){
				Turn_Left();
			}
			// 右拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==1){
				Turn_Left();
			}
			// 左拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0){
				Turn_Right();
			}
			// 后左拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0){
				Turn_Right();
			}
			// 左拐
	else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==1&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0&&
//			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0&&
			GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)==0){
				Turn_Right();
			}
}

这样,自动寻迹就完成了,主函数调用就可以跑了。

四、总结

代码待优化,后续来总结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/691863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

const详解

关键字const用来定义常量&#xff0c;如果一个变量被const修饰&#xff0c;那么它的值就不能再被改变。 但是&#xff0c;可以通过取地址进行修改。 将const 在指针前进行修饰&#xff0c;那么就修饰指针所指向的变量。 但是指针变量可以被修改。 将const 在指针后进行修饰&am…

轻松连接远程服务器SecureCRT for Mac/Windows

SecureCRT是一款功能强大的终端仿真器和文件传输工具&#xff0c;专为网络管理员、开发人员和系统工程师设计。它支持SSH、Telnet、RDP和串口等多种协议&#xff0c;提供安全、高效的远程访问和管理体验。SecureCRT具有多窗口/多标签管理、自定义终端仿真、颜色方案优化等高级功…

30-unittest生成测试报告(HTMLTestRunner插件)

批量执行完测试用例后&#xff0c;为了更好的展示测试报告&#xff0c;最好是生成HTML格式的。本文使用第三方HTMLTestRunner插件生成测试报告。 一、导入HTMLTestRunner模块 这个模块下载不能通过pip安装&#xff0c;只能下载后手动导入&#xff0c;下载地址是&#xff1a;ht…

DOM型xss靶场实验

DOM型xss可以使用js去控制标签中的内容。 我使用的是一个在线的dom型xss平台&#xff0c;靶场链接&#xff1a;Challenges 第一关Ma Spaghet!&#xff1a; Ma Spaghet! 关卡 <h2 id"spaghet"></h2> <script>spaghet.innerHTML (new URL(locatio…

数字校园的优势有哪些

数字化时代下&#xff0c;数字校园已成为教育领域一股显著趋势。数字校园旨在借助信息技术工具对传统校园进行改造&#xff0c;提供全新的教学、管理和服务方式。那么&#xff0c;数字校园究竟具备何种优势&#xff1f;现从三个方面为您详细介绍。 首先&#xff0c;数字校园为教…

【NI国产替代】产线测试:数字万用表(DMM),功率分析仪,支持定制

数字万用表&#xff08;DMM&#xff09; • 6 位数字表显示 • 24 位分辨率 • 5S/s-250KS/s 采样率 • 电源和数字 I/O 均采用隔离抗噪技术 • 电压、电流、电阻、电感、电容的高精度测量 • 二极管/三极管测试 功率分析仪 0.8V-14V 的可调输出电压&#xff0c;最大连…

鸿业的【管立得】设计的地下管线BIM模型如何导入到图新地球

0序&#xff1a; 在城乡建设行业&#xff0c;不论是园区的建设还是整个区划的智慧城市应用&#xff0c;地下管线都是很重要的组成元素。地下管线的直接测绘成果是管点表、管线表&#xff0c;存档及交付的成果多数是CAD文件&#xff0c;在智慧城市、市政工程、三维GIS信息化平台…

linux系统——ping命令

ping命令可以用来判断对远端ip的连通性&#xff0c;可以加域名也可以加公共ip地址 这里发送出56字节&#xff0c;返回64字节

How to: Add and Customize the Ribbon Skin List and Skin Gallery

皮肤列表和皮肤库允许用户选择皮肤。本文介绍如何在功能区中显示“皮肤列表”或“皮肤库”并对其进行自定义。 DevExpress演示中心中的大多数应用程序都允许您选择皮肤。例如&#xff0c;运行XtraGrid演示并导航到皮肤功能区页面以更改当前皮肤。 在功能区UI中显示皮肤列表或…

多模态模型是什么意思(国内外的AI多模态有哪些)

在人工智能和机器学习的领域&#xff0c;我们经常会遇到一些专业术语&#xff0c;这些术语可能会让初学者感到困惑。其中&#xff0c;"多模态模型"就是这样一个概念。 什么是AI多模态。它是什么意思呢&#xff1f; 那么&#xff0c;多模态模型是什么意思呢&#xff1…

前端工程化:基于Vue.js 3.0的设计与实践

这里写目录标题 《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》书籍引言本书概述主要内容作者简介为什么选择这本书&#xff1f;结语 《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》书籍 够买连接—>https://item.jd.com/13952512.html 引言 在前端技术日…

MySQL基础_10.约束

文章目录 第一章、约束1.1 约束的定义1.2 非空约束1.3 唯一性约束1.4 主键约束1.5 自增列1.6 外键约束1.7 CHECK约束1.8 DEFAULT约束 第一章、约束 1.1 约束的定义 约束是对表中字段的限制。 约束按照作用范围可以分为&#xff1a;列级约束和表级约束 列级约束&#xff1a;声…

人体感应提醒 大声公+微波模块

文章目录 模块简介接线程序示例 模块简介 微波感应开关模块 RCWL-0516是一款采用多普勒雷达技术&#xff0c;专门检测物体移动的微波感应模块。采用 2.7G 微波信号检测&#xff0c;该模块具有灵敏度高&#xff0c;感应距离远&#xff0c;可靠性强&#xff0c;感应角度大&#…

vue2组件封装实战系列之tag组件

作为本系列的第一篇文章&#xff0c;不会过于的繁杂&#xff0c;并且前期的组件都会是比较简单的基础组件&#xff01;但是不要忽视这些基础组件&#xff0c;因为纵观elementui、elementplus还是其他的流行组件库&#xff0c;组件库的封装都是套娃式的&#xff0c;很多复杂组件…

LVS负载均衡群集+NAT部署

目录 1.企业群集应用概述 1.1 群集的含义 1.2 企业群集分类 2.LVS负载均衡群集运用理论 2.1 负载均衡的架构 2.2 LVS负载均衡群集工作的三种模式 3.LVS虚拟服务器&#xff08;Linux Virtual Server&#xff09; 3.1 ip_vs通用模块 3.2 LVS调度器用的调度方法 4.ipvs…

嵌入式Linux系统编程 — 3.1 Linux系统中的文件类型

目录 1 Linux 系统中的文件类型简介 2 普通文件 2.1 什么是普通文件 2.2 普通文件分类 3 目录文件 4 字符设备文件和块设备文件 4.1 什么是设备文件 4.2 查看设备文件的方法&#xff1a; 5 符号链接文件&#xff08;link&#xff09; 5.1 什么是符号链接文件 5.2 如…

C# WPF入门学习主线篇(九)—— ComboBox常见属性和事件

欢迎来到C# WPF入门学习系列的第九篇。在前面的文章中&#xff0c;我们已经学习了 Button、TextBox、Label 和 ListBox 控件。今天&#xff0c;我们将探讨 WPF 中的另一个重要控件——ComboBox。本文将详细介绍 ComboBox 的常见属性和事件&#xff0c;并通过示例代码展示其在实…

RocketMQ的安装

首先到RocketMQ官网下载页面下载 | RocketMQ (apache.org)&#xff0c;本机解压缩&#xff0c;作者在这里用的是最新的5.2.0版本。按照如下步骤安装。 1、环境变量配置rocket mq地址 ROCKETMQ_HOME D:\rocketmq-all-5.2.0-bin-release 在变量path中添加”%ROCKETMQ_HOME%\bi…

应用广义线性模型一|线性模型

文章目录 一、统计学及思维模式二、未知现象的数学描述三、线性模型&#xff08;一&#xff09;线性模型的定义&#xff08;二&#xff09;线性模型的参数估计&#xff08;三&#xff09;线性模型的应用&#xff08;四&#xff09;离散解释变量的设计向量构建方法 四、线性模型…

makefile与进度条

Linux项目自动化构建工具-make/makefile make是一个命令&#xff0c; makefile是一个文件&#xff0c;保存依赖关系和依赖方法。‘ touch Makefile/makefile mybin:mytest.c//依赖关系 目标文件&#xff1a;依赖文件列表 文件列表的文件之间以空格分隔 gcc -o mybin mytest.…