本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本设计实现了一个基于51单片机的温度监测系统,通过DS1621数字温度传感器采集环境温度,并利用LCD12864图形液晶屏实时显示数据。系统采用I²C通信协议进行传感器数据读取,结合C语言编程实现LCD驱动和温度信息可视化。项目包含完整源码(如LCD_DRIVE.c)、可烧录HEX文件及Protues仿真文件(lcd.DSN、lcd.PWI等),支持虚拟仿真与实物部署,适用于毕业设计、课程设计及嵌入式系统学习,涵盖单片机控制、传感器应用、液晶显示驱动和仿真调试等核心技术。

51单片机系统架构与开发环境搭建

在嵌入式世界的入门之路上,总有一块芯片像“老朋友”一样陪伴着无数工程师的成长——它就是 51单片机 。尽管如今高性能MCU层出不穷,但STC89C52RC这类经典51系列因其结构清晰、资料丰富、成本低廉,依然是学习嵌入式系统设计的不二起点 🚀。

我们今天要构建的,是一个完整的温度监控系统:从传感器采集数据,到主控处理信息,再到液晶屏上直观显示结果。整个过程就像一场精密协作的舞台剧,而主角,正是这颗看似简单却内有乾坤的51单片机。


CPU核心与内存组织:大脑与记忆库

51单片机的核心是其8位CPU,采用冯·诺依曼架构(严格来说是哈佛改进型),具备基本的算术逻辑单元(ALU)、程序计数器(PC)、累加器(ACC)和寄存器组。它的时钟通常由外部晶振提供(如常见的12MHz),每12个时钟周期构成一个机器周期,这意味着指令执行速度相对较慢——但也正因如此,它更适合教学和稳定控制场景。

它的内部资源虽然有限,但足够支撑中小型项目:

  • 程序存储器(ROM) :4KB ~ 64KB Flash,用于存放编译后的代码。以STC89C52为例,拥有8KB可擦写Flash,支持ISP在线编程。
  • 数据存储器(RAM) :256字节内部RAM,其中低128字节为通用工作区(含4组R0~R7寄存器),高128字节为特殊功能寄存器(SFR)映射区,比如P0、P1口状态、定时器控制等都通过这些地址访问。
  • I/O端口 :4个8位双向I/O口(P0~P3),共32个GPIO引脚,可通过设置为准双向、推挽或开漏模式,驱动LED、按键、继电器等外设毫无压力 💡。

别小看这“古董级”的配置,正是这种简洁的设计让我们能真正理解底层硬件是如何工作的——没有抽象层遮挡视线,一切都赤裸裸地摆在你面前!


开发环境搭建:Keil + Proteus 黄金搭档 ⚙️

想让这个“铁疙瘩”动起来?得先给它配好工具链。目前最主流的组合是:

  • Keil μVision5 :老牌IDE,专为8051优化,语法高亮、调试仿真一应俱全;
  • Proteus 8 Professional :电路仿真神器,能虚拟运行HEX文件,实时观察波形、电压变化,简直是调试利器!
Step 1:安装并创建工程

打开Keil,新建一个 Project ,选择目标芯片为 AT89C52 STC89C52RC (厂商不同,但兼容性良好)。接着添加一个新的 .c 源文件,比如叫 main.c

然后记得包含头文件:

#include <reg52.h>  // 包含STC89C52寄存器定义

如果你用的是其他增强型51,可能需要对应替换为 <stc8h.h> 之类的头文件哦~

Step 2:工程配置要点 ✅

右键项目名 → Options for Target ,关键设置如下:

  • Device Tab :确认选对型号;
  • Target Tab
  • 晶振频率填 12.000 MHz (根据实际硬件调整);
  • 如果使用外部RAM/ROM,勾选相应选项;
  • Output Tab
  • 勾选 Create HEX File ,这是烧录必需的输出格式;
  • Debug Tab
  • 选择 Use Simulator ,方便后续在Proteus中联动调试。

