基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----中篇(第456点)

接上篇

链接:基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----上篇(第123点)

其实上篇就是一些基础的东西,本篇讲一下如何让小车循迹

四,如何通过pwm调整电机的转速(调节小车的快慢)

1,为何pwm能调速度?

我的理解是,对于直流电机来讲,一上高平电机就会转动,如果改变了高电平占有的比例,就是让电机在单位时间内接到的高电平时间减少,它的速度就会减小。
注意这里的pwm是由单片机输出到电机驱动的IN1-IN4端的,所以我们要进行软件设置,以输出想要的高电平,这里主要就是软件方面的事啦。

2,如何连接stm32和电机驱动?

stm32没有单独的pwm模块,它是利用定时器来输出pwm的,每个定时器有4个通道,我们需要找到定时器这4个通道所接的引脚,再将这4个引脚接到电机驱动的输入端上就好了。先简单回顾一下stm32定时器的相关知识。首先我查看了stm3210x系列的技术手册
基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----中篇(第456点)_第1张图片
可以看到的是,我们手上的这块STM32F103C8T6有3个通用定时器(我的工程中把这三个定时器都用完了),再往下,每个定时器有4个通道,我们需要找到这4个通道分别对应的引脚。比如我给电机所使用的定时器为TIM3,查看数据手册对引脚的定义后发现如下:
基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----中篇(第456点)_第2张图片
所以定时器3的通道1-4分别对应的是PA6,PA7,PB0,PB1,我将这4个引脚分别接到电机驱动的IN1-IN4,如此一来,关于电机调速的硬件电路我们已经搭好了。

3,如何设置定时器输出4路可调pwm?

这是最关键的地方了,先贴一下我的电机设置的代码吧,下面是moter.c程序

#include "moter.h"

void CarGo(void)
{
  TIM_SetCompare1(TIM3 , 400);  //数值越大速度越慢
  TIM_SetCompare2(TIM3 , 900);
  TIM_SetCompare3(TIM3 , 400);  
  TIM_SetCompare4(TIM3 , 900);	
}

void CarStop(void)
{
  TIM_SetCompare1(TIM3 , 900);
  TIM_SetCompare2(TIM3 , 900);
  TIM_SetCompare3(TIM3 , 900);	
  TIM_SetCompare4(TIM3 , 900);
}

void CarBack(void)
{
  TIM_SetCompare1(TIM3 , 900);
  TIM_SetCompare2(TIM3 , 300);
  TIM_SetCompare3(TIM3 , 900);	
  TIM_SetCompare4(TIM3 , 300);
}

void CarLeft(void)
{
  TIM_SetCompare1(TIM3 , 900);
  TIM_SetCompare2(TIM3 , 300);
  TIM_SetCompare3(TIM3 , 300);
  TIM_SetCompare4(TIM3 , 900);
}

void CarBigLeft(void)
{
  TIM_SetCompare1(TIM3 , 900);
  TIM_SetCompare2(TIM3 , 100);
  TIM_SetCompare3(TIM3 , 100);
  TIM_SetCompare4(TIM3 , 900);
}

void CarRight(void)
{
  TIM_SetCompare1(TIM3 , 300);
  TIM_SetCompare2(TIM3 , 900);
  TIM_SetCompare3(TIM3 , 900);
  TIM_SetCompare4(TIM3 , 300);
  
}

void CarBigRight(void)
{
  TIM_SetCompare1(TIM3 , 100);
  TIM_SetCompare2(TIM3 , 900);
  TIM_SetCompare3(TIM3 , 900);
  TIM_SetCompare4(TIM3 , 100);
  
}


