├── .gitignore ├── requirements.txt ├── history ├── ch347-hidapi-0.0.1.tar.gz └── ch347_hidapi-0.0.1-py3-none-any.whl ├── .idea ├── .gitignore ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── encodings.xml ├── modules.xml ├── aws.xml ├── misc.xml ├── CH347PythonLib.iml └── deployment.xml ├── ch347api ├── gpio.py ├── __init__.py ├── __i2c.py ├── i2c.py ├── uart.py ├── __spi.py ├── spi.py └── __device.py ├── LICENSE ├── setup.py ├── README.md ├── test_deprecated.py └── demo.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hidapi -------------------------------------------------------------------------------- /history/ch347-hidapi-0.0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i2cy/CH347-HIDAPI/HEAD/history/ch347-hidapi-0.0.1.tar.gz -------------------------------------------------------------------------------- /history/ch347_hidapi-0.0.1-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i2cy/CH347-HIDAPI/HEAD/history/ch347_hidapi-0.0.1-py3-none-any.whl -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /ch347api/gpio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347-HIDAPI 5 | # Filename: gpio 6 | # Created on: 2024/1/13 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /ch347api/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: __init__ 6 | # Created on: 2022/11/11 7 | 8 | from .__device import CH347HIDDev, VENDOR_ID, PRODUCT_ID 9 | from .i2c import I2CDevice 10 | from .spi import SPIDevice 11 | from .uart import UARTDevice 12 | from .__spi import SPIClockFreq 13 | from .__i2c import I2CClockFreq 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/CH347PythonLib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Icy Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: setup 6 | # Created on: 2022/11/22 7 | 8 | import setuptools 9 | 10 | with open("README.md", "r", encoding="utf-8") as fh: 11 | long_description = fh.read() 12 | 13 | setuptools.setup( 14 | name="ch347api", 15 | version="0.3.1", 16 | author="I2cy Cloud", 17 | author_email="i2cy@outlook.com", 18 | description="A Python Library provides full access of SPI/I2C/UART settings and communication" 19 | " with CH347 USB-SPI/I2C/UART bridge chip in Python language.", 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | url="https://github.com/i2cy/ch347-hidapi", 23 | project_urls={ 24 | "Bug Tracker": "https://github.com/i2cy/ch347-hidapi/issues", 25 | "Source Code": "https://github.com/i2cy/ch347-hidapi", 26 | "Documentation": "https://github.com/i2cy/CH347-HIDAPI/blob/master/README.md" 27 | }, 28 | classifiers=[ 29 | "Programming Language :: Python :: 3", 30 | "License :: OSI Approved :: MIT License", 31 | "Operating System :: OS Independent", 32 | ], 33 | install_requires=[ 34 | 'hidapi' 35 | ], 36 | packages=setuptools.find_packages(), 37 | python_requires=">=3.7", 38 | entry_points={'console_scripts': 39 | [] 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /ch347api/__i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: __i2c 6 | # Created on: 2023/7/31 7 | 8 | 9 | from typing import Any 10 | import struct 11 | 12 | 13 | class I2CClockFreq: 14 | f_20K = 0 15 | f_100K = 1 16 | f_400K = 2 17 | f_750K = 3 18 | 19 | 20 | def convert_i2c_address(addr: (int, bytes), read: bool = False) -> bytes: 21 | """ 22 | static method for address conversion 23 | :param addr: Any[int, bytes], 7-bits of device address 24 | :param read: bool, False -> write, True -> read 25 | :return: bytes 26 | """ 27 | if isinstance(addr, int): 28 | addr = addr << 1 29 | else: 30 | addr = addr[0] << 1 31 | 32 | if read: 33 | addr += 1 34 | 35 | return struct.pack('B', addr) 36 | 37 | 38 | def convert_int_to_bytes(inputs: (int, bytes)) -> bytes: 39 | """ 40 | this method will automatically convert it into bytes with byte order 'big' unsigned if data type is int, 41 | e.g. 0x00f1f2 -> b'\xf1\xf2' 42 | :param inputs: Any[int, bytes] 43 | :return: bytes 44 | """ 45 | if isinstance(inputs, int): 46 | b_len = 0 47 | data_copy = inputs 48 | while data_copy: 49 | b_len += 1 50 | data_copy = data_copy // 256 51 | if b_len == 0: 52 | b_len = 1 53 | inputs = inputs.to_bytes(b_len, 'big', signed=False) 54 | 55 | 56 | return inputs 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | # CH347-HIDAPI Python Library 6 | 7 | _+ [CH347-HIDAPI Github Page](https://github.com/i2cy/ch347-hidapi) +_ 8 | 9 |
10 | 11 |

12 | 13 | license 14 | 15 | 16 | pypi 17 | 18 | python 19 |

