一、系统概述
实现PC端VC++应用程序通过串口与51单片机通信,读取温度传感器数据(如DS18B20),并进行实时显示、数据存储和曲线绘制。
1.1 系统组成
- 硬件端:51单片机(STC89C52)+ DS18B20温度传感器 + MAX232电平转换
- 软件端:VC++ 6.0/VS2010编写的Windows应用程序
- 通信协议:自定义串口协议(波特率9600,8N1)
1.2 功能特点
- 实时温度显示(数字+曲线)
- 温度数据存储(CSV格式)
- 超限报警功能
- 串口参数配置
- 数据统计分析
二、51单片机端程序(C语言)
2.1 硬件连接
DS18B20引脚:
VDD → 5V
DQ → P3.7
GND → GND
MAX232连接:
TXD → P3.1 (RXD)
RXD → P3.0 (TXD)
2.2 单片机程序代码
c
#include <reg52.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
// DS18B20指令
#define SKIP_ROM 0xCC
#define CONVERT_T 0x44
#define READ_SCRATCHPAD 0xBE
// 串口初始化
void UART_Init() {
TMOD = 0x20; // 定时器1工作方式2
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
SM0 = 0; SM1 = 1; // 串口工作方式1
REN = 1; // 允许接收
EA = 1; // 开总中断
ES = 1; // 开串口中断
}
// DS18B20初始化
bit DS18B20_Init() {
bit ack;
DQ = 1; _nop_();
DQ = 0; delay_us(480); // 480us低电平复位
DQ = 1; delay_us(60); // 等待60us
ack = DQ; // 检测存在脉冲
delay_us(420); // 等待复位完成
return ack;
}
// DS18B20写一个字节
void DS18B20_WriteByte(uchar dat) {
uchar i;
for(i=0; i<8; i++) {
DQ = 0; _nop_();
DQ = dat & 0x01; // 低位在前
delay_us(60); // 保持60us
DQ = 1; // 释放总线
dat >>= 1;
}
}
// DS18B20读一个字节
uchar DS18B20_ReadByte() {
uchar i, dat = 0;
for(i=0; i<8; i++) {
DQ = 0; _nop_();
DQ = 1; _nop_(); // 产生读时序
if(DQ) dat |= 0x01<<i;
delay_us(60); // 等待60us
}
return dat;
}
// 读取温度值
float Read_Temperature() {
uchar low, high;
int temp;
float temperature;
DS18B20_Init();
DS18B20_WriteByte(SKIP_ROM);
DS18B20_WriteByte(CONVERT_T);
delay_ms(750); // 等待转换完成
DS18B20_Init();
DS18B20_WriteByte(SKIP_ROM);
DS18B20_WriteByte(READ_SCRATCHPAD);
low = DS18B20_ReadByte();
high = DS18B20_ReadByte();
temp = (high<<8) | low;
temperature = temp * 0.0625; // 转换为实际温度
return temperature;
}
// 串口发送一个字符
void UART_SendChar(uchar ch) {
SBUF = ch;
while(!TI);
TI = 0;
}
// 串口发送字符串
void UART_SendString(uchar *str) {
while(*str != '\0') {
UART_SendChar(*str++);
}
}
// 主函数
void main() {
float temp;
uchar temp_str[10];
UART_Init();
while(1) {
if(RI) { // 收到PC请求
RI = 0;
if(SBUF == 0x01) { // 请求温度命令
temp = Read_Temperature();
sprintf(temp_str, "%.2f", temp);
UART_SendString(temp_str);
UART_SendChar('\r');
UART_SendChar('\n');
}
}
delay_ms(1000); // 每秒读取一次
}
}
三、VC++端程序实现
3.1 创建VC++项目
- 打开Visual Studio → 新建项目 → Win32控制台应用程序
- 项目名称:TemperatureMonitor
- 选择"空项目",添加源文件
3.2 串口通信类(SerialPort.h)
cpp
#if !defined(AFX_SERIALPORT_H__)
#define AFX_SERIALPORT_H__
class CSerialPort {
public:
CSerialPort();
virtual ~CSerialPort();
BOOL Open(int port, int baud = 9600);
BOOL Close();
int Read(char *buf, int len);
int Write(char *buf, int len);
BOOL IsOpened() { return m_bOpened; }
private:
HANDLE m_hComm;
BOOL m_bOpened;
};
#endif
3.3 串口通信实现(SerialPort.cpp)
cpp
#include "stdafx.h"
#include "SerialPort.h"
#include <windows.h>
CSerialPort::CSerialPort() : m_hComm(NULL), m_bOpened(FALSE) {}
CSerialPort::~CSerialPort() {
Close();
}
BOOL CSerialPort::Open(int port, int baud) {
char szPort[16];
sprintf(szPort, "\\\\.\\COM%d", port);
m_hComm = CreateFile(szPort, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (m_hComm == INVALID_HANDLE_VALUE)
return FALSE;
DCB dcb;
GetCommState(m_hComm, &dcb);
dcb.BaudRate = baud;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(m_hComm, &dcb)) {
CloseHandle(m_hComm);
return FALSE;
}
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 5000;
SetCommTimeouts(m_hComm, &timeouts);
m_bOpened = TRUE;
return TRUE;
}
BOOL CSerialPort::Close() {
if (m_bOpened) {
CloseHandle(m_hComm);
m_bOpened = FALSE;
}
return TRUE;
}
int CSerialPort::Read(char *buf, int len) {
DWORD read = 0;
if (!ReadFile(m_hComm, buf, len, &read, NULL))
return 0;
return read;
}
int CSerialPort::Write(char *buf, int len) {
DWORD written = 0;
if (!WriteFile(m_hComm, buf, len, &written, NULL))
return 0;
return written;
}
3.4 主程序界面与功能(MainDlg.cpp)
cpp
#include "stdafx.h"
#include "TemperatureMonitor.h"
#include "SerialPort.h"
#include <windows.h>
#include <commctrl.h>
#include <vector>
#include <algorithm>
#include <fstream>
#define ID_TIMER 1
#define WM_UPDATE_DATA WM_USER + 100
// 对话框控件ID
#define IDC_COMBO_PORT 1001
#define IDC_COMBO_BAUD 1002
#define IDC_BUTTON_OPEN 1003
#define IDC_BUTTON_CLOSE 1004
#define IDC_EDIT_TEMP 1005
#define IDC_BUTTON_START 1006
#define IDC_BUTTON_STOP 1007
#define IDC_CHART 1008
#define IDC_EDIT_MIN 1009
#define IDC_EDIT_MAX 1010
// 全局变量
CSerialPort m_serial;
bool m_bRunning = false;
std::vector<float> m_tempData;
const int MAX_POINTS = 100;
// 初始化对话框
BOOL CMainDlg::OnInitDialog() {
CDialog::OnInitDialog();
// 初始化串口列表
CComboBox* pComboPort = (CComboBox*)GetDlgItem(IDC_COMBO_PORT);
for (int i = 1; i <= 16; i++) {
CString str;
str.Format("COM%d", i);
pComboPort->AddString(str);
}
pComboPort->SetCurSel(0);
// 初始化波特率列表
CComboBox* pComboBaud = (CComboBox*)GetDlgItem(IDC_COMBO_BAUD);
int baudRates[] = {9600, 19200, 38400, 57600, 115200};
for (int i = 0; i < 5; i++) {
CString str;
str.Format("%d", baudRates[i]);
pComboBaud->AddString(str);
}
pComboBaud->SetCurSel(0);
// 初始化图表
m_chart.SubclassDlgItem(IDC_CHART, this);
m_chart.SetGridColor(RGB(200, 200, 200));
m_chart.SetPlotColor(RGB(0, 0, 255));
return TRUE;
}
// 打开串口
void CMainDlg::OnButtonOpen() {
CComboBox* pComboPort = (CComboBox*)GetDlgItem(IDC_COMBO_PORT);
CComboBox* pComboBaud = (CComboBox*)GetDlgItem(IDC_COMBO_BAUD);
CString strPort, strBaud;
pComboPort->GetWindowText(strPort);
pComboBaud->GetWindowText(strBaud);
int port = atoi(strPort.Mid(3));
int baud = atoi(strBaud);
if (m_serial.Open(port, baud)) {
MessageBox("串口打开成功!", "提示", MB_OK);
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(TRUE);
} else {
MessageBox("串口打开失败!", "错误", MB_ICONERROR);
}
}
// 关闭串口
void CMainDlg::OnButtonClose() {
m_serial.Close();
m_bRunning = false;
KillTimer(ID_TIMER);
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_START)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);
}
// 开始采集
void CMainDlg::OnButtonStart() {
m_bRunning = true;
m_tempData.clear();
SetTimer(ID_TIMER, 1000, NULL); // 1秒定时器
GetDlgItem(IDC_BUTTON_START)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(TRUE);
}
// 停止采集
void CMainDlg::OnButtonStop() {
m_bRunning = false;
KillTimer(ID_TIMER);
GetDlgItem(IDC_BUTTON_START)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);
}
// 定时器处理
void CMainDlg::OnTimer(UINT_PTR nIDEvent) {
if (nIDEvent == ID_TIMER && m_bRunning) {
// 发送请求命令
char cmd = 0x01;
m_serial.Write(&cmd, 1);
// 读取响应
char buf[32] = {0};
int len = m_serial.Read(buf, 31);
if (len > 0) {
buf[len] = '\0';
float temp = atof(buf);
// 更新显示
CString strTemp;
strTemp.Format("%.2f °C", temp);
SetDlgItemText(IDC_EDIT_TEMP, strTemp);
// 存储数据
m_tempData.push_back(temp);
if (m_tempData.size() > MAX_POINTS) {
m_tempData.erase(m_tempData.begin());
}
// 更新图表
UpdateChart();
// 检查报警
CheckAlarm(temp);
}
}
CDialog::OnTimer(nIDEvent);
}
// 更新图表
void CMainDlg::UpdateChart() {
if (m_tempData.empty()) return;
// 准备数据点
std::vector<double> x, y;
for (int i = 0; i < m_tempData.size(); i++) {
x.push_back(i);
y.push_back(m_tempData[i]);
}
// 更新图表
m_chart.SetData(x, y);
m_chart.Invalidate();
}
// 检查报警
void CMainDlg::CheckAlarm(float temp) {
CEdit* pEditMin = (CEdit*)GetDlgItem(IDC_EDIT_MIN);
CEdit* pEditMax = (CEdit*)GetDlgItem(IDC_EDIT_MAX);
CString strMin, strMax;
pEditMin->GetWindowText(strMin);
pEditMax->GetWindowText(strMax);
float minTemp = atof(strMin);
float maxTemp = atof(strMax);
if (temp < minTemp || temp > maxTemp) {
// 触发报警(声音+闪烁)
Beep(1000, 500);
GetDlgItem(IDC_EDIT_TEMP)->FlashWindow(TRUE);
}
}
// 保存数据
void CMainDlg::OnButtonSave() {
CFileDialog dlg(FALSE, "csv", "temperature.csv",
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
"CSV文件 (*.csv)|*.csv||");
if (dlg.DoModal() == IDOK) {
std::ofstream out(dlg.GetPathName());
if (out) {
out << "时间,温度(°C)\n";
for (int i = 0; i < m_tempData.size(); i++) {
out << i << "," << m_tempData[i] << "\n";
}
out.close();
MessageBox("数据保存成功!", "提示", MB_OK);
}
}
}
3.5 图表控件实现(ChartCtrl.h)
cpp
#if !defined(AFX_CHARTCTRL_H__)
#define AFX_CHARTCTRL_H__
class CChartCtrl : public CWnd {
public:
CChartCtrl();
virtual ~CChartCtrl();
void SetData(const std::vector<double>& x, const std::vector<double>& y);
void SetGridColor(COLORREF color) { m_gridColor = color; }
void SetPlotColor(COLORREF color) { m_plotColor = color; }
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
private:
std::vector<double> m_xData;
std::vector<double> m_yData;
COLORREF m_gridColor;
COLORREF m_plotColor;
};
#endif
3.6 图表绘制实现(ChartCtrl.cpp)
cpp
#include "stdafx.h"
#include "ChartCtrl.h"
#include <math.h>
BEGIN_MESSAGE_MAP(CChartCtrl, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
CChartCtrl::CChartCtrl() : m_gridColor(RGB(200, 200, 200)), m_plotColor(RGB(0, 0, 255)) {}
CChartCtrl::~CChartCtrl() {}
void CChartCtrl::SetData(const std::vector<double>& x, const std::vector<double>& y) {
m_xData = x;
m_yData = y;
Invalidate();
}
void CChartCtrl::OnPaint() {
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
// 绘制背景
dc.FillSolidRect(rect, RGB(255, 255, 255));
// 绘制网格
CPen gridPen(PS_SOLID, 1, m_gridColor);
CPen* pOldPen = dc.SelectObject(&gridPen);
// 水平网格线
for (int y = rect.top + 20; y < rect.bottom; y += 20) {
dc.MoveTo(rect.left, y);
dc.LineTo(rect.right, y);
}
// 垂直网格线
for (int x = rect.left + 20; x < rect.right; x += 20) {
dc.MoveTo(x, rect.top);
dc.LineTo(x, rect.bottom);
}
// 绘制坐标轴
dc.SelectStockObject(BLACK_PEN);
dc.MoveTo(rect.left + 20, rect.top + 20);
dc.LineTo(rect.left + 20, rect.bottom - 20);
dc.LineTo(rect.right - 20, rect.bottom - 20);
// 绘制数据曲线
if (m_xData.size() > 1 && m_yData.size() > 1) {
CPen plotPen(PS_SOLID, 2, m_plotColor);
dc.SelectObject(&plotPen);
// 计算坐标变换
double xMin = *min_element(m_xData.begin(), m_xData.end());
double xMax = *max_element(m_xData.begin(), m_xData.end());
double yMin = *min_element(m_yData.begin(), m_yData.end());
double yMax = *max_element(m_yData.begin(), m_yData.end());
// 绘制曲线
for (size_t i = 1; i < m_xData.size(); i++) {
int x1 = rect.left + 20 + (int)((m_xData[i-1] - xMin) * (rect.Width()-40) / (xMax - xMin));
int y1 = rect.bottom - 20 - (int)((m_yData[i-1] - yMin) * (rect.Height()-40) / (yMax - yMin));
int x2 = rect.left + 20 + (int)((m_xData[i] - xMin) * (rect.Width()-40) / (xMax - xMin));
int y2 = rect.bottom - 20 - (int)((m_yData[i] - yMin) * (rect.Height()-40) / (yMax - yMin));
dc.MoveTo(x1, y1);
dc.LineTo(x2, y2);
}
}
dc.SelectObject(pOldPen);
}
参考代码 温度传感器VC源程序,用串口与51单片机通讯 www.youwenfan.com/contentcst/124012.html
四、系统使用说明
4.1 硬件连接
- DS18B20数据线 → 单片机P3.7
- 单片机TXD → MAX232 TXIN
- 单片机RXD → MAX232 RXIN
- MAX232 T1OUT → PC串口RXD
- MAX232 R1IN → PC串口TXD
- 5V电源供电
4.2 软件操作
- 编译并烧录单片机程序
- 运行VC++应用程序
- 选择串口号和波特率(默认COM1,9600)
- 点击"打开串口"按钮
- 设置温度上下限(报警阈值)
- 点击"开始采集"按钮
- 实时温度曲线将显示在图表中
- 点击"停止采集"结束监测
- 点击"保存数据"导出CSV文件
4.3 界面功能
- 串口设置区:选择串口号和波特率
- 控制按钮区:打开/关闭串口,开始/停止采集
- 数据显示区:当前温度值显示
- 图表显示区:实时温度曲线
- 报警设置区:设置温度上下限
- 数据管理区:保存数据到CSV文件
五、常见问题解决
5.1 串口通信失败
- 检查硬件连接是否正确
- 确认串口号选择正确(设备管理器查看)
- 检查波特率设置是否匹配
- 关闭其他占用串口的程序
5.2 温度读数异常
- 检查DS18B20接线(VCC、DQ、GND)
- 确认DS18B20数据线有4.7K上拉电阻
- 检查单片机程序是否正确读取温度
- 使用串口调试助手验证数据
5.3 曲线显示问题
- 检查数据点是否超出范围
- 确认图表控件已正确子类化
- 调整图表坐标范围
- 增加数据点数量限制
六、扩展功能建议
6.1 多传感器支持
cpp
// 支持多种温度传感器
enum SensorType { DS18B20, DHT11, LM35 };
void ReadTemperature(SensorType type) {
switch(type) {
case DS18B20: /* DS18B20读取代码 */ break;
case DHT11: /* DHT11读取代码 */ break;
case LM35: /* LM35读取代码 */ break;
}
}
6.2 网络通信模块
cpp
// 添加TCP/IP通信
#include <winsock2.h>
void SendDataToServer(float temp) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = inet_addr("192.168.1.100");
connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));
char data[32];
sprintf(data, "TEMP:%.2f", temp);
send(sock, data, strlen(data), 0);
closesocket(sock);
}
6.3 数据库存储
cpp
// 使用SQLite存储数据
#include <sqlite3.h>
void SaveToDatabase(float temp) {
sqlite3* db;
sqlite3_open("temperature.db", &db);
char* sql = "CREATE TABLE IF NOT EXISTS temps (time DATETIME, value REAL)";
sqlite3_exec(db, sql, NULL, NULL, NULL);
char insert[100];
sprintf(insert, "INSERT INTO temps VALUES (datetime('now'), %.2f)", temp);
sqlite3_exec(db, insert, NULL, NULL, NULL);
sqlite3_close(db);
}
七、总结
实现了PC端VC++应用程序通过串口与51单片机通信,完成温度数据的采集、显示、存储和分析。系统具有以下特点:
-
完整通信链路:
- 51单片机端:DS18B20驱动、串口通信协议
- VC++端:串口通信类、数据解析、界面显示
-
丰富功能:
- 实时温度显示与曲线绘制
- 温度超限报警
- 数据存储与导出
- 串口参数配置
-
可扩展性:
- 支持多种温度传感器
- 可添加网络通信功能
- 可集成数据库存储