基于STM32的智能循迹避障小车设计

一、项目背景与意义

学习 STM32 的过程中,许多人在掌握理论知识后,往往面临缺乏实践项目锻炼的困境。智能循迹避障小车系统作为一个综合性项目,涵盖了 STM32 的 GPIO 控制、传感器数据采集、算法逻辑实现等核心知识点,非常适合作为 STM32 学习后的首个实践项目。本文将分享基于 STM32 的智能循迹避障小车系统设计与实现过程,希望能为正在探索嵌入式开发的同行提供参考和启发。以下大部分都是基于江协的代码修改实现,欢迎大家进行交流学习。

二、系统整体架构

三、硬件设计详解

核心控制器:STM32F103C8T6

STM32F103C8T6是一款高性能、低功耗的32位ARM Cortex- M3处理器,主频可达72MHz,具备强大的处理能力。
内置多种外设接口,如USART、SPI、I2C等,便于与其他模块通信,满足小车系统控制需求。

电机驱动模块:TB6612FNG

TB6612FNG是一款双通道直流电机驱动芯片,可驱动两路电机独立运行。
具有驱动能力强、效率高、控制简单等特点,适用于小车电机驱动

将TB6612FNG与小车电机相连,通过STM32输出PWM信号控制电机转速。

红外传感器:TCRT5000

TCRT5000是一种反射式红外传感器,通过发射红外光并接收反射光来检测物体表面的反射率差异。
当小车行驶在黑白路径上时,传感器可依据反射光强度判断路径位置,实现精准循迹

采用基于传感器阵列的数字信号处理算法,实时分析路径信息,确定小车行驶方向。
当检测到路径偏移时,通过调整电机转速与转向,使小车快速回归正确路径,确保循迹精度与稳定性

蓝牙串口模块: HC-05

HC- 05是一款主从机一体的蓝牙串口透传模块,支持蓝牙2.0协议,可实现设备之间的无线数据通信。在本项目中,通过HC- 05将手机端与小车控制系统连接,实现远程遥控功能。将HC- 05的串口引脚与STM32的USART接口相连,即可完成硬件连接。在手机端使用蓝牙调试器,通过蓝牙协议与HC- 05通信,发送控制指令。STM32接收手机端发送的指令,解析并执行相应操作,如前进、后退、转弯等。实现小车的远程操控功能,拓展小车的应用场景与操作便利性。

超声波传感器: HC-SR04

HC- SR04利用超声波在空气中的传播特性,通过发射超声波并接收反射波,测量超声波往返时间,计算障碍物距离。

将HC- SR04的信号引脚与STM32的GPIO口相连,通过控制信号引脚触发超声波发射,接收回波信号并计算距离。

设定安全距离阈值,当检测到障碍物距离小于阈值时,触发避障算法。
通过调整小车行驶方向与速度,避开障碍物,选择最优路径继续行驶,保障小车安全运行

其他模块:OLED显示屏、DC3V-6V直流减速电机、SG90 9g舵机、12v锂电池组

其他模块这里就不再详细介绍,使用一块OLED显示屏会对我们调试程序有非常大的帮助,直流电机配合电机驱动芯片控制小车运动,舵机转动配合超声波模块实现避障。

3.1 核心控制单元

  • STM32最小系统设计要点

  • PWM 输出:利用 TIM(定时器)外设产生 PWM 信号控制电机转速,通常选择具有互补输出功能的定时器通道,确保电机驱动的稳定性。

  • ADC 采集:将循迹模块的红外传感器输出连接至 STM32 的 ADC 引脚,配置为连续转换模式,通过 DMA(直接内存访问)减少 CPU 负载,快速获取传感器模拟信号。​

  • UART 通信:预留 USART1 作为调试串口,连接至 USB 转 TTL 模块,方便烧录程序与打印调试信息;USART2 用于蓝牙模块通信,通过配置波特率实现无线数据传输。

3.2 运动控制模块

  • 将 STM32 的 GPIO 引脚与 TB6612FNG 的 IN1-IN4(电机转向控制)和 PWMA、PWMB(电机速度控制)连接,通过高低电平组合实现电机正转

  • PWM 调速通过调节占空比实现,公式为:​Vout​=Vmax​×Tton​​,其中​ton​为高电平时间,​T为 PWM 周期。在 STM32 中,通过配置 TIM 外设的 ARR(自动重装载值)和 CCR(捕获 / 比较寄存器)调整占空比,例如设置 ARR=100,CCR=50 时,占空比为 50%。

