找回密码
 立即注册
楼主: manzunzu

求助关于DS18B20和modbus rtu从站

[复制链接]

865

主题

1万

回帖

2万

积分

管理员

积分
25122
发表于 2026-7-13 22:20:36 | 显示全部楼层
截图202607132220331711.jpg
回复

使用道具 举报 送花

865

主题

1万

回帖

2万

积分

管理员

积分
25122
发表于 2026-7-13 22:21:14 | 显示全部楼层

好的,我已经完整阅读了该帖子的全部内容。下面我来为你做一个全面的分析和解答。


问题分析:DS18B20 与 Modbus RTU 从站的冲突

你遇到的问题非常典型,核心矛盾在于:

冲突方 特点
DS18B20 单总线通信,时序要求极其严格(微秒级),如复位脉冲 480μs、读时隙 60μs 等,期间不能被中断打断
Modbus RTU 串口中断随时可能触发(尤其是接收中断),打断正在进行的 DS18B20 时序

根本原因:DS18B20 的读写函数中通常使用 _nop_()delay_us() 做精确延时,如果串口中断在延时中途触发,会导致时序偏移,DS18B20 通信失败。


解决方案(推荐 3 种,按推荐程度排序)

方案一:状态机 + 分步执行(最推荐)

将 DS18B20 的完整读写流程拆解为多个短步骤,每个步骤在主循环中执行一次,每次执行时间控制在 200μs 以内,这样串口中断即使触发,也只是打断当前这个微小的步骤,不会破坏整个时序。

DS18B20 状态机流程拆解:

状态 操作 说明 最大耗时
0 发复位脉冲(拉低 480μs) 启动通信 480μs
1 释放总线并等待存在脉冲 检测 DS18B20 应答 60μs
2 发跳过 ROM 指令 (0xCC) 单点无需匹配 ROM ~100μs
3 发启动转换指令 (0x44) 启动温度转换 ~100μs
4 等待转换完成(约 750ms) 此状态可退出,下次再查 750ms(可轮询)
5 发复位脉冲 重新初始化 480μs
6 发跳过 ROM 指令 (0xCC) ~100μs
7 发读暂存器指令 (0xBE) 准备读取温度值 ~100μs
8~15 逐字节读取温度数据(9字节) 每字节约 100μs 共 ~900μs
16 校验 CRC,更新温度值 完成

关键技巧:状态 4(等待转换完成)可以退出状态机,下次主循环再进入时检查是否已到 750ms,避免长时间阻塞。

方案二:读取 DS18B20 时临时关闭串口中断(简单粗暴)

EA = 0;                     // 关总中断
read_temp_from_ds18b20();   // 执行完整的 DS18B20 读取(约 5ms)
EA = 1;                     // 开总中断

缺点:关中断约 5ms,如果此时 Modbus 主机发送数据帧,可能会丢失帧头,导致整帧解析失败。不推荐用于 Modbus 实时性要求高的场景。

方案三:串口中断只收不发,主循环处理(论坛用户推荐)

串口中断中只做接收缓冲(将收到的字节放入 FIFO 缓冲区),不做任何协议解析。Modbus 协议解析和 DS18B20 读取都在主循环中分时执行。

// 串口中断服务函数
void UART1_ISR(void) interrupt 4
{
    if(RI) {
        RI = 0;
        rx_buffer[rx_head++] = SBUF;  // 仅存入缓冲区
    }
}

// 主循环
void main(void)
{
    while(1) {
        // 1. 处理 Modbus 协议(从缓冲区取数据解析)
        process_modbus();
      
        // 2. 执行 DS18B20 状态机的一个步骤
        ds18b20_state_machine();
      
        // 3. 其他任务
    }
}

完整示例代码(状态机方案,基于 STC8H 系列)

/******************************************************************************
 * DS18B20 + Modbus RTU 从站 状态机方案
 * 芯片: STC8H系列 (如 STC8H8K64U)
 * 编译: Keil C51
 * 
 * 说明:
 *   1. DS18B20 读取采用状态机分步执行,每次主循环执行一个步骤
 *   2. 串口中断仅做接收缓冲,Modbus 解析在主循环中完成
 *   3. 温度值存储在全局变量中,Modbus 读取时直接返回
 ******************************************************************************/

#include "STC8H.h"
#include <string.h>

#define MAIN_Fosc        11059200L   // 主时钟频率 11.0592MHz
#define DS18B20_PIN      P10         // DS18B20 数据线接 P1.0

// DS18B20 状态机状态定义
#define DS_STATE_IDLE            0   // 空闲
#define DS_STATE_RESET1          1   // 发复位脉冲
#define DS_STATE_WAIT_PRESENCE   2   // 等待存在脉冲
#define DS_STATE_SKIP_ROM1       3   // 发跳过ROM
#define DS_STATE_CONVERT_T       4   // 发启动转换
#define DS_STATE_WAIT_CONVERT    5   // 等待转换完成
#define DS_STATE_RESET2          6   // 第二次复位
#define DS_STATE_WAIT_PRESENCE2  7   // 等待存在脉冲
#define DS_STATE_SKIP_ROM2       8   // 发跳过ROM
#define DS_STATE_READ_SCRATCHPAD 9   // 发读暂存器
#define DS_STATE_READ_BYTE0      10  // 读字节0 (温度LSB)
#define DS_STATE_READ_BYTE1      11  // 读字节1 (温度MSB)
#define DS_STATE_READ_DONE       12  // 读取完成