20 | 21 | ## Abstract 22 | This project is the API library of CH347T USB-SPI/I2C/UART bridge chip based on Python. 23 | 24 | `Standard USB-HID mode setting of CH347T chip supported only` 25 | 26 | This library provides full access of SPI/I2C/UART settings and communication with CH347 USB-SPI/I2C/UART 27 | bridge chip in Python language. 28 | 29 | __For demonstration and code reference please refer to the `demo.py` file in [source page](https://github.com/i2cy/CH347-HIDAPI/blob/master/demo.py).__ 30 | 31 | [CH347-Chip Official Site](https://www.wch.cn/products/CH347.html) 32 | 33 | ## Installation 34 | `pip install ch347api` 35 | 36 | ## Requirements 37 | `Python >= 3.7` 38 | `hidapi` 39 | 40 | ## CAUTION 41 | The communication protocol with CH347 through USB-HID I wrote in this project based on the official 42 | demonstration APP. In other words that it was inferred from captured HID package when APP communicates. 43 | 44 | THUS, THIS API MAY NOT FULLY CAPABLE OF EVERY FUNCTION IN OFFICIAL API FROM CH347DLL.DLL. 45 | 46 | ## Update Notes 47 | 48 | #### 2024-01-12 49 | 1. Now with fully compatible UART (UART1 with pins TXD1/RXD1/RTS1/CTS1/DTR1) support under mode 3 (which is HID mode), 50 | 2. Baudrate supports ranging from 1.2K to 9M 51 | 3. Multithread receiver for UART (optional, default is on) to receive the data in parallel 52 | 53 | #### 2024-01-08 54 | 1. Added independent I2C interface class objects (I2CDevice) and SPI interface class objects (SPIDevice) 55 | 2. Added new demo file `demo.py` to demonstrate the usage of classes added above (simplified code) 56 | 3. Added device lock to make thread safe 57 | 58 | #### 2023-08-06 59 | 1. Now with fully compatible I2C support, I2C clock speed level: 0 -> 20KHz, 1 -> 100KHz, 2 -> 400KHz, 3 -> 750KHz 60 | 2. Added test.py for demonstration 61 | -------------------------------------------------------------------------------- /ch347api/i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347-HIDAPI 5 | # Filename: i2c 6 | # Created on: 2024/1/6 7 | 8 | 9 | from .__device import CH347HIDDev 10 | from .__i2c import convert_int_to_bytes 11 | from typing import Tuple, Any 12 | 13 | 14 | class I2CDevice: 15 | 16 | def __init__(self, addr: int, clock_freq_level: int = 1, ch347_device: CH347HIDDev = None): 17 | """ 18 | Encapsulated class of I2C device 19 | :param addr: I2C device address 20 | :type addr: int 21 | :param clock_freq_level: Clock frequency, 0-20KHz, 1-100KHz, 2-400KHz, 3-750KHz 22 | :type clock_freq_level: int, optional 23 | :param ch347_device: CH347HIDDev (defaults to None), will create a new CH347HIDDev if unset 24 | :type ch347_device: CH347HIDDev, optional 25 | """ 26 | if ch347_device is None: 27 | # create new CH347 HID device port if unset 28 | ch347_device = CH347HIDDev() 29 | 30 | self.dev = ch347_device 31 | self.addr = addr 32 | 33 | if not self.dev.i2c_initiated: 34 | # initialize i2c device if doesn't 35 | self.dev.init_I2C(clock_freq_level=clock_freq_level) 36 | 37 | def write(self, reg: (int, bytes) = None, data: (int, bytes) = None) -> bool: 38 | """ 39 | Write data to the device through I2C bus 40 | :param reg: address of the register, or None for direct transmission 41 | :type reg: :obj:`int`, :obj:`bytes` 42 | :param data: data to send, or None for write probing 43 | :type data: :obj:`int`, :obj:`bytes` 44 | :return: operation status 45 | """ 46 | payload = b"" 47 | if reg is not None: 48 | payload += convert_int_to_bytes(reg) 49 | 50 | if data is not None: 51 | payload += convert_int_to_bytes(data) 52 | 53 | return self.dev.i2c_write(self.addr, data=payload) 54 | 55 | def read(self, reg: (int, bytes) = None, length: int = 0): 56 | """ 57 | Read data from the device through I2C bus 58 | :param reg: address of the register, or None for direct transmission 59 | :type reg: :obj:`int`, :obj:`bytes` 60 | :param length: number of bytes to read, default is 0 for read probing 61 | :type length: int 62 | :return: return bytes when length is greater than 0, or bool status if length is 0 63 | :rtype: :obj:`bytes`, :obj:`bool` 64 | """ 65 | status, feedback = self.dev.i2c_read(addr=self.addr, read_length=length, register_addr=reg) 66 | 67 | if length: 68 | return feedback 69 | else: 70 | return status 71 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 47 | -------------------------------------------------------------------------------- /test_deprecated.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: test 6 | # Created on: 2023/7/31 7 | import struct 8 | 9 | import random 10 | import time 11 | from hashlib import sha256 12 | from ch347api import CH347HIDDev, VENDOR_ID, PRODUCT_ID 13 | 14 | 15 | def generate_random_data(length=50): 16 | # generate test bytes for transmission 17 | res = [] 18 | for i in range(length): 19 | res.append(int(random.random() * 255)) 20 | return bytes(res) 21 | 22 | 23 | """ 24 | Tests below requires MOSI-MISO short connected (outer-loop) 25 | """ 26 | 27 | if __name__ == '__main__': 28 | 29 | # initialize a new CH347HIDDev device object 30 | test_dev = CH347HIDDev(VENDOR_ID, PRODUCT_ID, 1) 31 | 32 | # print HID device information 33 | print("Manufacturer: %s" % test_dev.get_manufacturer_string()) 34 | print("Product: %s" % test_dev.get_product_string()) 35 | print("Serial No: %s" % test_dev.get_serial_number_string()) 36 | 37 | # -*- [ i2c test ] -*- 38 | 39 | # initialize I2C with speed 400KHz 40 | test_dev.init_I2C(2) 41 | 42 | input("(press ENTER to perform I2C test)") 43 | 44 | # write 0x75 to device with address 0x68 45 | print("I2C test address 0x68") 46 | status = test_dev.i2c_write(0x68, 0x75) 47 | print("I2C write 0x75 test: {}".format(status)) 48 | 49 | # read 2 bytes from device with address 0x68 50 | status, feedback = test_dev.i2c_read(0x68, 2) 51 | print("I2C read 2 bytes test: {}, {}".format(status, feedback.hex())) 52 | 53 | # read 1 byte of register 0x75 from device with address 0x68 54 | status, feedback = test_dev.i2c_read(0x68, 1, 0x75) 55 | print("I2C read 1 byte with register address 0x75 test: {}, {}".format(status, feedback.hex())) 56 | 57 | # read 2 bytes of register 0x74 from device with address 0x68 58 | status, feedback = test_dev.i2c_read(0x68, 2, b"\x74") 59 | print("I2C read 2 bytes with register address 0x74 test: {}, {}".format(status, feedback.hex())) 60 | 61 | # initialize SPI settings 62 | test_dev.init_SPI(0, mode=0) # CLK speed: 60Mhz, SPI mode: 0b11 63 | 64 | test_data_frame_length = 32768 65 | time.sleep(0.2) 66 | 67 | # generate test bytes 68 | data = generate_random_data(test_data_frame_length) 69 | 70 | input("(press ENTER to perform SPI test)") 71 | 72 | # -*- [ spi test ] -*- 73 | 74 | # write A5 5A 5A A5 through API 75 | test_dev.set_CS1() # enable CS1 for transmission 76 | test_dev.spi_write(b"\xa5\x5a\x5a\xa5") 77 | test_dev.spi_read(2) 78 | test_dev.set_CS1(False) 79 | 80 | # specialized speed test (for project) 81 | t0 = time.time() 82 | test_dev.set_CS1() # enable CS1 for transmission 83 | for ele in range(4 * 3 * 200_000 // test_data_frame_length + 1): 84 | feed = test_dev.spi_read(test_data_frame_length) 85 | test_dev.set_CS1(False) 86 | print("1 sec of gtem data trans time spent {:.2f} ms".format((time.time() - t0) * 1000)) 87 | 88 | # read & write test 89 | print("performing spi_read_write test...") 90 | test_dev.set_CS1() # enable CS1 for transmission 91 | feed = test_dev.spi_read_write(data) 92 | test_dev.set_CS1(False) 93 | print("R/W loop accusation test result: {}".format(bytes(feed) == data)) 94 | 95 | test_dev.close() 96 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 134 | -------------------------------------------------------------------------------- /ch347api/uart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347-HIDAPI 5 | # Filename: uart 6 | # Created on: 2024/1/8 7 | 8 | from .__device import CH347HIDUART1 9 | import threading 10 | import time 11 | 12 | 13 | class UARTDevice: 14 | 15 | def __init__(self, baudrate: int = 115200, stop_bits: int = 1, verify_bits: int = 0, timeout: int = 32, 16 | multithreading: bool = True): 17 | """ 18 | Initialize the configuration of the UART interface 19 | :param baudrate: the baudrate of the device, default is 115200, (Min: 1200, Max: 9M) 20 | :type baudrate: int 21 | :param stop_bits: number of stop bits, default is 1 22 | :type stop_bits: int 23 | :param verify_bits: number of verify bits, default is 0 24 | :type verify_bits: int 25 | :param timeout: timeout in milliseconds, default is 32, this value should not exceed the maximum of 255 26 | :type timeout: int 27 | :param multithreading: whether to use multithreading to receive the data in parallel, default is True 28 | :type multithreading: bool 29 | """ 30 | self.dev = CH347HIDUART1() 31 | self.__multithreading = False 32 | status = self.dev.init_UART(baudrate=baudrate, stop_bits=stop_bits, verify_bits=verify_bits, timeout=timeout) 33 | if not status: 34 | raise Exception("failed to initialize UART-1 interface on CH347") 35 | 36 | self.__multithreading = multithreading 37 | 38 | self.__received_data = [] 39 | self.__live = True 40 | self.__thread = threading.Thread(target=self.__receiver_thread) 41 | self.__data_lock = threading.Lock() 42 | 43 | if self.__multithreading: 44 | # enable multithreading receiver 45 | self.__thread.start() 46 | 47 | def __receiver_thread(self): 48 | """ 49 | Receive data from the UART interface and put it in a queue to store the received data 50 | :return: 51 | """ 52 | while self.__live: 53 | data = self.dev.read_raw() 54 | if data: 55 | self.__data_lock.acquire() 56 | # print("received data:", bytes(data).hex()) 57 | self.__received_data.extend(data) 58 | self.__data_lock.release() 59 | else: 60 | time.sleep(0.01) 61 | 62 | def __del__(self) -> None: 63 | if self.__multithreading: 64 | self.__live = False 65 | try: 66 | self.__data_lock.release() 67 | self.__thread.join() 68 | except RuntimeError: 69 | pass 70 | 71 | def kill(self): 72 | """ 73 | Kill receiver thread if multithreading is on 74 | :return: 75 | """ 76 | if self.__multithreading: 77 | self.__live = False 78 | try: 79 | self.__data_lock.release() 80 | self.__thread.join() 81 | except RuntimeError: 82 | pass 83 | 84 | def write(self, data: bytes) -> int: 85 | """ 86 | Write data to the device 87 | :param data: data to write 88 | :type data: bytes 89 | :return: wrote length 90 | :rtype: int 91 | """ 92 | return self.dev.write_raw(data) 93 | 94 | def read(self, length: int = -1, timeout: int = 5) -> list: 95 | """ 96 | Read data from the device if any byte is available 97 | :param length: maximum length of the data, default is -1 which means read all bytes that received 98 | :type length: int 99 | :param timeout: timeout in seconds, default is 5, set to 0 means return data from buffer immediately instead of 100 | waiting for the buffer to collect enough data (available only for multithreading) 101 | :type timeout: int 102 | :return: list of bytes read 103 | :rtype: list 104 | """ 105 | if self.__multithreading: 106 | # wait for data 107 | t0 = time.time() 108 | 109 | if length < 0: 110 | while time.time() - t0 < timeout and len(self.__received_data) == 0: 111 | time.sleep(0.002) 112 | else: 113 | while time.time() - t0 < timeout and length > len(self.__received_data): 114 | time.sleep(0.002) 115 | 116 | self.__data_lock.acquire() 117 | 118 | if len(self.__received_data) > length > 0: 119 | ret = self.__received_data[:length] 120 | self.__received_data = self.__received_data[length:] 121 | 122 | else: 123 | ret = self.__received_data 124 | self.__received_data = [] 125 | 126 | self.__data_lock.release() 127 | 128 | else: 129 | ret = self.dev.read_raw(length) 130 | 131 | return ret 132 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347-HIDAPI 5 | # Filename: demo_2 6 | # Created on: 2024/1/7 7 | 8 | 9 | import random 10 | import time 11 | from ch347api import CH347HIDDev, I2CDevice, SPIDevice, UARTDevice, SPIClockFreq, I2CClockFreq 12 | 13 | 14 | def i2c_demo(): 15 | print('[I2C] Scan start...') 16 | hiddev = CH347HIDDev() 17 | hiddev.init_I2C() 18 | print(' ' + ''.join(map(lambda a : '{:02X} '.format(a), range(16)))) 19 | for i in range(128): 20 | if i % 16 == 0: 21 | print('0x{:02X}: '.format(i), end='') 22 | exists = hiddev.i2c_exists(i) 23 | if exists: 24 | print('{:02X} '.format(i), end='') 25 | else: 26 | print('__ ', end='') 27 | if i % 16 == 15: 28 | print() 29 | 30 | print('[I2C] MPU6050 example') 31 | # initialize an i2c communication object (MPU6050 I2C address: 0x68) 32 | # -*- Way 1 -*- 33 | i2c = I2CDevice(addr=0x68) 34 | 35 | # -*- Way 2 -*- 36 | # i2c = I2CDevice(addr=0x68, clock_freq_level=I2CClockFreq.f_100K) 37 | 38 | # -*- Way 3 -*- (using one device object to make thread safe) 39 | # dev = CH347HIDDev() 40 | # i2c_1 = I2CDevice(addr=0x68, ch347_device=dev) 41 | # i2c_2 = I2CDevice(addr=0x23, ch347_device=dev) 42 | # i2c_3 = I2CDevice(addr=0x22, ch347_device=dev) 43 | 44 | # read MPU6050 factory data 45 | d = i2c.read(0x75, 1) 46 | print("[I2C] read from MPU6050 register 0x75 (should be 0x68): 0x{}".format(d.hex())) 47 | 48 | # reset MPU6050 49 | status = i2c.write(0x6b, 0x80) 50 | print("[I2C] write to MPU6050 register 0x6B with data 0x80 to reset the device, status: {}".format(status)) 51 | time.sleep(0.1) 52 | 53 | # setting up MPU6050 54 | status = i2c.write(0x6b, 0x01) 55 | print("[I2C] write to MPU6050 register 0x6B with data 0x01, status: {}".format(status)) 56 | status = i2c.write(0x6c, 0x00) 57 | print("[I2C] write to MPU6050 register 0x6C with data 0x00, status: {}".format(status)) 58 | status = i2c.write(0x19, 0x00) 59 | print("[I2C] write to MPU6050 register 0x19 with data 0x00, status: {}".format(status)) 60 | status = i2c.write(0x1a, 0x02) 61 | print("[I2C] write to MPU6050 register 0x1a with data 0x02, status: {}".format(status)) 62 | status = i2c.write(0x1c, 0x08) 63 | print("[I2C] write to MPU6050 register 0x1c with data 0x08, status: {}".format(status)) 64 | 65 | 66 | def spi_demo(): 67 | # initialize a spi communication object 68 | # -*- Way 1 -*- 69 | # spi = SPIDevice() 70 | 71 | # -*- Way 2 -*-: 72 | spi = SPIDevice(clock_freq_level=SPIClockFreq.f_30M, is_16bits=False) 73 | 74 | # -*- Way 3 -*- (using one device object to make thread safe): 75 | # dev = CH347HIDDev() 76 | # spi = SPIDevice(ch347_device=dev) 77 | # i2c = I2CDevice(addr=0x68, ch347_device=dev) 78 | 79 | # write test (activate CS -> write data -> deactivate CS) 80 | print("[SPI] performing SPI write test") 81 | spi.write_CS1(b"hello world") 82 | spi.write_CS2(b"this is ch347") 83 | spi.write_CS1([0, 1, 2, 3]) 84 | spi.write_CS2([252, 253, 254, 255]) 85 | 86 | # write test (activate CS -> write data -> write data -> deactivate CS) 87 | spi.write_CS1(b"hello world", keep_cs_active=True) 88 | spi.write_CS1(b"this is ch347") 89 | 90 | # read test (activate CS -> read data -> deactivate CS) 91 | print("[SPI] performing SPI read test") 92 | read_length = 32768 93 | for i in range(2048): 94 | ret = bytes(spi.read_CS1(read_length)) 95 | print(f"[SPI] received {read_length} bytes from SPI bus on CS1: {ret[:16]}...(total: {len(ret)} bytes)", ) 96 | 97 | # write&read test (activate CS -> read data -> deactivate CS) 98 | random_bytes = b"\xa5\x5a\x5a\xa5" * 128 99 | print("[SPI] write read test result (with MOSI, MISO short connected): {}".format( 100 | bytes(spi.writeRead_CS1(random_bytes)) == random_bytes 101 | )) 102 | 103 | 104 | def uart_demo(): 105 | # while performing this test please make sure TX and RX pin short connected 106 | 107 | # initialize an uart communication object 108 | # -*- Way 1 -*- 109 | uart = UARTDevice(baudrate=7_500_000) 110 | 111 | # -*- Way 2 -*- (with no multithreading receiver) 112 | # uart = UARTDevice(baudrate=115200, stop_bits=1, verify_bits=0, timeout=128, multithreading=False) 113 | 114 | # uart write test 115 | test_b1 = b"hello world, this is ch347. " 116 | test_b2 = b"using CH347-HIDAPI" 117 | test_b3 = b"\xa5\x5a\x5a\xa5\x00\x01\x02\x03\xfc\xfd\xfe\xff\xa5\x5a\x5a\xa5" 118 | wrote = uart.write(test_b1) 119 | print("[UART] wrote {} bytes with content \"{}\"".format(wrote, test_b1.decode("utf-8"))) 120 | wrote = uart.write(test_b2) 121 | print("[UART] wrote {} bytes with content \"{}\"".format(wrote, test_b2.decode("utf-8"))) 122 | 123 | # uart read test 124 | # time.sleep(0.2) 125 | read = uart.read(len(test_b1 + test_b2)) 126 | print("[UART] read {} bytes of data test result: {}".format(len(read), bytes(read) == test_b1 + test_b2)) 127 | print("[UART] received: {}".format(bytes(read))) 128 | 129 | # uart accuracy test 130 | print("[UART] continuous sending and receiving test with 4MB size in progress..") 131 | payload = test_b3 * 64 * 1024 * 4 132 | t0 = time.time() 133 | uart.write(payload) 134 | read = uart.read(len(payload), timeout=15) 135 | print("[UART] 4MB payload received, time spent: {:.2f} ms, accuracy test result: {}".format( 136 | (time.time() - t0) * 1000, bytes(read) == payload)) 137 | 138 | # [VITAL] kill sub-thread(receiver thread) for safe exit 139 | uart.kill() 140 | 141 | 142 | if __name__ == "__main__": 143 | #i2c_demo() 144 | spi_demo() 145 | #uart_demo() 146 | -------------------------------------------------------------------------------- /ch347api/__spi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: hid 6 | # Created on: 2022/11/11 7 | 8 | 9 | class SPIClockFreq: 10 | f_60M = 0 11 | f_30M = 1 12 | f_15M = 2 13 | f_7M5 = 3 14 | f_3M75 = 4 15 | f_1M875 = 5 16 | f_937K5 = 6 17 | f_468K75 = 7 18 | 19 | 20 | class SPIConfig(list): 21 | 22 | def __init__(self): 23 | super(SPIConfig, self).__init__( 24 | b"\x00\x1d\x00\xc0\x1a\x00\x00\x00" 25 | b"\x04\x01\x00\x00\x02\x00\x01\x00" 26 | b"\x00\x02\x08\x00\x00\x00\x07\x00" 27 | b"\x00\x00\xff\x00\x00\x00\x00\x00" 28 | ) 29 | 30 | def as_bytes(self): 31 | return bytes(self) 32 | 33 | def set_mode(self, mode: int): 34 | """ 35 | set SPI mode 36 | :param mode: int, can only be 0, 1, 2, 3 37 | :return: None 38 | """ 39 | if mode == 0: 40 | self[12:16] = b"\x00\x00\x00\x00" 41 | elif mode == 1: 42 | self[12:16] = b"\x00\x00\x01\x00" 43 | elif mode == 2: 44 | self[12:16] = b"\x02\x00\x00\x00" 45 | elif mode == 3: 46 | self[12:16] = b"\x02\x00\x01\x00" 47 | else: 48 | raise Exception("SPI mode value needs to be one of 0-3") 49 | 50 | def set_clockSpeed(self, speed_ind: int): 51 | """ 52 | set SPI clock speed, 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz,7=468.75KHz 53 | :param speed_ind: int, 0, 1, 2, 3, 4, 5, 6, 7 54 | :return: None 55 | """ 56 | if speed_ind == 0: 57 | self[18] = 0x00 58 | elif speed_ind == 1: 59 | self[18] = 0x08 60 | elif speed_ind == 2: 61 | self[18] = 0x10 62 | elif speed_ind == 3: 63 | self[18] = 0x18 64 | elif speed_ind == 4: 65 | self[18] = 0x20 66 | elif speed_ind == 5: 67 | self[18] = 0x28 68 | elif speed_ind == 6: 69 | self[18] = 0x30 70 | elif speed_ind == 7: 71 | self[18] = 0x38 72 | else: 73 | raise Exception("SPI mode value needs to be one of 0-7") 74 | 75 | def set_MSB(self, is_msb: bool = False): 76 | """ 77 | set SPI MSB 78 | :param is_msb: bool 79 | :return: None 80 | """ 81 | if is_msb: 82 | self[20] = 0x00 83 | else: 84 | self[20] = 0x80 85 | 86 | def set_writeReadInterval(self, us: int): 87 | """ 88 | set SPI write read interval value 89 | :param us: int, 0-65535 90 | :return: None 91 | """ 92 | self[24:26] = us.to_bytes(2, "little", signed=False) 93 | 94 | def set_CS1Polar(self, high: bool = False): 95 | """ 96 | set SPI CS1 port polarity, high or low 97 | :param high: bool 98 | :return: None 99 | """ 100 | if high: 101 | self[27] |= 0x80 102 | else: 103 | self[27] &= 0x7f 104 | 105 | def set_CS2Polar(self, high: bool = False): 106 | """ 107 | set SPI CS2 port polarity, high or low 108 | :param high: bool 109 | :return: None 110 | """ 111 | if high: 112 | self[27] |= 0x40 113 | else: 114 | self[27] &= 0xbf 115 | 116 | def set_mode16bits(self, is_16bits: bool = True): 117 | """ 118 | set SPI 16 bits mode (default: 8bits) 119 | :param is_16bits: bool 120 | :return: None 121 | """ 122 | if is_16bits: 123 | self[11] = 0x08 124 | else: 125 | self[11] = 0x00 126 | 127 | 128 | class CSConfig(list): 129 | 130 | def __init__(self): 131 | super(CSConfig, self).__init__( 132 | b"\x00\x0d\x00\xc1\x0a\x00\xc0\x00" 133 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 134 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 135 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 136 | ) 137 | self.active_delay_us = 0 138 | self.deactive_delay_us = 0 139 | 140 | def as_bytes(self) -> bytes: 141 | """ 142 | export data list as bytes 143 | :return: bytes 144 | """ 145 | return bytes(self) 146 | 147 | def set_CS1Enable(self, enable: bool = True): 148 | """ 149 | set Chip-Select pin 1 enable 150 | :param enable: bool, default = True 151 | :return: None 152 | """ 153 | if enable: 154 | self[5:32] = b"\x00\x80" + \ 155 | self.active_delay_us.to_bytes(2, "little", signed=False) + \ 156 | self.deactive_delay_us.to_bytes(2, "little", signed=False) + \ 157 | b"\x00" * 5 158 | else: 159 | self[5:32] = b"\x00\xc0" + \ 160 | self.active_delay_us.to_bytes(2, "little", signed=False) + \ 161 | self.deactive_delay_us.to_bytes(2, "little", signed=False) + \ 162 | b"\x00" * 5 163 | 164 | def set_CS2Enable(self, enable: bool = True): 165 | """ 166 | set Chip-select pin 2 enable 167 | :param enable: bool, default = True 168 | :return: None 169 | """ 170 | if enable: 171 | self[5:32] = b"\x00" * 5 + b"\x00\x80" + \ 172 | self.active_delay_us.to_bytes(2, "little", signed=False) + \ 173 | self.deactive_delay_us.to_bytes(2, "little", signed=False) 174 | else: 175 | self[5:32] = b"\x00" * 5 + b"\x00\xc0" + \ 176 | self.active_delay_us.to_bytes(2, "little", signed=False) + \ 177 | self.deactive_delay_us.to_bytes(2, "little", signed=False) 178 | 179 | def set_activeDelay(self, us: int): 180 | """ 181 | set the delay time(us) from CS enabled to actually start transmitting 182 | :param us: int 183 | :return: None 184 | """ 185 | self.active_delay_us = us 186 | 187 | def set_deactivateDelay(self, us: int): 188 | """ 189 | set the delay time(us) before deactivate CS 190 | :param us: int 191 | :return: None 192 | """ 193 | self.deactive_delay_us = us 194 | -------------------------------------------------------------------------------- /ch347api/spi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347-HIDAPI 5 | # Filename: spi 6 | # Created on: 2024/1/6 7 | 8 | 9 | from .__device import CH347HIDDev 10 | from .__spi import SPIClockFreq 11 | 12 | 13 | class SPIDevice: 14 | 15 | def __init__(self, clock_freq_level: int = 1, is_MSB: bool = True, 16 | mode: int = 0, write_read_interval: int = 0, 17 | CS1_high: bool = False, CS2_high: bool = False, 18 | is_16bits: bool = False, 19 | ch347_device: CH347HIDDev = None): 20 | """ 21 | Encapsulated class of SPI device 22 | :param clock_freq_level: clock freq, 0=60M, 1=30M, 2=15M, 3=7.5M, 4=3.75M, 5=1.875M, 6=937.5K,7=468.75K 23 | :type clock_freq_level: int 24 | :param is_MSB: enable MSB mode 25 | :type is_MSB: bool 26 | :param mode: set SPI mode, can only be 0, 1, 2, 3 27 | :type mode: int 28 | :param write_read_interval: set SPI write read interval value 29 | :type write_read_interval: int 30 | :param CS1_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low 31 | :type CS1_high: bool 32 | :param CS2_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low 33 | :type CS2_high: bool 34 | :param is_16bits: set SPI 16-bit mode 35 | :type is_16bits: bool 36 | :param ch347_device: (defaults to None), will create a new CH347HIDDev if unset 37 | :type ch347_device: CH347HIDDev, optional 38 | """ 39 | if ch347_device is None: 40 | # create new CH347 HID device port if unset 41 | ch347_device = CH347HIDDev() 42 | 43 | self.dev = ch347_device 44 | if not self.dev.spi_initiated: 45 | self.dev.init_SPI(clock_freq_level=clock_freq_level, is_MSB=is_MSB, mode=mode, 46 | write_read_interval=write_read_interval, CS1_high=CS1_high, CS2_high=CS2_high, 47 | is_16bits=is_16bits) 48 | 49 | def write_CS1(self, data: (list, bytes), keep_cs_active: bool = False) -> int: 50 | """ 51 | Write data to SPI bus with CS1 activated 52 | :param data: max length up to 32768 bytes at one time 53 | :type data: :obj:`list`, :obj:`bytes` 54 | :param keep_cs_active: keep CS pin active after sending messages 55 | :type keep_cs_active: bool 56 | :return: length of sent data 57 | :rtype: int 58 | """ 59 | if self.dev.CS2_enabled: 60 | # deactivate CS2 if activated 61 | self.dev.set_CS2(False) 62 | 63 | if not self.dev.CS1_enabled: 64 | # activate CS1 if not 65 | self.dev.set_CS1(True) 66 | 67 | ret = self.dev.spi_write(bytes(data)) 68 | 69 | if not keep_cs_active: 70 | self.dev.set_CS1(False) 71 | 72 | return ret 73 | 74 | def read_CS1(self, length: int, keep_cs_active: bool = False) -> list: 75 | """ 76 | Read data from SPI bus with CS1 activated and return list of bytes received 77 | :param length: length of data to read 78 | :type length: int 79 | :param keep_cs_active: keep CS pin active after sending messages 80 | :type keep_cs_active: bool 81 | :return: list of bytes received 82 | :rtype: list 83 | """ 84 | if self.dev.CS2_enabled: 85 | # deactivate CS2 if activated 86 | self.dev.set_CS2(False) 87 | 88 | if not self.dev.CS1_enabled: 89 | # activate CS1 if not 90 | self.dev.set_CS1(True) 91 | 92 | ret = self.dev.spi_read(length) 93 | 94 | if not keep_cs_active: 95 | self.dev.set_CS1(False) 96 | 97 | return ret 98 | 99 | def writeRead_CS1(self, data: (list, bytes), keep_cs_active: bool = False) -> list: 100 | """ 101 | Write and read data through SPI bus with CS1 activated and return list of bytes 102 | :param data: data to write 103 | :type data: :obj:`list`, :obj:`bytes` 104 | :param keep_cs_active: keep CS1 pin active after sending messages 105 | :type keep_cs_active: bool 106 | :return: list of bytes received 107 | :rtype: list 108 | """ 109 | if self.dev.CS2_enabled: 110 | # deactivate CS2 if activated 111 | self.dev.set_CS2(False) 112 | 113 | if not self.dev.CS1_enabled: 114 | # activate CS1 if not 115 | self.dev.set_CS1(True) 116 | 117 | ret = self.dev.spi_read_write(data) 118 | 119 | if not keep_cs_active: 120 | self.dev.set_CS1(False) 121 | 122 | return ret 123 | 124 | def write_CS2(self, data: (list, bytes), keep_cs_active: bool = False) -> int: 125 | """ 126 | Write data to SPI bus with CS2 activated 127 | :param data: max length up to 32768 bytes at one time 128 | :type data: :obj:`list`, :obj:`bytes` 129 | :param keep_cs_active: bool, keep CS pin active after sending messages 130 | :type keep_cs_active: bool 131 | :return: length of sent data 132 | :rtype: int 133 | """ 134 | if self.dev.CS1_enabled: 135 | # deactivate CS1 if activated 136 | self.dev.set_CS1(False) 137 | 138 | if not self.dev.CS2_enabled: 139 | # activate CS2 if not 140 | self.dev.set_CS2(True) 141 | 142 | ret = self.dev.spi_write(bytes(data)) 143 | 144 | if not keep_cs_active: 145 | self.dev.set_CS2(False) 146 | 147 | return ret 148 | 149 | def read_CS2(self, length: int, keep_cs_active: bool = False) -> list: 150 | """ 151 | Read data from SPI bus with CS2 activated and return list of bytes received 152 | :param length: length of data to read 153 | :type length: intr sending messages 154 | :type keep_cs_active: bool 155 | :param keep_cs_active: keep CS pin active afte 156 | :return: list of bytes received 157 | :rtype: list 158 | """ 159 | if self.dev.CS1_enabled: 160 | # deactivate CS1 if activated 161 | self.dev.set_CS1(False) 162 | 163 | if not self.dev.CS2_enabled: 164 | # activate CS2 if not 165 | self.dev.set_CS2(True) 166 | 167 | ret = self.dev.spi_read(length) 168 | 169 | if not keep_cs_active: 170 | self.dev.set_CS2(False) 171 | 172 | return ret 173 | 174 | def writeRead_CS2(self, data: (list, bytes), keep_cs_active: bool = False) -> list: 175 | """ 176 | Write and read data through SPI bus with CS2 activated and return list of bytes 177 | :param data: data to write 178 | :type data: :obj:`list`, :obj:`bytes` 179 | :param keep_cs_active: keep CS1 pin active after sending messages 180 | :type keep_cs_active: bool 181 | :return: list of bytes received 182 | :rtype: list 183 | """ 184 | if self.dev.CS1_enabled: 185 | # deactivate CS1 if activated 186 | self.dev.set_CS1(False) 187 | 188 | if not self.dev.CS2_enabled: 189 | # activate CS2 if not 190 | self.dev.set_CS2(True) 191 | 192 | ret = self.dev.spi_read_write(data) 193 | 194 | if not keep_cs_active: 195 | self.dev.set_CS2(False) 196 | 197 | return ret 198 | -------------------------------------------------------------------------------- /ch347api/__device.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Author: i2cy(i2cy@outlook.com) 4 | # Project: CH347PythonLib 5 | # Filename: device 6 | # Created on: 2023/7/31 7 | import time 8 | 9 | import hid 10 | import struct 11 | from .__spi import CSConfig, SPIConfig 12 | from .__i2c import convert_i2c_address, convert_int_to_bytes 13 | from typing import Tuple, Any 14 | from functools import wraps 15 | import warnings 16 | 17 | VENDOR_ID: int = 6790 18 | PRODUCT_ID: int = 21980 19 | 20 | 21 | class CH347HIDUART1(hid.device): 22 | 23 | def __init__(self, vendor_id=VENDOR_ID, product_id=PRODUCT_ID): 24 | """ 25 | Class of CH347 UART1 interface based on hidapi 26 | :param vendor_id: the vender ID of the device 27 | :type vendor_id: int 28 | :param product_id: the product ID of the device 29 | :type product_id: int 30 | """ 31 | super(CH347HIDUART1, self).__init__() 32 | target = None 33 | for ele in hid.enumerate(): 34 | if ele["vendor_id"] == vendor_id and ele["product_id"] == product_id: 35 | if ele['interface_number'] == 0: # UART interface ID: 0 36 | target = ele['path'] 37 | 38 | self.open_path(target) 39 | 40 | def init_UART(self, baudrate: int = 115200, stop_bits: int = 1, verify_bits: int = 0, timeout: int = 32) -> bool: 41 | """ 42 | Initialize the configuration of the UART interface 43 | :param baudrate: the baudrate of the device, default is 115200 44 | :type baudrate: int 45 | :param stop_bits: number of stop bits, default is 1 46 | :type stop_bits: int 47 | :param verify_bits: number of verify bits, default is 0 48 | :type verify_bits: int 49 | :param timeout: timeout in milliseconds, default is 32, this value should not exceed the maximum of 255 50 | :type timeout: int 51 | :return: operation status 52 | :rtype: bool 53 | """ 54 | header = b"\x00\xcb\x08\x00" 55 | stop_bits = stop_bits * 2 - 2 56 | 57 | if baudrate < 1200 or baudrate > 7_500_000: 58 | raise ValueError("Invalid baudrate, correct value should ranging from 1200 to 7500000") 59 | 60 | if timeout > 255: 61 | raise Exception("timeout should not exceed the maximum number of 255") 62 | 63 | payload = header + struct.pack(" int: 70 | """ 71 | Write data to the device 72 | :param data: data to write 73 | :type data: bytes 74 | :return: wrote length 75 | :rtype: int 76 | """ 77 | offset = 0 78 | while len(data) - offset > 510: 79 | payload = struct.pack(" list: 89 | """ 90 | Read data from the device if any byte is available 91 | :param length: maximum length of the data, default is -1 which means read all bytes that received 92 | :type length: int 93 | :return: list of bytes read 94 | :rtype: list 95 | """ 96 | self.set_nonblocking(1) 97 | 98 | ret = [] 99 | while len(ret) < length or length < 0: 100 | chunk = self.read(512, timeout_ms=200) 101 | if chunk: 102 | chunk_len = struct.unpack(" bool: 155 | """ 156 | Check if device is busy 157 | :param timeout: time to wait for other thread to reset the busy flag 158 | :type timeout: int 159 | :return: busy status 160 | :rtype: bool 161 | """ 162 | t0 = time.time() 163 | while self.lock_enabled and self.is_busy and time.time() - t0 < timeout: 164 | time.sleep(0.001) 165 | 166 | return self.is_busy 167 | 168 | def __device_lock(timeout: int = 5): 169 | """ 170 | device lock decorator 171 | :param timeout: timeout to wait for device to unlock 172 | :type timeout: int 173 | :return: 174 | """ 175 | 176 | def aop(func): 177 | @wraps(func) 178 | def wrapper(self, *args, **kwargs): 179 | # check busy flag 180 | self.__busy_check(timeout) 181 | # set busy flag 182 | self.is_busy = True 183 | ret = func(self, *args, **kwargs) 184 | # unset busy flag 185 | self.is_busy = False 186 | return ret 187 | 188 | return wrapper 189 | 190 | return aop 191 | 192 | @__device_lock(2) 193 | def reset(self): 194 | """ 195 | reset device 196 | :return: 197 | """ 198 | self.write(b"\x00\x04\x00\xca\x01\x00\x01\x00") # reset device 199 | self.read(512, timeout_ms=200) 200 | 201 | # --*-- [ I2C ] --*-- 202 | @__device_lock(2) 203 | def init_I2C(self, clock_freq_level: int = 1): 204 | """ 205 | initialize I2C configuration 206 | :param clock_freq_level: 0-20KHz, 1-100KHz, 2-400KHz, 3-750KHz 207 | :type clock_freq_level: int 208 | :return: 209 | """ 210 | self.write(struct.pack(" bool: 215 | """ 216 | write data through I2C bus using 7-bits address 217 | :param addr: Any[int, bytes], 7-bits of device address 218 | :type addr: :obj:`int`, :obj:`bytes` 219 | :param data: one byte or several bytes of data to send (62 bytes max), 220 | this method will automatically convert it into bytes with byte order 'big' unsigned if data type is int, 221 | e.g. 0x00f1f2 -> b'\xf1\xf2' 222 | :type data: :obj:`int`, :obj:`bytes` 223 | :return: operation status 224 | :rtype: bool 225 | """ 226 | # convert address 227 | addr = convert_i2c_address(addr, read=False) 228 | 229 | # convert data 230 | data = convert_int_to_bytes(data) 231 | 232 | # assemble i2c frame 233 | payload = addr + data 234 | # send data through i2c stream 235 | status, feedback = self.__i2c_read_write_raw(payload) 236 | 237 | return status 238 | 239 | @__device_lock(2) 240 | def i2c_read(self, addr: (int, bytes), read_length: int, 241 | register_addr: (int, bytes) = None) -> Tuple[bool, bytes]: 242 | """ 243 | read byte(s) data from i2c bus with register address using 7-bits of device address 244 | :param addr: 7-bits of device address 245 | :type addr: :obj:`int`, :obj:`bytes` 246 | :param read_length: length of data to read from i2c bus 247 | :type read_length: int 248 | :param register_addr: Optional[int, bytes], one byte or several bytes of address of register, 249 | this method will automatically convert it into bytes with byte order 'big' unsigned if data type is int, 250 | e.g. 0x00f1f2 -> b'\xf1\xf2' 251 | :type register_addr: :obj:`int`, :obj:`bytes` (optional) 252 | :return: Tuple[operation_status bool, feedback bytes] 253 | :rtype: (bool, bytes) 254 | """ 255 | # convert address 256 | if register_addr is None: 257 | register_addr = b"" 258 | # convert address with reading signal 259 | addr = convert_i2c_address(addr, read=True) 260 | 261 | else: 262 | register_addr = convert_int_to_bytes(register_addr) 263 | # convert address with writing signal 264 | addr = convert_i2c_address(addr, read=False) 265 | 266 | # assemble payload 267 | payload = addr + register_addr 268 | 269 | # send and receive data from i2c bus 270 | status, feedback = self.__i2c_read_write_raw(payload, read_len=read_length) 271 | 272 | return status, feedback 273 | 274 | def i2c_exists(self, addr: int | bytes) -> bool: 275 | """ 276 | checks if device responds at given address (useful for I2C scanner) 277 | :param addr: 7-bits of device address 278 | :type addr: :obj:`int`, :obj:`bytes` 279 | :return: bool indicating device available 280 | :rtype: bool 281 | """ 282 | addr = convert_i2c_address(addr, read=False) 283 | 284 | _, feedback = self.__i2c_read_write_raw(addr) 285 | return len(feedback) == 1 and feedback[0] != 0x00 286 | 287 | 288 | def __i2c_read_write_raw(self, data: bytes, read_len: int = 0) -> Tuple[bool, bytes]: 289 | """ 290 | read and write i2c bus through I2CStream 291 | :param read_len: length of data to read (max 63B) 292 | :type read_len: int 293 | :param data: data to write 294 | :type data: bytes 295 | :return: tuple(, ) 296 | :rtype: (:obj:`bool`, :obj:`bytes`) 297 | """ 298 | if not self.i2c_initiated: 299 | raise Exception('I2C device initialization required') 300 | 301 | if read_len == 0: 302 | tail = b"\x75" 303 | elif read_len == 1: 304 | tail = b"\xc0\x75" 305 | if len(data) > 1: 306 | # Convert write address into read address 307 | tail = b"\x74\x81" + bytes([data[0] | 0x01]) + tail 308 | elif read_len < 64: 309 | tail = struct.pack(" 1: 311 | # Convert write address into read address 312 | tail = b"\x74\x81" + bytes([data[0] | 0x01]) + tail 313 | else: 314 | raise Exception("read length exceeded max size of 63 Bytes") 315 | payload = struct.pack("= 0: 388 | self.cs_activate_delay = active_delay_us 389 | if deactivate_delay_us >= 0: 390 | self.cs_deactivate_delay = deactivate_delay_us 391 | conf = CSConfig() 392 | conf.set_activeDelay(self.cs_activate_delay) 393 | conf.set_deactivateDelay(self.cs_deactivate_delay) 394 | conf.set_CS1Enable(enable) 395 | self.write(conf) 396 | if enable: 397 | self.CS1_enabled = True 398 | self.CS2_enabled = False 399 | else: 400 | self.CS1_enabled = False 401 | self.CS2_enabled = False 402 | 403 | @__device_lock(2) 404 | def set_CS2(self, enable: bool = True, active_delay_us: int = -1, 405 | deactivate_delay_us: int = -1): 406 | """ 407 | set CS2 enable/disable with delay settings 408 | :param enable: enable/disable CS2 409 | :type enable: bool 410 | :param active_delay_us: delay for CS2 (in μs) to activate 411 | :type active_delay_us: int 412 | :param deactivate_delay_us: delay for CS2 (in μs) to deactivate 413 | :type deactivate_delay_us: int 414 | :return: 415 | """ 416 | if enable and self.CS1_enabled: 417 | self.set_CS1(enable=False) 418 | if active_delay_us >= 0: 419 | self.cs_activate_delay = active_delay_us 420 | if deactivate_delay_us >= 0: 421 | self.cs_deactivate_delay = deactivate_delay_us 422 | conf = CSConfig() 423 | conf.set_activeDelay(self.cs_activate_delay) 424 | conf.set_deactivateDelay(self.cs_deactivate_delay) 425 | conf.set_CS2Enable(enable) 426 | self.write(conf) 427 | if enable: 428 | self.CS2_enabled = True 429 | self.CS1_enabled = False 430 | else: 431 | self.CS1_enabled = False 432 | self.CS2_enabled = False 433 | 434 | @__device_lock(2) 435 | def spi_write(self, data: bytes) -> int: 436 | """ 437 | write data to SPI devices 438 | :param data: max length up to 32768 Bytes 439 | :type data: bytes 440 | :return: int, length of sent data 441 | :rtype: int 442 | """ 443 | if not (self.CS1_enabled or self.CS2_enabled): 444 | raise Exception("no CS enabled yet") 445 | 446 | length = len(data) 447 | if length > 32768: 448 | # exceeded max package size 449 | raise Exception("package size {} exceeded max size of 32768 Bytes".format(length)) 450 | 451 | raw = struct.pack(" list: 459 | """ 460 | write data to SPI devices 461 | :param data: bytes, max length up to 32768 bytes 462 | :type data: bytes 463 | :return: list of bytes received from SPI device 464 | :rtype: list 465 | """ 466 | if not (self.CS1_enabled or self.CS2_enabled): 467 | raise Exception("no CS enabled yet") 468 | 469 | length = len(data) 470 | if length > 32768: 471 | # exceeded max package size 472 | raise Exception("package size {} exceeded max size of 32768 Bytes".format(length)) 473 | 474 | sent = 0 475 | while sent < length: 476 | left = length - sent 477 | if left >= 507: 478 | frame_payload_length = 507 479 | else: 480 | frame_payload_length = left 481 | raw = struct.pack(" list: 500 | """ 501 | read data from SPI device with given length 502 | :param length: length of data to read (no more than 32768) 503 | :type length: int 504 | :return: list of data received from SPI device 505 | :rtype: list 506 | """ 507 | if not (self.CS1_enabled or self.CS2_enabled): 508 | raise Exception("no CS enabled yet") 509 | 510 | if length > 32768: 511 | # exceeded max package size 512 | raise Exception("package size {} exceeded max size of 32768 Bytes".format(length)) 513 | 514 | raw = struct.pack("