【STM32---传感器】基于状态机机制实现多路超声波传感器数据采集及异常检测

一、超声波传感器介绍

1.1 测距原理

网络图

(1)采用IO口TRIG触发测距,给至少10us的高电平信号;
(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间,发射点距离障碍物的距离S为:
S=v×∆t/2
其中,v为超声波在空气中的传播速度,∆t为计时器记录的测出发射和接收回波的时间差

1.2 超声波数据测量注意事项

超声波传感器缺点:
1、超声波传播速度易受温度和风向等环境因素干扰;
2、超声波很有可能被吸音材料(如毛毯、毛衣、多孔材料等)吸收,造成测距不稳定;
3、传感器可能受外部噪音(干扰源与传感器有同样的频率)或相同传感器干扰;
4、超声波传感器检测与自身斜角较大的物体可能出现检测不到的情况;
5、前方有平滑物体(如图书封面,镜面,墙体等)与传感器夹角大于45度(非垂直反射)时误差明显,且在5-40cm范围内读数不稳定.其中可能会出现的误差有三角误差、镜面反射、多次反射等。
实际测量要求:
测距时,被测物体面积不宜少于0.5平方米且尽量要求平整。

二、工程介绍

2.1 前期准备

先说一下,鉴于超声波传感器的影响因素过多,所以不建议超声波传感器作为主要距离探测工具,仅可作为辅助测量工具,另外,本工程并未考虑温度对超声波传感器的影响,所以这一部分内容请自行查阅。
工具:STM32F405(Timer+ I/O)、HC-SR04(4个)

2.2 工程详解

2.2.1 状态机

超声波按序号0-3排序,利用Sonar_ID进行不同的超声波操作,同时基于实际的使用情况,将超声波状态机分为以下5种状态,其中(RCVED_A 、RCVED_B 、RCVED_C 、RCVED_D )为同一种状态,只不过按超声波个数进行了区分。

#define START 			1
#define WAIT			2
#define RCVED_A 		3
#define RCVED_B 		4
#define RCVED_C 		5
#define RCVED_D 		6
#define END_SINGLE		7
#define END_LOOP 		8

START状态:发布Trig信号
WAIT状态:空闲状态
RCVED状态:中断中采集到超声波数据
END_SINGLE状态:完成单个超声波数据采集
END_LOOP状态:完成多路超声波采集循环

2.2.2 引脚定义:

#define TRIPA PCout(0)			//A路超声波 PC0接触发引脚 	PA0接回波引脚
#define TRIPB PCout(1)			//B路超声波 PC1接触发引脚 	PA1接回波引脚
#define TRIPC PCout(2)			//C路超声波 PC2接触发引脚 	PA2接回波引脚
#define TRIPD PCout(3)			//D路超声波 PC3接触发引脚  PA3接回波引脚

2.2.3 Timer定时器及IO初始化

利用定时器的CC1、CC2、CC3、CC4及Update溢出中断,通过CC1、CC2、CC3、CC4进行捕获上升沿及下降沿中断,通过Update溢出中断进行距离时间计算,一定要注意该定时器中断的优先级,否则极易造成超声波数据长时间无返回值

void TIM5_Cap_Init(u16 arr,u16 psc)	
{	
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_ICInitTypeDef  TIM5_ICInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
 
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 				//使能PORTA时钟	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); 				//使能PORTC时钟	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);  				//TIM5时钟使能  
	
	
	/****** ECOH 回响信号******/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; 		//GPIOA0
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;									//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;								//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 									//推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; 									//下拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); 											//初始化PA0
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5); 				//PA0复用位定时器5
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM5); 				//PA1复用位定时器5
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_TIM5); 				//PA2复用位定时器5
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_TIM5); 				//PA3复用位定时器5
	
	/****** TRIG 触发信号******/
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;     
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  						//输出
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;						//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;					//速度100MHZ
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;						//上拉
	GPIO_Init(GPIOC, &GPIO_InitStructure);      

	  
	TIM_TimeBaseStructure.TIM_Prescaler=psc;  							//定时器分频
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 			//向上计数模式
	TIM_TimeBaseStructure.TIM_Period=arr;   							//自动重装载值
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);
	

	//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; 					//CC1S=01 	选择输入端 IC1映射到TI1上
	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿捕获
	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 	//映射到TI1上
	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 			//配置输入分频,不分频 
	TIM5_ICInitStructure.TIM_ICFilter = 0x00;							//IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
	
		//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_2; 					//CC1S=02 	选择输入端 IC1映射到TI2上
	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿捕获
	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 	//映射到TI2上
	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 			//配置输入分频,不分频 
	TIM5_ICInitStructure.TIM_ICFilter = 0x00;							//IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
	
		//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_3; 					//CC1S=03 	选择输入端 IC1映射到TI3上
	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿捕获
	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 	//映射到TI3上
	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 			//配置输入分频,不分频 
	TIM5_ICInitStructure.TIM_ICFilter = 0x00;							//IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
	
		//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_4; 					//CC1S=04 	选择输入端 IC1映射到TI4上
	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿捕获
	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 	//映射到TI4上
	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 			//配置输入分频,不分频 
	TIM5_ICInitStructure.TIM_ICFilter = 0x00;							//IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
		
	TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);							//允许更新中断 
	TIM_ITConfig(TIM5,TIM_IT_CC1,ENABLE);								//允许CC1IE捕获中断
	TIM_ITConfig(TIM5,TIM_IT_CC2,ENABLE);								//允许CC2IE捕获中断
	TIM_ITConfig(TIM5,TIM_IT_CC3,ENABLE);								//允许CC3IE捕获中断
	TIM_ITConfig(TIM5,TIM_IT_CC4,ENABLE);								//允许CC4IE捕获中断
	
	TIM_Cmd(TIM5,ENABLE ); 												//使能定时器5
 
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;				//抢占优先级0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;					//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;						//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);										//根据指定的参数初始化VIC寄存器、
}

