目录
一、前言
学习的过程是先易后难的,所以我先为大家讲解普通的死程序去循迹,后面再讲PID控制的循迹。好了,废话少说,直接来程序思路。
首先,我们要知道,让小车向前跑,但是黑线是弯的,这个时候就需要去转向,比如黑线在小车的左侧,我们需要左边加一个传感器,检测到左边的黑线,传给小车一个信号,让小车左转,顺着黑线走;同理黑线在右边,我们用上述一样的方法;所以总结一下,给小车多加几个传感器,反馈黑线位置,让小车知道怎么转。
具体的要结合程序,大家就一目了然了,在这里先跟大家说几个点:
1,注意别买错红外传感器,本人上过太多的当了,希望大家别和我一样,少花一些冤枉钱,最重要的是少浪费一些时间。
如果大家是买单个的,不要买这类这个模块完全没用,就买这个样子的好一点
,但是这种单个的红外传感器,检测效果差,而且只能识别黑线,不同的环境,检测效果不一样,要自己去调灵敏度,相当的麻烦,如果有小伙伴是应付一下期末检测,那这个也勉勉强强。
2,如果大家是买多路的合在一起的那种,我不建议大家去买这类
虽然很便宜只要10块左右,但是一点用都没有,完全没有效果,还不如用单个的呢,如果大家是做着玩玩,就可以买亚博的,但是大家要注意,亚博的传感器,他的位置跟普通的多路传感器的位置不一样,而且市面上大部分你买到的红外传感器,检测到黑线都是给高电平,而亚博是给低电平,这一点是值得注意的。
如果是要搞竞赛的小伙伴们,就用这种灰度传感器,灵敏度自调,且不仅检测黑线,还检测红线。
3,大家注意在定义管脚的时候,有些管脚是不能直接用的,需要修改部分程序。就比如STM32F103C8T6的PC3大家查了就知道它是电源指示灯的管脚。
4,循迹模块的路数越多,越准越稳。
二、代码
xunji.h
#ifndef __XUNJI_H
#define __XUNJI_H
//这里的xunji_x就是代表读取到相应管脚的高低电平
#define xunji_1 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)
#define xunji_2 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)
#define xunji_3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)
#define xunji_4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)
void xunji_config(void);
void Read_xunji_Date(void); //读循迹模块返回的值
#endif
xunji.c
#include "stm32f10x.h" // Device header
#include "xunji.h"
#include "Motor.h"
void xunji_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; //选择对应的引脚
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU ;//配置GPIO模式,输入上拉,这里是检测的作用,所以用上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PC端口
}
void Read_xunji_Date(void)
{
xunji_1;
xunji_2;
xunji_3;
xunji_4;
//红外检测到黑线,令xunji_x=1,意思就是红外检测到黑线发送高电平
if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==0)//没有检测到黑线
{
CarGo();//小车直行
}
if(xunji_1==1&&xunji_2==0&&xunji_3==0&&xunji_4==0)//最左边检测到黑线
{
CarBigRight(); //左转大
}
if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==1)//最右边检测到黑线
{
CarBigLeft(); //右转大
}
if(xunji_1==0&&xunji_2==1&&xunji_3==0&&xunji_4==0)//依次类推,不过多赘述
{
CarGo();//小车直行
}
if(xunji_1==0&&xunji_2==1&&xunji_3==1&&xunji_4==0)
{
CarGo();//小车直行
}
if(xunji_1==0&&xunji_2==0&&xunji_3==1&&xunji_4==0)
{
CarGo();//小车直行
}
if(xunji_1==1&&xunji_2==1&&xunji_3==0&&xunji_4==0)
{
CarRight(); //左转小
}
if(xunji_1==0&&xunji_2==0&&xunji_3==1&&xunji_4==1)
{
CarLeft(); //右转小
}
}
pwm.c
#include "stm32f10x.h" // Device header
//这里设置的ARR,PSC就是让PWM最大为100,更直观,方便的使用
void PWM_Init(void)
{
// 用结构体初始化输出比较单元,不同函数不同的GPIO(A0)
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO的时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//部分重映射
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//关闭调试端口的复用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
GPIO_Initstructure.GPIO_Mode= GPIO_Mode_AF_PP;//复用推挽输出
GPIO_Initstructure.GPIO_Pin= GPIO_Pin_1;
GPIO_Initstructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
TIM_InternalClockConfig(TIM2);
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=720 - 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;//输出比较模式
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_OC3PreloadConfig(TIM2 , TIM_OCPreload_Enable);
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare3(uint16_t Compare3)
{
TIM_SetCompare3(TIM2,Compare3);
}
void PWM_SetCompare2(uint16_t Compare2)
{
TIM_SetCompare2(TIM2,Compare2);
}
Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.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;
GPIO_Initstructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
GPIO_SetBits(GPIOA,GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
PWM_Init();
}
//如果你们想让小车倒着走,只需要将GPIO_SetBits与 GPIO_ResetBits交换一下就可以了
void CarGo(void)//小车直行函数
{
TIM_SetCompare2(TIM2 , 60);//数值越小速度越慢
TIM_SetCompare3(TIM2 , 60);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
void CarBigLeft(void)//小车右转大函数
{
TIM_SetCompare2(TIM2 ,0);
TIM_SetCompare3(TIM2, 60);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
void CarBigRight(void)//小车左转大函数
{
TIM_SetCompare2(TIM2, 60);
TIM_SetCompare3(TIM2 , 0);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
void CarLeft(void)//小车右转小函数
{
TIM_SetCompare2(TIM2 , 40);
TIM_SetCompare3(TIM2, 60);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
void CarRight(void)//小车左转小函数
{
TIM_SetCompare2(TIM2, 60);
TIM_SetCompare3(TIM2 , 40);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
main.c
//提高PWM的频率,让电机在堵转的时候无声,人耳听到的频率在20Hz-20kHz
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "xunji.h"
int main(void)
{
SystemInit(); //开启系统时钟
xunji_config();//循迹管脚的初始化
OLED_Init();
Motor_Init();
while (1)
{
Read_xunji_Date(); //读循迹线值
//车前4个循迹模块从左到右分别是xunji_1,xunji_2,xunji_3,xunji_4
}
}
三、总结
说实话,我当时就是自己一个人慢慢摸索,网上查资料,当时觉得好难啊,确确实实很累,没有人指导自己,但是我不断地坚持学习,我克服了许多困难,现在我觉得这个循迹再简单不过,虽然我谈不上有什么多大的进步,但是我一直在成长,看着自己比昨天更厉害,这种感觉真的很好,相信大家也会学有所成!
其实,我还发现一个小BUG,就是有时候电脑没办法打开百度网盘的链接,但是手机可以,这就很神奇。
链接:https://pan.baidu.com/s/1JxuH_H6eXbQ0hxsRKBB83g?pwd=yl66
提取码:yl66