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

简介:1线通讯协议(单总线协议)是一种高效的数据通信方式,仅需一根数据线实现双向通信,广泛应用于嵌入式系统中。本文围绕在8051微控制器上使用C51编写的1线通讯协议示例展开,讲解如何在资源受限环境下实现该协议。内容涵盖引脚配置、时序控制、数据编码解码、握手机制、错误检测等关键技术点,并结合示例程序帮助开发者掌握1线协议的实现流程与调试技巧。
1线通讯协议

1. 1线通讯协议简介

1线通讯协议(One-Wire Protocol)是一种由Maxim Integrated(原Dallas Semiconductor)开发的串行通信协议,仅需一根数据线(外加地线)即可实现主从设备之间的双向通信。其核心优势在于布线简单、硬件开销小、适用于低速、低功耗的应用场景,如温度传感器(DS18B20)、身份识别芯片(iButton)等嵌入式系统。

与I2C、SPI等常见总线协议相比,1线协议采用半双工通信方式,所有设备共享同一根数据线,通过时序控制实现多设备寻址与数据交换。主设备通过精确控制时序来发起通信,从设备则依赖内部寄生电源或外部供电完成响应。这种结构极大地降低了硬件复杂度,但对软件时序控制提出了更高要求。

由于其低功耗、低成本、易于集成的特性,1线协议广泛应用于智能家居、环境监测、工业控制等对通信速率要求不高但对稳定性和布线简化有较高需求的场景。理解其工作原理和实现机制,对于嵌入式开发者来说具有重要的实践意义。

2. C51与8051微控制器开发环境搭建

在嵌入式系统开发中,构建一个稳定、高效的开发环境是成功实现项目的第一步。本章将围绕C51与8051系列微控制器,详细介绍开发环境的搭建流程,涵盖开发平台的选择、硬件仿真工具的使用、工程的创建与配置,以及程序的下载与调试流程。通过本章的学习,开发者将能够熟练掌握在Keil μVision环境中进行C51项目开发的基础技能,并理解与标准8051指令集的兼容性问题。

2.1 开发平台概述

嵌入式系统开发平台是开发者编写、编译、调试和下载程序的综合工具集。对于8051系列微控制器而言,Keil μVision 是最主流的集成开发环境(IDE),它不仅支持C语言开发,还提供丰富的调试功能和仿真支持。

2.1.1 Keil μVision集成开发环境简介

Keil μVision 是由ARM公司推出的嵌入式开发平台,广泛支持包括8051、ARM7、Cortex-M系列在内的多种微控制器。其核心优势在于:

  • 统一的用户界面 :提供代码编辑、编译、调试一体化操作。
  • 强大的调试功能 :支持软件仿真、硬件调试、断点设置等。
  • 丰富的库支持 :包含标准外设库、RTOS支持等。

安装Keil μVision 后,可以通过菜单【Project】→【New μVision Project】来创建新项目,选择目标芯片型号(如STC89C52、AT89C51等)后,IDE会自动加载相应的头文件和启动代码。

小贴士 :首次使用Keil μVision时,建议安装C51编译器组件,否则无法编译8051系列芯片的代码。

2.1.2 C51编译器与标准8051指令集的兼容性

C51编译器是Keil μVision中用于编译C语言程序的工具链,其生成的目标代码严格遵循8051指令集架构。以下是C51编译器的一些关键特性:

特性 描述
数据类型 支持标准C语言的数据类型,如 char int long 等,同时也支持8051特有的 bit sfr 类型
存储模型 提供 small compact large 三种存储模型,适应不同的内存需求
寄存器优化 可通过 #pragma 指令指定寄存器分配,提升执行效率
指令映射 编译器会将C语言代码映射为标准8051指令,如 MOV JMP SETB
#include <reg52.h>

sbit LED = P1^0;  // 定义P1.0为LED控制引脚

void delay(unsigned int time) {
    unsigned int i, j;
    for(i = 0; i < time; i++)
        for(j = 0; j < 120; j++);
}

void main() {
    while(1) {
        LED = 0;         // 点亮LED
        delay(500);      // 延时
        LED = 1;         // 关闭LED
        delay(500);
    }
}

逐行代码分析

  1. #include <reg52.h> :引入8051标准寄存器头文件,定义了P0~P3等端口寄存器。
  2. sbit LED = P1^0; :使用sbit类型定义一个位变量,对应P1口的第0位。
  3. delay() 函数:基于双重循环实现的软件延时,适用于8051的简单应用。
  4. main() 函数中控制LED闪烁,展示了一个基本的C51程序结构。

2.2 硬件仿真与调试工具

在实际硬件调试之前,使用仿真工具可以提前发现逻辑错误,降低调试成本。

2.2.1 使用STC-ISP进行芯片烧录

STC-ISP是STC系列单片机的官方烧录工具,支持串口下载、时钟配置、看门狗设置等功能。使用流程如下:

  1. 将单片机插入下载座,连接USB转TTL模块。
  2. 打开STC-ISP,选择对应芯片型号(如STC89C52RC)。
  3. 点击“打开程序文件”,加载.hex格式的编译输出文件。
  4. 设置串口号与波特率,点击“下载”按钮开始烧录。

参数说明

参数 说明
COM Port 串口号,需与USB转TTL模块一致
Baud Rate 波特率,一般设置为9600或115200
Crystal Frequency 晶振频率,影响定时器精度
Watchdog Enable 是否启用看门狗

2.2.2 利用Proteus进行电路仿真

