【STM32篇】温控门闸

本文介绍了基于STM32的温控门闸装置,利用MXL90614红外温度计进行非接触测温,HR4988驱动步进电机控制门闸开关,HC_SR04超声波模块检测人体距离。系统通过STM32处理各传感器数据,实现自动开门和关门功能,其中电机控制采用脉冲控制,OLED显示屏用于显示信息。
摘要由CSDN通过智能技术生成

本篇文章讲述的是一个基于STM32的温控门闸装置的毕业设计,实现基本测温和自动开关门功能。

主要功能器件:

STM32F103VET6(主控芯片);

MXL90614(红外温度计);

HR4988(步进电机驱动IC);

HC_SR04(超声波测距模块);

OLED(0.96寸显示屏);

光电开关;

蜂鸣器。

注:以上测温、电机、测距3个器件的驱动在以下文件中有介绍,本片文章就不另作介绍。

【STM32篇】驱动MXL90614红外测温模块

【STM32篇】4988驱动步进电机

【STM32篇】驱动HC_SR04超声波测距模块

1. 设计框架

        温控门闸,测温和门闸控制是放在首位的。测温传感器在该设计上主打一个无接触式测温,所以选用了红外温度计。MXL90614测温模块的作用在于测温,MCU可根据测温情况控制门闸打开。门闸的开关控制则使用步进电机实现,通过控制电机的正反转一定角度实现门闸开关效果。

        在设计时使用HC_SR04模块作为人体检测传感器,通过检测到与障碍物的距离小于一定值时,判断为有人经过(本次设计的距离范围为25cm以内)。在实际使用中,使用了两个该模块,主要用于出门人体检测以及是否完全通过。当行人出门时,检测到行人则控制门闸打开,当两个传感器都没有检测到行人时则表示完全通过,MCU控制门闸关闭。工作流程如下图。

 2. 驱动设计

2.1 门闸控制

HR4988引脚连接:

ENABLEPB5
STEPPB6
DIRPB7
GATEPE3

驱动程序1:初始化。

控制步进电机的转动方向,转动速度以及角位移实际是控制HR4988的方向引脚以及步进引脚。给DIR引脚高低电平可控制电机正反转;STEP引脚则使用脉冲控制,在1/16步进模式下,16个脉冲电机转动1.8度。控制脉冲的频率可实现电机转动速度的控制,控制脉冲数,可实现角位移量的控制。在编写程序时使用了TIM4输出PWM波控制STEP引脚。

GATE_Init()为光电开关初始化函数,空闲时,光电开关输出引脚输出高电平,遮挡光源后为低电平。光电开关用于门闸定位。

/*
引脚连接:
		DIR     PB7     方向
		ENABLE	PB5		使能引脚(低有效)
		STEP 	PB6		步进引脚
*/
//步进电机驱动初始化
void GATE_Init(void)
{
	GPIO_InitTypeDef GATE_init;
	GATE_init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GATE_init.GPIO_Pin = GPIO_Pin_3;
	GATE_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOE,&GATE_init); 
}
void HR4988_Init(void)
{
	//1.引脚初始化配置
	GPIO_InitTypeDef hr4988_gpio_init;
	hr4988_gpio_init.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_5;
	hr4988_gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
	hr4988_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&hr4988_gpio_init);  				//PB4、PB5通用推挽输出模式
	hr4988_gpio_init.GPIO_Pin = GPIO_Pin_6;
	hr4988_gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;		//PB6复用推挽输出
	hr4988_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&hr4988_gpio_init);  
	MOTOR_ENABLE();//使能输出
	 
	//2.配置TIM4基本计数模式
	TIM_DeInit(TIM4);									//复位定时器
	TIM_TimeBaseInitTypeDef hr4988_TimeBase_Init;
	hr4988_TimeBase_Init.TIM_Prescaler = 71;			//预分频值 72分频
	hr4988_TimeBase_Init.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
	hr4988_TimeBase_Init.TIM_Period = 3999;				//重装载值4000
	hr4988_TimeBase_Init.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频:不分频
	hr4988_TimeBase_Init.TIM_RepetitionCounter = 0;		//重复计数值
	TIM_TimeBaseInit(TIM4,&hr4988_TimeBase_Init);
	//3.配置TIM输出比较模式
	TIM_OCInitTypeDef hr4988_OCInit;
	TIM_OCStructInit(&hr4988_OCInit);
	hr4988_OCInit.TIM_OCMode = TIM_OCMode_PWM1;			//PWM1模式
	hr4988_OCInit.TIM_Pulse = 1999;						//输出比较值
	hr4988_OCInit.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OC1Init(TIM4,&hr4988_OCInit);
	//4.配置中断
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//使能更新中断
	TIM_ClearFlag(TIM4,TIM_IT_Update);//清除挂起标志位
}

