寻迹小车的实现

提示:本文章是基于江协科技的stm32标准库教程 编码接口测速之前的实战项目。主要运用了GPIO的输入输出、定时中断、PWM驱动知识点做出的寻迹小车项目,开源分享。


声明

本项目用于巩固关于PWM驱动知识点,用于刚接触STM32的项目分享。
!!!最重要的是小车的寻迹速度是根据 具体所铺设的黑线 所决定的,并不一定都能适用!!
当铺设的黑线转弯角度改变,左右轮电机速度也需要改变,不能一味的照搬
总的工程文件已经开源分享,压缩密码为stm32,免费开源!
欢迎大家提出问题,作者也会虚心请教,如果没有显示资源,可以直接找我询问


一、硬件准备

1.STM32开发板

(备注:本人使用的是STM32F103C8T6)
STM32F103C8T6

2.L298N模块

L298N模块

3.红外寻迹模块(4个)

红外寻迹模块也可以选择一个4路巡线传感器
4路巡线传感器

4.12V锂电池

12V锂电池

5.OLED屏幕

(用于调试)
OLED

6.面包板

面包板

7.杜邦线

杜邦线

8.智能小车框架

智能小车框架


二、实现思路

思维导图
寻迹小车思维导图


三、Keil5代码实现

1.配置标准库

2.main.c文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Adjust.h"



int main(void)
{
	OLED_Init();
	Adjust_Init();
	OLED_ShowString(3,1," Track Car");

	 
	while(1)
	{
		Straight();
		Straight_Adjust();
		Turn_Left();
		Turn_Right();
	}
}

3.PWMA.c

#include "stm32f10x.h"                  // Device header

void PWMA_Init(void)
{
	//开启时钟(GPIOA 和 TIM2)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);				//将PA7引脚初始化为复用推挽输出	
														//受外设控制的引脚,均需要配置为复用模式
	
	
	
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;				     //定义结构体变量
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;         //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;     //计数器模式,选择向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 100 - 1;				 	 //计数周期,即ARR的值。到ARR后重新计数(100这个数字也直接读取占空比)
	TIM_TimeBaseInitStruct.TIM_Prescaler = 36 - 1;	          		 //预分频器,即PSC的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);                  //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	//输出比较初始化
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
	
	//使能TIM
	TIM_Cmd(TIM3, ENABLE);			//使能TIM2,定时器开始运行
}



//PWM设置CCR
void PWMA_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM3, Compare);		//设置CCR3的值
}


4.PWMA.h

#ifndef __PWMA_H
#define __PWMA_H

void PWMA_Init(void);
void PWMA_SetCompare2(uint16_t Compare);


#endif

5.PWMB.c

#include "stm32f10x.h"                  // Device header

void PWMB_Init(void)
{
	//开启时钟(GPIOA 和 TIM2)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

	//GPIO初始化
	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);				//将PA7引脚初始化为复用推挽输出	
														//受外设控制的引脚,均需要配置为复用模式
	
	
	
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;				     //定义结构体变量
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;         //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;     //计数器模式,选择向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 100 - 1;				 	 //计数周期,即ARR的值。到ARR后重新计数
	TIM_TimeBaseInitStruct.TIM_Prescaler = 36 - 1;	          		 //预分频器,即PSC的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);                  //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	//输出比较初始化
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	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_OC3Init,配置TIM2的输出比较通道3
	
	//使能TIM
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}



//PWM设置CCR
void PWMB_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);		//设置CCR3的值
}


6.PWMB.h

#ifndef __PWMB_H
#define __PWMB_H

void PWMB_Init(void);
void PWMB_SetCompare3(uint16_t Compare);


#endif

7.Motor.c

#include "stm32f10x.h"                  // Device header
#include "PWMA.h"
#include "PWMB.h"

void Motor_Init(void)
{

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_5|GPIO_Pin_4|GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA6~3引脚初始化为推挽输出	
	
	PWMA_Init();	
											//初始化直流电机的底层PWMA
	PWMB_Init();							//初始化直流电机的底层PWMB
}


	//直流电机设置速度