搞定之后,就可以写第一行点亮LED的代码了👇

sbit LED = P1^0;  // 定义P1.0连接LED

void main() {
    while(1) {
        LED = 0;      // LED亮(假设共阳)
        delay_ms(500);
        LED = 1;      // LED灭
        delay_ms(500);
    }
}

当然,你还得自己实现 delay_ms() 函数,利用定时器或者简单的循环延时都可以。例如:

void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = ms; i > 0; i--)
        for(j = 115; j > 0; j--);  // 12MHz下约1ms
}

🔍 小贴士:精确延时建议使用定时器中断,避免因编译器优化导致误差!


联合仿真:Keil + Proteus 实现闭环验证 🔗

光看代码跑不放心?来试试软硬结合的仿真大法!

Proteus 中绘制电路图:
- 放置 STC89C52 AT89C52
- 添加12MHz晶振 + 两个30pF电容
- 复位电路:10kΩ上拉电阻 + 10μF电解电容接地
- P1.0接一个LED和限流电阻(220Ω)

然后双击MCU元件,在 Program File 里加载Keil生成的 .hex 文件,设置Clock Frequency为12MHz。

点击左下角绿色三角▶️启动仿真!你会看到LED以500ms间隔闪烁,完美同步 👏👏👏

更厉害的是,你还可以加入虚拟示波器探头测量SCL/SDA波形,或者用I²C Debugger查看通信帧结构——这一切都不需要一块实物板子就能完成!


DS1621数字温度传感器工作原理与I²C通信实现

当你走进一间智能温室、工业控制柜甚至一台高端咖啡机时,背后很可能藏着一颗小小的温度传感器,默默守护着系统的安全与效率。而在众多温感方案中, DS1621 凭借其高精度、低功耗和标准I²C接口,成为许多嵌入式项目的首选。

但这块芯片到底怎么工作?为什么不用额外ADC也能输出数字温度值?我们来揭开它的神秘面纱 🔍


内部结构解析:从物理效应到数字输出

DS1621可不是普通的模拟传感器。它内部集成了完整的测温电路,直接将环境温度转化为16位二进制补码形式的数据,省去了外部ADC转换环节,极大简化了系统设计。

核心原理:PN结电压随温漂移 + Δ-Σ ADC

一切始于半导体材料的基本特性—— PN结的正向压降会随着温度线性变化 。DS1621利用这一现象,内置一个温度感应单元,持续监测这个微弱的电压差。

但仅有模拟信号还不够,必须数字化。这里就轮到 Δ-Σ模数转换器(Delta-Sigma ADC) 登场了!

🧠 打个比方:想象你在听一段嘈杂的音乐,但只想听到主旋律。Δ-Σ技术就像是用高速录音设备不断采样,把噪音“推”到高频段去,再通过滤波器把这些杂音去掉,最终留下干净的声音。

具体流程如下:

graph TD
    A[环境温度变化] --> B(PN结电压变化)
    B --> C{片内放大器调理}
    C --> D[Δ-Σ调制器]
    D --> E[高速比特流输出]
    E --> F[数字低通滤波]
    F --> G[16位补码温度值]
    G --> H[存储至温度寄存器]

整个过程全自动运行,无需主控干预。而且由于采用了过采样技术和噪声整形,即使在低成本工艺下也能实现±0.5°C的典型精度,性价比爆棚!


温度寄存器格式详解:如何读懂原始数据?

DS1621的温度数据存储在一个只读寄存器中(地址 0x00 ),宽度16位,采用二进制补码表示。但并不是所有位都是有效温度数据——分辨率可配置为9~12位,其余低位用于状态标志或保留。

分辨率 有效位数 数据格式说明
9位 Bit15~Bit7 符号位 + 8位整数部分
10位 Bit15~Bit6 符号位 + 9位整数
11位 Bit15~Bit5 符号位 + 10位整数
12位 Bit15~Bit4 符号位 + 11位整数

