智能小车-练手小项目(含源码)

智能小车

1. 描述

1.1 项目描述

智能小车项目是一个集成了机械、机电、计算机、传感器、等多种技术的综合性项目。本项目旨在通过设计制作智能小车,探索机器人控制、机械设计及电路控制等多方面知识,并将其汇总应用到实际中,以提高工程设计能力和动手能力。

1.2 物料清单

  • 物料在淘宝搜索即可
  • 充电宝供电的好处:调试时可以移植开着,不用担心电量不足导致的驱动力不够
  • 除了小车底盘,价格都是几块钱左右

1.3 开发方案

  • 蓝牙接收控制信号

  • PWM 驱动电机4个电机、1个舵机,

    • TIM2,ch3驱动4个电机;
    • TIM3,ch3驱动1个舵机;
  • 超声波测距,实现避障,

    • TIM4作为定时器计时;
    • EXTI外部中断检测指定GPIO电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请;
      • 选择配置NVIC的EXTI15_10中的13号线为外部中断
    • 根据声音传递的速度、距离、时间 测量距离
  • 寻迹,根据红外反射传感器接收引脚高低电平判断小车转弯或直行

2. Bug记录

2.1 左转、右转问题

  • 问题:左转、右转控制不了,要么同时不转动,要么同时不转
    • 方式一、同一个PWM 控制不同电机,制动其中一个,直接给低电平就行
    • 方式二、用不同PWM控制不同电机,就不会存在上述问题

2.2 舵机在某一位置抖动

  • 现象 :舵机在某一位置附近不停地 抖动,有时会突然安装单片机指令转动一定角度,之后继续抖动,感觉不受单片机控制。
  • 原因:STM32开发板 ---> 舵机 供电
  • 解决5V 直连 舵机 即可解决。

2.3 超声波测距 影响电机测速

  • 问题描述: 只要打开超声波测距模块,电机转速就会变成一个固定值

  • 原因: 超声波用的是TIM4计时,电机用的时TIM2 生成PWM波形控制转速,这里手误写成了TIM2,导致电机使用的TIM2失能,不起作用,只能是一个惯性的固定值

  • 解决: 将TIM2 改成TIM4即可。

3. 超声波模块

型号:HC_SR04

3.1 超声波测距原理

  • 超声波 发射器 向外发射超声波信号同时定时器开始计时,超声波在空气中传播时若遇到障碍物就立即被反射回来超声波 接收器 在收到反射波后立即停止定时器;
  • 超声波在空气中的传播速度约为 340m/s
  • 若定时器记录的时间为t(s),那么超声波传感器距离障碍物的距离为:s = 340 * t / 2

3.2 超声波传感器

  • 超声波传感器共有四个引脚Vcc、Gnd、Trig、Echo,

    • 其中 VccGnd 为供电引脚;
    • 模块工作时 向Trig引脚发送一个持续 10us以上 高电平信号,模块就 对外发送 40kHz 的超声波信号
    • 发送完成后 Echo 引脚 自动变为 高电平,若模块 接收到反射回来回波信号 Echo引脚又会自动变为 低电平,即Echo引脚上 高电平 持续的时间 即为 超声波信号在空气中传播的时间
    • 测试距离 = (高电平时间 * 音速)/ 2
  • 实物:

  • 特点:

    • 宽电压工作:3V-5.5V
    • 探测距离:
      • 5V: 2cm -- 450cm
      • 3.3V: 2cm -- 400cm
    • 探测角度:<15°
    • 工作温度:-20℃ -- 80℃
  • 时序图:

  • 距离计算方式:

    • 以上时序图表明只需要提供一个 10us以上 脉冲触发信号,该 模块内部 将发出 8个40kHz周期电平 并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。

    • 公式:

      • uS/58 = 厘米 或者 uS/148=英寸
      • 或是:距离=高电平时间*声速(340M/S) /2
      • 建议测量周期为60ms以上,以防止发射信号对回响信号的影响
    • 所以:

      • 我们仅需提供10us的高电平给Trig口即可, 然后HC-SR04在测量完毕之后会将结果通过Echo回响回来;

      • 只需要将Trig口拉高,等待10us(最好再延长一些,15us~20us)后再拉低即可;

      • 接着就只需要等待Echo将数据传输回来,通过时序图我们可以得知回响信号是拉高Echo口,再拉低,中间持续的时间就是测距的结果。

      • 所以我们给Echo口配置一个中断事件,设置为上跳变下跳变都触发,另外再用一个变量记录Echo口到底是拉高还是拉低即可。

        • 如果是拉高,那么我们需要记录下持续的时间,这时候我们需要用定时器计时,所以需要在一开始的时候就配置好定时器的初始化。唯一的问题就是该如何配置定时器的预分频器和自动重装器了。
        • 根据说明书我们可以知道HC-SR04的精度为3mm,而测距的公式为 us/58-cm,稍加计算可知,如果我们需要测量3mm,那么得到的时间为17.4us,以此为一个刻度,那么定时器的频率应该为57471Hz。然而这样太麻烦了,而且也不好用;
        • 因此我们可以随意一些,在代码中使用的是 预分频器为72自动重装器为100,那么得到的频率为72MHz/72/100=10000Hz,也就是 一次定时器中断的时间为100us,而 自动重装器里的每一个值就是1us
        • 所以每次外部中断的下降沿触发之后只需要将 定时器触发的次数*100+自动重装器里的值 就可以得到回响信号的持续时间了,单位是us。

