SC16IS752 树莓派 底层驱动
SC16IS752在树莓派官方系统有现成驱动,但是部分没有驱动的linux 主机可能就得从寄存器底层开发了,这里展示:
驱动串口
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ User-space pseudo TTY bridge for SC16IS752 on Raspberry Pi SPI1. This does not use the Linux sc16is7xx driver and does not create /dev/ttySC*. Instead it creates pseudo serial devices: /tmp/ttySC0_soft /tmp/ttySC1_soft Programs can open those paths like a normal serial port while this script is running. Bytes are moved between the pseudo TTY and the SC16IS752 UART FIFOs. """ import argparse import glob import os import pty import select import sys import termios import time import tty try: import gpiod except ImportError: print("Missing python3-gpiod.", file=sys.stderr) raise PIN_CS = 18 PIN_MISO = 19 PIN_MOSI = 20 PIN_SCLK = 21 XTAL = 14_745_600 REG_RHR_THR = 0x00 REG_IER = 0x01 REG_FCR_IIR = 0x02 REG_LCR = 0x03 REG_MCR = 0x04 REG_LSR = 0x05 REG_TXLVL = 0x08 REG_RXLVL = 0x09 REG_EFCR = 0x0F REG_DLL = 0x00 REG_DLH = 0x01 REG_EFR = 0x02 REG_SPR = 0x07 def find_gpiochip(): env_path = os.environ.get("SC16_GPIOCHIP") paths = [env_path] if env_path else sorted(glob.glob("/dev/gpiochip*")) errors = [] for path in paths: try: chip = gpiod.Chip(path) if hasattr(chip, "get_line"): lines = [chip.get_line(pin) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK)] names = [(line.name() or "") for line in lines] else: lines = [chip.get_line_info(pin) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK)] names = [(line.name or "") for line in lines] if names[0] in ("GPIO18", "PIN12") or any(name.startswith("GPIO") for name in names): print(f"Using GPIO chip {path} for pins GPIO18-21") chip.close() return path chip.close() except Exception as exc: errors.append(f"{path}: {exc}") try: chip.close() except Exception: pass # Last resort for older libgpiod/kernel combinations that expose usable # offsets but do not provide helpful line names. for path in paths: try: chip = gpiod.Chip(path) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK): if hasattr(chip, "get_line"): chip.get_line(pin) else: chip.get_line_info(pin) print(f"Using GPIO chip {path} for pins GPIO18-21") chip.close() return path except Exception as exc: errors.append(f"{path}: {exc}") try: chip.close() except Exception: pass detail = "; ".join(errors) if errors else "no /dev/gpiochip* devices found" raise RuntimeError( "Could not find GPIO chip for 40-pin GPIO18-21. " "Run 'gpioinfo | grep -E \"GPIO18|GPIO19|GPIO20|GPIO21\"' and, if needed, " "set SC16_GPIOCHIP=/dev/gpiochipN. Checked: " + detail ) class GpiodV2Line: def __init__(self, request, offset): self.request = request self.offset = offset def set_value(self, value): from gpiod.line import Value self.request.set_value(self.offset, Value.ACTIVE if value else Value.INACTIVE) def get_value(self): from gpiod.line import Value return 1 if self.request.get_value(self.offset) == Value.ACTIVE else 0 def release(self): pass class BitBangSPI: def __init__(self): chip_path = find_gpiochip() if hasattr(gpiod, "request_lines"): from gpiod.line import Direction, Value self.chip = None self.request = gpiod.request_lines( chip_path, consumer="sc16-soft-tty", config={ PIN_CS: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.ACTIVE), PIN_MOSI: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE), PIN_SCLK: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE), PIN_MISO: gpiod.LineSettings(direction=Direction.INPUT), }, ) self.cs = GpiodV2Line(self.request, PIN_CS) self.miso = GpiodV2Line(self.request, PIN_MISO) self.mosi = GpiodV2Line(self.request, PIN_MOSI) self.sclk = GpiodV2Line(self.request, PIN_SCLK) else: self.request = None self.chip = gpiod.Chip(chip_path) self.cs = self.chip.get_line(PIN_CS) self.miso = self.chip.get_line(PIN_MISO) self.mosi = self.chip.get_line(PIN_MOSI) self.sclk = self.chip.get_line(PIN_SCLK) self.cs.request(consumer="sc16-soft-tty", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[1]) self.mosi.request(consumer="sc16-soft-tty", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) self.sclk.request(consumer="sc16-soft-tty", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) self.miso.request(consumer="sc16-soft-tty", type=gpiod.LINE_REQ_DIR_IN) def close(self): for line in (self.cs, self.mosi, self.sclk, self.miso): try: line.release() except Exception: pass if self.request is not None: self.request.release() if self.chip is not None: self.chip.close() def transfer_byte(self, value): received = 0 for bit in range(7, -1, -1): self.mosi.set_value((value >> bit) & 1) self.sclk.set_value(1) received = (received << 1) | self.miso.get_value() self.sclk.set_value(0) return received def transfer(self, data): self.cs.set_value(0) out = [self.transfer_byte(value) for value in data] self.cs.set_value(1) return out class SC16IS752: def __init__(self, spi): self.spi = spi @staticmethod def addr(reg, channel, read): return (0x80 if read else 0x00) | ((reg & 0x0F) << 3) | ((channel & 0x01) << 1) def read_reg(self, channel, reg): return self.spi.transfer([self.addr(reg, channel, True), 0x00])[1] def write_reg(self, channel, reg, value): self.spi.transfer([self.addr(reg, channel, False), value & 0xFF]) def init_uart(self, channel, baud): divisor = int(round(XTAL / (16 * baud))) self.write_reg(channel, REG_IER, 0x00) self.write_reg(channel, REG_LCR, 0xBF) self.write_reg(channel, REG_EFR, 0x10) self.write_reg(channel, REG_LCR, 0x80) self.write_reg(channel, REG_DLL, divisor & 0xFF) self.write_reg(channel, REG_DLH, (divisor >> 8) & 0xFF) self.write_reg(channel, REG_LCR, 0x03) self.write_reg(channel, REG_FCR_IIR, 0x07) time.sleep(0.01) self.write_reg(channel, REG_FCR_IIR, 0x01) # The Waveshare 2-CH RS485 HAT uses active-high DE. Enable the # SC16IS752 automatic RS485 RTS direction control and invert RTS so # the transceiver drives the bus only while bytes are being sent. self.write_reg(channel, REG_EFCR, 0x30) self.write_reg(channel, REG_MCR, 0x00) def probe(self): ok = True for channel, value in ((0, 0x5A), (1, 0xA5)): self.write_reg(channel, REG_SPR, value) got = self.read_reg(channel, REG_SPR) print(f"CH{channel}: SPR wrote 0x{value:02x}, read 0x{got:02x}") ok = ok and got == value return ok def write_uart(self, channel, data): index = 0 while index < len(data): space = self.read_reg(channel, REG_TXLVL) if not space: time.sleep(0.001) continue chunk = data[index:index + min(space, 64)] for value in chunk: self.write_reg(channel, REG_RHR_THR, value) index += len(chunk) def read_uart(self, channel): count = self.read_reg(channel, REG_RXLVL) if not count: return b"" return bytes(self.read_reg(channel, REG_RHR_THR) for _ in range(count)) class SoftTTY: def __init__(self, sc16, channel, link_path): self.sc16 = sc16 self.channel = channel self.link_path = link_path self.master_fd, slave_fd = pty.openpty() tty.setraw(self.master_fd) slave_name = os.ttyname(slave_fd) os.close(slave_fd) try: os.unlink(link_path) except FileNotFoundError: pass os.symlink(slave_name, link_path) print(f"CH{channel}: {link_path} -> {slave_name}") def close(self): try: os.unlink(self.link_path) except FileNotFoundError: pass os.close(self.master_fd) def pump_from_pty(self): try: data = os.read(self.master_fd, 4096) except OSError: return if data: self.sc16.write_uart(self.channel, data) def pump_from_uart(self): data = self.sc16.read_uart(self.channel) if data: os.write(self.master_fd, data) def main(): parser = argparse.ArgumentParser(description="Create pseudo TTY devices for SC16IS752 UART channels.") parser.add_argument("--baud", type=int, default=9600) parser.add_argument("--ch0", default="/tmp/ttySC0_soft") parser.add_argument("--ch1", default="/tmp/ttySC1_soft") args = parser.parse_args() spi = BitBangSPI() ttys = [] try: sc16 = SC16IS752(spi) if not sc16.probe(): raise RuntimeError("SC16IS752 scratchpad probe failed") sc16.init_uart(0, args.baud) sc16.init_uart(1, args.baud) ttys = [ SoftTTY(sc16, 0, args.ch0), SoftTTY(sc16, 1, args.ch1), ] print("Soft TTY bridge is running. Keep this process alive.") print("Example: python3 -m serial.tools.miniterm /tmp/ttySC0_soft 9600") while True: readable, _, _ = select.select([tty_obj.master_fd for tty_obj in ttys], [], [], 0.01) for tty_obj in ttys: if tty_obj.master_fd in readable: tty_obj.pump_from_pty() tty_obj.pump_from_uart() finally: for tty_obj in ttys: tty_obj.close() spi.close() if __name__ == "__main__": main()RS485 CH·-CH2 回环测试
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import time import serial def read_for(port, seconds): data = bytearray() end = time.monotonic() + seconds while time.monotonic() < end: chunk = port.read(256) if chunk: data.extend(chunk) else: time.sleep(0.01) return bytes(data) def exchange(tx, rx, label, payload, read_seconds): tx.reset_input_buffer() rx.reset_input_buffer() tx.write(payload) tx.flush() print(f"{label}: sent {payload.hex(' ')}") received = read_for(rx, read_seconds) if received: print(f"{label}: received {received.hex(' ')}") else: print(f"{label}: received NO_DATA") return received def main(): parser = argparse.ArgumentParser(description="Two-port loopback test for ttySC or soft TTY serial ports.") parser.add_argument("--port0", default="/tmp/ttySC0_soft") parser.add_argument("--port1", default="/tmp/ttySC1_soft") parser.add_argument("--baud", type=int, default=9600) parser.add_argument("--payload", default="01 03 08 06", help="Hex bytes, for example: '01 03 08 06'") parser.add_argument("--read-seconds", type=float, default=1.0) parser.add_argument("--interval", type=float, default=1.0) args = parser.parse_args() payload = bytes.fromhex(args.payload) print(f"PORT0={args.port0}, PORT1={args.port1}, baud={args.baud}, payload={payload.hex(' ')}") print("Press Ctrl+C to stop.") port0 = serial.Serial(args.port0, args.baud, timeout=0.05, write_timeout=1) port1 = serial.Serial(args.port1, args.baud, timeout=0.05, write_timeout=1) try: while True: exchange(port0, port1, "PORT0 -> PORT1", payload, args.read_seconds) exchange(port1, port0, "PORT1 -> PORT0", payload, args.read_seconds) print("-" * 60) time.sleep(args.interval) except KeyboardInterrupt: print("\nStopped.") finally: port0.close() port1.close() if __name__ == "__main__": main()驱动和直接回环测试
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SC16IS752 双路 RS485 回环测试。 接线使用树莓派 SPI1: CS = GPIO18 MISO = GPIO19 MOSI = GPIO20 SCLK = GPIO21 本程序不需要同时运行 sc16_soft_tty.py。 它直接通过 GPIO 模拟 SPI 访问 SC16IS752,并开启芯片内部 RS485 自动方向控制。 """ import argparse import glob import os import sys import time try: import gpiod except ImportError: print("缺少 python3-gpiod,请先安装:sudo apt install python3-gpiod", file=sys.stderr) raise PIN_CS = 18 PIN_MISO = 19 PIN_MOSI = 20 PIN_SCLK = 21 # 这块板子的丝印/示例里,CH1/CH2 与 SC16IS752 的 A/B 通道顺序相反。 BOARD_CH1 = 1 BOARD_CH2 = 0 XTAL = 14_745_600 REG_RHR_THR = 0x00 REG_IER = 0x01 REG_FCR_IIR = 0x02 REG_LCR = 0x03 REG_MCR = 0x04 REG_LSR = 0x05 REG_SPR = 0x07 REG_TXLVL = 0x08 REG_RXLVL = 0x09 REG_EFCR = 0x0F REG_DLL = 0x00 REG_DLH = 0x01 REG_EFR = 0x02 LSR_TEMT = 0x40 def 找_gpiochip(): 指定芯片 = os.environ.get("SC16_GPIOCHIP") 芯片列表 = [指定芯片] if 指定芯片 else sorted(glob.glob("/dev/gpiochip*")) 错误列表 = [] for 路径 in 芯片列表: try: chip = gpiod.Chip(路径) if hasattr(chip, "get_line"): lines = [chip.get_line(pin) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK)] names = [(line.name() or "") for line in lines] else: lines = [chip.get_line_info(pin) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK)] names = [(line.name or "") for line in lines] if names[0] in ("GPIO18", "PIN12") or any(name.startswith("GPIO") for name in names): chip.close() print(f"使用 GPIO 芯片:{路径}") return 路径 chip.close() except Exception as exc: 错误列表.append(f"{路径}: {exc}") try: chip.close() except Exception: pass for 路径 in 芯片列表: try: chip = gpiod.Chip(路径) for pin in (PIN_CS, PIN_MISO, PIN_MOSI, PIN_SCLK): if hasattr(chip, "get_line"): chip.get_line(pin) else: chip.get_line_info(pin) chip.close() print(f"使用 GPIO 芯片:{路径}") return 路径 except Exception as exc: 错误列表.append(f"{路径}: {exc}") try: chip.close() except Exception: pass raise RuntimeError( "找不到 GPIO18/19/20/21 所在的 gpiochip。" "可运行:gpioinfo | grep -E 'GPIO18|GPIO19|GPIO20|GPIO21'。" "也可手动指定:SC16_GPIOCHIP=/dev/gpiochipN。" "检查结果:" + "; ".join(错误列表) ) class GpiodV2Line: def __init__(self, request, offset): self.request = request self.offset = offset def set_value(self, value): from gpiod.line import Value self.request.set_value(self.offset, Value.ACTIVE if value else Value.INACTIVE) def get_value(self): from gpiod.line import Value return 1 if self.request.get_value(self.offset) == Value.ACTIVE else 0 def release(self): pass class 软件SPI: def __init__(self): chip_path = 找_gpiochip() if hasattr(gpiod, "request_lines"): from gpiod.line import Direction, Value self.chip = None self.request = gpiod.request_lines( chip_path, consumer="sc16-ch-loop", config={ PIN_CS: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.ACTIVE), PIN_MOSI: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE), PIN_SCLK: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE), PIN_MISO: gpiod.LineSettings(direction=Direction.INPUT), }, ) self.cs = GpiodV2Line(self.request, PIN_CS) self.miso = GpiodV2Line(self.request, PIN_MISO) self.mosi = GpiodV2Line(self.request, PIN_MOSI) self.sclk = GpiodV2Line(self.request, PIN_SCLK) else: self.request = None self.chip = gpiod.Chip(chip_path) self.cs = self.chip.get_line(PIN_CS) self.miso = self.chip.get_line(PIN_MISO) self.mosi = self.chip.get_line(PIN_MOSI) self.sclk = self.chip.get_line(PIN_SCLK) self.cs.request(consumer="sc16-ch-loop", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[1]) self.mosi.request(consumer="sc16-ch-loop", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) self.sclk.request(consumer="sc16-ch-loop", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) self.miso.request(consumer="sc16-ch-loop", type=gpiod.LINE_REQ_DIR_IN) def close(self): for line in (self.cs, self.mosi, self.sclk, self.miso): try: line.release() except Exception: pass if self.request is not None: self.request.release() if self.chip is not None: self.chip.close() def 传输字节(self, value): received = 0 for bit in range(7, -1, -1): self.mosi.set_value((value >> bit) & 1) self.sclk.set_value(1) received = (received << 1) | self.miso.get_value() self.sclk.set_value(0) return received def 传输(self, data): self.cs.set_value(0) out = [self.传输字节(value) for value in data] self.cs.set_value(1) return out class SC16IS752: def __init__(self, spi): self.spi = spi @staticmethod def 地址(reg, channel, read): return (0x80 if read else 0x00) | ((reg & 0x0F) << 3) | ((channel & 0x01) << 1) def 读寄存器(self, channel, reg): return self.spi.传输([self.地址(reg, channel, True), 0x00])[1] def 写寄存器(self, channel, reg, value): self.spi.传输([self.地址(reg, channel, False), value & 0xFF]) def 初始化串口(self, channel, baud): divisor = int(round(XTAL / (16 * baud))) self.写寄存器(channel, REG_IER, 0x00) self.写寄存器(channel, REG_LCR, 0xBF) self.写寄存器(channel, REG_EFR, 0x10) self.写寄存器(channel, REG_LCR, 0x80) self.写寄存器(channel, REG_DLL, divisor & 0xFF) self.写寄存器(channel, REG_DLH, (divisor >> 8) & 0xFF) self.写寄存器(channel, REG_LCR, 0x03) self.写寄存器(channel, REG_FCR_IIR, 0x07) time.sleep(0.01) self.写寄存器(channel, REG_FCR_IIR, 0x01) # 规格书 EFCR:bit4=RTSCON 自动方向控制,bit5=RTSINVER 发送时高电平。 self.写寄存器(channel, REG_EFCR, 0x30) self.写寄存器(channel, REG_MCR, 0x00) def 探测芯片(self): ok = True for channel, value in ((BOARD_CH1, 0x5A), (BOARD_CH2, 0xA5)): self.写寄存器(channel, REG_SPR, value) got = self.读寄存器(channel, REG_SPR) print(f"通道{1 if channel == BOARD_CH1 else 2}: SPR 写入 0x{value:02x},读出 0x{got:02x}") ok = ok and got == value return ok def 发送(self, channel, data): for value in data: deadline = time.monotonic() + 1.0 while time.monotonic() < deadline: if self.读寄存器(channel, REG_TXLVL): self.写寄存器(channel, REG_RHR_THR, value) break time.sleep(0.001) else: print(f"通道发送超时,字节 0x{value:02x}") return False deadline = time.monotonic() + 1.0 while time.monotonic() < deadline: if self.读寄存器(channel, REG_LSR) & LSR_TEMT: return True time.sleep(0.001) return True def 接收(self, channel, seconds): data = bytearray() deadline = time.monotonic() + seconds while time.monotonic() < deadline: count = self.读寄存器(channel, REG_RXLVL) if count: for _ in range(count): data.append(self.读寄存器(channel, REG_RHR_THR)) else: time.sleep(0.01) return bytes(data) def 交换测试(dev, tx_ch, rx_ch, label, payload, read_seconds): dev.接收(rx_ch, 0.1) ok = dev.发送(tx_ch, payload) print(f"{label}: 发送 {payload.hex(' ')},结果={'成功' if ok else '失败'}") received = dev.接收(rx_ch, read_seconds) if received: print(f"{label}: 收到 {received.hex(' ')}") else: print(f"{label}: 没收到数据") return received == payload def main(): parser = argparse.ArgumentParser(description="SC16IS752 CH1/CH2 RS485 回环测试") parser.add_argument("--baud", type=int, default=9600, help="波特率,默认 9600") parser.add_argument("--payload", default="01 03 08 06", help="发送数据,十六进制字符串") parser.add_argument("--read-seconds", type=float, default=1.0, help="每次接收等待秒数") parser.add_argument("--interval", type=float, default=1.0, help="每轮测试间隔秒数") parser.add_argument("--count", type=int, default=0, help="测试轮数,0 表示一直循环") args = parser.parse_args() payload = bytes.fromhex(args.payload) spi = 软件SPI() try: dev = SC16IS752(spi) if not dev.探测芯片(): raise RuntimeError("SC16IS752 探测失败,请检查 SPI 接线、电源、CS。") dev.初始化串口(BOARD_CH1, args.baud) dev.初始化串口(BOARD_CH2, args.baud) print(f"开始 CH1/CH2 回环测试,波特率={args.baud},数据={payload.hex(' ')}") print("按 Ctrl+C 停止。") round_index = 0 while args.count == 0 or round_index < args.count: round_index += 1 ok12 = 交换测试(dev, BOARD_CH1, BOARD_CH2, "CH1 -> CH2", payload, args.read_seconds) ok21 = 交换测试(dev, BOARD_CH2, BOARD_CH1, "CH2 -> CH1", payload, args.read_seconds) print(f"第 {round_index} 轮:{'通过' if ok12 and ok21 else '失败'}") print("-" * 60) time.sleep(args.interval) except KeyboardInterrupt: print("\n已停止。") finally: spi.close() if __name__ == "__main__": main()