unit USBasp; { This file is part of Nephelae's Object Pascal FPUSBasp. USBasp Class. Copyright (C) 2022 - 2025 Dimitrios Chr. Ioannidis. Nephelae - https://www.nephelae.eu https://www.nephelae.eu/ Licensed under the MIT License (MIT). See licence file in root directory. 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. } {$mode objfpc}{$H+} interface uses Classes, SysUtils, syncobjs, hidapi, SPSCRingBuffer, USBasp_Threads; const USBASP_SHARED_VID = $16C0; USBASP_SHARED_PID = $05DC; USBASP_FUNC_GETCAPABILITIES = 127; USBASP_CAP_0_TPI = 1; USBASP_CAP_PDI = 16; USBASP_CAP_2_SNHIDUPDATE = 32; USBASP_CAP_6_UART = 64; USBASP_CAP_7_HID_UART = 128; USBASP_NO_CAPS = (-4); USBASP_CAP_12MHZ_CLOCK = 0; USBASP_CAP_16MHZ_CLOCK = 1; USBASP_CAP_18MHZ_CLOCK = 2; USBASP_CAP_20MHZ_CLOCK = 3; PROG_STATE_IDLE = 0; PROG_STATE_WRITEFLASH = 1; PROG_STATE_READFLASH = 2; PROG_STATE_READEEPROM = 3; PROG_STATE_WRITEEEPROM = 4; PROG_STATE_TPI_READ = 5; PROG_STATE_TPI_WRITE = 6; PROG_STATE_SET_REPORT = 7; UART_STATE_ENABLED = 16; UART_STATE_DISABLED = 0; // USBasp UART Extension // https://github.com/dioannidis/usbasp/blob/master/firmware/usbasp.h USBASP_UART_PARITY_MASK = %11; USBASP_UART_PARITY_NONE = %00; USBASP_UART_PARITY_EVEN = %01; USBASP_UART_PARITY_ODD = %10; USBASP_UART_STOP_MASK = %100; USBASP_UART_STOP_1BIT = %000; USBASP_UART_STOP_2BIT = %100; USBASP_UART_BYTES_MASK = %111000; USBASP_UART_BYTES_5B = %000000; USBASP_UART_BYTES_6B = %001000; USBASP_UART_BYTES_7B = %010000; USBASP_UART_BYTES_8B = %011000; USBASP_UART_BYTES_9B = %100000; TUARTBaudRate: array[0..13] of integer = (300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 57600, 74880, 115200); TUARTDataBits: array[0..4] of integer = (USBASP_UART_BYTES_5B, USBASP_UART_BYTES_6B, USBASP_UART_BYTES_7B, USBASP_UART_BYTES_8B, USBASP_UART_BYTES_9B); TUARTParity: array[0..2] of integer = (USBASP_UART_PARITY_NONE, USBASP_UART_PARITY_EVEN, USBASP_UART_PARITY_ODD); TUARTStopBit: array[0..1] of integer = (USBASP_UART_STOP_1BIT, USBASP_UART_STOP_2BIT); type { TUSBasp } TUSBasp = class(TObject) private FUART_HIDDevice, FStatus_HIDDevice: PHidDevice; FUART_HIDDevicePath, FStatus_HIDDevicePath, FSerial, FManufacturer, FProductName, FHidApiVersionString: string; FConnected, FHasHIDUart, FHasStatus, FHasPDI, FHasTPI, FHasSNWrite, FUARTOpened: boolean; FCrystalOSC: integer; FStatus_RxEvent, FStatus_TxEvent, FUART_RxEvent, FUART_TxEvent, FUnpluggedEvent: TEvent; //FLastUsbError: integer; FUART_RxBuffer, FUART_TxBuffer, FStatus_RxBuffer, FStatus_TxBuffer: TSPSCRingBuffer; FUART_RxThread: TThread_HID_UART_Rx; FUART_TxThread: TThread_HID_UART_Tx; FStatus_RxThread: TThread_HID_Status_Rx; FStatus_TxThread: TThread_HID_Status_Tx; function HIDEnumerate(const ASerialNumber: string = ''): boolean; protected property ReceiveBuffer: TSPSCRingBuffer read FUART_RxBuffer; property ReceiveEvent: TEvent read FUART_RxEvent; property TransmitBuffer: TSPSCRingBuffer read FUART_TxBuffer; property TransmitEvent: TEvent read FUART_TxEvent; property MonitorBuffer: TSPSCRingBuffer read FStatus_RxBuffer; property MonitorEvent: TEvent read FStatus_RxEvent; property DisconnectEvent: TEvent read FUnpluggedEvent; public constructor Create; destructor Destroy; override; function Connect(const ASerialNumber: string = ''): boolean; function Disconnect: boolean; function UARTOpen(const ABaudRate, ADataBits, AParity, AStopBits: integer): boolean; function UARTClose: boolean; function ChangeSerialNumber(const ASerialNumber: string): integer; property Connected: boolean read FConnected; property UARTOpened: boolean read FUARTOpened; property HidApiVersion: string read FHidApiVersionString; end; implementation { TUSBasp } constructor TUSBasp.Create; begin FConnected := False; FUARTOpened := False; FSerial := ''; FUART_RxBuffer := TSPSCRingBuffer.Create(1024); FUART_TxBuffer := TSPSCRingBuffer.Create(1024); FStatus_RxBuffer := TSPSCRingBuffer.Create(1024); FStatus_TxBuffer := TSPSCRingBuffer.Create(1024); Randomize; FUART_RxEvent := TEvent.Create(nil, False, False, 'UARTRX_' + IntToStr(Random(999))); FUART_TxEvent := TEvent.Create(nil, True, False, 'UARTTX_' + IntToStr(Random(999))); FStatus_RxEvent := TEvent.Create(nil, False, False, 'STUSRX_' + IntToStr(Random(999))); FStatus_TxEvent := TEvent.Create(nil, True, False, 'STUSTX_' + IntToStr(Random(999))); FUnpluggedEvent := TEvent.Create(nil, False, False, 'UNPLD_' + IntToStr(Random(999))); if HidInit = 0 then HIDEnumerate; end; destructor TUSBasp.Destroy; begin Disconnect; FreeAndNil(FUART_RxBuffer); FreeAndNil(FUART_TxBuffer); FreeAndNil(FStatus_RxBuffer); FreeAndNil(FStatus_TxBuffer); FUART_RxEvent.Free; FUART_TxEvent.Free; FStatus_RxEvent.Free; FStatus_TxEvent.Free; FUnpluggedEvent.Free; HidExit(); inherited Destroy; end; function TUSBasp.Connect(const ASerialNumber: string = ''): boolean; begin FConnected := False; if not FConnected then begin FUART_HIDDevice := THidDevice.OpenPath(FUART_HIDDevicePath); FUART_HIDDevice^.SetNonBlocking(0); FUART_RxThread := TThread_HID_UART_Rx.Create( FUART_HIDDevice, FUART_RxBuffer, FUART_RxEvent, FUnpluggedEvent); FUART_TxThread := TThread_HID_UART_Tx.Create( FUART_HIDDevice, FUART_TxBuffer, FUART_TxEvent, FUnpluggedEvent); if FHasStatus then begin FStatus_HIDDevice := THidDevice.OpenPath(FStatus_HIDDevicePath); FStatus_HIDDevice^.SetNonBlocking(0); FStatus_RxThread := TThread_HID_Status_Rx.Create(FStatus_HIDDevice, FStatus_RxBuffer, FStatus_RxEvent, FUnpluggedEvent); FStatus_TxThread := TThread_HID_Status_Tx.Create(FStatus_HIDDevice, FStatus_TxBuffer, FStatus_TxEvent, FUnpluggedEvent); end; FConnected := True; end; Result := FConnected; end; function TUSBasp.Disconnect: boolean; begin if FConnected then begin if FUARTOpened then UARTClose; FUART_RxThread.Terminate; FUART_RxThread.WaitFor; FreeAndNil(FUART_RxThread); FUART_TxThread.Terminate; FUART_TxEvent.SetEvent; FUART_TxThread.WaitFor; FreeAndNil(FUART_TxThread); FUART_HIDDevice^.Close; FUART_HIDDevice := nil; if FHasStatus then begin FStatus_RxThread.Terminate; FStatus_RxThread.WaitFor; FreeAndNil(FStatus_RxThread); FStatus_TxThread.Terminate; FStatus_TxEvent.SetEvent; FStatus_TxThread.WaitFor; FreeAndNil(FStatus_TxThread); FStatus_HIDDevice^.Close; FStatus_HIDDevice := nil; end; FConnected := False; end; Result := FConnected; end; function TUSBasp.UARTOpen(const ABaudRate, ADataBits, AParity, AStopBits: integer): boolean; var Buffer: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0); Prescaler: word; begin if FConnected and not FUARTOpened then begin Prescaler := FCrystalOsc div 8 div ABaudRate - 1; Buffer[0] := 0; Buffer[1] := lo(Prescaler); Buffer[2] := hi(Prescaler); Buffer[3] := ADataBits or AStopBits or AParity; if FUART_HIDDevice^.SendFeatureReport(Buffer, 8 + 1) > 0 then FUARTOpened := True; end; Result := FUARTOpened; end; function TUSBasp.UARTClose: boolean; var Buffer: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0); begin if FConnected and FUARTOpened then begin Buffer[0] := 0; Buffer[1] := 0; Buffer[2] := 0; Buffer[3] := 0; FUART_HIDDevice^.SendFeatureReport(Buffer, 8 + 1); FUARTOpened := False; end; Result := FUARTOpened; end; function TUSBasp.HIDEnumerate(const ASerialNumber: string): boolean; var HidItemRoot, HidItem: PHidDeviceInfo; HIDDeviceMatch: boolean; FeatureReportBuffer: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0); begin Result := False; FUART_HIDDevicePath := ''; FStatus_HIDDevicePath := ''; FUART_HIDDevice := nil; FStatus_HIDDevice := nil; try HidItemRoot := THidDeviceInfo.Enumerate(USBASP_SHARED_VID, USBASP_SHARED_PID); HidItem := HidItemRoot; while Assigned(HidItem) do begin HIDDeviceMatch := True; if ASerialNumber <> '' then if ASerialNumber <> PCWCharToUnicodeString(HidItem^.SerialNumber) then HIDDeviceMatch := False; if HIDDeviceMatch then case HidItem^.InterfaceNumber of 1: begin FUART_HIDDevicePath := HidItem^.Path; Result := True; end; 2: begin FStatus_HIDDevicePath := HidItem^.Path; FHasStatus := True; Break; end; end; HidItem := HidItem^.Next; end; finally HidItemRoot^.Free; end; if Result then begin FUART_HIDDevice := THidDevice.OpenPath(FUART_HIDDevicePath); FSerial := FUART_HIDDevice^.GetSerialNumberString; FManufacturer := FUART_HIDDevice^.GetManufacturerString; FProductName := FUART_HIDDevice^.GetProductString; if FUART_HIDDevice^.GetFeatureReport(FeatureReportBuffer, 8 + 1) > 0 then begin case FeatureReportBuffer[6] of USBASP_CAP_12MHZ_CLOCK: FCrystalOsc := 12000000; USBASP_CAP_16MHZ_CLOCK: FCrystalOsc := 16000000; USBASP_CAP_18MHZ_CLOCK: FCrystalOsc := 18000000; USBASP_CAP_20MHZ_CLOCK: FCrystalOsc := 20000000; end; FHasHIDUart := (FeatureReportBuffer[5] and USBASP_CAP_7_HID_UART) = USBASP_CAP_7_HID_UART; FHasPDI := (FeatureReportBuffer[5] and USBASP_CAP_PDI) = USBASP_CAP_PDI; FHasTPI := (FeatureReportBuffer[5] and USBASP_CAP_0_TPI) = USBASP_CAP_0_TPI; FHasSNWrite := (FeatureReportBuffer[5] and USBASP_CAP_2_SNHIDUPDATE) = USBASP_CAP_2_SNHIDUPDATE; end; FUART_HIDDevice^.Close; FUART_HIDDevice := nil; end; end; function TUSBasp.ChangeSerialNumber(const ASerialNumber: string): integer; var Buffer: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0); ErrorCode: integer; SerNumValue: word; begin Result := 0; if FConnected and not FUARTOpened then begin Val(ASerialNumber, SerNumValue, ErrorCode); Buffer[0] := 0; Buffer[1] := lo(SerNumValue); Buffer[2] := Hi(SerNumValue); Buffer[4] := 1; FUART_HIDDevice^.SendFeatureReport(Buffer, 8 + 1); end; end; end.