From 4732398e07122549b26125b0b3c47c7a262c8ebf Mon Sep 17 00:00:00 2001 From: ChisBread Date: Tue, 12 Nov 2024 22:03:52 +0800 Subject: [PATCH 01/18] Add Bacon Flasher --- .gitignore | 2 + FlashGBX/FlashGBX_CLI.py | 4 +- FlashGBX/FlashGBX_GUI.py | 4 +- FlashGBX/LK_Device.py | 9 +- FlashGBX/bacon/.gitignore | 2 + FlashGBX/bacon/__init__.py | 55 +++ FlashGBX/bacon/bacon.py | 384 ++++++++++++++++ FlashGBX/bacon/ch347.py | 665 +++++++++++++++++++++++++++ FlashGBX/bacon/command.py | 290 ++++++++++++ FlashGBX/bacon/include/CH347DLL.H | 613 ++++++++++++++++++++++++ FlashGBX/bacon/include/CH347DLL_EN.H | 610 ++++++++++++++++++++++++ FlashGBX/bacon/lib/CH347DLL.DLL | Bin 0 -> 71680 bytes FlashGBX/bacon/lib/CH347DLLA64.DLL | Bin 0 -> 103928 bytes FlashGBX/bacon/serial.py | 356 ++++++++++++++ FlashGBX/bacon/test.py | 218 +++++++++ FlashGBX/hw_Bacon.py | 166 +++++++ setup.py | 2 +- 17 files changed, 3373 insertions(+), 7 deletions(-) create mode 100644 .gitignore create mode 100644 FlashGBX/bacon/.gitignore create mode 100644 FlashGBX/bacon/__init__.py create mode 100644 FlashGBX/bacon/bacon.py create mode 100644 FlashGBX/bacon/ch347.py create mode 100644 FlashGBX/bacon/command.py create mode 100644 FlashGBX/bacon/include/CH347DLL.H create mode 100644 FlashGBX/bacon/include/CH347DLL_EN.H create mode 100644 FlashGBX/bacon/lib/CH347DLL.DLL create mode 100644 FlashGBX/bacon/lib/CH347DLLA64.DLL create mode 100644 FlashGBX/bacon/serial.py create mode 100644 FlashGBX/bacon/test.py create mode 100644 FlashGBX/hw_Bacon.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d90124 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/__pycache__/ +FlashGBX/config/*bak \ No newline at end of file diff --git a/FlashGBX/FlashGBX_CLI.py b/FlashGBX/FlashGBX_CLI.py index 3c0c4b9..bfb87d2 100644 --- a/FlashGBX/FlashGBX_CLI.py +++ b/FlashGBX/FlashGBX_CLI.py @@ -16,8 +16,8 @@ from .PocketCamera import PocketCamera from .Util import APPNAME, ANSI from . import Util -from . import hw_GBxCartRW, hw_GBFlash, hw_JoeyJr -hw_devices = [hw_GBxCartRW, hw_GBFlash, hw_JoeyJr] +from . import hw_Bacon, hw_GBxCartRW, hw_GBFlash, hw_JoeyJr +hw_devices = [hw_Bacon, hw_GBxCartRW, hw_GBFlash, hw_JoeyJr] class FlashGBX_CLI(): ARGS = {} diff --git a/FlashGBX/FlashGBX_GUI.py b/FlashGBX/FlashGBX_GUI.py index 003953e..ce51fb6 100644 --- a/FlashGBX/FlashGBX_GUI.py +++ b/FlashGBX/FlashGBX_GUI.py @@ -12,8 +12,8 @@ from .UserInputDialog import UserInputDialog from .Util import APPNAME, VERSION, VERSION_PEP440 from . import Util -from . import hw_GBxCartRW, hw_GBFlash, hw_JoeyJr -hw_devices = [hw_GBxCartRW, hw_GBFlash, hw_JoeyJr] +from . import hw_Bacon, hw_GBxCartRW, hw_GBFlash, hw_JoeyJr +hw_devices = [hw_Bacon, hw_GBxCartRW, hw_GBFlash, hw_JoeyJr] class FlashGBX_GUI(QtWidgets.QWidget): CONN = None diff --git a/FlashGBX/LK_Device.py b/FlashGBX/LK_Device.py index 5473304..2b34866 100644 --- a/FlashGBX/LK_Device.py +++ b/FlashGBX/LK_Device.py @@ -626,7 +626,7 @@ def _cart_write_flash(self, commands, flashcart=False): buffer.extend(struct.pack("B", 1 if flashcart else 0)) buffer.extend(struct.pack("B", num)) for i in range(0, num): - dprint("Writing to cartridge: 0x{:X} = 0x{:X} ({:d} of {:d})".format(commands[i][0], commands[i][1], i+1, num)) + dprint("Writing to cartridge: 0x{:X} = 0x{:X} ({:d} of {:d}) flashcart={:s}".format(commands[i][0], commands[i][1], i+1, num, str(flashcart))) if self.MODE == "AGB" and flashcart: buffer.extend(struct.pack(">I", commands[i][0] >> 1)) else: @@ -919,6 +919,7 @@ def ReadInfo(self, setPinsAsInputs=False, checkRtc=True): print("{:s}Error: No mode was set.{:s}".format(ANSI.RED, ANSI.RESET)) return False + dprint("Reading ROM header") header = self.ReadROM(0, 0x180) if ".dev" in Util.VERSION_PEP440 or Util.DEBUG: @@ -1424,6 +1425,8 @@ def ReadFlashSaveID(self): return (agb_flash_chip, agb_flash_chip_name) def ReadROM(self, address, length, skip_init=False, max_length=64): + if self.DEVICE_NAME == "Bacon" and max_length < 0x10000: + max_length = 0x10000 num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge ROM at 0x{:X} in {:d} iteration(s)".format(length, address, num)) if length > max_length: length = max_length @@ -1491,6 +1494,8 @@ def ReadROM_GBAMP(self, address, length, max_length=64): return self.ReadROM(address=addr, length=length, max_length=max_length) def ReadRAM(self, address, length, command=None, max_length=64): + if self.DEVICE_NAME == "Bacon" and max_length < 0x10000: + max_length = 0x10000 num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge RAM in {:d} iteration(s)".format(length, num)) if length > max_length: length = max_length @@ -1703,7 +1708,7 @@ def WriteRAM_TAMA5(self, buffer): def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False, max_length=MAX_BUFFER_WRITE): length = len(buffer) num = math.ceil(length / max_length) - dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s)".format(length, num)) + dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s) flash_buffer_size=0x{:X} skip_init={:s}".format(length, num, flash_buffer_size, str(skip_init))) if length == 0: dprint("Length is zero?") return False diff --git a/FlashGBX/bacon/.gitignore b/FlashGBX/bacon/.gitignore new file mode 100644 index 0000000..c8fdf1b --- /dev/null +++ b/FlashGBX/bacon/.gitignore @@ -0,0 +1,2 @@ +**/__pycache__/ +gba*.bin \ No newline at end of file diff --git a/FlashGBX/bacon/__init__.py b/FlashGBX/bacon/__init__.py new file mode 100644 index 0000000..202c16d --- /dev/null +++ b/FlashGBX/bacon/__init__.py @@ -0,0 +1,55 @@ +""" +CH347 Module +------------ + +The `ch347` module provides functions for interacting with the CH347 USB-UART/SPI/I2C/JTAG bridge. + +Initialization: +---------------- + +- Use `ch347.init()` to initialize the CH347 device. + +Configuration: +-------------- + +- Use `ch347.set_spi_config(clock: int, mode: int)` to configure the SPI parameters. + +SPI Communication: +------------------ + +- Use `ch347.spi_write(device_index: int, chip_select: int, write_data: bytes, write_step: int)` + to write data to the SPI bus. + +- Use `ch347.spi_read(device_index: int, chip_select: int, write_data: bytes, read_length: int)` + to read data from the SPI bus. + +Closing: +-------- + +- Use `ch347.close()` to close the CH347 device. + +Usage Example: +-------------- + +import ch347 + +# Initialize the CH347 device +ch347.init() + +# Configure the SPI parameters +ch347.set_spi_config(clock=1000000, mode=0) + +# Write data to the SPI bus +ch347.spi_write(device_index=0, chip_select=1, write_data=b'\x01\x02\x03', write_step=3) + +# Read data from the SPI bus +data = ch347.spi_read(device_index=0, chip_select=1, write_data=b'\x00', read_length=3) + +# Close the CH347 device +ch347.close() +""" + +from .ch347 import * +from .bacon import * +from .command import * +from .serial import * \ No newline at end of file diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py new file mode 100644 index 0000000..3ff81e3 --- /dev/null +++ b/FlashGBX/bacon/bacon.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +# bacon +# Author: ChisBread (github.com/ChisBread) + +import platform +import random +import time +import os + +from .command import * +#HIDAPI +from ch347api import CH347HIDDev, I2CDevice, SPIDevice, UARTDevice, SPIClockFreq, I2CClockFreq +#WCHAPI +from .ch347 import SPIConfig, CH347 +spi_config = SPIConfig( + Mode=3, + Clock=0, + ByteOrder=1, + SpiWriteReadInterval=0, + SpiOutDefaultData=0xFF, + ChipSelect=0x80, + CS1Polarity=0, + CS2Polarity=0, + IsAutoDeative=1, + ActiveDelay=0, + DelayDeactive=0, +) + +ROM_MAX_SIZE = 0x2000000 # 32MB +RAM_MAX_SIZE = 0x20000 # 128KB (with bank) + +class BaconWritePipeline: + def __init__(self, dev, flush_func): + self.cmds = "" + self.dev = dev + self.flush_func = flush_func + + def WillFlush(self, cmd: str): + if (len(self.cmds) + 1 + len(cmd))//8 >= 0x1000: + return True + return False + + def Write(self, cmd: str): + if (len(self.cmds) + 1 + len(cmd))//8 >= 0x1000: + self.flush_func(command2bytes(self.cmds)) + self.cmds = "" + if self.cmds: + self.cmds += "0" + self.cmds += cmd + return self + + def Flush(self): + if self.cmds: + self.flush_func(command2bytes(self.cmds)) + self.cmds = "" + return self + + + + +class BaconDevice: + def __init__(self, hid_device=None, ch347_device=None): + self.hid_device = hid_device + self.ch347_device = ch347_device + self.power = 0 + # if all device is None, find a device + ## check if windows + if self.ch347_device is None and self.hid_device is None: + if platform.system() == "Windows": + if self.ch347_device is None: + ch347 = CH347(device_index=0, dll_path=os.path.join(os.path.dirname(__file__), "lib/CH347DLLA64.DLL")) + ch347.close_device() + ch347.open_device() + if ch347.spi_init(spi_config): # True if successful, False otherwise. + self.ch347_device = ch347 + # cannot find wch device, try hid device + if self.ch347_device is None: + self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) + if self.ch347_device is None and self.hid_device is None: + raise ValueError("No device found") + self.pipeline = BaconWritePipeline(self, self.WriteRead) + +######## Low Level API ######## + def Close(self): + if self.ch347_device is not None: + self.ch347_device.close_device() + if self.hid_device is not None: + self.hid_device.dev.close() + self.ch347_device = None + self.hid_device = None + + def Write(self, data: bytes) -> bool: + if len(data) > 0x1000: + raise ValueError("Data length must be less than 0x1000") + if self.ch347_device is not None: + return self.ch347_device.spi_write(0x80, data) + if self.hid_device is not None: + return self.hid_device.write_CS1(data) > 0 + raise ValueError("No device found") + + def WriteRead(self, data: bytes) -> bytes: + if len(data) > 0x1000: + raise ValueError("Data length must be less than 0x1000") + if self.ch347_device is not None: + return bytes(self.ch347_device.spi_write_read(0x80, data)) + if self.hid_device is not None: + return bytes(self.hid_device.writeRead_CS1(data)) + raise ValueError("No device found") + + def PiplelineFlush(self): + return self.pipeline.Flush() + + def DelayWithClock8(self, clock8: int) -> BaconWritePipeline: + return self.pipeline.Write( + "0".join([make_power_read_command(postfunc=echo_all)]*clock8) + ) + + def PowerControl(self, v3_3v: bool, v5v: bool) -> BaconWritePipeline: + if v3_3v and v5v: + raise ValueError("v3_3v and v5v can't be both enabled") + if v3_3v: + self.power = 3 + elif v5v: + self.power = 5 + else: + self.power = 0 + # return self.WriteRead(make_power_control_command(not v3_3v, v5v)) + return self.pipeline.Write(make_power_control_command(not v3_3v, v5v, postfunc=echo_all)) + +######## High Level API ######## + + def AGBReadROM(self, addr: int, size: int, callback=None) -> bytes: + self.PiplelineFlush() + if size % 2 != 0: + raise ValueError("Size must be a multiple of 2") + if addr % 2 != 0: + raise ValueError("Address must be a multiple of 2") + if addr + size > ROM_MAX_SIZE: + #raise ValueError("Address + Size must be less than 0x2000000 address:0x%08X size:0x%08X" % (addr, size)) + pass + if self.power != 3: + raise ValueError("Power must be 3.3v") + # prepare chip + ## to halfword + addr = addr // 2 + size = size // 2 + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([(addr >> 16) & 0xFF]) + )) + self.WriteRead(make_gba_rom_cs_write(cs=False)) + lowaddr = addr & 0xFFFF + highaddr = (addr >> 16) & 0xFF + # if lowaddr+1 == 0x10000, highaddr+1, and reset lowaddr + # prepare WriteRead stream + readbytes = [] + cycle_times = 0 + MAX_TIMES = 0x1000//(len(make_rom_read_cycle_command(times=1))+1) + cnt = 0 + for i in range(size): + cycle_times += 1 + lowaddr += 1 + cnt += 2 + if callback is not None and cnt != size*2: + callback(cnt, readbytes) + if lowaddr == 0x10000: + if cycle_times > 0: + ret = self.WriteRead(make_rom_read_cycle_command(times=cycle_times)) + exteds = extract_read_cycle_data(ret, times=cycle_times) + for exted in exteds: + readbytes.append(exted >> 8) + readbytes.append(exted & 0xFF) + cycle_times = 0 + highaddr += 1 + lowaddr = 0 + if highaddr <= 0xFF: + # cs1 re-falling? + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=False, cs2=True, + v16bit=b"\x00\x00", v8bit=bytes([highaddr]) + )) + if cycle_times == MAX_TIMES or i == size - 1 and cycle_times > 0: + ret = self.WriteRead(make_rom_read_cycle_command(times=cycle_times)) + exteds = extract_read_cycle_data(ret, times=cycle_times) + for exted in exteds: + readbytes.append(exted >> 8) + readbytes.append(exted & 0xFF) + cycle_times = 0 + if callback is not None: + callback(cnt, readbytes) + # reset chip + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + return bytes(readbytes) + + def AGBWriteROMSequential(self, addr, data: bytes, flash_prepare=None, flash_confirm=None, callback=None) -> BaconWritePipeline: + if addr % 2 != 0: + raise ValueError("Address must be a multiple of 2") + if len(data) % 2 != 0: + raise ValueError("Data length must be a multiple of 2") + if self.power != 3: + raise ValueError("Power must be 3.3v") + addr = addr // 2 + # prepare chip + self.pipeline.Write( + make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([(addr >> 16) & 0xFF]), + postfunc=echo_all + ) + ) + self.pipeline.Write(make_gba_rom_cs_write(cs=False, postfunc=echo_all)) + lowaddr = addr & 0xFFFF + highaddr = (addr >> 16) & 0xFF + cnt = 0 + for i in range(0, len(data), 2): + self.pipeline.Write(make_rom_write_cycle_command_sequential(datalist=[int.from_bytes(data[i:i+2], byteorder='little')], postfunc=echo_all)) + lowaddr += 1 + cnt += 2 + if callback is not None and cnt != len(data): + callback(cnt, None) + if lowaddr == 0x10000: + highaddr += 1 + lowaddr = 0 + if highaddr <= 0xFF: + # cs1 re-falling? + self.pipeline.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=False, cs2=True, + v16bit=b"\x00\x00", v8bit=bytes([highaddr]), postfunc=echo_all + )) + if callback is not None: + callback(cnt, None) + # reset chip. is necessary? + self.pipeline.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all + )) + return self.pipeline + + def AGBWriteROMWithAddress(self, commands: list, callback=None) -> BaconWritePipeline: + if self.power != 3: + raise ValueError("Power must be 3.3v") + cnt = 0 + # addr这里使用绝对地址,需要//2 + # 如果commands中的data为bytes,需要转换为int(小端) + for i in range(len(commands)): + if type(commands[i][1]) == bytes: + commands[i] = (commands[i][0]//2, int.from_bytes(commands[i][1], byteorder='little')) + else: + commands[i] = (commands[i][0]//2, commands[i][1]) + for i in range(len(commands)): + self.pipeline.Write(make_rom_write_cycle_command_with_addr(addrdatalist=[commands[i]], postfunc=echo_all)) + cnt += 1 + if callback is not None and cnt != len(commands): + callback(cnt, None) + if callback is not None: + callback(cnt, None) + # reset chip + self.pipeline.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all + )) + return self.pipeline + + def AGBReadRAM(self, addr: int, size: int, bankswitch=None, callback=None) -> bytes: + if addr + size > RAM_MAX_SIZE: + raise ValueError("Address + Size must be less than 0x20000") + if addr + size > RAM_MAX_SIZE/2 and bankswitch is None: + raise ValueError("Address + Size must be less than 0x10000 or bankswitch must be provided address:0x%08X size:0x%08X" % (addr, size)) + if self.power != 3: + raise ValueError("Power must be 3.3v") + # prepare chip + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=False, + cs1=True, cs2=False, + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=b"\x00" + )) + readbytes = [] + cycle_times = 0 + MAX_TIMES = 0x1000//(len(make_ram_read_cycle_command(addr=0, times=1))+1) + cnt = 0 + start_addr = addr + for i in range(size): + cycle_times += 1 + cnt += 1 + if callback is not None and cnt != size: + callback(cnt, readbytes) + if cycle_times == MAX_TIMES or i == size - 1 and cycle_times > 0: + ret = self.WriteRead(make_ram_read_cycle_command(addr=start_addr, times=cycle_times)) + exteds = extract_ram_read_cycle_data(ret, times=cycle_times) + for exted in exteds: + readbytes.append(exted) + start_addr += cycle_times + cycle_times = 0 + if callback is not None: + callback(cnt, readbytes) + # reset chip + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + return bytes(readbytes) + + def AGBWriteRAM(self, addr: int, data: bytes, bankswitch=None, callback=None) -> bool: + size = len(data) + if addr + size > RAM_MAX_SIZE: + raise ValueError("Address + Size must be less than 0x20000") + if addr + size > RAM_MAX_SIZE/2 and bankswitch is None: + raise ValueError("Address + Size must be less than 0x10000 or bankswitch must be provided address:0x%08X size:0x%08X" % (addr, size)) + if self.power != 3: + raise ValueError("Power must be 3.3v") + readbytes = [] + cycle_times = 0 + MAX_TIMES = 0x1000//(len(make_ram_write_cycle_command(addr=0, data=b"\00"))+1) + cnt = 0 + start_addr = addr + for i in range(size): + cycle_times += 1 + cnt += 1 + if callback is not None and cnt != size: + callback(cnt, readbytes) + if cycle_times == MAX_TIMES or i == size - 1 and cycle_times > 0: + ret = self.WriteRead(make_ram_write_cycle_command(addr=start_addr, data=data[start_addr-addr:start_addr-addr+cycle_times])) + readbytes = readbytes + ([0]*cycle_times) + start_addr += cycle_times + cycle_times = 0 + if callback is not None: + callback(cnt, readbytes) + # reset chip + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + return bytes(readbytes) + + def AGBWriteRAMWithAddress(self, commands: list, callback=None) -> bool: + if self.power != 3: + raise ValueError("Power must be 3.3v") + readbytes = [] + cycle_times = 0 + MAX_TIMES = 0x1000//(len(make_ram_write_cycle_with_addr(addrdatalist=[(0, 0)]))+1) + cnt = 0 + for i in range(len(commands)): + cycle_times += 1 + cnt += 1 + if callback is not None and cnt != len(commands): + callback(cnt, readbytes) + if cycle_times == MAX_TIMES or i == len(commands) - 1 and cycle_times > 0: + ret = self.WriteRead(make_ram_write_cycle_with_addr(addrdatalist=commands[i-cycle_times+1:i+1])) + readbytes = readbytes + ([0]*cycle_times) + cycle_times = 0 + if callback is not None: + callback(cnt, readbytes) + # reset chip + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + + def AGBCustomWriteCommands(self, commands: list, callback=None) -> bool: + pass diff --git a/FlashGBX/bacon/ch347.py b/FlashGBX/bacon/ch347.py new file mode 100644 index 0000000..6b9fbc5 --- /dev/null +++ b/FlashGBX/bacon/ch347.py @@ -0,0 +1,665 @@ +import ctypes +from typing import List + + +class DeviceInfo(ctypes.Structure): + MAX_PATH = 260 + _fields_ = [ + ("DeviceIndex", ctypes.c_ubyte), # 当前打开序号 + ("DevicePath", ctypes.c_char * MAX_PATH), # 设备路径 + ( + "UsbClass", + ctypes.c_ubyte, + ), # USB设备类别: 0=CH341 Vendor; 1=CH347 Vendor; 2=HID + ("FuncType", ctypes.c_ubyte), # 设备功能类型: 0=UART1; 1=SPI+I2C; 2=JTAG+I2C + ("DeviceID", ctypes.c_char * 64), # USB设备ID: USB\VID_xxxx&PID_xxxx + ( + "ChipMode", + ctypes.c_ubyte, + ), # 芯片模式: 0=Mode0(UART*2); 1=Mode1(Uart1+SPI+I2C); 2=Mode2(HID Uart1+SPI+I2C); 3=Mode3(Uart1+Jtag+I2C) + ("DevHandle", ctypes.c_void_p), # 设备句柄 + ("BulkOutEndpMaxSize", ctypes.c_ushort), # 上传端点大小 + ("BulkInEndpMaxSize", ctypes.c_ushort), # 下传端点大小 + ("UsbSpeedType", ctypes.c_ubyte), # USB速度类型: 0=FS; 1=HS; 2=SS + ("CH347IfNum", ctypes.c_ubyte), # USB接口号 + ("DataUpEndp", ctypes.c_ubyte), # 端点地址 + ("DataDnEndp", ctypes.c_ubyte), # 端点地址 + ("ProductString", ctypes.c_char * 64), # USB产品字符串 + ("ManufacturerString", ctypes.c_char * 64), # USB厂商字符串 + ("WriteTimeout", ctypes.c_ulong), # USB写超时 + ("ReadTimeout", ctypes.c_ulong), # USB读超时 + ("FuncDescStr", ctypes.c_char * 64), # 接口功能描述符 + ("FirmwareVer", ctypes.c_ubyte), # 固件版本 + ] + + +class SPIConfig(ctypes.Structure): + _fields_ = [ + ("Mode", ctypes.c_ubyte), # 0-3: SPI Mode0/1/2/3 + ( + "Clock", + ctypes.c_ubyte, + ), # 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz, 7=468.75KHz + ("ByteOrder", ctypes.c_ubyte), # 0=LSB first(LSB), 1=MSB first(MSB) + ( + "SPIWriteReadInterval", + ctypes.c_ushort, + ), # Regular interval for SPI read/write commands, in microseconds + ( + "SPIOutDefaultData", + ctypes.c_ubyte, + ), # Default output data when reading from SPI + ( + "ChipSelect", + ctypes.c_ulong, + ), # Chip select control. Bit 7 as 0 ignores chip select control, + # Bit 7 as 1 makes the parameters valid: + # Bit 1 and Bit 0 as 00/01 selects CS1/CS2 pin as the active low chip select. + ( + "CS1Polarity", + ctypes.c_ubyte, + ), # Bit 0: CS1 polarity control, 0: active low, 1: active high + ( + "CS2Polarity", + ctypes.c_ubyte, + ), # Bit 0: CS2 polarity control, 0: active low, 1: active high + ( + "IsAutoDeativeCS", + ctypes.c_ushort, + ), # Automatically de-assert chip select after the operation is completed + ( + "ActiveDelay", + ctypes.c_ushort, + ), # Delay time for executing read/write operations after chip select is set, in microseconds + ( + "DelayDeactive", + ctypes.c_ulong, + ), # Delay time for executing read/write operations after chip select is de-asserted, in microseconds + ] + + +class CH347: + + # MAX devices number + MAX_DEVICE_NUMBER = 8 + + # Define the callback function type + NOTIFY_ROUTINE = ctypes.CFUNCTYPE(None, ctypes.c_ulong) + + INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value + + def __init__(self, device_index=0, dll_path="ch347/lib/CH347DLLA64.DLL"): + self.ch347dll = ctypes.WinDLL(dll_path) + self.device_index = device_index + + # 创建回调函数对象并绑定到实例属性 + self.callback_func = self.NOTIFY_ROUTINE(self.event_callback) + + # Set the function argument types and return type for CH347OpenDevice + self.ch347dll.CH347OpenDevice.argtypes = [ctypes.c_ulong] + self.ch347dll.CH347OpenDevice.restype = ctypes.c_void_p + + # Set the function argument types and return type for CH347CloseDevice + self.ch347dll.CH347CloseDevice.argtypes = [ctypes.c_ulong] + self.ch347dll.CH347CloseDevice.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347GetDeviceInfor + self.ch347dll.CH347GetDeviceInfor.argtypes = [ + ctypes.c_ulong, + ctypes.POINTER(DeviceInfo), + ] + self.ch347dll.CH347GetDeviceInfor.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347GetVersion + self.ch347dll.CH347GetVersion.argtypes = [ + ctypes.c_ulong, + ctypes.POINTER(ctypes.c_ubyte), + ctypes.POINTER(ctypes.c_ubyte), + ctypes.POINTER(ctypes.c_ubyte), + ctypes.POINTER(ctypes.c_ubyte), + ] + self.ch347dll.CH347GetVersion.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SetDeviceNotify + self.ch347dll.CH347SetDeviceNotify.argtypes = [ + ctypes.c_ulong, + ctypes.c_char_p, + ctypes.CFUNCTYPE(None, ctypes.c_ulong), + ] + self.ch347dll.CH347SetDeviceNotify.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347ReadData + self.ch347dll.CH347ReadData.argtypes = [ + ctypes.c_ulong, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_ulong), + ] + self.ch347dll.CH347ReadData.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347WriteData + self.ch347dll.CH347WriteData.argtypes = [ + ctypes.c_ulong, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_ulong), + ] + self.ch347dll.CH347WriteData.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SetTimeout + self.ch347dll.CH347SetTimeout.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ] + self.ch347dll.CH347SetTimeout.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_Init + self.ch347dll.CH347SPI_Init.argtypes = [ + ctypes.c_ulong, + ctypes.POINTER(SPIConfig), + ] + self.ch347dll.CH347SPI_Init.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_GetCfg + self.ch347dll.CH347SPI_GetCfg.argtypes = [ + ctypes.c_ulong, + ctypes.POINTER(SPIConfig), + ] + self.ch347dll.CH347SPI_GetCfg.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_ChangeCS + self.ch347dll.CH347SPI_ChangeCS.argtypes = [ctypes.c_ulong, ctypes.c_ubyte] + self.ch347dll.CH347SPI_ChangeCS.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_SetChipSelect + self.ch347dll.CH347SPI_SetChipSelect.argtypes = [ + ctypes.c_ulong, + ctypes.c_ushort, + ctypes.c_ushort, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ] + self.ch347dll.CH347SPI_SetChipSelect.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_Write + self.ch347dll.CH347SPI_Write.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ] + self.ch347dll.CH347SPI_Write.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_Read + self.ch347dll.CH347SPI_Read.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.POINTER(ctypes.c_ulong), + ctypes.c_void_p, + ] + self.ch347dll.CH347SPI_Read.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347SPI_WriteRead + self.ch347dll.CH347SPI_WriteRead.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ] + self.ch347dll.CH347SPI_WriteRead.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347StreamSPI4 + self.ch347dll.CH347StreamSPI4.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ] + self.ch347dll.CH347StreamSPI4.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347I2C_Set + self.ch347dll.CH347I2C_Set.argtypes = [ctypes.c_ulong, ctypes.c_ulong] + self.ch347dll.CH347I2C_Set.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347I2C_SetDelaymS + self.ch347dll.CH347I2C_SetDelaymS.argtypes = [ctypes.c_ulong, ctypes.c_ulong] + self.ch347dll.CH347I2C_SetDelaymS.restype = ctypes.c_bool + + # Set the function argument types and return type for CH347StreamI2C + self.ch347dll.CH347StreamI2C.argtypes = [ + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ctypes.c_ulong, + ctypes.c_void_p, + ] + self.ch347dll.CH347StreamI2C.restype = ctypes.c_bool + + def list_devices(self): + # List all devices + num_devices = 0 + dev_info = DeviceInfo() + for i in range(self.MAX_DEVICE_NUMBER): + if self.ch347dll.CH347OpenDevice(i) == self.INVALID_HANDLE_VALUE: + break + num_devices += 1 + if self.ch347dll.CH347GetDeviceInfor(i, ctypes.byref(dev_info)): + for field_name, _ in dev_info._fields_: + value = getattr(dev_info, field_name) + print(f"{field_name}: {value}") + print("-" * 40) + self.ch347dll.CH347CloseDevice(i) + print(f"Number of devices: {num_devices}") + return num_devices + + @staticmethod + def event_callback(self, event_status): + # Callback function implementation + print("Callback event status:", event_status) + if event_status == 0: + # Device unplug event + print("Device unplugged") + elif event_status == 3: + # Device insertion event + print("Device inserted") + + def open_device(self): + """ + Open USB device. + + Returns: + int: Handle to the opened device if successful, None otherwise. + """ + handle = self.ch347dll.CH347OpenDevice(self.device_index) + if handle != self.INVALID_HANDLE_VALUE: + return handle + else: + return None + + def close_device(self): + """ + Close USB device. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347CloseDevice(self.device_index) + return result + + def get_device_info(self): + """ + Get device information. + + Returns: + bool: True if successful, False otherwise. + DeviceInfo: Device information. + """ + dev_info = DeviceInfo() + result = self.ch347dll.CH347GetDeviceInfor( + self.device_index, ctypes.byref(dev_info) + ) + if result: + return dev_info + else: + return None + + def get_version(self): + """ + Obtain driver version, library version, device version, and chip type. + + This method retrieves various versions related to the CH347 device and returns them as a tuple. + + Returns: + tuple or None: A tuple containing the following information if successful: + - driver_ver (int): The driver version. + - dll_ver (int): The library version. + - device_ver (int): The device version. + - chip_type (int): The chip type. + Returns None if the retrieval fails. + """ + # Create variables to store the version information + driver_ver = ctypes.c_ubyte() + dll_ver = ctypes.c_ubyte() + device_ver = ctypes.c_ubyte() + chip_type = ctypes.c_ubyte() + + # Call the CH347GetVersion function + result = self.ch347dll.CH347GetVersion( + self.device_index, + ctypes.byref(driver_ver), + ctypes.byref(dll_ver), + ctypes.byref(device_ver), + ctypes.byref(chip_type), + ) + if result: + return driver_ver.value, dll_ver.value, device_ver.value, chip_type.value + else: + return None + + def set_device_notify(self, device_id, notify_routine=event_callback): + """ + Configure device event notifier. + + Args: + device_id (str): Optional parameter specifying the ID of the monitored device. + notify_routine (callable): Callback function to handle device events. + + Returns: + bool: True if successful, False otherwise. + """ + callback = self.NOTIFY_ROUTINE(notify_routine) + result = self.ch347dll.CH347SetDeviceNotify( + self.device_index, device_id, callback + ) + return result + + def read_data(self, buffer, length): + """ + Read USB data block. + + Args: + buffer (ctypes.c_void_p): Pointer to a buffer to store the read data. + length (ctypes.POINTER(ctypes.c_ulong)): Pointer to the length unit. Contains the length to be read as input and the actual read length after return. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347ReadData(self.device_index, buffer, length) + return result + + def write_data(self, buffer, length): + """ + Write USB data block. + + Args: + buffer (ctypes.c_void_p): Pointer to a buffer containing the data to be written. + length (ctypes.POINTER(ctypes.c_ulong)): Pointer to the length unit. Input length is the intended length, and the return length is the actual length. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347WriteData(self.device_index, buffer, length) + return result + + def set_timeout(self, write_timeout, read_timeout): + """ + Set the timeout of USB data read and write. + + Args: + write_timeout (int): Timeout for USB to write data blocks, in milliseconds. Use 0xFFFFFFFF to specify no timeout (default). + read_timeout (int): Timeout for USB to read data blocks, in milliseconds. Use 0xFFFFFFFF to specify no timeout (default). + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347SetTimeout( + self.device_index, write_timeout, read_timeout + ) + return result + + def spi_init(self, spi_config: SPIConfig) -> bool: + """ + Initialize the SPI Controller. + + Args: + spi_config (SPIConfig): The configuration for the SPI controller. + + Returns: + bool: True if initialization is successful, False otherwise. + """ + result = self.ch347dll.CH347SPI_Init( + self.device_index, ctypes.byref(spi_config) + ) + return result + + def spi_get_config(self): + """ + Get SPI controller configuration information. + + Returns: + tuple: A tuple containing a boolean value indicating if the operation was successful + and the SPI configuration structure. + + The first element (bool) represents whether the operation was successful or not. + - True: The operation was successful. + - False: The operation failed. + + The second element (SPIConfig): An instance of the SPIConfig class, representing the + SPI configuration structure. If the operation was successful, this object will contain + the configuration information retrieved from the SPI controller. Otherwise, it will be + an empty object with default values. + """ + spi_config = SPIConfig() + result = self.ch347dll.CH347SPI_GetCfg( + self.device_index, ctypes.byref(spi_config) + ) + if result: + return spi_config + else: + return None + + def spi_change_cs(self, status): + """ + Change the chip selection status. + + Args: + status (int): Chip selection status. 0 = Cancel the piece to choose, 1 = Set piece selected. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347SPI_ChangeCS(self.device_index, status) + return result + + def spi_set_chip_select( + self, + enable_select, + chip_select, + is_auto_deactive_cs, + active_delay, + delay_deactive, + ): + """ + Set SPI chip selection. + + Args: + enable_select (int): Enable selection status. The lower octet is CS1 and the higher octet is CS2. + A byte value of 1 sets CS, 0 ignores this CS setting. + chip_select (int): Chip selection status. The lower octet is CS1 and the higher octet is CS2. + A byte value of 1 sets CS, 0 ignores this CS setting. + is_auto_deactive_cs (int): Auto deactivation status. The lower 16 bits are CS1 and the higher 16 bits are CS2. + Whether to undo slice selection automatically after the operation is complete. + active_delay (int): Latency of read/write operations after chip selection, in microseconds. + The lower 16 bits are CS1 and the higher 16 bits are CS2. + delay_deactive (int): Delay time for read and write operations after slice selection, in microseconds. + The lower 16 bits are CS1 and the higher 16 bits are CS2. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347SPI_SetChipSelect( + self.device_index, + enable_select, + chip_select, + is_auto_deactive_cs, + active_delay, + delay_deactive, + ) + return result + + def spi_write( + self, chip_select: int, write_data: List[int], write_step: int = 512 + ) -> bool: + result = self.spi_write(chip_select, bytes(write_data), write_step) + def spi_write( + self, chip_select: int, write_data: bytes, write_step: int = 512 + ) -> bool: + """ + SPI write data. + + Args: + chip_select (int): Chip selection control. When bit 7 is 0, chip selection control is ignored. + When bit 7 is 1, chip selection operation is performed. + write_data (List[int]): List of integers to write. + write_step (int, optional): The length of a single block to be read. Default is 512. + + Returns: + bool: True if successful, False otherwise. + """ + write_length = len(write_data) + write_buffer = ctypes.create_string_buffer(write_data) + result = self.ch347dll.CH347SPI_Write( + self.device_index, chip_select, write_length, write_step, write_buffer + ) + return result + + def spi_read( + self, chip_select: int, write_data: List[int], read_length: int + ) -> List[int]: + """ + SPI read data. + + Args: + chip_select (int): Chip selection control. When bit 7 is 0, chip selection control is ignored. + When bit 7 is 1, chip selection operation is performed. + write_data (List[int]): List of integers to write. + read_length (int): Number of bytes to read. + + Returns: + List[int]: Data read in from the SPI stream if successful, None otherwise. + """ + write_length = len(write_data) + + # Create ctypes buffer for write data + write_buffer = ctypes.create_string_buffer(bytes(write_data)) + + # Create ctypes buffer for read data + read_buffer = ctypes.create_string_buffer(read_length) + + # Create combined buffer for read and write data + combined_buffer = ctypes.create_string_buffer( + write_buffer.raw[:write_length] + read_buffer.raw + ) + + result = self.ch347dll.CH347SPI_Read( + self.device_index, + chip_select, + write_length, + ctypes.byref(ctypes.c_ulong(read_length)), + combined_buffer, + ) + + if result: + # Extract the read data from the combined buffer + read_data = list(combined_buffer[:read_length]) + return read_data + else: + return None + + def spi_write_read(self, chip_select: int, write_data: bytes) -> bytes: + io_buffer = ctypes.create_string_buffer(write_data) + result = self.ch347dll.CH347SPI_WriteRead( + self.device_index, chip_select, len(write_data), io_buffer + ) + # transform the ctypes buffer to bytes + io_data = io_buffer.raw[: len(write_data)] + return io_data + + def _spi_write_read(self, chip_select: int, length: int, io_buffer: ctypes.c_void_p) -> bool: + """ + Handle SPI data stream 4-wire interface. + + Args: + chip_select (int): Selection control. If the film selection control bit 7 is 0, ignore the film selection control. + If bit 7 is 1, perform the film selection. + length (int): Number of bytes of data to be transferred. + io_buffer (ctypes.c_void_p): Points to a buffer that places the data to be written out from DOUT. + Returns the data read in from DIN. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347SPI_WriteRead( + self.device_index, chip_select, length, io_buffer + ) + return result + + def stream_spi4(self, chip_select, length, io_buffer): + """ + Handle SPI data stream 4-wire interface. + + Args: + chip_select (int): Film selection control. If bit 7 is 0, slice selection control is ignored. + If bit 7 is 1, the parameter is valid: Bit 1 bit 0 is 00/01/10. + Select D0/D1/D2 pins as low-level active chip options, respectively. + length (int): Number of bytes of data to be transferred. + io_buffer (ctypes.c_void_p): Points to a buffer that places data to be written out from DOUT. + Returns data to be read in from DIN. + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347StreamSPI4( + self.device_index, chip_select, length, io_buffer + ) + return result + + def i2c_set(self, interface_speed): + """ + Set the serial port flow mode. + + Args: + interface_speed (int): I2C interface speed / SCL frequency. Bit 1-bit 0: + 0 = low speed / 20KHz + 1 = standard / 100KHz (default) + 2 = fast / 400KHz + 3 = high speed / 750KHz + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347I2C_Set(self.device_index, interface_speed) + return result + + def i2c_set_delay_ms(self, delay_ms): + """ + Set the hardware asynchronous delay to a specified number of milliseconds before the next stream operation. + + Args: + delay_ms (int): Delay duration in milliseconds (ms). + + Returns: + bool: True if successful, False otherwise. + """ + result = self.ch347dll.CH347I2C_SetDelaymS(self.device_index, delay_ms) + return result + + def stream_i2c(self, write_data, read_length): + """ + Process I2C data stream. + + Args: + write_data (bytes): Data to write. The first byte is usually the I2C device address and read/write direction bit. + read_length (int): Number of bytes of data to read. + + Returns: + bytes: Data read from the I2C stream. + """ + write_length = len(write_data) + + # Convert write_data to ctypes buffer + write_buffer = ctypes.create_string_buffer(bytes(write_data)) + + # Create ctypes buffer for read data + read_buffer = ctypes.create_string_buffer(read_length) + + result = self.ch347dll.CH347StreamI2C( + self.device_index, write_length, write_buffer, read_length, read_buffer + ) + + if result: + return read_buffer[:read_length] + else: + return None diff --git a/FlashGBX/bacon/command.py b/FlashGBX/bacon/command.py new file mode 100644 index 0000000..166213f --- /dev/null +++ b/FlashGBX/bacon/command.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +# bacon +# Author: ChisBread (github.com/ChisBread) + +# 命令结束后需要跟一个0bit + +# -- | Command Value (5 bits) | Command Name | Description | Input Bits | Input Description | Output Bits | Output Description | +# -- |------------------------|--------------|-------------|------------|-------------------|-------------|--------------------| +# -- | 00001 | CART_30BIT_WRITE | 30位数据写入 | 30 | 30位数据 | - | 无返回 | +# -- | 00010 | CART_30BIT_READ | 30位数据读取 | - | 无输入 | 30 | 30位数据 | +# -- | 00011 | MOD_WRITE | 控制电源 | 2 | 10: 都不启用
00: 启用3.3v(GBA)
11: 启用5v(GB)
01: 无效 | - | 无返回 | +# -- | 00100 | MOD_READ | 读取电源状态 | - | 无输入 | 2 | 0: 都不启用
01: 启用3.3v(GBA)
10: 启用5v(GB)
11: 无效 | +# -- | 00101 | GBA_WR/RD_WRITE | GBA 2bit寄存器操作,每次上升沿会使锁存的16bit地址自增1 | 2 | 0: 无效
01: 启用WR
10: 启用RD
11: 默认 | - | 无返回 | +# -- | 00110 | GBA_WR/RD_READ | 读取GBA 2bit寄存器状态 | - | 无输入 | 2 | 0: 无效
01: 启用WR
10: 启用RD
11: 默认 | +# -- | 00111 | GBA_ROM_EN_WRITE | GBA ROM使能 | 1 | 使能 | - | 无返回 | +# -- | 01000 | GBA_ROM_ADDR_READ | 读取GBA R高8位地址 | - | 无输入 | 8 | 8位数据 | +# -- | 01001 | GBA_ROM_DATA_WRITE | GBA ROM写16位数据 | 16 | 16位数据 | - | 无返回 | +# -- | 01010 | GBA_ROM_DATA_READ | 读取GBA ROM数据 | - | 无输入 | 16 | 16位数据 | +# -- -- | 01011 | GBA_ROM_ADDR_WRITE | GBA ROM写高8位地址 | 8 | 8位地址 | - | 无返回 | +# -- | 10000 - 11111 | RESERVED | 预留命令 | - | - | - | - | +import traceback +from bitarray import bitarray + +def command2bytes(command, endclk=True) -> bytes: + if isinstance(command, str): + command = bitarray(command) + if endclk and len(command) % 8 == 0: + # end clk cycle + command += "0" + if len(command) % 8: + command += bitarray("0" * (8 - len(command) % 8)) + return command.tobytes() + +def echo_all(data): + return data + +def bytes2command(data: bytes): + ret = bitarray() + ret.frombytes(data) + return ret + +def make_power_control_command( + # 3.3v低电平有效, 5v高电平有效 + not_v3_3v: bool = True, v5v: bool = False, postfunc=command2bytes) -> bytes: + # 不能两个都有效 + if not not_v3_3v and v5v: + raise ValueError("v3_3v and v5v can't be both enabled") + command = "00011" + command += "1" if v5v else "0" + command += "1" if not_v3_3v else "0" + return postfunc(command) + +def make_power_read_command(postfunc=command2bytes) -> bytes: + command = "00100" + return postfunc(command) + +def make_cart_30bit_write_command( + phi: bool = False, req: bool = False, + # 低电平有效 + wr: bool = True, rd: bool = True, + cs1: bool = True, cs2: bool = True, + # 小端模式 + v16bit: bytes = b"\00\00", v8bit: bytes = b"\00", postfunc=command2bytes) -> bytes: + command = "00001" + command += "1" if req else "0" + command += "1" if cs2 else "0" + if len(v16bit) != 2: + raise ValueError("v16bit must be 2 bytes") + if len(v8bit) != 1: + raise ValueError("v8bit must be 1 byte") + command += bin(v8bit[0])[2:].rjust(8, "0") + command += bin(v16bit[1])[2:].rjust(8, "0") + command += bin(v16bit[0])[2:].rjust(8, "0") + command += "1" if cs1 else "0" + command += "1" if rd else "0" + command += "1" if wr else "0" + command += "1" if phi else "0" + return postfunc(command) + +def make_cart_30bit_read_command(postfunc=command2bytes) -> bytes: + command = "00010" + command += "0" * 30 + return postfunc(command) + +def make_gba_wr_rd_write_command( + wr: bool = False, rd: bool = False, postfunc=command2bytes) -> bytes: + command = "00101" + command += "1" if wr else "0" + command += "1" if rd else "0" + return postfunc(command) + +def extract_cart_30bit_read_data(data: bytes) -> dict: + if len(data) != 5: + raise ValueError("data must be 5 bytes, but got %d" % len(data)) + ret = {} + # 输出和输出是反的,且有6bit无效数据,所以 + # 000000 00 | 10 010101 | 011010101001010101100010 + ret["phi"] = bool(data[0] >> 1 & 1) + ret["wr"] = bool(data[0] & 1) + ret["rd"] = bool(data[1] >> 7) + ret["cs1"] = bool(data[1] >> 6 & 1) + # 16bit数据, 其中6bit在data[1]的低位, 2bit在data[2]的高位 + v16bitB0 = ((data[1] & 0b00111111) << 2) | (data[2] >> 6) + # 16bit数据, 其中6bit在data[2]的低位, 2bit在data[3]的高位 + v16bitB1 = ((data[2] & 0b00111111) << 2) | (data[3] >> 6) + # 8bit数据, 其中6bit在data[3]的低位, 2bit在data[4]的高位 + v8bit = ((data[3] & 0b00111111) << 2) | (data[4] >> 6) + # 3byte都需要按位翻转 + # v16bitB0 = reverse_bits(v16bitB0) + # v16bitB1 = reverse_bits(v16bitB1) + v8bit = reverse_bits(v8bit) + # ret["v16bit"] = (v16bitB0 << 8) | v16bitB1 + ret["v16bit"] = reverse_bits_16bit((v16bitB1 << 8) | v16bitB0) + ret["v8bit"] = v8bit + ret["cs2"] = bool(data[4] >> 5 & 1) + ret["req"] = bool(data[4] >> 4 & 1) + return ret + + +def make_v16bit_data_write_command(data: int, postfunc=command2bytes) -> bytes: + if data > 0xFFFF: + raise ValueError("data must be less than 0xFFFF") + command = "01001" + bin(data)[2:].rjust(16, "0") + return postfunc(command) + +def make_gba_rom_data_write_command(data: int, postfunc=command2bytes) -> bytes: + return make_v16bit_data_write_command(data, postfunc) + +__readcyclecmd_30bit = "0".join([ + make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all), + make_cart_30bit_read_command(postfunc=echo_all), + make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all)]) +def make_rom_read_cycle_command_30bit(times=1, postfunc=command2bytes) -> bytes: + # 1. pull down RD + # 2. read data + # 3. pull up RD + return postfunc("0".join([__readcyclecmd_30bit for i in range(times)])) + +def extract_read_cycle_data_30bit(data: bytes, times=1): + ret = [] + if len(data)*8 < (len(__readcyclecmd_30bit)+1) * times: + raise ValueError("data must be %d bytes, but got %d" % ((len(__readcyclecmd_30bit)+1) * times, len(data)*8)) + bytesstr = bytes2command(data) + # 每隔len(__readcyclecmd_30bit)+1bit取一次数据 + for i in range(0, len(bytesstr), len(__readcyclecmd_30bit) + 1): + one = command2bytes( bytesstr[i + 8: i + len(__readcyclecmd_30bit) + 1], endclk=False) + ret.append(extract_cart_30bit_read_data(one[:len(one)-1])) + if len(ret) >= times: + break + return ret + +def make_gba_rom_data_read_command(postfunc=command2bytes) -> bytes: + command = "01010" + "0" * 16 + return postfunc(command) + +def extract_gba_rom_read_data(data: bytes) -> int: + if len(data) != 3: + raise ValueError("data must be 3 bytes, but got %d" % len(data)) + # 6bit无效数据 + #ret = (reverse_bits(data[0] << 6 | data[1] >> 2) << 8) | reverse_bits(data[1] << 6 | data[2] >> 2) + ret = reverse_bits_16bit(((data[1] << 6 | data[2] >> 2) << 8) | (data[0] << 6 | data[1] >> 2)) + return ret + +__readcyclecmd = "0".join([ + make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all), + make_gba_rom_data_read_command(postfunc=echo_all), + make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all)]) +def make_rom_read_cycle_command(times=1, postfunc=command2bytes) -> bytes: + # 1. pull down RD + # 2. read data + # 3. pull up RD + return postfunc("0".join([__readcyclecmd for i in range(times)])) + +def extract_read_cycle_data(data: bytes, times=1): + ret = [] + if len(data)*8 < (len(__readcyclecmd)+1) * times: + raise ValueError("data must be %d bits, but got %d" % ((len(__readcyclecmd)+1) * times, len(data)*8)) + bytesstr = bytes2command(data) + # 每隔len(__readcyclecmd)+1bit取一次数据 + for i in range(0, len(bytesstr), len(__readcyclecmd) + 1): + one = command2bytes(bytesstr[i + 8: i + len(__readcyclecmd)], endclk=False) + ret.append(extract_gba_rom_read_data(one[:len(one)-1])) + if len(ret) >= times: + break + return ret + +def make_rom_write_cycle_command_with_addr(addrdatalist: list, postfunc=command2bytes) -> bytes: + readram = "0".join(["0".join([ + # 1. write addr, reset cs1 and wr + make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([(addr >> 16) & 0xFF]), postfunc=echo_all), + # 2. pull down cs1 + make_gba_rom_cs_write(cs=False, postfunc=echo_all), + # 3. write data + make_gba_rom_data_write_command(data, postfunc=echo_all), + # 4. pull down wr + make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), + ]) for addr, data in addrdatalist]) + return postfunc(readram) + +def make_rom_write_cycle_command_sequential(datalist: list, postfunc=command2bytes) -> bytes: + readram = "0".join(["0".join([ + # 1. reset wr + make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all), + # 2. write data + make_gba_rom_data_write_command(data, postfunc=echo_all), + # 3. pull down wr + make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), + ]) for data in datalist]) + return postfunc(readram) + +def make_gba_rom_cs_write(cs: bool = True, postfunc=command2bytes) -> bytes: + command = "00111" + command += "1" if cs else "0" + return postfunc(command) + +def make_gba_rom_addr_read_command(postfunc=command2bytes) -> bytes: + command = "01000" + "0" * 8 + return postfunc(command) + +def extract_gba_rom_addr_read_data(data: bytes) -> int: + if len(data) != 2: + raise ValueError("data must be 2 byte, but got %d" % len(data)) + return reverse_bits((data[0] << 6 ) | (data[1] >> 2)) + +def make_ram_write_cycle_with_addr(addrdatalist: list, postfunc=command2bytes) -> bytes: + writeram = "0".join(["0".join( + [make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=False, + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([data]), postfunc=echo_all), + make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all)]) + for addr, data in addrdatalist]) + return postfunc(writeram) + +def make_ram_write_cycle_command(addr, data, postfunc=command2bytes) -> bytes: + cmd = "0".join(["0".join( + [make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=False, + v16bit=bytes([(addr+i) & 0xFF, ((addr+i) >> 8) & 0xFF]), v8bit=bytes([data[i]]), postfunc=echo_all), + make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all)]) + for i in range(len(data))]) + return postfunc(cmd) + +def make_ram_read_cycle_command(addr=0, times=1, postfunc=command2bytes) -> bytes: + cmd = "0".join(["0".join([ + make_v16bit_data_write_command(addr+i, postfunc=echo_all), + make_gba_rom_addr_read_command(postfunc=echo_all) + ]) for i in range(times)]) + return postfunc(cmd) + +__len_of_write = len(make_v16bit_data_write_command(data=0, postfunc=echo_all)) +__len_of_read = len(make_gba_rom_addr_read_command(postfunc=echo_all)) +def extract_ram_read_cycle_data(data: bytes, times=1): + command = bytes2command(data) + ret = [] + for i in range(0, len(command), __len_of_write + __len_of_read + 2): + one = command[i + __len_of_write + 1: i + __len_of_write + 1 + __len_of_read + 1] + ret.append(extract_gba_rom_addr_read_data(command2bytes(one, endclk=False))) + if len(ret) >= times: + break + return ret + +def _reverse_bits(byte): + byte = ((byte & 0xF0) >> 4) | ((byte & 0x0F) << 4) + byte = ((byte & 0xCC) >> 2) | ((byte & 0x33) << 2) + byte = ((byte & 0xAA) >> 1) | ((byte & 0x55) << 1) + return byte + +lookup_rev = [_reverse_bits(i) for i in range(256)] + +def reverse_bits(byte): + return lookup_rev[byte & 0xFF] + +def _reverse_bits_16bit(data): + return (reverse_bits(data & 0xFF) << 8) | reverse_bits(data >> 8) + +lookup_rev_16bit = [_reverse_bits_16bit(i) for i in range(0x10000)] + +def reverse_bits_16bit(data): + return lookup_rev_16bit[data & 0xFFFF] + + + \ No newline at end of file diff --git a/FlashGBX/bacon/include/CH347DLL.H b/FlashGBX/bacon/include/CH347DLL.H new file mode 100644 index 0000000..692eb2b --- /dev/null +++ b/FlashGBX/bacon/include/CH347DLL.H @@ -0,0 +1,613 @@ +/***************************************************************************** +** Copyright (C) WCH 2001-2023 ** +** Web: http://wch.cn ** +******************************************************************************/ +// USB߽ӿоƬCH341/7Ӧòӿڿ,CH347480MbpsUSBչUART/SPI/I2C/JTAG/SWD +// CH347-DLL V1.2 +// л: Windows 98/ME, Windows 2000/XP, WIN7/8/10/11,and later. +// support USB chip: CH341, CH341A,CH347 +// USB => Parallel, I2C, SPI, JTAG, SWD, UART ... +//Notes: +//Copyright (C) 2023 Nanjing Qinheng Microelectronics Co., Ltd. + + +// +#ifndef _CH347_DLL_H +#define _CH347_DLL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN64 +#define mOFFSET( s, m ) ( (ULONG_PTR) & ( ( ( s * ) 0 ) -> m ) ) // ȡṹԱƫƵַĺ +#else +#define mOFFSET( s, m ) ( (ULONG) & ( ( ( s * ) 0 ) -> m ) ) // ȡṹԱƫƵַĺ +#endif + +#ifndef max +#define max( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) // ϴֵ +#endif + +#ifndef min +#define min( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) // Сֵ +#endif + +#ifdef ExAllocatePool +#undef ExAllocatePool // ɾTAGڴ +#endif + +#ifndef NTSTATUS +typedef LONG NTSTATUS; // ״̬ +#endif + +//CH31DLLCH341WDM +#ifndef _CH341_DLL_H +typedef struct _USB_SETUP_PKT { // USBƴĽ׶εṹ + UCHAR mUspReqType; // 00H + UCHAR mUspRequest; // 01H + union { + struct { + UCHAR mUspValueLow; // 02H ֵֽ + UCHAR mUspValueHigh; // 03H ֵֽ + }; + USHORT mUspValue; // 02H-03H ֵ + }; + union { + struct { + UCHAR mUspIndexLow; // 04H ֽ + UCHAR mUspIndexHigh; // 05H ֽ + }; + USHORT mUspIndex; // 04H-05H + }; + USHORT mLength; // 06H-07H ݽ׶εݳ +} mUSB_SETUP_PKT, *mPUSB_SETUP_PKT; + + +typedef struct _WIN32_COMMAND { // WIN32ӿڽṹ + union { + ULONG mFunction; // ʱָܴ߹ܵ + NTSTATUS mStatus; // ʱز״̬ + }; + ULONG mLength; // ȡ,غݵij + union { + mUSB_SETUP_PKT mSetupPkt; // USBƴĽ׶ε + UCHAR mBuffer[ 512]; // ݻ,Ϊ0255B + }; +} mWIN32_COMMAND, *mPWIN32_COMMAND; +// WIN32Ӧòӿ +#define IOCTL_CH341_COMMAND ( FILE_DEVICE_UNKNOWN << 16 | FILE_ANY_ACCESS << 14 | 0x0f34 << 2 | METHOD_BUFFERED ) // רýӿ + +#define mWIN32_COMMAND_HEAD mOFFSET( mWIN32_COMMAND, mBuffer ) // WIN32ӿڵͷ + +#define mCH341_MAX_NUMBER 32 // ͬʱӵCH341/7豸 + +#define mMAX_BUFFER_LENGTH 0x1000 // ݻ󳤶4096 + +#define mMAX_COMMAND_LENGTH ( mWIN32_COMMAND_HEAD + mMAX_BUFFER_LENGTH ) // ݳȼṹͷij + +#define mDEFAULT_BUFFER_LEN 0x0400 // ݻĬϳ1024 + +#define mDEFAULT_COMMAND_LEN ( mWIN32_COMMAND_HEAD + mDEFAULT_BUFFER_LEN ) // Ĭݳȼṹͷij + +// CH341˵ַ +#define mCH347_ENDP_DATA_UP 0x86 // CH347ݿϴ˵ĵַ +#define mCH347_ENDP_DATA_DOWN 0x06 // CH347ݿ´˵ĵַ + +// 豸ӿṩĹܵ +#define mPipeDeviceCtrl 0x00000004 // CH347ۺϿƹܵ +#define mPipeDataUp 0x00000006 // CH347ݿϴܵ +#define mPipeDataDown 0x00000007 // CH347ݿ´ܵ + +// ӦòӿڵĹܴ +#define mFuncNoOperation 0x00000000 // ޲ +#define mFuncGetVersion 0x00000001 // ȡ汾 +#define mFuncGetConfig 0x00000002 // ȡUSB豸 +#define mFuncSetTimeout 0x00000009 // USBͨѶʱ +#define mFuncSetExclusive 0x0000000b // öռʹ +#define mFuncResetDevice 0x0000000c // λUSB豸 +#define mFuncResetPipe 0x0000000d // λUSBܵ +#define mFuncAbortPipe 0x0000000e // ȡUSBܵ +#define mFuncBufferMode 0x00000020 // 趨ϴģʽѯеݳ +#define mFuncBufferModeDn 0x00000021 // 趨´ģʽѯеݳ +#define mFuncGetVersionEx 0x00000022 // ȡ汾żоƬͺ +// USB豸׼ +#define mUSB_CLR_FEATURE 0x01 +#define mUSB_SET_FEATURE 0x03 +#define mUSB_GET_STATUS 0x00 +#define mUSB_SET_ADDRESS 0x05 +#define mUSB_GET_DESCR 0x06 +#define mUSB_SET_DESCR 0x07 +#define mUSB_GET_CONFIG 0x08 +#define mUSB_SET_CONFIG 0x09 +#define mUSB_GET_INTERF 0x0a +#define mUSB_SET_INTERF 0x0b +#define mUSB_SYNC_FRAME 0x0c + +// CH341ƴijר +#define mCH341_VENDOR_READ 0xC0 // ͨƴʵֵCH341רö +#define mCH341_VENDOR_WRITE 0x40 // ͨƴʵֵCH341רд + +#define mCH341A_CMD_I2C_STREAM 0xAA // I2Cӿڵ,ӴֽڿʼΪI2C +#define mCH341A_CMD_UIO_STREAM 0xAB // UIOӿڵ,ӴֽڿʼΪ +#define mCH341A_CMD_PIO_STREAM 0xAE // PIOӿڵ,ӴֽڿʼΪ +// CH341Aƴijר +#define mCH341A_BUF_CLEAR 0xB2 // δɵ +#define mCH341A_I2C_CMD_X 0x54 // I2Cӿڵ,ִ +#define mCH341A_DELAY_MS 0x5E // Ϊλʱָʱ +#define mCH341A_GET_VER 0x5F // ȡоƬ汾 + +#define mCH341A_CMD_I2C_STM_STA 0x74 // I2Cӿڵ:ʼλ +#define mCH341A_CMD_I2C_STM_STO 0x75 // I2Cӿڵ:ֹͣλ +#define mCH341A_CMD_I2C_STM_OUT 0x80 // I2Cӿڵ:,λ5-λ0Ϊ,ֽΪ,0ֻһֽڲӦ +#define mCH341A_CMD_I2C_STM_IN 0xC0 // I2Cӿڵ:,λ5-λ0Ϊ,0ֻһֽڲӦ +#define mCH341A_CMD_I2C_STM_MAX ( min( 0x3F, mCH341_PACKET_LENGTH ) ) // I2Cӿڵݵ󳤶 +#define mCH341A_CMD_I2C_STM_SET 0x60 // I2Cӿڵ:ò,λ2=SPII/O(0=뵥,1=˫˫),λ1λ0=I2Cٶ(00=,01=׼,10=,11=) +#define mCH341A_CMD_I2C_STM_US 0x40 // I2Cӿڵ:΢Ϊλʱ,λ3-λ0Ϊʱֵ +#define mCH341A_CMD_I2C_STM_MS 0x50 // I2Cӿڵ:Ϊλʱ,λ3-λ0Ϊʱֵ +#define mCH341A_CMD_I2C_STM_DLY 0x0F // I2Cӿڵʱֵ +#define mCH341A_CMD_I2C_STM_END 0x00 // I2Cӿڵ:ǰ + +#define mCH341A_CMD_UIO_STM_IN 0x00 // UIOӿڵ:D7-D0 +#define mCH341A_CMD_UIO_STM_DIR 0x40 // UIOӿڵ:趨I/OD5-D0,λ5-λ0Ϊ +#define mCH341A_CMD_UIO_STM_OUT 0x80 // UIOӿڵ:D5-D0,λ5-λ0Ϊ +#define mCH341A_CMD_UIO_STM_US 0xC0 // UIOӿڵ:΢Ϊλʱ,λ5-λ0Ϊʱֵ +#define mCH341A_CMD_UIO_STM_END 0x20 // UIOӿڵ:ǰ + +#define MAX_DEVICE_PATH_SIZE 128 // 豸Ƶַ +#define MAX_DEVICE_ID_SIZE 64 // 豸IDַ +#endif _CH341_DLL_H + +//ӿ +#define CH347_USB_VENDOR 0 +#define CH347_USB_HID 2 +#define CH347_USB_VCP 3 + +//CH347_USB_VENDOR֧CH341/7 +#define CHIP_TYPE_CH341 0 +#define CHIP_TYPE_CH347 1 +#define CHIP_TYPE_CH347F 2 +#define CHIP_TYPE_CH347T CHIP_TYPE_CH347 + +//оƬܽӿ +#define CH347_FUNC_UART 0 +#define CH347_FUNC_SPI_IIC 1 +#define CH347_FUNC_JTAG_IIC 2 +#define CH347_FUNC_JTAG_IIC_SPI 3 //CH347F + +#define DEFAULT_READ_TIMEOUT 500 //Ĭ϶ʱ +#define DEFAULT_WRITE_TIMEOUT 500 //Ĭдʱ + +#define mCH347_PACKET_LENGTH 512 // CH347ֵ֧ݰij + +#pragma pack(1) + +//SPI +typedef struct _SPI_CONFIG{ + UCHAR iMode; // 0-3:SPI Mode0/1/2/3 + UCHAR iClock; // 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz7=468.75KHz + UCHAR iByteOrder; // 0=λǰ(LSB), 1=λǰ(MSB) + USHORT iSpiWriteReadInterval; // SPIӿڳȡдλΪuS + UCHAR iSpiOutDefaultData; // SPIʱĬ + ULONG iChipSelect; // Ƭѡ, λ7Ϊ0Ƭѡ, λ7Ϊ1Ч: λ1λ0Ϊ00/01ֱѡCS1/CS2Ϊ͵ƽЧƬѡ + UCHAR CS1Polarity; // λ0ƬѡCS1Կƣ0͵ƽЧ1ߵƽЧ + UCHAR CS2Polarity; // λ0ƬѡCS2Կƣ0͵ƽЧ1ߵƽЧ + USHORT iIsAutoDeativeCS; // ɺǷԶƬѡ + USHORT iActiveDelay; // Ƭѡִждʱʱ,λus + ULONG iDelayDeactive; // Ƭѡִждʱʱ,λus +}mSpiCfgS,*mPSpiCfgS; + +//豸Ϣ +typedef struct _DEV_INFOR{ + UCHAR iIndex; // ǰ + UCHAR DevicePath[MAX_PATH]; // 豸,CreateFile + UCHAR UsbClass; // 0:CH347_USB_CH341, 2:CH347_USB_HID,3:CH347_USB_VCP + UCHAR FuncType; // 0:CH347_FUNC_UART,1:CH347_FUNC_SPI_I2C,2:CH347_FUNC_JTAG_I2C + CHAR DeviceID[64]; // USB\VID_xxxx&PID_xxxx + UCHAR ChipMode; // оƬģʽ,0:Mode0(UART0/1); 1:Mode1(Uart1+SPI+I2C); 2:Mode2(HID Uart1+SPI+I2C) 3:Mode3(Uart1+Jtag+IIC) 4:CH347F(Uart*2+Jtag/SPI/IIC) + HANDLE DevHandle; // 豸 + USHORT BulkOutEndpMaxSize; // ϴ˵С + USHORT BulkInEndpMaxSize; // ´˵С + UCHAR UsbSpeedType; // USBٶͣ0:FS,1:HS,2:SS + UCHAR CH347IfNum; // USBӿں: CH347T: IF0:UART; IF1:SPI/IIC/JTAG/GPIO + // CH347F: IF0:UART0; IF1:UART1; IF 2:SPI/IIC/JTAG/GPIO + UCHAR DataUpEndp; // ϴ˵ַ + UCHAR DataDnEndp; // ´˵ַ + CHAR ProductString[64]; // USBƷַ + CHAR ManufacturerString[64]; // USBַ + ULONG WriteTimeout; // USBдʱ + ULONG ReadTimeout; // USBʱ + CHAR FuncDescStr[64]; // ӿڹ + UCHAR FirewareVer; // ̼汾,ʮֵ +}mDeviceInforS,*mPDeviceInforS; + +#pragma pack() + +// CH347ģʽú,֧CH347ģʽµĴ򿪡رաUSBUSBдHID +//USB豸 +HANDLE WINAPI CH347OpenDevice(ULONG DevI); + +//رUSB豸 +BOOL WINAPI CH347CloseDevice(ULONG iIndex); + +//ȡ豸Ϣ +BOOL WINAPI CH347GetDeviceInfor(ULONG iIndex,mDeviceInforS *DevInformation); + +// ȡCH347оƬ:CHIP_TYPE_CH347T/CHIP_TYPE_CH347F +UCHAR WINAPI CH347GetChipType(ULONG iIndex ); // ָ豸 + +// ȡ汾汾豸汾оƬ(CH341(FS)/CH347HS) +BOOL WINAPI CH347GetVersion(ULONG iIndex, + PUCHAR iDriverVer, + PUCHAR iDLLVer, + PUCHAR ibcdDevice, + PUCHAR iChipType); //CHIP_TYPE_CH341/7 + +typedef VOID ( CALLBACK * mPCH347_NOTIFY_ROUTINE ) ( // 豸֪ͨ¼ص + ULONG iEventStatus ); // 豸¼͵ǰ״̬(ж): 0=豸γ¼, 3=豸¼ + +#define CH347_DEVICE_ARRIVAL 3 // 豸¼,Ѿ +#define CH347_DEVICE_REMOVE_PEND 1 // 豸Ҫγ +#define CH347_DEVICE_REMOVE 0 // 豸γ¼,Ѿγ + +// 趨豸¼֪ͨ +BOOL WINAPI CH347SetDeviceNotify(ULONG iIndex, // ָ豸,0Ӧһ豸 + PCHAR iDeviceID, // ѡ,ַָ,ָص豸ID,ַ\0ֹ + mPCH347_NOTIFY_ROUTINE iNotifyRoutine ); // ָ豸¼ص,ΪNULLȡ¼֪ͨ,ڼ⵽¼ʱøó + +// ȡUSBݿ +BOOL WINAPI CH347ReadData( ULONG iIndex, // ָ豸 + PVOID oBuffer, // ָһ㹻Ļ,ڱȡ + PULONG ioLength ); // ָ򳤶ȵԪ,ʱΪ׼ȡij,غΪʵʶȡij + +// дȡUSBݿ +BOOL WINAPI CH347WriteData(ULONG iIndex, // ָ豸 + PVOID iBuffer, // ָһ,׼д + PULONG ioLength ); // ָ򳤶ȵԪ,ʱΪ׼дij,غΪʵдij + +// USBݶдijʱ +BOOL WINAPI CH347SetTimeout(ULONG iIndex, // ָ豸 + ULONG iWriteTimeout, // ָUSBдݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + ULONG iReadTimeout ); // ָUSBȡݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + +/***************SPI********************/ +// SPIʼ +BOOL WINAPI CH347SPI_Init(ULONG iIndex,mSpiCfgS *SpiCfg); + +//CH347FSPIʱƵʡiSpiSpeedHz= 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz 7=468.75KHz +BOOL WINAPI CH347SPI_SetFrequency(ULONG iIndex, ULONG iSpiSpeedHz); + +//CH347FSPI֧λ. +BOOL WINAPI CH347SPI_SetDataBits(ULONG iIndex, + UCHAR iDataBits); //iDataBits= 0:8bit, 1:16bit + +//ȡSPIϢ +BOOL WINAPI CH347SPI_GetCfg(ULONG iIndex,mSpiCfgS *SpiCfg); + +//Ƭѡ״̬,ʹǰȵCH347SPI_InitCS +BOOL WINAPI CH347SPI_ChangeCS(ULONG iIndex, // ָ豸 + UCHAR iStatus); // 0=Ƭѡ,1=Ƭѡ + +//SPIƬѡ +BOOL WINAPI CH347SPI_SetChipSelect(ULONG iIndex, // ָ豸 + USHORT iEnableSelect, // ͰλΪCS1߰λΪCS2; ֵֽΪ1=CS,Ϊ0=ԴCS + USHORT iChipSelect, // ͰλΪCS1߰λΪCS2;Ƭѡ,0=Ƭѡ,1=Ƭѡ + ULONG iIsAutoDeativeCS, // 16λΪCS116λΪCS2;ɺǷԶƬѡ + ULONG iActiveDelay, // 16λΪCS116λΪCS2;Ƭѡִждʱʱ,λus + ULONG iDelayDeactive); // 16λΪCS116λΪCS2;Ƭѡִждʱʱ,λus + +//SPI4д +BOOL WINAPI CH347SPI_Write(ULONG iIndex, // ָ豸 + ULONG iChipSelect, // Ƭѡ, λ7Ϊ0Ƭѡ, λ7Ϊ1Ƭѡ + ULONG iLength, // ׼ֽ + ULONG iWriteStep, // ׼ȡĵij + PVOID ioBuffer); // ָһ,׼MOSIд + +//SPI4.дݣЧʽCH347SPI_WriteReadߺܶ +BOOL WINAPI CH347SPI_Read(ULONG iIndex, // ָ豸 + ULONG iChipSelect, // Ƭѡ, λ7Ϊ0Ƭѡ, λ7Ϊ1Ƭѡ + ULONG oLength, // ׼ֽ + PULONG iLength, // ׼ֽ + PVOID ioBuffer); // ָһ,׼DOUTд,غǴDIN + +// SPI,4߽ӿ +BOOL WINAPI CH347SPI_WriteRead(ULONG iIndex, // ָ豸 + ULONG iChipSelect, // Ƭѡ, λ7Ϊ0Ƭѡ, λ7Ϊ1Ƭѡ + ULONG iLength, // ׼ֽ + PVOID ioBuffer ); // ָһ,׼DOUTд,غǴDIN + +// SPI,4߽ӿ +BOOL WINAPI CH347StreamSPI4(ULONG iIndex, // ָ豸 + ULONG iChipSelect, // Ƭѡ, λ7Ϊ0Ƭѡ, λ7Ϊ1Ч + ULONG iLength, // ׼ֽ + PVOID ioBuffer ); // ָһ,׼DOUTд,غǴDIN + +/***************JTAG********************/ +//JTAGӿڳʼģʽٶ +BOOL WINAPI CH347Jtag_INIT(ULONG iIndex, + UCHAR iClockRate); //ͨٶȣЧֵΪ0-5ֵԽͨٶԽ + +//ȡJtagٶ +BOOL WINAPI CH347Jtag_GetCfg(ULONG iIndex, // ָ豸 + UCHAR *ClockRate); //ͨٶȣЧֵΪ0-5ֵԽͨٶԽ + + +//ıTMSֵ״̬л +BOOL WINAPI CH347Jtag_TmsChange(ULONG iIndex, // 豸 + PUCHAR tmsValue, // лTMSλֵ,ֽΪλ + ULONG Step, // tmsValueڴ洢TMSЧλ + ULONG Skip); // Чʼλ + +//shift-dr/ir״̬ждִExit DR/IR +//״̬:Shift-DR/IR.RW.->Exit DR/IR +BOOL WINAPI CH347Jtag_IoScan(ULONG iIndex, + PUCHAR DataBits, //Ҫдλ + ULONG DataBitsNb, //Ҫݵλ + BOOL IsRead); //ǷҪȡ + +//shift-dr/ir״̬жд,ִɺ,һExit DR/IR;,ͣShift-DR/IR +//״̬:Shift-DR/IR.RW..->[Exit DR/IR] +BOOL WINAPI CH347Jtag_IoScanT(ULONG iIndex, // 豸 + PUCHAR DataBits, // Ҫдλ + ULONG DataBitsNb, // Ҫݵλ + BOOL IsRead, // ǷҪȡ + BOOL IsLastPkt); // ǷΪһ + +//CH347 λTap״̬.TCKTMSΪ߽ɽ״̬ΪTest-Logic Reset״̬ +ULONG WINAPI CH347Jtag_Reset(ULONG iIndex); + +//CH347 TRSTӲλ +BOOL WINAPI CH347Jtag_ResetTrst(ULONG iIndex, BOOL TRSTLevel); + +//λʽJTAG IR/DRݶд.ݵĶдָ״̬лȿഫ䡣ݴ䣬ʹCH347Jtag_WriteRead_Fast +//4096ֽΪλд +//״̬:Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead(ULONG iIndex, // ָ豸 + BOOL IsDR, // =TRUE: DRݶд,=FALSE:IRݶд + ULONG iWriteBitLength, // д,׼дij + PVOID iWriteBitBuffer, // ָһ,׼д + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡij + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//JTAG IR/DRд,ڶֽдJTAG̼زӲ4KдȲ4096ֽڡСе +//״̬:Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead_Fast(ULONG iIndex, // ָ豸 + BOOL IsDR, // =TRUE: DRݶд,=FALSE:IRݶд + ULONG iWriteBitLength, // д,׼дij + PVOID iWriteBitBuffer, // ָһ,׼д + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡij + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//λʽJTAG IR/DRݶд.ݵĶдָ״̬лȿഫ䡣ݴ䣬ʹCH347Jtag_WriteRead_Fast +//4096ֽΪλд +//״̬:Run-Test-> Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteReadEx(ULONG iIndex, // ָ豸 + BOOL IsInDrOrIr, // =TRUE: SHIFT-DR/IR״̬ݽ ==FALSE: Run-Test->Shift-IR/DR.ݽ.->Exit IR/DR -> Run-Test + BOOL IsDR, // =TRUE: DRݶд,=FALSE:IRݶд + ULONG iWriteBitLength, // д,׼дij + PVOID iWriteBitBuffer, // ָһ,׼д + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡij + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//JTAG IR/DRд,ڶֽдJTAG̼زӲ4KдȲ4096ֽڡСе +//״̬:Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead_FastEx(ULONG iIndex, // ָ豸 + BOOL IsInDrOrIr, // =TRUE: SHIFT-DR/IR״̬ݽ ==FALSE: Run-Test->Shift-IR/DR.ݽ.->Exit IR/DR -> Run-Test + BOOL IsDR, // =TRUE: DRݶд,=FALSE:IRݶд + ULONG iWriteBitLength, // д,׼дij + PVOID iWriteBitBuffer, // ָһ,׼д + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡij + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//лJTAG״̬ +BOOL WINAPI CH347Jtag_SwitchTapState(UCHAR TapState); + +//JTAG DRд,ֽΪλ,ڶֽдJTAG̼ز +//״̬:Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_ByteWriteDR(ULONG iIndex, // ָ豸 + ULONG iWriteLength, // д,׼дֽڳ + PVOID iWriteBuffer); // ָһ,׼д + +//JTAG DR,ֽΪλ,ֽ +//״̬:Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_ByteReadDR(ULONG iIndex, // ָ豸 + PULONG oReadLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID oReadBuffer ); // ָһ㹻Ļ,ڱȡ + +//JTAG IRд,ֽΪλ,ֽд +//״̬:Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_ByteWriteIR(ULONG iIndex, // ָ豸 + ULONG iWriteLength, // д,׼дֽڳ + PVOID iWriteBuffer); // ָһ,׼д + +//JTAG IR,ֽΪλ,ֽд +//״̬:Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_ByteReadIR(ULONG iIndex, // ָ豸 + PULONG oReadLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID oReadBuffer ); // ָһ㹻Ļ,ڱȡ + +//λʽJTAG DRд.ݵĶдָ״̬лȿഫ䡣ݴ䣬ʹUSB20Jtag_ByeWriteDR +//״̬:Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_BitWriteDR(ULONG iIndex, // ָ豸 + ULONG iWriteBitLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID iWriteBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//λʽJTAG IRд.ݵĶдָ״̬лȿഫ䡣ݴ䣬ʹUSB20Jtag_ByteWriteIR +//״̬:Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_BitWriteIR(ULONG iIndex, // ָ豸 + ULONG iWriteBitLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID iWriteBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//λʽJTAG IRݶ.ݵĶдָ״̬лȡݴ䣬ʹUSB20Jtag_ByteReadIR +//״̬:Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_BitReadIR(ULONG iIndex, // ָ豸 + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +//λʽJTAG DRݶ.ݵĶд͸ݴ䣬ʹUSB20Jtag_ByteReadDR +//״̬:Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_BitReadDR(ULONG iIndex, // ָ豸 + PULONG oReadBitLength, // ָ򳤶ȵԪ,غΪʵʶȡֽڳ + PVOID oReadBitBuffer ); // ָһ㹻Ļ,ڱȡ + +/***************GPIO********************/ +//ȡCH347GPIOŵƽֵ +BOOL WINAPI CH347GPIO_Get(ULONG iIndex, // ָ豸 + UCHAR *iDir, // ŷ:GPIO0-7Ӧλ0-7.0룻1 + UCHAR *iData); // GPIO0ƽ:GPIO0-7Ӧλ0-7,0͵ƽ1ߵƽ) + +//CH347GPIOŵƽֵ +BOOL WINAPI CH347GPIO_Set(ULONG iIndex, // ָ豸 + UCHAR iEnable, // Ч־:Ӧλ0-7,ӦGPIO0-7. + UCHAR iSetDirOut, // I/O,ijλ0ӦΪ,ijλ1ӦΪ.GPIO0-7Ӧλ0-7. + UCHAR iSetDataOut); // ,I/OΪ,ôijλ0ʱӦ͵ƽ,ijλ1ʱӦߵƽ + + +typedef VOID ( CALLBACK * mPCH347_INT_ROUTINE ) ( // жϷ + PUCHAR iStatus ); // ж״̬,ολ˵ +// 8ֽGPIO0-7״̬.ÿֽλ: +// λ7ǰGPIO00룻1 +// λ6ǰGPIO0ƽ0͵ƽ1ߵƽ +// λ5ǰGPIO0ǷΪжϣ0ѯģʽ1жģʽ +// λ4-3GPIO0жģʽ00½ش01ش;10˫ش11: ; +// λ2-0 + +// 趨GPIOжϷ +BOOL WINAPI CH347SetIntRoutine(ULONG iIndex, // ָ豸 + UCHAR Int0PinN, // ж0 GPIOź,7:ôжԴ; Ϊ0-7Ӧgpio0-7 + UCHAR Int0TripMode, // ж0: 00:½ش; 01:ش; 02:˫ش; 03:; + UCHAR Int1PinN, // ж1 GPIOź,7ôжԴ,Ϊ0-7Ӧgpio0-7 + UCHAR Int1TripMode, // ж1: 00:½ش; 01:ش; 02:˫ش; 03:; + mPCH347_INT_ROUTINE iIntRoutine );// ָжϷ,ΪNULLȡжϷ,жʱøó + +// ȡж +BOOL WINAPI CH347ReadInter(ULONG iIndex, // ָ豸 + PUCHAR iStatus ); // ָ8ֽڵԪ,ֱΪGPIO0-7״̬,ÿֽλ˵οжϷiStatusλ˵ + +// жݶ +BOOL WINAPI CH347AbortInter(ULONG iIndex ); // ָ豸 + +//IAP̼ģʽ +BOOL WINAPI CH347StartIapFwUpate(ULONG iIndex, + ULONG FwSize); // ̼ + +/**************HID/VCP**********************/ +//򿪴 +HANDLE WINAPI CH347Uart_Open(ULONG iIndex); + +//رմ +BOOL WINAPI CH347Uart_Close(ULONG iIndex); + +BOOL WINAPI CH347Uart_SetDeviceNotify( // 趨豸¼֪ͨ + ULONG iIndex, // ָ豸,0Ӧһ豸 + PCHAR iDeviceID, // ѡ,ַָ,ָص豸ID,ַ\0ֹ + mPCH347_NOTIFY_ROUTINE iNotifyRoutine ); // ָ豸¼ص,ΪNULLȡ¼֪ͨ,ڼ⵽¼ʱøó + +//ȡUARTӲ +BOOL WINAPI CH347Uart_GetCfg(ULONG iIndex, // ָ豸 + PULONG BaudRate, // + PUCHAR ByteSize, // λ(5,6,7,8,16) + PUCHAR Parity, // Уλ(0None; 1Odd; 2Even; 3Mark; 4Space) + PUCHAR StopBits, // ֹͣλ(01ֹͣλ; 11.5ֹͣλ; 22ֹͣλ) + PUCHAR ByteTimeout); // ֽڳʱ + +//UART +BOOL WINAPI CH347Uart_Init(ULONG iIndex, // ָ豸 + DWORD BaudRate, // + UCHAR ByteSize, // λ(5,6,7,8,16) + UCHAR Parity, // Уλ(0None; 1Odd; 2Even; 3Mark; 4Space) + UCHAR StopBits, // ֹͣλ(01ֹͣλ; 11.5ֹͣλ; 22ֹͣλ) + UCHAR ByteTimeout);// ֽڳʱʱ,λ100uS + +// USBݶдijʱ +BOOL WINAPI CH347Uart_SetTimeout(ULONG iIndex, // ָ豸 + ULONG iWriteTimeout, // ָUSBдݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + ULONG iReadTimeout ); // ָUSBȡݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + +// ȡݿ +BOOL WINAPI CH347Uart_Read(ULONG iIndex, // ָ豸 + PVOID oBuffer, // ָһ㹻Ļ,ڱȡ + PULONG ioLength );// ָ򳤶ȵԪ,ʱΪ׼ȡij,غΪʵʶȡij +// дݿ +BOOL WINAPI CH347Uart_Write(ULONG iIndex, // ָ豸 + PVOID iBuffer, // ָһ,׼д + PULONG ioLength );// ָ򳤶ȵԪ,ʱΪ׼дij,غΪʵдij + +//ѯжֽδȡ +BOOL WINAPI CH347Uart_QueryBufUpload(ULONG iIndex, // ָ豸 + LONGLONG *RemainBytes); // δȡֽ + +//ȡ豸Ϣ +BOOL WINAPI CH347Uart_GetDeviceInfor(ULONG iIndex,mDeviceInforS *DevInformation); + +// USBݶдijʱ +BOOL WINAPI CH347Uart_SetTimeout(ULONG iIndex, // ָ豸 + ULONG iWriteTimeout, // ָUSBдݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + ULONG iReadTimeout ); // ָUSBȡݿijʱʱ,ԺmSΪλ,0xFFFFFFFFָʱ(Ĭֵ) + +/********IIC***********/ +// I2C +BOOL WINAPI CH347I2C_Set(ULONG iIndex, // ָ豸 + ULONG iMode ); // ָģʽ, +// λ1-λ0: I2Cӿٶ/SCLƵ, 00=/20KHz,01=׼/100KHz(Ĭֵ),10=/400KHz,11=/750KHz +// ,Ϊ0 + +//Set I2C Clock Stretch +BOOL WINAPI CH347I2C_SetStretch(ULONG iIndex, // ָ豸 + BOOL iEnable); // I2C Clock Stretch enable, 1:enable,0:disable + +// Ӳ첽ʱ,úܿ췵,һ֮ǰʱָ +BOOL WINAPI CH347I2C_SetDelaymS(ULONG iIndex, // ָ豸 + ULONG iDelay ) ; // ָʱĺ + + // I2C,2߽ӿ,ʱΪSCL,ΪSDA +BOOL WINAPI CH347StreamI2C( ULONG iIndex, // ָ豸 + ULONG iWriteLength, // ׼дֽ + PVOID iWriteBuffer, // ָһ,׼д,ֽͨI2C豸ַдλ + ULONG iReadLength, // ׼ȡֽ + PVOID oReadBuffer ); // ָһ,غǶ + +BOOL WINAPI CH347StreamI2C_RetACK( // I2C,2߽ӿ,ʱΪSCL,ΪSDA(׼˫I/O),ٶԼ56Kֽ,˻ȡACK + ULONG iIndex, // ָCH341豸 + ULONG iWriteLength, // ׼дֽ + PVOID iWriteBuffer, // ָһ,׼д,ֽͨI2C豸ַдλ + ULONG iReadLength, // ׼ȡֽ + PVOID oReadBuffer, // ָһ,غǶ + PULONG rAckCount); // ָдصACKֵ + +#ifndef _CH341_DLL_H +typedef enum _EEPROM_TYPE {// EEPROMͺ + ID_24C01, + ID_24C02, + ID_24C04, + ID_24C08, + ID_24C16, + ID_24C32, + ID_24C64, + ID_24C128, + ID_24C256, + ID_24C512, + ID_24C1024, + ID_24C2048, + ID_24C4096 +} EEPROM_TYPE; +#endif + +// EEPROMжȡݿ,ٶԼ56Kֽ +BOOL WINAPI CH347ReadEEPROM(ULONG iIndex, // ָ豸 + EEPROM_TYPE iEepromID, // ָEEPROMͺ + ULONG iAddr, // ָݵԪĵַ + ULONG iLength, // ׼ȡֽ + PUCHAR oBuffer ); // ָһ,غǶ +// EEPROMдݿ +BOOL WINAPI CH347WriteEEPROM(ULONG iIndex, // ָ豸 + EEPROM_TYPE iEepromID, // ָEEPROMͺ + ULONG iAddr, // ָݵԪĵַ + ULONG iLength, // ׼дֽ + PUCHAR iBuffer ); // ָһ,׼д + +#ifdef __cplusplus +} +#endif + +#endif // _CH347_DLL_H diff --git a/FlashGBX/bacon/include/CH347DLL_EN.H b/FlashGBX/bacon/include/CH347DLL_EN.H new file mode 100644 index 0000000..a41c333 --- /dev/null +++ b/FlashGBX/bacon/include/CH347DLL_EN.H @@ -0,0 +1,610 @@ +/***************************************************************************** +** Copyright (C) WCH 2001-2023 ** +** Web: http://wch.cn ** +*****************************************************************************/ + +//USB bus interface chip CH341/7 parallel port application layer interface library, CH347 based on 480mbps high-speed USB bus, extends UART/SPI/I2C/JTAG +//CH347-DLL V1.2 +//Environment: Windows 98/ME, Windows 2000/XP, WIN7/8/10/11,and later. +//support USB chip: CH341, CH341A,CH347 +//USB => Parallel, I2C, SPI, JTAG, SWD, UART ... +//Notes: +//Copyright (c) 2023 Nanjing Qinheng Microelectronics Co., Ltd. + + +#ifndef _CH347_DLL_H +#define _CH347_DLL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN64 +#define mOFFSET(s, m) ((ULONG_PTR) & (((s *)0)->m)) // Define macro, get relative offset address of structure member +#else +#define mOFFSET(s, m) ((ULONG) & (((s *)0)->m)) // Define macro, get relative offset address of structure member +#endif + +#ifndef max +#define max(a, b) (((a) > (b)) ? (a) : (b)) // Determine maximum value +#endif + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) // Determine minimum value +#endif + +#ifdef ExAllocatePool +#undef ExAllocatePool // Delete memory allocation with TAG +#endif + +#ifndef NTSTATUS +typedef LONG NTSTATUS; // Return status +#endif + +// Sharing CH341WDM driver with CH341DLL +#ifndef _CH341_DLL_H +typedef struct _USB_SETUP_PKT { // USB control transfer structure + UCHAR mUspReqType; // 00H request type + UCHAR mUspRequest; // 01H request code + union { + struct { + UCHAR mUspValueLow; // 02H Value parameter low byte + UCHAR mUspValueHigh; // 03H Value parameter high byte + }; + USHORT mUspValue; // 02H-03H value parameters + }; + union { + struct { + UCHAR mUspIndexLow; // 04H index parameter low byte + UCHAR mUspIndexHigh; // 05H index parameter high byte + }; + USHORT mUspIndex; // 04H-05H index parameter + }; + USHORT mLength; // 06H-07H data length +} mUSB_SETUP_PKT, *mPUSB_SETUP_PKT; + +typedef struct _WIN32_COMMAND { // Define WIN32 command interface structure + union { + ULONG mFunction; // Specify function code or pipe number when input + NTSTATUS mStatus; // Return operation status when output + }; + ULONG mLength; // Access length, return the length of subsequent data + union { + mUSB_SETUP_PKT mSetupPkt; // Data request in the setup phase of USB control transfer + UCHAR mBuffer[512]; // Data buffer, the length is 0 to 255B + }; +} mWIN32_COMMAND, *mPWIN32_COMMAND; +// WIN32 application layer interface command +#define IOCTL_CH341_COMMAND (FILE_DEVICE_UNKNOWN << 16 | FILE_ANY_ACCESS << 14 | 0x0f34 << 2 | METHOD_BUFFERED) // Private interface + +#define mWIN32_COMMAND_HEAD mOFFSET(mWIN32_COMMAND, mBuffer) // Header length of WIN32 command interface + +#define mCH341_MAX_NUMBER 32 // Maximum number of CH341/7 connected at the same time + +#define mMAX_BUFFER_LENGTH 0x1000 // Maximum length of the data buffer is 4096 + +#define mMAX_COMMAND_LENGTH (mWIN32_COMMAND_HEAD + mMAX_BUFFER_LENGTH) // Maximum data length plus command structure header length + +#define mDEFAULT_BUFFER_LEN 0x0400 // Data buffer default length is 1024 + +#define mDEFAULT_COMMAND_LEN (mWIN32_COMMAND_HEAD + mDEFAULT_BUFFER_LEN) // default data length plus command structure header length + +// CH341 endpoint address +#define mCH347_ENDP_DATA_UP 0x86 // Data upload endpoint of CH347 +#define mCH347_ENDP_DATA_DOWN 0x06 // Data download endpoint of CH347 + +// Pipeline operation command provided by equipment layer interface +#define mPipeDeviceCtrl 0x00000004 // CH347 integrated control pipeline +#define mPipeDataUp 0x00000006 // CH347 data block upload pipeline +#define mPipeDataDown 0x00000007 // CH347 data block download pipeline + +// Function code of application layer interface +#define mFuncNoOperation 0x00000000 // No operation +#define mFuncGetVersion 0x00000001 // Get driver version number +#define mFuncGetConfig 0x00000002 // Get USB device configuration descriptor +#define mFuncSetTimeout 0x00000009 // Set USB communication timeout +#define mFuncSetExclusive 0x0000000b // Set exclusive use +#define mFuncResetDevice 0x0000000c // Reset USB device +#define mFuncResetPipe 0x0000000d // Reset USB pipe +#define mFuncAbortPipe 0x0000000e // Cancel data request of USB pipe +#define mFuncBufferMode 0x00000020 // Set buffer upload mode and query data length in the buffer +#define mFuncBufferModeDn 0x00000021 // Set buffer download mode and query data length in the buffer +#define mFuncGetVersionEx 0x00000022 // Get driver version number and chip model +// USB device standard request code +#define mUSB_CLR_FEATURE 0x01 +#define mUSB_SET_FEATURE 0x03 +#define mUSB_GET_STATUS 0x00 +#define mUSB_SET_ADDRESS 0x05 +#define mUSB_GET_DESCR 0x06 +#define mUSB_SET_DESCR 0x07 +#define mUSB_GET_CONFIG 0x08 +#define mUSB_SET_CONFIG 0x09 +#define mUSB_GET_INTERF 0x0a +#define mUSB_SET_INTERF 0x0b +#define mUSB_SYNC_FRAME 0x0c + +// Vendor specific request type of CH341 control transfer +#define mCH341_VENDOR_READ 0xC0 // CH341 vendor-specific read operation via control transfer +#define mCH341_VENDOR_WRITE 0x40 // CH341 vendor-specific write operation via control transfer + +#define mCH341A_CMD_I2C_STREAM 0xAA // Command package of I2C, starting with the second byte is I2C command stream +#define mCH341A_CMD_UIO_STREAM 0xAB // Command package of UIO, starting with the second byte is command stream +#define mCH341A_CMD_PIO_STREAM 0xAE // Command package of PIO, starting with the second byte is data stream +// Vendor specific request code of CH341A control transfer +#define mCH341A_BUF_CLEAR 0xB2 // Clear incomplete data +#define mCH341A_I2C_CMD_X 0x54 // Issue command of I2C interface and executes it immediately +#define mCH341A_DELAY_MS 0x5E // Delay specified time, in millisecond +#define mCH341A_GET_VER 0x5F // Get chip version + +#define mCH341A_CMD_I2C_STM_STA 0x74 // I2C interface command stream: generate start bit +#define mCH341A_CMD_I2C_STM_STO 0x75 // I2C interface command stream: generate stop bit +#define mCH341A_CMD_I2C_STM_OUT 0x80 // I2C interface command stream: output data, bit5 - bit0 is length, subsequent bytes are data, length 0 only sends one byte and returns an response +#define mCH341A_CMD_I2C_STM_IN 0xC0 // I2C interface command stream: input data, bit 5-bit 0 is length, length 0 only receives one byte and sends no response +#define mCH341A_CMD_I2C_STM_MAX (min(0x3F, mCH341_PACKET_LENGTH)) // Maximum length of a single command input/output data of I2C interface command stream +#define mCH341A_CMD_I2C_STM_SET 0x60 // I2C interface command stream: set parameters, bit2=SPI I/O number (0= single input single output, 1= double input double output), bit1 bit0=I2C speed (00= low speed, 01= standard, 10= fast, 11= high speed) +#define mCH341A_CMD_I2C_STM_US 0x40 // I2C interface command stream: delay in microseconds, bit3 - bit0 is delay value +#define mCH341A_CMD_I2C_STM_MS 0x50 // I2C interface command stream: delay in milliseconds, bit3 - bit0 is delay value +#define mCH341A_CMD_I2C_STM_DLY 0x0F // Maximum value of a single command delay of I2C interface command stream +#define mCH341A_CMD_I2C_STM_END 0x00 // I2C interface command stream: command package ends in advance + +#define mCH341A_CMD_UIO_STM_IN 0x00 // UIO interface command stream: input data D7-D0 +#define mCH341A_CMD_UIO_STM_DIR 0x40 // UIO interface command stream: set I/O direction D5-D0, bit5 - bit0 is direction data +#define mCH341A_CMD_UIO_STM_OUT 0x80 // UIO interface command stream: output data D5-D0, bit5 - bit0 is data +#define mCH341A_CMD_UIO_STM_US 0xC0 // UIO interface command stream: delay in microseconds, bit5 - bit0 is delay value +#define mCH341A_CMD_UIO_STM_END 0x20 // UIO interface command stream: command package ends in advance + +#define MAX_DEVICE_PATH_SIZE 128 // Maximum number of characters for device name +#define MAX_DEVICE_ID_SIZE 64 // Maximum number of characters for device ID +#endif _CH341_DLL_H + +// Driver interface +#define CH347_USB_VENDOR 0 +#define CH347_USB_HID 2 +#define CH347_USB_VCP 3 + +// CH347_USB_VENDOR support CH341/7 +#define CHIP_TYPE_CH341 0 +#define CHIP_TYPE_CH347 1 +#define CHIP_TYPE_CH347F 2 +#define CHIP_TYPE_CH347T CHIP_TYPE_CH347 + +// Chip function interface type +#define CH347_FUNC_UART 0 +#define CH347_FUNC_SPI_IIC 1 +#define CH347_FUNC_JTAG_IIC 2 +#define CH347_FUNC_JTAG_IIC_SPI 3 // CH347F + +#define DEFAULT_READ_TIMEOUT 500 // Default read timeout milliseconds +#define DEFAULT_WRITE_TIMEOUT 500 // Default write timeout milliseconds + +#define mCH347_PACKET_LENGTH 512 // Length of data packet supported by CH347 +#pragma pack(1) +// SPI controller configuration +typedef struct _SPI_CONFIG { + UCHAR iMode; // 0-3:SPI Mode0/1/2/3 + UCHAR iClock; // 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz, 7=468.75KHz + UCHAR iByteOrder; // 0=LSB, 1=MSB + USHORT iSpiWriteReadInterval; // SPI regular read/write data command, in uS + UCHAR iSpiOutDefaultData; // Default output data when SPI reads data + ULONG iChipSelect; // Chip select control, Bit7=0: ignore chip select control; bit7=1: parameter is valid, bit1/bit0 are 00/01 to select the CS1/CS2 pins as low-level active chip select, respectively. + UCHAR CS1Polarity; // Bit0: CS1 polarity control: 0: active low; 1: active high; + UCHAR CS2Polarity; // Bit0: CS2 polarity control: 0: active low; 1: active high; + USHORT iIsAutoDeativeCS; // Auto undo chip selection or not after operation is completed + USHORT iActiveDelay; // Setting delay time for performing read/write operations after chip selection, in uS + ULONG iDelayDeactive; // Delay time for read/write operations after de-selecting chip selection, in uS +} mSpiCfgS, *mPSpiCfgS; + +// Device information +typedef struct _DEV_INFOR { + UCHAR iIndex; // Currently open index + UCHAR DevicePath[MAX_PATH]; // Device link name, used for CreateFile + UCHAR UsbClass; // Driver Type 0:CH347_USB_CH341, 2:CH347_USB_HID, 3:CH347_USB_VCP + UCHAR FuncType; // Functional Type 0:CH347_FUNC_UART, 1:CH347_FUNC_SPI_I2C, 2:CH347_FUNC_JTAG_I2C + CHAR DeviceID[64]; // USB\VID_xxxx&PID_xxxx + UCHAR ChipMode; // Chip Mode, 0:Mode0(UART0/1); 1:Mode1(Uart1+SPI+I2C); 2:Mode2(HID Uart1+SPI+I2C) 3:Mode3(Uart1+Jtag+I2C) 4:CH347F(Uart*2+Jtag/SPI/I2C) + HANDLE DevHandle; // Device handle + USHORT BulkOutEndpMaxSize; // Upload endpoint size + USHORT BulkInEndpMaxSize; // Downstream endpoint size + UCHAR UsbSpeedType; // USB speed, 0:FS,1:HS,2:SS + UCHAR CH347IfNum; // USB interface number: CH347T: IF0:UART; IF1:SPI/IIC/JTAG/GPIO + // CH347F: IF0:UART0; IF1:UART1; IF2:SPI/IIC/JTAG/GPIO + UCHAR DataUpEndp; // Bulk upload endpoint address + UCHAR DataDnEndp; // Bulk download endpoint address + CHAR ProductString[64]; // USB product string + CHAR ManufacturerString[64]; // USB manufacturer string + ULONG WriteTimeout; // USB write timeout + ULONG ReadTimeout; // USB read timeout + CHAR FuncDescStr[64]; // Interface function descriptor + UCHAR FirewareVer; // Firmware version, hex value + +} mDeviceInforS, *mPDeviceInforS; +#pragma pack() + +// CH347 each mode public function, support CH347 all modes of open, close, USB read, USB write, including HID +// Open USB device +HANDLE WINAPI CH347OpenDevice(ULONG DevI); + +// Close USB device +BOOL WINAPI CH347CloseDevice(ULONG iIndex); + +// Get device information +BOOL WINAPI CH347GetDeviceInfor(ULONG iIndex, mDeviceInforS *DevInformation); + +// Get CH347 chip type:CHIP_TYPE_CH347T/CHIP_TYPE_CH347F +UCHAR WINAPI CH347GetChipType(ULONG iIndex); // Specify device serial number + +// Obtain driver version, library version, device version and chip type(CH341(FS)/CH347HS) +BOOL WINAPI CH347GetVersion(ULONG iIndex, + PUCHAR iDriverVer, + PUCHAR iDLLVer, + PUCHAR ibcdDevice, + PUCHAR iChipType); // CHIP_TYPE_CH341/7 + +typedef VOID(CALLBACK *mPCH347_NOTIFY_ROUTINE)( // Device plug/unplug notification event callback routine + ULONG iEventStatus); // Device plug/unplug event and current status (define below): 0=Device unplug event, 3=Device insertion event + +#define CH347_DEVICE_ARRIVAL 3 // Device insertion event, has been inserted +#define CH347_DEVICE_REMOVE_PEND 1 // Device wil be unplugged +#define CH347_DEVICE_REMOVE 0 // Device unplug event, has been pulled out + +// Set the device event notification routine +BOOL WINAPI CH347SetDeviceNotify(ULONG iIndex, // Specify device serial number, 0 corresponds to the first device + PCHAR iDeviceID, // Optional parameter, points to a string, specifying the monitored device ID, ending with \0. + mPCH347_NOTIFY_ROUTINE iNotifyRoutine); // Specifies the port device event callback program. If it is NULL, tehn cancel the event notification, otherwise call the routine when an event is detected. + +// Read USB data block +BOOL WINAPI CH347ReadData(ULONG iIndex, // Specify device serial number + PVOID oBuffer, // Points to a buffer large enough to save the read data + PULONG ioLength); // Points to length unit; When input, it is the length to be read, and after return, it is the actual length to be read. + +// Write USB data block +BOOL WINAPI CH347WriteData(ULONG iIndex, // Specify device serial number + PVOID iBuffer, // Points to a buffer large enough to save the written data + PULONG ioLength); // Points to length unit; When input, it is the length to be written, and after return, it is the actual length to be written. + +// Set the timeout of USB data read/write +BOOL WINAPI CH347SetTimeout(ULONG iIndex, // Specify device serial number + ULONG iWriteTimeout, // Specifies the timeout for USB to write data blocks, in mS, 0xFFFFFFFF specifies no timeout (default) + ULONG iReadTimeout); // Specifies the timeout for USB to read data blocks, in mS, 0xFFFFFFFF specifies no timeout (default) + +/***************SPI********************/ +// SPI controller initialization +BOOL WINAPI CH347SPI_Init(ULONG iIndex, mSpiCfgS *SpiCfg); + +//CH347F set SPI clock frequency. iSpiSpeedHz= 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz$17 7=468.75KHz +BOOL WINAPI CH347SPI_SetFrequency(ULONG iIndex, ULONG iSpiSpeedHz); + +// CH347F set the SPI databit +BOOL WINAPI CH347SPI_SetDataBits(ULONG iIndex, + UCHAR iDataBits); // iDataBits= 0:8bit, 1:16bit + +// Get SPI controller configuration information +BOOL WINAPI CH347SPI_GetCfg(ULONG iIndex, mSpiCfgS *SpiCfg); + +// Sets the chip select state, call CH347SPI_Init to set up the CS before use +BOOL WINAPI CH347SPI_ChangeCS(ULONG iIndex, // Specify device serial number + UCHAR iStatus); // 0= Undo chip select, 1=Set chip select + +// Set SPI chip select +BOOL WINAPI CH347SPI_SetChipSelect(ULONG iIndex, // Specify device serial number + USHORT iEnableSelect, // Lower 8-bit is CS1, higher 8-bit is CS2; A byte value of 1= sets CS, 0= ignores this CS setting + USHORT iChipSelect, // Lower 8-bit is CS1, higher 8-bit is CS2; A byte value of 1= sets CS, 0= ignores this CS setting + ULONG iIsAutoDeativeCS, // Lower 16-bit is CS1, higher 16-bit is CS2; auto undo chip selection or not after operation is completed + ULONG iActiveDelay, // Lower 16-bit is CS1, higher 16-bit is CS2; set the delay time of performing read/write operations after chip selection, in us + ULONG iDelayDeactive); // Lower 116-bit is CS1, higher 16-bit is CS2; do not set the delay time of performing read/write operations after chip selection, in us + +// SPI4 write data +BOOL WINAPI CH347SPI_Write(ULONG iIndex, // Specify device serial number + ULONG iChipSelect, // Chip select control, bit7=0: ignore chip select control, bit7=1: perform chip select + ULONG iLength, // Number of data bytes ready to be transferred + ULONG iWriteStep, // Length of a single block to be read + PVOID ioBuffer); // Point to a buffer, place the data to be written out from MOSI + +// SPI4 read data, no need to write data first, much more efficient than CH347SPI_WriteRead +BOOL WINAPI CH347SPI_Read(ULONG iIndex, // Specify device serial number + ULONG iChipSelect, // Chip select control, bit7=0: ignore chip select control, bit7=1: perform chip select + ULONG oLength, // Number of data bytes ready to be transferred + PULONG iLength, // Number of data bytes of data to be read + PVOID ioBuffer); // Points to a buffer, place the data to be written out from DOUT, returned data is read from DIN + +// Handle SPI data stream, 4-wire interface +BOOL WINAPI CH347SPI_WriteRead(ULONG iIndex, // Specify device serial number + ULONG iChipSelect, // Chip select control, bit7=0: ignore chip select control, bit7=1: perform chip select + ULONG iLength, // Number of data bytes ready to be transferred + PVOID ioBuffer); // Points to a buffer that place the data to be written out from DOUT, returned data is read from DIN + +// Handle SPI data stream, 4-wire interface +BOOL WINAPI CH347StreamSPI4(ULONG iIndex, // Specify device serial number + ULONG iChipSelect, // Chip select control, bit7=0: ignore chip select control, bit7=1: parameter is valid + ULONG iLength, // Number of data bytes ready to be transferred + PVOID ioBuffer); // Points to a buffer, places data to be written out from DOUT, returned data is read from DIN + +/***************JTAG********************/ +// JTAG interface initialization, set mode and speed +BOOL WINAPI CH347Jtag_INIT(ULONG iIndex, + UCHAR iClockRate); // Communication speed; valid values are 0-5, larger values indicating faster speeds + +// Gets Jtag speed configuration +BOOL WINAPI CH347Jtag_GetCfg(ULONG iIndex, // Specify device serial number + UCHAR *ClockRate); // Communication speed; valid values are 0-5, larger values indicating faster speeds + +// Changing the TMS value for state switching +BOOL WINAPI CH347Jtag_TmsChange(ULONG iIndex, // Specify device serial number + PUCHAR tmsValue, // TMS bit value for switching, in bytes + ULONG Step, // Number of valid TMS bits stored in tmsValue + ULONG Skip); // Valid start bit + +// Read/write data in the shift-dr/ir state, switch to Exit DR/IR state after execution. +// State machine: Shift-DR/ ir.rw.->Exit DR/IR +BOOL WINAPI CH347Jtag_IoScan(ULONG iIndex, + PUCHAR DataBits, // Data bits to be transmitted + ULONG DataBitsNb, // Number of bits of data to be transmitted + BOOL IsRead); // Read data or not + +// Switch to shift-dr/ir state for read/write, after execution is complete, If it is the last packet, then shift to Exit DR/IR; if not, then in Shift-DR/IR +// State machine :Shift-DR/IR.RW.. ->[Exit DR/IR] +BOOL WINAPI CH347Jtag_IoScanT(ULONG iIndex, // Specify the device number + PUCHAR DataBits, // Data bits to be transmitted + ULONG DataBitsNb, // Number of bits of data to be transmitted + BOOL IsRead, // Read data or not + BOOL IsLastPkt); // Is it the last packet or not + +// CH347 reset Tap state function. six or more consecutive TCKs & TMS are high��will set the state machine to Test-Logic Reset +ULONG WINAPI CH347Jtag_Reset(ULONG iIndex); + +// CH347 operates TRST to complete a hardware reset +BOOL WINAPI CH347Jtag_ResetTrst(ULONG iIndex, BOOL TRSTLevel); + +// Bit band mode JTAG IR/DR data read/write, suitable for read/write small amounts of data. Such as command operation, state machine switching and other control transfer. For batch data transmission, recommended CH347Jtag_WriteRead_Fast +// Command packages are read/write in batches of 4096 bytes +// State machine: Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead(ULONG iIndex, // Specify device serial number + BOOL IsDR, // =TRUE: DR data read/write, =FALSE:IR data read/write + ULONG iWriteBitLength, // Write length, the length to be written + PVOID iWriteBitBuffer, // Points to a buffer to place data ready to be written out + PULONG oReadBitLength, // Points to the length unit, returned length is the actual length read + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +// JTAG IR/DR data batch read/write, for multi-byte sequential read/write. Such as JTAG download firmware. Because the hardware has a 4K buffer, if you write first and read later, the length will not exceed 4096 bytes. The buffer size can be adjusted by yourself +// State machine: Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead_Fast(ULONG iIndex, // Specify device serial number + BOOL IsDR, // =TRUE: DR data read/write, =FALSE:IR data read/write + ULONG iWriteBitLength, // Write length, length to be written + PVOID iWriteBitBuffer, // Points to a buffer to place data ready to be written out + PULONG oReadBitLength, // Point to the length unit, returned length is the actual length read. + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +// Bitband mode JTAG IR/DR data read/write, suitable for read/write small amounts of data. Such as command operation, state machine switching and other control transfer. For batch data transmission, recommended CH347Jtag_WriteRead_Fast +// Command packages are read/write in batches of 4096 bytes +// State machine:Run-Test-> Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteReadEx(ULONG iIndex, // Specify device serial number + BOOL IsInDrOrIr, // =TRUE: in SHIFT-DR read/write ==FALSE: Run-Test->Shift-IR/DR.(perform data interaction).->Exit IR/DR -> Run-Test + BOOL IsDR, // =TRUE: DR data read/write, =FALSE:IR data read/write + ULONG iWriteBitLength, // Write length, The length to be written + PVOID iWriteBitBuffer, // Points to a buffer to place data ready to be written out + PULONG oReadBitLength, // Point to the length unit, returned length is the actual length read + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +// JTAG IR/DR data batch read/write, for multi-byte sequential read/write. Such as JTAG download firmware. Because the hardware has a 4K buffer, if you write first and read later, the length will not exceed 4096 bytes. The buffer size can be adjusted by yourself +// State machine:Run-Test->Shift-IR/DR..->Exit IR/DR -> Run-Test +BOOL WINAPI CH347Jtag_WriteRead_FastEx(ULONG iIndex, // Specify the device number + BOOL IsInDrOrIr, // =TRUE: In SHIFT-DR read/write ==FALSE: Run-Test->Shift-IR/DR.(perform data interaction).->Exit IR/DR -> Run-Test + BOOL IsDR, // =TRUE: DR read/write, =FALSE:IR data read/write + ULONG iWriteBitLength, // Write length. The length to be written + PVOID iWriteBitBuffer, // Points to a buffer to place data ready to be written out + PULONG oReadBitLength, // Point to the length unit, returned length is the actual length read + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +// Switch the JTAG state machine +BOOL WINAPI CH347Jtag_SwitchTapState(UCHAR TapState); + +// JTAG DR Write, in bytes, for multi-byte sequential read/write. such as JTAG firmware download operations. +// State machine: Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_ByteWriteDR(ULONG iIndex, // Specify the device serial number + ULONG iWriteLength, // Write length, length of bytes to be written + PVOID iWriteBuffer); // Points to a buffer, place the data ready to be written out + +// JTAG DR read, in bytes, for multi-byte sequential read. +// State machine: Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_ByteReadDR(ULONG iIndex, // Specify the device serial number + PULONG oReadLength, // Points to the length unit, returned length is the actual length read + PVOID oReadBuffer); // Points to a buffer large enough to place the read data + +// JTAG IR write, in bytes, for multi-byte sequential write. +// State machine: Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_ByteWriteIR(ULONG iIndex, // Specify device serial number + ULONG iWriteLength, // Write length, the length of bytes to be written + PVOID iWriteBuffer); // Points to a buffer, place the data ready to be written out + +// JTAG IR read, in bytes, for multi-byte sequential read/write. +// The state machine: Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_ByteReadIR(ULONG iIndex, // Specify device serial number + PULONG oReadLength, // Points to the length unit, returned data is the length of actual bytes read. + PVOID oReadBuffer); // Points to a buffer large enough to place the read data + +// Bitband mode JTAG DR data write. suitable for read/write small amounts of data. Such as command operation, state machine switching and other control transfer. For batch data transmission, recommended CH347Jtag_ByteWriteDR +// The state machine: Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_BitWriteDR(ULONG iIndex, // Specify device serial number + ULONG iWriteBitLength, // Points to the length unit, returned data is the length of actual bytes read. + PVOID iWriteBitBuffer); // Points to a buffer large enough to place the read data + +// Bit band mode JTAG IR data write. Suitable for read/write small amounts of data. Such as command operation, state machine switching and other control transfer. For batch data transmission, recommended CH347Jtag_ByteWriteIR +// The state machine: Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_BitWriteIR(ULONG iIndex, // Specify device serial number + ULONG iWriteBitLength, // Points to the length unit, returned data is the length of actual bytes read. + PVOID iWriteBitBuffer); // Points to a buffer large enough to place the read data + +// Bit band mode JTAG IR data read. Suitable for reading and writing small amounts of data. Such as command operation, state machine switching, etc. For batch data transfer, CH347Jtag_ByteReadIR is recommended. +// The state machine: Run-Test->Shift-IR..->Exit IR -> Run-Test +BOOL WINAPI CH347Jtag_BitReadIR(ULONG iIndex, // Specify device serial number + PULONG oReadBitLength, // Points to the length unit, returned data is the length of actual bytes read. + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +// Bit band mode JTAG DR data read. Suitable for reading and writing small amounts of data. For batch and high-speed data transfer, CH347Jtag_ByteReadDR is recommended +// The state machine: Run-Test->Shift-DR..->Exit DR -> Run-Test +BOOL WINAPI CH347Jtag_BitReadDR(ULONG iIndex, // Specify device serial number + PULONG oReadBitLength, // Points to the length unit, returned data is the length of actual bytes read. + PVOID oReadBitBuffer); // Points to a buffer large enough to place the read data + +/***************GPIO********************/ +// Get the GPIO direction and pin level of CH347 +BOOL WINAPI CH347GPIO_Get(ULONG iIndex, + UCHAR *iDir, // Pin direction: GPIO0-7 corresponding bit0-7, 0: input; 1: output + UCHAR *iData); // GPIO0 level: GPIO0-7 corresponding bit0-7, 0: low level; 1: high level + +// Set the GPIO direction and pin level of CH347 +BOOL WINAPI CH347GPIO_Set(ULONG iIndex, // Specify device serial number + UCHAR iEnable, // Data valid flag: corresponding bit0-7, correspond to GPIO0-7. + UCHAR iSetDirOut, // Sets I/O direction, A bit is 0 means corresponding pin is input, a bit is 1 means corresponding pin is output. GPIO0-7 corresponds to bit0-7. + UCHAR iSetDataOut); // Output data. If I/O direction is output, then a pin outputs low level when a bit cleared to 0, a pin outputs high level when a bit set to 1 + +typedef VOID(CALLBACK *mPCH347_INT_ROUTINE)( // Interrupt service routine + PUCHAR iStatus); // For interrupt status data, refer to the bit description below +// 8 byte GPIO0-7 pin status, the bits per byte are defined as follows. +// Bit7:Current GPIO0 direction, 0:Input; 1:Output +// Bit6:Current GPIO0 level, 0:Low level;1:High level +// Bit5:Whether the current GPIO0 is set to interrupt; 0:Query mode; 1:Interrupt mode +// Bit4-3:Set the GPIO0 interrupt mode, 00:Falling edge trigger; 01:Rising edge trigger; 10:Double edge trigger;11: reserved; +// Bit2-0:reserved; + +// Set GPIO interrupt service routine +BOOL WINAPI CH347SetIntRoutine(ULONG iIndex, // Specify the device serial number + UCHAR Int0PinN, // INT0 GPIO pin number greater than 7: disable this interrupt source; 0-7 corresponds to GPIO0-7 + UCHAR Int0TripMode, // INT0 type: 00:Falling edge trigger; 01:Rising edge trigger; 02:Double edge trigger; 03:reserve; + UCHAR Int1PinN, // INT1 GPIO pin number. If it is greater than 7, disable this interrupt source; 0-7 corresponds to GPIO0-7 + UCHAR Int1TripMode, // INT1 type: 00:Falling edge trigger; 01:Rising edge trigger; 02:Double edge trigger; 03:reserve; + mPCH347_INT_ROUTINE iIntRoutine); // Specify the interrupt service routine, if it is NULL, the interrupt service is canceled, otherwise, the program is called when interrupted. + +// Read interrupt data +BOOL WINAPI CH347ReadInter(ULONG iIndex, // Specify the device serial number + PUCHAR iStatus); // Point to the 8-byte unit, used to save read GPIO pin status data, refer to the following bit description + +// Abandon interrupt data read operation +BOOL WINAPI CH347AbortInter(ULONG iIndex); // Specify the device serial number + +// Enter IAP firmware upgrade mode +BOOL WINAPI CH347StartIapFwUpate(ULONG iIndex, + ULONG FwSize); // Firmware length + +/**************HID/VCP Serial Port*********************/ +// Open serial port +HANDLE WINAPI CH347Uart_Open(ULONG iIndex); + +// Close serial port +BOOL WINAPI CH347Uart_Close(ULONG iIndex); + +// Set device event notification program +BOOL WINAPI CH347Uart_SetDeviceNotify( + ULONG iIndex, // Specify the device serial number, 0 corresponds to the first device + PCHAR iDeviceID, // Optional parameter, points to a string, specify the monitored device ID, end with \0 + mPCH347_NOTIFY_ROUTINE iNotifyRoutine); // Specifies device event callback program; if it is NULL then cancel event notification, otherwise the program is called when an event is detected. + +// Obtain UART hardware configuration +BOOL WINAPI CH347Uart_GetCfg(ULONG iIndex, // Specify the device serial number + PULONG BaudRate, // Baud rate + PUCHAR ByteSize, // Data bits (5,6,7,8,16) + PUCHAR Parity, // Parity bits(0:None; 1:Odd; 2:Even; 3:Mark; 4:Space) + PUCHAR StopBits, // Stop bits (0: 1 stop bits; 1: 1.5 stop bit; 2: 2 stop bit); + PUCHAR ByteTimeout); // Byte timeout + +// Set UART configuration +BOOL WINAPI CH347Uart_Init(ULONG iIndex, // Specify the device serial number + DWORD BaudRate, // Baud rate + UCHAR ByteSize, // Data bits (5,6,7,8,16) + UCHAR Parity, // Parity bits (0:None; 1:Odd; 2:Even; 3:Mark; 4:Space) + UCHAR StopBits, // Stop bits (0: 1 Stop bit; 1: 1.5 stop bit; 2: 2 stop bit); + UCHAR ByteTimeout); // Byte timeout, in unit of 100uS + +// Set USB data read/write timeout +BOOL WINAPI CH347Uart_SetTimeout(ULONG iIndex, // Specify the device serial number + ULONG iWriteTimeout, // Specify the timeout for USB to write data blocks, in mS,0xFFFFFFFF specifies no timeout (default) + ULONG iReadTimeout); // Specify the timeout for USB to read data blocks, in mS,0xFFFFFFFF specifies no timeout (default) + +// Read data block +BOOL WINAPI CH347Uart_Read(ULONG iIndex, // Specify the device serial number + PVOID oBuffer, // Points to a buffer large enough to place the read data + PULONG ioLength); // Refers to the length unit, the input is the length to be read, the return is the actual length to be read + +// Write data blocks +BOOL WINAPI CH347Uart_Write(ULONG iIndex, // Specify the device serial number + PVOID iBuffer, // Points to a buffer to place data ready to be written out + PULONG ioLength); // Point to the length unit, the input is the intended length, the return is the actual length + +// Query how many bytes are unfetched in read buffer +BOOL WINAPI CH347Uart_QueryBufUpload(ULONG iIndex, // Specify the device serial number + LONGLONG *RemainBytes); // Read buffer unfetched bytes + +// Obtain device information +BOOL WINAPI CH347Uart_GetDeviceInfor(ULONG iIndex, mDeviceInforS *DevInformation); + +// Set USB data read/write timeout +BOOL WINAPI CH347Uart_SetTimeout(ULONG iIndex, // Specify device serial number + ULONG iWriteTimeout, // Specify timeout for USB to write data block, in mS, 0xFFFFFFFF specifies no timeout (default) + ULONG iReadTimeout); // Specify timeout for USB to read data block, in mS, 0xFFFFFFFF specifies no timeout (default) +/********I2C***********/ +// I2C settings +BOOL WINAPI CH347I2C_Set(ULONG iIndex, // Specify device serial number + ULONG iMode); // Specify mode, see next line +// bit1 - bit 0: I2C interface speed/SCL frequency, 00=low speed/20KHz, 01=standard/100KHz(default), 10= fast/400KHz, 11= high speed/750KHz +// Others are reserved, must be 0 + +// Set I2C clock stretch +BOOL WINAPI CH347I2C_SetStretch(ULONG iIndex, // Specify device serial number + BOOL iEnable); // I2C Clock Stretch enable, 1:enable,0:disable + +// Set hardware asynchronous delay, returns shortly after being called, and then delays for specified milliseconds before the next stream operation. +BOOL WINAPI CH347I2C_SetDelaymS(ULONG iIndex, // Specify device serial number + ULONG iDelay); // Specify the delay in milliseconds + +// Process I2C data stream, 2-wire interface, clock wire is SCL pin, data wire is SDA pin +BOOL WINAPI CH347StreamI2C(ULONG iIndex, // Specify device serial number + ULONG iWriteLength, // Number of data bytes to be written + PVOID iWriteBuffer, // Points to a buffer to place data ready to be written out, first byte is usually the I2C device address and read/write direction bit + ULONG iReadLength, // Number of bytes of data to be read + PVOID oReadBuffer); // Points to a buffer, returned data is the read-in data + +// Process I2C data stream, 2-wire interface, clock wire is SCL pin, data wire is SDA pin (standard bidirectional I/O), speed about 56K bytes, and return the number of ACKs obtained by the host side +BOOL WINAPI CH347StreamI2C_RetACK(ULONG iIndex, // Specify device serial number + ULONG iWriteLength, // Number of data bytes to be written + PVOID iWriteBuffer, // Points to a buffer to place data ready to be written out, first byte is usually the I2C device address and read/write direction bit + ULONG iReadLength, // Number of bytes of data to be read + PVOID oReadBuffer, // Points to a buffer, returned data is the read-in data + PULONG rAckCount); // Points to the ACK value returned by read/write + +#ifndef _CH341_DLL_H +typedef enum _EEPROM_TYPE { // EEPROM type + ID_24C01, + ID_24C02, + ID_24C04, + ID_24C08, + ID_24C16, + ID_24C32, + ID_24C64, + ID_24C128, + ID_24C256, + ID_24C512, + ID_24C1024, + ID_24C2048, + ID_24C4096 +} EEPROM_TYPE; +#endif + +// Read data blocks from EEPROM, speed about 56KB +BOOL WINAPI CH347ReadEEPROM(ULONG iIndex, // Specify device serial number + EEPROM_TYPE iEepromID, // Specify EEPROM model + ULONG iAddr, // Specify data unit address + ULONG iLength, // Number of data bytes to be read + PUCHAR oBuffer); // Points to a buffer, returned data is the read-in data + +// Write data block to the EEPROM +BOOL WINAPI CH347WriteEEPROM(ULONG iIndex, // Specify device serial number + EEPROM_TYPE iEepromID, // Specify EEPROM model + ULONG iAddr, // Specify data unit address + ULONG iLength, // Number of data bytes to be written out + PUCHAR iBuffer); // Points to a buffer to place data ready to be written out + +#ifdef __cplusplus +} +#endif + +#endif // _CH347_DLL_H diff --git a/FlashGBX/bacon/lib/CH347DLL.DLL b/FlashGBX/bacon/lib/CH347DLL.DLL new file mode 100644 index 0000000000000000000000000000000000000000..7cd5b1ed9a0cccb9c3b65b0ee2c855a478335fae GIT binary patch literal 71680 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 M&7y)Dk(0>(0ko)8g#Z8m literal 0 HcmV?d00001 diff --git a/FlashGBX/bacon/lib/CH347DLLA64.DLL b/FlashGBX/bacon/lib/CH347DLLA64.DLL new file mode 100644 index 0000000000000000000000000000000000000000..3ba5295974a2a25e42f9da4f5289de63ac01208b GIT binary patch literal 103928 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⋘%(~ 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
  • LaPWCYn~QrI0Eyix8CBa z^Ky9}MO<47Ij5>`J>b>%8>@*TB+8gE)`>hdgLlY|k(aAl;!Kk3XLfxjMmtTn}x)-t(hpp$&_4-ajYhq#R^Phwh2(?yCKu99V2u3HRK)E!?>J_c zcobx~6Qb|oDPeFf$m85|%3kxKgiZW_PGNdoR85g~!VVSFePJ}us}3#<%2 z*3(ZZ<4?RaR&8;AG>VZ*!vq1Cz>I*uS+5U#*-t=#2PMFp8OVD8a4=B(z$g+(6d+;z zqeBw-+5w;esh`XS20;h{`~brr}gj*h;C>@^ueHs#)cF>oBpBEp4!OK(}&ot)I;@lZ+Rbw3Ae5$VzINK8= zQ#I05m!>AA=NCY#@;o)8_qE^BsvzSQt6@9~Iy;#6j>I|M#BGeaibQ?DKods|GOrpmreTCI20l`xD5m8G2~^gkxo} z>Rjd)bj8~`m!UAkrjM`3jz`JVn|k>?fZ0B<0;mWAGYep*0n7w|sRbYEJ*V8c>?T-WoEkRUNpr?(q||O{1yu}` ze9;=66c4YWyW1I|l$Bn4vo^uJ*7yF|R6YO=CTYPl|rG;t^NG%2oZ#sKUi zncatXO2R6e%8qO(N<`Qo3OFO@R~oXdJK*LXT*^iC_m?_J+4SEwJsUE%Y}u?F9kZos zA6hv_YYuQ{U#i%kIQGg;Uv%Um-ia6rR`g9v?5Vb1RjunbckJovrR_Cea@RW_f8i$) zrI(*ELLgx|t?5r5-*}Za<%R~go7V|OEr%W952nyParWw@ zvRO>5WK8xqNdeH@zG%1RN6VWl1(wU59O4KJUrV{N2DfkFz{n_?>}j)Lo6$&PNF>%r zCZjHN6TdXet)*L3d6r;FPwBZ~Xb|u6fch}hnbDDOFp>X7xu~|Mk#EXEB6_cH#WZxw ae$Sirfg6P&%u;6-du+g3H}4bQ)V~0)skzty literal 0 HcmV?d00001 diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py new file mode 100644 index 0000000..cfae8aa --- /dev/null +++ b/FlashGBX/bacon/serial.py @@ -0,0 +1,356 @@ +# -*- coding: utf-8 -*- +# bacon +# Author: ChisBread (github.com/ChisBread) +import zlib +from .bacon import BaconDevice +DEBUG = False +DEVICE_CMD = {} +CMD_TO_NAME = {} +DEVICE_VAR = {} +VARKEY_TO_NAME = {} + +FLASH_TYPES = { + "AMD": 0x01, + "INTEL": 0x02, + "SHARP": 0x02, + "OTHERS":0x00, +} + +FLASH_MODS = { + "FLASH_METHOD_AGB_FLASH2ADVANCE": 0x05, + "FLASH_METHOD_DMG_MMSA": 0x03, + "FLASH_METHOD_DMG_DATEL_ORBITV2": 0x09, + "FLASH_METHOD_DMG_E201264": 0x0A, + "FLASH_METHOD_AGB_GBAMP": 0x0B, + "FLASH_METHOD_DMG_BUNG_16M": 0x0C, + "FLASH_METHOD_BUFFERED": 0x02, + "FLASH_METHOD_PAGED": 0x08, + "FLASH_METHOD_UNBUFFERED": 0x01, +} + +def SetDebug(debug: bool): + global DEBUG + DEBUG = debug + +def SetDeviceCMD(device_cmd: dict, device_var: dict): + DEVICE_CMD = device_cmd + for key in device_cmd.keys(): + CMD_TO_NAME[device_cmd[key]] = key + for key in device_var.keys(): + VARKEY_TO_NAME[tuple(device_var[key])] = key + +def ParseCommand(cmd: bytes): + if len(cmd) == 0: + return None + return CMD_TO_NAME.get(cmd[0], "UNKNOWN") + +def dprint(*args, **kwargs): + if DEBUG: + print(*args, **kwargs) + +# | 地址范围 | 功能 | 大小 | 说明 | +# |------------|-----------|---------|------| +# | 08000000-09FFFFFF | ROM/FlashROM | 32MB | ROM/FlashROM的地址空间(等待状态0) | +# | 0A000000-0BFFFFFF | ROM/FlashROM | 32MB | ROM/FlashROM的地址空间(等待状态1) | +# | 0C000000-0DFFFFFF | ROM/FlashROM | 32MB | ROM/FlashROM的地址空间(等待状态2) | +# | 0E000000-0E00FFFF | SRAM | 64KB | SRAM的地址空间(8位总线) | + +def MappingAddressToReal(addr): + if addr >= 0x08000000 and addr <= 0x09FFFFFF: + return addr - 0x08000000 + if addr >= 0x0A000000 and addr <= 0x0BFFFFFF: + return addr - 0x0A000000 + if addr >= 0x0C000000 and addr <= 0x0DFFFFFF: + return addr - 0x0C000000 + if addr >= 0x0E000000 and addr <= 0x0E00FFFF: + return addr - 0x0E000000 + return addr + +# 模拟串口设备,兼容GBX协议 +class BaconFakeSerialDevice: + def __init__(self): + self.timeout = 1 + + self.in_buff = b"" * 0x4000 + self.in_buff_size = 0x4000 + self.out_buff = b"\x00" * 0x4000 + self.out_buff_offset = 0 + self.out_buff_size = 0x4000 + + self.in_waiting = 0 + + self.bacon_dev = BaconDevice() + self.is_open = True + + self.FLASH_CMD_TYPE = 0x00 + self.FLASH_CMD_MOD = 0x00 + self.FLASH_CMD_WE = 0x00 + self.FLASH_CUSTOM_CMDS = [0x00]*6 + + self.FW_VARS = {} + self.MODE = "AGB" # or DMG + self.POWER = 0 + self.AGB_SRAM_WRITING = False + self.CALC_CRC32_WAITING = False + self.FLASH_PROGRAMMING = False + self.SET_FLASH_CMD_WAITING = 0 + + def isOpen(self): + return self.bacon_dev is not None + + def close(self): + if self.bacon_dev is not None: + self.bacon_dev.Close() + self.bacon_dev = None + self.reset_input_buffer() + self.reset_output_buffer() + self.is_open = False + + def open(self): + self.bacon_dev = BaconDevice() + self.is_open = True + + def reset_input_buffer(self): + self.in_buff = b"" + self.in_waiting = 0 + + def reset_output_buffer(self): + self.out_buff_offset = 0 + + def push_to_input_buffer(self, data): + dprint("[BaconFakeSerialDevice] PushData:%s" % data.hex()) + self.in_buff = self.in_buff + data + self.in_waiting = len(data) + + def _push_ack(self): + self.push_to_input_buffer(b"\x01") + + def write(self, data): + self._cmd_parse(data) + return len(data) + + def _cmd_parse(self, cmd): + if self.AGB_SRAM_WRITING: + if self.in_waiting > 0: + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_SRAM:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) + self.bacon_dev.AGBWriteRAM(addr, cmd) + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] + return + else: + self.AGB_SRAM_WRITING = False + if self.CALC_CRC32_WAITING: + chunk_size = int.from_bytes(cmd, byteorder='big') + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) + dprint("[BaconFakeSerialDevice] CALC_CRC32:0x%08X Size:0x%08X" % (self.FW_VARS["ADDRESS"], chunk_size)) + ret = self.bacon_dev.AGBReadROM(addr, chunk_size) + crc32 = zlib.crc32(ret) + # push crc32 4byte big-endian + self.push_to_input_buffer(crc32.to_bytes(4, byteorder='big')) + self.CALC_CRC32_WAITING = False + return + if self.FLASH_PROGRAMMING: + #TODO: More AGB Flash Type, And DMG Flash + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) + size = self.FW_VARS["TRANSFER_SIZE"] + buffer_size = self.FW_VARS["BUFFER_SIZE"] + dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING:0x%08X ValueSize:%s TransferSize:%s BufferSize:%s" % (self.FW_VARS["ADDRESS"], len(cmd), size, buffer_size)) + if self.FLASH_CMD_MOD == FLASH_MODS["FLASH_METHOD_BUFFERED"] and buffer_size > 0: + # per buffer Seq + for i in range(0, size, buffer_size): + # make flash cmds + flash_prepare = [] + flash_commit = [] + for j in range(6): + tcmd = (self.FLASH_CUSTOM_CMDS[j][0]<<1, self.FLASH_CUSTOM_CMDS[j][1]) + flash_prepare.append(tcmd) + + if flash_prepare[-1] == (0x00, 0x00): # write buffer size? + flash_prepare[-1] = (addr, buffer_size//2-1) + for k in range(j+1, 6): + # commit + if not (self.FLASH_CUSTOM_CMDS[k] == (0x00, 0x00)): + tcmd = (self.FLASH_CUSTOM_CMDS[k][0]<<1, self.FLASH_CUSTOM_CMDS[k][1]) + flash_commit.append(tcmd) + if flash_commit[-1][0] == 0x00: + flash_commit[-1] = (addr, flash_commit[-1][1]) + break + elif flash_prepare[-1][0] == 0x00: + flash_prepare[-1] = (addr, flash_prepare[-1][1]) + #dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING Prepare:%s Commit:%s" % ([(hex(i[0]), hex(i[1])) for i in flash_prepare], [(hex(i[0]), hex(i[1])) for i in flash_commit])) + self.bacon_dev.AGBWriteROMWithAddress(commands=flash_prepare) + self.bacon_dev.AGBWriteROMSequential(addr=addr, data=cmd[i:i+buffer_size]) + self.bacon_dev.AGBWriteROMWithAddress(commands=flash_commit).Flush() #这里有200us的延迟, 怎么也够了 + #TODO 校验 + addr += buffer_size + else: + # write with cmd + pass + self._push_ack() + self.FLASH_PROGRAMMING = False + self.FW_VARS["ADDRESS"] += size // 2 + return + if self.SET_FLASH_CMD_WAITING > 0: + if self.SET_FLASH_CMD_WAITING == 9: + self.FLASH_CMD_TYPE = int.from_bytes(cmd, byteorder='big') + elif self.SET_FLASH_CMD_WAITING == 8: + self.FLASH_CMD_MOD = int.from_bytes(cmd, byteorder='big') + elif self.SET_FLASH_CMD_WAITING == 7: + self.FLASH_CMD_WE = int.from_bytes(cmd, byteorder='big') + elif self.SET_FLASH_CMD_WAITING <= 6: + # 0~3: addr + # 4~5: cmd + self.FLASH_CUSTOM_CMDS[6-self.SET_FLASH_CMD_WAITING] = (int.from_bytes(cmd[:4], byteorder='big'), int.from_bytes(cmd[4:], byteorder='big')) + dprint("[BaconFakeSerialDevice] SET_FLASH_CMD: Type:%s Mod:%s WE:%s Cmds:%s" % (self.FLASH_CMD_TYPE, self.FLASH_CMD_MOD, self.FLASH_CMD_WE, self.FLASH_CUSTOM_CMDS)) + self.SET_FLASH_CMD_WAITING = self.SET_FLASH_CMD_WAITING - 1 + if self.SET_FLASH_CMD_WAITING == 0: + self._push_ack() + return + + cmdname = ParseCommand(cmd) + + if cmdname == 'SET_VARIABLE': + # 0: cmd + # 1: size + # 2-5: key + # 6-: value + size = int(cmd[1])*8 + key = int.from_bytes(cmd[2:6], byteorder='big') + value = int.from_bytes(cmd[6:], byteorder='big') + # save + self.FW_VARS[VARKEY_TO_NAME.get((size, key), "UNKNOWN")] = value + self._push_ack() + dprint("[BaconFakeSerialDevice] SetVariable:", VARKEY_TO_NAME.get((size, key), "UNKNOWN"), value) + elif cmdname == "GET_VARIABLE": + # 0: cmd + # 1: size + # 2-5: key + size = int(cmd[1]) + key = int.from_bytes(cmd[2:6], byteorder='big') + # get + value = self.FW_VARS.get(VARKEY_TO_NAME.get((size, key), "UNKNOWN"), 0) + self.push_to_input_buffer(bytes([(value >> (i*8)) & 0xFF for i in range(4)])) + elif cmdname == "SET_MODE_AGB": + self.MODE = "AGB" + self._push_ack() + elif cmdname == "SET_VOLTAGE_3_3V": + self.POWER = 3 + self._push_ack() + elif cmdname == "CART_PWR_ON": + if self.POWER == 3: + self.bacon_dev.PowerControl(v3_3v=True, v5v=False).Flush() + elif self.POWER == 5: + self.bacon_dev.PowerControl(v3_3v=False, v5v=True).Flush() + self._push_ack() + elif cmdname == "CART_PWR_OFF": + self.bacon_dev.PowerControl(v3_3v=False, v5v=False).Flush() + self._push_ack() + elif cmdname == "QUERY_CART_PWR": + self.push_to_input_buffer(bytes([self.bacon_dev.power])) + dprint("[BaconFakeSerialDevice] QueryCartPower:", self.bacon_dev.power) + elif cmdname == "AGB_CART_READ_SRAM": + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) + dprint("[BaconFakeSerialDevice] AGB_CART_READ_SRAM:0x%08X(0x%08X) Size:%d" % (self.FW_VARS["ADDRESS"], addr, self.FW_VARS["TRANSFER_SIZE"])) + ret = self.bacon_dev.AGBReadRAM(addr, self.FW_VARS["TRANSFER_SIZE"]) + self.push_to_input_buffer(ret) + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] + elif cmdname == "AGB_CART_WRITE_SRAM": + self.AGB_SRAM_WRITING = True + self._push_ack() + elif cmdname == "AGB_CART_WRITE_FLASH_DATA": + # serious???? + self.AGB_SRAM_WRITING = True + self._push_ack() + elif cmdname == "CALC_CRC32": # 读取一段数据,计算CRC32 + # 0: cmd + # 1~4: chunk_size + self.CALC_CRC32_WAITING = True + elif cmdname == "AGB_CART_READ": + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) + dprint("[BaconFakeSerialDevice] AGB_CART_READ:0x%08X(0x%08X) Size:%d" % (self.FW_VARS["ADDRESS"], addr, self.FW_VARS["TRANSFER_SIZE"])) + ret = self.bacon_dev.AGBReadROM(addr, self.FW_VARS["TRANSFER_SIZE"]) + if ret is not False: + self.push_to_input_buffer(ret) + if self.MODE == "AGB": + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"]//2 + else: + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] + elif cmdname == "AGB_FLASH_WRITE_SHORT": + # 0: cmd + # 1~4: addr + # 5~6: short + dprint("[BaconFakeSerialDevice] AGB_FLASH_WRITE_SHORT:0x%08X Value:%s" % (int.from_bytes(cmd[1:5], byteorder='big'), hex(int.from_bytes(cmd[5:7], byteorder='big')))) + addr = int.from_bytes(cmd[1:5], byteorder='big') + addr = MappingAddressToReal(addr<<1) + self.bacon_dev.AGBWriteROMWithAddress(commands=[(addr, int.from_bytes(cmd[5:7], byteorder='big'))]).Flush() + self._push_ack() + elif cmdname == "CART_WRITE_FLASH_CMD": + # 0: cmd + # 1: flashcart + # 2: num + # 6byte * num: cmds(4byte addr, 2byte cmd) + flashcart = int(cmd[1]) + num = int(cmd[2]) + cmds = [] + for i in range(num): + addr = int.from_bytes(cmd[3+i*6:7+i*6], byteorder='big') + #if self.MODE == "AGB" and flashcart: + addr = MappingAddressToReal(addr<<1) + data = int.from_bytes(cmd[7+i*6:9+i*6], byteorder='big') + dprint("[BaconFakeSerialDevice] CART_WRITE_FLASH_CMD:0x%08X Value:%s" % (addr, hex(data))) + cmds.append((addr, data)) + self.bacon_dev.AGBWriteROMWithAddress(commands=cmds).Flush() + self._push_ack() + elif cmdname == "FLASH_PROGRAM": + self.FLASH_PROGRAMMING = True + # self._push_ack() 0x03? + elif cmdname == "SET_FLASH_CMD": + self.SET_FLASH_CMD_WAITING = 9 + elif cmdname == "AGB_CART_WRITE": + # 0: cmd + # 1~4: addr + # 5~6: short + addr = int.from_bytes(cmd[1:5], byteorder='big') + addr = MappingAddressToReal(addr<<1) + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE:0x%08X Value:%s" % (addr, hex(int.from_bytes(cmd[5:7], byteorder='big')))) + self.bacon_dev.AGBWriteROMWithAddress(commands=[(addr, int.from_bytes(cmd[5:7], byteorder='big'))]).Flush() + self._push_ack() + elif cmdname == "AGB_READ_GPIO_RTC": + dprint("[BaconFakeSerialDevice] !!!! AGB_READ_GPIO_RTC is not implemented !!!!") + self.push_to_input_buffer(b"\x00"*8) + elif cmdname == "ENABLE_PULLUPS": + # TODO + dprint("[BaconFakeSerialDevice] !!!! DISABLE_PULLUPS is not implemented !!!!") + self._push_ack() + elif cmdname == "DISABLE_PULLUPS": + # TODO + dprint("[BaconFakeSerialDevice] !!!! DISABLE_PULLUPS is not implemented !!!!") + self._push_ack() + elif cmdname == "AGB_BOOTUP_SEQUENCE": + # TODO + dprint("[BaconFakeSerialDevice] !!!! AGB_BOOTUP_SEQUENCE is not implemented !!!!") + self._push_ack() + ###### DMG CMDS ###### + elif cmdname == "DMG_MBC_RESET": + self._push_ack() + elif cmd[0] == 0: + self._push_ack() + else: + dprint("[BaconFakeSerialDevice] UnsupportedCommand:%s Value:%s" % (cmdname, cmd.hex())) + + + def read(self, size: int): + dprint("[BaconFakeSerialDevice] ReadSize:%s, Left:%s" % (size, self.in_waiting)) + if size == 0: + return b"" + if size > self.in_waiting: + size = self.in_waiting + data = self.in_buff[:size] + self.in_buff = self.in_buff[size:] + self.in_waiting = len(self.in_buff) + dprint("[BaconFakeSerialDevice] ReadData:%s size:%s" % ([ hex(i) for i in data[:64] ], size)) + return data + + def flush(self): + # TODO: Parse the command + self.reset_output_buffer() diff --git a/FlashGBX/bacon/test.py b/FlashGBX/bacon/test.py new file mode 100644 index 0000000..519919c --- /dev/null +++ b/FlashGBX/bacon/test.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# bacon +# Author: ChisBread (github.com/ChisBread) + +import time +import os +import sys +# append path +from .bacon import BaconDevice +from .command import * + +bacon_dev = BaconDevice() + +def open_current_dif(filename, mode): + return open(os.path.join(os.path.dirname(__file__), filename), mode) + +def bacon_write_test(): + # 11: 5v + # 00: 3.3v + print("[SPI] write power control command") + powercmd = make_power_control_command(not_v3_3v=False, v5v=False) + print("[SPI] write bytes %s" % bytes2command(powercmd)) + ret = bacon_dev.Write(powercmd) + #True if successful, False otherwise. + print("[SPI] write result %s" % ret) + print("[SPI] write cart 30bit write command") + pintestcmd = make_cart_30bit_write_command( + phi=False, req=False, + wr=False, rd=True, + cs1=False, cs2=True, + v16bit=b"\xFF\xFF", v8bit=b"\xFF" + ) + print("[SPI] write bytes %s" % bytes2command(pintestcmd)) + bacon_dev.Write(pintestcmd) + print("[SPI] write test done") + +def bacon_read_test_30bit(): + power_read_cmd = make_power_read_command() + cart_read_cmd = make_cart_30bit_read_command() + # 测试3.3v + print("[SPI] write power control command") + bacon_dev.Write(make_power_control_command(not_v3_3v=False, v5v=False)) + ret = bacon_dev.WriteRead(power_read_cmd) + print("[SPI] read bytes %s, expected %s" % (bytes2command(ret)[::-1], "00")) + ###### 测试读取卡带数据 ###### + bacon_dev.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + ## CS1上升沿,写入低位地址0x0000 + bacon_dev.Write(make_gba_rom_cs_write(cs=False)) + ret = bacon_dev.WriteRead(cart_read_cmd) + print(extract_cart_30bit_read_data(ret)) + readbytes = [] + avg_start = time.time() + start = time.time() + # 每0x10000,需要对v8bit+1 + # 所以0x10000需要能被BULK_SIZE整除 + high_addr = 0x00 + BULK_SIZE = 512 + TOTAL_SIZE = 0x800000 # 16MB + if TOTAL_SIZE % BULK_SIZE != 0: + raise ValueError("TOTAL_SIZE must be a multiple of BULK_SIZE") + if 0x10000 % BULK_SIZE != 0: + raise ValueError("BULK_SIZE must be a factor of 0x10000") + readcyclecmd = make_rom_read_cycle_command(times=BULK_SIZE) + for i in range(TOTAL_SIZE//BULK_SIZE): + ret = bacon_dev.WriteRead(readcyclecmd) + exteds = extract_read_cycle_data(ret, times=BULK_SIZE) + for idx, exted in enumerate(exteds): + num = i * BULK_SIZE + idx + readbytes.append(exted["v16bit"] >> 8) + readbytes.append(exted["v16bit"] & 0xFF) + if num < 2: + print(f"header bytes: {hex(readbytes[-2])[2:].rjust(2, '0')}, {hex(readbytes[-1])[2:].rjust(2, '0')}") + if num % 0x10000 == 0x10000 - 1: + high_addr += 1 + bacon_dev.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=False, cs2=True, + v16bit=b"\x00\x00", v8bit=bytes([high_addr]) + )) + if num % 0x1000 == 0x1000 - 1: + # print(f"[SPI] high address: 0x{hex(high_addr)[2:].rjust(2, '0')}") + # print(f"[SPI] read 0x2000 bytes in {time.time() - start}s speed(KB/s): {0x2000 / (time.time() - start) / 1024}") + print(f"\rAddress:0x{num+1:06X} Speed(KB/s):{(0x2000 / (time.time() - start) / 1024):.2f}", end="") + start = time.time() + print(f"\n[SPI] read {hex(TOTAL_SIZE*2)} bytes in {time.time() - avg_start}s speed(KB/s): {TOTAL_SIZE*2 / (time.time() - avg_start) / 1024}") + ch347.spi_change_cs(0x00) + with open_current_dif("gba_rom.bin", "wb") as f: + f.write(bytes(readbytes)) + +def bacon_read_test_16bit(): + power_read_cmd = make_power_read_command() + cart_read_cmd = make_cart_30bit_read_command() + # 测试3.3v + print("[SPI] write power control command") + bacon_dev.Write(make_power_control_command(not_v3_3v=False, v5v=False)) + ret = bacon_dev.WriteRead(power_read_cmd) + print("[SPI] read bytes %s, expected %s" % (bytes2command(ret)[::-1], "00")) + ###### 测试读取卡带数据 ###### + bacon_dev.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + ## CS1上升沿,写入低位地址0x0000 + bacon_dev.Write(make_gba_rom_cs_write(cs=False)) + ret = bacon_dev.WriteRead(cart_read_cmd) + print(extract_cart_30bit_read_data(ret)) + readbytes = [] + avg_start = time.time() + start = time.time() + # 每0x10000,需要对v8bit+1 + # 所以0x10000需要能被BULK_SIZE整除 + high_addr = 0x00 + MAX_BULK_SIZE = 0x1000//(len(make_rom_read_cycle_command(times=1))) # buffer不能超过0x1000,但尽量大一点 + bulk_list = [MAX_BULK_SIZE]*(0x10000//MAX_BULK_SIZE) + [0x10000%MAX_BULK_SIZE] + # print(f"[SPI] bulk list: {bulk_list}") + TOTAL_SIZE = 0x800000 # 16MB + for i in range(TOTAL_SIZE//0x10000): + # readcyclecmd = make_rom_read_cycle_command(times=MAX_BULK_SIZE) + # ret = bacon_dev.WriteRead(readcyclecmd) + # exteds = extract_read_cycle_data(ret, times=dynamic_bulk_size) + for j in range(len(bulk_list)): + readcyclecmd = make_rom_read_cycle_command(times=bulk_list[j]) + ret = bacon_dev.WriteRead(readcyclecmd) + exteds = extract_read_cycle_data(ret, times=bulk_list[j]) + for idx, exted in enumerate(exteds): + readbytes.append(exted >> 8) + readbytes.append(exted & 0xFF) + if num < 2: + print(f"header bytes: {hex(readbytes[-2])[2:].rjust(2, '0')}, {hex(readbytes[-1])[2:].rjust(2, '0')}") + if num % 0x10000 == 0x10000 - 1: + high_addr += 1 + bacon_dev.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=False, cs2=True, + v16bit=b"\x00\x00", v8bit=bytes([high_addr]) + )) + if num % 0x1000 == 0x1000 - 1: + # print(f"[SPI] high address: 0x{hex(high_addr)[2:].rjust(2, '0')}") + # print(f"[SPI] read 0x2000 bytes in {time.time() - start}s speed(KB/s): {0x2000 / (time.time() - start) / 1024}") + print(f"\rAddress:0x{num+1:06X} Speed(KB/s):{(0x2000 / (time.time() - start) / 1024):.2f}", end="") + start = time.time() + num += 1 + print(f"\n[SPI] read {hex(TOTAL_SIZE*2)} bytes in {time.time() - avg_start}s speed(KB/s): {TOTAL_SIZE*2 / (time.time() - avg_start) / 1024}") + with open_current_dif("gba_rom.bin", "wb") as f: + f.write(bytes(readbytes)) + +def bacon_test_api(): + bacon_dev.PowerControl(v3_3v=True, v5v=False).Flush() + bacon_dev.AGBWriteRAM(addr=0, data=open_current_dif("gba_bak_ram.bin", "rb").read()) + TOTAL_BYTE_SIZE = 0x800000*2 # 16MB + start = time.time() + avg_start = time.time() + first = True + last_addr = 0 + def callback(addr, data): + nonlocal first, start, avg_start, last_addr + if addr == 0 or (addr % 0x1000 != 0 and addr != TOTAL_BYTE_SIZE): + return + if first: + print(f"header bytes: {hex(data[0])[2:].rjust(2, '0')}, {hex(data[1])[2:].rjust(2, '0')}") + first = False + last_addr = 0 + if addr == TOTAL_BYTE_SIZE: + print(f"\n[SPI] read {hex(TOTAL_BYTE_SIZE)} bytes in {time.time() - avg_start}s speed(KB/s): {TOTAL_BYTE_SIZE / (time.time() - avg_start) / 1024}") + else: + delta = addr - last_addr + print(f"\rAddress:0x{addr:06X} Speed(KB/s):{(delta / (time.time() - start) / 1024):.2f}", end="") + start = time.time() + last_addr = addr + ## 读取卡带存档 + TOTAL_BYTE_SIZE = 0x8000 # 32KB + start = time.time() + avg_start = time.time() + first = True + ram = bacon_dev.AGBReadRAM(addr=0, size=TOTAL_BYTE_SIZE, callback=callback) + with open_current_dif("gba_ram.bin", "wb") as f: + f.write(ram) + ## 写入卡带存档 + new_ram = bytes([0xFF]*TOTAL_BYTE_SIZE) + start = time.time() + avg_start = time.time() + first = True + ret = bacon_dev.AGBWriteRAM(addr=0, data=new_ram, callback=callback) + TOTAL_BYTE_SIZE = 0x8000 # 32KB + start = time.time() + avg_start = time.time() + first = True + new_ram_ret = bacon_dev.AGBReadRAM(addr=0, size=TOTAL_BYTE_SIZE, callback=callback) + #### 判断是否写入成功 + if new_ram_ret == new_ram and new_ram_ret != ram: + print("[SPI] write and read ram success") + else: + print("[SPI] write and read ram failed head 10 bytes: %s, %s" % (new_ram[:10], new_ram_ret[:10])) + ## 恢复卡带存档 + bacon_dev.AGBWriteRAM(addr=0, data=ram) + ## 读取卡带ROM + TOTAL_BYTE_SIZE = 0x800000*2 # 16MB + start = time.time() + avg_start = time.time() + first = True + rom = bacon_dev.AGBReadROM(addr=0, size=TOTAL_BYTE_SIZE, callback=callback) + with open_current_dif("gba_rom.bin", "wb") as f: + f.write(rom) +if __name__ == "__main__": + # print(ch347.get_version()) + # bacon_read_test_30bit() + # bacon_read_test_16bit() + bacon_test_api() + bacon_dev.Close() \ No newline at end of file diff --git a/FlashGBX/hw_Bacon.py b/FlashGBX/hw_Bacon.py new file mode 100644 index 0000000..ba4d85d --- /dev/null +++ b/FlashGBX/hw_Bacon.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# FlashGBX +# Author: Lesserkuma (github.com/lesserkuma) + +# pylint: disable=wildcard-import, unused-wildcard-import +from .LK_Device import * + +from .bacon import BaconFakeSerialDevice, SetDebug, SetDeviceCMD + +class GbxDevice(LK_Device): + DEVICE_NAME = "Bacon" + DEVICE_MIN_FW = 1 + DEVICE_MAX_FW = 12 + DEVICE_LATEST_FW_TS = { 5:1730731680, 10:1730731680, 11:1730731680, 12:1730731680, 13:1730731680 } + PCB_VERSIONS = { 5:'', 12:'v1.2', 13:'v1.3' } + + def __init__(self): + SetDebug(Util.DEBUG) + SetDeviceCMD(LK_Device.DEVICE_CMD, LK_Device.DEVICE_VAR) + pass + + def Initialize(self, flashcarts, port=None, max_baud=2000000): + conn_msg = [] + try: + self.DEVICE = BaconFakeSerialDevice() + self.LoadFirmwareVersion() + except Exception as e: + dprint("Failed to initialize BaconFakeSerialDevice:", e) + return False + dprint(f"Found a {self.DEVICE_NAME}") + dprint("Firmware information:", "?") + + self.MAX_BUFFER_READ = 0x1000 + self.MAX_BUFFER_WRITE = 0x800 + + self.PORT = "Dummy" + self.DEVICE.timeout = 1 + + conn_msg.append([0, "Welcome to use " + self.DEVICE_NAME + "."]) + + # Load Flash Cartridge Handlers + self.UpdateFlashCarts(flashcarts) + return conn_msg + + def LoadFirmwareVersion(self): + dprint("Querying firmware version") + try: + self.DEVICE.timeout = 0.075 + self.DEVICE.reset_input_buffer() + self.DEVICE.reset_output_buffer() + self.DEVICE.timeout = 1 + self.FW = {} + self.FW["fw_ts"] = 1730731680 + self.FW["cfw_id"] = "L" + self.FW["fw_ver"] = 13 + self.FW["fw_dt"] = datetime.datetime.fromtimestamp(self.FW["fw_ts"]).astimezone().replace(microsecond=0).isoformat() + self.FW["ofw_ver"] = None + self.FW["pcb_ver"] = 13 + self.FW["pcb_name"] = "Bacon" + # Cartridge Power Control support + self.FW["cart_power_ctrl"] = True + # Reset to bootloader support + self.FW["bootloader_reset"] = False #True if temp & 1 == 1 else False + self.FW["unregistered"] = False #True if temp >> 7 == 1 else False + return True + except Exception as e: + traceback.print_exc() + dprint("Disconnecting due to an error", e, sep="\n") + try: + if self.DEVICE.isOpen(): + self.DEVICE.reset_input_buffer() + self.DEVICE.reset_output_buffer() + self.DEVICE.close() + self.DEVICE = None + except: + pass + return False + + def ChangeBaudRate(self, _): + dprint("Baudrate change is not supported.") + + def CheckActive(self): + if time.time() < self.LAST_CHECK_ACTIVE + 1: return True + dprint("Checking if device is active") + if self.DEVICE is None: + dprint("Device is None") + return False + if self.LoadFirmwareVersion(): + dprint("Device is active") + self.LAST_CHECK_ACTIVE = time.time() + return True + return False + + def GetFirmwareVersion(self, more=False): + s = "{:s}{:d}".format(self.FW["cfw_id"], self.FW["fw_ver"]) + if self.FW["pcb_name"] == None: + s += " " + if more: + s += " ({:s})".format(self.FW["fw_dt"]) + return s + + def GetFullNameExtended(self, more=False): + if more: + return "{:s} – Firmware {:s} ({:s}) on {:s}".format(self.GetFullName(), self.GetFirmwareVersion(), self.FW["fw_dt"], self.GetPort()) + else: + return "{:s} – Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), self.GetPort()) + + def CanSetVoltageManually(self): + return False + + def CanSetVoltageAutomatically(self): + return True + + def CanPowerCycleCart(self): + return self.FW["cart_power_ctrl"] + + def GetSupprtedModes(self): + return ["DMG", "AGB"] + + def IsSupported3dMemory(self): + return True + + def IsClkConnected(self): + return True + + def SupportsFirmwareUpdates(self): + return True + + def FirmwareUpdateAvailable(self): + return False + + def GetFirmwareUpdaterClass(self): + return None + + def ResetLEDs(self): + pass + + def SupportsBootloaderReset(self): + return self.FW["bootloader_reset"] + + def BootloaderReset(self): + return False + + def SupportsAudioAsWe(self): + return not (self.FW["pcb_ver"] < 13 and self.CanPowerCycleCart()) + + def GetMode(self): + return super().GetMode() + + def SetAutoPowerOff(self, value): + value &= 0xFFFFFFFF + #if value == 0 or value > 5000: value = 1500 + return super().SetAutoPowerOff(value) + + def GetFullName(self): + if self.FW["pcb_ver"] < 13 and self.CanPowerCycleCart(): + s = "{:s} {:s} + PLUGIN 01".format(self.GetName(), self.GetPCBVersion()) + else: + s = "{:s} {:s}".format(self.GetName(), self.GetPCBVersion()) + if self.IsUnregistered(): + s += " (unregistered)" + return s + + def GetRegisterInformation(self): + text = "hahaha, no" + return text diff --git a/setup.py b/setup.py index 87c3d5b..de279c4 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ description="Reads and writes Game Boy and Game Boy Advance cartridge data", url="https://github.com/lesserkuma/FlashGBX", packages=setuptools.find_packages(), - install_requires=['pyserial>=3.5', 'Pillow', 'setuptools', 'requests', 'python-dateutil'], + install_requires=['pyserial>=3.5', 'Pillow', 'setuptools', 'requests', 'python-dateutil', 'bitarray', 'ch347api'], extras_require={ "qt5":["PySide2>=5.14"], "qt6":["PySide6"] From 4e05ff01f03e38a729be0468bca4d9889084a46d Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 02:39:45 +0800 Subject: [PATCH 02/18] Bacon bugfix --- FlashGBX/LK_Device.py | 12 ++++++++---- FlashGBX/bacon/command.py | 8 ++++---- FlashGBX/bacon/serial.py | 9 ++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/FlashGBX/LK_Device.py b/FlashGBX/LK_Device.py index 2b34866..2e86d6c 100644 --- a/FlashGBX/LK_Device.py +++ b/FlashGBX/LK_Device.py @@ -1425,8 +1425,8 @@ def ReadFlashSaveID(self): return (agb_flash_chip, agb_flash_chip_name) def ReadROM(self, address, length, skip_init=False, max_length=64): - if self.DEVICE_NAME == "Bacon" and max_length < 0x10000: - max_length = 0x10000 + if self.DEVICE_NAME == "Bacon": + max_length = length num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge ROM at 0x{:X} in {:d} iteration(s)".format(length, address, num)) if length > max_length: length = max_length @@ -1494,8 +1494,8 @@ def ReadROM_GBAMP(self, address, length, max_length=64): return self.ReadROM(address=addr, length=length, max_length=max_length) def ReadRAM(self, address, length, command=None, max_length=64): - if self.DEVICE_NAME == "Bacon" and max_length < 0x10000: - max_length = 0x10000 + if self.DEVICE_NAME == "Bacon": + max_length = length num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge RAM in {:d} iteration(s)".format(length, num)) if length > max_length: length = max_length @@ -1582,6 +1582,8 @@ def ReadRAM_TAMA5(self): def WriteRAM(self, address, buffer, command=None, max_length=256): length = len(buffer) + if self.DEVICE_NAME == "Bacon": + max_length = length num = math.ceil(length / max_length) dprint("Writing 0x{:X} bytes to cartridge RAM in {:d} iteration(s)".format(length, num)) if length > max_length: length = max_length @@ -1707,6 +1709,8 @@ def WriteRAM_TAMA5(self, buffer): def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False, max_length=MAX_BUFFER_WRITE): length = len(buffer) + if self.DEVICE_NAME == "Bacon": + max_length = length num = math.ceil(length / max_length) dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s) flash_buffer_size=0x{:X} skip_init={:s}".format(length, num, flash_buffer_size, str(skip_init))) if length == 0: diff --git a/FlashGBX/bacon/command.py b/FlashGBX/bacon/command.py index 166213f..0d46fdc 100644 --- a/FlashGBX/bacon/command.py +++ b/FlashGBX/bacon/command.py @@ -255,13 +255,13 @@ def make_ram_read_cycle_command(addr=0, times=1, postfunc=command2bytes) -> byte ]) for i in range(times)]) return postfunc(cmd) -__len_of_write = len(make_v16bit_data_write_command(data=0, postfunc=echo_all)) -__len_of_read = len(make_gba_rom_addr_read_command(postfunc=echo_all)) +__len_of_v16bit_write = len(make_v16bit_data_write_command(data=0, postfunc=echo_all)) +__len_of_v8bit_write = len(make_gba_rom_addr_read_command(postfunc=echo_all)) def extract_ram_read_cycle_data(data: bytes, times=1): command = bytes2command(data) ret = [] - for i in range(0, len(command), __len_of_write + __len_of_read + 2): - one = command[i + __len_of_write + 1: i + __len_of_write + 1 + __len_of_read + 1] + for i in range(0, len(command), __len_of_v16bit_write + __len_of_v8bit_write + 2): + one = command[i + __len_of_v16bit_write + 1: i + __len_of_v16bit_write + 1 + __len_of_v8bit_write + 1] ret.append(extract_gba_rom_addr_read_data(command2bytes(one, endclk=False))) if len(ret) >= times: break diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index cfae8aa..574f0be 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -294,12 +294,15 @@ def _cmd_parse(self, cmd): cmds = [] for i in range(num): addr = int.from_bytes(cmd[3+i*6:7+i*6], byteorder='big') - #if self.MODE == "AGB" and flashcart: - addr = MappingAddressToReal(addr<<1) + if self.MODE == "AGB" and flashcart: + addr = MappingAddressToReal(addr<<1) data = int.from_bytes(cmd[7+i*6:9+i*6], byteorder='big') dprint("[BaconFakeSerialDevice] CART_WRITE_FLASH_CMD:0x%08X Value:%s" % (addr, hex(data))) cmds.append((addr, data)) - self.bacon_dev.AGBWriteROMWithAddress(commands=cmds).Flush() + if self.MODE == "AGB" and flashcart: + self.bacon_dev.AGBWriteROMWithAddress(commands=cmds).Flush() + else: + self.bacon_dev.AGBWriteRAMWithAddress(commands=cmds) self._push_ack() elif cmdname == "FLASH_PROGRAM": self.FLASH_PROGRAMMING = True From f56bc73eaf6706a7660e6544f379c48db0d59c33 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 12:38:29 +0800 Subject: [PATCH 03/18] Bacon Bugfix And RTC Support --- FlashGBX/LK_Device.py | 4 ++-- FlashGBX/bacon/bacon.py | 21 ++++++++++++++++++++- FlashGBX/bacon/command.py | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/FlashGBX/LK_Device.py b/FlashGBX/LK_Device.py index 2e86d6c..b892a9a 100644 --- a/FlashGBX/LK_Device.py +++ b/FlashGBX/LK_Device.py @@ -1037,7 +1037,7 @@ def ReadInfo(self, setPinsAsInputs=False, checkRtc=True): data["rtc_string"] = "Not available" if checkRtc and data["logo_correct"] is True and header[0xC5] == 0 and header[0xC7] == 0 and header[0xC9] == 0: _agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle) - if self.FW["fw_ver"] >= 12: + if self.FW["fw_ver"] >= 12 and self.DEVICE_NAME != "Bacon": # Bacon has a different RTC implementation self._write(self.DEVICE_CMD["AGB_READ_GPIO_RTC"]) temp = self._read(8) data["has_rtc"] = _agb_gpio.HasRTC(temp) is True @@ -3410,7 +3410,7 @@ def _BackupRestoreRAM(self, args): elif self.MODE == "AGB": _agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle) rtc_buffer = None - if self.FW["fw_ver"] >= 12: + if self.FW["fw_ver"] >= 12 and self.DEVICE_NAME != "Bacon": # Bacon has a different RTC implementation self._write(self.DEVICE_CMD["AGB_READ_GPIO_RTC"]) rtc_buffer = self._read(8) if len(rtc_buffer) == 8 and _agb_gpio.HasRTC(rtc_buffer) is True: diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 3ff81e3..1179e7e 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -59,6 +59,10 @@ def Flush(self): class BaconDevice: + GPIO_REG_DAT = 0xC4 # Data low 4bit + GPIO_REG_CNT = 0xC6 # IO Select 1:Write to GPIO Device 0:Read from GPIO Device + GPIO_REG_RE = 0xC8 # Read Enable Flag Register 1:Enable 0:Disable + def __init__(self, hid_device=None, ch347_device=None): self.hid_device = hid_device self.ch347_device = ch347_device @@ -367,7 +371,7 @@ def AGBWriteRAMWithAddress(self, commands: list, callback=None) -> bool: if callback is not None and cnt != len(commands): callback(cnt, readbytes) if cycle_times == MAX_TIMES or i == len(commands) - 1 and cycle_times > 0: - ret = self.WriteRead(make_ram_write_cycle_with_addr(addrdatalist=commands[i-cycle_times+1:i+1])) + self.WriteRead(make_ram_write_cycle_with_addr(addrdatalist=commands[i-cycle_times+1:i+1])) readbytes = readbytes + ([0]*cycle_times) cycle_times = 0 if callback is not None: @@ -382,3 +386,18 @@ def AGBWriteRAMWithAddress(self, commands: list, callback=None) -> bool: def AGBCustomWriteCommands(self, commands: list, callback=None) -> bool: pass + + def AGBGPIOEnable(self) -> BaconWritePipeline: + if self.power != 3: + raise ValueError("Power must be 3.3v") + return self.AGBWriteROMSequential(self.GPIO_REG_RE, b"\x01") + + def AGBGPIOSetDirection(self, direction: int) -> BaconWritePipeline: + if self.power != 3: + raise ValueError("Power must be 3.3v") + return self.AGBWriteROMSequential(self.GPIO_REG_CNT, bytes([direction])) + + def AGBGPIORead(self) -> int: + if self.power != 3: + raise ValueError("Power must be 3.3v") + return self.AGBReadROM(self.GPIO_REG_DAT, 1)[0] diff --git a/FlashGBX/bacon/command.py b/FlashGBX/bacon/command.py index 0d46fdc..4cc7622 100644 --- a/FlashGBX/bacon/command.py +++ b/FlashGBX/bacon/command.py @@ -233,7 +233,7 @@ def make_ram_write_cycle_with_addr(addrdatalist: list, postfunc=command2bytes) - wr=True, rd=True, cs1=True, cs2=False, v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([data]), postfunc=echo_all), - make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all)]) + make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all)]) for addr, data in addrdatalist]) return postfunc(writeram) From 0c3588fef20af344f4f64a01e8cae5f2c5fab6d0 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 13:25:47 +0800 Subject: [PATCH 04/18] TODO: Bacon Single Write --- FlashGBX/bacon/serial.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index 574f0be..0abe5fe 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -87,10 +87,13 @@ def __init__(self): self.FLASH_CMD_WE = 0x00 self.FLASH_CUSTOM_CMDS = [0x00]*6 + self.BAK_FLASH_TYPE = 0x00 + self.FW_VARS = {} self.MODE = "AGB" # or DMG self.POWER = 0 self.AGB_SRAM_WRITING = False + self.AGB_BAK_FLASH_WRITING = False self.CALC_CRC32_WAITING = False self.FLASH_PROGRAMMING = False self.SET_FLASH_CMD_WAITING = 0 @@ -131,14 +134,22 @@ def write(self, data): def _cmd_parse(self, cmd): if self.AGB_SRAM_WRITING: - if self.in_waiting > 0: - dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_SRAM:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) - addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) - self.bacon_dev.AGBWriteRAM(addr, cmd) - self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] - return - else: - self.AGB_SRAM_WRITING = False + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_SRAM:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) + self.bacon_dev.AGBWriteRAM(addr, cmd) + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] + self.AGB_SRAM_WRITING = False + self._push_ack() + return + if self.AGB_BAK_FLASH_WRITING: + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) + addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) + # prepare unlock cmds and write + # self.bacon_dev.AGBWriteROM(addr, cmd) + self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] + self.AGB_BAK_FLASH_WRITING = False + self._push_ack() + return if self.CALC_CRC32_WAITING: chunk_size = int.from_bytes(cmd, byteorder='big') addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) @@ -184,7 +195,7 @@ def _cmd_parse(self, cmd): #TODO 校验 addr += buffer_size else: - # write with cmd + #TODO write with cmd pass self._push_ack() self.FLASH_PROGRAMMING = False @@ -256,11 +267,11 @@ def _cmd_parse(self, cmd): self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] elif cmdname == "AGB_CART_WRITE_SRAM": self.AGB_SRAM_WRITING = True - self._push_ack() elif cmdname == "AGB_CART_WRITE_FLASH_DATA": - # serious???? - self.AGB_SRAM_WRITING = True - self._push_ack() + dprint("[BaconFakeSerialDevice] !!!! AGB_CART_WRITE_FLASH_DATA is not implemented !!!!") + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA CMD:%s" % cmd.hex()) + self.AGB_BAK_FLASH_WRITING = True + self.BAK_FLASH_TYPE = int(cmd[1]) elif cmdname == "CALC_CRC32": # 读取一段数据,计算CRC32 # 0: cmd # 1~4: chunk_size From 4f9ffa500f643c38041186fd3fcaad66d3ee63f6 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 18:06:33 +0800 Subject: [PATCH 05/18] Fix Bacon Serial bugs --- FlashGBX/bacon/bacon.py | 99 ++++++++++++++++++++++------------------ FlashGBX/bacon/serial.py | 37 ++++++++++++--- 2 files changed, 84 insertions(+), 52 deletions(-) diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 1179e7e..424fb9f 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -34,14 +34,15 @@ def __init__(self, dev, flush_func): self.cmds = "" self.dev = dev self.flush_func = flush_func + self.MAX_LEN = 0x1000 def WillFlush(self, cmd: str): - if (len(self.cmds) + 1 + len(cmd))//8 >= 0x1000: + if (len(self.cmds) + 1 + len(cmd))//8 >= self.MAX_LEN: return True return False def Write(self, cmd: str): - if (len(self.cmds) + 1 + len(cmd))//8 >= 0x1000: + if (len(self.cmds) + 1 + len(cmd))//8 >= self.MAX_LEN: self.flush_func(command2bytes(self.cmds)) self.cmds = "" if self.cmds: @@ -67,6 +68,7 @@ def __init__(self, hid_device=None, ch347_device=None): self.hid_device = hid_device self.ch347_device = ch347_device self.power = 0 + self.MAX_LEN = 0x1000 # if all device is None, find a device ## check if windows if self.ch347_device is None and self.hid_device is None: @@ -83,6 +85,7 @@ def __init__(self, hid_device=None, ch347_device=None): if self.ch347_device is None and self.hid_device is None: raise ValueError("No device found") self.pipeline = BaconWritePipeline(self, self.WriteRead) + self.pipeline.MAX_LEN = self.MAX_LEN ######## Low Level API ######## def Close(self): @@ -94,8 +97,8 @@ def Close(self): self.hid_device = None def Write(self, data: bytes) -> bool: - if len(data) > 0x1000: - raise ValueError("Data length must be less than 0x1000") + if len(data) > self.MAX_LEN: + raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) if self.ch347_device is not None: return self.ch347_device.spi_write(0x80, data) if self.hid_device is not None: @@ -103,8 +106,8 @@ def Write(self, data: bytes) -> bool: raise ValueError("No device found") def WriteRead(self, data: bytes) -> bytes: - if len(data) > 0x1000: - raise ValueError("Data length must be less than 0x1000") + if len(data) > self.MAX_LEN: + raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) if self.ch347_device is not None: return bytes(self.ch347_device.spi_write_read(0x80, data)) if self.hid_device is not None: @@ -133,8 +136,7 @@ def PowerControl(self, v3_3v: bool, v5v: bool) -> BaconWritePipeline: ######## High Level API ######## - def AGBReadROM(self, addr: int, size: int, callback=None) -> bytes: - self.PiplelineFlush() + def AGBReadROM(self, addr: int, size: int, reset=True, callback=None) -> bytes: if size % 2 != 0: raise ValueError("Size must be a multiple of 2") if addr % 2 != 0: @@ -148,20 +150,20 @@ def AGBReadROM(self, addr: int, size: int, callback=None) -> bytes: ## to halfword addr = addr // 2 size = size // 2 - self.WriteRead(make_cart_30bit_write_command( + self.pipeline.Write(make_cart_30bit_write_command( phi=False, req=False, wr=True, rd=True, cs1=True, cs2=True, - v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([(addr >> 16) & 0xFF]) + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=bytes([(addr >> 16) & 0xFF]), postfunc=echo_all )) - self.WriteRead(make_gba_rom_cs_write(cs=False)) + self.pipeline.Write(make_gba_rom_cs_write(cs=False, postfunc=echo_all)).Flush() lowaddr = addr & 0xFFFF highaddr = (addr >> 16) & 0xFF # if lowaddr+1 == 0x10000, highaddr+1, and reset lowaddr # prepare WriteRead stream readbytes = [] cycle_times = 0 - MAX_TIMES = 0x1000//(len(make_rom_read_cycle_command(times=1))+1) + MAX_TIMES = self.MAX_LEN//(len(make_rom_read_cycle_command(times=1))+1) cnt = 0 for i in range(size): cycle_times += 1 @@ -197,15 +199,26 @@ def AGBReadROM(self, addr: int, size: int, callback=None) -> bytes: if callback is not None: callback(cnt, readbytes) # reset chip - self.WriteRead(make_cart_30bit_write_command( + if reset: + self.WriteRead(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00" + )) + return bytes(readbytes) + + def ResetChip(self) -> BaconWritePipeline: + if self.power != 3: + raise ValueError("Power must be 3.3v") + return self.pipeline.Write(make_cart_30bit_write_command( phi=False, req=False, wr=True, rd=True, cs1=True, cs2=True, - v16bit=b"\x00\x00", v8bit=b"\x00" + v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all )) - return bytes(readbytes) - - def AGBWriteROMSequential(self, addr, data: bytes, flash_prepare=None, flash_confirm=None, callback=None) -> BaconWritePipeline: + + def AGBWriteROMSequential(self, addr, data: bytes, reset=True, callback=None) -> BaconWritePipeline: if addr % 2 != 0: raise ValueError("Address must be a multiple of 2") if len(data) % 2 != 0: @@ -247,12 +260,13 @@ def AGBWriteROMSequential(self, addr, data: bytes, flash_prepare=None, flash_con if callback is not None: callback(cnt, None) # reset chip. is necessary? - self.pipeline.Write(make_cart_30bit_write_command( - phi=False, req=False, - wr=True, rd=True, - cs1=True, cs2=True, - v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all - )) + if reset: + self.pipeline.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all + )) return self.pipeline def AGBWriteROMWithAddress(self, commands: list, callback=None) -> BaconWritePipeline: @@ -290,15 +304,15 @@ def AGBReadRAM(self, addr: int, size: int, bankswitch=None, callback=None) -> by if self.power != 3: raise ValueError("Power must be 3.3v") # prepare chip - self.WriteRead(make_cart_30bit_write_command( + self.pipeline.Write(make_cart_30bit_write_command( phi=False, req=False, wr=True, rd=False, cs1=True, cs2=False, - v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=b"\x00" - )) + v16bit=bytes([addr & 0xFF, (addr >> 8) & 0xFF]), v8bit=b"\x00", postfunc=echo_all + )).Flush() readbytes = [] cycle_times = 0 - MAX_TIMES = 0x1000//(len(make_ram_read_cycle_command(addr=0, times=1))+1) + MAX_TIMES = self.MAX_LEN//(len(make_ram_read_cycle_command(addr=0, times=1))+1) cnt = 0 start_addr = addr for i in range(size): @@ -334,7 +348,7 @@ def AGBWriteRAM(self, addr: int, data: bytes, bankswitch=None, callback=None) -> raise ValueError("Power must be 3.3v") readbytes = [] cycle_times = 0 - MAX_TIMES = 0x1000//(len(make_ram_write_cycle_command(addr=0, data=b"\00"))+1) + MAX_TIMES = self.MAX_LEN//(len(make_ram_write_cycle_command(addr=0, data=b"\00"))+1) cnt = 0 start_addr = addr for i in range(size): @@ -358,31 +372,26 @@ def AGBWriteRAM(self, addr: int, data: bytes, bankswitch=None, callback=None) -> )) return bytes(readbytes) - def AGBWriteRAMWithAddress(self, commands: list, callback=None) -> bool: + def AGBWriteRAMWithAddress(self, commands: list, reset=True, callback=None) -> BaconWritePipeline: if self.power != 3: raise ValueError("Power must be 3.3v") - readbytes = [] - cycle_times = 0 - MAX_TIMES = 0x1000//(len(make_ram_write_cycle_with_addr(addrdatalist=[(0, 0)]))+1) cnt = 0 for i in range(len(commands)): - cycle_times += 1 + self.pipeline.Write(make_ram_write_cycle_with_addr(addrdatalist=[commands[i]], postfunc=echo_all)) cnt += 1 if callback is not None and cnt != len(commands): - callback(cnt, readbytes) - if cycle_times == MAX_TIMES or i == len(commands) - 1 and cycle_times > 0: - self.WriteRead(make_ram_write_cycle_with_addr(addrdatalist=commands[i-cycle_times+1:i+1])) - readbytes = readbytes + ([0]*cycle_times) - cycle_times = 0 + callback(cnt, None) if callback is not None: - callback(cnt, readbytes) + callback(cnt, None) # reset chip - self.WriteRead(make_cart_30bit_write_command( - phi=False, req=False, - wr=True, rd=True, - cs1=True, cs2=True, - v16bit=b"\x00\x00", v8bit=b"\x00" - )) + if reset: + self.pipeline.Write(make_cart_30bit_write_command( + phi=False, req=False, + wr=True, rd=True, + cs1=True, cs2=True, + v16bit=b"\x00\x00", v8bit=b"\x00", postfunc=echo_all + )) + return self.pipeline def AGBCustomWriteCommands(self, commands: list, callback=None) -> bool: pass diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index 0abe5fe..a94f20a 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -2,6 +2,7 @@ # bacon # Author: ChisBread (github.com/ChisBread) import zlib +import time from .bacon import BaconDevice DEBUG = False DEVICE_CMD = {} @@ -145,7 +146,20 @@ def _cmd_parse(self, cmd): dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) # prepare unlock cmds and write - # self.bacon_dev.AGBWriteROM(addr, cmd) + # TODO: other flash type + flash_cmds = [] + if self.BAK_FLASH_TYPE == FLASH_TYPES["AMD"]: + flash_cmds = [ + (0x5555, 0xAA), + (0x2AAA, 0x55), + (0x5555, 0xA0), + ] + else: + raise Exception("Unsupported Backup Flash Type") + for i, data in enumerate(cmd): + self.bacon_dev.AGBWriteRAMWithAddress(commands = flash_cmds + [(addr+i, data)], reset=False) + self.bacon_dev.DelayWithClock8(2048//8) + self.bacon_dev.PiplelineFlush() self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] self.AGB_BAK_FLASH_WRITING = False self._push_ack() @@ -154,7 +168,7 @@ def _cmd_parse(self, cmd): chunk_size = int.from_bytes(cmd, byteorder='big') addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) dprint("[BaconFakeSerialDevice] CALC_CRC32:0x%08X Size:0x%08X" % (self.FW_VARS["ADDRESS"], chunk_size)) - ret = self.bacon_dev.AGBReadROM(addr, chunk_size) + ret = self.bacon_dev.AGBReadROM(addr, chunk_size, reset=False) crc32 = zlib.crc32(ret) # push crc32 4byte big-endian self.push_to_input_buffer(crc32.to_bytes(4, byteorder='big')) @@ -168,6 +182,7 @@ def _cmd_parse(self, cmd): dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING:0x%08X ValueSize:%s TransferSize:%s BufferSize:%s" % (self.FW_VARS["ADDRESS"], len(cmd), size, buffer_size)) if self.FLASH_CMD_MOD == FLASH_MODS["FLASH_METHOD_BUFFERED"] and buffer_size > 0: # per buffer Seq + start_time = time.time() for i in range(0, size, buffer_size): # make flash cmds flash_prepare = [] @@ -190,10 +205,18 @@ def _cmd_parse(self, cmd): flash_prepare[-1] = (addr, flash_prepare[-1][1]) #dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING Prepare:%s Commit:%s" % ([(hex(i[0]), hex(i[1])) for i in flash_prepare], [(hex(i[0]), hex(i[1])) for i in flash_commit])) self.bacon_dev.AGBWriteROMWithAddress(commands=flash_prepare) - self.bacon_dev.AGBWriteROMSequential(addr=addr, data=cmd[i:i+buffer_size]) - self.bacon_dev.AGBWriteROMWithAddress(commands=flash_commit).Flush() #这里有200us的延迟, 怎么也够了 - #TODO 校验 + self.bacon_dev.AGBWriteROMSequential(addr=addr, data=cmd[i:i+buffer_size], reset=False) + self.bacon_dev.AGBWriteROMWithAddress(commands=flash_commit).Flush() + # S29GL256: + # 2-byte[33] 125 750 µs + # 32-byte[33] 160 750 + # 64-byte[33] 175 750 + # 128-byte[33] 198 750 + # 256-byte[33] 239 750 + # 512-byte 340 750 + # Don't need verify if time is enough addr += buffer_size + dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING PerBufferTime:%s(us)" % ((time.time()-start_time)/(size//buffer_size)*1000000)) else: #TODO write with cmd pass @@ -279,7 +302,7 @@ def _cmd_parse(self, cmd): elif cmdname == "AGB_CART_READ": addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) dprint("[BaconFakeSerialDevice] AGB_CART_READ:0x%08X(0x%08X) Size:%d" % (self.FW_VARS["ADDRESS"], addr, self.FW_VARS["TRANSFER_SIZE"])) - ret = self.bacon_dev.AGBReadROM(addr, self.FW_VARS["TRANSFER_SIZE"]) + ret = self.bacon_dev.AGBReadROM(addr, self.FW_VARS["TRANSFER_SIZE"], reset=False) if ret is not False: self.push_to_input_buffer(ret) if self.MODE == "AGB": @@ -313,7 +336,7 @@ def _cmd_parse(self, cmd): if self.MODE == "AGB" and flashcart: self.bacon_dev.AGBWriteROMWithAddress(commands=cmds).Flush() else: - self.bacon_dev.AGBWriteRAMWithAddress(commands=cmds) + self.bacon_dev.AGBWriteRAMWithAddress(commands=cmds).Flush() self._push_ack() elif cmdname == "FLASH_PROGRAM": self.FLASH_PROGRAMMING = True From b8c7ad2d2a725048831653c7ca6106e52917f7e5 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 19:49:01 +0800 Subject: [PATCH 06/18] Bacon Restore Flash Works --- FlashGBX/bacon/bacon.py | 19 ++++++++++++++----- FlashGBX/bacon/serial.py | 23 +++++++++-------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 424fb9f..4f517fb 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -49,7 +49,10 @@ def Write(self, cmd: str): self.cmds += "0" self.cmds += cmd return self - + + def IsEmpty(self): + return len(self.cmds) == 0 + def Flush(self): if self.cmds: self.flush_func(command2bytes(self.cmds)) @@ -117,10 +120,16 @@ def WriteRead(self, data: bytes) -> bytes: def PiplelineFlush(self): return self.pipeline.Flush() - def DelayWithClock8(self, clock8: int) -> BaconWritePipeline: - return self.pipeline.Write( - "0".join([make_power_read_command(postfunc=echo_all)]*clock8) - ) + def ResetAndDelayNS(self, ns: int, v16bit=b"\x00\x00", v8bit=b"\x00", phi=False, req=False, wr=True, rd=True, cs1=True, cs2=True) -> BaconWritePipeline: + # 1clk=16.67ns + # 1reset=35clk + for i in range(ns//int(16.6*35)+1): + if self.pipeline.Write(make_cart_30bit_write_command( + phi=phi, req=req, wr=wr, rd=rd, cs1=cs1, cs2=cs2, + v16bit=v16bit, v8bit=v8bit, postfunc=echo_all + )).IsEmpty(): + return self.pipeline + return self.pipeline def PowerControl(self, v3_3v: bool, v5v: bool) -> BaconWritePipeline: if v3_3v and v5v: diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index a94f20a..d0c22ab 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -143,23 +143,20 @@ def _cmd_parse(self, cmd): self._push_ack() return if self.AGB_BAK_FLASH_WRITING: - dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) + dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA:0x%08X" % (self.FW_VARS["ADDRESS"], )) addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]) # prepare unlock cmds and write # TODO: other flash type - flash_cmds = [] - if self.BAK_FLASH_TYPE == FLASH_TYPES["AMD"]: - flash_cmds = [ - (0x5555, 0xAA), - (0x2AAA, 0x55), - (0x5555, 0xA0), - ] - else: + flash_cmds = [ + (0x5555, 0xAA), + (0x2AAA, 0x55), + (0x5555, 0xA0), + ] + flash_exit = (0, 0xF0) + if self.BAK_FLASH_TYPE != FLASH_TYPES["AMD"]: raise Exception("Unsupported Backup Flash Type") for i, data in enumerate(cmd): - self.bacon_dev.AGBWriteRAMWithAddress(commands = flash_cmds + [(addr+i, data)], reset=False) - self.bacon_dev.DelayWithClock8(2048//8) - self.bacon_dev.PiplelineFlush() + self.bacon_dev.AGBWriteRAMWithAddress(commands = flash_cmds+[(addr+i, data), flash_exit], reset=False).Flush() self.FW_VARS["ADDRESS"] += self.FW_VARS["TRANSFER_SIZE"] self.AGB_BAK_FLASH_WRITING = False self._push_ack() @@ -291,8 +288,6 @@ def _cmd_parse(self, cmd): elif cmdname == "AGB_CART_WRITE_SRAM": self.AGB_SRAM_WRITING = True elif cmdname == "AGB_CART_WRITE_FLASH_DATA": - dprint("[BaconFakeSerialDevice] !!!! AGB_CART_WRITE_FLASH_DATA is not implemented !!!!") - dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_FLASH_DATA CMD:%s" % cmd.hex()) self.AGB_BAK_FLASH_WRITING = True self.BAK_FLASH_TYPE = int(cmd[1]) elif cmdname == "CALC_CRC32": # 读取一段数据,计算CRC32 From 8120f01ff1aa08b438cfdae5be061cc440b873de Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 21:18:42 +0800 Subject: [PATCH 07/18] Bacon RD Auto Flip --- FlashGBX/bacon/command.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/FlashGBX/bacon/command.py b/FlashGBX/bacon/command.py index 4cc7622..999828a 100644 --- a/FlashGBX/bacon/command.py +++ b/FlashGBX/bacon/command.py @@ -16,7 +16,7 @@ # -- | 01000 | GBA_ROM_ADDR_READ | 读取GBA R高8位地址 | - | 无输入 | 8 | 8位数据 | # -- | 01001 | GBA_ROM_DATA_WRITE | GBA ROM写16位数据 | 16 | 16位数据 | - | 无返回 | # -- | 01010 | GBA_ROM_DATA_READ | 读取GBA ROM数据 | - | 无输入 | 16 | 16位数据 | -# -- -- | 01011 | GBA_ROM_ADDR_WRITE | GBA ROM写高8位地址 | 8 | 8位地址 | - | 无返回 | +# -- | 01011 | GBA_ROM_DATA_READ_FLIP | 读取GBA ROM数据 | - | 无输入 | 16 | 16位数据 | # -- | 10000 - 11111 | RESERVED | 预留命令 | - | - | - | - | import traceback from bitarray import bitarray @@ -149,12 +149,12 @@ def extract_read_cycle_data_30bit(data: bytes, times=1): break return ret -def make_gba_rom_data_read_command(postfunc=command2bytes) -> bytes: - command = "01010" + "0" * 16 +def make_gba_rom_data_read_command(flip=False, postfunc=command2bytes) -> bytes: + command = "0101" + ("1" if flip else "0") + "0" * 16 return postfunc(command) def extract_gba_rom_read_data(data: bytes) -> int: - if len(data) != 3: + if len(data) < 3: raise ValueError("data must be 3 bytes, but got %d" % len(data)) # 6bit无效数据 #ret = (reverse_bits(data[0] << 6 | data[1] >> 2) << 8) | reverse_bits(data[1] << 6 | data[2] >> 2) @@ -163,8 +163,9 @@ def extract_gba_rom_read_data(data: bytes) -> int: __readcyclecmd = "0".join([ make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all), - make_gba_rom_data_read_command(postfunc=echo_all), - make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all)]) + make_gba_rom_data_read_command(flip=True, postfunc=echo_all), + #make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all), + ]) def make_rom_read_cycle_command(times=1, postfunc=command2bytes) -> bytes: # 1. pull down RD # 2. read data @@ -178,8 +179,8 @@ def extract_read_cycle_data(data: bytes, times=1): bytesstr = bytes2command(data) # 每隔len(__readcyclecmd)+1bit取一次数据 for i in range(0, len(bytesstr), len(__readcyclecmd) + 1): - one = command2bytes(bytesstr[i + 8: i + len(__readcyclecmd)], endclk=False) - ret.append(extract_gba_rom_read_data(one[:len(one)-1])) + one = command2bytes(bytesstr[i + 8: i + len(__readcyclecmd) + 1], endclk=False) + ret.append(extract_gba_rom_read_data(one[:len(one)])) if len(ret) >= times: break return ret From fdc540591c3d49fac88e410a03d47dbe9a94141a Mon Sep 17 00:00:00 2001 From: ChisBread Date: Wed, 13 Nov 2024 22:27:28 +0800 Subject: [PATCH 08/18] Bacon WR Auto Flip --- FlashGBX/bacon/bacon.py | 2 +- FlashGBX/bacon/command.py | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 4f517fb..b68e66f 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -268,7 +268,7 @@ def AGBWriteROMSequential(self, addr, data: bytes, reset=True, callback=None) -> )) if callback is not None: callback(cnt, None) - # reset chip. is necessary? + # reset chip. if reset: self.pipeline.Write(make_cart_30bit_write_command( phi=False, req=False, diff --git a/FlashGBX/bacon/command.py b/FlashGBX/bacon/command.py index 999828a..16ddbdd 100644 --- a/FlashGBX/bacon/command.py +++ b/FlashGBX/bacon/command.py @@ -17,6 +17,7 @@ # -- | 01001 | GBA_ROM_DATA_WRITE | GBA ROM写16位数据 | 16 | 16位数据 | - | 无返回 | # -- | 01010 | GBA_ROM_DATA_READ | 读取GBA ROM数据 | - | 无输入 | 16 | 16位数据 | # -- | 01011 | GBA_ROM_DATA_READ_FLIP | 读取GBA ROM数据 | - | 无输入 | 16 | 16位数据 | +# -- | 01100 | GBA_ROM_DATA_WRITE_FLIP | GBA ROM写16位数据 | 16 | 16位数据 | - | 无返回 | # -- | 10000 - 11111 | RESERVED | 预留命令 | - | - | - | - | import traceback from bitarray import bitarray @@ -117,14 +118,14 @@ def extract_cart_30bit_read_data(data: bytes) -> dict: return ret -def make_v16bit_data_write_command(data: int, postfunc=command2bytes) -> bytes: +def make_v16bit_data_write_command(data: int, flip=False, postfunc=command2bytes) -> bytes: if data > 0xFFFF: raise ValueError("data must be less than 0xFFFF") - command = "01001" + bin(data)[2:].rjust(16, "0") + command = ("01100" if flip else "01001") + bin(data)[2:].rjust(16, "0") return postfunc(command) -def make_gba_rom_data_write_command(data: int, postfunc=command2bytes) -> bytes: - return make_v16bit_data_write_command(data, postfunc) +def make_gba_rom_data_write_command(data: int, flip=False, postfunc=command2bytes) -> bytes: + return make_v16bit_data_write_command(data, flip, postfunc) __readcyclecmd_30bit = "0".join([ make_gba_wr_rd_write_command(wr=True, rd=False, postfunc=echo_all), @@ -185,7 +186,7 @@ def extract_read_cycle_data(data: bytes, times=1): break return ret -def make_rom_write_cycle_command_with_addr(addrdatalist: list, postfunc=command2bytes) -> bytes: +def make_rom_write_cycle_command_with_addr(addrdatalist: list, flip=True, postfunc=command2bytes) -> bytes: readram = "0".join(["0".join([ # 1. write addr, reset cs1 and wr make_cart_30bit_write_command( @@ -196,21 +197,21 @@ def make_rom_write_cycle_command_with_addr(addrdatalist: list, postfunc=command2 # 2. pull down cs1 make_gba_rom_cs_write(cs=False, postfunc=echo_all), # 3. write data - make_gba_rom_data_write_command(data, postfunc=echo_all), + make_gba_rom_data_write_command(data, flip=flip, postfunc=echo_all), # 4. pull down wr - make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), - ]) for addr, data in addrdatalist]) + # make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), + ]) + ("0"+make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all) if not flip else "") for addr, data in addrdatalist]) return postfunc(readram) -def make_rom_write_cycle_command_sequential(datalist: list, postfunc=command2bytes) -> bytes: +def make_rom_write_cycle_command_sequential(datalist: list, flip=True, postfunc=command2bytes) -> bytes: readram = "0".join(["0".join([ # 1. reset wr make_gba_wr_rd_write_command(wr=True, rd=True, postfunc=echo_all), # 2. write data - make_gba_rom_data_write_command(data, postfunc=echo_all), + make_gba_rom_data_write_command(data, flip=flip, postfunc=echo_all), # 3. pull down wr - make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), - ]) for data in datalist]) + # make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all), + ]) + ("0"+make_gba_wr_rd_write_command(wr=False, rd=True, postfunc=echo_all) if not flip else "") for data in datalist]) return postfunc(readram) def make_gba_rom_cs_write(cs: bool = True, postfunc=command2bytes) -> bytes: From 1d4cb1cfea7b4d6e6c2da38c9e5917e152535243 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 30 Nov 2024 15:00:18 +0800 Subject: [PATCH 09/18] Bacon ROM Read Cache --- FlashGBX/bacon/serial.py | 90 ++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index d0c22ab..84eee2b 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -99,6 +99,27 @@ def __init__(self): self.FLASH_PROGRAMMING = False self.SET_FLASH_CMD_WAITING = 0 + # ROM cache 32MB + self.ROM_CACHE = [0x00]*0x2000000 + self.ROM_CACHED = [False]*0x2000000 + + def cache_rom(self, addr, data): + for i in range(len(data)): + self.ROM_CACHE[addr+i] = data[i] + self.ROM_CACHED[addr+i] = True + + def cache_rom_reset(self, addr=0, size=0x2000000): + for i in range(size): + self.ROM_CACHED[addr+i] = False + + def read_rom(self, addr, size): + if self.ROM_CACHED[addr:addr+size].count(False) > 0: + dprint("[BaconFakeSerialDevice] ReadROM:0x%08X Size:%s" % (addr, size)) + self.cache_rom(addr, self.bacon_dev.AGBReadROM(addr, size)) + else: + dprint("[BaconFakeSerialDevice] ReadROMCached:0x%08X Size:%s" % (addr, size)) + return bytes(self.ROM_CACHE[addr:addr+size]) + def isOpen(self): return self.bacon_dev is not None @@ -132,7 +153,29 @@ def _push_ack(self): def write(self, data): self._cmd_parse(data) return len(data) - + + def _make_flash_cmds(self, addr, buffer_size): + flash_prepare = [] + flash_commit = [] + for j in range(6): + tcmd = (self.FLASH_CUSTOM_CMDS[j][0]<<1, self.FLASH_CUSTOM_CMDS[j][1]) + flash_prepare.append(tcmd) + + if flash_prepare[-1] == (0x00, 0x00): # write buffer size? + flash_prepare[-1] = (addr, buffer_size//2-1) + for k in range(j+1, 6): + # commit + if not (self.FLASH_CUSTOM_CMDS[k] == (0x00, 0x00)): + tcmd = (self.FLASH_CUSTOM_CMDS[k][0]<<1, self.FLASH_CUSTOM_CMDS[k][1]) + flash_commit.append(tcmd) + if flash_commit[-1][0] == 0x00: + flash_commit[-1] = (addr, flash_commit[-1][1]) + break + elif flash_prepare[-1][0] == 0x00: + flash_prepare[-1] = (addr, flash_prepare[-1][1]) + return flash_prepare, flash_commit + + def _cmd_parse(self, cmd): if self.AGB_SRAM_WRITING: dprint("[BaconFakeSerialDevice] AGB_CART_WRITE_SRAM:0x%08X Value:%s" % (self.FW_VARS["ADDRESS"], cmd.hex())) @@ -165,7 +208,7 @@ def _cmd_parse(self, cmd): chunk_size = int.from_bytes(cmd, byteorder='big') addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) dprint("[BaconFakeSerialDevice] CALC_CRC32:0x%08X Size:0x%08X" % (self.FW_VARS["ADDRESS"], chunk_size)) - ret = self.bacon_dev.AGBReadROM(addr, chunk_size, reset=False) + ret = self.read_rom(addr, chunk_size) crc32 = zlib.crc32(ret) # push crc32 4byte big-endian self.push_to_input_buffer(crc32.to_bytes(4, byteorder='big')) @@ -173,33 +216,18 @@ def _cmd_parse(self, cmd): return if self.FLASH_PROGRAMMING: #TODO: More AGB Flash Type, And DMG Flash - addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) + raw_addr = MappingAddressToReal(self.FW_VARS["ADDRESS"]<<1) + addr = raw_addr size = self.FW_VARS["TRANSFER_SIZE"] buffer_size = self.FW_VARS["BUFFER_SIZE"] dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING:0x%08X ValueSize:%s TransferSize:%s BufferSize:%s" % (self.FW_VARS["ADDRESS"], len(cmd), size, buffer_size)) if self.FLASH_CMD_MOD == FLASH_MODS["FLASH_METHOD_BUFFERED"] and buffer_size > 0: # per buffer Seq start_time = time.time() + self.cache_rom_reset(raw_addr, size) for i in range(0, size, buffer_size): # make flash cmds - flash_prepare = [] - flash_commit = [] - for j in range(6): - tcmd = (self.FLASH_CUSTOM_CMDS[j][0]<<1, self.FLASH_CUSTOM_CMDS[j][1]) - flash_prepare.append(tcmd) - - if flash_prepare[-1] == (0x00, 0x00): # write buffer size? - flash_prepare[-1] = (addr, buffer_size//2-1) - for k in range(j+1, 6): - # commit - if not (self.FLASH_CUSTOM_CMDS[k] == (0x00, 0x00)): - tcmd = (self.FLASH_CUSTOM_CMDS[k][0]<<1, self.FLASH_CUSTOM_CMDS[k][1]) - flash_commit.append(tcmd) - if flash_commit[-1][0] == 0x00: - flash_commit[-1] = (addr, flash_commit[-1][1]) - break - elif flash_prepare[-1][0] == 0x00: - flash_prepare[-1] = (addr, flash_prepare[-1][1]) + flash_prepare, flash_commit = self._make_flash_cmds(addr, buffer_size) #dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING Prepare:%s Commit:%s" % ([(hex(i[0]), hex(i[1])) for i in flash_prepare], [(hex(i[0]), hex(i[1])) for i in flash_commit])) self.bacon_dev.AGBWriteROMWithAddress(commands=flash_prepare) self.bacon_dev.AGBWriteROMSequential(addr=addr, data=cmd[i:i+buffer_size], reset=False) @@ -213,7 +241,27 @@ def _cmd_parse(self, cmd): # 512-byte 340 750 # Don't need verify if time is enough addr += buffer_size + # retry + ret = self.read_rom(raw_addr, size) dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING PerBufferTime:%s(us)" % ((time.time()-start_time)/(size//buffer_size)*1000000)) + addr = raw_addr + for i in range(0, size, buffer_size): + j = 1 + while ret[i:i+buffer_size] != cmd[i:i+buffer_size]: + if j > 10: + dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING Retry Failed!") + break + flash_prepare, flash_commit = self._make_flash_cmds(addr, buffer_size) + self.bacon_dev.AGBWriteROMWithAddress(commands=flash_prepare) + self.bacon_dev.AGBWriteROMSequential(addr=addr, data=cmd[i:i+buffer_size], reset=False) + self.bacon_dev.AGBWriteROMWithAddress(commands=flash_commit).Flush() + # 等待Nms + time.sleep(0.001*j) + dprint("[BaconFakeSerialDevice] FLASH_PROGRAMMING Retry:%s" % j) + ret = ret[:i] + self.read_rom(addr, buffer_size) + ret[i+buffer_size:] + j += 1 + addr += buffer_size + else: #TODO write with cmd pass From 803ddff78dec2909f22cc4d25ffc941225b5c423 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 30 Nov 2024 15:34:21 +0800 Subject: [PATCH 10/18] Bacon ROM SectorErase Cache --- FlashGBX/LK_Device.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/FlashGBX/LK_Device.py b/FlashGBX/LK_Device.py index b892a9a..fdb012b 100644 --- a/FlashGBX/LK_Device.py +++ b/FlashGBX/LK_Device.py @@ -4296,6 +4296,8 @@ def _FlashROM(self, args): sector_size = se_ret dprint("Next sector size: 0x{:X}".format(sector_size)) skip_init = False + if "Bacon" in self.DEVICE_NAME: + self.DEVICE.cache_rom(pos, [0xFF]*buffer_len) # ↑↑↑ Sector erase if se_ret is not False: @@ -4655,7 +4657,12 @@ def TransferData(self, args, signal): if args['mode'] == 1: ret = self._BackupROM(args) elif args['mode'] == 2: ret = self._BackupRestoreRAM(args) elif args['mode'] == 3: ret = self._BackupRestoreRAM(args) - elif args['mode'] == 4: ret = self._FlashROM(args) + elif args['mode'] == 4: + if "Bacon" in self.DEVICE_NAME: + self.DEVICE.cache_rom_reset() + ret = self._FlashROM(args) + if "Bacon" in self.DEVICE_NAME: + self.DEVICE.cache_rom_reset() elif args['mode'] == 5: ret = self._DetectCartridge(args) elif args['mode'] == 0xFF: self.Debug() if self.FW is None: return False From c82d07878e57033a764d2449aaa41dbd038d3414 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 12:22:19 +0800 Subject: [PATCH 11/18] Bacon GPIO SPI --- FlashGBX/bacon/__init__.py | 3 +- FlashGBX/bacon/bacon.py | 34 ++- FlashGBX/bacon/linuxspi.py | 437 +++++++++++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+), 7 deletions(-) create mode 100644 FlashGBX/bacon/linuxspi.py diff --git a/FlashGBX/bacon/__init__.py b/FlashGBX/bacon/__init__.py index 202c16d..0510f02 100644 --- a/FlashGBX/bacon/__init__.py +++ b/FlashGBX/bacon/__init__.py @@ -52,4 +52,5 @@ from .ch347 import * from .bacon import * from .command import * -from .serial import * \ No newline at end of file +from .serial import * +from .linuxspi import * \ No newline at end of file diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index b68e66f..b31609d 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -12,6 +12,8 @@ from ch347api import CH347HIDDev, I2CDevice, SPIDevice, UARTDevice, SPIClockFreq, I2CClockFreq #WCHAPI from .ch347 import SPIConfig, CH347 +from .linuxspi import SPI + spi_config = SPIConfig( Mode=3, Clock=0, @@ -67,14 +69,15 @@ class BaconDevice: GPIO_REG_CNT = 0xC6 # IO Select 1:Write to GPIO Device 0:Read from GPIO Device GPIO_REG_RE = 0xC8 # Read Enable Flag Register 1:Enable 0:Disable - def __init__(self, hid_device=None, ch347_device=None): + def __init__(self, hid_device=None, ch347_device=None, gpio_device=None): self.hid_device = hid_device self.ch347_device = ch347_device + self.gpio_device = gpio_device self.power = 0 self.MAX_LEN = 0x1000 # if all device is None, find a device ## check if windows - if self.ch347_device is None and self.hid_device is None: + if self.ch347_device is None and self.hid_device is None and self.gpio_device is None: if platform.system() == "Windows": if self.ch347_device is None: ch347 = CH347(device_index=0, dll_path=os.path.join(os.path.dirname(__file__), "lib/CH347DLLA64.DLL")) @@ -82,26 +85,43 @@ def __init__(self, hid_device=None, ch347_device=None): ch347.open_device() if ch347.spi_init(spi_config): # True if successful, False otherwise. self.ch347_device = ch347 - # cannot find wch device, try hid device - if self.ch347_device is None: - self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) - if self.ch347_device is None and self.hid_device is None: + # cannot find wch device, try hid device + if self.ch347_device is None: + self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) + elif platform.system() == "Linux" and os.path.exists("/dev/spidev3.0"): + try: + spi = SPI("/dev/spidev3.0") # TODO. Auto find spi device + spi.mode = SPI.MODE_3 + spi.bits_per_word = 8 + spi.speed = 60000000 + self.gpio_device = spi + except Exception as e: + print(e) + + + if self.ch347_device is None and self.hid_device is None and self.gpio_device is None: raise ValueError("No device found") self.pipeline = BaconWritePipeline(self, self.WriteRead) self.pipeline.MAX_LEN = self.MAX_LEN ######## Low Level API ######## def Close(self): + if self.gpio_device is not None: + self.gpio_device.close() if self.ch347_device is not None: self.ch347_device.close_device() if self.hid_device is not None: self.hid_device.dev.close() self.ch347_device = None self.hid_device = None + self.gpio_device = None def Write(self, data: bytes) -> bool: if len(data) > self.MAX_LEN: raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) + if self.gpio_device is not None: + self.gpio_device.write(data) + return True if self.ch347_device is not None: return self.ch347_device.spi_write(0x80, data) if self.hid_device is not None: @@ -111,6 +131,8 @@ def Write(self, data: bytes) -> bool: def WriteRead(self, data: bytes) -> bytes: if len(data) > self.MAX_LEN: raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) + if self.gpio_device is not None: + return bytes(self.gpio_device.transfer(data)) if self.ch347_device is not None: return bytes(self.ch347_device.spi_write_read(0x80, data)) if self.hid_device is not None: diff --git a/FlashGBX/bacon/linuxspi.py b/FlashGBX/bacon/linuxspi.py new file mode 100644 index 0000000..fc22882 --- /dev/null +++ b/FlashGBX/bacon/linuxspi.py @@ -0,0 +1,437 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015 Thomas Stokes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +__author__ = 'Tom Stokes' + +import ctypes +import struct +import fcntl +import array +import os.path + + +def _ioc(direction, number, structure): + """ + ioctl command encoding helper function + + Calculates the appropriate spidev ioctl op argument given the direction, + command number, and argument structure in python's struct.pack format. + + Returns a tuple of the calculated op and the struct.pack format + + See Linux kernel source file /include/uapi/asm-generic/ioctl.h + """ + ioc_magic = ord('k') + ioc_nrbits = 8 + ioc_typebits = 8 + ioc_sizebits = 14 # XXX: 13 on PPC, MIPS, Sparc, and Alpha + ioc_nrshift = 0 + ioc_typeshift = ioc_nrshift + ioc_nrbits + ioc_sizeshift = ioc_typeshift + ioc_typebits + ioc_dirshift = ioc_sizeshift + ioc_sizebits + + size = struct.calcsize(structure) + + op = (direction << ioc_dirshift) | (ioc_magic << ioc_typeshift) | \ + (number << ioc_nrshift) | (size << ioc_sizeshift) + + return direction, op, structure + + +class SPI(object): + """ + struct spi_ioc_transfer { + __u64 tx_buf; + __u64 rx_buf; + __u32 len; + __u32 speed_hz; + __u16 delay_usecs; + __u8 bits_per_word; + __u8 cs_change; + __u8 tx_nbits; + __u8 rx_nbits; + __u16 pad; + """ + _IOC_TRANSFER_FORMAT = "QQIIHBBBBH" + + _IOC_WRITE = 1 + _IOC_READ = 2 + + # _IOC_MESSAGE is a special case, so we ony need the ioctl number + _IOC_MESSAGE = _ioc(_IOC_WRITE, 0, _IOC_TRANSFER_FORMAT)[1] + + _IOC_RD_MODE = _ioc(_IOC_READ, 1, "B") + _IOC_WR_MODE = _ioc(_IOC_WRITE, 1, "B") + + _IOC_RD_LSB_FIRST = _ioc(_IOC_READ, 2, "B") + _IOC_WR_LSB_FIRST = _ioc(_IOC_WRITE, 2, "B") + + _IOC_RD_BITS_PER_WORD = _ioc(_IOC_READ, 3, "B") + _IOC_WR_BITS_PER_WORD = _ioc(_IOC_WRITE, 3, "B") + + _IOC_RD_MAX_SPEED_HZ = _ioc(_IOC_READ, 4, "I") + _IOC_WR_MAX_SPEED_HZ = _ioc(_IOC_WRITE, 4, "I") + + _IOC_RD_MODE32 = _ioc(_IOC_READ, 5, "I") + _IOC_WR_MODE32 = _ioc(_IOC_WRITE, 5, "I") + + CPHA = 0x01 + CPOL = 0x02 + CS_HIGH = 0x04 + LSB_FIRST = 0x08 + THREE_WIRE = 0x10 + LOOP = 0x20 + NO_CS = 0x40 + READY = 0x80 + TX_DUAL = 0x100 + TX_QUAD = 0x200 + RX_DUAL = 0x400 + RX_QUAD = 0x800 + + MODE_0 = 0 + MODE_1 = CPHA + MODE_2 = CPOL + MODE_3 = CPHA | CPOL + + def __init__(self, device, speed=None, bits_per_word=None, phase=None, + polarity=None, cs_high=None, lsb_first=None, + three_wire=None, loop=None, no_cs=None, ready=None): + """Create spidev interface object. + + Args: + device: Tuple of (bus, device) or string of device path + speed: Optional target bus speed in Hz. + bits_per_word: Optional number of bits per word. + + Raises: + IOError: The spidev device could not be opened (check permissions) + """ + if isinstance(device, tuple): + (bus, dev) = device + device = "/dev/spidev{:d}.{:d}".format(bus, dev) + + if not os.path.exists(device): + raise IOError("{} does not exist".format(device)) + + self.handle = open(device, "w+") + + if speed is not None: + self.speed = speed + + if bits_per_word is not None: + self.bits_per_word = bits_per_word + + if phase is not None: + self.phase = phase + + if polarity is not None: + self.polarity = polarity + + if cs_high is not None: + self.cs_high = cs_high + + if lsb_first is not None: + self.lsb_first = lsb_first + + if three_wire is not None: + self.three_wire = three_wire + + if self.loop is not None: + self.loop = loop + + if self.no_cs is not None: + self.no_cs = no_cs + + if self.ready is not None: + self.ready = ready + + def _ioctl(self, ioctl_data, data=None): + """ioctl helper function. + + Performs an ioctl on self.handle. If the ioctl is an SPI read type + ioctl, returns the result value. + + Args: + ioctl_data: Tuple of (direction, op structure), where direction + is one of SPI._IOC_READ or SPI._IOC_WRITE, op is the + pre-computed ioctl op (see _ioc above) and structure is the + Python format string for the ioctl arguments. + + Returns: + If ioctl_data specifies an SPI._IOC_READ, returns the result. + For SPI._IOC_WRITE types, returns None + """ + (direction, ioctl, structure) = ioctl_data + if direction == SPI._IOC_READ: + arg = array.array(structure, [0]) + fcntl.ioctl(self.handle, ioctl, arg, True) + return arg[0] + else: + arg = struct.pack("=" + structure, data) + fcntl.ioctl(self.handle, ioctl, arg) + return + + def _get_mode(self): + """Helper function to get spidev mode + + Returns: + spidev mode as an integer. Bits correspond to SPI.CPHA, + SPI.CPOL, SPI.CS_HIGH, SPI.LSB_FIRST, SPI.THREE_WIRE, SPI.LOOP, + SPI.NO_CS, and SPI.READY + """ + return self._ioctl(SPI._IOC_RD_MODE) + + def _set_mode(self, mode): + """Helper function to set the spidev mode + + Args: + mode: spidev mode as an integer. Bits correspond to SPI.CPHA, + SPI.CPOL, SPI.CS_HIGH, SPI.LSB_FIRST, SPI.THREE_WIRE, SPI.LOOP, + SPI.NO_CS, and SPI.READY + """ + self._ioctl(SPI._IOC_WR_MODE, mode) + + def _get_mode_field(self, field): + """Helper function to get specific spidev mode bits + + Args: + field: Bit mask to apply to spidev mode. + + Returns: + bool(mode & field). True if specified bit is 1, otherwise False + """ + return True if self._get_mode() & field else False + + def _set_mode_field(self, field, value): + """Helper function to set a spidev mode bit + + Args: + field: Bitmask of bit(s) to set to value + value: True to set bit(s) to 1, false to set bit(s) to 0 + """ + mode = self._get_mode() + if value: + mode |= field + else: + mode &= ~field + self._set_mode(mode) + + @property + def phase(self): + """SPI clock phase bit + + False: Sample at leading edge of clock + True: Sample at trailing edge of clock + """ + return self._get_mode_field(SPI.CPHA) + + @phase.setter + def phase(self, phase): + self._set_mode_field(SPI.CPHA, phase) + + @property + def polarity(self): + """SPI polarity bit + + False: Data sampled at rising edge, data changes on falling edge + True: Data sampled at falling edge, data changes on rising edge + """ + return self._get_mode_field(SPI.CPOL) + + @polarity.setter + def polarity(self, polarity): + self._set_mode_field(SPI.CPOL, polarity) + + @property + def cs_high(self): + """SPI chip select active level + + True: Chip select is active high + False: Chip select is active low + """ + return self._get_mode_field(SPI.CS_HIGH) + + @cs_high.setter + def cs_high(self, cs_high): + self._set_mode_field(SPI.CS_HIGH, cs_high) + + @property + def lsb_first(self): + """Bit order of SPI word transfers + + False: Send MSB first + True: Send LSB first + """ + return self._get_mode_field(SPI.LSB_FIRST) + + @lsb_first.setter + def lsb_first(self, lsb_first): + self._set_mode_field(SPI.LSB_FIRST, lsb_first) + + @property + def three_wire(self): + """SPI 3-wire mode + + True: Data is read and written on the same line (3-wire mode) + False: Data is read and written on separate lines (MOSI & MISO) + """ + return self._get_mode_field(SPI.THREE_WIRE) + + @three_wire.setter + def three_wire(self, three_wire): + self._set_mode_field(SPI.THREE_WIRE, three_wire) + + @property + def loop(self): + """SPI loopback mode""" + return self._get_mode_field(SPI.LOOP) + + @loop.setter + def loop(self, loop): + self._set_mode_field(SPI.LOOP, loop) + + @property + def no_cs(self): + """No chipselect. Single device on bus.""" + return self._get_mode_field(SPI.NO_CS) + + @no_cs.setter + def no_cs(self, no_cs): + self._set_mode_field(SPI.NO_CS, no_cs) + + @property + def ready(self): + """Slave pulls low to pause""" + return self._get_mode_field(SPI.READY) + + @ready.setter + def ready(self, ready): + self._set_mode_field(SPI.READY, ready) + + @property + def speed(self): + """Maximum SPI transfer speed in Hz. + + Note that the controller cannot necessarily assign the requested + speed. + """ + return self._ioctl(SPI._IOC_RD_MAX_SPEED_HZ) + + @speed.setter + def speed(self, speed): + self._ioctl(SPI._IOC_WR_MAX_SPEED_HZ, speed) + + @property + def bits_per_word(self): + """Number of bits per word of SPI transfer. + + A value of 0 is equivalent to 8 bits per word + """ + return self._ioctl(SPI._IOC_RD_BITS_PER_WORD) + + @bits_per_word.setter + def bits_per_word(self, bits_per_word): + self._ioctl(SPI._IOC_WR_BITS_PER_WORD, bits_per_word) + + @property + def mode(self): + return self._get_mode() + + @mode.setter + def mode(self, mode): + self._set_mode(mode) + + def write(self, data, speed=0, bits_per_word=0, delay=0): + """Perform half-duplex SPI write. + + Args: + data: List of words to write + speed: Optional temporary bitrate override in Hz. 0 (default) + uses existing spidev speed setting. + bits_per_word: Optional temporary bits_per_word override. 0 + (default) will use the current bits_per_word setting. + delay: Optional delay in usecs between sending the last bit and + deselecting the chip select line. 0 (default) for no delay. + """ + data = array.array('B', data).tostring() + length = len(data) + transmit_buffer = ctypes.create_string_buffer(data) + spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, + ctypes.addressof(transmit_buffer), 0, + length, speed, delay, bits_per_word, 0, + 0, 0, 0) + fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) + + def read(self, length, speed=0, bits_per_word=0, delay=0): + """Perform half-duplex SPI read as a binary string + + Args: + length: Integer count of words to read + speed: Optional temporary bitrate override in Hz. 0 (default) + uses existing spidev speed setting. + bits_per_word: Optional temporary bits_per_word override. 0 + (default) will use the current bits_per_word setting. + delay: Optional delay in usecs between sending the last bit and + deselecting the chip select line. 0 (default) for no delay. + + Returns: + List of words read from device + """ + receive_buffer = ctypes.create_string_buffer(length) + spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, 0, + ctypes.addressof(receive_buffer), + length, speed, delay, bits_per_word, 0, + 0, 0, 0) + fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) + return [ord(byte) for byte in ctypes.string_at(receive_buffer, length)] + + def transfer(self, data, speed=0, bits_per_word=0, delay=0): + """Perform full-duplex SPI transfer + + Args: + data: List of words to transmit + speed: Optional temporary bitrate override in Hz. 0 (default) + uses existing spidev speed setting. + bits_per_word: Optional temporary bits_per_word override. 0 + (default) will use the current bits_per_word setting. + delay: Optional delay in usecs between sending the last bit and + deselecting the chip select line. 0 (default) for no delay. + + Returns: + List of words read from SPI bus during transfer + """ + data = array.array('B', data).tostring() + length = len(data) + transmit_buffer = ctypes.create_string_buffer(data) + receive_buffer = ctypes.create_string_buffer(length) + spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, + ctypes.addressof(transmit_buffer), + ctypes.addressof(receive_buffer), + length, speed, delay, bits_per_word, 0, + 0, 0, 0) + fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) + return [ord(byte) for byte in ctypes.string_at(receive_buffer, length)] + def close(self): + """Close the spidev device""" + self.handle.close() \ No newline at end of file From 9411d81a0b8bee7acbe66cd29faee7ef530490e0 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 13:30:53 +0800 Subject: [PATCH 12/18] Bacon Fix spidev --- FlashGBX/bacon/linuxspi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlashGBX/bacon/linuxspi.py b/FlashGBX/bacon/linuxspi.py index fc22882..dbc234e 100644 --- a/FlashGBX/bacon/linuxspi.py +++ b/FlashGBX/bacon/linuxspi.py @@ -134,7 +134,7 @@ def __init__(self, device, speed=None, bits_per_word=None, phase=None, if not os.path.exists(device): raise IOError("{} does not exist".format(device)) - self.handle = open(device, "w+") + self.handle = open(device, "w+b", buffering=0) if speed is not None: self.speed = speed From 3914950f760832a677a62da4bf07c9888b51c56d Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 14:53:25 +0800 Subject: [PATCH 13/18] Bacon CS Active Low --- FlashGBX/bacon/bacon.py | 10 ++++++---- FlashGBX/bacon/linuxspi.py | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index b31609d..13b4e9d 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -90,10 +90,12 @@ def __init__(self, hid_device=None, ch347_device=None, gpio_device=None): self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) elif platform.system() == "Linux" and os.path.exists("/dev/spidev3.0"): try: - spi = SPI("/dev/spidev3.0") # TODO. Auto find spi device - spi.mode = SPI.MODE_3 - spi.bits_per_word = 8 - spi.speed = 60000000 + spi = SPI("/dev/spidev3.0", speed=60000000, phase=True, polarity=True, bits_per_word=8, lsb_first=False, cs_high=False) # TODO. Auto find spi device + # spi.mode = SPI.MODE_3 + # spi.bits_per_word = 8 + # spi.speed = 60000000 + # spi.cs_high = True + # spi.lsb_first = False self.gpio_device = spi except Exception as e: print(e) diff --git a/FlashGBX/bacon/linuxspi.py b/FlashGBX/bacon/linuxspi.py index dbc234e..bdd27f0 100644 --- a/FlashGBX/bacon/linuxspi.py +++ b/FlashGBX/bacon/linuxspi.py @@ -374,7 +374,7 @@ def write(self, data, speed=0, bits_per_word=0, delay=0): delay: Optional delay in usecs between sending the last bit and deselecting the chip select line. 0 (default) for no delay. """ - data = array.array('B', data).tostring() + data = array.array('B', data).tobytes() length = len(data) transmit_buffer = ctypes.create_string_buffer(data) spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, @@ -404,7 +404,7 @@ def read(self, length, speed=0, bits_per_word=0, delay=0): length, speed, delay, bits_per_word, 0, 0, 0, 0) fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) - return [ord(byte) for byte in ctypes.string_at(receive_buffer, length)] + return [byte for byte in ctypes.string_at(receive_buffer, length)] def transfer(self, data, speed=0, bits_per_word=0, delay=0): """Perform full-duplex SPI transfer @@ -421,7 +421,7 @@ def transfer(self, data, speed=0, bits_per_word=0, delay=0): Returns: List of words read from SPI bus during transfer """ - data = array.array('B', data).tostring() + data = array.array('B', data).tobytes() length = len(data) transmit_buffer = ctypes.create_string_buffer(data) receive_buffer = ctypes.create_string_buffer(length) @@ -431,7 +431,7 @@ def transfer(self, data, speed=0, bits_per_word=0, delay=0): length, speed, delay, bits_per_word, 0, 0, 0, 0) fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) - return [ord(byte) for byte in ctypes.string_at(receive_buffer, length)] + return [byte for byte in ctypes.string_at(receive_buffer, length)] def close(self): """Close the spidev device""" self.handle.close() \ No newline at end of file From 147c41e9c58074dc448539a3e2d24bd77549ea4c Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 14:57:55 +0800 Subject: [PATCH 14/18] Bacon CS Change --- FlashGBX/bacon/linuxspi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FlashGBX/bacon/linuxspi.py b/FlashGBX/bacon/linuxspi.py index bdd27f0..1a34d4f 100644 --- a/FlashGBX/bacon/linuxspi.py +++ b/FlashGBX/bacon/linuxspi.py @@ -379,7 +379,7 @@ def write(self, data, speed=0, bits_per_word=0, delay=0): transmit_buffer = ctypes.create_string_buffer(data) spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, ctypes.addressof(transmit_buffer), 0, - length, speed, delay, bits_per_word, 0, + length, speed, delay, bits_per_word, 1, 0, 0, 0) fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) @@ -401,7 +401,7 @@ def read(self, length, speed=0, bits_per_word=0, delay=0): receive_buffer = ctypes.create_string_buffer(length) spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, 0, ctypes.addressof(receive_buffer), - length, speed, delay, bits_per_word, 0, + length, speed, delay, bits_per_word, 1, 0, 0, 0) fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) return [byte for byte in ctypes.string_at(receive_buffer, length)] @@ -428,7 +428,7 @@ def transfer(self, data, speed=0, bits_per_word=0, delay=0): spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, ctypes.addressof(transmit_buffer), ctypes.addressof(receive_buffer), - length, speed, delay, bits_per_word, 0, + length, speed, delay, bits_per_word, 1, 0, 0, 0) fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) return [byte for byte in ctypes.string_at(receive_buffer, length)] From a381b660f875266b423fb21520bbc039438f17a8 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 15:50:35 +0800 Subject: [PATCH 15/18] Bacon Use spidev --- FlashGBX/bacon/bacon.py | 58 ++--- FlashGBX/bacon/linuxspi.py | 437 ------------------------------------- 2 files changed, 31 insertions(+), 464 deletions(-) delete mode 100644 FlashGBX/bacon/linuxspi.py diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 13b4e9d..60eab16 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -8,25 +8,7 @@ import os from .command import * -#HIDAPI -from ch347api import CH347HIDDev, I2CDevice, SPIDevice, UARTDevice, SPIClockFreq, I2CClockFreq -#WCHAPI -from .ch347 import SPIConfig, CH347 -from .linuxspi import SPI -spi_config = SPIConfig( - Mode=3, - Clock=0, - ByteOrder=1, - SpiWriteReadInterval=0, - SpiOutDefaultData=0xFF, - ChipSelect=0x80, - CS1Polarity=0, - CS2Polarity=0, - IsAutoDeative=1, - ActiveDelay=0, - DelayDeactive=0, -) ROM_MAX_SIZE = 0x2000000 # 32MB RAM_MAX_SIZE = 0x20000 # 128KB (with bank) @@ -80,22 +62,44 @@ def __init__(self, hid_device=None, ch347_device=None, gpio_device=None): if self.ch347_device is None and self.hid_device is None and self.gpio_device is None: if platform.system() == "Windows": if self.ch347_device is None: + #WCHAPI + from .ch347 import SPIConfig, CH347 ch347 = CH347(device_index=0, dll_path=os.path.join(os.path.dirname(__file__), "lib/CH347DLLA64.DLL")) ch347.close_device() ch347.open_device() + spi_config = SPIConfig( + Mode=3, + Clock=0, + ByteOrder=1, + SpiWriteReadInterval=0, + SpiOutDefaultData=0xFF, + ChipSelect=0x80, + CS1Polarity=0, + CS2Polarity=0, + IsAutoDeative=1, + ActiveDelay=0, + DelayDeactive=0, + ) if ch347.spi_init(spi_config): # True if successful, False otherwise. self.ch347_device = ch347 # cannot find wch device, try hid device if self.ch347_device is None: - self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) + try: + #HIDAPI + from ch347api import SPIDevice, SPIClockFreq + self.hid_device = SPIDevice(clock_freq_level=SPIClockFreq.f_60M, is_16bits=False, mode=3, is_MSB=True) + except Exception as e: + print(e) elif platform.system() == "Linux" and os.path.exists("/dev/spidev3.0"): try: - spi = SPI("/dev/spidev3.0", speed=60000000, phase=True, polarity=True, bits_per_word=8, lsb_first=False, cs_high=False) # TODO. Auto find spi device - # spi.mode = SPI.MODE_3 - # spi.bits_per_word = 8 - # spi.speed = 60000000 - # spi.cs_high = True - # spi.lsb_first = False + import spidev + spi = spidev.SpiDev() + spi.open(3, 0) # bus 3, device 0 + spi.max_speed_hz = 60000000 + spi.mode = 0b11 + spi.bits_per_word = 8 + spi.lsbfirst = False + spi.cshigh = False self.gpio_device = spi except Exception as e: print(e) @@ -122,7 +126,7 @@ def Write(self, data: bytes) -> bool: if len(data) > self.MAX_LEN: raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) if self.gpio_device is not None: - self.gpio_device.write(data) + self.gpio_device.xfer(data) return True if self.ch347_device is not None: return self.ch347_device.spi_write(0x80, data) @@ -134,7 +138,7 @@ def WriteRead(self, data: bytes) -> bytes: if len(data) > self.MAX_LEN: raise ValueError("Data length must be less than 0x%04X" % self.MAX_LEN) if self.gpio_device is not None: - return bytes(self.gpio_device.transfer(data)) + return bytes(self.gpio_device.xfer(data)) if self.ch347_device is not None: return bytes(self.ch347_device.spi_write_read(0x80, data)) if self.hid_device is not None: diff --git a/FlashGBX/bacon/linuxspi.py b/FlashGBX/bacon/linuxspi.py deleted file mode 100644 index 1a34d4f..0000000 --- a/FlashGBX/bacon/linuxspi.py +++ /dev/null @@ -1,437 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2015 Thomas Stokes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -__author__ = 'Tom Stokes' - -import ctypes -import struct -import fcntl -import array -import os.path - - -def _ioc(direction, number, structure): - """ - ioctl command encoding helper function - - Calculates the appropriate spidev ioctl op argument given the direction, - command number, and argument structure in python's struct.pack format. - - Returns a tuple of the calculated op and the struct.pack format - - See Linux kernel source file /include/uapi/asm-generic/ioctl.h - """ - ioc_magic = ord('k') - ioc_nrbits = 8 - ioc_typebits = 8 - ioc_sizebits = 14 # XXX: 13 on PPC, MIPS, Sparc, and Alpha - ioc_nrshift = 0 - ioc_typeshift = ioc_nrshift + ioc_nrbits - ioc_sizeshift = ioc_typeshift + ioc_typebits - ioc_dirshift = ioc_sizeshift + ioc_sizebits - - size = struct.calcsize(structure) - - op = (direction << ioc_dirshift) | (ioc_magic << ioc_typeshift) | \ - (number << ioc_nrshift) | (size << ioc_sizeshift) - - return direction, op, structure - - -class SPI(object): - """ - struct spi_ioc_transfer { - __u64 tx_buf; - __u64 rx_buf; - __u32 len; - __u32 speed_hz; - __u16 delay_usecs; - __u8 bits_per_word; - __u8 cs_change; - __u8 tx_nbits; - __u8 rx_nbits; - __u16 pad; - """ - _IOC_TRANSFER_FORMAT = "QQIIHBBBBH" - - _IOC_WRITE = 1 - _IOC_READ = 2 - - # _IOC_MESSAGE is a special case, so we ony need the ioctl number - _IOC_MESSAGE = _ioc(_IOC_WRITE, 0, _IOC_TRANSFER_FORMAT)[1] - - _IOC_RD_MODE = _ioc(_IOC_READ, 1, "B") - _IOC_WR_MODE = _ioc(_IOC_WRITE, 1, "B") - - _IOC_RD_LSB_FIRST = _ioc(_IOC_READ, 2, "B") - _IOC_WR_LSB_FIRST = _ioc(_IOC_WRITE, 2, "B") - - _IOC_RD_BITS_PER_WORD = _ioc(_IOC_READ, 3, "B") - _IOC_WR_BITS_PER_WORD = _ioc(_IOC_WRITE, 3, "B") - - _IOC_RD_MAX_SPEED_HZ = _ioc(_IOC_READ, 4, "I") - _IOC_WR_MAX_SPEED_HZ = _ioc(_IOC_WRITE, 4, "I") - - _IOC_RD_MODE32 = _ioc(_IOC_READ, 5, "I") - _IOC_WR_MODE32 = _ioc(_IOC_WRITE, 5, "I") - - CPHA = 0x01 - CPOL = 0x02 - CS_HIGH = 0x04 - LSB_FIRST = 0x08 - THREE_WIRE = 0x10 - LOOP = 0x20 - NO_CS = 0x40 - READY = 0x80 - TX_DUAL = 0x100 - TX_QUAD = 0x200 - RX_DUAL = 0x400 - RX_QUAD = 0x800 - - MODE_0 = 0 - MODE_1 = CPHA - MODE_2 = CPOL - MODE_3 = CPHA | CPOL - - def __init__(self, device, speed=None, bits_per_word=None, phase=None, - polarity=None, cs_high=None, lsb_first=None, - three_wire=None, loop=None, no_cs=None, ready=None): - """Create spidev interface object. - - Args: - device: Tuple of (bus, device) or string of device path - speed: Optional target bus speed in Hz. - bits_per_word: Optional number of bits per word. - - Raises: - IOError: The spidev device could not be opened (check permissions) - """ - if isinstance(device, tuple): - (bus, dev) = device - device = "/dev/spidev{:d}.{:d}".format(bus, dev) - - if not os.path.exists(device): - raise IOError("{} does not exist".format(device)) - - self.handle = open(device, "w+b", buffering=0) - - if speed is not None: - self.speed = speed - - if bits_per_word is not None: - self.bits_per_word = bits_per_word - - if phase is not None: - self.phase = phase - - if polarity is not None: - self.polarity = polarity - - if cs_high is not None: - self.cs_high = cs_high - - if lsb_first is not None: - self.lsb_first = lsb_first - - if three_wire is not None: - self.three_wire = three_wire - - if self.loop is not None: - self.loop = loop - - if self.no_cs is not None: - self.no_cs = no_cs - - if self.ready is not None: - self.ready = ready - - def _ioctl(self, ioctl_data, data=None): - """ioctl helper function. - - Performs an ioctl on self.handle. If the ioctl is an SPI read type - ioctl, returns the result value. - - Args: - ioctl_data: Tuple of (direction, op structure), where direction - is one of SPI._IOC_READ or SPI._IOC_WRITE, op is the - pre-computed ioctl op (see _ioc above) and structure is the - Python format string for the ioctl arguments. - - Returns: - If ioctl_data specifies an SPI._IOC_READ, returns the result. - For SPI._IOC_WRITE types, returns None - """ - (direction, ioctl, structure) = ioctl_data - if direction == SPI._IOC_READ: - arg = array.array(structure, [0]) - fcntl.ioctl(self.handle, ioctl, arg, True) - return arg[0] - else: - arg = struct.pack("=" + structure, data) - fcntl.ioctl(self.handle, ioctl, arg) - return - - def _get_mode(self): - """Helper function to get spidev mode - - Returns: - spidev mode as an integer. Bits correspond to SPI.CPHA, - SPI.CPOL, SPI.CS_HIGH, SPI.LSB_FIRST, SPI.THREE_WIRE, SPI.LOOP, - SPI.NO_CS, and SPI.READY - """ - return self._ioctl(SPI._IOC_RD_MODE) - - def _set_mode(self, mode): - """Helper function to set the spidev mode - - Args: - mode: spidev mode as an integer. Bits correspond to SPI.CPHA, - SPI.CPOL, SPI.CS_HIGH, SPI.LSB_FIRST, SPI.THREE_WIRE, SPI.LOOP, - SPI.NO_CS, and SPI.READY - """ - self._ioctl(SPI._IOC_WR_MODE, mode) - - def _get_mode_field(self, field): - """Helper function to get specific spidev mode bits - - Args: - field: Bit mask to apply to spidev mode. - - Returns: - bool(mode & field). True if specified bit is 1, otherwise False - """ - return True if self._get_mode() & field else False - - def _set_mode_field(self, field, value): - """Helper function to set a spidev mode bit - - Args: - field: Bitmask of bit(s) to set to value - value: True to set bit(s) to 1, false to set bit(s) to 0 - """ - mode = self._get_mode() - if value: - mode |= field - else: - mode &= ~field - self._set_mode(mode) - - @property - def phase(self): - """SPI clock phase bit - - False: Sample at leading edge of clock - True: Sample at trailing edge of clock - """ - return self._get_mode_field(SPI.CPHA) - - @phase.setter - def phase(self, phase): - self._set_mode_field(SPI.CPHA, phase) - - @property - def polarity(self): - """SPI polarity bit - - False: Data sampled at rising edge, data changes on falling edge - True: Data sampled at falling edge, data changes on rising edge - """ - return self._get_mode_field(SPI.CPOL) - - @polarity.setter - def polarity(self, polarity): - self._set_mode_field(SPI.CPOL, polarity) - - @property - def cs_high(self): - """SPI chip select active level - - True: Chip select is active high - False: Chip select is active low - """ - return self._get_mode_field(SPI.CS_HIGH) - - @cs_high.setter - def cs_high(self, cs_high): - self._set_mode_field(SPI.CS_HIGH, cs_high) - - @property - def lsb_first(self): - """Bit order of SPI word transfers - - False: Send MSB first - True: Send LSB first - """ - return self._get_mode_field(SPI.LSB_FIRST) - - @lsb_first.setter - def lsb_first(self, lsb_first): - self._set_mode_field(SPI.LSB_FIRST, lsb_first) - - @property - def three_wire(self): - """SPI 3-wire mode - - True: Data is read and written on the same line (3-wire mode) - False: Data is read and written on separate lines (MOSI & MISO) - """ - return self._get_mode_field(SPI.THREE_WIRE) - - @three_wire.setter - def three_wire(self, three_wire): - self._set_mode_field(SPI.THREE_WIRE, three_wire) - - @property - def loop(self): - """SPI loopback mode""" - return self._get_mode_field(SPI.LOOP) - - @loop.setter - def loop(self, loop): - self._set_mode_field(SPI.LOOP, loop) - - @property - def no_cs(self): - """No chipselect. Single device on bus.""" - return self._get_mode_field(SPI.NO_CS) - - @no_cs.setter - def no_cs(self, no_cs): - self._set_mode_field(SPI.NO_CS, no_cs) - - @property - def ready(self): - """Slave pulls low to pause""" - return self._get_mode_field(SPI.READY) - - @ready.setter - def ready(self, ready): - self._set_mode_field(SPI.READY, ready) - - @property - def speed(self): - """Maximum SPI transfer speed in Hz. - - Note that the controller cannot necessarily assign the requested - speed. - """ - return self._ioctl(SPI._IOC_RD_MAX_SPEED_HZ) - - @speed.setter - def speed(self, speed): - self._ioctl(SPI._IOC_WR_MAX_SPEED_HZ, speed) - - @property - def bits_per_word(self): - """Number of bits per word of SPI transfer. - - A value of 0 is equivalent to 8 bits per word - """ - return self._ioctl(SPI._IOC_RD_BITS_PER_WORD) - - @bits_per_word.setter - def bits_per_word(self, bits_per_word): - self._ioctl(SPI._IOC_WR_BITS_PER_WORD, bits_per_word) - - @property - def mode(self): - return self._get_mode() - - @mode.setter - def mode(self, mode): - self._set_mode(mode) - - def write(self, data, speed=0, bits_per_word=0, delay=0): - """Perform half-duplex SPI write. - - Args: - data: List of words to write - speed: Optional temporary bitrate override in Hz. 0 (default) - uses existing spidev speed setting. - bits_per_word: Optional temporary bits_per_word override. 0 - (default) will use the current bits_per_word setting. - delay: Optional delay in usecs between sending the last bit and - deselecting the chip select line. 0 (default) for no delay. - """ - data = array.array('B', data).tobytes() - length = len(data) - transmit_buffer = ctypes.create_string_buffer(data) - spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, - ctypes.addressof(transmit_buffer), 0, - length, speed, delay, bits_per_word, 1, - 0, 0, 0) - fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) - - def read(self, length, speed=0, bits_per_word=0, delay=0): - """Perform half-duplex SPI read as a binary string - - Args: - length: Integer count of words to read - speed: Optional temporary bitrate override in Hz. 0 (default) - uses existing spidev speed setting. - bits_per_word: Optional temporary bits_per_word override. 0 - (default) will use the current bits_per_word setting. - delay: Optional delay in usecs between sending the last bit and - deselecting the chip select line. 0 (default) for no delay. - - Returns: - List of words read from device - """ - receive_buffer = ctypes.create_string_buffer(length) - spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, 0, - ctypes.addressof(receive_buffer), - length, speed, delay, bits_per_word, 1, - 0, 0, 0) - fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) - return [byte for byte in ctypes.string_at(receive_buffer, length)] - - def transfer(self, data, speed=0, bits_per_word=0, delay=0): - """Perform full-duplex SPI transfer - - Args: - data: List of words to transmit - speed: Optional temporary bitrate override in Hz. 0 (default) - uses existing spidev speed setting. - bits_per_word: Optional temporary bits_per_word override. 0 - (default) will use the current bits_per_word setting. - delay: Optional delay in usecs between sending the last bit and - deselecting the chip select line. 0 (default) for no delay. - - Returns: - List of words read from SPI bus during transfer - """ - data = array.array('B', data).tobytes() - length = len(data) - transmit_buffer = ctypes.create_string_buffer(data) - receive_buffer = ctypes.create_string_buffer(length) - spi_ioc_transfer = struct.pack(SPI._IOC_TRANSFER_FORMAT, - ctypes.addressof(transmit_buffer), - ctypes.addressof(receive_buffer), - length, speed, delay, bits_per_word, 1, - 0, 0, 0) - fcntl.ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer) - return [byte for byte in ctypes.string_at(receive_buffer, length)] - def close(self): - """Close the spidev device""" - self.handle.close() \ No newline at end of file From 532802975ce601438b5f01dae2b9428aea1782ab Mon Sep 17 00:00:00 2001 From: ChisBread Date: Sat, 7 Dec 2024 15:53:10 +0800 Subject: [PATCH 16/18] Bacon Fix --- FlashGBX/bacon/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FlashGBX/bacon/__init__.py b/FlashGBX/bacon/__init__.py index 0510f02..202c16d 100644 --- a/FlashGBX/bacon/__init__.py +++ b/FlashGBX/bacon/__init__.py @@ -52,5 +52,4 @@ from .ch347 import * from .bacon import * from .command import * -from .serial import * -from .linuxspi import * \ No newline at end of file +from .serial import * \ No newline at end of file From 68a69587eff97e6ebe236a4d3adfd070c1413d94 Mon Sep 17 00:00:00 2001 From: ChisBread Date: Fri, 9 May 2025 20:14:42 +0800 Subject: [PATCH 17/18] Feat: faster rom cache --- FlashGBX/bacon/serial.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/FlashGBX/bacon/serial.py b/FlashGBX/bacon/serial.py index 84eee2b..9b7b893 100644 --- a/FlashGBX/bacon/serial.py +++ b/FlashGBX/bacon/serial.py @@ -104,14 +104,10 @@ def __init__(self): self.ROM_CACHED = [False]*0x2000000 def cache_rom(self, addr, data): - for i in range(len(data)): - self.ROM_CACHE[addr+i] = data[i] - self.ROM_CACHED[addr+i] = True - + self.ROM_CACHE[addr:addr+len(data)] = data + self.ROM_CACHED[addr:addr+len(data)] = [True]*len(data) def cache_rom_reset(self, addr=0, size=0x2000000): - for i in range(size): - self.ROM_CACHED[addr+i] = False - + self.ROM_CACHED[addr:addr+size] = [False]*size def read_rom(self, addr, size): if self.ROM_CACHED[addr:addr+size].count(False) > 0: dprint("[BaconFakeSerialDevice] ReadROM:0x%08X Size:%s" % (addr, size)) @@ -312,9 +308,15 @@ def _cmd_parse(self, cmd): elif cmdname == "SET_MODE_AGB": self.MODE = "AGB" self._push_ack() + elif cmdname == "SET_MODE_DMG": + self.MODE = "DMG" + self._push_ack() elif cmdname == "SET_VOLTAGE_3_3V": self.POWER = 3 self._push_ack() + elif cmdname == "SET_VOLTAGE_5V": + self.POWER = 5 + self._push_ack() elif cmdname == "CART_PWR_ON": if self.POWER == 3: self.bacon_dev.PowerControl(v3_3v=True, v5v=False).Flush() From 958521bca6a1c7f66bfe08ad4f18afa60f94127e Mon Sep 17 00:00:00 2001 From: ChisBread Date: Fri, 9 May 2025 21:24:56 +0800 Subject: [PATCH 18/18] Feat: fix GetMode? --- FlashGBX/LK_Device.py | 8 +++++++- FlashGBX/bacon/bacon.py | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/FlashGBX/LK_Device.py b/FlashGBX/LK_Device.py index fdb012b..9ca9947 100644 --- a/FlashGBX/LK_Device.py +++ b/FlashGBX/LK_Device.py @@ -807,8 +807,14 @@ def SetVarState(self, var_state): self._write(self.DEVICE_CMD["SET_VAR_STATE"]) time.sleep(0.2) self.DEVICE.write(var_state) - + def GetMode(self): + mode = self._getMode() + if mode is None: + return self._getMode() + return mode + + def _getMode(self): if time.time() < self.LAST_CHECK_ACTIVE + 1: return self.MODE if self.CheckActive() is False: return None if self.MODE is None: return None diff --git a/FlashGBX/bacon/bacon.py b/FlashGBX/bacon/bacon.py index 60eab16..d87e68e 100644 --- a/FlashGBX/bacon/bacon.py +++ b/FlashGBX/bacon/bacon.py @@ -447,3 +447,6 @@ def AGBGPIORead(self) -> int: if self.power != 3: raise ValueError("Power must be 3.3v") return self.AGBReadROM(self.GPIO_REG_DAT, 1)[0] + + def DMGReadROM(self, addr: int, size: int, reset=True, callback=None) -> bytes: + pass \ No newline at end of file