// 全局变量
unsigned char ds_state = DS_STATE_IDLE;
unsigned int  ds_timer = 0;          // 状态机计时
unsigned char ds_byte_count = 0;     // 已读取字节数
unsigned char ds_data[9];            // 暂存器数据
bit ds_busy = 0;                     // DS18B20 忙标志
int  temperature = 0;                // 温度值 (℃*10, 如 25.5℃ = 255)

// Modbus 相关
unsigned char modbus_rx_buf[64];
unsigned char modbus_rx_len = 0;
unsigned char modbus_rx_index = 0;
bit modbus_frame_ready = 0;

// 微秒级延时函数 (11.0592MHz)
void delay_us(unsigned int us)
{
    unsigned int i;
    for(i = 0; i < us; i++) {
        _nop_(); _nop_(); _nop_(); _nop_();
        _nop_(); _nop_(); _nop_(); _nop_();
        _nop_(); _nop_(); _nop_(); _nop_();
    }
}

// DS18B20 单总线操作函数 (原子操作,每个都很短)
void ds_write_byte(unsigned char dat)
{
    unsigned char i;
    for(i = 0; i < 8; i++) {
        DS18B20_PIN = 0;
        _nop_(); _nop_();
        if(dat & 0x01) DS18B20_PIN = 1;
        delay_us(60);
        DS18B20_PIN = 1;
        dat >>= 1;
    }
}

unsigned char ds_read_byte(void)
{
    unsigned char i, dat = 0;
    for(i = 0; i < 8; i++) {
        dat >>= 1;
        DS18B20_PIN = 0;
        _nop_(); _nop_();
        DS18B20_PIN = 1;
        _nop_(); _nop_();
        if(DS18B20_PIN) dat |= 0x80;
        delay_us(60);
    }
    return dat;
}

bit ds_reset(void)
{
    bit presence;
    DS18B20_PIN = 0;
    delay_us(480);
    DS18B20_PIN = 1;
    delay_us(60);
    presence = DS18B20_PIN;
    delay_us(420);
    return presence;  // 0=存在, 1=不存在
}

// DS18B20 状态机 - 每次主循环调用一次
void ds18b20_state_machine(void)
{
    if(!ds_busy) return;  // 未启动则跳过
  
    switch(ds_state) {
        case DS_STATE_RESET1:
            if(ds_reset() == 0) ds_state = DS_STATE_WAIT_PRESENCE;
            else ds_state = DS_STATE_IDLE;  // 不存在,复位
            break;
          
        case DS_STATE_WAIT_PRESENCE:
            ds_write_byte(0xCC);            // 跳过ROM
            ds_state = DS_STATE_SKIP_ROM1;
            break;
          
        case DS_STATE_SKIP_ROM1:
            ds_write_byte(0x44);            // 启动温度转换
            ds_state = DS_STATE_CONVERT_T;
            break;
          
        case DS_STATE_CONVERT_T:
            ds_timer = 0;
            ds_state = DS_STATE_WAIT_CONVERT;
            break;
          
        case DS_STATE_WAIT_CONVERT:
            ds_timer++;
            if(ds_timer >= 750) {           // 等待约750ms (主循环频率需估算)
                ds_state = DS_STATE_RESET2;
            }
            break;
          
        case DS_STATE_RESET2:
            if(ds_reset() == 0) ds_state = DS_STATE_WAIT_PRESENCE2;
            else ds_state = DS_STATE_IDLE;
            break;
          
        case DS_STATE_WAIT_PRESENCE2:
            ds_write_byte(0xCC);            // 跳过ROM
            ds_state = DS_STATE_SKIP_ROM2;
            break;
          
        case DS_STATE_SKIP_ROM2:
            ds_write_byte(0xBE);            // 读暂存器
            ds_byte_count = 0;
            ds_state = DS_STATE_READ_SCRATCHPAD;
            break;
          
        case DS_STATE_READ_SCRATCHPAD:
            ds_data[ds_byte_count] = ds_read_byte();
            ds_byte_count++;
            if(ds_byte_count >= 9) {
                ds_state = DS_STATE_READ_DONE;
            }
            break;
          
        case DS_STATE_READ_DONE:
            // 计算温度值 (12位分辨率)
            temperature = (int)(ds_data[1] << 8 | ds_data[0]);
            temperature = temperature * 10 / 16;  // 转换为 0.1℃ 单位
            ds_busy = 0;
            ds_state = DS_STATE_IDLE;
            break;
          
        default:
            ds_state = DS_STATE_IDLE;
            break;
    }
}

// 启动一次 DS18B20 温度读取
void ds_start_convert(void)
{
    if(!ds_busy) {
        ds_busy = 1;
        ds_state = DS_STATE_RESET1;
    }
}