Proteus 是一款强大的电路仿真软件,支持8051系列微控制器的软硬件联合仿真。其优势在于:

  • 支持虚拟元器件(如LED、LCD、DS18B20等)。
  • 可加载Keil生成的.hex文件进行仿真。
  • 实时观察I/O引脚电平、波形等信号。

使用步骤

  1. 在Proteus中绘制电路图,添加8051芯片和LED等外围元件。
  2. 将Keil生成的.hex文件加载到芯片中。
  3. 点击“运行”按钮,观察LED闪烁效果。
graph TD
    A[绘制电路图] --> B[添加8051芯片]
    B --> C[连接LED与限流电阻]
    C --> D[加载.hex文件]
    D --> E[运行仿真]

2.3 工程创建与配置

一个良好的工程结构对于代码维护和团队协作至关重要。

2.3.1 新建C51工程的步骤

  1. 打开Keil μVision,点击【Project】→【New μVision Project】。
  2. 选择工程保存路径并命名工程文件(如LED_Blink.uvprojx)。
  3. 在弹出的芯片选择窗口中,选择目标MCU(如AT89C51)。
  4. 点击“OK”,Keil将自动生成启动文件(STARTUP.A51)和默认配置。
  5. 添加源文件(如main.c)到工程中。

2.3.2 添加头文件与源文件

在Keil中添加头文件和源文件的步骤如下:

  1. 在左侧“Project”窗口右键点击“Source Group 1”。
  2. 选择【Add New Item to Group】,创建main.c文件。
  3. 编写主程序代码后,保存并编译。
  4. 添加外部头文件(如delay.h、ds18b20.h)时,需在Options for Target中设置Include路径。
// main.c
#include <reg52.h>
#include "delay.h"

sbit LED = P1^0;

void main() {
    while(1) {
        LED = 0;
        delay_ms(500);
        LED = 1;
        delay_ms(500);
    }
}
// delay.h
#ifndef _DELAY_H_
#define _DELAY_H_

void delay_ms(unsigned int ms);

#endif
// delay.c
#include <reg52.h>

void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = 0; i < ms; i++)
        for(j = 0; j < 123; j++);
}

代码说明

  • delay_ms() 函数实现了毫秒级延时,通过外部头文件封装,便于复用。
  • sbit 类型用于访问单片机的位地址,提高代码可读性。

2.4 程序下载与调试流程

2.4.1 下载器的连接与配置

常见的8051下载器包括USB转TTL模块、STC专用下载器等。连接方式如下:

引脚 连接说明
TXD 发送引脚,连接MCU的RXD
RXD 接收引脚,连接MCU的TXD
GND 接地
VCC 电源(根据MCU电压选择3.3V或5V)

配置Keil进行串口下载:

  1. 点击【Flash】→【Configure Flash Tools】。
  2. 在“Utilities”选项卡中选择“Use External Tool for Downloading”。
  3. 设置串口工具路径为STC-ISP或其它下载工具。

2.4.2 调试接口与断点设置

Keil μVision支持通过硬件调试器(如ULINK、ST-Link)进行实时调试。调试流程如下:

  1. 点击【Debug】→【Start/Stop Debug Session】进入调试模式。
  2. 使用【Step Over】、【Step Into】等按钮进行单步调试。
  3. 在代码行号前点击设置断点,程序运行到断点处将自动暂停。
  4. 使用【Watch Window】查看变量值,使用【Memory Window】查看内存内容。
graph LR
    A[进入调试模式] --> B[设置断点]
    B --> C[单步执行]
    C --> D[观察变量]
    D --> E[查看内存]

调试技巧

  • 使用“Call Stack”查看函数调用栈。
  • 使用“Registers”查看CPU寄存器状态。
  • 使用“Disassembly”查看汇编代码,分析指令执行情况。

通过本章的学习,开发者已经掌握了从开发环境搭建、代码编写、工程管理到程序下载与调试的完整流程。下一章我们将深入探讨1线总线的硬件连接方式与上拉电阻的设置,为后续的通信协议实现打下基础。

3. 单总线引脚配置与上拉电阻设置

在嵌入式系统中,1线(1-Wire)总线协议因其仅需一根数据线即可实现双向通信而被广泛应用于低功耗、低成本场景。然而,1线协议的物理层特性决定了其对硬件连接方式和上拉电阻设置的依赖性极强。本章将围绕单总线的硬件配置展开深入探讨,包括引脚输入输出模式的设置、端口寄存器的配置、上拉电阻的作用与选取原则,以及实际电路搭建与测试方法,帮助读者掌握1线总线的硬件基础配置要点。

3.1 单总线的硬件连接方式

1线总线采用开漏输出结构,通信线在没有被主动拉低时处于高电平状态,因此必须通过上拉电阻维持高电平。由于总线仅有一根数据线,主控设备与从设备之间的通信完全依赖该线的状态变化。因此,正确的引脚配置是1线通信稳定运行的前提。

3.1.1 单总线引脚的输入输出模式配置

在8051系列微控制器中,通常将一个通用I/O引脚配置为开漏输出或双向输入/输出模式以适应1线协议的需要。以C51为例,需对P0M1和P0M0寄存器进行设置以改变引脚的输入输出模式。

以下是一个配置P1.0为开漏输出的示例代码:

#include <reg52.h>

sbit OW_PIN = P1^0;  // 定义1线总线连接的引脚

void OW_Init(void) {
    P1M1 &= ~0x01;   // 设置P1.0为开漏输出
    P1M0 |= 0x01;
}

void main(void) {
    OW_Init();
    while (1) {
        // 主循环
    }
}

