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

接中篇

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

上篇主要是讲一些基础的东西,中篇讲了如何制作循迹,本篇讲一下制作避障小车。

七,如何用使用pwm让舵机旋转到相应的角度

这位博主讲的很详细:
stm32 智能避障小车(二)之sg90
我对他其中的一些关键信息再说明一下:
1,接线
橙色信号线  
红色正极 
棕褐色负极
2,舵机控制对pwm的要求
舵机的控制需要一个20ms的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分:
t = 0.5ms——————-舵机会转动 0 °
t = 1.0ms——————-舵机会转动 45°
t = 1.5ms——————-舵机会转动 90°
t = 2.0ms——————-舵机会转动 135°
t = 2.5ms——————-舵机会转动180°

我设置的是在转90度的时候为舵机的正前方,这样就能让舵机左右转了。
好了,经过上面的分析我们可以开始写程序了,思路如下:
利用定时器输出一个占空比可调的pwm,且这个pwm的周期为20ms。

下面看一下我的sg90.c文件:

#include "sg90.h"

void SG90_pwm_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;   
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	/* 开启时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

	/*  配置GPIO的模式和IO口 */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;// PA1
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//TIM3定时器初始化
	TIM_TimeBaseInitStructure.TIM_Period = 199; //PWM 频率=72000/(199+1)=36Khz//设置自动重装载寄存器周期的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7199;//设置用来作为TIMx时钟频率预分频值
	TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;//设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//TIM向上计数模式
	TIM_TimeBaseInit(TIM2, & TIM_TimeBaseInitStructure);


	//PWM初始化	  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//PWM输出使能
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;

	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	//注意此处初始化时TIM_OC1Init而不是TIM_OCInit,否则会出错。因为固件库的版本不一样。
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能或者失能TIMx在CCR1上的预装载寄存器
	TIM_Cmd(TIM2,ENABLE);//使能或者失能TIMx外设
}

下面是sg90.h文件内容:

#ifndef __SG90_H
#define	__SG90_H

#include "stm32f10x.h"
#include "delay.h"
   
#define   SG90_Right_90     TIM_SetCompare2(TIM2, 195)		//右转90度
#define   SG90_Right_45		TIM_SetCompare2(TIM2, 190)		
#define   SG90_Front		TIM_SetCompare2(TIM2, 185)		//舵机摆正
#define   SG90_Left_45		TIM_SetCompare2(TIM2, 180)		//左转45度
#define   SG90_Left_90		TIM_SetCompare2(TIM2, 175)
		
void SG90_pwm_init(void);  //舵机pwm初始化

#endif

我来解释一下舵机代码比较关键的几个参数设置:
1,引脚怎么接?
PA1接舵机橙色线。
2,设置周期为20ms
TIM_TimeBaseInitStructure.TIM_Period = 199;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7199;
通过这两句话,根据周期计算公式:
PWM周期为 (7200*200)/72000000=0.02=20ms
3,设置pwm模式和初始极性
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//初始极性为低
PWM1模式的意思如下:
当计时器值小于比较器设定值时则TIMX输出脚此时输出有效低电位。
当计时器值大于或等于比较器设定值时则TIMX输出脚此时输出高电位。
4,通过TIM_SetCompare2设置占空比
前面已经说了,我所设置的正前方是舵机转90度的时候,下面解释一下怎么通过设置占空比来设置方向吧。比如下面这句函数:

#define   SG90_Front		TIM_SetCompare2(TIM2, 185)		//舵机摆正

由于高电平t = 1.5ms舵机会转动 90°,计数值TIM_Period设置的为200,比较值为185,所以高电平占比为(15/200)x20ms=1.5ms。
类似的要设置其它的高电平占比也是这样设置了。

八,如何配置定时器使用超声波模块测距

1,先来看看舵机和云台是怎么接在一起的?

基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----下篇(第789点)_第1张图片
类似这种,把超声波模块插在云台上,云台的作用其实就是把超声波模块固定在舵机上,再把模块的线引出来而已,舵机的线单独接或者接在云台上都是可以的。