默认情况下,DS1621以上电即处于 10位分辨率模式 ,即每步进代表0.5°C。

举个例子:
- 若读取到 0x0100 ,换算成十进制为256,乘以0.5 → 实际温度为 +128°C ❌ 等等!这不对吧?

错了!因为它是补码表示法。正确做法是先右移4位(舍弃低4位),得到高12位,再判断符号。

✅ 正确解读方式:

int16_t raw = 0xFFE7;         // 原始读数
raw >>= 4;                    // 只保留高12位
if (raw & 0x800) {            // 判断是否为负数(第11位为1)
    raw -= 4096;              // 补码转原码:减去2^12
}
float temp = raw * 0.0625;    // 每LSB = 0.0625°C

所以 0xFFE7 >> 4 = 0xFFF = -1 ,对应 -1 × 0.0625 = -0.0625°C ——这才合理!

💡 提示:为了获得最高精度,建议始终配置为12位模式,并按上述方法解码。


I²C协议基础:两根线如何传遍天下?

现在我们知道DS1621输出的是数字信号,那它是怎么跟单片机说话的呢?答案就是—— I²C总线协议

I²C(Inter-Integrated Circuit)是由飞利浦(现NXP)发明的一种双线串行通信协议,仅需两根线即可实现多设备互联:

  • SCL :串行时钟线,由主设备(如MCU)控制;
  • SDA :串行数据线,双向传输,所有设备共享。

它的最大优势在于:
- 引脚少,布线简单;
- 支持多主多从架构;
- 地址寻址机制允许挂载多个同类型设备;
- 速率灵活:标准模式100kbps,快速模式400kbps;

不过问题来了: 大多数51单片机没有硬件I²C模块怎么办?

👉 那就 软件模拟 呗!只要有两个GPIO口,就能手动“捏”出符合规范的时序波形。


软件模拟I²C:手搓时序的艺术 🛠️

我们选用P2.0作为SCL,P2.1作为SDA,全部设为开漏输出(配合外部4.7kΩ上拉电阻)。

先定义引脚宏:

sbit SCL = P2^0;
sbit SDA = P2^1;

#define I2C_DELAY() delay_us(5)

注意:这里的延时函数至关重要!必须满足I²C时序要求。对于100kHz通信,典型参数如下:

参数 最小值 单位
T_HIGH 4.0 μs
T_LOW 4.7 μs
T_RISE - ≤1000ns
T_FALL - ≤300ns

所以我们写的 delay_us(5) 刚好够用(12MHz晶振下空循环约1μs/次)。


起始与停止信号:通信的开关门

任何I²C通信都始于 起始条件 (START),终于 停止条件 (STOP)。

  • START :SCL为高时,SDA由高→低;
  • STOP :SCL为高时,SDA由低→高;
void I2C_Start() {
    SDA = 1; I2C_DELAY();
    SCL = 1; I2C_DELAY();
    SDA = 0; I2C_DELAY();  // START发生
    SCL = 0;               // 准备发送数据
}

void I2C_Stop() {
    SDA = 0; I2C_DELAY();
    SCL = 1; I2C_DELAY();  // SCL上升沿
    SDA = 1; I2C_DELAY();  // STOP发生
}

⚠️ 注意顺序不能错!否则从设备可能无法识别。


字节写入与读取:逐位传输的艺术

每个字节传输都要遵循“高位先发”原则,在SCL上升沿被采样。

void I2C_Write(uint8 byte) {
    uint8 i;
    for(i=0; i<8; i++) {
        SCL = 0;
        if(byte & 0x80) SDA = 1;
        else            SDA = 0;
        I2C_DELAY();
        SCL = 1;           // 上升沿锁存
        I2C_DELAY();
        SCL = 0;
        byte <<= 1;
    }
    // 接收ACK
    SCL = 0; SDA = 1; I2C_DELAY();  // 释放SDA
    SCL = 1; I2C_DELAY();
    uint8 ack = SDA;
    SCL = 0;
}