void TIM3_PWM_Init(void)  //TIM3的pwm设置和相应的引脚设置
{
  GPIO_InitTypeDef GPIO_InitStructure;

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure; 
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
  
  
  TIM_TimeBaseStructure.TIM_Period = 899;
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseInit(TIM3 , &TIM_TimeBaseStructure);
  
  //端口复用
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;   //初始化要用的A6/A7口
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 	
  GPIO_Init(GPIOA, &GPIO_InitStructure);   
  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;   //初始化要用的B0/B1口
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 	
  GPIO_Init(GPIOB, &GPIO_InitStructure);   
  
  //PWM通道1
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  TIM_OCInitStructure.TIM_Pulse = 900;
  TIM_OC1Init(TIM3 , &TIM_OCInitStructure);
  TIM_OC1PreloadConfig(TIM3 , TIM_OCPreload_Enable);
  
  //PWM通道2
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  TIM_OCInitStructure.TIM_Pulse = 900;
  TIM_OC2Init(TIM3 , &TIM_OCInitStructure);
  TIM_OC2PreloadConfig(TIM3 , TIM_OCPreload_Enable);
  
  //PWM通道3
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  TIM_OCInitStructure.TIM_Pulse = 900;
  TIM_OC3Init(TIM3 , &TIM_OCInitStructure);
  TIM_OC3PreloadConfig(TIM3 , TIM_OCPreload_Enable);
  
  //PWM通道4
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  TIM_OCInitStructure.TIM_Pulse = 900;
  TIM_OC4Init(TIM3 , &TIM_OCInitStructure);
  TIM_OC4PreloadConfig(TIM3 , TIM_OCPreload_Enable);
  
  TIM_Cmd(TIM3 , ENABLE);
}

下面是moter.h文件内容

#ifndef __MOTER_H
#define	__MOTER_H


#include "stm32f10x.h"


void TIM3_PWM_Init(void);
void CarGo(void);
void CarStop(void);
void CarBack(void);
void CarLeft(void);
void CarBigLeft(void);  //大右转
void CarRight(void);
void CarBigRight(void);   //大左转

#endif

这些代码也很好用,在main函数前调用TIM3_PWM_Init()这一个函数就可以完成引脚和定时器的初始化了,在后面直接用前进后退转弯这些函数就可以了。

