前言

是大二的单片机课设,在这里存档,暂时是草稿流,有时间就精修。


设计要求

设计并制作智能多控晾衣架,其基本要求如下:

        1、实时检测环境光强值和湿度状态,并通过OLED显示屏显示。

        2、具备语音识别功能,用户可通过语音命令控制晾衣架的收回/伸出、切换手动/自动工作模式和切换蓝牙、键盘控制模式。

        3、具备矩阵键盘控制模式,用户可通过按键进行设置光强阈值、切换工作模式、收回/伸出晾衣架,并将设置好的参数存储在AT24C02中掉电保存。

        4、具备蓝牙控制模式,用户可通过手机发送数据实现确认光强阈值输入完成、重新输入光强阈值、切换工作模式、收回/伸出晾衣架等功能。

        5、具备自动工作模式:当光强高于阈值时,自动伸出晾衣架;当光强低于设定值时,自动收回晾衣架。

        6、具备手动工作模式:用户可通过按键手动控制晾衣架的收回/伸出。

        7、当处于自动工作模式时,若用户尝试通过按键手动控制晾衣架,系统自动切换回手动工作模式。

        8、当检测到下雨时,无论处于自动/手动工作模式,系统都会控制舵机收回晾衣架。

        9、当检测到晾衣架上没有衣服时,无论处于自动/手动工作模式,系统都会控制舵机收回晾衣架。

系统硬件设计

一、中央控制系统:

语音识别核心板ASRPro

(1) 概述

        这是一款低成本、高性能的语音识别模组,采用ASRPRO芯片,内置神经网络处理器,支持多种神经网络及卷积运算。它具备优秀的回声消除和噪声抑制能力,识别效果优于其他芯片。支持中、英、日等多国语言,可应用于家电、照明、玩具、穿戴设备、工业、汽车等领域。搭配天问Block编程软件,可用于快速实现语音交互及控制和智能语音方案应用。

(2) ASRPRO-CODE 核心板

        体积小巧,长宽为 18x23mm,对外接口采用 2 排邮票孔和插针孔,方便采用回流贴片使用和焊接插针使用,喇叭和麦克风都需要自己外接,下载程序需要搭配 STC-LINK 下载器。

                                                        

蓝牙无线通讯HC-05

(1)概述

        HC-05蓝牙模块是一种基于蓝牙协议的简单无线通信设备。该模块基于BC417单芯片蓝牙IC,符合蓝牙v2.0标准,支持UART和USB接口。

        HC-05具有两种工作模式:命令响应工作模式和自动连接工作模式。本课程设计使用自动连接工作模式,HC-05为从模式。

        HC-05模块处于自动连接工作模式时,将自动根据事先设定的方式进行数据传输。主模式:该模块可以主动搜索并连接其它蓝牙模块并接收发送数据。从模式:只能被搜索、被其它蓝牙模块连接,进行接收发送数据。

(2) 管脚

STATE:状态指示。未连接时输出低电平,连接时输出高电平。

RXD  :UART接收引脚。

TXD  :UART发射引脚。

GND  :地。

VCC  :接电源,可以用+5V。

EN   :使能。接地禁用模块,悬空或接3。3V使能。

矩阵键盘(开发板自带)

二、晾衣架仿真控制系统:

舵机SG90

(1) 概述

        SG90舵机是一种重要的位置(角度)伺服驱动器,适用于需要角度不断变化并可以保持的控制系统。在控制系统中,舵机控制效果是性能的重要影响因素。作为基本的输出执行机构,舵机凭借其简洁的控制和输出方式,极大地方便了单片机系统与之进行接口集成。

        SG90舵机上有三根线,分别是GND(棕色线)、VCC(红色线)和SIG(黄色线),也就是地线、电源线和信号线。

(2) 工作原理

        控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms 的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。

        舵机的控制需要一个20ms 的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms 范围内的角度控制脉冲部分。以180 度角度伺服为例,对应的控制关系为: 0.5ms—0 度;1.0ms—45 度;1.5ms—90 度;2.0ms—135 度;2.5ms—180 度。

三、环境检测采集系统

ADC转换芯片XPT2046

(1) 概述

         XPT2046是一款4线制电阻式触摸屏控制器,内含12位分辨率、125KHz 转换速率逐步逼近型 A/D 转换器。它具有以下特点:

        - 高达12位的分辨率:XPT2046可以实现高达12位的模拟信号转换,能够满足大多数应用场景对精度要求。

        - 高速转换:该芯片的最大转换速率为100ksps(千次转换每秒),在较短时间内完成模拟信号的转换,有利于提高系统的实时性。

        - 多通道同时转换:XPT2046具有4个独立通道,可以同时对4个模拟信号进行转换,减轻了单片机的工作负担。

        - 低功耗:该芯片的工作电压范围为2.7V-5.5V,待机模式下功耗仅为100uA,适合电池供电或低功耗系统。