3.3 感知系统设计

循迹模块

TCRT5000 阵列布局方案​

TCRT5000 是红外反射式传感器,通过发射红外光并检测反射信号判断地面颜色。推荐采用 5 路传感器阵列布局:中间 1 路用于直行检测,两侧各 2 路用于弯道和偏移修正。传感器间距建议为 2cm,确保能覆盖 1cm 宽的黑色循迹线。​(代码里使用的是四路传感器)

红外反射式检测原理​

传感器通过发射红外光并接收反射光来检测物体表面的反射率差异。当小车行驶在黑白路径上时,传感器可依据反射光强度判断路径位置,同时返回一个数字信号,在软件设计里通过数字信号的判断实现精准循迹。

避障模块

HC- SR04利用超声波在空气中的传播特性,通过发射超声波并接收反射波,测量超声波往返时间,计算障碍物距离。我们只需要设定安全距离阈值,当检测到障碍物距离小于阈值时,触发避障算法。通过调整小车行驶方向与速度,避开障碍物,选择最优路径继续行驶,保障小车安全运行。

蓝牙通信模块

HC-05 主从模式配置​

HC-05 默认工作在从模式,通过 AT 指令可切换至主模式:​

  1.   将 HC-05 与 STM32 的 USART2 连接,设置波特率为 38400bps(AT 指令默认波特率)。​
  2.   发送AT指令测试通信,返回OK表示连接正常。​
  3.   切换主模式:发送AT+ROLE=1,重启后生效。

将HC- 05的串口引脚与STM32的USART接口相连,即可完成硬件连接。在手机端使用蓝牙调试器,通过蓝牙协议与HC- 05通信,发送控制指令。STM32接收手机端发送的指令,解析并执行相应操作,如前进、后退、转弯等。实现小车的远程操控功能,拓展小车的应用场景与操作便利性。

四、软件设计

主函数 main

处理OLED显示和模式切换

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Timer.h"
#include "TB6612.h"
#include "OLED.h"
#include "Key.h"
#include "TCRT5000.h"
#include "OLED.h"
#include "USART.h"
#include "ADC.h"
#include "HC_SR04.h"

uint16_t ADValue;	//ADC采样值
float Voltage;		//电压值

void Mode_Change(void)
{	
	switch(Mode)
	{	
		case 0:
			TIM_SetCompare1(TIM3,72);	//控制舵机朝前
		
			AIN1 = 0; AIN2 = 0; 	//停止
			BIN1 = 0; BIN2 = 0;
			OLED_ShowString(4, 1, "Stop     ");
		break;
		
		case 1:
			SR04_Follow();	//超声波跟随
		break;
			
		case 2:
			Usart_Ctrl();	//蓝牙遥控
		break;
		
		case 3:
			SR04_bizhang();	//超声波避障
		break;
		
		case 4:
			HW_Tracking();	//红外循迹
		break;
	}
}

int main(void)
{	
	LED_Init();		 //LED初始化
	TB6612_Init();	 //电机初始化
	Exti_Init();	 //外部中断初始化
	TCRT5000_Init(); //红外对管初始化
	OLED_Init();	 //OLED显示初始化
	Serial_Init();	 //串口初始化	
	AD_Init();		 //ADC初始化
	SR04_Init();	 //超声波模块初始化
	IC_Init();		 //定时器2通道2输入捕获初始化
	
	TIM1_PWM_Init(1999, 359);	//定时器1 PWM 输出初始化(电机)
	TIM3_PWM_Init(999, 1439);	//定时器3 PWM 输出初始化(舵机)  72M / 1000 / 1440 = 50
	
	OLED_ShowString(1,1,"Mode: ");		//显示模式
	OLED_ShowString(2,1,"Voltage:0.00V");	//显示电压值
	OLED_ShowString(3,1,"Distance:");		//显示距离
	
	while(1)
	{	
		OLED_ShowNum(1,6,Mode,1);
		
		Usart_Ctrl();
		
		Mode_Change();
		
		//ADC电压转换
		ADValue = AD_GetValue();	//获取ADC采样数据
		Voltage = (float)ADValue / 4095 * 3.3;	//转换电压值
		
		OLED_ShowNum(2,9,Voltage,1);	//电压值整数部分
		OLED_ShowNum(2,11,(uint16_t)(Voltage * 100) %100,2); //电压值小数部分
		
		//超声波测距
		float dist = Get_Distance();
		if(dist < 100)	OLED_ShowNum(3,10,dist,2);
		else OLED_ShowNum(3,10,dist,3);
        Delay_ms(100);  // 每次测量间隔至少60ms		
	}
}