电机结构体参数:

typedef struct
{
    uint32_t currentLocation;//当前位置
    uint32_t sargetLocation;//目标位置
    uint16_t state;//运行状态
    uint16_t speedCount;
    uint8_t direction;//方向
}Motor_parameter;

电机在工作时可分为复位状态、工作状态以及停止状态,复位状态则是上电后,电机控制门闸运行到关闭位置的这一阶段;工作模式则是正常开门和关门动作;停止状态时电机处于静止。在控制电机运行指定角位移时,可用到当前位置和目标位置两个参数。(例如,门闸关闭时与装置处于90度角,电机带动门闸转动90度至开门状态,1.8度需要16个脉冲,90度则需要800个脉冲。将当前距离设为0,目标距离为800,每进一次定时器中断当前位置加1,待当前位置等于目标位置时,门闸以实现90度开门动作。)

驱动程序2:电机工作函数

1.复位工作:复位就是把门闸运行至关闭状态。

在此处目标位置可不用管,因为目标位置是未知的(上电前门闸的位置是未知的)。只需要把门闸按顺时针方向运动到光电开关处,将此处位置设为0,随后逆转反向,再转动90度便到达关门复位位置。

在连接电机和门闸时使用同步轮,电机与门闸的同步齿轮大小为1:2,门闸转动90度电机需要转动180度,即门闸转动90度需要1600个脉冲。

2.开关门工作:开门分为顺时针开门和逆时针开门。

在进门时,门闸需要顺着行人方向打开,即逆时针开门。出门时,门闸顺时针方向开门。

复位后,电机的距离设定为  0,1600,3200。光电开关处位置为0,关门时当前位置为1600。控制门闸进行开门动作,进门时,则需将方向设为逆时针,目标位置为3200,电机便会控制门闸转动90度至开门;出门开门时,将方向设为顺时针,目标位置为0;而关门则与开门方向相反,目标位置1600。

逆时针转动当前位置++,顺时针转动当前位置--    详细看中断服务函数。由于电机控制门闸转动速度不需要很快,就没有可屏蔽S曲线。

//电机复位工作
void Motor_Reset(void)
{
	motor_parameter.direction = CW;
	motor_parameter.currentLocation = 0;
	motor_parameter.sargetLocation = 0;
	motor_parameter.state = MOTOR_STATE_RESET;	//进入复位模式
	MOTOR_DIR(motor_parameter.direction);		//顺时针
	MOTOR_SET_SPEED(MOTOR_GO_HOME_SPEED);
	MOTOR_START();
	while(motor_parameter.state != MOTOR_STATE_STOP);
	MOTOR_STOP();
	//calculate_S_PulseFreq(2 ,MOTOR_90_LOCATION);//S曲线运动可不用
}
/*
	\brief:	配置motor工作模式
	\param:	dir :运行方向
				location:目标距离
	\retval:	none
*/
void MOTOR_Work_Config(uint8_t dir,uint32_t location)
{
	if(dir==CW)//顺时针 目标距离<当前距离
	{
		if(location > motor_parameter.currentLocation)
		{
			motor_parameter.sargetLocation = motor_parameter.currentLocation;
		}
	}
	else 
	{
		if(location < motor_parameter.currentLocation)
		{
			motor_parameter.sargetLocation = motor_parameter.currentLocation;
		}
		else if(location > MOTOR_180_LOCATION)
		{
			motor_parameter.sargetLocation = MOTOR_180_LOCATION;
		}
	}
	motor_parameter.speedCount = 0;
	motor_parameter.state = MOTOR_STATE_WORKING;
	motor_parameter.direction = dir;
	MOTOR_DIR(motor_parameter.direction);
	//MOTOR_SET_SPEED(motor_S_Array[motor_parameter.speedCount]);//设置速度
    MOTOR_SET_SPEED(MOTOR_GO_HOME_SPEED);//设置速度,可不用S曲线速度
	MOTOR_START();
}

