基于STM32F103系列单片机的家用燃气监控系统

家用燃气监控系统 

一,题目任务要求和具体功能要求

1.1任务要求:

设计基于STM32F103系列单片机的家用燃气监控系统。系统需要实现以下功能:

1. 采集家居厨房环境中的天然气和一氧化碳浓度数据。

2. 当浓度数据超过设定阈值时,进行声光报警提示。

3. 利用定时器输出PWM信号驱动2路舵机摆动:

   1路舵机控制燃气开关。

   1路舵机控制窗户开关。

4. 通过串口通信将数据传输到远端PC机,并在PC端进行模拟显示和报警提示。

1.2具体功能要求:

1. 设计单片机的最小系统(基本项)

2. 串口通信电路设计,实现与PC机通信(基本项)

3. 按键电路设计(基本项)

4. 燃气浓度采集电路设计(基本项)

5. 通用输入输出程序设计(基本项)

6. 燃气浓度采样程序设计(基本项)

7. 串口通信程序设计(基本项)

8. 定时器PWM程序设计(基本项)

9. 提高测量精度的方法(创新项)

二、设计方案论证

2.1 系统总体架构

系统主要由以下几个部分组成:

传感器模块:包括天然气传感器和一氧化碳传感器,用于采集环境中的燃气浓度数据。

主控模块:基于STM32F103单片机,负责数据处理、判断、控制输出。

报警模块:包括LED指示灯和蜂鸣器,用于声光报警提示。

执行模块:通过PWM信号控制2路舵机,分别控制燃气开关和窗户开关。

通信模块:通过串口通信将数据传输到PC端,实现数据的实时监控和报警提示。

用户接口模块:包括按键,用于系统设置和手动控制。

显示模块:显示天然气传感器(MQ-4)与一氧化碳传感器(MQ-7)的阈值与实时数据。

 2.2 主要功能模块设计

 2.2.1 单片机最小系统设计

核心器件:STM32F103C8T6单片机。

电源部分:提供稳压电源,确保单片机及各外设正常工作。

时钟部分:外接晶振(如8MHz)提供单片机工作时钟。

复位电路:复位电路确保单片机上电后正常启动。

编程接口:连接下载接口线,实现程序下载与调试。

2.2.2 串口通信电路设计

串口接口:利用STM32的USART接口,通过串口转USB模块与PC机通信。

电平转换:确保串口信号与USB模块的电平匹配,避免信号失真。

连接方式:TXD和RXD分别连接到单片机的对应引脚,GND连接共地。

2.2.3 按键电路设计

按键数量:设置三个按键,一个用于切换控制传感器的阈值,一个用于控制传感器阈值的增大,一个用于控制传感器阈值的减少。

去抖设计:每个按键并联一个电容进行去抖处理,确保按键信号稳定。

接口电路:每个按键一端连接VCC,另一端连接到单片机的GPIO引脚。

2.2.4 燃气浓度采集电路设计

传感器选择:选用高灵敏度的天然气传感器(MQ-4)和一氧化碳传感器(MQ-7)。

接口电路:传感器VCC接口连接到VCC(5V),传感器GND接口接地线,采用AO与单片机GPIO口链接

2.2.5 通用输入输出程序设计

GPIO:配置各个GPIO口的输入输出模式,包括传感器输入、声光报警输出、舵机控制输出等。

2.2.6 燃气浓度采样程序设计

ADC配置:配置ADC模块,设定采样频率和分辨率,便于传感器的浓度采集。

2.2.7 串口通信程序设计

初始化:配置USART的波特率、数据位、停止位和校验方式。

数据发送:将采集到的浓度数据通过串口发送给PC端。

数据接收:根据需要接收来自PC端的控制指令。

2.2.8 定时器PWM程序设计

定时器配置:配置定时器模块以产生PWM信号,控制舵机的角度。

PWM参数:设定PWM频率和占空比,确保舵机的正常驱动。

