温度传感器VC++串口通信程序(与51单片机通讯)

一、系统概述

实现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++项目

  1. 打开Visual Studio → 新建项目 → Win32控制台应用程序
  2. 项目名称:TemperatureMonitor
  3. 选择"空项目",添加源文件

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 硬件连接

  1. DS18B20数据线 → 单片机P3.7
  2. 单片机TXD → MAX232 TXIN
  3. 单片机RXD → MAX232 RXIN
  4. MAX232 T1OUT → PC串口RXD
  5. MAX232 R1IN → PC串口TXD
  6. 5V电源供电

4.2 软件操作

  1. 编译并烧录单片机程序
  2. 运行VC++应用程序
  3. 选择串口号和波特率(默认COM1,9600)
  4. 点击"打开串口"按钮
  5. 设置温度上下限(报警阈值)
  6. 点击"开始采集"按钮
  7. 实时温度曲线将显示在图表中
  8. 点击"停止采集"结束监测
  9. 点击"保存数据"导出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单片机通信,完成温度数据的采集、显示、存储和分析。系统具有以下特点:

  1. 完整通信链路

    • 51单片机端:DS18B20驱动、串口通信协议
    • VC++端:串口通信类、数据解析、界面显示
  2. 丰富功能

    • 实时温度显示与曲线绘制
    • 温度超限报警
    • 数据存储与导出
    • 串口参数配置
  3. 可扩展性

    • 支持多种温度传感器
    • 可添加网络通信功能
    • 可集成数据库存储
相关推荐
LCMICRO-133108477462 小时前
国产长芯微LDC4048完全P2P替代DAC128S085,是一款 8 通道、带输出放大器的数模转换器 (DAC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·数模转换器dac
768dh2 小时前
NCP1654学习(一)
单片机·嵌入式硬件
雅斯驰2 小时前
电流模式控制+快速瞬态响应:LTM4650AY的FPGA供电技术解析
stm32·单片机·嵌入式硬件·物联网·fpga开发·汽车
从零点2 小时前
用VScode+CubeMX进行串口打印
单片机·嵌入式硬件
zd8451015002 小时前
51单片机-串口程序代码
单片机·嵌入式硬件·51单片机
JaneHan_2 小时前
STM32CubeMX+HAL+Keil5 GPIO输入 按键控制
stm32·单片机·嵌入式硬件
SariHcr1232 小时前
PG2K100千兆以太网接口速度测试
网络·嵌入式硬件·嵌入式实时数据库
平凡灵感码头2 小时前
C51 与 STM32 编程对比:从数据类型、关键字到程序结构
stm32·单片机·嵌入式硬件
LCG元2 小时前
STM32实战:基于STM32F103的HC-SR04超声波测距与OLED显示
stm32·单片机·嵌入式硬件