代码逻辑分析:

  • sbit OW_PIN = P1^0; :定义P1.0引脚为1线通信引脚。
  • P1M1 &= ~0x01; :清除P1M1寄存器的第0位,表示不使能高阻态。
  • P1M0 |= 0x01; :设置P1M0寄存器的第0位,表示设置为开漏输出。

这种配置允许引脚在输出低电平时拉低总线,而在释放总线时由上拉电阻拉高,实现1线协议所需的双向通信机制。

3.1.2 引脚复用与端口寄存器设置

在某些嵌入式系统中,GPIO引脚可能具有复用功能(如定时器输出、串口引脚等),需要确认所选引脚是否支持开漏输出或双向输入/输出模式。例如,在STC系列单片机中,可以通过配置PnM1和PnM0寄存器来实现不同模式。

端口模式配置表如下:

PnM1 PnM0 引脚模式
0 0 准双向口(默认)
0 1 推挽输出
1 0 高阻输入
1 1 开漏输出

逻辑分析:

  • 在1线通信中,主设备需要能够读取总线状态并主动拉低总线,因此推荐使用开漏输出模式(PnM1=1, PnM0=1)。
  • 如果使用准双向口(默认模式),可能会因为内部上拉电阻过大导致总线拉低困难,影响通信稳定性。

3.2 上拉电阻的作用与选取

由于1线总线的通信机制依赖于开漏输出结构,因此必须通过外部上拉电阻将总线拉高。上拉电阻的选择直接影响通信速度、信号完整性以及功耗表现。

3.2.1 为什么单总线需要上拉电阻

1线总线采用单线双向通信方式,所有设备(包括主设备和从设备)均通过该线发送和接收数据。当总线空闲时,必须维持高电平状态,而主设备或从设备在通信时会拉低总线。由于所有设备的输出端均为开漏结构,无法主动输出高电平,因此必须依靠外部上拉电阻将总线拉高。

关键作用如下:

  • 维持总线空闲时的高电平状态;
  • 在通信过程中为从设备提供工作电流;
  • 控制总线上升沿的斜率,影响通信速度和信号完整性。

3.2.2 阻值选择对信号完整性的影响

上拉电阻的阻值选择需综合考虑通信速率、功耗、噪声抑制等因素。常见阻值范围为4.7kΩ至10kΩ。

典型阻值及其影响分析:

阻值(Ω) 特点分析
1k 上升沿快,功耗大,可能引起噪声干扰
4.7k 平衡通信速度与功耗,适用于多数场景
10k 上升沿较慢,降低功耗,适合低速通信
100k 信号完整性差,通信失败风险高

电路分析示意图(mermaid流程图):

graph TD
    A[主设备] --> B(1线总线)
    C[从设备] --> B
    B --> D[上拉电阻]
    D --> E[VCC]

结论:

  • 对于标准1线通信(如DS18B20温度传感器),推荐使用4.7kΩ上拉电阻;
  • 在多设备并联或长距离通信中,可能需要减小阻值以提高驱动能力;
  • 为降低功耗可适当增大阻值,但需保证通信稳定性。

3.3 实际电路搭建与测试

在完成引脚配置与上拉电阻选择后,下一步是搭建实际电路并进行测试。本节将介绍1线总线最小系统的搭建方法,并通过示波器观察总线电平变化,验证通信的可行性。

3.3.1 搭建最小系统与总线连接

一个典型的1线总线系统包括:

  • 8051或C51微控制器;
  • DS18B20温度传感器(或其他1线设备);
  • 上拉电阻(4.7kΩ);
  • 电源(通常为3.3V或5V);
  • 复位电路与晶振电路。

电路连接示意图如下:

graph TD
    A[微控制器 P1.0] --> B(1线总线)
    C[DS18B20 DQ引脚] --> B
    B --> D[4.7kΩ上拉电阻]
    D --> E[VCC]
    F[DS18B20 GND] --> G[地]
    H[DS18B20 VDD] --> I[VCC]

连接说明:

  • DS18B20的DQ引脚连接到微控制器的1线通信引脚(如P1.0);
  • VDD引脚连接到VCC,确保传感器获得稳定电源;
  • GND引脚接地;
  • 4.7kΩ上拉电阻连接在DQ与VCC之间。

3.3.2 使用示波器观察信号电平变化

为了验证1线通信是否正常工作,可以使用示波器测量DQ引脚的电平变化。以下是一个典型的复位脉冲和存在脉冲的波形示例:

// 示例:发送复位脉冲并检测存在信号
bit OW_Reset(void) {
    bit presence;

    OW_PIN = 0;         // 拉低总线
    Delay_us(480);      // 保持低电平480us
    OW_PIN = 1;         // 释放总线,等待从设备响应
    Delay_us(60);       // 等待60us后读取存在信号
    presence = !OW_PIN; // 检测是否存在设备
    Delay_us(240);      // 等待存在信号结束
    return presence;
}

代码逻辑分析:

  • OW_PIN = 0; :主设备拉低总线,开始复位过程;
  • Delay_us(480); :保持低电平至少480μs,确保所有从设备复位;
  • OW_PIN = 1; :释放总线,允许从设备拉低总线以发送存在信号;
  • presence = !OW_PIN; :若在60μs内总线被拉低,则表示有从设备存在;
  • Delay_us(240); :等待存在信号结束。

示波器观察要点:

  • 复位脉冲应持续约480μs;
  • 存在脉冲应在60μs内出现;
  • 若未检测到存在信号,可能原因为:
  • 总线未正确连接;
  • 上拉电阻未接或阻值过大;
  • 从设备未供电或损坏;
  • 引脚配置错误导致无法读取总线状态。