uint8 I2C_Read(bit ack) {
    uint8 i, byte = 0;
    SDA = 1;  // 释放总线
    for(i=0; i<8; i++) {
        byte <<= 1;
        SCL = 0; I2C_DELAY();
        SCL = 1; I2C_DELAY();
        if(SDA) byte |= 0x01;
        SCL = 0;
    }
    // 发送ACK/NACK
    SCL = 0;
    if(ack == 0) SDA = 0;  // ACK
    else         SDA = 1;  // NACK
    I2C_DELAY();
    SCL = 1; I2C_DELAY();
    SCL = 0;
    return byte;
}

看到没?这就是所谓的“位 banging”——靠精准延时和引脚翻转来模拟协议。虽然效率不如硬件I²C,但在资源受限的51平台上完全够用!


与DS1621通信实战:初始化与读温全流程

DS1621上电后默认处于“一次性测温”模式,也就是说每次都需要手动触发一次转换。但我们更希望它 连续自动更新温度 ,这就需要写入配置寄存器。

配置寄存器详解(地址0xAC)
Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
Done THF TLF NVB RS1 RS0 OS RST

重点关注:
- RS1/RS0 :分辨率设置(00=9bit, 11=12bit)
- OS :置1开启连续测温模式
- RST :正常工作应为0

我们要设为10位分辨率+连续模式 → 0x02

void DS1621_Init() {
    I2C_Start();
    I2C_Write(0x90);       // 写地址(A2A1A0=000)
    I2C_Write(0xAC);       // 命令寄存器
    I2C_Write(0x02);       // 连续模式,10位分辨率
    I2C_Stop();
}

📌 注意:DS1621的7位地址由A2/A1/A0引脚决定,默认为 1001000 (0x48),所以写地址为 0x90 (左移+R/W=0),读地址为 0x91

初始化完成后,就可以随时读取温度啦!

float get_temperature() {
    sint16 raw;

    // 先写寄存器指针
    I2C_Start();
    I2C_Write(0x90);
    I2C_Write(0x00);  // 指向温度寄存器
    I2C_Start();      // 重复起始

    // 开始读
    I2C_Write(0x91);
    raw = (sint16)I2C_Read(0) << 8;  // 高字节+ACK
    raw |= I2C_Read(1);              // 低字节+NACK
    I2C_Stop();

    // 解码温度
    raw >>= 4;
    if(raw & 0x800) raw -= 4096;
    return raw * 0.0625;
}

是不是很清晰?整个流程一气呵成,连贯流畅,就像一场精心编排的舞蹈💃🕺


LCD12864图形液晶显示模块驱动原理与初始化配置

如果说传感器是系统的“感官”,那么显示屏就是它的“脸面”。用户能不能一眼看懂当前状态,全靠这块屏幕的表现力。

LCD12864是一款经典的128×64点阵图形液晶模块,不仅能显示文字,还能画图、做动画,非常适合做小型HMI人机界面。但它不像OLED那么简单即插即用,要想驾驭它,必须深入理解其控制器和显存结构。


控制器家族揭秘:KS0108 vs KS0713

市面上常见的LCD12864大多基于 KS0108 KS0713 图形控制器。两者功能高度兼容,但略有差异:

特性 KS0108 KS0713
工作电压 +5V 3.3V ~ 5.5V
是否内置振荡
接口 并行8位 并行/可配串行
显存 1KB 相同
应用场景 教学常用 新型低功耗设计

无论哪种,它们都采用 分页式显存结构 ,并且需要两个控制器分别管理左右半屏(各64列)。


硬件连接:12根线掌控全局

典型连接方式如下:

graph TD
    A[51单片机] -->|P0.0-P0.7| B[D0-D7]
    A -->|P2.0| C[RS]
    A -->|P2.1| D[R_W]
    A -->|P2.2| E[E]
    A -->|P2.3| F[CS1]
    A -->|P2.4| G[CS2]
    H[Voltage Regulator] -->|Adjustable| I[VO for Contrast]
    J[Power Supply +5V] --> K[LCD VDD]
    L[Reset Circuit] --> M[RST]