2.2.4 中断处理

全部代码贴出来太长了,贴一个ID的给解释一下,其他的照着写就行,最后别忘清除中断标志位。

if(Sonar_ID == 0)
{
	if((TIM5CH1_CAPTURE_STA&0x80)==0)									//还未成功捕获	
	{
		if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)				//溢出
		{	     
			if(TIM5CH1_CAPTURE_STA&0x40)								//已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0x3F)>=0x0C)					//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0x80;							//标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0x03E8;
					Sonar_Status = RCVED_A;
					TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); 	//CC1P=0 设置为上升沿捕获
				}else 
				{
					TIM5CH1_CAPTURE_STA++;
				}
			}	 
		}
	if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)					//捕获1发生捕获事件
	{	
		if(TIM5CH1_CAPTURE_STA&0x40)								//捕获到一个下降沿 		
		{	
			TIM5CH1_CAPTURE_STA|=0x80;								//标记成功捕获到一次高电平脉宽
			TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);				//获取当前的捕获值.
			Sonar_Status = RCVED_A;
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); 		//CC1P=0 设置为上升沿捕获
		}
		else  														//还未开始,第一次捕获上升沿
		{
			TIM5CH1_CAPTURE_STA=0;									//清空
			TIM5CH1_CAPTURE_VAL=0;
			TIM5CH1_CAPTURE_STA|=0x40;								//标记捕获到了上升沿
			TIM_Cmd(TIM5,DISABLE); 									//关闭定时器5
			TIM_SetCounter(TIM5,0);
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);		//CC1P=1 设置为下降沿捕获
			TIM_Cmd(TIM5,ENABLE ); 									//使能定时器5
			}		    
		}			     	    					   
	}
}
	