(2) 工作原理

        AD 转换器(ADC)将模拟量转换为数字量通常要经过4个步骤:采样、保持、量化和编码。采样,即是将一个时间上连续变化的模拟量转换为时间上离散变化的模拟量。保持,即是将采样结果存储起来,直到下次采样,一般采样器和保持电路一起总称为采样保持电路。将采样电平归化为与之接近的离散数字电平,这个过程叫做量化。将量化后的结果按照一定数制形式表示就是编码。将采样电平(模拟值)转换为数字值时,主要有两类方法:直接比较型与间接比较型。

        直接比较型:就是将输入模拟信号直接与标准的参考电压比较,从而得到数字量。常见的有并行ADC和逐次比较型ADC。

        间接比较型:输入模拟量不是直接与参考电压比较,而是将二者变为中间的某种物理量在进行比较,然后将比较所得的结果进行数字编码。常见的有双积分型 ADC。

        采用逐次逼近法的 AD 转换器由一个比较器、DA 转换器、缓冲寄存器和控制逻辑电路组成,基本原理是:从高位到低位逐次试探比较,就像用天平秤物体,从重到轻逐级增减砝码进行试探。

        逐次逼近法的转换过程是:初始化时将逐次逼近寄存器各位清零,转换开始时,先将逐次逼近寄存器最高位置1,送入DA 转换器,经DA 转换后生成的模拟量送入比较器,称为U0,与送入比较器的待转换的模拟量Ux进行比较,若 U0<Ux,该位1被保留,否则被清除。然后再将逐次逼近寄存器次高位置1,将寄存器中新的数字量送DA转换器,输出的U0再与Ux比较,若U0<Ux, 该位 1 被保留,否则被清除。重复此过程,直至逼近寄存器最低位。转换结束后, 将逐次逼近寄存器中的数字量送入缓冲寄存器,得到数字量的输出。逐次逼近的 操作过程是在一个控制电路的控制下进行的。

光敏电阻

(1) 概述

        光敏电阻传感器模块具备对光线敏感度的反应能力,其主要用途是检测探头周围的光线强度(亮度)。该模块可通过DO口输出数字信号1和0,同时也可通过AO口输出模拟信号,以满足不同应用场景的需求。

        可以通过旋钮来改变它的阈值。当检测到周围光线较暗时(在阈值范围内),DO口输出高电平;当检测到周围光线较亮时(超过我们设定的阈值),DO口输出低电平。

        AO口作为模拟信号输出,可以连接到单片机上拥有的AD转换模块或者外置AD转换模块,通过转换,就可以得到更为精准的光线亮度数值。

(2)在本课程设计中,利用51开发板自带的外部设备光敏电阻,并结合模数转换模块(AD转换模块),对环境的光强度进行实时检测。此举旨在确保系统的稳定运行,并提高检测结果的准确性。

雨滴传感器

(1) 概述

          该传感器具有数字开关量输出(0和1)和模拟量AO电压输出两种输出形式。      接上5V电源和GND,电源指示灯亮,感应板上没有水滴时,DO输出为高电平,开关指示灯灭;滴上一滴水,DO输出为低电平,开关指示灯亮。刷掉上面的水滴,又恢复到输出高电平状态。AO模拟输出,可以连接单片机的AD口检测滴在上面的雨量大小。

OpenMV 图像识别

(1) 概述

        OpenMV是一个开源,低成本,功能强大的机器视觉模块。以STM32F427CPU为核心,集成了OV7725摄像头芯片,在小巧的硬件模块上,用C语言高效地实现了核心机器视觉算法,提供Python编程接口。OpenMV上的机器视觉算法包括寻找色块、人脸检测、眼球跟踪、边缘检测、标志跟踪等。可以用来实现非法入侵检测、产品的残次品筛选、跟踪固定的标记物等。使用者仅需要写一些简单的Python代码,即可轻松的完成各种机器视觉相关的任务。

         OpenMV采用的STM32F427拥有丰富的硬件资源,引出UART,I2C,SPI,PWM,ADC,DAC以及GPIO等接口方便扩展外围功能。USB接口用于连接电脑上的集成开发环境OpenMV IDE,协助完成编程、调试和更新固件等工作。TF卡槽支持大容量的TF卡,可以用于存放程序和保存照片等。

四、历史数据存储系统

AT24C02存储器

(1) 概述

          AT24C02是一种2K位串行EEPROM芯片,可存储256字节数据。采用I2C协议通信,可通过SDA和SCL两根线连接其他设备。

        AT24C02存储器具有以下特点:

        - 可编程:AT24C02可以通过编程来存储和读取数据。它支持字节级的读写操作,可以在任意地址上存储和读取数据。

        - 可擦除:AT24C02支持电擦除操作,可以将存储的数据擦除为全1状态,以便重新编程。

        - 低功耗:AT24C02在待机模式下具有低功耗特性,适合用于电池供电的应用。

        - 高可靠性:AT24C02具有较高的数据可靠性和抗干扰能力,适用于工业环境中的数据存储需求。

(2) I2C通信协议

        I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间进行短距离的数据传输。它由飞利浦公司(现在的恩智浦半导体)在1982年开发,并在现代电子设备中得到广泛应用。

        I2C协议使用两根线进行通信:串行数据线(SDA)和串行时钟线(SCL)。SDA线用于传输数据,而SCL线用于同步数据传输的时钟信号。这种双线结构使得多个设备可以在同一总线上进行通信,每个设备都有一个唯一的地址。

        I2C通信协议基本操作包括:起始条件(SDA低-高,SCL高-低)、地址传输(主设备发往总线)、数据传输(时钟边沿改变SDA电平)和停止条件(SDA高-低,SCL低-高)。

        I2C通信协议具有以下特点:

        - 可以连接多个设备,每个设备都有唯一的地址。

        - 支持主从模式,其中一个设备充当主设备,其他设备充当从设备。

        - 传输速率相对较慢,通常在100 kHz或400 kHz。

        - 支持半双工通信,即数据可以在主设备和从设备之间双向传输。

五、指令信息显示系统

OLED显示

(1) 概述

        OLED,即有机发光二极管( Organic Light Emitting Diode )。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