驱动程序3:中断服务函数

void TIM4_IRQHandler(void)
{
	if(RESET != TIM_GetITStatus(TIM4,TIM_IT_Update))
	{
		TIM_ClearITPendingBit(TIM4,TIM_IT_Update);			//清除中断标志位
		if(motor_parameter.state == MOTOR_STATE_STOP)		//停止模式
		{
			MOTOR_STOP();
		}
		else if(motor_parameter.state == MOTOR_STATE_RESET)	//复位模式
		{
			motor_parameter.currentLocation++;
			static uint8_t motor_flag=0;
			if(MOTOR_HOME() && motor_flag == 0)
			{
				motor_parameter.direction = CCW;
				motor_parameter.currentLocation = 0;		//归0当前位置
				motor_parameter.sargetLocation = MOTOR_90_LOCATION;//目标位置 90°
				MOTOR_DIR(motor_parameter.direction);
				motor_flag = 1;
			}
			if(motor_flag==1 && motor_parameter.currentLocation == motor_parameter.sargetLocation)
			{
				motor_parameter.state &= ~MOTOR_STATE_RESET;
				motor_flag = 0;
				MOTOR_STOP();
			}
		}
		else if(motor_parameter.state & MOTOR_STATE_WORKING)//运行
		{
			if(motor_parameter.direction == CCW)			//逆时针
				motor_parameter.currentLocation++;
			else if(motor_parameter.direction == CW)		//顺时针
				motor_parameter.currentLocation--;
			if(fabs((float )motor_parameter.currentLocation - (float )motor_parameter.sargetLocation) < (MOTOR_S_NUM -1))//减速阶段
			{
				motor_parameter.speedCount--;
			}
			else if(motor_parameter.speedCount < (MOTOR_S_NUM-1))//加速阶段
			{
				motor_parameter.speedCount++;
			}
			if(motor_parameter.currentLocation==motor_parameter.sargetLocation)
			{
				if(motor_parameter.currentLocation==MOTOR_90_LOCATION)//门关闭状态
				{
					DOOR_state = DOOR_STATIC;
				}
				motor_parameter.state &= ~MOTOR_STATE_WORKING;
				MOTOR_STOP();
			}
			//MOTOR_SET_SPEED(motor_S_Array[motor_parameter.speedCount]);
		}
	}
}

2.2 温度采集

MXL90614引脚连接:

SCLPB10
SDAPB11

红外温度计采集温度驱动程序可直接使用【STM32篇】驱动MXL90614红外测温模块

读取温度函数:

//读内存
uint16_t MLX906_ReadMemory(void)
{
	uint8_t Pec,PecReg,ErrorCounter;
	uint8_t TempL=0;
	uint8_t TempH=0;
	uint8_t arr[6];
	ErrorCounter=0;
	//slaveAddr <<=1;//2-7位表示从机地址
	do
	{
		ErrorCounter++;
		if(ErrorCounter==10)
		{
			return 0;
		}			
		MLX906_START_Sign();//起始信号
		if(MLX906_SendByte(0x00))//发送MLX90614地址
			continue;
		if(MLX906_SendByte(0x07))//发送读MLX90614 RAM地址
			continue;
		MLX906_START_Sign();//重新启动
		if(MLX906_SendByte(0x01))//发送数据采集命令
			continue;
		TempL=MLX906_ReadByte();//读取地位数据
		TempH=MLX906_ReadByte();//读取高位数据
		Pec=MLX906_ReadByte();//读取校验位
		MLX906_STOP_Sign();//停止信号
		arr[5]=	0x00;
		arr[4]=	0x07;
		arr[3]= 0x01;
		arr[2]= TempL;
		arr[1]= TempH;
		arr[0]= 0;
		PecReg=CRC_Calculation(arr);//计算CRC校验
	}while(PecReg!=Pec);
	return (uint16_t)((TempH<<8)|TempL);
}
//读温度
float MLX906_Read_Temp(void)
{
	return (float )MLX906_ReadMemory()*0.02-273.15;
}

2.3 人体检测

HC_SR04引脚连接:

TRIG_1PD11
ECHO_1PD0
TRIG_2PD13
ECHO_2PD1

