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., filesys.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(fUsing 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(fUsing 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, consumersc16-soft-tty, config{ PIN_CS: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.ACTIVE), PIN_MOSI: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.INACTIVE), PIN_SCLK: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.INACTIVE), PIN_MISO: gpiod.LineSettings(directionDirection.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(consumersc16-soft-tty, typegpiod.LINE_REQ_DIR_OUT, default_vals[1]) self.mosi.request(consumersc16-soft-tty, typegpiod.LINE_REQ_DIR_OUT, default_vals[0]) self.sclk.request(consumersc16-soft-tty, typegpiod.LINE_REQ_DIR_OUT, default_vals[0]) self.miso.request(consumersc16-soft-tty, typegpiod.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(fCH{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(fCH{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(descriptionCreate pseudo TTY devices for SC16IS752 UART channels.) parser.add_argument(--baud, typeint, default9600) 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(descriptionTwo-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, typeint, default9600) parser.add_argument(--payload, default01 03 08 06, helpHex bytes, for example: 01 03 08 06) parser.add_argument(--read-seconds, typefloat, default1.0) parser.add_argument(--interval, typefloat, default1.0) args parser.parse_args() payload bytes.fromhex(args.payload) print(fPORT0{args.port0}, PORT1{args.port1}, baud{args.baud}, payload{payload.hex( )}) print(Press CtrlC to stop.) port0 serial.Serial(args.port0, args.baud, timeout0.05, write_timeout1) port1 serial.Serial(args.port1, args.baud, timeout0.05, write_timeout1) 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, filesys.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, consumersc16-ch-loop, config{ PIN_CS: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.ACTIVE), PIN_MOSI: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.INACTIVE), PIN_SCLK: gpiod.LineSettings(directionDirection.OUTPUT, output_valueValue.INACTIVE), PIN_MISO: gpiod.LineSettings(directionDirection.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(consumersc16-ch-loop, typegpiod.LINE_REQ_DIR_OUT, default_vals[1]) self.mosi.request(consumersc16-ch-loop, typegpiod.LINE_REQ_DIR_OUT, default_vals[0]) self.sclk.request(consumersc16-ch-loop, typegpiod.LINE_REQ_DIR_OUT, default_vals[0]) self.miso.request(consumersc16-ch-loop, typegpiod.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) # 规格书 EFCRbit4RTSCON 自动方向控制bit5RTSINVER 发送时高电平。 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(descriptionSC16IS752 CH1/CH2 RS485 回环测试) parser.add_argument(--baud, typeint, default9600, help波特率默认 9600) parser.add_argument(--payload, default01 03 08 06, help发送数据十六进制字符串) parser.add_argument(--read-seconds, typefloat, default1.0, help每次接收等待秒数) parser.add_argument(--interval, typefloat, default1.0, help每轮测试间隔秒数) parser.add_argument(--count, typeint, default0, 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(按 CtrlC 停止。) 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()