(2)工作原理

        ITO透明电极和金属电极分别作为器件的阳极和阴极,在一定的电压驱动下,电子和空穴分别从阴极和阳极注入电子和空穴传输层,电子和空穴分别通过电子和空穴传输层迁移到发光层,在发光层中相遇形成激子,激发发光分子,经辐射弛豫后发出可见光,这个过程称为电致发光(EL)。为了使OLED有效工作,需要精确控制电流的流动,这通常通过TFT(薄膜晶体管)阵列来实现。

系统软件设计

main.c

#include <REGX51.H>
#include "UART.h"
#include "OLED.h"
#include "delay.h"
#include "Servo.h"
#include "AT24C02.h"
#include "XPT2046.h"
#include "MatrixKey.h"
#include "Timer0_Init.h"

#define RainSensor 		  			  	P0_4//雨滴传感引脚
#define Pin_Mode   		  			  	P2_2//语音-工作模式引脚
#define Pin_Action 		  				P2_3//语音-舵机动作引脚
#define Pin_Speech_MatrixAndBlueTooth   P2_4//语音-键盘-蓝牙控制模式转换引脚

#define Withdraw 1
#define Manual 1
#define Extend 0
#define Auto 0
#define Rain 0
#define Dry  1
#define Speech_Contral 1
#define MatrixAndBlueTooth_Contral 0
#define Enable 1
#define Disable 0

uint8_t LightPower = 0, LightPower_Limit = 0, LightPower_Limit_Temp = 0,Flag_SetLightPower, //光强、光强阈值、中间值、标志位(是否开始设置光强阈值)
MatrixKeyNum = 0, Num_Count = 0, Pressed_Count = 0,  //按键键码、输入数字位数、按键按下次数
Mode = 0, Action = 0, Flag_Speech_MatrixAndBlueTooth,//标志位:手动/自动模式、收回/伸出模式、键盘/语音/蓝牙控制模式
BlueTooth_Value = 0, BlueTooth_Mode = 0;             //蓝牙接收值、蓝牙模式

void Speech_Recognition(void);
void BlueTooth_Contral(void);
void OLED_Show(void);

void main ()        
{
	UART_Init();
	Timer0_Init();
	OLED_Init();
    OLED_Clear();
	
	OLED_ShowString(0, 0, "Lg:", 16);
	OLED_ShowString(63, 0, "Bound:", 16);
	OLED_ShowString(63, 6, "Count:", 16);
	
	while(1)
	{		
		/*******************************************按键控制模式设置****************************************************************/
		
		MatrixKeyNum = MatrixKey();                           //全局扫描矩阵键盘
		OLED_ShowNum(110, 6, Pressed_Count, 2, 16);           //显示总按下次数		
		if(MatrixKeyNum != 0)                            	  
		{ 
			Pressed_Count++;                                  //按键按下次数加一
			
			if(MatrixKeyNum <= 10)                	  	      /***按键1~10输入光强阈值***/
			{
				if(Flag_SetLightPower == Enable)
				{
					if(Num_Count < 2)                         //最大位数为2
					{
						LightPower_Limit_Temp *= 10;          //新输入的数字作个位
						LightPower_Limit_Temp += MatrixKeyNum % 10;//输入10即为0
						Num_Count++;                          //输入一个数,总位数加一
					}	
				}		
			}
			else if(MatrixKeyNum == 11)                       /***按键11开始(或重新)输入光强阈值***/
			{
				Flag_SetLightPower = Enable; 
				Num_Count = 0;
				LightPower_Limit = 0;
			}
			else if(MatrixKeyNum == 12)                       /***按键12确认最终光强阈值***/
			{
				LightPower_Limit = LightPower_Limit_Temp;
				Flag_SetLightPower = Disable;
				Num_Count = 2;
				AT24C02_WriteByte(0, LightPower_Limit);       //存储光强阈值,位置0
			}
			/************以下是功能按键***********/
			else if(MatrixKeyNum == 13)                       /***按键13切换手动/自动工作模式***/
			{
				if(Pin_Speech_MatrixAndBlueTooth == MatrixAndBlueTooth_Contral)Mode = !Mode;//键盘蓝牙控制模式该键有效
			}
			else if(MatrixKeyNum == 14)                       /***按键14收回/伸出晾衣架***/
			{
				if(Mode == Auto)Mode = Manual;                //自动工作模式下,按下按键,转换手动工作模式
				Action = !Action;						      //手动工作模式可收回/伸出,非雨天
			}
//			else if(MatrixKeyNum == 15)                       /***按键15转换键盘/语音蓝牙控制,加入语音转换后该键失效***/
//			{
//				Flag_Speech_MatrixAndBlueTooth = !Flag_Speech_MatrixAndBlueTooth;
//			}
		}	
		
		/***********************************************语音/键盘/蓝牙控制模式转换*************************************************************/
		
		//使用语音控制模式时,其他两种无效,但键盘和蓝牙控制模式可以共存,因为语音电平不变,但蓝牙和键盘进入一次即清零
		if(Pin_Speech_MatrixAndBlueTooth == Speech_Contral)//识别语音转换对应控制模式
			Flag_Speech_MatrixAndBlueTooth = Speech_Contral;  
		else if(Pin_Speech_MatrixAndBlueTooth == MatrixAndBlueTooth_Contral)
			Flag_Speech_MatrixAndBlueTooth = MatrixAndBlueTooth_Contral;
		
		//根据标志位启动对应控制程序
		if(Flag_Speech_MatrixAndBlueTooth == Speech_Contral)Speech_Recognition();
		else if(Flag_Speech_MatrixAndBlueTooth == MatrixAndBlueTooth_Contral)BlueTooth_Contral();
		
		/**********************************************自动/手动工作模式功能设置*********************************************************/
		
		if(Mode == Auto)                                      /***自动工作模式设置***/
		{
			if(LightPower > LightPower_Limit && RainSensor == Dry)Action = Extend;//白天/晴天自动伸出晾晒(非雨天),防止太阳雨 
			else Action = Withdraw;          	              //黑夜/阴天/雨天自动收回防潮/雨
		}
		if(Mode == Manual){}                                  /***手动工作模式设置***/
		
		/******************************独立于模式之外的规则:有雨时强制收回晾衣架,无法进行其他动作***********************************/
		
		if(RainSensor == Rain)Action = Withdraw;     		  
	
		/**********************************************当一切尘埃落地(显示指令及数据)*********************************************/
			
		OLED_Show();	
		
		/***************************************************收尾(最终按指令执行收回/伸出动作)*************************************************/
		
		if(Action == Withdraw)contral_1();                      //收回衣服
		else if(Action == Extend)contral_4();                   //晾晒衣服
	}                    
}


