提醒:本文章只叙述此小车相关大概内容(如模块的设置,C语言基础实现等),单片机详细教学不涉及。

摘要

        循迹小车是学习单片机的“地基”,它能够让初学者认识单片机内部硬件结构及其功能,熟悉单片机的一些基础操作,如I/O的应用,定时中断与外部中断的应用等,同时也能让初学者对于C语言编程有更深的认识。我采用STM32F103C8T6、TB6612、TCRT5000三个主要模块进行小车组装,刚开始确实有很多问题,随着进一步深入,问题也迎刃而解了,所以我们学习这个小车,主要在于思想的转变和善于去研究,我相信很多过程中遇到的难题都会被我们解决的。

目录

摘要

一、材料选择

二、模块思维导图

三、主要材料概述

1、STM32F103C8T6最小系统板

2、TB6612电机驱动

3、LM2596S DC-DC直流可调降压模块

 4、TCRT5000红外循迹模块

5、显示屏模块

三、相关代码


一、材料选择

        最近也是参加电赛,所以所有的材料都是运用学校实验室提供的。

  1. 单片机:STM32F103C8T6;
  2. 电机驱动:TB6612或者L298(我选用TB6612);
  3. 降压模块:(可随意)LM2596;
  4. 循迹模块:TCRT5000(5个);
  5. 显示屏模块:四针脚I²C版本OLED;
  6. 四个电机加轮子;
  7. 杜邦线若干;
  8. 开关一个;
  9. 电池(12V)。

二、模块思维导图

    

         注意:本思维导图只针对于代码中各模块部分。

三、主要材料概述

1、STM32F103C8T6最小系统板

        STM32是一个微控制器产品系列的总称,目前这个系列中已经包含了多个子系列,分别是: STM32 小容量产品、STM32 中容量产品、 STM32 大容量产品和 STM32 互联型产品;按照功能上的划分,又可分为STM32F101xx、 STM32F102xx STM32F103xx 系列。
        STM32F103 C8T6引脚定义图:
        注意:
        (1)单片机有些引脚对应功能是特有的,如定时器的通道输出口,它们在使用的时候只需要初始化I/O即可;
        (2)单片机最大承受电压为5V,注意通电之前一定要查看降压模块的连接是否正确或者是否有降压模块。

2、TB6612电机驱动

        TB6612电机驱动共有16个个引脚。

        VM最大接15V电源,本博客接12V足以;

        VCC接3.3V或5V;

        GND不用说了吧,接地就行;

        PWMA、PWMB需要PWM波(方波),以控制A电机或B电机的速度,连接单片机时要注意PWM波输出的端口对应好,本文采用的是定时器TIM2的3、4通道,所以PWMA、PWMB分别连接PA2、PA3口,具体要参考你用的哪个定时器,然后根据引脚定义图对应好引脚位置;

        AIN1、AIN2、BIN1、BIN2用来控制电机的正反转,需要连接单片机的I/O口来给予它们高低电平,AIN控制A电机,BIN控制B电机,具体控制如下表(以控制A电机为例):

AIN1 0 1 0
AIN2 0 0 1
停止 正转 反转

        AO1、A02、BO1、BO2可以驱动电机,所以直接连接电机来让它们转起来!(注意别连反了,不然你的电机总是逆天的反转或者转圈~)

        其实对于电机驱动模块最麻烦的就是PWM波的输入以及脉宽调制,具体体现在代码中。

3、LM2596S DC-DC直流可调降压模块

        因为在电路中有许多器件不能承受12V电压,所以我们需要将电源电压降到5V或者3.3V,这时我们就要用到降压模块,具体连接方法很明显,以LM2596为例,IN+连接电源正极,IN-连接电源负极,OUT+输出3.3V或者5V电压,OUT-输出GND。

 4、TCRT5000红外循迹模块

        相较于其它循迹方法,红外循迹较为简单,但也无法做到十分精准,本文以红外循迹为例,讲述循迹方法,希望各位同学能举一反三,继续学习!

        

       它有四个引脚:VCC(接3~5V电压)、GND(接地)、D0(接单片机I/O口)、A0(模拟信号输出,一般不接)

        话不多说,直接讲原理:当检测到黑线时,传感器上的指示灯灭掉,D0输出高电平返回到单片机上;当未检测到黑线时,传感器上的指示灯亮,D0输出低电平返回到单片机上。

5、显示屏模块


三、相关代码

motor.c

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

//注意:增加某个函数要在对应.h文件中声明,否则会报错。

void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;	//定义I/O口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	PWM_Init();
}

void Motor_LEFT_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);
	}
}
void Motor_RIGHT_SetSpeed(int8_t Speed)		//右电机正反转
{
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_6);
		GPIO_ResetBits(GPIOA, GPIO_Pin_7);
		PWM_SetCompare4(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_6);
		GPIO_SetBits(GPIOA, GPIO_Pin_7);
		PWM_SetCompare4(-Speed);
	}
}

pwm.c

#include "stm32f10x.h"                  // Device header