关键信号解释:
- RS :0=指令,1=数据;
- R/W :0=写,1=读(一般只写不读);
- E :使能信号,下降沿触发;
- CS1/CS2 :片选,分别控制左/右半屏;
- VO :对比度调节,建议接电位器中间抽头;

⚠️ 警告:不要同时激活CS1和CS2!否则可能导致总线冲突或显示异常。


显存映射机制:三维寻址的奥秘

LCD12864的显存不是线性的,而是按“页-列-字节”组织:

  • 8页 :每页8行像素(Y方向),共64行;
  • 每页128列 :X方向;
  • 每字节8位 :垂直排列,bit7在上,bit0在下;

这意味着:
- 修改某个像素必须“读-改-写”整个字节;
- 无法单独读取显存内容(除非硬件支持);
- 必须维护一个 显存镜像缓冲区 (Shadow Buffer)才能高效绘图!

例如,要绘制坐标(x=50, y=10)的点:
- y=10 → 属于Page 1(8~15行)
- bit位置 = y % 8 = 2 → 对应bit5(高位在顶)
- x=50 → 第50列
- 目标字节地址:Page1起始 + 50


初始化流程:让黑屏变活屏

刚上电的LCD是一片漆黑,必须经过一系列指令唤醒:

void LCD_Init() {
    delay_ms(50);  // 上电延迟

    LCD_Write_Command(0xC0, 0);  // 左半屏:起始行0
    LCD_Write_Command(0x3F, 0);  // 开启显示
    LCD_Write_Command(0xC0, 1);  // 右半屏
    LCD_Write_Command(0x3F, 1);

    LCD_Clear();           // 清屏
    Set_Address(0, 0);     // 回到左上角
}

其中:
- 0xC0 设置起始行(可用于滚屏效果)
- 0x3F 是Display ON指令,必不可少!
- Set_Address(x, page) 用于定位写入位置


文本显示:打造专属字体库

虽然它是图形屏,但我们仍希望能轻松打印字符串。为此可以预存ASCII字符点阵:

const unsigned char font_8x8[][8] = {
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // space
    {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00}, // '!'
    // ...更多字符
};

然后封装绘图函数:

void LCD_Draw_Char(unsigned char x, unsigned char y, char ch) {
    unsigned char i, page = y / 8;
    const unsigned char *p = font_8x8[ch - ' '];

    for(i = 0; i < 8; i++) {
        Set_Address(x + i, page);
        Write_Data(p[i], (x+i)<64 ? 0 : 1);
    }
}

中文怎么办?可以用PCtoLCD2002生成16×16点阵数组,同样固化进代码即可。


温度数据采集、处理与可视化流程设计

终于到了最后一步:把前面所有模块串联起来,打造一个真正可用的温度监控系统!

我们的目标是:
- 每500ms采集一次温度;
- 进行滤波去噪;
- 转换为带两位小数的字符串;
- 在LCD上动态刷新显示;
- 整体运行稳定可靠,无闪烁、无卡顿。


主控架构设计:状态机驱动一切

为了避免“意大利面条代码”,我们采用 状态机 + 定时器中断 的方式组织程序:

typedef enum {
    STATE_IDLE,
    STATE_TEMP_READ,
    STATE_PROCESSING,
    STATE_DISPLAY_UPDATE,
} SystemState;

SystemState current_state = STATE_IDLE;
bit Timer_Flag_500ms = 0;