/*********************************************************************************** 
函数名称:Speech_Recognition
函数功能:语音控制(电平检测),缺陷:语音控制模式下,按键模式失效
***********************************************************************************/
void Speech_Recognition(void)
{
	if(Pin_Mode == Manual)Mode = Manual;		//语音控制工作模式
	else if(Pin_Mode == Auto)Mode = Auto;
	if(Pin_Action == Withdraw)Action = Withdraw;//语音控制收伸
	else if(Pin_Action == Extend)Action = Extend;
}


/*********************************************************************************** 
函数名称:BlueTooth_Contral
函数功能:蓝牙控制模式(类似键盘)
***********************************************************************************/
void BlueTooth_Contral(void)
{
	if(BlueTooth_Mode == 1)					   //收回伸出晾衣架
	{
		if(Mode == Auto)Mode = Manual;         //自动工作模式下,按下按键,转换手动模式
		Action = !Action;
		BlueTooth_Mode = 0;
	}
	else if(BlueTooth_Mode == 2)               //转换手动/自动工作模式
	{
		if(Pin_Speech_MatrixAndBlueTooth == MatrixAndBlueTooth_Contral)Mode = !Mode;
		BlueTooth_Mode = 0;
	}
	else if(BlueTooth_Mode == 3)               //开始(或重新)输入光强阈值
	{
		Flag_SetLightPower = Enable; 
		Num_Count = 0;
		LightPower_Limit = 0;
		BlueTooth_Mode = 0;
	}
	else if(BlueTooth_Mode == 4)               //确认最终光强阈值
	{
		LightPower_Limit = LightPower_Limit_Temp;
		Flag_SetLightPower = Enable;
		Num_Count = 2;
		AT24C02_WriteByte(0, LightPower_Limit);//存储光强阈值,位置1
		BlueTooth_Mode = 0;
	}
}


//蓝牙串口中断函数
void UART_Routine()   interrupt 4  
{
	if(RI == 1)                      
	{
		RI = 0;                     //手动复位 
		BlueTooth_Value = SBUF;	
		switch(BlueTooth_Value)
		{
 			case 0x01:BlueTooth_Mode = 1;break;
			case 0x02:BlueTooth_Mode = 2;break;
			case 0x03:BlueTooth_Mode = 3;break; 
			case 0x04:BlueTooth_Mode = 4;break; 
		} 
		UART_SendByte(BlueTooth_Value);
	}
}


/*********************************************************************************** 
函数名称:OLED_Show
函数功能:OLED显示各项命令及检测数据
***********************************************************************************/
void OLED_Show(void)
{
	LightPower_Limit = AT24C02_ReadByte(0);                		   	   //将存储的光强阈值取出来
	OLED_ShowNum(110,0,LightPower_Limit,2, 16);                	   	   //显示光强阈值
	LightPower = XPT2046_ReadAD(XPT2046_VBAT_8) * 4;               	   //读取光强(0 ~ 100)
	OLED_ShowNum(23, 0,LightPower, 2, 16);                         	   //显示光强
//	LightPower = XPT2046_ReadAD(XPT2046_YP_8) * 4;               	   //读取温度(0 ~ 100)
//	OLED_ShowNum(47, 2,LightPower, 2, 16);                         	   //显示温度
	
	if(LightPower < LightPower_Limit)OLED_ShowString(0, 2,"Night", 16);//显示白天
	else OLED_ShowString(0, 2, "Day  ", 16); 			         	   //显示黑夜
	
	if(RainSensor == Rain)OLED_ShowString(94, 2, "Rain", 16);     	   //显示有雨
	else if(RainSensor == Dry)OLED_ShowString(94, 2, "Dry ", 16);	   //显示无雨
	
	if(Mode == Manual)OLED_ShowString(0, 4, "Manual", 16);             //显示手动模式
	else if(Mode == Auto)OLED_ShowString(0, 4, "Auto  ", 16);          //显示自动模式

	if(Action == Withdraw)OLED_ShowString(63, 4, "Withdraw", 16);	   //显示收回晾衣架
	else if(Action == Extend)OLED_ShowString(63, 4, "Extend  ", 16);   //显示伸出晾衣架
	
	if(Flag_Speech_MatrixAndBlueTooth == Speech_Contral)OLED_ShowString(0, 6, "Speak ", 16);//显示语音控制模式
	else if(Flag_Speech_MatrixAndBlueTooth == MatrixAndBlueTooth_Contral)OLED_ShowString(0, 6, "Matrix", 16);//显示非语音控制
}

MatrixKey.c

 #include <REGX51.H>
 #include <delay.h>
 
 //定时器扫描矩阵键盘
 unsigned char Scan_MatrixKeyNum;
 /**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval 键码KeyNum
  */  