舵机控制:通过调整PWM信号的占空比,实现舵机的精确定位控制。

2.2.9 提高测量精度的方法(创新项)

采用平均滤波算法来提高采样数据的精确度和减少噪声。

2.3工作原理

家用燃气监控系统通过MQ-4和MQ-7传感器实时检测天然气和一氧化碳浓度,STM32单片机处理传感器数据并判断是否超标。若浓度超标,系统通过蜂鸣器和LED报警,并通过舵机控制燃气阀门和窗户。数据通过串口传输至PC进行远程监控,用户可通过按键调整报警阈值。该系统提供了自动报警和安全控制功能,保障家庭安全。

三、硬件部分设计

3.1硬件部分的组成,器件的选型(主要参数,外部引脚定义和实物外形),器件清单

序号

名称

型号

外部引脚定义及参数

实际使用数量

实物外形

1

0.96寸OLED显示屏窄白光IIC

HS96L03W2C03

GND VCC SCL SDA

2

舵机

SG90

GND VCC(5V)

PWM

2

3

一氧化碳传感器

MQ-7

VCC GND DO AO

1

4

天然气传感器

MQ-4

VCC GND DO AO

1

5

通用串口通信模块

CH3430

GND 5V 3V TXD RXD  

1

6

发光二极管(红色,蓝色)

2

7

蜂鸣器

TMB12A05

GND VCC I/O

8

电容

0.1uf

9

按键

3

3.2各部分硬件电路图,总体电路原理

3.3硬件仿真电路图

四、软件部分设计

4.1模块函数

4.1.1 按键控制模块:包括按键初始化,按键获取键码。
include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * 函    数:按键初始化
  * 参    数:无
  * 返 回 值:无
  */
void Key_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_4| GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA2,PA4和PA6引脚初始化为上拉输入
}

/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
	
	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == 0)			//读PA2输入寄存器的状态,如果为0,则代表按键1按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 1;												//置键码为1
	}
	
	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0)			//读PA4输入寄存器的状态,如果为0,则代表按键2按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 2;												//置键码为2
	}
	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)			//读PA6输入寄存器的状态,如果为0,则代表按键3按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 3;												//置键码为2
	}
	
	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

4.1.2 蜂鸣器控制模块:包括蜂鸣器初始化,蜂鸣器开启,蜂鸣器关闭。
#include "stm32f10x.h"                  // Device header
void Buzzer_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_ON(void)
{
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_OFF(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0)
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_12);
	}
	else
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	}
}
4.1.3 PWM控制模块:包括PWM初始化,PWM设置CCR。
#include "stm32f10x.h"                  // Device header
void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);		
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);			
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);															TIM_InternalClockConfig(TIM3);		
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;			
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             
	TIM_OCInitTypeDef TIM_OCInitStructure;							
	TIM_OCStructInit(&TIM_OCInitStructure);                                                                                    
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  
	TIM_OCInitStructure.TIM_Pulse = 0;							
	TIM_OC3Init(TIM3, &TIM_OCInitStructure);                       
	TIM_OC4Init(TIM3, &TIM_OCInitStructure);                       
	TIM_Cmd(TIM3, ENABLE);			
	
}

void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM3, Compare);		//??CCR2??
}
void PWM_SetCompare4(uint16_t Compare)
{
	TIM_SetCompare4(TIM3, Compare);		//??CCR2??
}
4.1.4 舵机控制模块:包括舵机初始化,舵机设置角度。
#include "stm32f10x.h"                  // Device header
#include "PWM.h"
void Servo_Init(void)
{
	PWM_Init();								}
void Servo_SetAngle0(float Angle1)
{
	PWM_SetCompare3(Angle1 / 180 * 2000 + 500);									
}
void Servo_SetAngle1(float Angle2)
{
	PWM_SetCompare4(Angle2 / 180 * 2000 + 500);	
}
4.1.5 LED控制模块:包括LED初始化,LED开启,LED关闭。
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:LED初始化
  * 参    数:无
  * 返 回 值:无
  */