本章通过详细讲解1线总线的引脚配置、上拉电阻作用与选取、以及实际电路搭建与测试方法,为后续实现1线协议通信打下了坚实的硬件基础。下一章将深入探讨如何通过精确的时序控制实现1线协议的通信过程。

4. 精确时序控制实现(delay函数与时钟周期计算)

在1线通信协议的实现过程中, 精确的时序控制 是确保数据正确传输的关键因素。由于1线协议是一种半双工的通信方式,主设备与从设备之间的所有通信都依赖于严格的时序定义,任何时序误差都可能导致数据读写失败。因此,掌握如何在C51环境下通过 软件延时函数 定时器控制 来实现高精度的延时,并理解 时钟周期与指令周期之间的关系 ,是实现稳定1线通信的基础。

本章将从 时序控制的重要性 出发,深入探讨 基于循环的软件延时机制 基于定时器的硬件延时方法 ,并结合 8051微控制器的时钟频率 ,详细讲解 延时函数的执行时间计算方式 ,帮助开发者在资源有限的嵌入式系统中实现高效、准确的时序控制。

4.1 时序控制的重要性

4.1.1 1线协议对时序的严格要求

1线协议是一种单引脚、半双工的通信方式,其通信过程完全依赖于主设备对时间的精准控制。例如,在DS18B20温度传感器中,主设备发送一个 复位脉冲(Reset Pulse) ,必须保持低电平至少480μs,然后释放总线,等待从设备的 存在脉冲(Presence Pulse) 。从设备在60~240μs之间响应一个低电平,主设备需要在该时间段内检测到低电平,才能确认从设备的存在。

这些时序参数的容忍度非常小,因此, 主设备必须能够生成精确的延时 ,以满足协议规范。

以下是一个典型的1线协议时序图(使用mermaid流程图表示):

sequenceDiagram
    主设备->>总线: 拉低总线480us
    主设备->>总线: 释放总线,进入输入模式
    总线-->>主设备: 从设备拉低总线60~240us
    主设备->>总线: 读取总线电平,判断是否存在设备

从该图可以看出, 主设备必须在精确的时间点拉低或释放总线,并在指定的时间窗口内读取数据 ,否则通信将失败。

4.1.2 不同操作阶段的时序参数差异

1线协议中不同的操作阶段具有不同的时序要求。例如:

操作类型 时序参数(典型值)
复位脉冲 低电平持续480μs
存在脉冲检测窗口 60~240μs
写0 低电平持续60~120μs
写1 低电平持续1~15μs
读位采样点 低电平后15~60μs采样

由此可见, 同一个通信过程中的不同阶段可能需要不同精度的延时函数 ,这就要求开发者能够灵活配置延时函数以适应不同的时序要求。

4.2 delay函数的实现方式

4.2.1 基于循环的软件延时函数

在C51中, 基于循环的软件延时函数 是最常见、最简单的实现方式。其原理是通过嵌套的for循环,消耗CPU周期来达到延时的目的。虽然这种方法精度不高,但在资源受限的8位MCU中仍然被广泛使用。

示例:基于循环的延时函数
void delay_us(unsigned int us) {
    while(us--) {
        _nop_(); _nop_(); _nop_(); _nop_(); // 每个_nop_()占用1个机器周期
    }
}
逻辑分析:
  • _nop_() 是一个空操作指令,占1个机器周期。
  • 假设系统时钟为12MHz,则每个机器周期为 1μs。
  • 每个 _nop_() 执行1μs,4个 _nop_() 共4μs。
  • 因此, delay_us(1) 实际延时为 4μs。
参数说明:
  • us :表示期望延时的微秒数。
  • 实际延时 = us * 4μs (假设4个_nop_())
使用方式:
// 延时 100μs
delay_us(25); // 25 * 4 = 100μs
局限性:
  • 精度依赖系统时钟和编译器优化
  • 不可中断 ,在延时期间CPU无法执行其他任务。
  • 不适用于高精度或长时间延时场景

4.2.2 利用定时器实现高精度延时

为了实现更高精度和更灵活的延时控制,通常使用 定时器中断 来实现延时功能。8051微控制器内置了多个定时器/计数器,可以配置为定时器模式,实现高精度的延时。

示例:使用定时器0实现1ms延时
#include <reg52.h>

sbit LED = P1^0;

void Timer0_Init(void) {
    TMOD = 0x02;         // 定时器0,模式2(8位自动重载)
    TH0 = 0x06;          // 1ms定时初值(12MHz晶振)
    TL0 = 0x06;
    ET0 = 1;             // 使能定时器0中断
    EA = 1;              // 使能全局中断
    TR0 = 1;             // 启动定时器0
}

unsigned int ms_count = 0;

void Timer0_ISR(void) interrupt 1 {
    ms_count++;
}

void delay_ms(unsigned int ms) {
    unsigned int start = ms_count;
    while((ms_count - start) < ms); // 等待指定毫秒数
}

void main(void) {
    Timer0_Init();
    while(1) {
        LED = ~LED;          // 翻转LED状态
        delay_ms(500);       // 延时500ms
    }
}
逻辑分析:
  • TMOD = 0x02 :设置定时器0为模式2(8位自动重载),适合用于周期性定时。
  • TH0 = 0x06; TL0 = 0x06 :定时器初值为0x06,配合12MHz晶振,每1ms产生一次中断。
  • ms_count 变量用于记录中断次数,每中断一次即1ms。
  • delay_ms() 函数通过比较 ms_count 的变化来实现延时。
参数说明:
  • ms :表示期望延时的毫秒数。
  • 实际延时误差较小,适用于长时间延时。
