基于STM32F103C8T6的智能红外循迹避障小车
一,硬件原理图设计
二,代码实现
1.电机
编程思路:控制核心板IO口的电平(即电机驱动模块的AIN1,AIN2,BIN1,BIN2)从而控制AO1、AO2、BO1、BO2的电平高低,进而控制电机使其正转、反转、不转。由于单纯地拉高拉低电平只能让电机在转与不转的状态切换,故决定利用TIM4的两个通道分别产生两路PWM信号来控制电机速度,四个电机两个一组,同一侧的两个连接在同一个PWM上。(#include "dianji.h"文件中同时写了对循迹模块引脚的初始化)
#ifndef _DIANJI_H
#define _DIANJI_H
#include "sys.h"
#define IN1 PCin(13)
#define IN2 PCin(14)
#define IN3 PCin(15)
#define IN4 PBin(12)
void GPIO_XUJI_Init(void);
void Motor_Init(void);
void PWM_Init(void);
void PWM_SetCompare(int16_t Compare);
void Motor_SetSpeed1(float Speed);
void Motor_SetSpeed2(float Speed);
#endif
#include "dianji.h"
//循迹模块引脚初始化
void GPIO_XUJI_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14| GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//电机引脚初始化
void Motor_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_8 | GPIO_Pin_9| GPIO_Pin_5| GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
PWM_Init();
}
//PWM初始化
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //开启定时器2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIO时钟
/********** PWM引脚的GPIO配置 **********/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //对定时器GPIO设置为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM4);
//TIM4配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //TIM4配置
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(TIM4, &TIM_TimeBaseInitStructure);
//C8T6的主频为72MHZ,设置的PWM频率为 72M/((ARR+1)*(PSC+1))=72M/(100*36)=20KHZ
//TIM4的通道1配置
TIM_OCInitTypeDef TIM_OCInitStructure;
//TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//PWM1模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置极性为高
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM4,TIM_OCPreload_Enable);//使能预装载寄存器
//TIM4的通道2配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//PWM1模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置极性为高
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM4,TIM_OCPreload_Enable);//使能预装载寄存器
TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
//设置占空比
void PWM_SetCompare(int16_t Compare)
{
TIM_SetCompare2(TIM4, Compare);
TIM_SetCompare1(TIM4, Compare);
}
void Motor_SetSpeed1(float Speed) //设置电机1 2 转速,有符号量,正反转
{
if (Speed <0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_8);
GPIO_ResetBits(GPIOB, GPIO_Pin_9);//反转
PWM_SetCompare(-Speed);
}
else if(Speed>0)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
GPIO_SetBits(GPIOB, GPIO_Pin_9);//正转
PWM_SetCompare(Speed);
}
else if(Speed == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_8);
GPIO_SetBits(GPIOB, GPIO_Pin_9);//停止
PWM_SetCompare(100);
}
}
void Motor_SetSpeed2(float Speed) //设置电机3 4 转速,有符号量,正反转
{
if (Speed<0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOB, GPIO_Pin_4);//反转
PWM_SetCompare(-Speed);
}
else if(Speed >0)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOB, GPIO_Pin_4);//正转
PWM_SetCompare(Speed);
}
else if(Speed == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOB, GPIO_Pin_4);//停止
PWM_SetCompare(100);
}
}
2.红外循迹
编程思路:检测到黑线时红外模块将会返回低电平,否则返回高电平。
故只需要读取对应的IO端口电平用以控制小车转向从而实现巡线。(我使用的是四路循迹,中间两路用来检测黑线,两边的两路用来检测非黑线)
循迹逻辑:(黑色路线宽度为:大于P2到P3之间距离,小于P1到P4之间距离)
case1:P2,P3检测到黑线,P1,P4未检测到黑线时,所有电机都正转
case2:P1,P2,P3检测到黑线,P4未检测到黑线时;或者P1,P2检测到黑线,P3,P4未检测 到黑线时;或者P1检测到黑线,P2,P3,P4未检测时;或者P2检测到黑线,P1,P3,P4未检测时到黑线时;以上四种情况皆为 P4侧电机正转,P1侧电机反转,使车身向P1侧转向。
case3:P2,P3,P4检测到黑线,P1未检测到黑线时;或者P3,P4检测到黑线,P1,P2未检测 到黑线时;或者P4检测到黑线,P1,P2,P3未检测时;或者P3检测到黑线,P1,P2,P4未检测时到黑线时;以上四种情况皆为 P4侧电机反转,P1侧电机正转,使车身向P4侧转向。
case4:其他所有情况,皆为所有电机都停止(包括P1,P2,P3,P4都检测到黑线的情况,由于自然光的原因,在车轮未放在地上时, P1,P2,P3,P4都会检测到自然光中黑线,故该情况下所有电机也都停止)
if((!IN1)&&(!IN3)&&IN2&&IN4){
Motor_SetSpeed1(35);
Motor_SetSpeed2(35);
}
else if(((!IN1)&&(!IN3)&&(!IN4)&&(IN2)) || ((!IN3)&&(!IN4)&&(IN1)&&(IN2))||((!IN3)&&(IN1)&&(IN2)&&(IN4)) || ((!IN4)&&(IN1)&&(IN3)&&(IN2))){
Motor_SetSpeed1(30);
Motor_SetSpeed2(-30);//右转
}
else if(((!IN1)&&(!IN3)&&(!IN2)&&(IN4)) || ((!IN1)&&(IN3)&&(!IN2)&&(IN4))||((!IN1)&&(IN3)&&(IN2)&&(IN4)) || ((IN1)&&(IN3)&&(!IN2)&&(IN4))){
Motor_SetSpeed1(-30);
Motor_SetSpeed2(30);//左转
}
else{
Motor_SetSpeed1(0);
Motor_SetSpeed2(0);
}
3.避障
编程思路:红外避障模块检测到障碍时,会返回一个低电平。通过判断IO口的电平继而控制蜂鸣器。
#ifndef _BEEP_H
#define _BEEP_H
#include "sys.h"
void Beep_Init(void);
#endif
#include "beep.h"
void Beep_Init(void){
//GPIO 避障端口设置
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIO 蜂鸣器端口设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PAout (0)=1;
}
if(PAin(4)==0||PAin(6)==0)
PAout (0)=0;
else
PAout (0)=1;
4.main函数
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "dianji.h"
#include "beep.h"
int main(void)
{
delay_init();
uart_init(9600);
Beep_Init();
GPIO_XUJI_Init();
Motor_Init();
while(1)
{
if(PAin(4)==0||PAin(6)==0)
PAout (0)=0;
else
PAout (0)=1;
if((!IN1)&&(!IN3)&&IN2&&IN4){
Motor_SetSpeed1(35);
Motor_SetSpeed2(35);
}
else if(((!IN1)&&(!IN3)&&(!IN4)&&(IN2)) || ((!IN3)&&(!IN4)&&(IN1)&&(IN2))||((!IN3)&&(IN1)&&(IN2)&&(IN4)) || ((!IN4)&&(IN1)&&(IN3)&&(IN2))){
Motor_SetSpeed1(30);
Motor_SetSpeed2(-30);//右转
}
else if(((!IN1)&&(!IN3)&&(!IN2)&&(IN4)) || ((!IN1)&&(IN3)&&(!IN2)&&(IN4))||((!IN1)&&(IN3)&&(IN2)&&(IN4)) || ((IN1)&&(IN3)&&(!IN2)&&(IN4))){
Motor_SetSpeed1(-30);
Motor_SetSpeed2(30);//左转
}
else{
Motor_SetSpeed1(0);
Motor_SetSpeed2(0);
}
}
}
三,TB6612FNG 驱动模块
TB6612FNG 模块相对于传统的 L298N 效率上提高很多,体积上也大幅度减少,在额定范围内,芯片基本不发热,当然也就显得更加娇贵,所以我们建议有一定动手能力的朋友使用,接线的时候务必 细心细心再细心,注意正负极性。
1.TB6612 的的用法:
TB6612 是双驱动,也就是可以驱动两个电机
下面分别是控制两个电机的 IO 口
STBY 口接单片机的 IO 口清零电机全部停止,置 1 通过 AIN1 AIN2,BIN1,BIN2 来控
制正反转
VM 接 12V 以内电源
VCC 接 5V 电源
GND 接电源负极
驱动 1 路
PWMA 接单片机的 PWM 口
真值表:
AIN1 0 0 1
AIN2 0 1 0
停止 正传 反转
A01
AO2 接电机 1 的两个脚驱动 2 路
PWMB 接单片机的 PWM 口
真值表:
BIN1 0 0 1
BIN2 0 1 0
停止 正传 反转
B01
B02 接电机 2 的两个脚驱动 2 路
2.逻辑真值表
3.占空比
PWM 占空比大小的改变通过对输出比较寄存器 TIMx_
CCR 以及自动重装载寄存器TIMx_ARR
的数值操作来实现,例如当 CCR=203 ,ARR=255 时,占空比为 204/256=80%。编程时将速度变量值写入 CCR寄存器,从而达到改变占空比和对电机调速的目的。
3) 运行性能和建议
1.器件输出状
态在驱动/制动之间切换时,电机转速和 PWM 占空比之
间能保持较好的线性关系,其运行控制效果好于器件在驱动/停止状态之间
切换,所以表 1 中的 INl/IN2 一般不采用 L/L 控制组合。
2.fPWM 较高时,电机运行连续平稳、噪音小,但器件功耗会随频率升
高而增大;fPWM 较低时,利于降低功耗,并能提高调速线性度,但过低的
频率可能导致电机转动连贯性的降低。通常 fPWM>1 kHz 时,器件能够稳定
的控制电机。
3.过大的 PWM 占空比会影响电机驱动电流的稳定性和器件的输出负载
能力,应根据不同的速度要求合理设定占空比范围。
4.器件工作温度过高会导致其输出功率的下降,电路 PCB 设计中应保
证足够面积的覆铜,这样有助于散热,利于器件长时间稳定工作
。
四,结果演示
循迹避障小车
作者:Mr,Right