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