优势:
  • 高精度:基于晶振,误差小。
  • 可中断:CPU可以在延时期间执行其他任务。
  • 可扩展性强:可用于实现多任务调度、定时采集等复杂逻辑。

4.3 时钟周期与指令周期的计算

4.3.1 8051的时钟频率与机器周期关系

8051微控制器的 机器周期 是其基本的时间单位。它与 系统时钟频率 之间有如下关系:

\text{机器周期} = \frac{12}{\text{系统时钟频率 (Hz)}}

示例表格:
系统时钟频率 机器周期
12 MHz 1 μs
11.0592 MHz 1.085 μs
24 MHz 0.5 μs

这意味着,在12MHz系统时钟下,一个机器周期为1μs,这是C51编程中最常见的设定。

4.3.2 计算延时函数的精确执行时间

在编写延时函数时,必须根据 系统时钟 指令周期 来计算函数的执行时间。例如:

示例:计算循环延时函数的执行时间
void delay_100us(void) {
    unsigned char i;
    for(i = 0; i < 25; i++) {
        _nop_();
    }
}
  • 每个 _nop_() 执行1个机器周期(1μs)。
  • for循环内部每次循环执行4个机器周期(包括赋值、比较、自增、跳转)。
  • 所以每轮循环大约执行 1 + 4 = 5 μs。
  • 25次循环:25 × 5 = 125 μs

所以该函数实际延时约为125μs,而非100μs。

改进方式:

为了更精确地实现100μs延时,可调整循环次数:

void delay_100us(void) {
    unsigned char i;
    for(i = 0; i < 20; i++) {
        _nop_();
    }
}

此时:20 × 5 = 100μs,较为准确。

更复杂的延时函数(毫秒级)
void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = 0; i < ms; i++) {
        for(j = 0; j < 123; j++);
    }
}
  • 内层循环执行123次,每次约1μs(机器周期为1μs)。
  • 外层循环执行 ms 次。
  • 总延时 ≈ ms × 123 × 1μs ≈ ms × 123μs

所以若想实现1ms延时,应设置 ms = 8 ,因为 8 × 123 ≈ 984μs,接近1ms。

总结延伸

  • 1线协议的实现高度依赖于精确的时序控制 ,开发者必须理解并掌握 延时函数的实现方式
  • 基于循环的延时函数 适合短时间、低精度场景,而 定时器中断方式 适合高精度、长时间延时。
  • 了解8051的时钟频率与机器周期之间的关系 ,是编写精确延时函数的前提。
  • 在实际开发中,建议结合 示波器或逻辑分析仪 验证时序是否正确,以确保1线通信的稳定性。

在下一章中,我们将进一步探讨 1线协议的数据位传输格式与编码解码机制 ,帮助开发者理解如何正确读写数据位,并实现数据的准确解析。

5. 数据位传输格式与编码解码机制

1线协议(1-Wire Protocol)在数据传输过程中采用严格的时序控制机制,以确保主设备与从设备之间的通信可靠。本章将深入剖析1线协议中数据位的传输格式、编码方式以及解码机制,帮助开发者理解如何正确读写数据,并识别常见的通信错误。

5.1 1线协议的数据传输格式

1线协议的数据传输以“位(bit)”为单位,每个位的传输都必须遵循特定的时序规则。主设备通过发送特定宽度的低电平脉冲来表示写操作,而从设备则通过拉低总线的时间来表示响应读请求。

5.1.1 读写位的时序定义

  • 写0位 :主设备将总线拉低至少60μs,然后释放总线。
  • 写1位 :主设备将总线拉低1~15μs后释放。
  • 读位 :主设备将总线拉低1~15μs后释放,随后读取总线电平状态:
  • 如果在15~60μs内总线被从设备拉低,则为逻辑0;
  • 如果未被拉低,则为逻辑1。
// 示例:写1位操作函数
void OW_WriteBit(unsigned char bit)
{
    OW_PIN = 0;           // 拉低总线
    DelayUs(2);           // 保持低电平2μs
    OW_PIN = bit;         // 若为1,释放总线
    DelayUs(60);          // 等待时隙结束
}

逐行解析
- 第1行:定义函数,参数bit表示要写入的位(0或1)。
- 第2行:将单总线引脚置为低电平,启动写时序。
- 第3行:保持低电平2μs,符合写1的时序要求。
- 第4行:若bit为1,则引脚恢复高阻态;若为0,则保持低电平。
- 第5行:等待60μs,确保一个完整时隙的结束。

5.1.2 复位脉冲与存在脉冲的检测

复位操作是1线通信的起始信号,主设备发送复位脉冲后,从设备会回应一个存在脉冲(Presence Pulse)以表明已准备好通信。

// 示例:复位函数
unsigned char OW_Reset(void)
{
    unsigned char presence;
    OW_PIN = 0;        // 拉低总线
    DelayUs(480);      // 保持低电平至少480μs
    OW_PIN = 1;        // 释放总线
    DelayUs(70);       // 等待从设备响应
    presence = !OW_PIN; // 读取总线状态
    DelayUs(410);      // 等待存在脉冲结束
    return presence;   // 返回是否存在从设备
}

逐行解析
- 第1行:函数返回值表示是否存在从设备(1存在,0不存在)。
- 第2行:变量presence用于保存检测结果。
- 第3行:主设备拉低总线,开始复位。
- 第4行:保持低电平至少480μs,符合协议要求。
- 第5行:释放总线,等待从设备响应。
- 第6行:等待70μs后读取总线电平。
- 第7行:若此时总线仍为低电平,说明从设备存在。
- 第8行:等待剩余时间,完成整个复位周期。

