You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PCF8575配置修改后16通道继电器工作异常求助

问题描述

有一台连接LCR用于测量交流电感的16通道继电器,修改PCF8575配置后未备份,恢复配置后继电器无法正常工作:原本应循环所有16通道且始终保持1路接地激活,但现在仅运行4-5个通道就停止。尝试过将new_state = -16改为new_state = 0xFFFF,但不确定是否正确,需排查修复。

提供的PCF8575配置代码

import smbus

class IOPort(list):
    """
    Represents the PCF8575 IO port as a list of boolean values.
    """
    def __init__(self, pcf8575, *args, **kwargs):
        super(IOPort, self).__init__(*args, **kwargs)
        self.pcf8575 = pcf8575

    def __setitem__(self, key, value):
        """
        Set an individual output pin.
        """
        self.pcf8575.set_output(key, value)

    def __getitem__(self, key):
        """
        Get an individual pin state.
        """
        return self.pcf8575.get_pin_state(key)

    def __repr__(self):
        """
        Represent port as a list of booleans.
        """
        state = self.pcf8575.bus.read_word_data(self.pcf8575.address, 0)
        ret = []
        for i in range(16):
            ret.append(bool(state & 1<<15-i))
        return repr(ret)

    def __len__(self):
        return 16

    def __iter__(self):
        for i in range(16):
            yield self[i]

    def __reversed__(self):
        for i in range(16):
            yield self[15-i]


class PCF8575(object):
    """
    A software representation of a single PCF8575 IO expander chip.
    """
    def __init__(self, i2c_bus_no, address):
        self.bus_no = i2c_bus_no
        self.bus = smbus.SMBus(i2c_bus_no)
        self.address = address

    def __repr__(self):
        return "PCF8575(i2c_bus_no=%r, address=0x%02x)" % (self.bus_no, self.address)

    @property
    def port(self):
        """
        Represent IO port as a list of boolean values.
        """
        return IOPort(self)

    @port.setter
    def port(self, value):
        """
        Set the whole port using a list.
        """
        assert isinstance(value, list)
        assert len(value) == 16
        new_state = 0xFFFF
        for i, val in enumerate(value):
            if val:
                new_state |= 1 << 15-i
        self.bus.write_byte_data(self.address, new_state & 0xff, (new_state >> 8) & 0xff)

    def set_output(self, output_number, value):
        """
        Set a specific output high (True) or low (False).
        """
        assert output_number in range(16), "Output number must be an integer between 0 and 15"
        current_state = self.bus.read_word_data(self.address, 0)
        bit = 1 << 15-output_number
        new_state = current_state | bit if value else current_state & (~bit & 0xFFFF)
        self.bus.write_byte_data(self.address, new_state & 0xff, (new_state >> 8) & 0xff)

    def get_pin_state(self, pin_number):
        """
        Get the boolean state of an individual pin.
        """
        assert pin_number in range(16), "Pin number must be an integer between 0 and 15"
        state = self.bus.read_word_data(self.address, 0)
        return bool(state & 1<<15-pin_number)

提供的继电器控制代码relay.py

from pcf8575 import PCF8575
import time

"""
Relay.Py allows to control the relay card, its functions allow to choose the cables to be connected to the LCR meter.
"""

i2c_port_num = 1                        # on terminal do : "i2cdetect -l"  ,return : i2c-1, so i2c_port_num = 1
pcf_adress = 0x20                       # on terminal do : "i2cdetect -y 1"  ,return : 0x20, pcf_adress = 0x20,
                                        #you can change it with the i2c slave address switch which is physically on the relayboard

pcf=PCF8575(i2c_port_num,pcf_adress)

#we assimilate the relay numbers to their chosen designations
Relay_1A = 1
Relay_1B = 16
Relay_2A = 2
Relay_2B = 15
Relay_3A = 3
Relay_3B = 14
Relay_4A = 4
Relay_4B = 13
Relay_5A = 5
Relay_5B = 12
Relay_6A = 6
Relay_6B = 11
Relay_7A = 7
Relay_7B = 10
Relay_size = 8   #normally closed on the side A
Relay_gnd = 9    #normally closed on the reinforcement A

Relay_A=(Relay_1A, Relay_2A, Relay_3A , Relay_4A, Relay_5A, Relay_6A, Relay_7A)
Relay_B=(Relay_1B, Relay_2B, Relay_3B , Relay_4B, Relay_5B, Relay_6B, Relay_7B)
Relay=(Relay_A, Relay_B)
A=0
B=1
def clear_all():
    State=[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]
    pcf.port=State
    return State