2,超声波模块怎么用?

我是参考的这位博主的介绍:
STM32的超声波测距程序

HC-SR04基本工作原理:
(1)采用IO口TRIG触发测距,给最少10us的高电平信呈。
(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回, 通过IO口ECHO输出一个高电平, 高电平持续的时间就是超声波从发射到返回的时间。 测试距离=(高电平时间*声速(340M/S))/2。

3,程序怎么写?

如果要写测距的话要观察测的是否准确,因为我手里正好有一块OLED屏,我就讲测得的距离放到了屏幕上,就可以直接观察测的距离了,你如果手里没有屏幕也可以用个串口程序讲数值传到电脑上去,只是这个我还没尝试过,这里就不介绍了。
下面贴一下测距文件cs.c里的内容:

#include "cs.h"

#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"

/*记录定时器溢出次数*/
uint overcount=0;

/*设置中断优先级*/
void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructer;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructer.NVIC_IRQChannelSubPriority=0;
	NVIC_InitStructer.NVIC_IRQChannel=TIM4_IRQn;
	NVIC_InitStructer.NVIC_IRQChannelCmd=ENABLE;

	NVIC_Init(&NVIC_InitStructer);
}

/*初始化模块的GPIO以及初始化定时器TIM2*/
void CH_SR04_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructer;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;

	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	/*TRIG触发信号*/
	GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_8;
	GPIO_Init(GPIOB, &GPIO_InitStructer);

	/*ECOH回响信号*/
	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_9;
	GPIO_Init(GPIOB, & GPIO_InitStructer);

	/*定时器TIM2初始化*/
	TIM_DeInit(TIM4);
	TIM_TimeBaseInitStructer.TIM_Period=999;//定时周期为1000
	TIM_TimeBaseInitStructer.TIM_Prescaler=71; //分频系数72
	TIM_TimeBaseInitStructer.TIM_ClockDivision=TIM_CKD_DIV1;//不分频
	TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructer);

	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//开启更新中断
	NVIC_Config();
	TIM_Cmd(TIM4,DISABLE);//关闭定时器使能

}



float Senor_Using(void)
{
	float length=0,sum=0;
	u16 tim;
	uint i=0;
	/*测5次数据计算一次平均值*/
	while(i!=5)
	{
		PBout(8)=1; //拉高信号,作为触发信号
		delay_us(20); //高电平信号超过10us
		PBout(8)=0;
		/*等待回响信号*/
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==RESET);
		TIM_Cmd(TIM4,ENABLE);//回响信号到来,开启定时器计数

		i+=1; //每收到一次回响信号+1,收到5次就计算均值
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET);//回响信号消失
		TIM_Cmd(TIM4,DISABLE);//关闭定时器

		tim=TIM_GetCounter(TIM4);//获取计TIM4数寄存器中的计数值,一边计算回响信号时间

		length=(tim+overcount*1000)/58.0;//通过回响信号计算距离

		sum=length+sum;
		TIM4->CNT=0; //将TIM4计数寄存器的计数值清零
		overcount=0; //中断溢出次数清零
		delay_ms(10);
	}
	length=sum/5;
	return length;//距离作为函数返回值
}



void TIM4_IRQHandler(void) //中断,当回响信号很长是,计数值溢出后重复计数,用中断来保存溢出次数
{
	if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
	 {
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除中断标志
	overcount++;

	 }
}

然后下面是cs.h文件:

#ifndef __CS_H
#define __CS_H 	

#include "stm32f10x.h"
#include "delay.h"

#include "sys.h"
#define uint unsigned int
#define TRIG_Send PBout(8)
#define ECHO_Reci PBin(9)

void CH_SR04_Init(void);  //超声波模块相关配置初始化
float Senor_Using(void);  //测距函数,返回值即为距离
void NVIC_Config(void);   //中断配置


#endif

下面简单介绍怎么使用:
(1):引脚怎么接?
PB8接Trig口,PB9接Echo口。