void LED_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB11和PB10引脚初始化为推挽输出
	
	/*设置GPIO初始化后的默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_11| GPIO_Pin_10);				//设置B11和PB10引脚为高电平
}

/**
  * 函    数:LED1开启
  * 参    数:无
  * 返 回 值:无
  */
void LED1_ON(void)
{
	GPIO_ResetBits(GPIOB, GPIO_Pin_11);		//设置PA1引脚为低电平
}

/**
  * 函    数:LED1关闭
  * 参    数:无
  * 返 回 值:无
  */
void LED1_OFF(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_11);		//设置PA1引脚为高电平
}

/**
  * 函    数:LED1状态翻转
  * 参    数:无
  * 返 回 值:无
  */
void LED1_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_11) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_11);					//则设置PA1引脚为高电平
	}
	else													//否则,即当前引脚输出高电平
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_11);					//则设置PA1引脚为低电平
	}
}

/**
  * 函    数:LED2开启
  * 参    数:无
  * 返 回 值:无
  */
void LED2_ON(void)
{
	GPIO_ResetBits(GPIOB, GPIO_Pin_10);		//设置PA2引脚为低电平
}

/**
  * 函    数:LED2关闭
  * 参    数:无
  * 返 回 值:无
  */
void LED2_OFF(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_10);		//设置PA2引脚为高电平
}

/**
  * 函    数:LED2状态翻转
  * 参    数:无
  * 返 回 值:无
  */
void LED2_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_10) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
	{                                                  
		GPIO_SetBits(GPIOB, GPIO_Pin_10);               		//则设置PA2引脚为高电平
	}                                                  
	else                                               		//否则,即当前引脚输出高电平
	{                                                  
		GPIO_ResetBits(GPIOB, GPIO_Pin_10);             		//则设置PA2引脚为低电平
	}
}

4.1.6O LED控制模块:包括OLED初始化,OLED显示字符串,OLED显示数字,OLED显示汉字,OLED清屏。
#include "stm32f10x.h"
#include "OLED_Font.h"
#include <Stdio.h>      //使用Sprintf时要包含

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);     //GPIOB
	
		GPIO_InitTypeDef GPIO_InitStructure;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;   // PB8  SCL
		GPIO_Init(GPIOB, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;   // PB9  SDA
		GPIO_Init(GPIOB, &GPIO_InitStructure);
		
		OLED_W_SCL(1);
		OLED_W_SDA(1);
}

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(Byte & (0x80 >> i));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}

}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}
/**
  * @brief  OLED显示波形函数
	* @param  Wave_sum波形值,范围:0~15
  * @retval 无
  */
void OLED_Wave(u8 Wave_Sum)//波形刷新
{
		static u8 i;		  
		OLED_WriteCommand (0xb6);    						//设置页地址
		OLED_WriteCommand((i & 0x0f));      		//设置显示位置—列低地址
		OLED_WriteCommand(((i & 0xf0) >> 4) |0x10);      	//设置显示位置—列高地址   
		OLED_WriteData(PPG_Wave_Fig[Wave_Sum*2]); 
	
		OLED_WriteCommand (0xb7);    						//设置页地址
		OLED_WriteCommand ((i & 0x0f));      				//设置显示位置—列低地址
		OLED_WriteCommand (((i & 0xf0) >> 4) |0x10);      	//设置显示位置—列高地址 
		OLED_WriteData(PPG_Wave_Fig[Wave_Sum*2+1]);		//显示数据,PPG_wave_fig[n]的值跟OLED寄存器配置的扫描方式相关
		i++;
		if(i>127) i=0;
}
/**
  * @brief  OLED显示中文
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
* @param  ChineseLoca 中文字符位置,范围:自定义
  * @retval 无
  */