extern uint16_t Num;			//调用.c文件定义的Num变量
extern uint16_t t;
extern int FLAG;
void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	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_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
	
	
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
	
	TIM_OC4Init(TIM2, &TIM_OCInitStructure);
	
	
  
	TIM_Cmd(TIM2, ENABLE);
}
void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		Num ++;
		if(FLAG == 3)
		{
			t = Num;
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);
}

void PWM_SetCompare4(uint16_t Compare)
{
	TIM_SetCompare4(TIM2, Compare);
}

main.c

#include "stm32f10x.h"
#include "delay.h"
#include "motor.h"
#include "tcrt5000.h"
#include "OLED.h"
#include "pwm.h"

uint16_t t;
uint16_t Num;
uint16_t LEFT1,RIGHT1,LEFT2,RIGHT2,MIDDLE;
int FLAG = 0;
int main(void)
{	
	Timer_Init();	//初始化计时函数
	OLED_Init();	//初始化显示屏函数
	Motor_Init();	//初始化电机驱动函数
	tcrt5000_init();	//初始化红外循迹函数
    while (1)
    {
		OLED_ShowString(2, 1, "TIME:");
		OLED_ShowNum(2, 6, Num, 5);
		RIGHT1 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5);
		LEFT1 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6);
		RIGHT2 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);
		LEFT2 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8);
		MIDDLE = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9);
		if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)				//直走
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(65);
		}

		else if((LEFT1 == 1 || LEFT2 == 1) && MIDDLE == 0 && RIGHT2 == 0 && RIGHT1 == 0)		//左转
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}

		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 0 && (RIGHT1 == 1 || RIGHT2 == 1))		//右转
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}

		else if(LEFT1 == 0 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)			//左转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}
		
		else if(LEFT1 == 1 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)			//左转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 0)			//右转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 1)			//右转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}
		
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 0 && RIGHT2 == 0 && RIGHT1 == 0)			//未探测到黑线的时候直走(防止未检测到黑线时不动)
		{	
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(65);
		}

		else if(LEFT1 == 1 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 1)			//检测到横线
		{	
			if(FLAG == 0)							//标志位1(停止5秒后前进)
			{
				Motor_LEFT_SetSpeed(0);
				Motor_RIGHT_SetSpeed(0);
				delay_init();
				OLED_Clear();
				OLED_ShowString(1,7,"READY");
				OLED_ShowNum(3,1,5,1);
				delay_ms(1000);
				OLED_ShowNum(3,3,4,1);
				delay_ms(1000);
				OLED_ShowNum(3,5,3,1);
				delay_ms(1000);
				OLED_ShowNum(3,7,2,1);
				delay_ms(1000);
				OLED_Clear();
				OLED_ShowString(3,8,"GO!");
				delay_ms(1000);
				OLED_Clear();
				Motor_LEFT_SetSpeed(30);
				delay_ms(100);
				Motor_RIGHT_SetSpeed(30);
				delay_ms(100);
				FLAG ++;
			}
			else if(FLAG == 1)					//标志位2(停止5秒后前进)
			{
				Motor_LEFT_SetSpeed(0);
				Motor_RIGHT_SetSpeed(0);
				delay_init();
				OLED_Clear();
				OLED_ShowString(1,7,"READY");
				OLED_ShowNum(3,1,5,1);
				delay_ms(1000);
				OLED_ShowNum(3,3,4,1);
				delay_ms(1000);
				OLED_ShowNum(3,5,3,1);
				delay_ms(1000);
				OLED_ShowNum(3,7,2,1);
				delay_ms(1000);
				OLED_Clear();
				OLED_ShowString(3,8,"GO!");
				delay_ms(1000);
				OLED_Clear();
				Motor_LEFT_SetSpeed(80);
				delay_ms(500);
				Motor_RIGHT_SetSpeed(80);
				delay_ms(500);
				FLAG ++;
			}
			else if(FLAG == 2)					//标志位3(直走)
			{
				delay_init();
				OLED_ShowString(1,5,"STRINGHT");
				OLED_ShowString(2, 1, "TIME:");
				OLED_ShowNum(2, 6, Num, 5);
				Motor_LEFT_SetSpeed(85);
				Motor_RIGHT_SetSpeed(85);
				delay_ms(500);
				OLED_Clear();
				FLAG ++;
			}
			else if(FLAG == 3)					//标志位4(比赛结束,停止)
			{	
				FLAG = 0;
				int i;
				for(i = 0;i >= 0;i++)
				{
					OLED_ShowString(1,5,"STOP");
					OLED_ShowString(2, 1, "TIME:");
					OLED_ShowNum(2, 6, t, 5);
					Motor_LEFT_SetSpeed(0);
					Motor_RIGHT_SetSpeed(0);
				}
			}
		}	
    }
}

        代码中有很多冗余,由于时间紧张所以就没有仔细去改写,各位同学可以根据实际情况去修改内容。

       ( 另附:想要工程文件的同学可以评论邮箱,内含小车视频哦~转载请标明出处。)


        本次分享就到这里了,博主其实也是初学者,所以也非常希望各路大佬来批评指正,当然,如果我的文章能帮到您,请在评论区积极发言(手动狗头),这也是对我最大的鼓舞,谢谢!

转自:https://blog.csdn.net/m0_64945147/article/details/130640070