void Motor_SetSpeed_Left(int8_t Speed)
{
	if (Speed >= 0)							//如果设置正转的速度值
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_6);	//PA6置高电平
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);	//PA5置低电平,设置方向为正转
		
		PWMA_SetCompare2(Speed);			//PWMA设置为速度值
		
	}
	else									//否则,即设置反转的速度值
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_6);	//PA5置低电平
		GPIO_SetBits(GPIOA, GPIO_Pin_5);	//PA6置高电平,设置方向为反转


		PWMA_SetCompare2(-Speed);			//PWMA设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
	}
}

void Motor_SetSpeed_Right(int8_t Speed)
{
	if(Speed >= 0)
	{
	GPIO_SetBits(GPIOA, GPIO_Pin_3);	//PA3置高电平
	GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//PA4置低电平,设置方向为正转	
	PWMB_SetCompare3(Speed);			//PWMB设置为速度值
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_3);	//PA3置低电平
		GPIO_SetBits(GPIOA, GPIO_Pin_4);	//PA4置高电平,设置方向为反转
		PWMB_SetCompare3(-Speed);			//PWMB设置为速度值
	}	
}

8.Motor.h

#ifndef __MOTOR_H
#define __MOTOR_H


void Motor_Init(void);
void Motor_SetSpeed_Left(int8_t Speed);
void Motor_SetSpeed_Right(int8_t Speed);

#endif

9.Track.c

#include "stm32f10x.h"                  // Device header

void Track_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15|GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
}


uint8_t Track_Mid_1(void)
{
	return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14);	//如果被遮住,就为低电平,输出0
														//即有反射,没有黑线区域,输出0
														//黑线即没有反射,遇到黑线为输出1
}

uint8_t Track_Mid_2(void)
{
	return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15);	//如果被遮住,就为低电平,输出0
	
}

uint8_t Track_Left(void)
{
	return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12);
}

uint8_t Track_Right(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9);
}

10.Track.h

#ifndef __TRACK_H
#define __TRACK_H

void Track_Init(void);

uint8_t Track_Mid_1(void);
uint8_t Track_Mid_2(void);
uint8_t Track_Left(void);
uint8_t Track_Right(void);

#endif

11.Adjust.c

#include "stm32f10x.h"                  // Device header
#include "Adjust.h"


//寻迹模块默认高电压,设置没有遮挡为默认0,黑线识别为不被遮挡(不反射)
uint8_t Track_1 = 0;		//即有反射,没有黑线区域,输出0
uint8_t Track_2 = 0;        //黑线即没有反射,遇到黑线为输出1
uint8_t Track_3 = 0;
uint8_t Track_4 = 0;
uint8_t flag = 0;

void Adjust_Init(void)
{
	Track_Init();
	Motor_Init();

	
	Motor_SetSpeed_Left(Speed_Stop);
	Motor_SetSpeed_Right(Speed_Stop);
	
	Track_1 = Track_Mid_1();
	Track_2 = Track_Mid_2();

}

	/*判断标志位*/  
uint8_t IS_Flag(void)	
{
		Track_1 = Track_Mid_1();
		Track_2 = Track_Mid_2();
		Track_3 = Track_Left();
		Track_4 = Track_Right();
	
	//没有反射,即寻迹模块OUT端口为高电平
	if(Track_1 == 1 && Track_2 == 1 && Track_3 == 0 && Track_4 == 0 )	//&&Track_3 == 0 && Track_4 == 0
		{
			flag = 1;
		}else
			{
				flag = 0;
			}
	return flag;
}

	/*直线行走*/
void Straight(void)
{
		
		if(IS_Flag() == 1)
		{
			Motor_SetSpeed_Left(Speed_Nor);
			Motor_SetSpeed_Right(Speed_Nor);

		
		}else
			{
				Motor_SetSpeed_Left(Speed_Stop);
				Motor_SetSpeed_Right(Speed_Stop);

			}	
}
	/*直线的微调*/  