(2):怎么用?
主函数中调用CH_SR04_Init()这一个函数即可初始化测距的相关配置,
测距直接用调用Senor_Using()这个函数,函数的返回值即为距离。

其它关于代码的设置我就不讲了,注释里已经很详细了,或者去看一下这个博客
STM32的超声波测距程序

(3):使用例子
我用了oled屏幕显示,这段程序在我上传的代码里没有,仅仅用来测试的而已,但是我上传的代码已经将oled初始化的代码在主函数里调用了,所以在主函数也可以直接用oled屏幕。
下面是我的测试程序:

//头文件
#include "stm32f10x.h"
#include "led.h"
#include "moter.h"
#include "xunji.h"  
#include "sg90.h"
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"
#include "cs.h"

 int main(void)
{
	char str[20];          //用来存放浮点数字符
	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)
  {	
     	SG90_Front;   //舵机摆正
        length_res[0] =Senor_Using();  //测前方距离放在数组里
				
		num2char(str,length_res[0],3,3);     //将浮点数转为字符
		OLED_ShowString(44,24,str,16);		
		OLED_ShowString(0,2,"distance is",16);
		OLED_Refresh();	
  
  }	

}

效果是这样的:
基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----下篇(第789点)_第2张图片

好了,测距已经准备就绪了,下面就可以开始写避障函数啦。

九,完成避障小车的制作

这里的任务主要就是编写主函数里的循环了。
设计思路如下:
1,舵机向前摆正,测量正前方的距离,如果距离小于30cm就停下来。
2,停下后,舵机检测左边45度和右边45度的距离,比较这两个距离。
3,假如左边的距离比右边大,就用一个do-while循环,使舵机摆正不断测量前方距离,同时小车缓慢左转,一直转到前方距离大于30cm,小车继续向前,循环继续。
下面是主函数:

int main(void)
{
	char str[20];          //用来存放浮点数字符
	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)
	{	
		
		SG90_Front;   //舵机摆正
		delay_ms(100);
        length_res[0] =Senor_Using();  //测前方距离放在数组里
		delay_ms(100);
		
		if(length_res[0]>30.00)       //如果前方距离大于30cm  前进
			{
			CarGo();
			}	
				
		if(length_res[0]<30.00)     //如果前方距离小于30厘米  停车测左右距离
			{
			CarStop();  
			SG90_Left_45;      //舵机左转45度测距
			delay_ms(700);		
			length_res[1] =Senor_Using();    //把测量结果放进数组
			
            SG90_Right_45;     //舵机右转45度测距
			delay_ms(700);	
            length_res[4] =Senor_Using();     //把测量结果放进数组				
				
			SG90_Front;           //舵机摆正
			delay_ms(100); 
			if(length_res[1]>length_res[4])    //如果左边的距离大于右边的距离
				{
					do                        //舵机摆正
					{
					SG90_Front;
					delay_ms(10);
					length_res[0] =Senor_Using();	//重复测前方的距离同时左转
					delay_ms(10);						
					CarLeft();
					}
					while(length_res[0]<30.00);		//一直转到前方距离大于30cm		
				}
		   if(length_res[1]<length_res[4])    //如果右边的距离大于左边的距离
				{
					do
					{
					SG90_Front;
					delay_ms(10);
					length_res[0] =Senor_Using();  //重复测前方的距离同时右转
					delay_ms(10);						
					CarRight();
					}
					while(length_res[0]<30.00);		//一直转到前方距离大于30cm
				}
				
			}		
	
	}	

}

好了,到此为止,循迹避障小车已经设计完成了。

下面总结一下如何接线的:
电机驱动:
A6----IN1
A7----IN2
B0----IN3
B1----IN4
循迹模块:(从左到右为1234)
B4----第1个循迹模块的D0
B5----第2个循迹模块的D0
B6----第3个循迹模块的D0
B7----第4个循迹模块的D0
舵机:
A1----舵机橙色线
超声波模块:
PB8----Trig
PB9----Echo
oled(iic协议):
A8----SCL
A9----SDA

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

整个完整过程会分为上中下三篇,此篇为下篇,完结。

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

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