void main() {
    System_Init();  // 所有外设初始化

    while(1) {
        switch(current_state) {
            case STATE_IDLE:
                if(Timer_Flag_500ms) {
                    current_state = STATE_TEMP_READ;
                }
                break;

            case STATE_TEMP_READ:
                raw_temp = DS1621_ReadTemperature();
                if(raw_temp != ERROR) {
                    current_state = STATE_PROCESSING;
                }
                break;

            case STATE_PROCESSING:
                filtered_temp = Apply_Filter(raw_temp);
                Convert_To_String(filtered_temp, temp_str);
                current_state = STATE_DISPLAY_UPDATE;
                break;

            case STATE_DISPLAY_UPDATE:
                LCD_Update_Temperature(temp_str);
                current_state = STATE_IDLE;
                Timer_Flag_500ms = 0;
                break;
        }
    }
}

这样的结构清晰、易于扩展,未来加报警、串口上传都不怕!


数据滤波:滑动平均 + 限幅双重保险

原始温度容易跳变,我们可以叠加两种轻量级滤波算法:

滑动平均(Moving Average)
#define FILTER_SIZE 4
int16_t buffer[FILTER_SIZE];
uint8_t index = 0;

int16_t moving_avg(int16_t new_val) {
    buffer[index] = new_val;
    index = (index + 1) % FILTER_SIZE;

    int32_t sum = 0;
    for(int i = 0; i < FILTER_SIZE; i++) sum += buffer[i];
    return sum / FILTER_SIZE;
}
限幅滤波(Deadband)

防止突变干扰:

int16_t last_valid = 0;

int16_t limit_amp(int16_t curr) {
    if(abs(curr - last_valid) > 60)  // 超过3°C视为异常
        return last_valid;
    else
        return last_valid = curr;
}

最终输出:

filtered = limit_amp(moving_avg(raw));

LCD显示布局与局部刷新策略

为了美观,我们将屏幕划分为三部分:

|         温度监控系统 v1.0          |  ← Page 0
|                                   |
|       当前温度: +25.75 ℃         |  ← Page 3
|                                   |
|     更新时间: 14:32:15           |  ← Page 6

为了避免全屏刷新带来的闪烁,只更新“脏区域”:

typedef struct {
    uint8_t page_start, page_end;
    uint8_t col_start, col_end;
    uint8_t dirty;
} DisplayRegion;

DisplayRegion temp_region = {3, 3, 0, 127, 1};

void LCD_Refresh_Regions() {
    if(temp_region.dirty) {
        LCD_Set_Page(3);
        LCD_Set_Column(0);
        LCD_Print_String("当前温度: ");
        LCD_Print_String(temp_str);
        temp_region.dirty = 0;
    }
}

这样既流畅又节能,用户体验瞬间提升好几个档次 😎


实物部署与稳定性测试

最后一步,把HEX文件烧录进开发板!

使用 STC-ISP 工具 + CH340G USB转TTL模块:

  1. 断电连接:TXD→P3.1, RXD→P3.0, GND→GND
  2. 打开软件,选择MCU型号、晶振频率
  3. 加载HEX文件,点击下载
  4. 给单片机上电,自动开始烧录
  5. 成功后观察LED闪烁、LCD显示温度

实测表现:
- 温度响应延迟 < 600ms
- 连续运行24小时无死机
- I²C通信稳定,LCD刷新平滑

🎉 至此,一个完整的嵌入式温度监控系统宣告完成!


这套方案不仅适用于教学实验,也可用于工业现场监测、智能家居温控等真实场景。更重要的是,它教会我们如何从零开始构建一个完整系统—— 硬件连接、协议解析、软件架构、人机交互,缺一不可

而这,也正是嵌入式开发的魅力所在 ❤️

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本设计实现了一个基于51单片机的温度监测系统,通过DS1621数字温度传感器采集环境温度,并利用LCD12864图形液晶屏实时显示数据。系统采用I²C通信协议进行传感器数据读取,结合C语言编程实现LCD驱动和温度信息可视化。项目包含完整源码(如LCD_DRIVE.c)、可烧录HEX文件及Protues仿真文件(lcd.DSN、lcd.PWI等),支持虚拟仿真与实物部署,适用于毕业设计、课程设计及嵌入式系统学习,涵盖单片机控制、传感器应用、液晶显示驱动和仿真调试等核心技术。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