unsigned char MatrixKey_GetState(void)
 {
	 //列扫描
	 unsigned char MatrixKeyNum = 0;//这一步赋值0很重要,不能省略
	 P1=0xff;     //全部复位
	 P1_0=0;      //确定扫描列数          
	 if(P1_4==0){MatrixKeyNum=16;}
	 if(P1_5==0){MatrixKeyNum=12;}
	 if(P1_6==0){MatrixKeyNum=8;}
	 if(P1_7==0){MatrixKeyNum=4;}
	 
	 
	 P1=0xff;
	 P1_1=0;
	 if(P1_4==0){MatrixKeyNum=15;}
	 if(P1_5==0){MatrixKeyNum=11;}
	 if(P1_6==0){MatrixKeyNum=7;}
	 if(P1_7==0){MatrixKeyNum=3;}
	 
	 P1=0xff;
	 P1_2=0;
	 if(P1_4==0){MatrixKeyNum=14;}
	 if(P1_5==0){MatrixKeyNum=10;}
	 if(P1_6==0){MatrixKeyNum=6;}
	 if(P1_7==0){MatrixKeyNum=2;}
	 
	 P1=0xff;
	 P1_3=0;
	 if(P1_4==0){MatrixKeyNum=13;}
	 if(P1_5==0){MatrixKeyNum=9;}
	 if(P1_6==0){MatrixKeyNum=5;}
	 if(P1_7==0){MatrixKeyNum=1;}
	  
	 return MatrixKeyNum;
 }

 /**
  * @brief  中断时执行的函数,功能是检测按键落下并返回相应的值,去除了原来的delay和死循环
  * @param  无
  * @retval 无
  */
void MatrixKey_Loop(void)
{
	static unsigned char laststate,nowstate;
	laststate = nowstate;
	nowstate = MatrixKey_GetState();
	//更换10位置可以按下即变,不需松手
	if(laststate==1&&nowstate==0)Scan_MatrixKeyNum = 1;	
	else if(laststate==0 && nowstate == 2 )Scan_MatrixKeyNum = 2;	
	else if(laststate==0 && nowstate == 3 )Scan_MatrixKeyNum = 3;	
	else if(laststate==0 && nowstate == 4 )Scan_MatrixKeyNum = 4;	
	else if(laststate==0 && nowstate == 5 )Scan_MatrixKeyNum = 5;	
	else if(laststate==0 && nowstate == 6 )Scan_MatrixKeyNum = 6;	
	else if(laststate==0 && nowstate == 7 )Scan_MatrixKeyNum = 7;	
	else if(laststate==0 && nowstate == 8 )Scan_MatrixKeyNum = 8;	
	else if(laststate==0 && nowstate == 9 )Scan_MatrixKeyNum = 9;	
	else if(laststate==0 && nowstate == 10)Scan_MatrixKeyNum = 10;	
	else if(laststate==0 && nowstate == 11)Scan_MatrixKeyNum = 11;	
	else if(laststate==0 && nowstate == 12)Scan_MatrixKeyNum = 12;	
	else if(laststate==0 && nowstate == 13)Scan_MatrixKeyNum = 13;	
	else if(laststate==0 && nowstate == 14)Scan_MatrixKeyNum = 14;	
	else if(laststate==0 && nowstate == 15)Scan_MatrixKeyNum = 15;	
	else if(laststate==0 && nowstate == 16)Scan_MatrixKeyNum = 16;	
}
/**
  * @brief  返回按键键码
  * @param  无
  * @retval 键码
  */
unsigned char MatrixKey(void)
{
	unsigned char temp;
	temp = Scan_MatrixKeyNum;     //返回按键值前对Scan_KeyNum清零,防止不按下时返回原值
	Scan_MatrixKeyNum = 0;
	return temp;
}

Servo.c

#include <REGX51.H>
#include "MatrixKey.h"
#include "delay.h"

#define uint8_t unsigned char
sbit PWM_Servo = P0^7;

uint8_t Angle;

void contral_0(void)
{
	Angle = 0;   
	Delay_ms(20);
}
void contral_4(void)
{
	Angle = 4;   
	Delay_ms(20);
}void contral_1(void)
{
	Angle = 1;   
	Delay_ms(20);
}

//定时中断函数
void Timer0_Routine () interrupt 1
{
	static unsigned int T0count1, T0count2;//静态局部变量

	TL0 = 0x33;				     //重新赋初值
	TH0 = 0xFE;	
	
	//舵机转动
	T0count1++;
	if(T0count1 == 40)           //0.5ms * 40 = 20ms
	{
		T0count1 = 0;
	}
	if(T0count1 <= Angle)
	{
		 PWM_Servo = 1;
	}
	else
	{
		 PWM_Servo = 0;
	}
	//定时器扫描矩阵键盘
	T0count2++;
	if(T0count2 == 100)           //0.5ms * 100 = 50ms
	{
		T0count2 = 0;
		MatrixKey_Loop();
	}
}
	

OLED.c

#include <REGX51.H>
#include "OLED.h"
#include "OLED_Font.h"  	 
//OLEDµÄÏÔ´æ
//´æ·Å¸ñʽÈçÏÂ.
//[0]0 1 2 3 ... 127	
//[1]0 1 2 3 ... 127	
//[2]0 1 2 3 ... 127	
//[3]0 1 2 3 ... 127	
//[4]0 1 2 3 ... 127	
//[5]0 1 2 3 ... 127	
//[6]0 1 2 3 ... 127	
//[7]0 1 2 3 ... 127 			   



/**********************************************
//IIC Start
**********************************************/
void IIC_Start()
{

	OLED_SCLK_Set() ;
	OLED_SDIN_Set();
	OLED_SDIN_Clr();
	OLED_SCLK_Clr();
}

