#1 Add USBasp base sources

Merged
d.ioannidis merged 1 commits from d.ioannidis/master into Nephelae/master 6 months ago

BIN
demo/opendevice/prjUSBaspOpen.ico


+ 130 - 0
demo/opendevice/prjUSBaspOpen.lpi

@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <PathDelim Value="\"/>
+    <General>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="prjUSBaspOpen"/>
+      <Scaled Value="True"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+      <XPManifest>
+        <DpiAware Value="True"/>
+      </XPManifest>
+      <Icon Value="0"/>
+    </General>
+    <BuildModes>
+      <Item Name="Debug" Default="True"/>
+      <Item Name="Release">
+        <CompilerOptions>
+          <Version Value="11"/>
+          <PathDelim Value="\"/>
+          <Target>
+            <Filename Value="bin\$(TargetCPU)-$(TargetOS)\$(BuildMode)\prjUSBaspOpen"/>
+          </Target>
+          <SearchPaths>
+            <IncludeFiles Value="$(ProjOutDir)"/>
+            <OtherUnitFiles Value="..\..\source"/>
+            <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\$(BuildMode)"/>
+          </SearchPaths>
+          <CodeGeneration>
+            <SmartLinkUnit Value="True"/>
+            <Optimizations>
+              <OptimizationLevel Value="3"/>
+            </Optimizations>
+          </CodeGeneration>
+          <Linking>
+            <Debugging>
+              <GenerateDebugInfo Value="False"/>
+              <RunWithoutDebug Value="True"/>
+            </Debugging>
+            <LinkSmart Value="True"/>
+            <Options>
+              <Win32>
+                <GraphicApplication Value="True"/>
+              </Win32>
+            </Options>
+          </Linking>
+        </CompilerOptions>
+      </Item>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <RequiredPackages>
+      <Item>
+        <PackageName Value="LCL"/>
+      </Item>
+    </RequiredPackages>
+    <Units>
+      <Unit>
+        <Filename Value="prjUSBaspOpen.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="prjusbaspopendevice.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="Form1"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="prjUSBaspOpenDevice"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value="bin\$(TargetCPU)-$(TargetOS)\$(BuildMode)\prjUSBaspOpen"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="..\..\source"/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\$(BuildMode)"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <IncludeAssertionCode Value="True"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <Checks>
+        <IOChecks Value="True"/>
+        <RangeChecks Value="True"/>
+        <OverflowChecks Value="True"/>
+        <StackChecks Value="True"/>
+      </Checks>
+      <VerifyObjMethodCallValidity Value="True"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf3"/>
+        <UseHeaptrc Value="True"/>
+        <TrashVariables Value="True"/>
+        <UseExternalDbgSyms Value="True"/>
+      </Debugging>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 25 - 0
demo/opendevice/prjUSBaspOpen.lpr

@@ -0,0 +1,25 @@
+program prjUSBaspOpen;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}
+  cthreads,
+  {$ENDIF}
+  {$IFDEF HASAMIGA}
+  athreads,
+  {$ENDIF}
+  Interfaces, // this includes the LCL widgetset
+  Forms, prjUSBaspOpenDevice
+  { you can add units after this };
+
+{$R *.res}
+
+begin
+  RequireDerivedFormResource:=True;
+  Application.Scaled:=True;
+  Application.Initialize;
+  Application.CreateForm(TForm1, Form1);
+  Application.Run;
+end.
+

+ 47 - 0
demo/opendevice/prjusbaspopendevice.lfm

@@ -0,0 +1,47 @@
+object Form1: TForm1
+  Left = 200
+  Height = 240
+  Top = 100
+  Width = 320
+  Caption = 'Form1'
+  ClientHeight = 240
+  ClientWidth = 320
+  OnCreate = FormCreate
+  OnDestroy = FormDestroy
+  object Button1: TButton
+    Left = 24
+    Height = 25
+    Top = 24
+    Width = 75
+    Caption = 'Button1'
+    TabOrder = 0
+    OnClick = Button1Click
+  end
+  object Button2: TButton
+    Left = 24
+    Height = 25
+    Top = 72
+    Width = 75
+    Caption = 'Button2'
+    TabOrder = 1
+    OnClick = Button2Click
+  end
+  object Button3: TButton
+    Left = 144
+    Height = 25
+    Top = 24
+    Width = 75
+    Caption = 'Button3'
+    TabOrder = 2
+    OnClick = Button3Click
+  end
+  object Button4: TButton
+    Left = 144
+    Height = 25
+    Top = 72
+    Width = 75
+    Caption = 'Button4'
+    TabOrder = 3
+    OnClick = Button4Click
+  end
+end