def switch_on(State,relay):
    State[abs(relay-16)]=False   #the pcf.port list is reversed (pcf.port[0]=relay 16, pcf.port[1]=relay 15, etc...)
    pcf.port=State               #if you do directly pcf.port[0]= false, relay from 9 to 16 will be False (activate), I don't know why, so we use State
    return State

def connect_tendon(num_tendon):
    State=clear_all()
    State=switch_on(State, Relay_A[num_tendon-1])
    State=switch_on(State, Relay_B[num_tendon-1])
# With this function you can connect you to one side of a cable and choose one ground
def connect_with_ground(num_tendon, tendon_side, ground_side):
    State=clear_all()
    State=switch_on(State, Relay[tendon_side][num_tendon-1])
    if tendon_side==0:
        State=switch_on(State, Relay_size)
    else:
        pass
    if ground_side==1:
        State=switch_on(State, Relay_gnd)
    else:
        pass
    
    
# These examples of ground tests are all combinations that can be found with two grounds and two connections per cable.

def Connect_gnd_1(num_tendon):
    connect_with_ground(num_tendon,A,A)
def Connect_gnd_2(num_tendon):
    connect_with_ground(num_tendon,A,B)
def Connect_gnd_3(num_tendon):
    connect_with_ground(num_tendon,B,A)
def Connect_gnd_4(num_tendon):
    connect_with_ground(num_tendon,B,B)

try:
    clear_all()
except:
    pass


clear_all()

排查与修复建议

1. 修复PCF8575的字节写入顺序

PCF8575的I2C传输规则是先发送高字节(对应引脚P15-P8),再发送低字节(对应引脚P7-P0),原代码的写入顺序完全颠倒,导致引脚映射混乱。
修改PCF8575类中的两处写入逻辑:

# 修改port.setter中的写入代码
self.bus.write_byte_data(self.address, (new_state >> 8) & 0xff, new_state & 0xff)

# 修改set_output中的写入代码
self.bus.write_byte_data(self.address, (new_state >> 8) & 0xff, new_state & 0xff)

2. 修正继电器编号与PCF引脚的映射逻辑

relay.py中switch_on函数的索引计算错误,继电器编号1-16对应PCF引脚15-0,正确的索引计算应为:

def switch_on(State, relay):
    # 继电器编号1→PCF引脚15,继电器16→PCF引脚0
    pin_idx = 15 - (relay - 1)
    State[pin_idx] = False  # 低电平激活继电器(多数继电器模块为低电平触发)
    pcf.port = State
    return State

原代码的abs(relay-16)会导致继电器9对应PCF引脚7,而实际应该对应引脚6,这是部分通道失效的核心原因。

3. 添加全通道循环逻辑

原代码未实现循环16通道的功能,补充以下代码到relay.py末尾,确保接地通道始终激活:

def cycle_all_channels():
    try:
        while True:
            # 初始状态:保持接地激活
            State = clear_all()
            State = switch_on(State, Relay_gnd)
            
            # 循环Relay_A组7个通道
            for idx in range(len(Relay_A)):
                State = switch_on(State, Relay_A[idx])
                print(f"激活通道Relay_A{idx+1},保持接地")
                time.sleep(2)
                # 重置当前通道,保留接地
                State = clear_all()
                State = switch_on(State, Relay_gnd)
            
            # 循环Relay_B组7个通道
            for idx in range(len(Relay_B)):
                State = switch_on(State, Relay_B[idx])
                print(f"激活通道Relay_B{idx+1},保持接地")
                time.sleep(2)
                State = clear_all()
                State = switch_on(State, Relay_gnd)
            
            # 激活Relay_size通道
            State = switch_on(State, Relay_size)
            print(f"激活通道Relay_size,保持接地")
            time.sleep(2)
    except KeyboardInterrupt:
        clear_all()
        print("循环终止,重置所有继电器")

# 启动循环
cycle_all_channels()

4. 硬件与基础验证

  • 用i2cdetect -y 1确认PCF8575的0x20地址能被正常识别,排除I2C通信故障
  • 检查继电器模块的供电电压/电流是否满足16路继电器的驱动需求
  • 确认故障通道的接线无松动、短路情况

内容的提问来源于stack exchange,提问作者Povilas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 20:57:29