/**********************************************
//IIC Stop
**********************************************/
void IIC_Stop()
{
	OLED_SCLK_Set() ;
//	OLED_SCLK_Clr();
	OLED_SDIN_Clr();
	OLED_SDIN_Set();
	
}

void IIC_Wait_Ack()
{
	OLED_SCLK_Set() ;
	OLED_SCLK_Clr();
}

/**********************************************
// IIC Write byte
**********************************************/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
	unsigned char i;
	unsigned char m,da;
	da=IIC_Byte;
	OLED_SCLK_Clr();
	for(i=0;i<8;i++)		
	{
		m=da;
		m=m&0x80;
		if(m==0x80)
		{OLED_SDIN_Set();}
		else OLED_SDIN_Clr();
			da=da<<1;
		OLED_SCLK_Set();
		OLED_SCLK_Clr();
	}
}

/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{
   IIC_Start();
   Write_IIC_Byte(0x78);            //Slave address,SA0=0
   IIC_Wait_Ack();	
   Write_IIC_Byte(0x00);			//write command
   IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Command); 
   IIC_Wait_Ack();	
   IIC_Stop();
}

/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
   IIC_Start();
   Write_IIC_Byte(0x78);			//D/C#=0; R/W#=0
   IIC_Wait_Ack();	
   Write_IIC_Byte(0x40);			//write data
   IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Data);
   IIC_Wait_Ack();	
   IIC_Stop();
}

void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
	if(cmd)
	{
		Write_IIC_Data(dat);
    }
	else 
	{
	Write_IIC_Command(dat);	
	}
}

/********************************************
// fill_Picture
********************************************/
void fill_picture(unsigned char fill_Data)
{
	unsigned char m,n;
	for(m=0;m<8;m++)
	{
		OLED_WR_Byte(0xb0+m,0);		//page0-page1
		OLED_WR_Byte(0x02,0);		//low column start address
		OLED_WR_Byte(0x10,0);		//high column start address
		for(n=0;n<128;n++)
		{
			OLED_WR_Byte(fill_Data,1);
		}
	}
}


/***********************Delay****************************************/
void Delay_50ms(unsigned int Del_50ms)
{
	unsigned int m;
	for(;Del_50ms>0;Del_50ms--)
		for(m=6245;m>0;m--);
}

void Delay_1ms(unsigned int Del_1ms)
{
	unsigned char j;
	while(Del_1ms--)
	{	
		for(j=0;j<123;j++);
	}
}

//×ø±êÉèÖÃ
void OLED_Set_Pos(unsigned char x, unsigned char y) 
{ 	
	OLED_WR_Byte(0xb0+y,OLED_CMD);
	OLED_WR_Byte((((x+2)&0xf0)>>4)|0x10,OLED_CMD);
	OLED_WR_Byte(((x+2)&0x0f),OLED_CMD); 
}  

//¿ªÆôOLEDÏÔʾ    
void OLED_Display_On(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDCÃüÁî
	OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
	OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}

//¹Ø±ÕOLEDÏÔʾ     
void OLED_Display_Off(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDCÃüÁî
	OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
	OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}	

//ÇåÆÁº¯Êý,ÇåÍêÆÁ,Õû¸öÆÁÄ»ÊǺÚÉ«µÄ!ºÍûµãÁÁÒ»Ñù!!!	  
void OLED_Clear(void)  
{  
	uint8_t i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //ÉèÖÃÒ³µØÖ·£¨0~7£©
		OLED_WR_Byte (0x02,OLED_CMD);      //ÉèÖÃÏÔʾλÖáªÁе͵ØÖ·
		OLED_WR_Byte (0x10,OLED_CMD);      //ÉèÖÃÏÔʾλÖáªÁиߵØÖ·   
		for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 
	} //¸üÐÂÏÔʾ
}

void OLED_On(void)  
{  
	uint8_t i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //ÉèÖÃÒ³µØÖ·£¨0~7£©
		OLED_WR_Byte (0x02,OLED_CMD);      //ÉèÖÃÏÔʾλÖáªÁе͵ØÖ·
		OLED_WR_Byte (0x10,OLED_CMD);      //ÉèÖÃÏÔʾλÖáªÁиߵØÖ·   
		for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA); 
	} //¸üÐÂÏÔʾ
}

//ÔÚÖ¸¶¨Î»ÖÃÏÔʾһ¸ö×Ö·û,°üÀ¨²¿·Ö×Ö·û
//x:0~127
//y:0~63
//mode:0,·´°×ÏÔʾ;1,Õý³£ÏÔʾ				 
//size:Ñ¡Ôñ×ÖÌå 16/12 
void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr,uint8_t Char_Size)
{      	
	unsigned char c=0,i=0;	
		c=chr-' ';//µÃµ½Æ«ÒƺóµÄÖµ			
		if(x>Max_Column-1){x=0;y=y+2;}
		if(Char_Size ==16)
		{
			OLED_Set_Pos(x,y);	
			for(i=0;i<8;i++)
				OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
			OLED_Set_Pos(x,y+1);
			for(i=0;i<8;i++)
				OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
		}
		else 
		{	
			OLED_Set_Pos(x,y);
			for(i=0;i<6;i++)
				OLED_WR_Byte(F6x8[c][i],OLED_DATA);
			
		}
}

//m^nº¯Êý
uint32_t oled_pow(uint8_t m,uint8_t n)
{
	uint32_t result=1;	 
	while(n--)result*=m;    
	return result;
}		