3.3 测试代码

MyTimer.c

c 复制代码
#include "stm32f10x.h"                  // Device header

void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);			//开启TIM4的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM4);		//选择TIM4为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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(TIM4, &TIM_TimeBaseInitStructure);				
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM4, TIM_FLAG_Update);   //清除定时器更新标志位,TIM_TimeBaseInit函数末尾,手动产生了更新事件
	
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);					//开启TIM4的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2,
																
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								
	
	/*TIM使能*/
	TIM_Cmd(TIM4, ENABLE);			//使能TIM4,定时器开始运行
}

Ultrasound.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "MyTimer.h"

uint8_t flage=0;		//用于记录中断信号是上升沿还是下降沿
uint16_t Timer_cnt;		//记录定时器中断的次数
uint16_t times;			//记录回想信号持续时间

void Ultrasound_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		// 推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;				// Trig
	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_13;				// Echo
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/*AFIO选择中断引脚*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);//将外部中断的13号线映射到GPIOB,即选择PB13为外部中断引脚
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;							//定义结构体变量
	EXTI_InitStructure.EXTI_Line = EXTI_Line13;						//选择配置外部中断的13号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;						//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;				//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;	//指定外部中断线为上升、下降沿触发
	EXTI_Init(&EXTI_InitStructure);	
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								
	
	Timer_Init();
}

/*TIM4定时器中断*/
void TIM4_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)		//判断是否是TIM4的更新事件触发的中断
	{
		Timer_cnt++;										//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);			//清除TIM4更新事件的中断标志位
	}
}


/*EXTI15_10外部中断函数*/
void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line13) == SET)		//判断是否是外部中断13号线触发的中断
	{
		if (flage == 0) 							//上升沿即回响电平开始,打开计数器
		{
			Timer_cnt = 0;							//定时器中断次数置0
			flage = 1;
			TIM_SetCounter(TIM4, 0);				//设置TIM4值为0
            TIM_Cmd(TIM4, ENABLE);					//使能TIM4
		}
		else										//下降沿即回响电平结束,统计高电平持续时长
		{
			TIM_Cmd(TIM4, DISABLE);
			flage = 0;
            times=Timer_cnt * 100 + TIM_GetCounter(TIM4);  //得到回响的高电平持续的us
		}
		EXTI_ClearITPendingBit(EXTI_Line13);		//清除外部中断13号线的中断标志位
	}
}

// 测量距离
float Get_Distance(void)
{
	uint32_t distance = 0;
	for(int i=0; i<5; i++)
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_12);		// Trig引脚拉高保持大于10us以上
		Delay_us(20);
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);	
		Delay_ms(60);                   		//根据说明书,每个周期至少需要等待60ms
		distance += (times/58);          		//根据说明书提供的公式,获取单位为cm的距离
	}
	return distance/5;
}

main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Ultrasound.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Ultrasound_Init();
	
	OLED_ShowString(1, 1, "Dis:", OLED_8X16);
	
	while (1)
	{
		OLED_ShowNum(32, 1, Get_Distance(), 5, OLED_8X16);
		OLED_Update();
	}
}

4. 红外寻迹模块

型号:TCRT5000,反射是红外传感器

4.1 原理

