一、前言
煤氣泄漏是一個嚴重的安全隱患,可能導致火災、爆炸以及對人體健康的威脅。為了提高家庭和工業(yè)環(huán)境中煤氣泄漏的檢測和預防能力,設計了一種基于單片機的防煤氣泄漏裝置。
單片機選擇STC89C52作為主控芯片。為了檢測煤氣泄漏,采用了MQ4傳感器,能夠快速、準確地檢測煤氣濃度。通過采集MQ4傳感器的模擬信號,使用PCF8591模數轉換芯片將模擬信號轉換為數字信號。采用IIC接口的OLED顯示屏,將采集到的數據顯示出來,方便用戶獲取檢測結果。用戶可以通過兩個獨立按鍵設置煤氣泄漏的報警閾值,以適應不同環(huán)境的需求。
當檢測到煤氣泄漏超過設定的閾值時,裝置會觸發(fā)蜂鳴器進行報警,并同時打開換氣扇進行通風換氣,以迅速排除煤氣并降低安全風險。
這種基于單片機設計的防煤氣泄漏裝置具有以下優(yōu)點:高效可靠的煤氣泄漏檢測能力、靈活的報警閾值設置、直觀清晰的數據顯示以及及時的安全響應措施??梢詮V泛應用于家庭、工業(yè)和商業(yè)場所,提供有效的煤氣泄漏監(jiān)測和安全保護。
二、硬件選型
在設計基于單片機的防煤氣泄漏裝置時,硬件選型是非常關鍵的。以下是詳細介紹硬件選型的相關內容:
【1】主控芯片選擇:STC89C52 STC89C52是一款8051架構的單片機,具有豐富的接口資源、較高的性能和穩(wěn)定可靠的工作特性,廣泛應用于各種嵌入式系統(tǒng)中。具有8位數據總線、16位地址總線和4KB的內部存儲器。STC89C52具備多個通用I/O口、定時器/計數器、串口等功能,非常適合本項目需求。
【2】煤氣傳感器選擇:MQ4 MQ4傳感器是一種能夠檢測多種可燃氣體,如天然氣、甲烷等的傳感器。具有高靈敏度和快速響應的特點,能夠準確地檢測煤氣泄漏情況。MQ4傳感器的輸出為模擬信號,需要通過模數轉換器將其轉換為數字信號供主控芯片處理。
【3】模數轉換器選擇:PCF8591 PCF8591是一款集成了8位模數/數模轉換和4個模擬輸入通道的模數轉換器。采用IIC總線通訊接口,能夠將模擬信號轉換為數字信號,并通過IIC協議發(fā)送給主控芯片。本項目中,PCF8591用于采集MQ4傳感器輸出的模擬信號,并將其轉換為數字信號供STC89C52處理。
【4】顯示屏選擇:0.96寸OLED顯示屏(IIC接口) 本設計采用基于IIC接口的OLED顯示屏,具有高亮度、對比度和快速響應的特點。通過簡單的通訊方式,可以將煤氣濃度信息實時顯示在屏幕上。OLED顯示屏使用面積小、功耗低,在嵌入式系統(tǒng)中應用廣泛。
【5】按鍵選擇:獨立按鍵 本設計采用兩個獨立按鍵來設置報警的閥值。一個按鍵用于遞增閥值,另一個按鍵用于遞減閥值。獨立按鍵具有簡單可靠、使用方便等特點,適合本項目需求。
【6】報警裝置選擇:蜂鳴器和換氣扇 當檢測到煤氣泄漏超過設定的報警閥值時,蜂鳴器將發(fā)出警報,用于提醒周圍人員。同時,為了降低煤氣濃度,需要啟動換氣扇進行通風換氣。具體的報警和換氣扇電路可以根據實際需求設計。
三、設計思路
軟件設計思路如下:
【1】初始化:在程序開始時,進行主控芯片STC89C52的初始化設置,包括引腳配置、定時器設置等。同時,初始化PCF8591和OLED顯示屏,確保它們可以正常工作。
【2】傳感器檢測:通過MQ4傳感器檢測煤氣是否泄漏。將MQ4傳感器與STC89C52的模擬輸入引腳連接,通過讀取該引腳的模擬電壓值,獲取煤氣濃度數據。
【3】數據采集與處理:使用PCF8591模數轉換芯片,將MQ4傳感器的模擬輸出信號轉換為數字信號,并通過STC89C52的IIC接口與PCF8591進行通信,獲取轉換后的數字數據。
【4】數據顯示:將采集到的煤氣濃度數據通過IIC接口的OLED顯示屏進行顯示。使用STC89C52的IIC通信功能,將數據發(fā)送給OLED顯示屏,通過顯示屏將數據以可讀的方式展示給用戶。
【5】閾值設置:通過兩個獨立按鍵實現報警閾值的設置。將按鍵與STC89C52的GPIO引腳連接,通過讀取按鍵狀態(tài)來判斷用戶是否進行閾值設置操作。當按鍵按下時,進入設置模式,用戶可以通過按鍵的不同組合來調整報警閾值,并將設置的值保存在相應的變量中。
【6】報警與通風控制:根據當前采集到的煤氣濃度數據和用戶設置的報警閾值進行比較。如果煤氣濃度超過設定的閾值,觸發(fā)蜂鳴器進行報警,并控制換氣扇打開進行通風換氣。反之,當煤氣濃度低于或等于設定的閾值時,停止報警并關閉換氣扇。
【7】循環(huán)監(jiān)測:使用主循環(huán)結構,不斷進行煤氣泄漏檢測、數據采集、數據顯示和閾值比較等操作,以實現持續(xù)的監(jiān)測和反饋。
四、項目模塊代碼
4.1 PCF8591采集代碼
下面是使用STC89C52單片機通過PCF8591讀取MQ4傳感器的ADC數據的代碼。使用IIC總線進行PCF8591之間的通信,使用了自定義的IIC總線函數。通過readADC()
函數實現了讀取MQ4傳感器模擬量的ADC轉換結果。
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit SDA = P2^0; // IIC總線數據線
sbit SCL = P2^1; // IIC總線時鐘線
sbit MQ4_DOUT = P3^0; // MQ4傳感器數字輸出引腳
sbit MQ4_AIN = P3^1; // MQ4傳感器模擬輸入引腳
sfr IAP_DATA = 0xe2; // 定義IAP_DATA寄存器
sfr IAP_ADDRH = 0xe3; // 定義IAP_ADDRH寄存器
sfr IAP_ADDRL = 0xe4; // 定義IAP_ADDRL寄存器
sfr IAP_CMD = 0xe5; // 定義IAP_CMD寄存器
sfr IAP_TRIG = 0xe6; // 定義IAP_TRIG寄存器
sfr IAP_CONTR = 0xe7; // 定義IAP_CONTR寄存器
// 函數聲明
void delay(uint ms);
void startIIC();
void stopIIC();
void sendByte(uchar dat);
uchar receiveByte();
void writeDAC(uchar dat);
uchar readADC();
void main() {
uchar mq4Value;
while (1) {
mq4Value = readADC(); // 讀取ADC轉換結果
// 處理mq4Value值,進行相應操作
delay(100); // 延時一段時間
}
}
// 延時函數
void delay(uint ms) {
uint i, j;
for(i = ms; i > 0; i--) {
for(j = 110; j > 0; j--);
}
}
// IIC總線起始信號
void startIIC() {
SDA = 1;
_nop_();
SCL = 1;
_nop_();
SDA = 0;
_nop_();
SCL = 0;
_nop_();
}
// IIC總線停止信號
void stopIIC() {
SDA = 0;
_nop_();
SCL = 1;
_nop_();
SDA = 1;
_nop_();
}
// 發(fā)送一個字節(jié)的數據
void sendByte(uchar dat) {
uchar i;
for (i = 0; i < 8; i++) {
SDA = dat >> 7;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
dat <<= 1;
}
SDA = 1;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
}
// 接收一個字節(jié)的數據
uchar receiveByte() {
uchar i, dat = 0;
SDA = 1;
for (i = 0; i < 8; i++) {
dat <<= 1;
SCL = 1;
_nop_();
dat |= SDA;
SCL = 0;
_nop_();
}
return dat;
}
// 寫入DAC數值
void writeDAC(uchar dat) {
startIIC();
sendByte(0x90); // 地址和寫命令
receiveByte(); // 接收應答
sendByte(0x40); // DAC通道A,并寫入數據
receiveByte(); // 接收應答
sendByte(dat); // DAC數據
receiveByte(); // 接收應答
stopIIC();
}
// 讀取ADC轉換結果
uchar readADC() {
uchar adcValue;
startIIC();
sendByte(0x91); // 地址和讀命令
receiveByte(); // 接收應答
adcValue = receiveByte(); // 讀取ADC數據
stopIIC();
return adcValue;
}
4.2 OLED顯示屏代碼
以下是通過STC89C52控制IIC接口的OLED顯示屏顯示ADC數據實現代碼。
#include <reg51.h>
#define SCL P1_0 // IIC時鐘線
#define SDA P1_1 // IIC數據線
#define OLED_ADDR 0x78 // OLED顯示屏的IIC地址
// OLED顯示屏初始化函數
void OLED_Init() {
// 初始化OLED顯示屏
// ...
// 發(fā)送初始化命令到OLED顯示屏
// ...
}
// IIC總線開始信號
void IIC_Start() {
SDA = 1;
SCL = 1;
SDA = 0;
SCL = 0;
}
// IIC總線停止信號
void IIC_Stop() {
SDA = 0;
SCL = 1;
SDA = 1;
}
// IIC總線發(fā)送一個字節(jié)的數據
void IIC_WriteByte(unsigned char dat) {
unsigned char i;
for (i = 0; i < 8; i++) {
SCL = 0;
if (dat & 0x80)
SDA = 1;
else
SDA = 0;
SCL = 1;
dat <<= 1;
}
SCL = 0;
SDA = 1;
SCL = 1;
}
// 設置OLED顯示屏光標位置
void OLED_SetPos(unsigned char x, unsigned char y) {
IIC_Start();
IIC_WriteByte(OLED_ADDR);
IIC_WriteByte(0xb0 + y);
IIC_WriteByte(((x & 0xf0) >> 4) | 0x10);
IIC_WriteByte((x & 0x0f) | 0x01);
IIC_Stop();
}
// 在OLED顯示屏上顯示一個字符
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch) {
unsigned char c = 0, i = 0;
c = ch - ' '; // 獲取字符在字庫中的偏移量
if (x > 128 - 8 || y > 64 - 16)
return; // 超出屏幕范圍,退出函數
OLED_SetPos(x, y);
for (i = 0; i < 8; i++) {
IIC_Start();
IIC_WriteByte(OLED_ADDR);
IIC_WriteByte(0x40);
IIC_WriteByte(*(OLED_CharSet + c * 16 + i));
IIC_Stop();
x++;
}
}
// 在OLED顯示屏上顯示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str) {
while (*str) {
OLED_ShowChar(x, y, *str);
x += 8;
str++;
}
}
// ADC模擬數值轉換為字符串
void ADC_ToString(unsigned int adcValue, unsigned char *str) {
unsigned char i, j;
unsigned int temp;
temp = adcValue;
for (i = 0; i < 4; i++) {
str[i] = temp % 10 + '0';
temp /= 10;
}
// 反轉字符串
i = 0;
j = 3;
while (i < j) {
temp = str[i];
str[i] = str[j];
str[j] = temp;
i++;
j--;
}
str[4] = '?'; // 字符串結束符
}
// 主函數
void main() {
unsigned int adcValue = 0;
unsigned char str[5];
// 初始化OLED顯示屏
OLED_Init();
while (1) {
// 讀取ADC數據
adcValue = ADC_Read(); // 假設使用的函數為ADC_Read(),用于讀取ADC數據
// 將ADC數據轉換為字符串
ADC_ToString(adcValue, str);
// 在OLED顯示屏上顯示ADC數據
OLED_ShowString(0, 0, "ADC Value:");
OLED_ShowString(0, 2, str);
}
}
4.3 主代碼邏輯
#include <reg51.h>
#define SCL P1_0 // IIC時鐘線
#define SDA P1_1 // IIC數據線
#define OLED_ADDR 0x78 // OLED顯示屏的IIC地址
#define MQ4_PIN P2 // MQ4傳感器連接的引腳
sbit Buzzer = P3^0; // 蜂鳴器控制引腳
sbit VentilationFan = P3^1; // 換氣扇控制引腳
// 全局變量
unsigned int alarmThreshold = 100; // 報警閾值,默認為100
unsigned int adcValue = 0; // 保存ADC采集到的數值
// IIC總線開始信號
void IIC_Start() {
SDA = 1;
SCL = 1;
SDA = 0;
SCL = 0;
}
// IIC總線停止信號
void IIC_Stop() {
SDA = 0;
SCL = 1;
SDA = 1;
}
// IIC總線發(fā)送一個字節(jié)的數據
void IIC_WriteByte(unsigned char dat) {
unsigned char i;
for (i = 0; i < 8; i++) {
SCL = 0;
if (dat & 0x80)
SDA = 1;
else
SDA = 0;
SCL = 1;
dat <<= 1;
}
SCL = 0;
SDA = 1;
SCL = 1;
}
// 設置OLED顯示屏光標位置
void OLED_SetPos(unsigned char x, unsigned char y) {
IIC_Start();
IIC_WriteByte(OLED_ADDR);
IIC_WriteByte(0xb0 + y);
IIC_WriteByte(((x & 0xf0) >> 4) | 0x10);
IIC_WriteByte((x & 0x0f) | 0x01);
IIC_Stop();
}
// 在OLED顯示屏上顯示一個字符
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch) {
unsigned char c = 0, i = 0;
c = ch - ' '; // 獲取字符在字庫中的偏移量
if (x > 128 - 8 || y > 64 - 16)
return; // 超出屏幕范圍,退出函數
OLED_SetPos(x, y);
for (i = 0; i < 8; i++) {
IIC_Start();
IIC_WriteByte(OLED_ADDR);
IIC_WriteByte(0x40);
IIC_WriteByte(*(OLED_CharSet + c * 16 + i));
IIC_Stop();
x++;
}
}
// 在OLED顯示屏上顯示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str) {
while (*str) {
OLED_ShowChar(x, y, *str);
x += 8;
str++;
}
}
// ADC轉換函數
unsigned int ADC_Convert(unsigned char channel) {
ADC_CONTR = ADC_POWER | ADC_START | channel | ADC_SPEED;
_nop_();
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & ADC_FLAG))
;
ADC_CONTR &= ~ADC_FLAG;
return (ADC_RES * 256 + ADC_RESL);
}
// ADC模擬數值轉換為字符串
void ADC_ToString(unsigned int adcValue, unsigned char *str) {
unsigned char i, j;
unsigned int temp;
temp = adcValue;
for (i = 0; i < 4; i++) {
str[i] = temp % 10 + '0';
temp /= 10;
}
// 反轉字符串
i = 0;
j = 3;
while (i < j) {
temp = str[i];
str[i] = str[j];
str[j] = temp;
i++;
j--;
}
str[4] = '?'; // 字符串結束符
}
主函數
void main() {
unsigned char str[5];
// 初始化OLED顯示屏
OLED_Init();
// 設置初始報警閾值
SetAlarmThreshold();
while (1) {
// 讀取MQ4傳感器的ADC數值
adcValue = ADC_Convert(0); // 假設MQ4傳感器連接到ADC的通道0
// 將ADC數值轉換為字符串
ADC_ToString(adcValue, str);
// 在OLED顯示屏上顯示ADC數值
OLED_ShowString(0, 0, "Gas Level:");
OLED_ShowString(0, 2, str);
// 判斷是否觸發(fā)報警
if (adcValue > alarmThreshold) {
// 觸發(fā)報警
ActivateAlarm();
} else {
// 取消報警
DeactivateAlarm();
}
// 檢測是否按下設置閾值的按鍵
if (IsThresholdButtonPressed()) {
// 設置報警閾值
SetAlarmThreshold();
}
// 檢測是否按下通風換氣的按鍵
if (IsVentilationButtonPressed()) {
// 控制通風換氣
ControlVentilation();
}
}
}