在工业自动化和智慧城市建设的浪潮中,环境监测系统正从传统的人工记录向智能化、网络化方向快速演进。这个基于MFC C++的环境监测系统设计方案,正是针对中小型监测场景开发的一套轻量级解决方案。
我十年前第一次接触环境监测项目时,现场还需要技术人员带着记录本逐个抄表。现在的传感器可以直接输出数字信号,但市场上成熟的SCADA系统对于小型项目来说又显得过于庞大。这套方案就是在这两个极端之间找到的平衡点——既能满足实时监测的基本需求,又不会带来过高的部署成本。
系统核心功能包括:
选择MFC框架主要基于三点考虑:
系统采用典型的三层架构:
code复制[硬件层]
└── [通信层]
└── [应用层]
├── 数据采集模块
├── 数据处理模块
└── 用户界面模块
根据项目预算和精度要求,我们选用了以下硬件配置:
通信协议选择:
串口通信的关键代码实现:
cpp复制class CSerialPortEx : public CSerialPort
{
public:
bool OpenPort(int nPort, DWORD dwBaudRate = 9600)
{
// 串口初始化代码
if(!CSerialPort::Open(nPort, dwBaudRate))
return false;
// 设置超时参数
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
SetTimeouts(timeouts);
return true;
}
int ReadData(BYTE* buffer, int limit)
{
DWORD dwBytesRead = 0;
if(!Read(buffer, limit, &dwBytesRead))
return -1;
return (int)dwBytesRead;
}
};
关键提示:工业现场使用时,建议在串口初始化后发送测试指令验证通信,避免因电磁干扰导致通信异常。
数据校验算法示例:
cpp复制bool VerifyChecksum(const BYTE* data, int length)
{
if(length < 3) return false;
BYTE sum = 0;
for(int i=0; i<length-1; i++){
sum += data[i];
}
return (sum == data[length-1]);
}
数据缓存采用环形缓冲区设计:
cpp复制#define BUFFER_SIZE 1024
class CRingBuffer
{
public:
void Push(const SensorData& data)
{
if((m_nTail + 1) % BUFFER_SIZE == m_nHead)
m_nHead = (m_nHead + 1) % BUFFER_SIZE; // 覆盖最旧数据
m_buffer[m_nTail] = data;
m_nTail = (m_nTail + 1) % BUFFER_SIZE;
}
bool Pop(SensorData& data)
{
if(m_nHead == m_nTail)
return false;
data = m_buffer[m_nHead];
m_nHead = (m_nHead + 1) % BUFFER_SIZE;
return true;
}
private:
SensorData m_buffer[BUFFER_SIZE];
int m_nHead = 0;
int m_nTail = 0;
};
MFC界面开发中的几个实用技巧:
cpp复制void CRealTimeGraph::DrawGraph(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
// 双缓冲绘图
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
memDC.SelectObject(&bitmap);
// 背景绘制
memDC.FillSolidRect(rect, RGB(240, 240, 240));
// 曲线绘制代码...
// 最终输出
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
}
系统采用"生产者-消费者"模型:
关键同步代码:
cpp复制// 全局事件和临界区
CEvent g_dataEvent(FALSE, TRUE);
CCriticalSection g_csData;
UINT DataAcquisitionThread(LPVOID pParam)
{
while(!m_bStopThread)
{
// 采集数据...
{
CSingleLock lock(&g_csData, TRUE);
// 写入共享缓冲区
g_ringBuffer.Push(newData);
}
g_dataEvent.SetEvent();
Sleep(50); // 20Hz采样率
}
return 0;
}
考虑到监测数据的高频写入特点,采用以下策略:
SQLite操作示例:
cpp复制void CDatabaseMgr::BatchInsert(const std::vector<SensorData>& dataList)
{
CString sql = _T("BEGIN TRANSACTION;");
for(const auto& data : dataList)
{
sql.AppendFormat(_T("INSERT INTO data VALUES(%d, %.2f, %.2f, %d, '%s');"),
data.nSensorID, data.fValue1, data.fValue2,
data.nTimestamp, data.strStatus);
}
sql += _T("COMMIT;");
try {
m_db.ExecuteSQL(sql);
}
catch(CDBException* e) {
// 错误处理...
e->Delete();
}
}
典型现场部署需要:
硬件连接检查表:
软件配置项:
ini复制[System]
SampleInterval=1000 ; 采样间隔(ms)
AlarmThreshold=75.0 ; PM2.5报警阈值
[Database]
AutoBackup=1 ; 开启自动备份
BackupInterval=3600 ; 备份间隔(秒)
在2000㎡厂房中的实测数据:
| 指标项 | 优化前 | 优化后 |
|---|---|---|
| CPU占用率 | 45% | 12% |
| 内存占用 | 320MB | 180MB |
| 数据延迟 | 800ms | 200ms |
关键优化措施:
code复制1. 检查物理连接
└─ [正常] → 2
└─ [异常] → 修复线缆
2. 测试端口状态
└─ [正常] → 3
└─ [异常] → 检查驱动/更换端口
3. 发送测试指令
└─ [响应正常] → 检查协议配置
└─ [无响应] → 检查传感器供电
现象:PM2.5数值持续为0
可能原因:
解决方案:
这套系统在实际使用中还可以进一步扩展:
我在三个工业园区的部署经验表明,系统连续运行180天的稳定性达到99.2%。对于需要更高可靠性的场景,建议增加看门狗程序和硬件热备方案。