TCRT5000传感器 的红外发射二极管不断 发射红外线

  • 当发射出的红外线没有被反射回来或被反射回来但强度不够大时,红外接收管一直处于关断状态,此时 模块的输出端为高电平指示二极管一直处于熄灭状态

  • 被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时 模块的输出端为低电平指示二极管被点亮

没反射------DO输出高电平------灭灯

反射------DO输出低电平------点亮

4.1.1 寻迹原理

普通的循迹跑道都是黑色的原因:

  • 黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED 常亮
  • 感应到黑线,DO输出高电平,灭灯

4.2 模块测试代码

main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"


int main(void)
{
	OLED_Init();

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// GPIO初始化
	GPIO_InitTypeDef GPIO_Initstructure;
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Initstructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_Initstructure);
	
	OLED_ShowString(0, 0, "Trace:", OLED_8X16);
	
	while(1)
	{
		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1))
		{OLED_ShowNum(48, 0, 1, 1, OLED_8X16);}
		else
		{OLED_ShowNum(48, 0, 0, 1, OLED_8X16);}
		OLED_Update();
	}
}

5. 代码

5.1 main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Car.h"
#include "Servo.h"
#include "Ultrasound.h"
#include "Serial.h"
#include "Motor.h"
#include "Trace.h"

#include <stdio.h>

uint8_t Mode;				// 小车模式
uint8_t Car_State;			// 小车运行状态
uint8_t Serial_RxData;		// 串口接收的数据变量
uint8_t Speed = 60;			// 定义速度变量


// 中断函数 接收蓝牙控制的数据(小车状态、模式)
void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		
	{
		Serial_RxData = USART_ReceiveData(USART1);
		if(Serial_RxData == 0x01) Car_State = 1; 	// 前进
		if(Serial_RxData == 0x02) Car_State = 2;	// 后退
		if(Serial_RxData == 0x03) Car_State = 3;	// 向左
		if(Serial_RxData == 0x04) Car_State = 4;	// 向右
		if(Serial_RxData == 0x05) Car_State = 5;	// 加速
		if(Serial_RxData == 0x06) Car_State = 6;	// 自转
		if(Serial_RxData == 0x11) Mode = 1;		  // 跟随
		if(Serial_RxData == 0x12) Mode = 2;		  // 蓝牙
		if(Serial_RxData == 0x13) Mode = 3;		  // 避障
		if(Serial_RxData == 0x14) Mode = 4;		  // 寻迹
		if(Serial_RxData == 0x00) Mode = 0;		  // 停止				
	}
}