// 串口1初始化 (9600bps, 8N1)
void UART1_Init(void)
{
    SCON = 0x50;        // 模式1, 8位UART, 允许接收
    TMOD &= 0x0F;
    TMOD |= 0x20;       // 定时器1, 模式2 (8位自动重装)
    TH1 = 0xFD;         // 9600bps @ 11.0592MHz
    TL1 = 0xFD;
    TR1 = 1;
    ES = 1;             // 使能串口中断
    EA = 1;
}

// 串口1中断服务函数 - 仅做接收缓冲
void UART1_ISR(void) interrupt 4
{
    if(RI) {
        RI = 0;
        if(modbus_rx_len < 64) {
            modbus_rx_buf[modbus_rx_len++] = SBUF;
        }
    }
    if(TI) {
        TI = 0;
    }
}

// 发送一字节
void UART1_SendByte(unsigned char dat)
{
    SBUF = dat;
    while(!TI);
    TI = 0;
}

// 发送缓冲区
void UART1_SendBuffer(unsigned char *buf, unsigned char len)
{
    unsigned char i;
    for(i = 0; i < len; i++) {
        UART1_SendByte(buf[i]);
    }
}

// 简单 Modbus RTU 03 功能码处理 (读保持寄存器)
void modbus_process(void)
{
    unsigned char i, crc_lo, crc_hi;
    unsigned int addr, count;
    unsigned char resp[64];
    unsigned char resp_len;
  
    if(!modbus_frame_ready) return;
    modbus_frame_ready = 0;
  
    // 检查地址 (假设本机地址为 0x01)
    if(modbus_rx_buf[0] != 0x01) return;
  
    // 检查 CRC (简化处理,实际应计算)
    // ...
  
    // 处理功能码
    switch(modbus_rx_buf[1]) {
        case 0x03:  // 读保持寄存器
            addr = (modbus_rx_buf[2] << 8) | modbus_rx_buf[3];
            count = (modbus_rx_buf[4] << 8) | modbus_rx_buf[5];
          
            resp[0] = 0x01;             // 从站地址
            resp[1] = 0x03;             // 功能码
            resp[2] = count * 2;        // 字节数
          
            for(i = 0; i < count; i++) {
                if(addr + i == 0) {     // 寄存器0: 温度值
                    resp[3 + i*2] = (unsigned char)(temperature >> 8);
                    resp[4 + i*2] = (unsigned char)(temperature & 0xFF);
                } else {
                    resp[3 + i*2] = 0;
                    resp[4 + i*2] = 0;
                }
            }
          
            resp_len = 3 + count * 2;
            // 添加 CRC (此处省略,实际需计算)
            // resp[resp_len] = crc_lo;
            // resp[resp_len+1] = crc_hi;
            // resp_len += 2;
          
            UART1_SendBuffer(resp, resp_len);
            break;
          
        default:
            break;
    }
  
    modbus_rx_len = 0;
}

void main(void)
{
    unsigned char main_loop_cnt = 0;
  
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;  // DS18B20 引脚设为开漏或准双向
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
  
    UART1_Init();
  
    // 首次启动温度读取
    ds_start_convert();
  
    while(1) {
        // 1. 检查 Modbus 帧是否接收完成 (3.5字符空闲判断)
        // 此处简化处理,实际需用定时器做超时判断
        if(modbus_rx_len > 0) {
            // 假设收到完整帧
            modbus_frame_ready = 1;
        }
      
        // 2. 处理 Modbus 协议
        modbus_process();
      
        // 3. 执行 DS18B20 状态机
        ds18b20_state_machine();
      
        // 4. 每 2 秒启动一次温度转换
        main_loop_cnt++;
        if(main_loop_cnt >= 200) {  // 假设主循环约 10ms 一次
            main_loop_cnt = 0;
            if(!ds_busy) {
                ds_start_convert();
            }
        }
    }
}

关键要点总结

  1. 状态机是核心:将 DS18B20 的完整读写流程拆成多个微步骤,每个步骤执行时间极短(<200μs),串口中断即使触发也只打断当前小步骤,不会破坏整体时序。
  2. 串口中断只收不处理:中断中仅将收到的字节存入缓冲区,Modbus 协议解析放在主循环中,避免在中断中做耗时操作。
  3. 温度值共享:DS18B20 读取完成后将温度值存入全局变量 temperature,Modbus 03 功能码直接读取该变量,两者互不干扰。
  4. 如需更精确的时序控制:可以在执行 DS18B20 关键步骤(如复位脉冲的 480μs 延时)时,使用定时器硬件计时而非软件延时,这样即使中断触发也不影响计时精度。

如果你需要我针对具体的 STC 型号(如 STC8H8K64U、STC32G 等)提供更精确的代码,或者需要完整的 Modbus CRC 计算函数,请告诉我具体的芯片型号,我可以进一步优化。


🔍 搜索 "无法访问链接,请提供需要提取关键词的文本内容。"

⏱️ 查找用时 4.3s | 总回答用时 37.8s

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-9-23 16:05 , Processed in 0.063744 second(s), 44 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表