延时函数 Delay.c 

延时函数可以直接调用江协的代码

#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

显示模块 OLED.c 

直接调用江协的代码再改一下GPIO(根据你的原理图进行修改)

#include "stm32f10x.h"
#include "OLED_Font.h"

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOC, GPIO_Pin_14, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOC, GPIO_Pin_15, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(!!(Byte & (0x80 >> i)));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

定时器模块 Timer.c 

产生PWM波控制电机和舵机转动

#include "stm32f10x.h"

// 定时器1 PWM 初始化(电机转动)
// 参数 ARR: 自动重装载值,用于设置PWM周期
// 参数 PSC: 预分频器值,用于设置定时器时钟频率
void TIM1_PWM_Init(uint16_t ARR, uint16_t PSC)
{
    // 使能TIM1的时钟,TIM1是高级定时器,用于产生PWM信号
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    // 使能GPIOA的时钟,因为PWM信号将通过GPIOA的引脚输出
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);

    // 配置TIM1使用内部时钟源
    TIM_InternalClockConfig(TIM1);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Period = ARR;
    TIM_TimeBaseInitStruct.TIM_Prescaler = PSC;
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);

    // 定义定时器输出比较初始化结构体
    TIM_OCInitTypeDef TIM_OCInitStructure;
	
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
	// 初始化TIM1的通道1输出比较参数
    TIM_OC1Init(TIM1,&TIM_OCInitStructure);

    // 使能TIM1通道1的预装载寄存器
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    // 初始化TIM1的通道4输出比较参数
    TIM_OC4Init(TIM1,&TIM_OCInitStructure);

    // 使能TIM1通道4的预装载寄存器
    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);

    // 使能TIM1定时器
    TIM_Cmd(TIM1, ENABLE);

    // 使能TIM1的PWM输出(高级定时器需开启)
    TIM_CtrlPWMOutputs(TIM1,ENABLE);
}

// 定时器3 PWM 初始化(舵机转向)
void TIM3_PWM_Init(uint16_t ARR, uint16_t PSC)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, 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_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_InternalClockConfig(TIM3);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Period = ARR;
    TIM_TimeBaseInitStruct.TIM_Prescaler = PSC;
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);

    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_Cmd(TIM3, ENABLE);
}    

电机驱动模块  TB6612.c

驱动电机前进、后退、转弯

#include "stm32f10x.h"
#include "TB6612.h"
#include "Timer.h"

//LED初始化
void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOC,GPIO_Pin_13);	//PC13置零,点亮LED
}

//电机初始化
void TB6612_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_12 | GPIO_Pin_1 | GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_13 | GPIO_Pin_12 | GPIO_Pin_1 | GPIO_Pin_0);
}

void Forward(void)		//小车前行
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 0;
	BIN2 = 1;
	
	TIM_SetCompare1(TIM1,1500);
	TIM_SetCompare4(TIM1,1500);
}

void Backward(void)		//小车后退
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 1;
	BIN2 = 0;
	
	TIM_SetCompare1(TIM1,1500);
	TIM_SetCompare4(TIM1,1500);
}

void TurnRight(void)	//小车右转
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 1;
	BIN2 = 0;
	
	TIM_SetCompare1(TIM1,1500);
	TIM_SetCompare4(TIM1,1500);
}

void TurnLeft(void)		//小车左转
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 1;
	
	TIM_SetCompare1(TIM1,1500);
	TIM_SetCompare4(TIM1,1500);
}

按键模块 Key.c

按键控制模式切换

#include "stm32f10x.h"
#include "Delay.h"
#include "Key.h"
#include "TB6612.h"
#include "USART.h"