int main(void)
{
	/*模块初始化*/
	OLED_Init();			// OLED初始化
	Car_Init();				// 小车初始化
	Serial_Init();			// 串口初始化
	Servo_Init();			// 舵机
	Ultrasound_Init();		// 超声波
	Trace_Init();			// 寻迹

	while (1)
	{
		OLED_ShowString(0, 0, "Mode:", OLED_8X16);
		OLED_ShowString(0, 17, "State:", OLED_8X16);
		
		if(Mode == 0)
		{
			Car_Stop();
		}
		if(Mode == 1)
		{
			// 定距离跟随
			OLED_ShowChinese(40, 0, "跟随");
			OLED_ShowString(0, 33, "Distance:", OLED_8X16);
			Servo_setAngle(110);			// 舵机向前
			if(Get_Distance() > 25)
			{
				Car_Ahead();
				OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
				OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
				Delay_ms(100);
			}
			if(Get_Distance() < 20)
			{
				Car_Back();
				OLED_ShowString(48, 17, "go_back  ", OLED_8X16);
				OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
				Delay_ms(100);
			}
			OLED_Update();
		}
		if(Mode == 2)
		{
			// 蓝牙控制小车
			OLED_ShowChinese(40, 0, "蓝牙");
			OLED_ShowString(0, 33, "Speed:", OLED_8X16);
			if(Car_State == 1)	// 前进
			{
				Car_Ahead();
				OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
				OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
				Car_State = 0;
			}
			if(Car_State == 2)	// 后退
			{
				Car_Back();
				OLED_ShowString(48, 17, "go_back  ", OLED_8X16);
				OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
				Car_State = 0;
			}
			if(Car_State == 3)	// 向左
			{
				Turn_Left();
				OLED_ShowString(48, 17, "turn_left", OLED_8X16);
				OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
				Delay_ms(500);
				Car_State = 0;
			}
			if(Car_State == 4)	// 向右
			{
				Turn_Right();
				OLED_ShowString(48, 17, "turn_righ", OLED_8X16);
				OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
				Delay_ms(500);
				Car_State = 0;
			}
			if(Car_State == 5)	// 加速
			{
				OLED_ShowString(48, 17, "speed_up ", OLED_8X16);
				Speed += 10;
				if (Speed > 100)
				{
					Speed = 60;
				}
				OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
				Change_Speed(Speed);
				Car_State = 0;
			}
			if(Car_State == 6)	// 自转
			{
				Self_Left();
				OLED_ShowString(48, 17, "self_left", OLED_8X16);
				OLED_ShowNum(48, 33, Speed, 3, OLED_8X16);
				Car_State = 0;
			}
			OLED_Update();
		}
		if(Mode == 3)
		{
			// 超声波避障
			OLED_ShowChinese(40, 0, "避障");
			OLED_ShowString(0, 33, "Distance:", OLED_8X16);
			Servo_setAngle(110);				// 舵机向前
			if(Get_Distance() > 30)				// 前方无障碍物
			{
				Car_Ahead();
				Delay_ms(100);
				OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
				OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
			}
			else								// 向前有障碍物
			{
				Car_Stop();
				Servo_setAngle(185); 			// 舵机左转
				Delay_ms(200);
				if(Get_Distance() > 30)			// 左侧无障碍物判断
				{
					Turn_Left();
					Delay_ms(1000);
					Delay_ms(700);
					Car_Ahead();
					OLED_ShowString(48, 17, "turn_left", OLED_8X16);
					OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
				}
				else 
				{
					Car_Stop();
					Servo_setAngle(25); 	// 舵机向右
					Delay_ms(200);
					if(Get_Distance() > 30)		// 右侧无障碍物
					{
						Turn_Right();
						Delay_ms(1000);
						Delay_ms(700);
						OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
						OLED_ShowString(48, 17, "turn_righ", OLED_8X16);
						Car_Ahead();
					}
					else
					{
						Car_Back();			// 右侧有障碍物,后退
						Delay_ms(1200);	
						OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
						OLED_ShowString(48, 17, "go_back  ", OLED_8X16);						
						Turn_Left();
						Delay_ms(1000);
						Delay_ms(700);
						OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
						OLED_ShowString(48, 17, "turn_left", OLED_8X16);
						Car_Ahead();
					}
				}
			}
			OLED_Update();
		}
		if(Mode == 4)
		{
			// 红外寻迹
			OLED_ShowChinese(40, 0, "寻迹");
			OLED_ShowString(0, 17, "              ", OLED_8X16);
			OLED_ShowString(0, 33, "              ", OLED_8X16);
			Servo_setAngle(110);			// 舵机向前
			Trace();
			OLED_Update();
		}
		OLED_Update();
	}
}

5.2 小车初始化

5.2.1 Car

Car.h

c 复制代码
#ifndef __CAR_H
#define __CAR_H

void Car_Init(void);
void Car_Ahead(void);
void Car_Back(void);
void Car_Stop(void);
void Turn_Left(void);
void Turn_Right(void);
void Self_Left(void);
void Self_Right(void);

void Change_Speed(int8_t speed);
#endif

Car.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Motor.h"
#include "Delay.h"

void Car_Init(void)
{
	Motor_Init();
}

void Car_Ahead(void)
{
	Motor_SetLeftSpeed(60);
	Motor_SetRightSpeed(60);
}

void Car_Back(void)
{
	Motor_SetLeftSpeed(-60);
	Motor_SetRightSpeed(-60);
}

void Car_Stop(void)
{
	Motor_SetLeftSpeed(0);
	Motor_SetRightSpeed(0);
}

void Turn_Left(void)
{
	Motor_SetLeftSpeed(60);
	Motor_SetRightSpeed(0);
}

void Turn_Right(void)
{
	Motor_SetLeftSpeed(0);
	Motor_SetRightSpeed(60);
}

void Self_Left(void)
{
	Motor_SetLeftSpeed(-60);
	Motor_SetRightSpeed(60);
}

void Self_Right(void)
{
	Motor_SetLeftSpeed(60);
	Motor_SetRightSpeed(-60);
}

void Change_Speed(int8_t speed)
{
	Motor_SetLeftSpeed(speed);
	Motor_SetRightSpeed(speed);
}
5.2.2 Motor

Motor.h

c 复制代码
#ifndef __MOTOR_H
#define __MOTOR_H