5.2 数据编码方式

1线协议的数据编码依赖于精确的时间控制。主设备必须严格按照协议规定的时序来发送写0或写1信号,同时在读取数据时选择合适的采样时机。

5.2.1 写0与写1的时序差异

操作类型 主设备拉低时间 总线释放后保持时间 从设备响应时间
写0 ≥60μs - -
写1 1~15μs - -
sequenceDiagram
    主设备->>总线: 拉低总线
    主设备->>总线: 保持60μs (写0)
    总线-->>主设备: 释放总线

说明 :上图展示了写0位的时序流程,主设备在60μs后释放总线,从设备识别为逻辑0。

5.2.2 读取数据位的采样时机

主设备发送读位命令后,从设备会在15~60μs之间拉低总线以表示逻辑0。因此,主设备应在发送完读位脉冲后约15μs后开始采样。

// 示例:读一位函数
unsigned char OW_ReadBit(void)
{
    unsigned char bit;
    OW_PIN = 0;       // 拉低总线
    DelayUs(2);       // 保持2μs
    OW_PIN = 1;       // 释放总线
    DelayUs(10);      // 等待10μs后采样
    bit = OW_PIN;     // 读取总线状态
    DelayUs(50);      // 等待读位周期结束
    return bit;
}

逐行解析
- 第1行:返回值为读取到的bit值(0或1)。
- 第2行:拉低总线以启动读操作。
- 第3行:保持低电平2μs。
- 第4行:释放总线,等待从设备响应。
- 第5行:等待10μs后采样。
- 第6行:读取当前总线电平。
- 第7行:等待50μs,完成整个读位周期。

5.3 数据解码与错误识别

在数据传输过程中,由于时序误差、噪声干扰或硬件连接问题,可能会导致通信失败。因此,理解数据解码过程及常见错误识别方法至关重要。

5.3.1 数据位的采样与转换

数据位的采样必须在指定时间窗口内进行,否则可能导致误读。通常主设备在发送完读位信号后15~60μs之间采样最为可靠。

// 示例:读一字节数据
unsigned char OW_ReadByte(void)
{
    unsigned char i, byte = 0;
    for(i=0; i<8; i++)
    {
        byte >>= 1;            // 右移准备接收新位
        if(OW_ReadBit())       // 读取一位
            byte |= 0x80;      // 若为1,则设置最高位
    }
    return byte;
}

逐行解析
- 第1行:循环读取8位组成一个字节。
- 第2行:byte初始化为0。
- 第4行:每次读取前将byte右移一位,腾出最高位空间。
- 第5行:调用读位函数。
- 第6行:若返回值为1,则将最高位置1。

5.3.2 常见时序错误与应对策略

错误类型 原因分析 解决方案
复位失败 总线未正确释放或上拉电阻异常 检查电路连接与上拉电阻值
数据读取错误 采样时机不对或噪声干扰 优化延时函数,增加滤波电容
无法识别从设备 地址冲突或设备损坏 更换设备,检查ROM地址读取
通信速率不稳定 时钟频率误差 使用定时器替代软件延时
graph TD
    A[开始通信] --> B[发送复位脉冲]
    B --> C{是否存在从设备?}
    C -->|是| D[发送ROM命令]
    C -->|否| E[报错并重试]
    D --> F[发送功能命令]
    F --> G[读/写数据]
    G --> H{数据是否正确?}
    H -->|是| I[通信完成]
    H -->|否| J[记录错误并重试]

说明 :该流程图展示了1线通信的基本流程及错误处理逻辑。主设备在每次通信前都应进行复位和存在检测,以确保从设备已准备就绪。

通过本章的学习,开发者可以掌握1线协议中数据位的传输格式、编码方式及解码机制,理解如何通过精确控制时序来实现稳定通信,并具备识别和处理常见错误的能力。这些知识将为后续章节中握手机制的设计与调试提供坚实基础。

6. 握手与应答机制设计

握手与应答机制是1线通信协议中确保主设备与从设备之间可靠通信的核心机制之一。在嵌入式系统中,特别是在使用如DS18B20等1线设备时,握手和应答的正确实现直接决定了设备是否能够被正确识别并完成数据交互。本章将深入分析1线协议中的握手流程、应答机制的设计与实现,并探讨在多设备环境下的总线冲突与仲裁策略。

6.1 握手机制的流程分析

6.1.1 主设备与从设备的交互过程

在1线总线中,主设备通过发送复位脉冲(Reset Pulse)来启动通信流程。该复位脉冲是一个低电平持续至少480微秒的信号,随后释放总线进入高电平状态。此时,所有连接在总线上的从设备会检测到这一信号,并在复位信号释放后约15~60微秒内返回一个“存在脉冲”(Presence Pulse),表示设备已准备好进行通信。

下图展示了复位信号与存在脉冲的典型时序关系:

sequenceDiagram
    主设备->>总线: 拉低总线 480us
    总线->>从设备: 复位信号
    从设备-->>总线: 等待15~60us后拉低总线 60~240us
    总线->>主设备: 存在脉冲

通过这种方式,主设备可以检测到是否有从设备响应,并据此决定是否继续后续的通信流程。

6.1.2 存在信号的检测与响应

主设备在发出复位脉冲后,需在15~60微秒的时间窗口内检测总线是否被拉低。如果检测到低电平持续约60~240微秒,则说明至少有一个从设备存在并已响应。

在C51编程中,我们可以通过精确的延时函数和IO口读取操作来实现这一检测逻辑。例如:

bit OW_Reset(void) {
    bit presence;
    DQ = 0;              // 拉低总线,开始复位脉冲
    DelayMicroseconds(480); // 持续480us
    DQ = 1;              // 释放总线
    DelayMicroseconds(70);  // 等待从设备响应
    presence = !DQ;      // 读取是否存在脉冲(低电平表示存在)
    DelayMicroseconds(410); // 等待存在脉冲结束
    return presence;
}

逐行分析与参数说明:

  • 第2行 :定义返回值为bit类型,表示是否存在从设备。
  • 第3行 :将DQ引脚设置为低电平,开始复位信号。
  • 第4行 :调用延时函数 DelayMicroseconds(480) ,确保复位脉冲持续时间符合规范。
  • 第5行 :释放总线,设置DQ为高电平。
  • 第6行 :等待约70微秒后读取DQ引脚电平。如果DQ为低,则说明存在脉冲被检测到。
  • 第7行 :继续延时以确保存在脉冲结束,避免影响后续操作。

此函数是握手流程的核心部分,其准确性直接影响后续通信是否能顺利进行。

6.2 应答机制的实现

6.2.1 从设备的应答信号生成

在1线协议中,除了复位信号外,主设备在发送ROM命令(如Skip ROM、Match ROM等)或功能命令后,从设备需要根据命令内容做出相应的响应。例如,在发送“Read Scratchpad”命令后,从设备将开始发送温度数据。

这些响应通常以“应答位”(Acknowledge Bit)的形式表示。主设备在发送完一个字节后,会释放总线并读取一个bit作为应答信号。如果从设备在指定时间内拉低总线,则表示应答成功(ACK);否则表示无应答(NACK)。

6.2.2 主设备的应答检测逻辑

在C51代码中,我们可以编写如下函数来检测应答位:

bit OW_Ack(void) {
    bit ack;
    DQ = 0;              // 开始应答周期
    DelayMicroseconds(2); // 短暂低电平
    DQ = 1;              // 释放总线
    DelayMicroseconds(10); // 等待从设备响应
    ack = !DQ;           // 读取应答信号
    DelayMicroseconds(50); // 完成整个应答周期
    return ack;
}

逐行分析与参数说明:

  • 第2行 :返回bit类型,表示是否收到应答。
  • 第3行 :将DQ拉低,启动应答周期。
  • 第4行 :短延时,确保总线有效拉低。
  • 第5行 :释放总线,允许从设备响应。
  • 第6行 :读取DQ电平,若为低则表示从设备应答。
  • 第7行 :延时以完成整个应答周期,避免干扰后续操作。

通过此函数,主设备可以判断从设备是否正常响应,从而决定是否继续数据读写。

6.3 多设备总线冲突与仲裁

6.3.1 设备地址识别与匹配

1线总线支持多个设备挂载,但通信时只能一次与一个设备进行交互。因此,主设备需要通过ROM命令(如Match ROM)来选择目标设备。每个从设备都有唯一的64位ROM地址(如DS18B20的序列号),主设备通过发送该地址来匹配特定设备。

以下是一个发送ROM地址的函数示例:

void OW_WriteROM(unsigned char *rom) {
    unsigned char i;
    for(i=0; i<8; i++) {
        OW_WriteByte(rom[i]); // 逐字节发送ROM地址
    }
}

逐行分析与参数说明:

  • 第2行 :函数接收一个指向ROM地址数组的指针。
  • 第4行 :循环发送8个字节,构成完整的64位ROM地址。
  • 第5行 :调用写字节函数,逐字节传输。

6.3.2 总线竞争的处理策略

当多个设备同时响应主设备时,可能导致总线竞争(Bus Contention)。1线协议通过“搜索ROM”机制来解决这一问题。主设备通过发送Search ROM命令(F0h),逐位扫描总线上的所有设备,记录下每个设备的ROM地址,从而实现设备枚举。

下表展示了Search ROM流程中的关键状态位含义:

位位置 主设备发送 从设备响应 含义
0 00 01 位0为0,表示存在冲突
1 11 11 所有设备都为1,无需分支
2 00 10 位2为1,表示存在冲突

通过该机制,主设备可以逐步识别所有连接设备,并为后续通信选择目标设备。

代码流程整合示例

以下是一个完整的握手与应答流程的整合示例:

void OneWire_Init(void) {
    if(!OW_Reset()) {
        // 无设备存在
        printf("No device detected.\n");
        return;
    }
    if(!OW_Ack()) {
        // 命令未被应答
        printf("Device not responding.\n");
        return;
    }
    // 发送ROM地址选择设备
    OW_WriteROM(deviceROM);
    // 发送功能命令,如Read Scratchpad
    OW_WriteByte(0xBE);
}

逻辑分析:

  • 首先执行复位握手,确认设备存在;
  • 然后发送命令并检测应答;
  • 成功应答后发送ROM地址;
  • 最后发送功能命令开始数据交互。

该流程体现了1线协议中握手、应答、地址识别的完整交互过程,是嵌入式系统中实现多设备通信的基础。

本章内容详细讲解了1线协议中握手与应答机制的设计与实现方式,并结合C51编程示例,展示了如何在8051微控制器上实现这一流程。下一章将围绕完整的示例程序结构与调试方法展开,进一步提升读者的实战能力。

7. 示例程序结构与调试方法

在掌握了1线协议的基本原理、硬件配置、时序控制、数据传输机制及握手应答逻辑后,本章将通过一个完整的C51程序示例,帮助读者理解如何在实际工程中组织代码结构,并掌握有效的调试方法。我们将以常见的DS18B20温度传感器为例,展示如何编写主程序流程、初始化函数、数据读写函数,并介绍如何使用调试工具进行问题排查。

