提醒:本文章只叙述此小车相关大概内容(如模块的设置,C语言基础实现等),单片机详细教学不涉及。
摘要
循迹小车是学习单片机的“地基”,它能够让初学者认识单片机内部硬件结构及其功能,熟悉单片机的一些基础操作,如I/O的应用,定时中断与外部中断的应用等,同时也能让初学者对于C语言编程有更深的认识。我采用STM32F103C8T6、TB6612、TCRT5000三个主要模块进行小车组装,刚开始确实有很多问题,随着进一步深入,问题也迎刃而解了,所以我们学习这个小车,主要在于思想的转变和善于去研究,我相信很多过程中遇到的难题都会被我们解决的。
目录
摘要
一、材料选择
二、模块思维导图
三、主要材料概述
1、STM32F103C8T6最小系统板
2、TB6612电机驱动
3、LM2596S DC-DC直流可调降压模块
4、TCRT5000红外循迹模块
5、显示屏模块
三、相关代码
一、材料选择
最近也是参加电赛,所以所有的材料都是运用学校实验室提供的。
- 单片机:STM32F103C8T6;
- 电机驱动:TB6612或者L298(我选用TB6612);
- 降压模块:(可随意)LM2596;
- 循迹模块:TCRT5000(5个);
- 显示屏模块:四针脚I²C版本OLED;
- 四个电机加轮子;
- 杜邦线若干;
- 开关一个;
- 电池(12V)。
二、模块思维导图
注意:本思维导图只针对于代码中各模块部分。
三、主要材料概述
1、STM32F103C8T6最小系统板

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 headerextern 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; //ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //PSCTIM_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; //CCRTIM_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; //CCRTIM_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);}}} }
}
代码中有很多冗余,由于时间紧张所以就没有仔细去改写,各位同学可以根据实际情况去修改内容。
( 另附:想要工程文件的同学可以评论邮箱,内含小车视频哦~转载请标明出处。)
本次分享就到这里了,博主其实也是初学者,所以也非常希望各路大佬来批评指正,当然,如果我的文章能帮到您,请在评论区积极发言(手动狗头),这也是对我最大的鼓舞,谢谢!