因为我的超声波状态机大体思路是按照Sonar_ID一路接一路进行采集,所以进中断时也是按照Sonar_ID进行了区分,首先,TIM5CH1_CAPTURE_STA为每路超声波传感器的标志位,主要标记捕获到下降沿,捕获到上升沿,以及溢出次数统计,在完成一次捕获后更新捕获完成bit位,所以多注意下这个参数的使用。
第一步:中断捕获(上升沿/下降沿)

if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)					//捕获1发生捕获事件

第二步:捕获到上升沿标记,同时各种标志位及计数清零,同时将定时器改为捕获下降沿

TIM5CH1_CAPTURE_STA|=0x40;								//标记捕获到了上升沿
TIM5CH1_CAPTURE_STA=0;									//清空
TIM5CH1_CAPTURE_VAL=0;
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);		//CC1P=1 设置为下降沿捕获

第三步:捕获到下降沿标记,更新捕获完成标志位,同时将定时器更改为上升沿捕获

TIM5CH1_CAPTURE_STA|=0x80;								//标记成功捕获到一次高电平脉宽
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); 		//CC1P=0 设置为上升沿捕获

另外,我将定时器超时未接收到下降沿也判定为捕获完成,因为距离太远了,所以果断截断,同时将定时器更改为上升沿捕获

2.2.5 状态机处理

全部代码贴出来太长了,贴一个ID的给解释一下

void Sonar_State_Machine(uint8_t status)
{
	if(Sonar_ID > 3)
		Sonar_ID = 0;
	
	switch(status)
	{
		case START:
			if(Sonar_ID == 0)
			{
				TRIPA=1;			//高电平触发
				delay_us(20);  		//延时
				TRIPA=0;			//低电平	
			}
			else
				Sonar_ID = 0;
			Sonar_Status = WAIT;		
			break;
		case WAIT:
			OverTime_Count++;
			if(OverTime_Count > 2)			//假设在等待时间内没有在中断内收到状态返回,则重发Trig
			{
				Sonar_Status = START;
				ReSend_Flag++;
				if(ReSend_Flag > 2)			//重发Trig3次仍未在中断内收到状态反馈,则判定该超声波存在问题
				{
					/**可添加故障处理函数,可通过串口/CAN将异常传感器ID进行上报**/
					Sonar_ID++;
					Sonar_Status = START;	
				}
			}
			break;
		case RCVED_A:
			Distance_A=TIM5CH1_CAPTURE_STA&0X3F;
			Distance_A*=1000;	 						//溢出时间总和
			Distance_A+=TIM5CH1_CAPTURE_VAL;			//得到总的高电平时间
			Distance_A=Distance_A*170/1000;				//转化成mm为单位,依据声音在空气中传播速度340m/s
			TIM5CH1_CAPTURE_STA=0;						//开启下一次捕获
			Sonar_Status = END_SINGLE;
			break;
		case END_SINGLE:
			Sonar_ID++;
			if(Sonar_ID >3)
				Sonar_Status = END_LOOP;
		case END_LOOP:
			Sonar_ID =0;
			Sonar_Status = START;
			break;
		default:
			break;
	}	
}

START状态发布Trig信号,然后状态机转为WAIT,WAIT状态下进行计数统计,如果在超时时间内中断未接收到捕获完成的状态,则自动将该Sonar_ID的Trig信号重发,重发次数超过三次且未接收到数据时,则判定超声波异常,可以做一些异常处理函数,假设在中断中状态机状态变为RCVED_A,则进行这一路超声波的距离计算,计算完成后状态机状态变为END_SINGLE,在该状态记为这一路超声波完成计算,开始进行下一路超声波计算(Sonar_ID++),如果超声波Sonar_ID大于我们需要计算的路数,则将状态机状态变为END_LOOP,标记完成一次循环统计,Sonar_ID赋0,自动开启下一超声波距离检测循环。
以上仅为参考网上诸多资料完成的工程,但是实际测试效果还不错,给大家分享一下,如有错误欢迎指正。
源码地址:https://download.csdn.net/download/weixin_41591545/69503086
黎明就在眼前

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡泡吐泡泡啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值