+ 72 - 0
demo/opendevice/prjusbaspopendevice.pas

@@ -0,0 +1,72 @@
+unit prjUSBaspOpenDevice;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  USBasp;
+
+type
+
+  { TForm1 }
+
+  TForm1 = class(TForm)
+    Button1: TButton;
+    Button2: TButton;
+    Button3: TButton;
+    Button4: TButton;
+    procedure Button1Click(Sender: TObject);
+    procedure Button2Click(Sender: TObject);
+    procedure Button3Click(Sender: TObject);
+    procedure Button4Click(Sender: TObject);
+    procedure FormCreate(Sender: TObject);
+    procedure FormDestroy(Sender: TObject);
+  private
+    FUSBasp: TUSBasp;
+  public
+
+  end;
+
+var
+  Form1: TForm1;
+
+implementation
+
+{$R *.lfm}
+
+{ TForm1 }
+
+procedure TForm1.FormCreate(Sender: TObject);
+begin
+  FUSBasp := TUSBasp.Create;
+end;
+
+procedure TForm1.Button1Click(Sender: TObject);
+begin
+  FUSBasp.Connect;
+end;
+
+procedure TForm1.Button2Click(Sender: TObject);
+begin
+  FUSBasp.Disconnect;
+end;
+
+procedure TForm1.Button3Click(Sender: TObject);
+begin
+  FUSBasp.UARTOpen(9600, USBASP_UART_BYTES_8B, USBASP_UART_PARITY_NONE, USBASP_UART_STOP_1BIT);
+end;
+
+procedure TForm1.Button4Click(Sender: TObject);
+begin
+  FUSBasp.UARTClose;
+end;
+
+procedure TForm1.FormDestroy(Sender: TObject);
+begin
+  FUSBasp.Free;
+end;
+
+end.
+

+ 170 - 0
source/spscringbuffer.pas