//ÏÔʾ2¸öÊý×Ö
//x,y :Æðµã×ø±ê	 
//len :Êý×ÖµÄλÊý
//size:×ÖÌå´óС
//mode:ģʽ	0,Ìî³äģʽ;1,µþ¼Óģʽ
//num:ÊýÖµ(0~4294967295);	 		  
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size2)
{         	
	uint8_t t,temp;
	uint8_t enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/oled_pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
				continue;
			}else enshow=1; 
		 	 
		}
	 	OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2); 
	}
} 

//ÏÔʾһ¸ö×Ö·û´®
void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t Char_Size)
{
	unsigned char j=0;
	while (chr[j]!='\0')
	{		
		OLED_ShowChar(x,y,chr[j],Char_Size);
		x+=8;
		if(x>120){x=0;y+=2;}
		j++;
	}
}

ÏÔʾºº×Ö
//void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no)
//{      			    
//	uint8_t t,adder=0;
//	OLED_Set_Pos(x,y);	
//    for(t=0;t<16;t++)
//	{
//		OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
//		adder+=1;
//    }	
//	OLED_Set_Pos(x,y+1);	
//    for(t=0;t<16;t++)
//	{	
//		OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
//		adder+=1;
//    }					
//}

/***********¹¦ÄÜÃèÊö£ºÏÔʾÏÔʾBMPͼƬ128¡Á64Æðʼµã×ø±ê(x,y),xµÄ·¶Î§0¡«127£¬yΪҳµÄ·¶Î§0¡«7*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{ 	
	unsigned int j=0;
	unsigned char x,y;
  
	if(y1%8==0) y=y1/8;      
	else y=y1/8+1;
	for(y=y0;y<y1;y++)
	{
		OLED_Set_Pos(x0,y);
		for(x=x0;x<x1;x++)
	    {      
	    	OLED_WR_Byte(BMP[j++],OLED_DATA);	    	
	    }
	}
} 

//³õʼ»¯SSD1306					    
void OLED_Init(void)
{ 	
 
	OLED_WR_Byte(0xAE,OLED_CMD);//--display off
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  
	OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
	OLED_WR_Byte(0x81,OLED_CMD); // contract control
	OLED_WR_Byte(0xFF,OLED_CMD);//--128   
	OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap 
	OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
	OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
	OLED_WR_Byte(0x00,OLED_CMD);//
	
	OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
	OLED_WR_Byte(0x80,OLED_CMD);//
	
	OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
	OLED_WR_Byte(0x05,OLED_CMD);//
	
	OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
	OLED_WR_Byte(0xF1,OLED_CMD);//
	
	OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
	OLED_WR_Byte(0x12,OLED_CMD);//
	
	OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
	OLED_WR_Byte(0x30,OLED_CMD);//
	
	OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
	OLED_WR_Byte(0x14,OLED_CMD);//
	
	OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}  

XPT2046.c

#include <REGX51.H>
#include "delay.h"
 
sbit XPT2046_CS = P3^5;
sbit XPT2046_DCLK = P3^6;
sbit XPT2046_DIN = P3^4;
sbit XPT2046_DOUT = P3^7;

/**
  * @brief  ¶ÁÈ¡µçѹ¾­A/Dת»»ºóµÄÖµ
  * @param  Command:ÃüÁî×Ö£¬¶ÁÈ¡ÄĸöÀàÐÍ
  * @retval ADValue£º¶ÁÈ¡µÄÖµ
  */
unsigned int XPT2046_ReadAD (unsigned char Command)
{
	unsigned int ADValue=0;
	unsigned char i;
	XPT2046_DCLK=0;
	XPT2046_CS=0;
	//дÈëÃüÁî×Ö
	for(i=0;i<8;i++)
	{
		XPT2046_DIN=Command&(0x80>>i);
		XPT2046_DCLK=1;
		XPT2046_DCLK=0;
	}
	//¶ÁÈ¡Êý¾Ý
	for(i=0;i<16;i++)
	{
		XPT2046_DCLK=1;
		XPT2046_DCLK=0;
		if(XPT2046_DOUT)ADValue|=(0x8000>>i);
	}
	XPT2046_CS=1;
	if(Command&0x08)
	{
		return ADValue>>8;//8λ·Ö±æÂÊ£¬Ç°8λÓÐÓ㬺ó°ËλΪ0£»ÊýÖµ±»·Å´óÁË2^8±¶£¬¹ÊÐèÒªÓÒÒÆ²ÅÄÜÏÔÊ¾ÕæÊµÊýÖµ0^255
	}
	else
	{
		return ADValue>>4;
	}
}

AT24C02.c

#include <REGX51.H>
#include "I2C.h"

#define AT24C02_ADDRESS 0xA0

/**
  * @brief  ÏòAT24C02´æ´¢Æ÷ÖÐдÈëÊý¾Ý 
  * @param  WORD_ADDRESS£¬ÒªÐ´ÈëµÄ×Ö½Ú¿Õ¼äµÄλÖÃ
  * @param  Data£¬ÒªÐ´ÈëµÄÊý¾Ý
  * @retval ÎÞ
  */
void AT24C02_WriteByte(unsigned char WORD_ADDRESS,Data)
{
	I2C_Start();                  //¿ªÊ¼
	I2C_SendByte(AT24C02_ADDRESS);//¶ÔAT24C02·¢ËÍдÃüÁî
	I2C_ReceiveACK();             //½ÓÊÕÓ¦´ð
	I2C_SendByte(WORD_ADDRESS);   //Ö¸¶¨ÒªÐ´ÈëµÄ×ÖµØÖ·
	I2C_ReceiveACK();             //½ÓÊÕÓ¦´ð
	I2C_SendByte(Data);           //ÏòEEPROM´æ´¢Æ÷·¢ËÍÊý¾Ý
	I2C_ReceiveACK();             //½ÓÊÕÓ¦´ð
	I2C_Stop();                   //Í£Ö¹
}
 

