├── .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 |
4 |
5 |
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 |
5 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/CH347PythonLib.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
14 |
15 |
16 |
17 |
18 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
133 |
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("