@@ -0,0 +1,170 @@
+unit SPSCRingBuffer;
+
+{
+
+  Single Producer Single Consumer (SPSC) Ring Buffer .
+
+  Copyright (C) 2022 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;
+
+type
+
+  { TSPSCRingBuffer }
+
+  TSPSCRingBuffer = class(TObject)
+  private
+    FMemoryData: Pointer;
+    FMemorySize, FReadIndex, FWriteIndex: PtrUInt;
+    function MaskIndex(const AValue: PtrUInt): PtrUInt;
+    function GetEmpty: boolean;
+    function GetFull: boolean;
+    function GetCapacity: PtrUInt;
+  protected
+    function ReadByte: byte;
+    procedure WriteByte(const AValue: byte);
+    function PeekByte(AIndex: PtrUInt = 0): byte;
+  public
+    constructor Create(const ASize: PtrUInt);
+    destructor Destroy; override;
+    function Read(out ABuffer; const ALength: PtrUInt): PtrUInt;
+    function Write(const ABuffer; const ALength: PtrUInt): PtrUInt;
+    function Peek(out ABuffer; ALength: PtrUInt): PtrUInt;
+    procedure AdvanceReadIdx(ACount: PtrUInt = 1);
+    property Empty: boolean read GetEmpty;
+    property Full: boolean read GetFull;
+    property Size: PtrUInt read FMemorySize;
+  end;
+
+implementation
+
+{ TSPSCRingBuffer }
+
+constructor TSPSCRingBuffer.Create(const ASize: PtrUInt);
+begin
+  inherited Create;
+  FReadIndex := 0;
+  FWriteIndex := 0;
+  FMemorySize := ASize;
+  Getmem(FMemoryData, FMemorySize);
+end;
+
+destructor TSPSCRingBuffer.Destroy;
+begin
+  Freemem(FMemoryData, FMemorySize);
+  inherited Destroy;
+end;
+
+function TSPSCRingBuffer.GetEmpty: boolean; //inline;
+begin
+  Result := FReadIndex = FWriteIndex;
+end;
+
+function TSPSCRingBuffer.GetFull: boolean; //inline;
+begin
+  Result := GetCapacity = FMemorySize - 1;
+end;
+
+function TSPSCRingBuffer.MaskIndex(const AValue: PtrUInt): PtrUInt; //inline;
+begin
+  Result := AValue and (FMemorySize - 1);
+end;
+
+// See : https://forum.lazarus.freepascal.org/index.php/topic,59796.msg446453.html#msg446453
+function TSPSCRingBuffer.GetCapacity: PtrUInt;  //inline;
+begin
+{$PUSH}
+{$Q-}
+{$R-}
+  Result := MaskIndex(FWriteIndex - FReadIndex);
+{$POP}
+end;
+
+function TSPSCRingBuffer.ReadByte: byte; inline;
+begin
+  Result := pbyte(FMemoryData)[MaskIndex(FReadIndex)];
+{$PUSH}
+{$Q-}
+  Inc(FReadIndex);
+{$POP}
+end;
+
+procedure TSPSCRingBuffer.WriteByte(const AValue: byte); inline;
+begin
+  pbyte(FMemoryData)[MaskIndex(FWriteIndex)] := AValue;
+{$PUSH}
+{$Q-}
+  Inc(FWriteIndex);
+{$POP}
+end;
+
+function TSPSCRingBuffer.PeekByte(AIndex: PtrUInt): byte;
+begin
+{$PUSH}
+{$Q-}
+  Result := pbyte(FMemoryData)[MaskIndex(FReadIndex + AIndex)];
+{$POP}
+end;
+
+function TSPSCRingBuffer.Read(out ABuffer; const ALength: PtrUInt): PtrUInt;
+begin
+  Result := 0;
+  while (not Empty) and (Result < ALength) do
+  begin
+    pbyte(@ABuffer + Result)^ := ReadByte;
+    Inc(Result);
+  end;
+end;
+
+function TSPSCRingBuffer.Write(const ABuffer; const ALength: PtrUInt): PtrUInt;
+begin
+  Result := 0;
+  while (not Full) and (Result < ALength) do
+  begin
+    WriteByte(pbyte(@ABuffer + Result)^);
+    Inc(Result);
+  end;
+end;
+
+function TSPSCRingBuffer.Peek(out ABuffer; ALength: PtrUInt): PtrUInt;
+begin
+  Result := 0;
+  while (Result < ALength) and (Result < GetCapacity) do
+  begin
+    pbyte(@ABuffer + Result)^ := PeekByte(Result);
+    Inc(Result);
+  end;
+end;
+
+procedure TSPSCRingBuffer.AdvanceReadIdx(ACount: PtrUInt);
+begin
+{$PUSH}
+{$Q-}
+  Inc(FReadIndex, ACount);
+{$POP}
+end;
+
+end.

+ 413 - 0
source/usbasp.pas

@@ -0,0 +1,413 @@
+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.

+ 187 - 0
source/usbasp_threads.pas

@@ -0,0 +1,187 @@
+unit USBasp_Threads;
+
+{
+
+  This file is part of
+    Nephelae USBasp HID UART.
+
+  USB HID Read / Write threads.
+
+  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;
+
+type
+
+  { TThreadHID_Base }
+
+  TThreadHID_Base = class(TThread)
+  protected
+    FThreadEvent: TEvent;
+    FThreadUnpluggedEvent: TEvent;
+    FBuffer: TSPSCRingBuffer;
+    FHIDDevice: PHidDevice;
+  public
+    constructor Create(const AHIDDevice: PHidDevice;
+      const ABuffer: TSPSCRingBuffer; const AThreadEvent: TEvent;
+      const AUnpluggedEvent: TEvent); reintroduce;
+  end;
+
+
+  { TThread_HID_Status_Rx }
+
+  TThread_HID_Status_Rx = class(TThreadHID_Base)
+  protected
+    procedure Execute; override;
+  end;
+
+  { TThread_HID_Status_Tx }
+
+  TThread_HID_Status_Tx = class(TThreadHID_Base)
+  protected
+    procedure Execute; override;
+  end;
+
+  { TThread_HID_UART_Rx }
+
+  TThread_HID_UART_Rx = class(TThreadHID_Base)
+    procedure Execute; override;
+  end;
+
+  { TThread_HID_UART_Tx }
+
+  TThread_HID_UART_Tx = class(TThreadHID_Base)
+  protected
+    procedure Execute; override;
+  end;
+
+implementation
+
+{ TThreadHID_Base }
+
+constructor TThreadHID_Base.Create(const AHIDDevice: PHidDevice;
+  const ABuffer: TSPSCRingBuffer; const AThreadEvent: TEvent;
+  const AUnpluggedEvent: TEvent);
+begin
+  inherited Create(False);
+  FThreadEvent := AThreadEvent;
+  FThreadUnpluggedEvent := AUnpluggedEvent;
+  FBuffer := ABuffer;
+  FHIDDevice := AHIDDevice;
+end;
+
+{ TThread_HID_Status_Rx }
+
+procedure TThread_HID_Status_Rx.Execute;
+var
+  USBAspHidPacket: array[0..7] of byte = (0, 0, 0, 0, 0, 0, 0, 0);
+  DataCount: SizeInt = 0;
+begin
+  repeat
+    DataCount := FHIDDevice^.Read(USBAspHidPacket, 8);
+    if DataCount > 0 then
+    begin
+      FBuffer.Write(USBAspHidPacket, DataCount);
+      FThreadEvent.SetEvent;
+    end;
+  until Terminated;
+end;
+
+{ TThread_HID_Status_Tx }
+
+procedure TThread_HID_Status_Tx.Execute;
+var
+  USBAspHidPacket: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0);
+  DataCount: byte = 0;
+begin
+  repeat
+    if FBuffer.Empty then
+      FThreadEvent.ResetEvent;
+    FThreadEvent.WaitFor(INFINITE);
+
+    FillChar(USBAspHidPacket, 8, 0);
+    DataCount := FBuffer.Peek(USBAspHidPacket[1], 8);
+    if DataCount > 0 then
+    begin
+      FHIDDevice^.Write(USBAspHidPacket, 8 + 1);
+      FBuffer.AdvanceReadIdx(DataCount);
+    end;
+  until Terminated;
+end;
+
+{ TThread_HID_UART_Rx }
+
+procedure TThread_HID_UART_Rx.Execute;
+var
+  USBAspHidPacket: array[0..7] of byte = (0, 0, 0, 0, 0, 0, 0, 0);
+  SerialDataCount, DataCount: byte;
+begin
+  repeat
+    DataCount := FHIDDevice^.Read(USBAspHidPacket, 8);
+    if (DataCount > 0) and (USBAspHidPacket[7] > 0) then
+    begin
+      if (USBAspHidPacket[7] > 7) then
+        SerialDataCount := 8
+      else
+        SerialDataCount := USBAspHidPacket[7];
+      if FBuffer.Write(USBAspHidPacket, SerialDataCount) <> SerialDataCount then
+        raise TExceptionClass.Create('Buffer OverRun');
+      FThreadEvent.SetEvent;
+    end;
+  until Terminated;
+end;
+
+{ TThread_HID_UART_Tx }
+
+procedure TThread_HID_UART_Tx.Execute;
+var
+  USBAspHidPacket: array[0..8] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0);
+  SerialCountOrDataByte, DataCount: byte;
+begin
+  repeat
+    if FBuffer.Empty then
+      FThreadEvent.ResetEvent;
+    FThreadEvent.WaitFor(INFINITE);
+
+    DataCount := FBuffer.Peek(USBAspHidPacket[1], 8);
+    SerialCountOrDataByte := DataCount;
+    if (SerialCountOrDataByte = 8) and (USBAspHidPacket[8] = 7) then
+      SerialCountOrDataByte := 7
+    else
+    if SerialCountOrDataByte < 8 then
+      USBAspHidPacket[8] := SerialCountOrDataByte;
+
+    if DataCount > 0 then
+    begin
+      SerialCountOrDataByte :=
+        FHIDDevice^.Write(USBAspHidPacket, 8 + 1);
+      FBuffer.AdvanceReadIdx(DataCount);
+    end;
+  until Terminated;
+end;
+
+end.