void Key_Init(void)	//按键初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;	  //PA12 (Key2)
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
 	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;	  //PA7  (Key1)
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void Exti_Init(void)
{
	Key_Init();
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource12);// 将GPIOB的第12个引脚与EXTI Line12关联,以便可以作为外部中断线使用。
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line12;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;// 设置中断触发方式为下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7); 

  	EXTI_InitStructure.EXTI_Line = EXTI_Line7;
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
	
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//分组
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;			//使能外部中断通道7
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//抢占优先级
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;		//子优先级
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//使能外部中断	
  	NVIC_Init(&NVIC_InitStructure); 

  	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//使能外部中断通道12
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;		
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				
  	NVIC_Init(&NVIC_InitStructure);  
}

void EXTI9_5_IRQHandler(void)
{
	if(Key1 == 1)	//按下Key1 (下拉输入)
	{	
		Mode ++;
		if(Mode == 5) Mode = 1;
		LED = ~LED;		
	}
	
	EXTI_ClearITPendingBit(EXTI_Line7); //清除EXTI_Line7上的中断标志位
}

void EXTI15_10_IRQHandler(void)
{
	if(Key2 == 0)	//按下Key2	(上拉输入)
	{	
		Mode = 0;
		LED = ~LED;
	}
	
	EXTI_ClearITPendingBit(EXTI_Line12);
}

 ADC电压转换模块 ADC.c

ADC转换电池电压,不影响主体功能

#include "stm32f10x.h"                  // Device header

void AD_Init(void)	//ADC初始化
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;	//选择GPIOA的引脚4作为模拟输入引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	ADC_RegularChannelConfig(ADC1,ADC_Channel_4,1,ADC_SampleTime_55Cycles5);
	
	ADC_InitTypeDef ADC_InitStructure;
	// 将ADC的数据对齐方式设置为右对齐
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    // 设置ADC的外部触发转换模式为无
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    // 将ADC的工作模式设置为独立模式
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    // 禁用ADC的连续转换模式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    // 禁用ADC的扫描转换模式,即只对单个通道进行转换
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    // 设置ADC规则组中要转换的通道数量为1个
    ADC_InitStructure.ADC_NbrOfChannel = 1;
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);	//使能
	
	ADC_ResetCalibration(ADC1);		//校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
}

uint16_t AD_GetValue(void)	//获取指定ADC通道转换值的函数
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);
	return ADC_GetConversionValue(ADC1);
}

红外循迹模块 TCRT5000 

根据红外传感器返回值调整小车运动状态

#include "stm32f10x.h"
#include "TCRT5000.h"
#include "Delay.h"
#include "TB6612.h"

void TCRT5000_Init(void)	//红外对管初始化
{	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB,ENABLE);
	
	//重映射配置关闭JTAG-DP 启用SW-DP从而可以使用PA15 PB3 PB4
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;	  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5 | GPIO_Pin_4 | GPIO_Pin_3;	  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void HW_Tracking(void)	//红外循迹
{	
	//前进
	if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
	{
		Forward();
		Delay_ms(50);
	}
	
	//右转
	if(HW_1 == 0 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
	{
		TurnRight();
		Delay_ms(150);
	}
	if(HW_1 == 1 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
	{ 
		TurnRight();
		Delay_ms(250);
	}
	if(HW_1 == 1 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
	{
		TurnRight();
		Delay_ms(300);
	}
	
	//左转
	if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 0)
	{
		TurnLeft();
		Delay_ms(150);
	}
	if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 1)
	{
		TurnLeft();
		Delay_ms(250);
	}
	if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 1)
	{
		TurnLeft();
		Delay_ms(300);
	}
}

蓝牙遥控模块 USART.c

通过连接蓝牙可以使用手机直接控制小车状态和模式选择

#include "stm32f10x.h" 
#include <stdio.h>
#include <stdarg.h>
#include "Delay.h"
#include "TB6612.h"
#include "OLED.h"

uint8_t Mode = 0;	//模式选择(1:超声波跟随;2:蓝牙遥控;3:超声波避障;4:红外循迹)

/*
uint8_t Serial_RxData;	// 存储串口接收到的单个字节数据
uint8_t Serial_RxFlag;	// 标记串口是否接收到新数据的标志位
uint8_t temp;
*/