void Straight_Adjust(void)
{
		//如果中间左边1号没有黑线,中间右边2号有黑线
		if(Track_1 == 0 && Track_2 == 1 )
		{
			Motor_SetSpeed_Left(Speed_Hig);
			Motor_SetSpeed_Right(Speed_Low);

			
		}else if(Track_1 == 1 && Track_2 == 0)
		{
			Motor_SetSpeed_Right(Speed_Hig);
			Motor_SetSpeed_Left(Speed_Low);

		}
}


	/*向左转弯	(面对着小车来说)*/
void Turn_Left(void)
{
	//当最左边上的没有反射(Track_Left == 1),其余都有反射( == 0)
	
	if(Track_4 == 0 && Track_3 == 1)
	{
		Motor_SetSpeed_Right(Speed_Turn);
		Motor_SetSpeed_Left(Speed_Low);
	}
	
}

void Turn_Right(void)
{
	if(Track_3 == 0 && Track_4 == 1)
	{
		Motor_SetSpeed_Left(Speed_Turn + 5);	//根据实际布线填写的CCR占空比的大小
		Motor_SetSpeed_Right(Speed_Stop);
	}
}


12.Adjust.h

#ifndef __ADJUST_H
#define __ADJUST_H

#define Speed_Stop 0
#define Speed_Nor  60
#define Speed_Turn 85   




#define Speed_Hig Speed_Nor + 5
#define Speed_Low Speed_Nor - 5

#include "Motor.h"
#include "Track.h"

void Adjust_Init(void);
void Straight(void);
void Straight_Adjust(void);
uint8_t IS_Flag(void);
void Turn_Left(void);
void Turn_Right(void);

#endif


四、结果展示

1.铺设路线

铺设路线

2.顺时针寻迹

在这里插入图片描述

3.逆时针寻迹

在这里插入图片描述

4.小车模型

正面

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


总结

寻迹小车涉及到的PWM、定时器、GPIO输入输出等的复习
自检缺点:改变铺设的黑线,左右轮电机的占空比就要改变,没有拓展性。

STM32巡线小车的代码通常基于STM32单片机平台编写,它涉及硬件连接、传感器读取、PID控制等部分。以下是一个简化的四线式超声波避障小车的基本控制代码示例,假设我们使用的是STM32F103C8T6和超声波传感器HC-SR04: ```c #include "stm32f1xx.h" #include "usart.h" #define US_RxPin GPIO_Pin_9 // 超声波传感器接收端口 #define US_TxPin GPIO_Pin_8 // 超声波传感器发射端口 #define Motor_DIR_PIN GPIO_Pin_5 // 左右电机方向控制 #define Motor_PWM_PIN GPIO_Pin_6 // 左右电机速度控制 // 定义超声波传感器和电机的相关函数 void ultrasonic_Init(void); void setMotorSpeed(float speed, bool left); void readUltrasonic(void); int main(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // 开启GPIOB时钟 GPIO_InitTypeDef GPIO_InitStructure; // 初始化GPIO和USART GPIO_InitStructure.GPIO_Pin = US_RxPin | US_TxPin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); ultrasonic_Init(); while (1) { readUltrasonic(); // 读取超声波数据 if (data > 20) // 避障条件,例如距离小于20cm { setMotorSpeed(-0.5f, true); // 减速并向左转 } else { setMotorSpeed(0.5f, false); // 加速并向右转 } } } // 超声波传感器初始化 void ultrasonic_Init() { // 连接和配置超声波传感器 // ... } // 设置电机速度和方向 void setMotorSpeed(float speed, bool left) { float dutyCycle = map(speed, -1, 1, 0, 255); // 缩放速度到PWM范围 GPIO_SetBits(GPIOD, left ? Motor_DIR_PIN : !Motor_DIR_PIN); // 切换电机方向 TIM2->CCR1 = dutyCycle; // 更新左电机速度 TIM2->CCR2 = dutyCycle; // 更新右电机速度 } // 读取超声波传感器值 void readUltrasonic() { // 发射超声波,等待回音,计算距离... int data = calculate_distance(); } ``` 请注意,这只是一个基本框架,实际代码需要连接硬件电路、处理中断以及进行更复杂的PID控制。同时,你需要根据具体的STM32库文件编写超声波传感器驱动和电机驱动函数。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值