下面介绍一下这个函数的一些关键参数:

	TIM_TimeBaseStructure.TIM_Period = 899;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	
	根据定时器的周期计算公式:周期=(arr+1*(psc+1/CLK=900/72000000,
	所以就是设置定时器基准频率为80KHZ

再看每个pwm口的设置基本是一样的,主要参数是这几个:

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_Pulse = 900;//脉宽设置

1,TIM_OCMode_PWM2的意思是什么?
当计时器值小于比较器设定值时则TIMX输出脚此时输出有效低电位。
当计时器值大于或等于比较器设定值时则TIMX输出脚此时输出高电位。
2,TIM_OCPolarity_High表示起始波形为高电位
3,TIM_Pulse = 900表示总的脉宽值为900
由于计数器的值为899+1,则此时的占空比是1,我们通过下面这个SetCompare函数再来设置比较值从而设置占空比。
4,最后我们用TIM_SetCompare1(TIM3 , 400)这个函数来设置比较值
比如说,我们这里设置比较值为400,总脉宽为900,初始电位为高电平,且设置的是PWM2模式,那么在0-400里为低电平,在400-900里为高电平。也就是说,设置的比较值越大,高电平占有的时间越短。
比如我的前进函数:

void CarGo(void)
{
  TIM_SetCompare1(TIM3 , 400);  //数值越大速度越慢
  TIM_SetCompare2(TIM3 , 900);
  TIM_SetCompare3(TIM3 , 400);  
  TIM_SetCompare4(TIM3 , 900);	
}

这样的设置就相当于是将CH1和CH3通道的pwm高电平设置在400-900这个时间里,将CH2和CH4这两个通道拉低,这样一来,改变通道1和通道3的比较值就可以改变电机的转速了。
好了 电机调速我们已经设置完成了。

五,如何设置IO口读取循迹模块的信号(检测黑线)

这个比较简单,就是读取io口的高低电平。
我们先看一下TCRT5000循迹模块怎么用,
基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----中篇(第456点)_第3张图片
它有4个IO接口,我们只需要用3个即可,就是VCC,GND,D0其中D0就是用来返回信号的,它有两个状态,就是高电平和低电平。
就我用的模块来讲,正常情况下,D0返回低电平,当模块检测到黑线的时候,返回高电平,以此,我们就可以进行相关的程序编写了。
下面是我的xunji.c文件内容:

#include "xunji.h"

	
	
void xunji_config(void)	
{
  GPIO_InitTypeDef GPIO_InitStructure;	
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口时钟
	
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5  | GPIO_Pin_6 | GPIO_Pin_7;	//选择对应的引脚
  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.h文件内容:

#ifndef __XUNJI_H
#define	__XUNJI_H

#include "stm32f10x.h"

#define xunji_1 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_4)
#define xunji_2 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)
#define xunji_3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)
#define xunji_4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)

void xunji_config(void);
void Read_xunji_Date(void);  //读循迹模块返回的值

#endif

可以看到,我所用的是引脚B4,B5,B6,B7,这4个引脚分别接到4个循迹模块的D0口上,这里将它们初始化,再调用GPIO_ReadInputDataBit读取引脚电平即可。

用法:在主函数中先调用xunji_config()进行引脚初始化,在循环的最前面调用Read_xunji_Date(void)函数读取4个循迹模块的电平值后就可以进行相关的循迹程序编写了。

六,完成循迹小车的制作(编写循迹主函数)

前面已经完成了调速和循迹模块函数的编写,这里可以直接编写主函数了:

 int main(void)
{
	float  length_res[5];  //用来存放测距结果
	
	SystemInit();	// 配置系统时钟为72M 
    delay_init();    //延时初始化		
	xunji_config();   //循迹初始化
	TIM3_PWM_Init();	//电机pwm   TIM3
	SG90_pwm_init();  //舵机pwm	    TIM2
	CH_SR04_Init();  //超声波定时器    TIM4
	OLED_Init();    //oled显示初始化
	
	 while(1)
  {	
     Read_xunji_Date(); //读循迹线值
	  
	  //车前4个循迹模块从左到右分别是xunji_1,xunji_2,xunji_3,xunji_4
   
	 if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==0)//0000
	  {
	  CarGo();		  //如果都没有读取到黑线,直走
	  delay_ms(10);
	  }
	 if(xunji_1==0&&xunji_2==1&&xunji_3==1&&xunji_4==0)//0110
	  {
	  CarGo();        //如果中间两个读取到黑线,直走
	  delay_ms(10);
	  }	  
	 if(xunji_1==0&&xunji_2==1&&xunji_3==0&&xunji_4==0)//0100
	  {
	  CarBigLeft();   //如果第二个读取到黑线,左转
	  delay_ms(10);
	  }
 	 if(xunji_1==0&&xunji_2==0&&xunji_3==1&&xunji_4==0)//0010
	  {
	  CarBigRight();   //如果第三个读取到黑线,右转
	  delay_ms(10); 
	  }
	  if(xunji_1==1&&xunji_2==0&&xunji_3==0&&xunji_4==0)//1000
	  {
	  CarBigLeft();   //如果第一个读取到黑线,左转
	  delay_ms(10);
	  }
	  if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==1)//0001
	  {
	  CarBigRight();   //如果第四个读取到黑线,左转
	  delay_ms(10); 
	  }
  
  }	

}

主函数前面是一些初始化的配置,while(1)里就是循迹的循环了,代码里我已经写了很多注释了,这里就不再多解释了,就是一堆if函数,也很容易看懂。
本篇就到此为止了。

下面是我写的已经测试完成了的工程,主函数里有两个循环,分别是循迹和避障循环,他们是单独工作的,引用掉一个再打开另一个就行。
https://download.csdn.net/download/weixin_43924857/11650617
当然循迹和避障的策略都是我自己为了完成任务写的,比较简单,能够实现循迹避障功能,到后面你已经会操作各个模块后,自己写个更好的循迹避障策略是完全没有问题的,或者是用我的工程,里面的各模块函数也都写好了,直接调用就行。

整个完整过程会分为上中下三篇,还剩一篇(下篇)过两天慢慢码完再发。

下面是下篇的地址:

基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----下篇(第789点)

工程文件已经放到百度网盘:
链接:https://pan.baidu.com/s/1VSRC418Tz8uLCF8cjrIY1g
提取码:7m9y

你可能感兴趣的:(stm32学习)