// 环形缓冲区配置
// 定义环形缓冲区的大小为64字节
#define RX_BUFFER_SIZE 64
// 定义一个8位整数数组,作为环形缓冲区,用于存储串口接收到的数据
volatile uint8_t Serial_RxBuffer[RX_BUFFER_SIZE];
// 定义一个16位整数,作为环形缓冲区的头指针,初始值为0
volatile uint16_t Serial_RxHead = 0;
// 定义一个16位整数,作为环形缓冲区的尾指针,初始值为0
volatile uint16_t Serial_RxTail = 0;

void Serial_Init(void)	//串口1函数初始化
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;	//TX  PB10
	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_11 ;	//RX  PB11
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 115200;	//波特率
	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;	//1停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//8位数据
	USART_Init(USART3, &USART_InitStructure);
	
	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART3, ENABLE);
}

void Serial_SendByte(uint8_t Byte)	// 发送单个字节数据的函数
{
	USART_SendData(USART3, Byte);
	while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
}

void USART3_IRQHandler(void) //串口中断服务函数
{
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) 
	{
        uint8_t data = USART_ReceiveData(USART3);
        uint16_t next_head = (Serial_RxHead + 1) % RX_BUFFER_SIZE;
        
        // 缓冲区未满时存储数据
        if(next_head != Serial_RxTail) 
		{
            Serial_RxBuffer[Serial_RxHead] = data;
            Serial_RxHead = next_head;
        }
        
        USART_ClearITPendingBit(USART3, USART_IT_RXNE);
    }
}

void Usart_Ctrl(void) 	//蓝牙控制小车运动
{
    while(Serial_RxTail != Serial_RxHead) 	 // 当环形缓冲区中有数据时
	{
        uint8_t command = Serial_RxBuffer[Serial_RxTail];	// 从环形缓冲区中取出一个字节数据作为命令
        Serial_RxTail = (Serial_RxTail + 1) % RX_BUFFER_SIZE;	 // 更新尾指针的位置

        LED = ~LED; // 状态指示
        
        switch(command) 
		{
            case 'A':
				if(Mode == 2)
				{
					Forward();	//前进
					OLED_ShowString(4, 1, "Forward  ");
				}  
            break;
            case 'B':
				if(Mode == 2)
				{
					Backward();	//后退
					OLED_ShowString(4, 1, "Backward ");
				}               
            break;
            case 'C':
				if(Mode == 2)
				{
					TurnRight();//右转
                OLED_ShowString(4, 1, "TurnRight");
				}               
            break;
            case 'D':
				if(Mode == 2)
				{
					TurnLeft();	//左转
					OLED_ShowString(4, 1, "TurnLeft ");
				}                
            break;
            case 'E':
				if(Mode == 2)
				{
					AIN1 = 0; AIN2 = 0; //停止
					BIN1 = 0; BIN2 = 0;
					OLED_ShowString(4, 1, "Stop     ");
				}                
            break;

			case 'F':
                Mode = 1;
            break;
			case 'G':
                Mode = 2;
            break;
			case 'H':
                Mode = 3;
            break;
			case 'I':
                Mode = 4;
            break;
			case 'J':
                Mode = 0;
            break;
        }
    }
}