/**
  * @brief  ´Ó´æ´¢Æ÷ÖжÁÈ¡Êý¾Ý
  * @param  WORD_ADDRESS£¬Òª¶ÁÈ¡µÄ×Ö½ÚµÄλÖÃ
  * @retval Êý¾ÝµÄÖµ£¬Ð´ÈëµÄÊý¾ÝÊÇʲôÐÎʽ£¬¶Á³öÀ´µÄ¾ÍÊÇʲôÐÎʽ
  */
unsigned char AT24C02_ReadByte(unsigned char WORD_ADDRESS)
{
	unsigned char Data;
	/**********Ö¸¶¨µØÖ·*********/
	
	I2C_Start();                      //¿ªÊ¼
	I2C_SendByte(AT24C02_ADDRESS);    //·¢ËÍдÃüÁî 
	I2C_ReceiveACK();                 //½ÓÊÕÓ¦´ð
	I2C_SendByte(WORD_ADDRESS);       //È·¶¨Ä¿±ê¿Õ¼äλÖÃ
	I2C_ReceiveACK();                 //ûÓÐÍ£Ö¹º¯Êý£¬ÒòΪÊÇͬһ¸öÊý¾ÝÖ¡
	
	/***************************/
	
	I2C_Start();                       //¿ªÊ¼
	I2C_SendByte(AT24C02_ADDRESS|0x01);//·¢ËͶÁÃüÁî
	I2C_ReceiveACK();                  //½ÓÊÕÓ¦´ð
	Data = I2C_ReceiveByte();          //µÃµ½¶ÁÈ¡µÄ×Ö½ÚÊý¾Ý
	I2C_SendACK(1);                    //·¢ËÍÓ¦´ð£¬ÕâÀïÒ»°ãΪ1£¨·ÇÓ¦´ð£©
	I2C_Stop();                        //Í£Ö¹
	
	/***************************/
	
	return Data;
}

OpenMV图像识别程序(Python)

# Untitled - By: Lenovo - 星期二 12月 19 2023

import sensor, image, time, pyb
from pyb import LED
from pyb import Pin
from pyb import Timer

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(time=2000)    # 跳帧
sensor.set_auto_gain(False)      # 关闭自动增益
sensor.set_auto_whitebal(False)  # 关闭白平衡
clock = time.clock()

# 颜色阈值
red_threshold_1 = [(24, 54, 55, 19, 16, 68)]
red_threshold = [(40, 72, 64, -104, 28, -10)]

P7_State = 2

P7_Out = Pin('P7',Pin.OUT_PP)  # P7设置成推挽输出

tim = Timer(2, freq=1)      # 1hz,1s检测一次

def tick(timer):      #这里开启了一个定时器
    global key1, P7_State, P8_State
    key1 = pin1.value()
    P7_State = P7_Out.value()  # 获取P7的引脚状态,0 or 1

tim.callback(tick)


# 以红色矩形作为衣服标志
if __name__ == "__main__":
    while True:
        clock.tick()
        print(P7_State)
        img = sensor.snapshot()
        for r in img.find_rects(threshold=10000):
            if r.rect() == 0:
                P7_Out.low()
                print("无衣服", P7_State)
            area = r.rect()  # 检测到矩形
            blobs_red = img.find_blobs(red_threshold, roi = area)
            if blobs_red: # 在矩形框内内检测红色色块,说明有衣服
                img.draw_rectangle(r.rect(), color = (0, 0, 255))
                # 在检测到的衣服上画框
                P7_Out.high()   # 设置p7_out引脚为高电平
                print("有衣服", P7_State)
                # LED_R.on()#红灯亮
                # time.sleep_ms(150)
                # LED_R.off()
            else:
                P7_Out.low()
                print("无衣服", P7_State)


STM32辅控检测湿度(ADC)

#include "stm32f10x.h"  // Device header              
#include "Stdint.h"
#include "OLED.h"
#include "AD.h"

uint16_t ADValue;
uint8_t Humidity_Past, Humidity_Now, Humidity_Limit;
void Humidity_Init(void);

int main(void)
{
	AD_Init();
	OLED_Init();
	Humidity_Init();
	
	Humidity_Limit = 40;
	
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Humidity:00");
	while(1)
	{
		ADValue = ADC_GetValue();
		Humidity_Past = Humidity_Now;
		Humidity_Now = (float)ADValue / 4095 * 100;//0x00~0x63
		
		OLED_ShowNum(1, 9, ADValue, 4);//第一行显示未转换值
		if(Humidity_Past != Humidity_Now)
		{
			OLED_ShowNum(2, 12, Humidity_Now, 2);//第二行显示转换值    
		}			
		
		if(Humidity_Now > Humidity_Limit)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_2); 
			OLED_ShowString(4, 1, "Dry ");
			OLED_ShowNum(4, 16, GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2),1);
		}
		else 
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_2); 
			OLED_ShowString(4, 1, "Rain");
			OLED_ShowNum(4, 16, GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2),1);
		}
	}
}

void Humidity_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_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//数据跳变是正常现象,但使用看门狗中断,利用阈值完成例如开关灯操作时,会出现灯闪的不良现象
//解决方法:施密特滤波;均值滤波;左对齐减小分辨率(精度),把数据的尾数去掉
//由于ADC内部硬件问题,ADValue最高时不能达到3.3V

硬件成品演示

单片机课设演示视频(基于51单片机的智能晾衣架)

注:演示视频全程没有断电,所以没有展示出掉电保存数据功能。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