7.1 程序结构设计

7.1.1 主函数流程与初始化设置

主函数( main() )是程序的入口,通常负责初始化各功能模块并进入主循环。以下是一个典型的主函数结构:

#include <reg52.h>
#include "onewire.h"
#include "ds18b20.h"

void main(void) {
    float temperature;
    // 初始化1线总线
    OW_Init();
    while (1) {
        // 启动温度转换
        DS18B20_StartConversion();
        // 延时等待转换完成
        DelayMs(750);
        // 读取温度值
        temperature = DS18B20_ReadTemperature();
        // 通过串口打印温度(假设已初始化串口模块)
        printf("Temperature: %.2f C\n", temperature);
    }
}

7.1.2 各功能模块的划分与调用

为了提高代码可读性和维护性,通常将功能划分为多个模块:

模块名称 功能说明
onewire.h/c 提供1线协议的基本操作函数
ds18b20.h/c DS18B20专用的初始化、读写操作函数
delay.h/c 延时函数,包括毫秒与微秒级延时
uart.h/c 串口通信模块,用于调试信息输出

各模块之间通过函数接口调用,主函数通过调用高层API完成操作。

7.2 关键函数实现分析

7.2.1 初始化与复位函数

OW_Init() 函数负责复位1线总线并检测是否有从设备存在:

bit OW_Init(void) {
    bit presence;
    EA = 0;           // 关闭中断
    OW_DIR_OUT;       // 设置为输出
    OW_LOW;           // 拉低总线
    DelayUs(480);     // 保持至少480us
    OW_DIR_IN;        // 释放总线,变为输入
    DelayUs(70);      // 等待60~70us
    presence = OW_READ; // 读取是否存在脉冲
    DelayUs(410);     // 等待剩余时间
    EA = 1;           // 恢复中断
    return presence;  // 返回存在信号状态
}

参数说明:

  • OW_DIR_OUT/IN :控制GPIO方向。
  • DelayUs(x) :微秒级延时,用于满足协议要求的时序。
  • presence :若为0,表示有从设备存在;若为1,表示未检测到设备。

7.2.2 数据读写函数的实现细节

数据写入函数 OW_WriteByte()

void OW_WriteByte(unsigned char dat) {
    unsigned char i;
    for (i=0; i<8; i++) {
        OW_DIR_OUT;
        OW_LOW;
        DelayUs(2);       // 低电平至少1us
        if (dat & 0x01) { // 写1时释放总线
            OW_DIR_IN;
        }
        DelayUs(60);      // 保持60us
        OW_DIR_IN;        // 释放总线
        dat >>= 1;
    }
}

数据读取函数 OW_ReadByte()

unsigned char OW_ReadByte(void) {
    unsigned char i, dat = 0;
    for (i=0; i<8; i++) {
        OW_DIR_OUT;
        OW_LOW;
        DelayUs(2);
        OW_DIR_IN;
        DelayUs(10);        // 采样时间
        if (OW_READ) dat |= 0x80;
        DelayUs(50);
    }
    return dat;
}

这两个函数通过循环处理每个bit,严格按照1线协议的读写时序执行操作。

7.3 程序调试与问题排查

7.3.1 使用串口调试信息输出

串口输出是调试嵌入式程序最常用的方式。可以使用 printf() 函数(需重定向 putchar() )来输出调试信息:

void putchar(char c) {
    while (!TI);
    TI = 0;
    SBUF = c;
}

示例输出:

Temperature: 23.50 C
Temperature: 23.75 C

7.3.2 利用逻辑分析仪捕获总线信号

使用逻辑分析仪(如Saleae Logic Analyzer)可以帮助我们精确观察1线总线上的信号变化:

sequenceDiagram
    主机->>总线: 拉低480us
    总线->>主机: 从机拉低60~240us表示存在
    主机->>总线: 写ROM命令(0xCC)
    主机->>总线: 写温度转换命令(0x44)
    主机->>总线: 再次复位
    主机->>总线: 发送ROM命令(0xCC)
    主机->>总线: 发送读温度命令(0xBE)
    总线->>主机: 返回温度数据(9字节)

通过观察逻辑分析仪的波形,可以判断是否存在时序错误、数据位错误等问题。

7.4 常见问题与解决方案

7.4.1 无法检测到从设备

可能原因:

  • 上拉电阻未接或阻值不合适
  • 引脚方向配置错误
  • 时序不满足协议要求

解决方法:

  • 检查电路是否连接DS18B20,并确认上拉电阻为4.7kΩ
  • 确保 OW_DIR_IN/OUT 设置正确
  • 使用逻辑分析仪检查复位脉冲是否符合480μs要求

7.4.2 数据传输错误的排查与修复

可能原因:

  • 延时函数精度不足
  • 数据采样时机不对
  • 多设备地址冲突

解决方法:

  • 校准 DelayUs() 函数,确保延时时间准确
  • 检查读写函数中采样点是否正确
  • 若使用多个DS18B20,应使用ROM搜索命令获取唯一地址

提示 :若总线上有多个设备,务必实现ROM搜索算法,否则无法区分不同设备。

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

简介:1线通讯协议(单总线协议)是一种高效的数据通信方式,仅需一根数据线实现双向通信,广泛应用于嵌入式系统中。本文围绕在8051微控制器上使用C51编写的1线通讯协议示例展开,讲解如何在资源受限环境下实现该协议。内容涵盖引脚配置、时序控制、数据编码解码、握手机制、错误检测等关键技术点,并结合示例程序帮助开发者掌握1线协议的实现流程与调试技巧。


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

Logo

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

更多推荐