diff --git a/.idea/misc.xml b/.idea/misc.xml index c2e4798..2fe0af5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + diff --git a/README.md b/README.md index 32b40d8..ac54abe 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This project is the API library of CH347 USB-SPI/I2C bridge chip based on Python This library provides full access of SPI/I2C settings and communication with CH347 USB-SPI bridge chip in Python language. -For demonstration and code reference please refer to the `test.py` file in [source page](https://github.com/i2cy/CH347-HIDAPI/blob/master/test.py). +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). [CH347-Chip Official Site](https://www.wch.cn/products/CH347.html) @@ -45,6 +45,10 @@ THUS, THIS API MAY NOT FULLY CAPABLE OF EVERY FUNCTION IN OFFICIAL API FROM CH34 ## Update Notes +#### 2024-01-08 + 1. Added independent I2C interface class objects (I2CDevice) and SPI interface class objects (SPIDevice) + 2. Added new demo file `demo.py` to demonstrate the usage of classes added above (simplified code) + #### 2023-08-06 1. Now with fully compatible I2C support, I2C clock speed level: 0 -> 20KHz, 1 -> 100KHz, 2 -> 400KHz, 3 -> 750KHz 2. Added test.py for demonstration diff --git a/ch347api/__device.py b/ch347api/__device.py index 5b9da62..042e99c 100644 --- a/ch347api/__device.py +++ b/ch347api/__device.py @@ -4,13 +4,14 @@ # Project: CH347PythonLib # Filename: device # Created on: 2023/7/31 - +import time import hid import struct from .__spi import CSConfig, SPIConfig from .__i2c import convert_i2c_address, convert_int_to_bytes from typing import Tuple, Any +from functools import wraps VENDOR_ID: int = 6790 PRODUCT_ID: int = 21980 @@ -18,12 +19,18 @@ class CH347HIDDev(hid.device): - def __init__(self, vendor_id, product_id, interface_num): + def __init__(self, vendor_id=VENDOR_ID, product_id=PRODUCT_ID, interface_num=1, + enable_device_lock=True): """ Class of CH347 device based on hidapi - :param vendor_id: int - :param product_id: int - :param interface_num: int + :param vendor_id: the vendor ID of the device + :type vendor_id: int + :param product_id: the product ID of the device + :type product_id: int + :param interface_num: the interface number of the device + :type interface_num: int + :param enable_device_lock: whether to enable device in case of multithreading communication + :type enable_device_lock: bool """ super(CH347HIDDev, self).__init__() target = None @@ -31,6 +38,7 @@ def __init__(self, vendor_id, product_id, interface_num): if ele["vendor_id"] == vendor_id and ele["product_id"] == product_id: if ele['interface_number'] == interface_num: target = ele['path'] + self.open_path(target) self.CS1_enabled = False @@ -38,7 +46,49 @@ def __init__(self, vendor_id, product_id, interface_num): self.cs_activate_delay = 0 self.cs_deactivate_delay = 0 self.i2c_initiated = False + self.spi_initiated = False + self.lock_enabled = enable_device_lock + self.is_busy = False + + def __busy_check(self, timeout=5) -> bool: + """ + Check if device is busy + :param timeout: time to wait for other thread to reset the busy flag + :type timeout: int + :return: busy status + :rtype: bool + """ + t0 = time.time() + while self.lock_enabled and self.is_busy and time.time() - t0 < timeout: + time.sleep(0.001) + + return self.is_busy + + def __device_lock(timeout: int = 5): + """ + device lock decorator + :param timeout: timeout to wait for device to unlock + :type timeout: int + :return: + """ + + def aop(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + # check busy flag + self.__busy_check(timeout) + # set busy flag + self.is_busy = True + ret = func(self, *args, **kwargs) + # unset busy flag + self.is_busy = False + return ret + + return wrapper + return aop + + @__device_lock(2) def reset(self): """ reset device @@ -48,23 +98,29 @@ def reset(self): self.read(64) # --*-- [ I2C ] --*-- - def init_I2C(self, clock_speed_level: int = 1): + @__device_lock(2) + def init_I2C(self, clock_freq_level: int = 1): """ initialize I2C configuration - :param clock_speed_level: int, 0-20KHz, 1-100KHz, 2-400KHz, 3-750KHz - :return: None + :param clock_freq_level: 0-20KHz, 1-100KHz, 2-400KHz, 3-750KHz + :type clock_freq_level: int + :return: """ - self.write(struct.pack(" bool: """ write data through I2C bus using 7-bits address :param addr: Any[int, bytes], 7-bits of device address - :param data: Any[int, bytes], one byte or several bytes of data to send (62 bytes max), + :type addr: :obj:`int`, :obj:`bytes` + :param data: one byte or several bytes of data to send (62 bytes max), this method will automatically convert it into bytes with byte order 'big' unsigned if data type is int, e.g. 0x00f1f2 -> b'\xf1\xf2' - :return: bool, operation status + :type data: :obj:`int`, :obj:`bytes` + :return: operation status + :rtype: bool """ # convert address addr = convert_i2c_address(addr, read=False) @@ -75,20 +131,25 @@ def i2c_write(self, addr: (int, bytes), data: (int, bytes)) -> bool: # assemble i2c frame payload = addr + data # send data through i2c stream - status, feedback = self.i2c_read_write_raw(payload) + status, feedback = self.__i2c_read_write_raw(payload) return status + @__device_lock(2) def i2c_read(self, addr: (int, bytes), read_length: int, register_addr: (int, bytes) = None) -> Tuple[bool, bytes]: """ read byte(s) data from i2c bus with register address using 7-bits of device address - :param addr: Any[int, bytes], 7-bits of device address - :param read_length: int, length + :param addr: 7-bits of device address + :type addr: :obj:`int`, :obj:`bytes` + :param read_length: length of data to read from i2c bus + :type read_length: int :param register_addr: Optional[int, bytes], one byte or several bytes of address of register, this method will automatically convert it into bytes with byte order 'big' unsigned if data type is int, e.g. 0x00f1f2 -> b'\xf1\xf2' + :type register_addr: :obj:`int`, :obj:`bytes` (optional) :return: Tuple[operation_status bool, feedback bytes] + :rtype: (bool, bytes) """ # convert address if register_addr is None: @@ -105,16 +166,19 @@ def i2c_read(self, addr: (int, bytes), read_length: int, payload = addr + register_addr # send and receive data from i2c bus - status, feedback = self.i2c_read_write_raw(payload, read_len=read_length) + status, feedback = self.__i2c_read_write_raw(payload, read_len=read_length) return status, feedback - def i2c_read_write_raw(self, data: bytes, read_len: int = 0) -> Tuple[bool, bytes]: + def __i2c_read_write_raw(self, data: bytes, read_len: int = 0) -> Tuple[bool, bytes]: """ read and write i2c bus through I2CStream - :param read_len: int, length of data to read (max 63B) - :param data: bytes + :param read_len: length of data to read (max 63B) + :type read_len: int + :param data: data to write + :type data: bytes :return: tuple(, ) + :rtype: (:obj:`bool`, :obj:`bytes`) """ if not self.i2c_initiated: raise Exception('I2C device initialization required') @@ -150,39 +214,51 @@ def i2c_read_write_raw(self, data: bytes, read_len: int = 0) -> Tuple[bool, byte return sum(ack_signals) == len(ack_signals), payload # --*-- [ SPI ] --*-- - def init_SPI(self, clock_speed_level: int = 1, is_MSB: bool = True, - mode: int = 3, write_read_interval: int = 0, + @__device_lock(2) + def init_SPI(self, clock_freq_level: int = 1, is_MSB: bool = True, + mode: int = 0, write_read_interval: int = 0, CS1_high: bool = False, CS2_high: bool = False): """ initialize SPI configuration - :param clock_speed_level: int, 0-7 - :param is_MSB: bool + :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 + :type clock_freq_level: int + :param is_MSB: enable MSB mode + :type is_MSB: bool :param mode: int, 0-3 - :param write_read_interval: int, 0-65535 - :param CS1_high: bool - :param CS2_high: bool + :type mode: int + :param write_read_interval: 0-65535 + :type write_read_interval: int + :param CS1_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low + :type CS1_high: bool + :param CS2_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low + :type CS2_high: bool :return: """ self.CS1_enabled = False self.CS2_enabled = False conf = SPIConfig() conf.set_mode(mode) - conf.set_clockSpeed(clock_speed_level) + conf.set_clockSpeed(clock_freq_level) conf.set_MSB(is_MSB) conf.set_writeReadInterval(write_read_interval) conf.set_CS1Polar(CS1_high) conf.set_CS2Polar(CS2_high) self.write(conf) self.read(64) + self.spi_initiated = True + @__device_lock(2) def set_CS1(self, enable: bool = True, active_delay_us: int = -1, deactivate_delay_us: int = -1): """ set CS1 enable/disable with delay settings - :param enable: bool - :param active_delay_us: int - :param deactivate_delay_us: int + :param enable: enable/disable CS1 + :type enable: bool + :param active_delay_us: delay for CS1 (in μs) to activate + :type active_delay_us: int + :param deactivate_delay_us: delay for CS1 (in ms) to deactivate + :type deactivate_delay_us: int :return: """ if enable and self.CS2_enabled: @@ -203,13 +279,17 @@ def set_CS1(self, enable: bool = True, active_delay_us: int = -1, self.CS1_enabled = False self.CS2_enabled = False + @__device_lock(2) def set_CS2(self, enable: bool = True, active_delay_us: int = -1, deactivate_delay_us: int = -1): """ set CS2 enable/disable with delay settings - :param enable: bool - :param active_delay_us: int - :param deactivate_delay_us: int + :param enable: enable/disable CS2 + :type enable: bool + :param active_delay_us: delay for CS2 (in μs) to activate + :type active_delay_us: int + :param deactivate_delay_us: delay for CS2 (in μs) to deactivate + :type deactivate_delay_us: int :return: """ if enable and self.CS1_enabled: @@ -230,11 +310,14 @@ def set_CS2(self, enable: bool = True, active_delay_us: int = -1, self.CS1_enabled = False self.CS2_enabled = False + @__device_lock(2) def spi_write(self, data: bytes) -> int: """ write data to SPI devices - :param data: bytes, max length up to 32768 bytes + :param data: max length up to 32768 Bytes + :type data: bytes :return: int, length of sent data + :rtype: int """ if not (self.CS1_enabled or self.CS2_enabled): raise Exception("no CS enabled yet") @@ -250,11 +333,14 @@ def spi_write(self, data: bytes) -> int: return length + @__device_lock(2) def spi_read_write(self, data: bytes) -> list: """ write data to SPI devices :param data: bytes, max length up to 32768 bytes - :return: int, length of sent data + :type data: bytes + :return: list of bytes received from SPI device + :rtype: list """ if not (self.CS1_enabled or self.CS2_enabled): raise Exception("no CS enabled yet") @@ -284,11 +370,14 @@ def spi_read_write(self, data: bytes) -> list: return ret + @__device_lock(2) def spi_read(self, length) -> list: """ read data from SPI device with given length - :param length: int - :return: + :param length: length of data to read + :type length: int + :return: list of data received from SPI device + :rtype: list """ if not (self.CS1_enabled or self.CS2_enabled): raise Exception("no CS enabled yet") diff --git a/ch347api/__i2c.py b/ch347api/__i2c.py index 736585c..66dbbc3 100644 --- a/ch347api/__i2c.py +++ b/ch347api/__i2c.py @@ -10,6 +10,13 @@ import struct +class I2CClockFreq: + f_20K = 0 + f_100K = 1 + f_400K = 2 + f_750K = 3 + + def convert_i2c_address(addr: (int, bytes), read: bool = False) -> bytes: """ static method for address conversion diff --git a/ch347api/__init__.py b/ch347api/__init__.py index 90507dc..fdf78ae 100644 --- a/ch347api/__init__.py +++ b/ch347api/__init__.py @@ -6,4 +6,7 @@ # Created on: 2022/11/11 from .__device import CH347HIDDev, VENDOR_ID, PRODUCT_ID - +from .i2c import I2CDevice +from .spi import SPIDevice +from .__spi import SPIClockFreq +from .__i2c import I2CClockFreq diff --git a/ch347api/__spi.py b/ch347api/__spi.py index b04ddd7..2b446bd 100644 --- a/ch347api/__spi.py +++ b/ch347api/__spi.py @@ -6,6 +6,17 @@ # Created on: 2022/11/11 +class SPIClockFreq: + f_60M = 0 + f_30M = 1 + f_15M = 2 + f_7M5 = 3 + f_3M75 = 4 + f_1M875 = 5 + f_937K5 = 6 + f_468K75 = 7 + + class SPIConfig(list): def __init__(self): diff --git a/ch347api/i2c.py b/ch347api/i2c.py new file mode 100644 index 0000000..3a449db --- /dev/null +++ b/ch347api/i2c.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Author: i2cy(i2cy@outlook.com) +# Project: CH347-HIDAPI +# Filename: i2c +# Created on: 2024/1/6 + + +from .__device import CH347HIDDev +from .__i2c import convert_int_to_bytes +from typing import Tuple, Any + + +class I2CDevice: + + def __init__(self, addr: int, clock_freq_level: int = 1, ch347_device: CH347HIDDev = None): + """ + Encapsulated class of I2C device + :param addr: I2C device address + :type addr: int + :param clock_freq_level: Clock frequency, 0-20KHz, 1-100KHz, 2-400KHz, 3-750KHz + :type clock_freq_level: int, optional + :param ch347_device: CH347HIDDev (defaults to None), will create a new CH347HIDDev if unset + :type ch347_device: CH347HIDDev, optional + """ + if ch347_device is None: + # create new CH347 HID device port if unset + ch347_device = CH347HIDDev() + + self.dev = ch347_device + self.addr = addr + + if not self.dev.i2c_initiated: + # initialize i2c device if doesn't + self.dev.init_I2C() + + def write(self, reg: (int, bytes) = None, data: (int, bytes) = None) -> bool: + """ + Write data to the device through I2C bus + :param reg: address of the register, or None for direct transmission + :type reg: :obj:`int`, :obj:`bytes` + :param data: data to send, or None for write probing + :type data: :obj:`int`, :obj:`bytes` + :return: operation status + """ + payload = b"" + if reg is not None: + payload += convert_int_to_bytes(reg) + + if data is not None: + payload += convert_int_to_bytes(data) + + return self.dev.i2c_write(self.addr, data=payload) + + def read(self, reg: (int, bytes) = None, length: int = 0): + """ + Read data from the device through I2C bus + :param reg: address of the register, or None for direct transmission + :type reg: :obj:`int`, :obj:`bytes` + :param length: number of bytes to read, default is 0 for read probing + :type length: int + :return: return bytes when length is greater than 0, or bool status if length is 0 + :rtype: :obj:`bytes`, :obj:`bool` + """ + status, feedback = self.dev.i2c_read(addr=self.addr, read_length=length, register_addr=reg) + + if length: + return feedback + else: + return status diff --git a/ch347api/spi.py b/ch347api/spi.py new file mode 100644 index 0000000..7b81cff --- /dev/null +++ b/ch347api/spi.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Author: i2cy(i2cy@outlook.com) +# Project: CH347-HIDAPI +# Filename: spi +# Created on: 2024/1/6 + + +from .__device import CH347HIDDev +from .__spi import SPIClockFreq + + +class SPIDevice: + + def __init__(self, clock_freq_level: int = 1, is_MSB: bool = True, + mode: int = 0, write_read_interval: int = 0, + CS1_high: bool = False, CS2_high: bool = False, + ch347_device: CH347HIDDev = None): + """ + Encapsulated class of SPI device + :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 + :type clock_freq_level: int + :param is_MSB: enable MSB mode + :type is_MSB: bool + :param mode: set SPI mode, can only be 0, 1, 2, 3 + :type mode: int + :param write_read_interval: set SPI write read interval value + :type write_read_interval: int + :param CS1_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low + :type CS1_high: bool + :param CS2_high: set SPI CS1 port polarity, True=Active-High, False=Active-Low + :type CS2_high: bool + :param ch347_device: (defaults to None), will create a new CH347HIDDev if unset + :type ch347_device: CH347HIDDev, optional + """ + if ch347_device is None: + # create new CH347 HID device port if unset + ch347_device = CH347HIDDev() + + self.dev = ch347_device + if not self.dev.spi_initiated: + self.dev.init_SPI(clock_freq_level=clock_freq_level, is_MSB=is_MSB, mode=mode, + write_read_interval=write_read_interval, CS1_high=CS1_high, CS2_high=CS2_high) + + def write_CS1(self, data: (list, bytes), keep_cs_active: bool = False) -> int: + """ + Write data to SPI bus with CS1 activated + :param data: max length up to 32768 bytes at one time + :type data: :obj:`list`, :obj:`bytes` + :param keep_cs_active: keep CS pin active after sending messages + :type keep_cs_active: bool + :return: length of sent data + :rtype: int + """ + if self.dev.CS2_enabled: + # deactivate CS2 if activated + self.dev.set_CS2(False) + + if not self.dev.CS1_enabled: + # activate CS1 if not + self.dev.set_CS1(True) + + ret = self.dev.spi_write(bytes(data)) + + if not keep_cs_active: + self.dev.set_CS1(False) + + return ret + + def read_CS1(self, length: int, keep_cs_active: bool = False) -> list: + """ + Read data from SPI bus with CS1 activated and return list of bytes received + :param length: length of data to read + :type length: int + :param keep_cs_active: keep CS pin active after sending messages + :type keep_cs_active: bool + :return: list of bytes received + :rtype: list + """ + if self.dev.CS2_enabled: + # deactivate CS2 if activated + self.dev.set_CS2(False) + + if not self.dev.CS1_enabled: + # activate CS1 if not + self.dev.set_CS1(True) + + ret = self.dev.spi_read(length) + + if not keep_cs_active: + self.dev.set_CS1(False) + + return ret + + def writeRead_CS1(self, data: (list, bytes), keep_cs_active: bool = False) -> list: + """ + Write and read data through SPI bus with CS1 activated and return list of bytes + :param data: data to write + :type data: :obj:`list`, :obj:`bytes` + :param keep_cs_active: keep CS1 pin active after sending messages + :type keep_cs_active: bool + :return: list of bytes received + :rtype: list + """ + if self.dev.CS2_enabled: + # deactivate CS2 if activated + self.dev.set_CS2(False) + + if not self.dev.CS1_enabled: + # activate CS1 if not + self.dev.set_CS1(True) + + ret = self.dev.spi_read_write(data) + + if not keep_cs_active: + self.dev.set_CS1(False) + + return ret + + def write_CS2(self, data: (list, bytes), keep_cs_active: bool = False) -> int: + """ + Write data to SPI bus with CS2 activated + :param data: max length up to 32768 bytes at one time + :type data: :obj:`list`, :obj:`bytes` + :param keep_cs_active: bool, keep CS pin active after sending messages + :type keep_cs_active: bool + :return: length of sent data + :rtype: int + """ + if self.dev.CS1_enabled: + # deactivate CS1 if activated + self.dev.set_CS1(False) + + if not self.dev.CS2_enabled: + # activate CS2 if not + self.dev.set_CS2(True) + + ret = self.dev.spi_write(bytes(data)) + + if not keep_cs_active: + self.dev.set_CS2(False) + + return ret + + def read_CS2(self, length: int, keep_cs_active: bool = False) -> list: + """ + Read data from SPI bus with CS2 activated and return list of bytes received + :param length: length of data to read + :type length: intr sending messages + :type keep_cs_active: bool + :param keep_cs_active: keep CS pin active afte + :return: list of bytes received + :rtype: list + """ + if self.dev.CS1_enabled: + # deactivate CS1 if activated + self.dev.set_CS1(False) + + if not self.dev.CS2_enabled: + # activate CS2 if not + self.dev.set_CS2(True) + + ret = self.dev.spi_read(length) + + if not keep_cs_active: + self.dev.set_CS2(False) + + return ret + + def writeRead_CS2(self, data: (list, bytes), keep_cs_active: bool = False) -> list: + """ + Write and read data through SPI bus with CS2 activated and return list of bytes + :param data: data to write + :type data: :obj:`list`, :obj:`bytes` + :param keep_cs_active: keep CS1 pin active after sending messages + :type keep_cs_active: bool + :return: list of bytes received + :rtype: list + """ + if self.dev.CS1_enabled: + # deactivate CS1 if activated + self.dev.set_CS1(False) + + if not self.dev.CS2_enabled: + # activate CS2 if not + self.dev.set_CS2(True) + + ret = self.dev.spi_read_write(data) + + if not keep_cs_active: + self.dev.set_CS2(False) + + return ret diff --git a/ch347api/uart.py b/ch347api/uart.py new file mode 100644 index 0000000..42a1a69 --- /dev/null +++ b/ch347api/uart.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Author: i2cy(i2cy@outlook.com) +# Project: CH347-HIDAPI +# Filename: uart +# Created on: 2024/1/8 diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..1aa090d --- /dev/null +++ b/demo.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Author: i2cy(i2cy@outlook.com) +# Project: CH347-HIDAPI +# Filename: demo_2 +# Created on: 2024/1/7 + + +import random +import time +from ch347api import CH347HIDDev, I2CDevice, SPIDevice, SPIClockFreq, I2CClockFreq + + +def i2c_demo(): + # initialize an i2c communication object (MPU6050 I2C address: 0x68) + # -*- Way 1 -*- + i2c = I2CDevice(addr=0x68) + + # -*- Way 2 -*- + # i2c = I2CDevice(addr=0x68, clock_freq_level=I2CClockFreq.f_100K) + + # -*- Way 3 -*- (using one device object to make thread safe) + # dev = CH347HIDDev() + # i2c_1 = I2CDevice(addr=0x68, ch347_device=dev) + # i2c_2 = I2CDevice(addr=0x23, ch347_device=dev) + # i2c_3 = I2CDevice(addr=0x22, ch347_device=dev) + + # read MPU6050 factory data + d = i2c.read(0x75, 1) + print("[I2C] read from MPU6050 register 0x75 (should be 0x68): 0x{}".format(d.hex())) + + # reset MPU6050 + status = i2c.write(0x6b, 0x80) + print("[I2C] write to MPU6050 register 0x6B with data 0x80 to reset the device, status: {}".format(status)) + time.sleep(0.1) + + # setting up MPU6050 + status = i2c.write(0x6b, 0x01) + print("[I2C] write to MPU6050 register 0x6B with data 0x01, status: {}".format(status)) + status = i2c.write(0x6c, 0x00) + print("[I2C] write to MPU6050 register 0x6C with data 0x00, status: {}".format(status)) + status = i2c.write(0x19, 0x00) + print("[I2C] write to MPU6050 register 0x19 with data 0x00, status: {}".format(status)) + status = i2c.write(0x1a, 0x02) + print("[I2C] write to MPU6050 register 0x1a with data 0x02, status: {}".format(status)) + status = i2c.write(0x1c, 0x08) + print("[I2C] write to MPU6050 register 0x1c with data 0x08, status: {}".format(status)) + + +def spi_demo(): + # initialize a spi communication object + # -*- Way 1 -*- + # spi = SPIDevice() + + # -*- Way 2 -*-: + spi = SPIDevice(clock_freq_level=SPIClockFreq.f_30M) + + # -*- Way 3 -*- (using one device object to make thread safe): + # dev = CH347HIDDev() + # spi = SPIDevice(ch347_device=dev) + # i2c = I2CDevice(addr=0x68, ch347_device=dev) + + # write test (activate CS -> write data -> deactivate CS) + print("performing SPI write test") + spi.write_CS1(b"hello world") + spi.write_CS2(b"this is ch347") + spi.write_CS1([0, 1, 2, 3]) + spi.write_CS2([252, 253, 254, 255]) + + # write test (activate CS -> write data -> write data -> deactivate CS) + spi.write_CS1(b"hello world", keep_cs_active=True) + spi.write_CS1(b"this is ch347") + + # read test (activate CS -> read data -> deactivate CS) + print("performing SPI read test") + print("received 16 bytes from SPI bus on CS1:", bytes(spi.read_CS1(16))) + + # write&read test (activate CS -> read data -> deactivate CS) + random_bytes = random.randbytes(512) + print("write read test result (with MOSI, MISO short connected): {}".format( + bytes(spi.writeRead_CS1(random_bytes)) == random_bytes + )) + + +if __name__ == "__main__": + i2c_demo() + spi_demo()