void OLED_ShowChinese(uint8_t Line,uint8_t Column,uint8_t ChineseLoca)
{
	uint8_t i = 0;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 16);		//设置光标位置在上半部分
	for (i = 0; i < 16; i++)
	{
		OLED_WriteData(Hzk[2*ChineseLoca][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line-1) * 2+1, ((Column - 1) * 16));	//设置光标位置在下半部分
	for (i = 0; i < 16; i++)
	{
		OLED_WriteData(Hzk[2*ChineseLoca+1][i]);		//显示下半部分内容
	}
}

void OLED_ShowFloat(uint8_t Line, uint8_t Column,float Parameter)    //把可变参数Parameter以小数类型显示
{
	  char buf[20];
	  sprintf(buf,"%.2f",Parameter);    //保留两位小数,并以逐个单字符存入数组buf中,包括小数点   Parameter=buf
	  OLED_ShowString(Line, Column, buf); //在line行Column列输出小数Parameter
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}
4.1.7 AD采样模块:包括ADC初始,获取ADC转换值等。
#include "stm32f10x.h"               
#include "Delay.h"                
#include "OLED.h"
#include "math.h"
/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	 
	RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	ADC_DeInit(ADC1); 
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	
	ADC_InitStructure.ADC_NbrOfChannel = 1;	
	ADC_Init(ADC1, &ADC_InitStructure);	
	ADC_Cmd(ADC1, ENABLE);	
	ADC_ResetCalibration(ADC1);		 
	while(ADC_GetResetCalibrationStatus(ADC1));
	ADC_StartCalibration(ADC1);	
	while(ADC_GetCalibrationStatus(ADC1));	
}				  
u16 Get_Adc(u8 ch)   
{
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_71Cycles5 );	    
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
	return ADC_GetConversionValue(ADC1);	
}
4.1.8 采样数据处理模块:
u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		Delay_ms(5);
	}
	return temp_val/times;
} 	 
4.1.9 传感器收集模块:
float MQ_4_work(void)   
{
	  u16 ad = 0;
	  float mqvalue,ppm;
	
	  ad=Get_Adc_Average(ADC_Channel_1,10);        
	  mqvalue = 3.3*ad/4095;   
	  ppm=mqvalue/0.1*200;
	  //OLED_ShowFloat(1,8,mqvalue);    
	  OLED_ShowNum(2,8,ppm,4);  
    //OLED_ShowChinese(1,1,2);
	  //OLED_ShowChinese(1,2,3);
	  //OLED_ShowChinese(1,3,4);  
	  //OLED_ShowString(1,7,":");
	  OLED_ShowString(2,1,"ppm :");
	  Delay_ms(500);
	  return ppm;
}
float MQ_7_work(void)     
{
	  u16 ad = 0;
	  float mqvalue,ppm;
	
	  ad=Get_Adc_Average(ADC_Channel_0,10);       
	  mqvalue = 5*ad/4095;  
	  float RS=(5-mqvalue)/(mqvalue*0.5);
	  float Ro=6.64;
	  ppm = pow(11.5428*Ro/RS,0.6549f);
	  //OLED_ShowFloat(1,8,mqvalue);   
	  OLED_ShowNum(4,8,ppm,4);   
    //OLED_ShowChinese(1,1,2);
	  //OLED_ShowChinese(1,2,3);
	  //OLED_ShowChinese(1,3,4);  
	  //OLED_ShowString(1,7,":");
	  OLED_ShowString(4,1,"ppm :");
	  Delay_ms(50);
	  return ppm;
}
4.1.10 串口通信控制模块:包括串口初始化,串口发送数字,串口发送字符串,串口中断。
#include "stm32f10x.h"                 
#include <stdio.h>
#include <stdarg.h>
char Serial_RxPacket[100];				
uint8_t Serial_RxFlag;					
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				
	USART_InitTypeDef USART_InitStructure;				
	USART_InitStructure.USART_BaudRate = 9600;				
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	
	USART_InitStructure.USART_Parity = USART_Parity_No;		
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		
	USART_Init(USART1, &USART_InitStructure);				
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);		
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			
	NVIC_InitTypeDef NVIC_InitStructure;				
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;	
	NVIC_Init(&NVIC_InitStructure);						
	USART_Cmd(USART1, ENABLE);								
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	

}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		
	{
		Serial_SendByte(Array[i]);	
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);	
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	
	while (Y --)		
	{
		Result *= X;		
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)	
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);		
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];				
	va_list arg;				
	va_start(arg, format);			
	vsprintf(String, format, arg);	
	va_end(arg);					
	Serial_SendString(String);	
}