void Motor_Init(void);
void Motor_SetLeftSpeed(int8_t speed);
void Motor_SetRightSpeed(int8_t speed);

#endif

Motor.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Motor_Init(void)
{
	// 开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 初始化GPIO(控制电机引脚)
	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);
	
	PWM_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_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
	}
	else{
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		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_ResetBits(GPIOA, GPIO_Pin_6);
		GPIO_ResetBits(GPIOA, GPIO_Pin_7);
	}
	else{
		GPIO_SetBits(GPIOA, GPIO_Pin_7);
		GPIO_ResetBits(GPIOA, GPIO_Pin_6);
		PWM_SetCompare3(-speed);
	}
}
5.2.3 PWM

PWM.h

c 复制代码
#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);

#endif

PWM.c

c 复制代码
#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	// 开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, 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);
	
	// 配置时钟源
	TIM_InternalClockConfig(TIM2);	// 选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	// 初始化时基单元
	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 = 36-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;				// 输出比较模式,选择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); 						// TIM2的通道3
	
	// 使能定时器
	TIM_Cmd(TIM2, ENABLE);
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);		//设置CCR1的值
}

5.3 串口初始化

Serial.h

c 复制代码
#ifndef __SERIAL_H
#define __SERIAL_H

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);

#endif

Serial.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;						// Tx
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;						// Rx
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// USART初始化
	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;			//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);						//将结构体变量交给USART_Init,配置USART1
	
	// 中断输出配置
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	// NVIC中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	// NVIC配置
	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线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	// USART使能
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

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

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
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;
}

5.4 舵机初始化

Servo.h

c 复制代码
#ifndef __SERVO_H
#define __SERVO_H

void Servo_Init(void);
void Servo_setAngle(float angle);

#endif

Servo.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "PWMServo.h"


void Servo_Init(void)
{
	PWM_Servo_Init();
}

void Servo_setAngle(float angle)
{
	PWM_Servo_SetCompare3(angle / 180 * 2000 + 500); //设置占空比
}

PWMServo.h

c 复制代码
#ifndef __PWMSERVO_H
#define __PWMSERVO_H

void PWM_Servo_Init(void);
void PWM_Servo_SetCompare3(uint16_t Compare);

#endif

PWMServo.c

c 复制代码
#include "stm32f10x.h"                  // Device header

void PWM_Servo_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);			//开启GPIOB的时钟
	
	/*GPIO初始化*/
	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);							//将PA0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式		
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输出比较初始化*/
	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(TIM3, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC3Init,配置TIM3的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_Servo_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM3, Compare);		//设置CCR1的值
}

5.5 超声波初始化

Ultrasound.h

c 复制代码
#ifndef __ULTRASOUND_H
#define __ULTRASOUND_H

void Ultrasound_Init(void);
float Get_Distance(void);

#endif

Ultrasound.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "MyTimer.h"

uint8_t flage=0;		//用于记录中断信号是上升沿还是下降沿
uint16_t Timer_cnt;		//记录定时器中断的次数
uint16_t times;			//记录回想信号持续时间

void Ultrasound_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		// 推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;				// Trig
	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_13;				// Echo
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/*AFIO选择中断引脚*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);//将外部中断的13号线映射到GPIOB,即选择PB13为外部中断引脚
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;								//定义结构体变量
	EXTI_InitStructure.EXTI_Line = EXTI_Line13;							//选择配置外部中断的13号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;							//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;					//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;		//指定外部中断线为上升、下降沿触发
	EXTI_Init(&EXTI_InitStructure);	
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	Timer_Init();
}

/*TIM4定时器中断*/
void TIM4_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)		//判断是否是TIM4的更新事件触发的中断
	{
		Timer_cnt++;										//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);			//清除TIM4更新事件的中断标志位
	}
}


/*EXTI15_10外部中断函数*/
void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line13) == SET)		//判断是否是外部中断13号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (flage == 0) 		//上升沿即回响电平开始,打开计数器
		{
			Timer_cnt = 0;						//定时器中断次数置0
			flage = 1;
			TIM_SetCounter(TIM4, 0);				//设置TIM4值为0
            TIM_Cmd(TIM4, ENABLE);					//使能TIM4
		}
		else										//下降沿即回响电平结束,统计高电平持续时长
		{
			TIM_Cmd(TIM4, DISABLE);
			flage = 0;
            times=Timer_cnt * 100 + TIM_GetCounter(TIM4);  //得到回响的高电平持续的us
		}
		EXTI_ClearITPendingBit(EXTI_Line13);		//清除外部中断13号线的中断标志位
	}
}