HC_SR04的驱动使用外部中断和TIM两个MCU外设,当距离小于25cm时,标志位置位,检测到行人,大于25cm时标志位复位。


#include "hc_sr04.h"
/*
引脚说明:
TRIG :触发控制信号输入(推挽输出)
ECHO :回响信号输出(配置为外部中断输入)
*/
uint32_t HCSR04_1_usTime=0;//HCSR04_1 回响信号时间间隔
uint32_t HCSR04_2_usTime=0;//HCSR04_2 回响信号时间间隔
uint8_t HC_TIM6_IT_count=0;//进中断次数
uint8_t HC_TIM7_IT_count=0;
uint8_t HCSR04_1_READ_STATE=0;//HCSR04_1 读状态标志位
uint8_t HCSR04_2_READ_STATE=0;
//TIM定时器配置
void HC_SR04_TIM_config(void)
{
	//1.外设复位
	TIM_DeInit(TIM6);
	TIM_DeInit(TIM7);
	//2.TIM外设相关功能配置
	TIM_TimeBaseInitTypeDef hcsr04_tim_init;
	hcsr04_tim_init.TIM_Prescaler = 71;		//72分频
	hcsr04_tim_init.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
	hcsr04_tim_init.TIM_Period = 0xFFFF;//重装载值65535
	hcsr04_tim_init.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频 1
	hcsr04_tim_init.TIM_RepetitionCounter = 0;//从0开始计数
	TIM_TimeBaseInit(TIM6,&hcsr04_tim_init);
	TIM_TimeBaseInit(TIM7,&hcsr04_tim_init);
	//3.配置TIM中断
	TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);//更新中断
	TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE);
	TIM_ClearFlag(TIM6,TIM_FLAG_Update);//清除更新标志位
	TIM_ClearFlag(TIM7,TIM_FLAG_Update);
	//失能TIM
	TIM_Cmd(TIM6,DISABLE);//关闭定时器
	TIM_Cmd(TIM7,DISABLE);//关闭定时器
}
//HC_SR04初始化配置
void HC_SR04_Init(void)
{
	//初始化GPIO
	GPIO_InitTypeDef hcsr04_gpio_init;
	hcsr04_gpio_init.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_3;
	hcsr04_gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽
	hcsr04_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD,&hcsr04_gpio_init);//初始化PD11 PD13
	hcsr04_gpio_init.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	hcsr04_gpio_init.GPIO_Mode = GPIO_Mode_IPD;//下拉输入
	GPIO_Init(GPIOD,&hcsr04_gpio_init);//初始化PD12 PD14
	//配置外部中断
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource0);//选择中断引脚
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource1);
	
	EXTI_InitTypeDef hcsr04_exti_init;
	hcsr04_exti_init.EXTI_Line = EXTI_Line0|EXTI_Line1;
	hcsr04_exti_init.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
	hcsr04_exti_init.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿触发
	hcsr04_exti_init.EXTI_LineCmd= ENABLE;//使能中断线
	EXTI_Init(&hcsr04_exti_init);