void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)	
	{
		uint8_t RxData = USART_ReceiveData(USART1);		
		if (RxState == 0)
		{
			if (RxData == '@' && Serial_RxFlag == 0)		
			{
				RxState = 1;			
				pRxPacket = 0;		
			}
		}
		else if (RxState == 1)
		{
			if (RxData == '\r')	
			{
				RxState = 2;		
			}
			else					
			{
				Serial_RxPacket[pRxPacket] = RxData;		
				pRxPacket ++;			
			}
		}

		else if (RxState == 2)
		{
			if (RxData == '\n')			
			{
				RxState = 0;			
				Serial_RxPacket[pRxPacket] = '\0';		
				Serial_RxFlag = 1;	
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);	
	}
}

4.2主函数:

1头文件定义:引入所有必要的模块头文件,以支持各功能模块的操作。

2. 初始化:程序开始时,初始化OLED显示屏、ADC(模数转换器)、舵机、按键、LED灯、蜂鸣器和串口通信。

3. 主循环:

   读取按键状态,并根据按键操作调整报警阈值。

   读取MQ-4和MQ-7传感器的值。

   在OLED显示屏上显示当前的阈值和传感器值。

   如果传感器值超过阈值,蜂鸣器响起,LED灯亮起,并通过串口发送数据。

   如果两个传感器的值都低于阈值,舵机转动到180度。

4. 按键操作:

   按键1用于切换调整的阈值(甲烷或一氧化碳)。

   按键2和按键3用于增加或减少当前选择的阈值。