// 测量距离
float Get_Distance(void)
{
	uint32_t distance = 0;
	for(int i=0; i<5; i++)
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_12);		// Trig引脚拉高保持大于10us以上
		Delay_us(20);
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);	
		Delay_ms(60);                   		//根据说明书,每个周期至少需要等待60ms
		distance += (times/58);          		//根据说明书提供的公式,获取单位为cm的距离
	}
	return distance/5;
}

MyTimer.h

c 复制代码
#ifndef __MYTIMER_H
#define __MYTIMER_H

void Timer_Init(void);

#endif

MyTimer.c

c 复制代码
#include "stm32f10x.h"                  // Device header


void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);			//开启TIM4的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM4);		//选择TIM4为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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(TIM4, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM4的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM4, TIM_FLAG_Update);   //清除定时器更新标志位,TIM_TimeBaseInit函数末尾,手动产生了更新事件
	
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);					//开启TIM4的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2,即抢占优先级和响应优先级范围:0~3
																
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM4, ENABLE);			//使能TIM4,定时器开始运行
}

5.6 寻迹初始化

Trace.h

c 复制代码
#ifndef __TRACE_H
#define __TRACE_H

#define DO1 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)
#define DO2 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define DO3 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)
#define DO4 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15)

void Trace_Init(void);
void Trace(void);

#endif

Trace.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Trace.h"
#include "Car.h"
#include "Delay.h"

void Trace_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_10 | GPIO_Pin_11 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_Initstructure);
}

void Trace(void)
{
	if(DO1==0 && DO2==0 && DO3==0 && DO4==0) 	//0000
	{
		Car_Ahead();
	}
	if(DO1==1 && DO2==1 && DO3==1 && DO4==1) 	//1111
	{
		Car_Stop();
	}
	if(DO1==1 && DO2==0 && DO3==0 && DO4==0) 	//1000
	{
		Turn_Left();
		Delay_ms(300);
	}
	if(DO1==0 && DO2==1 && DO3==0 && DO4==0) 	//0100
	{
		Turn_Left();
		Delay_ms(200);
	}
	if(DO1==1 && DO2==1 && DO3==0 && DO4==0) 	//1100
	{
		Turn_Left();
		Delay_ms(370);
	}
	if(DO1==0 && DO2==0 && DO3==1 && DO4==0) 	//0010
	{
		Turn_Right();
		Delay_ms(150);
	}
	if(DO1==0 && DO2==0 && DO3==0 && DO4==1) 	//0001
	{
		Turn_Right();
		Delay_ms(300);
	}
	if(DO1==0 && DO2==0 && DO3==1 && DO4==1) 	//0011
	{
		Turn_Right();
		Delay_ms(370);
	}
}

6. 说明

备注:江协科技STM32入门教程学完后,基本上可以完成这个小项目

部分代码参考江协科技教程

相关推荐
RIGOL小普24 分钟前
如何用李萨如图形测正弦信号的频率?若不使用李萨如图形,如何用示波器测交流信号频率?
单片机·嵌入式硬件·fpga开发·硬件工程·射频工程
charlie1145141915 小时前
STM32 从0开始系统学习2
stm32·嵌入式硬件·c·1024程序员节·arm架构·寄存器映射
乌南竹12 小时前
STM32外设应用
stm32·单片机·嵌入式硬件
CoderBob12 小时前
芯片固件加密方式
c语言·嵌入式硬件
唯创知音14 小时前
语音提示器-WT3000A离在线TTS方案-打破语种限制/AI对话多功能支持
人工智能·单片机·物联网·智能家居·语音识别
小狗爱吃黄桃罐头15 小时前
江协科技STM32学习- P23 DMA 直接存储器存取
科技·stm32·学习
沐欣工作室_lvyiyi16 小时前
基于STM32的智能寝室控制系统设计(论文+源码)
stm32·单片机·嵌入式硬件·物联网·毕业设计·智能家居
limingade16 小时前
手机实时提取SIM卡打电话的信令声音-新的篇章(三、Android虚拟声卡探索)
android·linux·嵌入式硬件·物联网·智能手机
微笑面对危险17 小时前
STM32之看门狗
stm32·单片机·嵌入式硬件