//	EXTI_GenerateSWInterrupt(EXTI_Line0);
//	EXTI_GenerateSWInterrupt(EXTI_Line1);
	EXTI_ClearFlag(EXTI_Line0|EXTI_Line1);//清除挂起标志位
	//配置定时器
	HC_SR04_TIM_config();
	HCSR04_1_TRIG_0();//拉低输入引脚
	HCSR04_2_TRIG_0();
}
/*
外部中断服务函数0
函数功能:捕获HCSR04_1_ECHO引脚电平
*/
void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0))
	{
		EXTI_ClearITPendingBit(EXTI_Line0);
		if(Bit_RESET!=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_0))	//上升沿
		{
			//启动定时器
			TIM_Cmd(TIM6,ENABLE);
		}
		else													//下降沿
		{
			HCSR04_1_usTime=TIM_GetCounter(TIM6);//获取计数值
			//失能定时器
			TIM_Cmd(TIM6,DISABLE);
			HCSR04_1_usTime=HCSR04_1_usTime+HC_TIM6_IT_count*65536;
			HC_TIM6_IT_count=0;
			TIM_SetCounter(TIM6,0x00);//清空计数器
			HCSR04_1_READ_STATE=HCSR04_1_READABLE;//可读
			if((HCSR04_1_usTime/58)<25)//小于25cm
			{
				HCSR04_1_READ_STATE = HCSR04_1_READABLE;//有人经过
			}
			else
			{
				HCSR04_1_READ_STATE = HCSR04_1_UNREADABLE;
			}
		}
	}
}
/*
外部中断服务函数1
函数功能:捕获HCSR04_2_ECHO引脚电平
*/
void EXTI1_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0))
	{
		EXTI_ClearITPendingBit(EXTI_Line0);
		if(Bit_RESET!=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_1))	//上升沿
		{
			//启动定时器
			TIM_Cmd(TIM7,ENABLE);
		}
		else													//下降沿
		{
			HCSR04_2_usTime=TIM_GetCounter(TIM7);//获取计数值
			//失能定时器
			TIM_Cmd(TIM7,DISABLE);
			HCSR04_2_usTime=HCSR04_2_usTime+HC_TIM7_IT_count*65536;
			HC_TIM7_IT_count=0;
			TIM_SetCounter(TIM7,0x00);//清空计数器
			if((HCSR04_2_usTime/58)<25)//小于25cm
			{
				HCSR04_2_READ_STATE = HCSR04_2_READABLE;//有人经过
			}
			else
			{
				HCSR04_2_READ_STATE = HCSR04_2_UNREADABLE;
			}
		}
	}
}
void TIM6_IRQHandler(void)
{
	if(RESET!=TIM_GetFlagStatus(TIM6,TIM_FLAG_Update))
	{
		TIM_ClearFlag(TIM6,TIM_FLAG_Update);//清除更新标志位
		HC_TIM6_IT_count++;
	}
}
void TIM7_IRQHandler(void)
{
	if(RESET!=TIM_GetFlagStatus(TIM7,TIM_FLAG_Update))
	{
		TIM_ClearFlag(TIM7,TIM_FLAG_Update);//清除更新标志位
		HC_TIM7_IT_count++;
	}
}

//触发信号
void HCSR04_TriggerGignal(uint8_t hcsr04_x)
{
	if(hcsr04_x==HCSR04_1)
	{
		HCSR04_1_TRIG_1();
		Delay_us(30);
		HCSR04_1_TRIG_0();
	}
	else if(hcsr04_x==HCSR04_2)
	{
		HCSR04_2_TRIG_1();
		Delay_us(30);
		HCSR04_2_TRIG_0();
	}
}

触发信号则使用TIM中断,定时发送触发信号,间隔为200ms。

/*
	\brief:	TIM3工作服务函数

	函数功能:	负责给超声波模块发送触发信号
*/
void TIM3_IRQHandler(void)
{
	if(RESET!=TIM_GetITStatus(TIM3,TIM_IT_Update))
	{
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除外部中断
		HCSR04_TriggerGignal(HCSR04_1);
		HCSR04_TriggerGignal(HCSR04_2);
	}
}

2.4 显示

OLED屏引脚连接:

D0   CLKPC0
D1   MOSIPC1
RES#PC2
DCPC3
CSPC4

在编写OLED显示函数时使用显存进行屏幕刷新,建立一个与屏幕等大的数组保存显示数据,随后需要实现画点函数,向数组里写入需要的数据,并将数据刷到屏幕上。

/*
	\brief:	画点函数
	\param:	x 横坐标 0~127
				y 纵坐标 0~63
				stat 1 点亮 0 不点亮
	\retval:	none
*/
void OLED_Draw_Dot(uint8_t x,uint8_t y,uint8_t stat)
{
	if(x<128 && y<64)
	{
		uint8_t bit=y%8;
		uint8_t page=y/8;
		if(stat)
			OLED_GRAM[page][x]|=1<<bit;
		else
			OLED_GRAM[page][x]&=~(1<<bit);
	}
}
//刷新函数(将缓存数据刷新到屏幕上)
void OLED_Refresh(uint8_t x1,uint8_t x2,uint8_t y1,uint8_t y2)
{
	uint8_t i,j;
	
	for(i = y1;i <= y2;i++)
	{
		OLED_Set_Pos(i,x1);//设置起始坐标
		for(j=x1;j<=x2;j++)
		{
			OLED_Write_Byte(OLED_GRAM[i][j],OLED_DAT);
		}
	}
}

详细请看附件。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值