5. 传感器数据读取:

   `MQ_4_work()`函数用于读取MQ-4传感器的值。

    MQ_7_work()`函数用于读取MQ-7传感器的值。

6. 反馈机制:

   当MQ-4传感器检测到的甲烷浓度超过阈值时,蜂鸣器响起,LED2灯亮起,舵机转动到90度,并通过串口发送数据。

   当MQ-7传感器检测到的一氧化碳浓度超过阈值时,蜂鸣器响起,LED1灯亮起,舵机转动到90度,并通过串口发送数据。

7. 串口通信:

    通过`Serial_SendNumber()`和`Serial_Printf()`函数将传感器数据发送到串口。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "OLED.h"
#include "AD.h"
#include "KEY.h"
#include "Servo.h"
#include "Buzzer.h"
#include "Serial.h"
uint16_t AD0, AD1;	//定义AD值变量
uint8_t KeyNum;			//定义用于接收键码的变量
float Angle1;		//定义角度变量
float Angle2;		//定义角度变量
float taq=0;
float co=0;
float taq1=100;
float co1=100;
uint8_t KEY=0;
int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	Adc_Init();					//AD初始化
	Servo_Init();		//舵机初始化
	Key_Init();			//按键初始化
  LED_Init();
	Buzzer_Init();
	OLED_ShowString(1, 1, "MQ-4:");
	OLED_ShowString(3, 1, "MQ-7:");
	Serial_Init();
	
	while (1)
	{ 
	 
		KeyNum = Key_GetNum();			//获取按键键码
		
		if (KeyNum == 1)				//按键1按下
		KEY=KEY+KeyNum;
		if (KEY%2==0)
		{if (KeyNum==2)			
			{
				taq1+=100;
			}
			                			
			if (KeyNum==3)			
			{
				taq1-=100;			
			}
		}
		if (KEY%2!=0)
		{if (KeyNum==2)			
			{
				co1+=100;
			}
			                			
			if (KeyNum==3)			
			{
				co1-=100;			
			}
		}
		taq = MQ_4_work();
		co = MQ_7_work();
		OLED_ShowNum(1,8,taq1,4);
	  OLED_ShowNum(3,8,co1,4);
		if(taq>=taq1)
		{Angle1=90;
	
		Buzzer_ON();
		LED2_ON();
		Servo_SetAngle0(90);//设置舵机的角度为角度变量
		Servo_SetAngle1(90);//设置舵机的角度为角度变量
		Delay_ms(100);
		Delay_ms(100);
		Buzzer_OFF();
	
		
		LED2_OFF();
		Serial_SendNumber(taq, 4);
	  Serial_Printf("\r\n");
		}
		if(co>=co1)
		{Angle2=90;
		Buzzer_ON();
		LED1_ON();
		Servo_SetAngle0(90);//设置舵机的角度为角度变量
		Servo_SetAngle1(90);//设置舵机的角度为角度变量
		Delay_ms(100);
		Buzzer_OFF();
		Serial_SendNumber(co, 4);
		Serial_Printf("\r\n");
		LED1_OFF();}
		if(taq<taq1&co<co1)
		{Servo_SetAngle1(180);//设置舵机的角度为角度变量
		Servo_SetAngle0(180);//设置舵机的角度为角度变量
		}
	}
	
}

五,测试及调试

5.1 硬件测试方法

5.1.1 电源系统测试方法

使用万用表对系统各供电点进行测量,包括3.3V和5V供电轨的电压值。同时使用示波器观察电源纹波情况,测量电源纹波峰峰值。在不同负载条件下测试电源的稳定性,包括空载、满载和负载突变情况下的电压变化情况。

5.1.2 传感器模块测试方法

对MQ-4和MQ-7传感器进行标准预热程序,使用标准气体进行传感器校准。在通风环境下,使用不同浓度的标准气体测试传感器的线性度和重复性。记录传感器从检测到响应的时间,验证传感器的灵敏度和响应速度是否满足系统要求。

5.1.3 舵机控制测试方法

使用示波器测量PWM信号的频率和占空比,确保信号稳定性。测试舵机在不同角度位置的控制精度,包括0度、90度和180度等关键位置。检查舵机在频繁动作时的发热情况和工作稳定性。

5.2 软件测试方法

5.2.1 按键测试方法

通过多种按键操作方式测试按键的可靠性,包括短按、长按和快速连续按压等情况。记录按键的抖动情况,测试消抖程序的效果。验证按键在不同操作速度下的响应情况,确保按键操作的稳定性。

5.2.2 传感器数据采集测试方法

使用示波器监测ADC采样的时序,验证采样频率是否符合设计要求。进行多次采样测试,分析采样数据的稳定性和可靠性。测试ADC在不同输入信号下的转换精度,确保数据采集的准确性。

5.2.3 串口通信测试方法

使用串口调试助手进行数据收发测试,验证不同波特率下的通信质量。测试系统在持续数据传输时的稳定性,模拟通信中断情况下的系统响应。检查数据传输的准确性和实时性,确保通信协议的可靠实现。

5.2.4 报警功能测试方法

通过改变传感器输入,测试系统在不同浓度数据下的报警响应。验证报警触发和解除的阈值设置是否准确,测试报警器件(LED和蜂鸣器)的工作状态。检查报警信息的显示和传输是否正确。

5.3. 系统整体测试方法

 5.3.1 功能测试方法对系统所有功能模块进行完整性测试,包括传感器采集、数据处理、显示输出、报警控制等。验证各个模块之间的配合是否协调,确保系统功能的完整性和可靠性。

 5.3.2 稳定性测试方法

设置系统连续运行测试,监控系统的各项参数变化。记录系统运行过程中的异常情况,包括死机、重启等。检查内存使用情况,确保没有内存泄漏问题。

5.3.3 异常处理测试方法

模拟各种可能的故障情况,如断电、传感器失效、通信中断等。测试系统的故障检测和处理能力,验证系统的自我保护功能是否有效。

5.4. 遇到的问题与解决方法

5.4.1 电路设计问题与解决方法

舵机驱动时出现动力不足问题,重新编写的PWM代码,选择5V为VCC。

5.4.2 引脚选择问题与解决方法

初始PWM引脚选择与串口引脚发生冲突。将舵机控制PWM信号改用定时器3的通道1(PB0)和通道2(PB1),避开了与串口的冲突。

5.4.3 虚焊问题与解决方法

传感器接口处出现虚焊现象,导致数据不稳定。重新使用高质量焊锡进行焊接,并使用万用表测量确保连接可靠。

5.4.4 代码结构设计问题与解决方法

代码中存在重复逻辑,造成维护困难。通过将相似功能封装成独立函数,如报警处理和传感器数据采集函数,提高了代码复用性和可维护性。

5.5. 调试结果

系统功能全部达到设计要求

传感器响应时间小于2秒

串口通信稳定,波特率9600

舵机控制精度在±2°以内

系统连续运行72小时无异常

六,总结

在这项基于STM32F103单片机的家用燃气监控系统设计中,主要涉及了嵌入式系统的各个方面。以下是我从这个项目中总结和掌握的知识和技能:

1. 嵌入式系统硬件设计

单片机的最小系统设计:熟悉STM32F103的最小系统设计,了解了如何连接电源、时钟、复位电路等基本组件。

传感器接口:掌握了如何通过模拟接口读取传感器数据,特别是MQ-4和MQ-7的气体浓度传感器。

舵机控制:学习了如何利用PWM信号控制舵机的角度,通过定时器配置和调节PWM占空比实现精确控制。

2. 软件开发技能

GPIO与定时器编程:了解了如何使用GPIO口进行输入输出操作,以及如何配置定时器产生PWM信号,驱动外部设备。

串口通信:掌握了如何通过串口与PC进行数据通信,使用串口调试助手测试数据传输的准确性和实时性。

按键扫描与去抖动:设计了按键电路并实现了去抖动处理,确保按键操作的稳定性。

数据采集与显示:使用ADC模块采集传感器数据并通过OLED屏幕实时显示,学习了如何通过程序设计来处理和展示传感器数据。

3. 调试与测试技能

电路和硬件调试:通过使用万用表和示波器进行电源电压、电流和信号质量的测试,确保硬件的稳定性和可靠性。

软件调试:通过串口调试、逻辑分析等手段测试软件的稳定性和功能的实现,确保系统在各种条件下都能正常运行。

性能优化:对系统进行性能评估,监控CPU和内存的使用,优化代码结构,确保系统的高效运行。

4. 整体系统设计与优化

系统整体架构设计:从系统功能需求出发,设计了数据采集、报警控制、舵机驱动、串口通信等模块,并实现了模块间的协调工作。

异常处理与容错设计:模拟了传感器故障、电源问题、通信中断等异常情况,设计了合适的异常处理机制,增强了系统的鲁棒性。

5. 团队协作与项目管理

团队合作:在项目中分工明确,充分利用团队成员的专业特长,共同解决系统设计中的难题。

文档编写与报告:通过编写详细的测试报告、调试记录和总结报告,锻炼了文档写作与项目管理的能力。

通过本项目,我不仅掌握了嵌入式系统的硬件与软件设计,也对嵌入式系统的调试、优化、以及团队合作有了更深入的理解。这些经验将为我未来在嵌入式系统开发方面打下坚实的基础。

作者:月夕24

物联沃分享整理
物联沃-IOTWORD物联网 » 基于STM32F103系列单片机的家用燃气监控系统

发表回复