/*
void Serial_SendArray(uint8_t *Array, uint16_t Length)	// 发送字节数组的函数
{
	uint16_t i;
	for(i=0; i<Length; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)	// 发送字符串的函数
{
	uint8_t i;
	for(i=0; String[i] != '\0'; i++)
	{
		Serial_SendByte(String[i]);
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)	// 计算X的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, ...)	// 实现类似printf功能的函数,支持格式化输出
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)	// 获取串口接收标志位的函数
{
	if(Serial_RxFlag != 0)
	{
		temp = Serial_RxFlag;
		Serial_RxFlag = 0;
		return temp;
	}
	return 0;
}

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

超声波避障模块 HC_SR04 

实现超声波测距,再通过测距进行避障

#include "stm32f10x.h"
#include "Delay.h"
#include "TB6612.h"

volatile uint32_t rise_time = 0;	//存储超声波信号上升沿的捕获时间
volatile uint32_t fall_time = 0;	//存储超声波信号下降沿的捕获时间
volatile uint32_t overflow_count = 0;	//记录定时器溢出的次数
volatile uint8_t capture_stage = 0; // 0:等待上升沿,1:等待下降沿
volatile float distance_cm = 0.0;	//存储计算得到的超声波测量距离

void SR04_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_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);
}

void IC_Init(void)	//定时器2通道2输入捕获配置
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // TIM2_CH2 对应 PA1
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
	
    TIM_InternalClockConfig(TIM2);
	
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;	//自动重装载寄存器(ARR)
    TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;	//预分频器(PSC)
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
	
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; // 指定使用 TIM2 的通道 2 进行输入捕获
    TIM_ICInitStructure.TIM_ICFilter = 0x0;	//输入捕获滤波器的值为 0
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//设置捕获边沿为上升沿
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	//每个有效边沿都进行捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//捕获信号直接来自通道 1 的输入
    TIM_ICInit(TIM2,&TIM_ICInitStructure);
	
    TIM_SelectInputTrigger(TIM2,TIM_TS_TI2FP2); // 选择 TI2FP2 作为触发源
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;	// TIM2 中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure);  
	
	TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);
	
    TIM_Cmd(TIM2,ENABLE);
}

void SR04_Trigger(void)
{
    GPIO_SetBits(GPIOA, GPIO_Pin_0);  // 发送10us高电平脉冲
    Delay_us(12);                    
    GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}

float Get_Distance(void) // 触发超声波模块发射超声波信号的函数
{
    SR04_Trigger();      // 发送触发信号
    Delay_ms(60);        // 等待测量完成
    return distance_cm;  // 返回计算结果
}

void TIM2_IRQHandler(void)	//定时器2的中断服务函数	
{
    // 处理溢出中断
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        overflow_count++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    
    // 处理捕获中断
    if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
    {
        if (capture_stage == 0)
        {
            // 上升沿捕获
            rise_time = TIM_GetCapture2(TIM2);
            overflow_count = 0;
            // 切换为下降沿捕获
            TIM_OC2PolarityConfig(TIM2, TIM_ICPolarity_Falling);
            capture_stage = 1;
        }
        else
        {
            // 下降沿捕获
            fall_time = TIM_GetCapture2(TIM2);
            // 计算时间差(单位:微秒)
            uint32_t time_diff = (overflow_count * 65536) + (fall_time - rise_time);
            // 计算距离(声速340m/s = 0.0343cm/μs,来回距离需除以2)
            distance_cm = (time_diff * 0.0343) / 2;
            // 切换回上升沿准备下次测量
            TIM_OC2PolarityConfig(TIM2, TIM_ICPolarity_Rising);
            capture_stage = 0;
        }
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
    }
}

void SR04_Follow(void)	//超声波跟随
{
	if(Get_Distance() > 30)
	{
		Forward();
		Delay_ms(50);
	}
	else if(Get_Distance() < 15)
	{
		Backward();
		Delay_ms(50);
	}
	else
	{
		AIN1 = 0;
		AIN2 = 0;
		BIN1 = 0;
		BIN2 = 0;
	}
}

void SR04_bizhang(void)		//超声波避障
{	
	Delay_ms(500);
	
	TIM_SetCompare1(TIM3,72);	//控制舵机朝前
	Delay_ms(200);
	
	if(Get_Distance() > 25)	//前方无障碍
	{
		Forward();			//向前运动
		Delay_ms(500);
	}
	else					//前方有障碍
	{
		TIM_SetCompare1(TIM3,42);	//控制舵机朝右30°
		Delay_ms(200);
		
		if(Get_Distance() > 25)	//右前方无障碍
		{
			TurnRight();		//右转
			Delay_ms(700);
		}
		else					//右前方有障碍
		{
			TIM_SetCompare1(TIM3,102);	//控制舵机朝左30°
			Delay_ms(200);
			
			if(Get_Distance() > 25)	//左前方无障碍
			{
				TurnLeft();			//左转
				Delay_ms(700);
			}
			else					//左前方有障碍
			{
				Backward();			//后退
				Delay_ms(700);
				TurnRight();		//右转
				Delay_ms(700);
			}
		}
	}
}	

五、总结与展望

通过完成基于 STM32 的智能循迹避障小车项目,不仅能巩固理论知识,更能提升将知识转化为实际应用的能力。在这个过程中,从硬件设计、程序编写到系统调试,每一个环节都是对个人技术能力的锻炼。如果你已经学完 STM32,不妨尝试从小车项目入手,迈出嵌入式开发实践的第一步。相信在解决各种实际问题的过程中,你会对 STM32 有更深刻的理解和掌握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值