光栅项目总结

概述:自动化检测物体体积


(一)前置知识
Modbus 协议详解:概念、使用方法与实例应用
一、Modbus 是什么?

Modbus 是一种 串行通信协议,由 Modicon 公司于 1979 年开发,用于工业自动化系统中设备间的通信。它采用 主从架构(Master-Slave),支持多种物理层接口(如 RS-232、RS-485、以太网)。

核心特点
  1. 简单性:协议结构简单,易于实现。
  2. 开放性:公开协议规范,无专利限制。
  3. 广泛支持:超过 80% 的工业设备支持 Modbus。
  4. 灵活性:支持多种传输模式(RTU、ASCII、TCP)。

二、Modbus 的通信模式

Modbus 主要有三种传输模式:

  1. Modbus RTU(常用):

    • 二进制编码,数据紧凑,适用于串口通信(RS-485/RS-232)。
    • 典型应用:工厂传感器、PLC 控制。
    • 示例:用户代码中使用 COM5 连接的电机和光栅设备。
  2. Modbus ASCII

    • 数据以 ASCII 字符传输,可读性高,效率低于 RTU。
    • 适用于调试场景。
  3. Modbus TCP

    • 基于以太网传输,协议扩展了 TCP/IP 封装。
    • 适用于工业物联网和远程监控。

三、Modbus 协议的数据模型

Modbus 通过 寄存器 定义设备的数据存储单元,分为四种类型:

寄存器类型地址范围访问权限功能码
线圈(Coils)0x0000-0xFFFF读写0x01, 0x05, 0x0F
离散输入(Input Status)0x0000-0xFFFF只读0x02
保持寄存器(Holding Registers)0x0000-0xFFFF读写0x03, 0x06, 0x10
输入寄存器(Input Registers)0x0000-0xFFFF只读0x04
示例
  • 用户代码中光栅传感器使用 保持寄存器(地址 0x009F 读取遮挡光束数)。
  • 电机控制通过写 保持寄存器(地址 0x02 启动电机)。

四、Modbus 通信流程:主从架构
  1. 主站(Master):发起请求的设备(如用户的工控机)。
  2. 从站(Slave):响应请求的设备(如光栅传感器、电机控制器)。
请求-响应流程
  1. 主站发送一个包含 从站地址、功能码、数据 的请求帧。
  2. 从站校验地址匹配后执行操作,返回响应帧。
  3. 主站解析响应帧,确认操作结果。


五、Modbus RTU 的数据帧格式

一个典型的 Modbus RTU 帧结构如下:

字段长度说明
地址1 字节从站地址(1-247)
功能码1 字节指定操作类型(如读/写寄存器)
数据可变长度寄存器地址、数量或写入值
CRC 校验2 字节循环冗余校验码
示例:读取保持寄存器(功能码 0x03
  • 主站请求帧
    [从站地址] [0x03] [起始地址高字节] [起始地址低字节] [寄存器数量高字节] [寄存器数量低字节] [CRC]
  • 从站响应帧
    [从站地址] [0x03] [字节数] [数据1高字节] [数据1低字节] ... [CRC]

六、如何使用 Modbus?

以下步骤结合用户项目代码说明如何实现 Modbus 通信:

1. 硬件连接
  • RS-485/RS-232 接线:确保设备与主站正确连接,使用终端电阻(RS-485)。
  • 用户代码中的配置
    // 创建 Modbus RTU 上下文(COM5,9600 波特率)
    modbus_t* ctx = modbus_new_rtu("COM5", 9600, 'N', 8, 1);
2. 初始化通信参数
  • 波特率:与从站设备一致(如 9600、19200)。
  • 校验位:无校验('N')、奇校验('O')、偶校验('E')。
  • 超时设置:避免主站阻塞。
    modbus_set_response_timeout(ctx, 1, 0); // 1秒超时
3. 读写寄存器
  • 读取光栅传感器数据
    
    // 设置从站地址为1(光栅X方向)                     
    modbus_set_slave(ctx, 1);                                 
    uint16_t blocked_beam_count;                   
    modbus_read_registers(ctx, 0x009F, 1, &blocked_beam_count);
  • 控制电机启停
    // 设置从站地址为3(电机控制器)                     
    modbus_set_slave(ctx, 3);                     
    modbus_write_register(ctx, 0x02, 1); // 启动电机
4. 错误处理
  • 检查返回值:Modbus API 返回 -1 表示失败。
  • 获取错误信息
    if (modbus_read_registers(ctx, addr, 1, &value) == -1)                   
    {                                                        
        qDebug() << "错误:" << modbus_strerror(errno);                                        
    }
5. 多线程同步
  • 互斥锁保护:避免多个线程同时操作同一 Modbus 上下文。
    
    std::lock_guard<std::mutex> lock(mutex);                
    modbus_read_registers(ctx, addr, 1, &value);

七、用户代码中的实际应用
1. 光栅传感器数据采集
  • 从站地址切换
    SlaveDevice->SetSlave(2);                                 
    _SpectralDataFecherY->GetBlockedBeamCount();// Y方向光栅
  • 寄存器地址
    • 0x009F:遮挡光束数。
    • 0x00A0:起始端光束状态。
2. 电机控制
  • 启动电机:向地址 0x02 写 1
  • 方向设置:地址 0x01 写 0(逆时针)。

八、调试工具与最佳实践
常用工具
  1. Modbus Poll:模拟主站,测试寄存器读写。
  2. Wireshark:抓包分析 Modbus TCP 通信。
  3. 串口调试助手:查看 RTU 数据帧。
最佳实践
  1. 统一从站地址规划:避免地址冲突(如光栅X:1,光栅Y:2,电机:3)。
  2. 超时与重试机制:防止主站因无响应阻塞。
  3. 数据校验:使用 CRC 确保传输完整性。
  4. 日志记录:记录关键操作和错误信息。

九、常见问题与解决
  1. 无响应

    • 检查物理连接(线缆、终端电阻)。
    • 确认从站地址和波特率匹配。
  2. CRC 校验错误

    • 检查数据帧格式是否符合设备要求。
    • 在代码中启用 modbus_set_debug(ctx, TRUE) 打印原始数据。
  3. 寄存器写失败

    • 确认寄存器是否可写(例如输入寄存器只读)。
    • 检查权限(某些设备需解锁才能写入)。

十、总结

Modbus 是实现工业设备通信的基石。通过理解其协议结构、寄存器模型和错误处理机制,开发者可以高效集成传感器、控制器等设备。用户项目中的代码展示了如何通过 libmodbus 库 实现多从站管理、数据采集与设备控制,典型应用包括:

  • 光栅遮挡检测
  • 电机启停与方向控制

通过结合调试工具和最佳实践,可大幅提升 Modbus 通信的稳定性和可靠性。

一、libmodbus 概述

libmodbus 是一个用 C 语言编写的开源库,支持 Modbus RTU(串行通信)和 Modbus TCP(以太网通信)协议。
📦 核心功能

  • 提供简洁的 API,实现 Modbus 主站(Master)和从站(Slave)通信。
  • 跨平台支持(Linux、Windows、macOS)。
  • 支持同步请求/响应模型。

二、核心概念与数据结构

1. modbus_t 结构体
  • 作用:表示一个 Modbus 通信上下文,保存串口或网络参数、超时设置等。
  • 用户代码中的封装
    
    // 用户代码中的 ModbusSlave 类封装了                               
    modbus_t class ModbusSlave { private: modbus_t* ctx; // 通过 GetCtx() 访问 };
2. 从站地址(Slave Address)
  • 作用:Modbus 网络中每个设备有唯一地址(1-247)。主站通过该地址选择通信目标。
  • 用户代码示例
    // 切换至电机从站(地址3) 
    SlaveDevice->SetSlave(3); 
    Motor->Start();

三、常用 API 详解

1. 创建上下文
  • RTU 模式(串口通信):
    modbus_t* ctx = modbus_new_rtu("/dev/ttyS0", 9600, 'N', 8, 1);
    • 参数:设备路径(如 COM5)、波特率、校验位、数据位、停止位。
  • TCP 模式
    modbus_t* ctx = modbus_new_tcp("192.168.1.100", 502);
2. 连接管理
int modbus_connect(modbus_t* ctx); // 建立连接 
void modbus_close(modbus_t* ctx); // 关闭连接
3. 设置从站地址
int modbus_set_slave(modbus_t* ctx, int slave);
  • 用户代码示例
    // 在 SpectralDataFecher 中切换光栅设备地址                          
    int SpectralDataFecher::SetSlave(int slave)                          
    { 
        return modbus_set_slave(SlaveDevice->GetCtx(), slave); 
    }
4. **读写寄存器
  • 读取保持寄存器
    int modbus_read_registers(modbus_t* ctx, int addr, int nb, uint16_t* dest);
    • addr:寄存器起始地址。
    • nb:读取数量。
    • dest:存储结果的数组。
  • 写入单个寄存器
    int modbus_write_register(modbus_t* ctx, int addr, uint16_t value);
5. **错误处理
  • 获取错误信息
    const char* modbus_strerror(int errnum);
  • 用户代码中的错误处理示例
    if (SlaveDevice->ReadRegisters(addr, 1, &value) == -1)                  
    { 
        qDebug() << "错误:" << modbus_strerror(errno); 
        return -1; 
    }

四、用户代码中的关键实现

1. Modbus 从站管理(ModbusSlave 类)
  • 职责:封装 modbus_t 上下文,提供连接、读写接口。
  • 代码亮点
    // modbus-slave.cpp 
    bool ModbusSlave::Connect(const char* device, int baud, ...) 
    { 
        ctx = modbus_new_rtu(device, baud, parity, data_bit, stop_bit);            
        if (modbus_connect(ctx) == -1)                                   
        {                                                         
            modbus_free(ctx); return false; // 连接失败释放资源                 
        }                                                    
        return true; 
    }
2. 光栅数据读取(SpectralDataFecher 类)
  • 功能:解析光栅传感器数据(遮挡光束数、两端状态)。
  • 关键逻辑
    uint16_t SpectralDataFecher::GetBlockedBeamCount()                     
    { 
        uint16_t value;                                         
        if (GetRegisterValue(FUNCTION::BLOCKED_BEAM_COUNT, value) == -1) return 0;       
        return value; 
    }
3. 电机控制(MotorCtrl 类)
  • 功能:通过写寄存器控制电机启停、方向、归位。
  • 代码逻辑
    void MotorCtrl::Start() 
    { 
        while (true)                             
        { 
            if (mbs->WriteRegister(0x02, 1) == 1) break; // 持续重试直至成功 
            std::this_thread::sleep_for(100ms); 
        } 
    }

五、线程安全与性能优化

1. 线程安全
  • 问题:libmodbus 非线程安全,多线程操作同一上下文需同步。
  • 用户解决方案
    // EdgeDetection 中使用 std::mutex 保护 Modbus 操作              
    std::lock_guard<std::mutex> lock(mutex); 
    SlaveDevice->SetSlave(3); 
    Motor->Start();
2. 通信延迟优化
  • 代码中的延迟
    std::this_thread::sleep_for(50ms); // 防止频繁请求
  • 原因:避免 Modbus 设备响应不过来或缓冲区溢出。

六、常见问题与调试

1. 连接失败
  • 可能原因:错误的串口参数、物理连接问题。
  • 用户代码处理
    CPP
    
    if (SlaveDevice->Connect() == -1) 
    {                                
        qDebug() << "连接异常,请检查设备!"; 
        exit(1); 
    }
2. 寄存器读写错误
  • 调试方法:使用 modbus_strerror(errno) 输出错误信息。
    if (modbus_read_registers(ctx, addr, 1, &value) == -1)                   
    { 
        qDebug() << "读取失败:" << modbus_strerror(errno); 
    }

七、总结

libmodbus 在用户项目中的角色是实现与 Modbus 从站设备(光栅传感器、电机控制器)的通信。通过封装 modbus_t 上下文、切换从站地址、读写寄存器数据,完成了以下功能:

  1. 光栅检测:实时读取光束遮挡状态,计算物体尺寸。
  2. 电机控制:根据检测结果启停电机,实现自动化测量。
  3. 错误恢复:通过重试机制提高通信可靠性。

最佳实践建议

  • 增加日志记录,便于追踪通信问题。
  • 使用异步通信模型(如 modbus_set_response_timeout)优化性能。
  • 定期检测连接状态,实现自动重连。

(二)项目流程速览

光栅项目运行流程说明

  1. 用户启动检测

    • 操作:点击界面上的 Start 按钮。
    • 触发EdgeDetection 类的 start() 方法设置 isStart = true,通知电机控制线程开始检测条件。
  2. 光栅传感器监测物体状态

    • X/Y方向检测
      • 线程GratingSensorThread 每秒约执行100次检测(通过休眠10ms控制频率)。
      • 步骤
        1. 切换Modbus从站地址至光栅设备(X: 地址1,Y: 地址2)。
        2. 检查光栅两端是否未被遮挡(properPlaceX/Y),确保物体放置正确。
        3. 统计中间被遮挡的光束数量(BlockedBeamX/Y),计算实际尺寸(如 2.4857mm/beam)。
      • 条件判断:若检测到物体(hasLightBlockingX/Y = true)且位置正确,标记为可启动电机。
  3. 电机启动与旋转

    • 条件满足:当 hasLightBlockingXhasLightBlockingYproperPlaceXproperPlaceY 均为 true 时:
      1. 锁保护:获取互斥锁,防止Modbus通信冲突。
      2. 发送指令:切换至电机从站(地址3),发送启动命令(写寄存器0x02为1)。
      3. 持续旋转:电机按预设方向(逆时针)转动,直到物体移出。
  4. 停止电机与复位

    • 触发条件:光栅检测到物体完全移出(hasLightBlockingX/Y = false)。
    • 动作
      1. 停止电机:发送停止命令(写寄存器0x03为1)。
      2. 返回原点:设置原点寄存器(0x0A),电机反向旋转至初始位置。
      3. 重置标志isStart = false,结束本次检测周期。
  5. 用户获取角度数据

    • 操作:点击 Get 按钮。
    • 执行:读取电机角度寄存器(0x16),返回原始计数值(后续可能需转换为实际角度)。

流程示意图

PLAINTEXT

用户操作
  │
  ▼
[Start按钮] → EdgeDetection.start() → isStart = true
  │
  ▼
光栅检测线程(GratingSensorThread)
  ├─ X方向检测(从站1)
  │   ├─ 是否遮挡两端? → 更新 properPlaceX
  │   └─ 统计遮挡光束数 → BlockedBeamX
  │
  └─ Y方向检测(从站2)
      ├─ 是否遮挡两端? → 更新 properPlaceY
      └─ 统计遮挡光束数 → BlockedBeamY
  │
  ▼
电机控制线程(MotorThread)
  ├─ 条件满足?
  │   ├─ 是 → 启动电机(从站3)
  │   └─ 否 → 等待...
  │
  ▼
物体移出 → 停止电机 → 回原点 → isStart = false
  │
  ▼
[Get按钮] → 读取电机角度 → 显示/日志
 

关键设计点

  1. 多线程协作

    • 光栅检测线程:高频读取传感器数据,确保实时性。
    • 电机控制线程:独立判断启动条件,避免阻塞界面响应。
    • 互斥锁mutex):确保Modbus通信的原子性,防止多线程同时操作串口。
  2. Modbus多从站管理

    • 地址切换:通过 SetSlave() 动态切换设备(光栅X:1,光栅Y:2,电机:3)。
    • 错误处理:通信失败时重试(如电机启动命令需循环发送直至成功)。
  3. 状态机逻辑

    • 启动条件:需同时满足物体存在(遮挡光束)且位置正确(两端无遮挡)。
    • 停止条件:物体完全离开光栅区域,触发复位流程。

典型应用场景

  1. 物体尺寸测量

    • 将物体放置在光栅区域,系统自动计算X/Y方向的遮挡长度(如 BlockedBeamX * 2.4857mm)。
  2. 自动化控制

    • 在生产线中,当物体到达检测位置时启动电机(如传送带),移出后停止并复位,实现循环作业。

(三)具体实现细节

一、主界面模块(MainWidget)

实现文件main-widget.h/main-widget.cpp

1. 核心功能
  • 提供用户操作界面,包含两个按钮:
    • Start按钮:触发边缘检测流程。
    • Get按钮:获取电机旋转角度。
2. 关键代码分析
// main-widget.cpp 中按钮点击事件
void MainWidget::on_pushButton_clicked() 
{
    edgeDetect.start(); // 启动检测流程
}

void MainWidget::on_pushButton_2_clicked() 
{
    qDebug() << "角度:" << edgeDetect.GetMotorAngle(); // 获取电机角度
}
  • edgeDetect 对象EdgeDetection类实例,负责核心逻辑。
  • start() 方法:设置标志位,通知电机线程开始检测。

二、边缘检测核心模块(EdgeDetection)

实现文件edgedetection.h/edgedetection.cpp

1. 初始化阶段
EdgeDetection::EdgeDetection() 
{
    // 初始化Modbus从站(COM5, 9600波特率)
    SlaveDevice = new ModbusSlave("COM5", 9600, 'N', 8, 1);
    if (SlaveDevice->Connect() == -1) exit(1); // 连接失败终止程序

    // 初始化光栅数据获取器(X/Y方向)
    _SpectralDataFecherX = new SpectralDataFecher(SlaveDevice);
    _SpectralDataFecherY = new SpectralDataFecher(SlaveDevice);

    // 初始化电机控制器(从站地址3)
    SlaveDevice->SetSlave(3);
    Motor = new MotorCtrl(SlaveDevice);
    Motor->SetValue(MotorCtrl::ANGLE, 0); // 持续旋转
    Motor->SetValue(MotorCtrl::DIRECTION, 0); // 逆时针

    // 启动光栅检测线程和电机控制线程
    GratingSensorThread();
    MotorThread();
}
  • Modbus多从站管理:通过切换从站地址(1/2/3)区分光栅X/Y和电机设备。
  • 电机初始配置:设置为逆时针无限旋转,等待触发条件。
2. 光栅检测线程(GratingSensorThread)
void EdgeDetection::GratingSensorThread() 
{
    SpectralThread = std::thread([this] {
        while (1) {
            std::lock_guard<std::mutex> lock(mutex);
            // 读取Y方向光栅(从站2)
            SlaveDevice->SetSlave(2);
            properPlaceY = !_SpectralDataFecherY->GetEndBeamState() 
                          && !_SpectralDataFecherY->GetBeginBeamState();
            _DeviceData.BlockedBeamY = _SpectralDataFecherY->GetBlockedBeamCount();

            // 读取X方向光栅(从站1)
            _SpectralDataFecherX->SetSlave(1);
            properPlaceX = !_SpectralDataFecherX->GetEndBeamState()
                          && !_SpectralDataFecherX->GetBeginBeamState();
            _DeviceData.BlockedBeamX = _SpectralDataFecherX->GetBlockedBeamCount();

            std::this_thread::sleep_for(10ms); // 降低CPU占用
        }
    });
}
  • 双光栅检测
    • properPlaceX/Y:检查物体是否未遮挡光栅两端(正确放置)。
    • BlockedBeamX/Y:计算被遮挡的光束数,通过SCALE_PER_CELL转换为实际尺寸。
  • 线程安全:使用std::mutex确保Modbus通信的独占访问。
3. 电机控制线程(MotorThread)
void EdgeDetection::MotorThread() {
    _MotorThread = std::thread([this]() {
        while (true) {
            if (isStart) {
                // 条件满足:物体存在且放置正确
                if (hasLightBlockingX && hasLightBlockingY && properPlaceX && properPlaceY) 
                {
                    std::lock_guard<std::mutex> lock(mutex);
                    SlaveDevice->SetSlave(3); // 切换至电机从站
                    if (!Motor->IsRuning()) 
                    {
                        Motor->Start(); // 启动电机
                    }
                }

                // 物体移出:停止电机并返原点
                if (!hasLightBlockingX && !hasLightBlockingY) {
                    SlaveDevice->SetSlave(3);
                    Motor->Stop();
                    Motor->SetValue(MotorCtrl::ORIGIN, 0); // 返回原点
                    isStart = false; // 重置启动标志
                }
            }
        }
    });
}
  • 条件触发
    • 启动电机:当X/Y光栅均检测到物体且位置正确。
    • 停止电机:物体离开后停止,并触发返回原点操作。
  • 原点复位:通过设置ORIGIN寄存器,电机反向旋转至初始位置。

三、Modbus通信模块(ModbusSlave)

实现文件modbus-slave.h/modbus-slave.cpp

1. 核心功能
  • 封装libmodbus库,提供简化API。
  • 支持多从站切换(通过SetSlave())。
2. 关键实现
// modbus-slave.cpp 中连接初始化
bool ModbusSlave::Connect(const char *device, int baud, ...) 
{
    d->ctx = modbus_new_rtu(device, baud, parity, data_bit, stop_bit);
    if (modbus_connect(d->ctx) == -1) 
    {
        modbus_free(d->ctx);
        return false; // 连接失败处理
    }
    return true;
}
  • 串口参数:配置波特率、校验位、数据位等,匹配硬件设备设置。
  • 错误处理:连接失败时释放资源,避免内存泄漏。

四、光栅数据处理模块(SpectralDataFecher)

实现文件spectral-data-fecher.h/spectral-data-fecher.cpp

1. 数据读取逻辑
uint16_t SpectralDataFecher::GetBlockedBeamCount() {
    uint16_t value;
    if (GetRegisterValue(FUNCTION::BLOCKED_BEAM_COUNT, value) == -1) return 0;
    return value; // 直接返回遮挡光束数
}

int SpectralDataFecher::GetEndBeamState() {
    uint16_t value;
    GetRegisterValue(FUNCTION::END_BEAM_STATE, value);
    return (value & 0x8000) ? 1 : 0; // 取最高位判断末尾光束状态
}
  • 寄存器地址
    • 0x009F:遮挡光束数量。
    • 0x00A3:末端光束状态(二进制最高位)。

五、电机控制模块(MotorCtrl)

实现文件motor-ctrl.h/motor-ctrl.cpp

1. 电机命令发送
void MotorCtrl::Start() 
{
    while (true) 
    {
        if (mbs->WriteRegister(0x02, 1) == 1) 
        { // 写入启动寄存器
            isRuning = true;
            break;
        }
        std::this_thread::sleep_for(100ms); // 失败后重试
    }
}
  • 寄存器映射
    • 0x02:启动/停止(1启动,0停止)。
    • 0x01:方向(0逆时针,1顺时针)。
    • 0x0A:返回原点命令。
2. 角度计算
uint16_t MotorCtrl::GetAngle() 
{
    uint16_t angle;
    mbs->ReadRegisters(0x16, 1, &angle); // 读取角度寄存器
    return angle;
}
  • 值转换:代码中angle / 440.740可能是将原始计数值转换为实际角度(需根据电机参数验证)。

六、模块交互流程



七、潜在优化点

  1. 线程管理:使用QThread替代std::thread,便于与Qt事件循环集成。
  2. 错误恢复:增加Modbus通信失败后的自动重连机制。
  3. 性能优化:调整光栅检测线程的休眠时间,平衡响应速度与CPU占用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值