Page 3 of 3

Re: DLL wrapper for RTL-SDR

Posted: Mon Jun 16, 2025 3:34 pm
by infratec
According to this

Code: Select all

typedef void(*rtlsdr_read_async_cb_t)(unsigned char *buf, uint32_t len, void *ctx)
You need:

Code: Select all

ProcedureC ReadBuffer(*buffer, size.l, *ctx)
And ... it returns nothing.
So no ProcedureReturns

Re: DLL wrapper for RTL-SDR

Posted: Mon Jun 16, 2025 8:58 pm
by infratec
I modified my listing above.

Re: DLL wrapper for RTL-SDR

Posted: Tue Jun 17, 2025 7:24 am
by vertexview
New test done. Compilation and SDR dongle initialization OK.
No error in Log. Debug console returns "ReadBuffer ctx:0" in loop.

Re: DLL wrapper for RTL-SDR

Posted: Tue Jun 17, 2025 7:48 am
by infratec
According to this:
RTLSDR_API int rtlsdr_read_async(rtlsdr_dev_t *dev,
rtlsdr_read_async_cb_t cb,
void *ctx,
uint32_t buf_num,
uint32_t buf_len);
You pass 0 for *ctx, so it will be always 0 in the callback. It is a custom structure parameter.
But it is not needed.
You can use @ThreadParameter, and add the target buffer, but this is not needed.

In your case:

Code: Select all

ProcedureC ReadBuffer(*buffer, size.l, *ctx)
  
  Debug "ReadBuffer size: " + Str(size)
  
    
  ; CopyMemory in Delphi (Destination: Pointer; Source: Pointer; Length: DWORD)
  ; CopyMemory in Purebasic (*Source, *Destination, Size)
  CopyMemory(*buffer, @buf(0), size)
    
  SignalSemaphore(hSem)
  
EndProcedure
But remember that you can break the rtlsdr_read_async() procdeure only with rtlsdr_cancel_async()
So you need to run rtlsdr_read_async() in an own thread, that you can terminate it in your real program.

Re: DLL wrapper for RTL-SDR

Posted: Tue Jun 17, 2025 4:09 pm
by vertexview
Thanks infratec for your time and explanations.
It works perfectly. :D

Re: DLL wrapper for RTL-SDR

Posted: Sat Jun 21, 2025 3:22 pm
by vertexview
After using rtlsdr_read_async in console application, i tried to use a window form with the same code (read_async, thread/semaphore).

I simplfied the application code to have only an empty window, the SDR initialization, and the frame ADSB data processing with hex values display.

Finally, i can see ADSB Data, long and short frames, in the Debug console, but the window Form hangs and becomes not usable.
I can't shutdown the application with the Quit button (window not responsive). Incoming ADSB frames are still shown in the Debug console.
After that, I can only shut the program in the debugger with the kill button...

Here is the full code:

Code: Select all

EnableExplicit

XIncludeFile "librtlsdr.pbi"

#SDR_SRATE		= 2000000
#ADSB_FREQ		= 1090000000
#SDR_GAIN     = 372
#PREAMBLE_LEN = 16
#LONG_FRAME   = 112
#SHORT_FRAME  = 56
#MESSAGEGO    = 253
#OVERWRITE    = 254
#BAD_SAMPLE   = 255
#BUF_LENGTH   = 16*16384

Structure ByteArray_Structure
  a.a[0]
EndStructure

Structure ThreadParameter_Structure
  Thread.i
  Exit.i
EndStructure

Global i.i, r.i
Global dev_index.l
Global *dev.rtlsdr_dev_t
Global *buf
Global ThreadParameter.ThreadParameter_Structure
Global hSem.i
Global event
Global device_count.i
Global allowed_errors.i=5

Global Dim pythagore.a(128, 128)
Global Dim adsb_frame.a(13)
Global Dim buf.a(#BUF_LENGTH)
Global Dim thread_buf.a(#BUF_LENGTH)

Procedure pythagore_precompute()
  
  Protected.i x, y
  Protected.d scale
  
  scale = 1.408
  For x = 0 To 128
    For y = 0 To 128
      pythagore(x, y) = Round(scale * Sqr(x*x + y*y), #PB_Round_Nearest)
    Next y  
  Next x
  
EndProcedure

Procedure.a abs8(x.a)
  
  If x >= 128
    ProcedureReturn x - 128
  Else  
    ProcedureReturn 128 - x
  EndIf
  
EndProcedure

Procedure.i magnitude(*buf.ByteArray_Structure, len.i)
  
  Protected.i i
  
  ; takes IQ samples. Changes in place with the magnitude value (Char/uint8_t) 
  While i < len
    *buf\a[i / 2] = pythagore(abs8(*buf\a[i]), abs8(*buf\a[i + 1]))
    i + 2
  Wend
  
  ;returns new buffer length
  ProcedureReturn len / 2
  
EndProcedure

Procedure.i single_manchester(a.i, b.i, c.i, d.i)
  
  Protected.i bit, bit_p
  
  ; takes 4 consecutive samples, return 0 or 1, #BADSAMPLE on error
  If a > b
    bit_p = 1
  Else
    bit_p = 0
  EndIf
  
  If c > d
    bit = 1
  Else
    bit = 0
  EndIf

  ; Sanity check two bits
  If ((bit = 1) And (bit_p = 1) And (c > b) And (d < a))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 1) And (bit_p = 0) And (c > a) And (d < b))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 0) And (bit_p = 1) And (c < a) And (d > b))
    ProcedureReturn 0
  EndIf
  
  If ((bit = 0) And (bit_p = 0) And (c < b) And (d > a))
    ProcedureReturn 0
  EndIf
  
  ProcedureReturn #BAD_SAMPLE
  
EndProcedure

Procedure.i preamble(*buf.ByteArray_Structure, len.i, i.i)
  
  Protected.i i2
  Protected.a low, high
  
  low.a = 0
  high.a = 255
  ; Sequence Check. Returns 0/1 for preamble at index i
  For i2 = 0 To #PREAMBLE_LEN - 1
    Select i2
      Case 0,2,7,9
        high = *buf\a[i + i2]
      Default
        low = *buf\a[i + i2]
    EndSelect

    If high <= low
      ; No preamble sequence identified
      ProcedureReturn 0
    EndIf
    
  Next i2
  
  ; Preamble sequence found
  ProcedureReturn 1
  
EndProcedure

Procedure manchester(*buf.ByteArray_Structure, len.i)
  
  Protected a.a, b.a, bit.a
  Protected i.i, i2.i, errors.i
  
  ; Overwrites magnitude buffer with valid bits (#BADSAMPLE on errors)
  While (i < len)
    ; Find preamble sequence
    While i < len - #PREAMBLE_LEN
      If preamble(*buf, len, i) = 0
        i + 1
        Continue
      EndIf
      
      a = *buf\a[i]
      b = *buf\a[i + 1]
      For i2 = 0 To #PREAMBLE_LEN - 1
        *buf\a[i + i2] = #MESSAGEGO
      Next i2
      
      i = i + #PREAMBLE_LEN
      Break
      
      i + 1
    Wend
    
    i2 = i
    errors = 0
    ; Mark bits until encoding breaks
    While i < len
      bit = single_manchester(a, b, *buf\a[i], *buf\a[i + 1])
      a = *buf\a[i]
      b = *buf\a[i + 1]
      If bit = #BAD_SAMPLE
        errors + 1
        If errors > allowed_errors
          *buf\a[i2] = #BAD_SAMPLE
          Break
        Else
          If a > b
            bit = 1
          Else
            bit = 0
          EndIf
          a = 0
          b = 255
        EndIf
      EndIf
      
      *buf\a[i] = #OVERWRITE
      *buf\a[i + 1] = #OVERWRITE
      *buf\a[i2] = bit
      i + 2
      i2 + 1
    Wend
  Wend
  
EndProcedure

Procedure display(*frame.ByteArray_Structure, len.i)
  
  Protected i.i, df.i
  Protected timeStamp.s, frameOut.s
  
  df = (*frame\a[0] >> 3) & $1F

  If Not ((df = 11) Or (df = 17) Or (df = 18) Or (df = 19))
    ProcedureReturn
  EndIf  
  
  timeStamp=FormatDate(" %yy/%mm/%dd %hh:%ii:%ss", Date())+Chr(10)
  For i = 0 To ((len + 7) / 8) - 1
    frameOut=frameOut+RSet(Hex(adsb_frame(i),#PB_Ascii),2,"0")
  Next i

  Debug("ADSB Frame: "+timeStamp+"   "+frameOut)
  
EndProcedure

Procedure outmessages(*buf.ByteArray_Structure, len.i)
  
  Protected.i i, data_i, index, shift, frame_len
  Protected.a val_shift
  
  While i < len
    If *buf\a[i] > 1
      i + 1
      Continue
    EndIf
    frame_len = #LONG_FRAME
    data_i = 0
    
    For index = 0 To 13
      adsb_frame(index) = 0
    Next index

    While (i < len) And (*buf\a[i] <= 1) And (data_i < frame_len)
      If *buf\a[i] = 1
        index = data_i / 8
        shift = 7 - (data_i % 8)
        adsb_frame(index) = adsb_frame(index) | 1 << shift
      EndIf
      
      If data_i = 7
        If adsb_frame(0) = 0
          Break
        EndIf  
        
        If adsb_frame(0) & $80 <> 0
          frame_len = #LONG_FRAME
        Else
          frame_len = #SHORT_FRAME
        EndIf  
      EndIf

      i + 1
      data_i + 1
    Wend
    
    If data_i < frame_len - 1
      i + 1
      Continue
    EndIf
    display(@adsb_frame(0), frame_len)
    
    i + 1
  Wend
  
EndProcedure

Procedure initSDR()

  Protected.i i, r
  Protected.s notification
  
  If UseLibrtlsdr()
  
    ; RTL-SDR System dll must be in application directory
    device_count = rtlsdr_get_device_count()
    
    If Not device_count
      Debug("No SDR device found. Application shutdown")
      Delay(3000)
      End 1
    EndIf
    
    Debug("Found " + Str(device_count) + " device(s)")
    
    For i = 0 To device_count - 1
      Debug("SDR Scan. "+Str(i) + ": " + PeekS(rtlsdr_get_device_name(i), -1, #PB_UTF8))
    Next i
    
    Debug("SDR ID used " + Str(dev_index) + ": " + PeekS(rtlsdr_get_device_name(dev_index), -1, #PB_UTF8))
    
    r = rtlsdr_open(@*dev, dev_index)
    If r < 0
      Debug("Failed to open SDR device #" + Str(dev_index))
      Delay(3000)
      End 1
    EndIf
    
    rtlsdr_set_center_freq(*dev, #ADSB_FREQ)
    rtlsdr_set_sample_rate(*dev, #SDR_SRATE)
    rtlsdr_set_tuner_gain(*dev, #SDR_GAIN) 
    rtlsdr_set_agc_mode(*dev, 0)
   
    r = rtlsdr_reset_buffer(*dev)
    If r < 0
      Debug("Failed to reset buffer")
    EndIf
    
  EndIf
  
EndProcedure

ProcedureC ReadBuffer(*buffer, size.l, *ctx)
  
  Debug "ReadBuffer size: " + Str(size)
  
  CopyMemory(*buffer, @buf(0), size)
  
  If IsThread(ThreadParameter\Thread)
    SignalSemaphore(hSem)
  EndIf  
  
EndProcedure

Procedure ThreadProc(*Parameter.ThreadParameter_Structure)
  
  Protected len.i
  
  Repeat
    
    WaitSemaphore(hSem)
    
    If Not *Parameter\Exit
      
      Debug("Threadproc process loop")
      
	   CopyMemory(@buf(0), @thread_buf(0), #BUF_LENGTH)
      ; ADSB data processing
      len = magnitude(@thread_buf(0), #BUF_LENGTH)
      manchester(@thread_buf(0), len)
      outmessages(@thread_buf(0), len)

    EndIf
    
  Until *Parameter\Exit
  
  ; Cancel Async reading
  rtlsdr_cancel_async(*dev)
  
EndProcedure

; Main code with thread creation 

OpenWindow(0, 0, 0, 800, 600, "ADSB SDR Async Reading", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

; SDR environment initialization
initSDR()
pythagore_precompute()

; Thread and semaphore creation
hSem = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@ThreadProc(), @ThreadParameter)

Debug("ThreadProc() running. Thread reference:"+ThreadParameter\Thread)

r = rtlsdr_read_async(*dev, @ReadBuffer(), 0, 32, #BUF_LENGTH)

; Window loop event management
Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        ; Gadget event
      EndSelect
  EndSelect    
      
Until Event = #PB_Event_CloseWindow

; Cleaning ressources after shutdown
ThreadParameter\Exit = #True

If IsThread(ThreadParameter\Thread)
  SignalSemaphore(hSem)
EndIf

If WaitThread(ThreadParameter\Thread, 1000) = 0
  KillThread(ThreadParameter\Thread)
  Debug ("Thread Timeout")
EndIf

If hSem
  FreeSemaphore(hSem)
EndIf
Debug ("Ressources cleaned")

rtlsdr_close(*dev)
Debug ("rtlsdr closed")

Delay(1000)
CloseWindow(0)
End
Here is a Debug console snapshot:

Code: Select all

Found 1 device(s)
SDR Scan. 0: Generic RTL2832U OEM
SDR ID used 0: Generic RTL2832U OEM
ThreadProc() running. Thread reference:1
ReadBuffer size: 262144
Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:46
   8D34164658A581DD9FBEBAA20D83
ADSB Frame:  25/06/21 15:35:46
   8D39856899107538D05441A41675
ReadBuffer size: 262144
Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:46
   8D3985685893A55F03CD08C25EEA
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:47
   8D3444D0EA466858013C085198A5
Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:47
   8D398568230464B2D88180F3A398
ReadBuffer size: 262144
Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:47
   8D39856899107538D05441A41675
ADSB Frame:  25/06/21 15:35:47
   8D34164658A5855E8BC060CBED29
ADSB Frame:  25/06/21 15:35:47
   5D3416460CDC62
ReadBuffer size: 262144
Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ADSB Frame:  25/06/21 15:35:48
   8D398568F8230002004AB8A60291
ReadBuffer size: 262144

Re: DLL wrapper for RTL-SDR

Posted: Sat Jun 21, 2025 4:22 pm
by infratec
You didn' tread or understood my last sentence:
But remember that you can break the rtlsdr_read_async() procdeure only with rtlsdr_cancel_async()
So you need to run rtlsdr_read_async() in an own thread, that you can terminate it in your real program.
rtlsdr_read_async() is a blocking function.
Everything after this call is not executed. (place a Debug statement for a test after this line)
You need to run it in an own thread.
And if you close your programm you have to call rtlsdr_cancel_async() to exit this thread correctly.
And wait if the thread is really ended.

Re: DLL wrapper for RTL-SDR

Posted: Sat Jun 21, 2025 5:02 pm
by infratec
A quick hack:

Code: Select all

EnableExplicit

XIncludeFile "librtlsdr.pbi"

#SDR_SRATE		= 2000000
#ADSB_FREQ		= 1090000000
#SDR_GAIN     = 372
#PREAMBLE_LEN = 16
#LONG_FRAME   = 112
#SHORT_FRAME  = 56
#MESSAGEGO    = 253
#OVERWRITE    = 254
#BAD_SAMPLE   = 255
#BUF_LENGTH   = 16*16384

#allowed_errors = 5


Structure ByteArray_Structure
  a.a[0]
EndStructure

Structure ThreadParameter_Structure
  Thread.i
  Exit.i
EndStructure

Global *dev.rtlsdr_dev_t
Global *buf
Global ThreadParameter.ThreadParameter_Structure
Global hSem.i

Global Dim pythagore.a(128, 128)
Global Dim adsb_frame.a(13)
Global Dim buf.a(#BUF_LENGTH)
Global Dim thread_buf.a(#BUF_LENGTH)


Procedure pythagore_precompute()
  
  Protected.i x, y
  Protected.d scale
  
  scale = 1.408
  For x = 0 To 128
    For y = 0 To 128
      pythagore(x, y) = Round(scale * Sqr(x*x + y*y), #PB_Round_Nearest)
    Next y  
  Next x
  
EndProcedure

Procedure.a abs8(x.a)
  
  If x >= 128
    ProcedureReturn x - 128
  Else  
    ProcedureReturn 128 - x
  EndIf
  
EndProcedure

Procedure.i magnitude(*buf.ByteArray_Structure, len.i)
  
  Protected.i i
  
  ; takes IQ samples. Changes in place with the magnitude value (Char/uint8_t) 
  While i < len
    *buf\a[i / 2] = pythagore(abs8(*buf\a[i]), abs8(*buf\a[i + 1]))
    i + 2
  Wend
  
  ;returns new buffer length
  ProcedureReturn len / 2
  
EndProcedure

Procedure.i single_manchester(a.i, b.i, c.i, d.i)
  
  Protected.i bit, bit_p
  
  ; takes 4 consecutive samples, return 0 or 1, #BADSAMPLE on error
  If a > b
    bit_p = 1
  Else
    bit_p = 0
  EndIf
  
  If c > d
    bit = 1
  Else
    bit = 0
  EndIf

  ; Sanity check two bits
  If ((bit = 1) And (bit_p = 1) And (c > b) And (d < a))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 1) And (bit_p = 0) And (c > a) And (d < b))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 0) And (bit_p = 1) And (c < a) And (d > b))
    ProcedureReturn 0
  EndIf
  
  If ((bit = 0) And (bit_p = 0) And (c < b) And (d > a))
    ProcedureReturn 0
  EndIf
  
  ProcedureReturn #BAD_SAMPLE
  
EndProcedure

Procedure.i preamble(*buf.ByteArray_Structure, len.i, i.i)
  
  Protected.i i2
  Protected.a low, high
  
  low.a = 0
  high.a = 255
  ; Sequence Check. Returns 0/1 for preamble at index i
  For i2 = 0 To #PREAMBLE_LEN - 1
    Select i2
      Case 0,2,7,9
        high = *buf\a[i + i2]
      Default
        low = *buf\a[i + i2]
    EndSelect

    If high <= low
      ; No preamble sequence identified
      ProcedureReturn 0
    EndIf
    
  Next i2
  
  ; Preamble sequence found
  ProcedureReturn 1
  
EndProcedure

Procedure manchester(*buf.ByteArray_Structure, len.i)
  
  Protected a.a, b.a, bit.a
  Protected i.i, i2.i, errors.i
  
  ; Overwrites magnitude buffer with valid bits (#BADSAMPLE on errors)
  While (i < len)
    ; Find preamble sequence
    While i < len - #PREAMBLE_LEN
      If preamble(*buf, len, i) = 0
        i + 1
        Continue
      EndIf
      
      a = *buf\a[i]
      b = *buf\a[i + 1]
      For i2 = 0 To #PREAMBLE_LEN - 1
        *buf\a[i + i2] = #MESSAGEGO
      Next i2
      
      i = i + #PREAMBLE_LEN
      Break
      
      i + 1
    Wend
    
    i2 = i
    errors = 0
    ; Mark bits until encoding breaks
    While i < len
      bit = single_manchester(a, b, *buf\a[i], *buf\a[i + 1])
      a = *buf\a[i]
      b = *buf\a[i + 1]
      If bit = #BAD_SAMPLE
        errors + 1
        If errors > #allowed_errors
          *buf\a[i2] = #BAD_SAMPLE
          Break
        Else
          If a > b
            bit = 1
          Else
            bit = 0
          EndIf
          a = 0
          b = 255
        EndIf
      EndIf
      
      *buf\a[i] = #OVERWRITE
      *buf\a[i + 1] = #OVERWRITE
      *buf\a[i2] = bit
      i + 2
      i2 + 1
    Wend
  Wend
  
EndProcedure

Procedure display(*frame.ByteArray_Structure, len.i)
  
  Protected i.i, df.i
  Protected timeStamp.s, frameOut.s
  
  df = (*frame\a[0] >> 3) & $1F

  If Not ((df = 11) Or (df = 17) Or (df = 18) Or (df = 19))
    ProcedureReturn
  EndIf  
  
  timeStamp=FormatDate(" %yy/%mm/%dd %hh:%ii:%ss", Date())+Chr(10)
  For i = 0 To ((len + 7) / 8) - 1
    frameOut=frameOut+RSet(Hex(adsb_frame(i),#PB_Ascii),2,"0")
  Next i

  Debug("ADSB Frame: "+timeStamp+"   "+frameOut)
  
EndProcedure

Procedure outmessages(*buf.ByteArray_Structure, len.i)
  
  Protected.i i, data_i, index, shift, frame_len
  Protected.a val_shift
  
  While i < len
    If *buf\a[i] > 1
      i + 1
      Continue
    EndIf
    frame_len = #LONG_FRAME
    data_i = 0
    
    For index = 0 To 13
      adsb_frame(index) = 0
    Next index

    While (i < len) And (*buf\a[i] <= 1) And (data_i < frame_len)
      If *buf\a[i] = 1
        index = data_i / 8
        shift = 7 - (data_i % 8)
        adsb_frame(index) = adsb_frame(index) | 1 << shift
      EndIf
      
      If data_i = 7
        If adsb_frame(0) = 0
          Break
        EndIf  
        
        If adsb_frame(0) & $80 <> 0
          frame_len = #LONG_FRAME
        Else
          frame_len = #SHORT_FRAME
        EndIf  
      EndIf

      i + 1
      data_i + 1
    Wend
    
    If data_i < frame_len - 1
      i + 1
      Continue
    EndIf
    display(@adsb_frame(0), frame_len)
    
    i + 1
  Wend
  
EndProcedure

Procedure initSDR()

  Protected.i i, r, device_count
  Protected.l dev_index
  Protected.s notification
  
  If UseLibrtlsdr()
  
    ; RTL-SDR System dll must be in application directory
    device_count = rtlsdr_get_device_count()
    
    If device_count = 0
      Debug("No SDR device found. Application shutdown")
      End 1
    EndIf
    
    Debug("Found " + Str(device_count) + " device(s)")
    
    For i = 0 To device_count - 1
      Debug("SDR Scan. "+Str(i) + ": " + PeekS(rtlsdr_get_device_name(i), -1, #PB_UTF8))
    Next i
    
    Debug("SDR ID used " + Str(dev_index) + ": " + PeekS(rtlsdr_get_device_name(dev_index), -1, #PB_UTF8))
    
    r = rtlsdr_open(@*dev, dev_index)
    If r < 0
      Debug("Failed to open SDR device #" + Str(dev_index))
      End 1
    EndIf
    
    rtlsdr_set_center_freq(*dev, #ADSB_FREQ)
    rtlsdr_set_sample_rate(*dev, #SDR_SRATE)
    rtlsdr_set_tuner_gain(*dev, #SDR_GAIN) 
    rtlsdr_set_agc_mode(*dev, 0)
   
    r = rtlsdr_reset_buffer(*dev)
    If r < 0
      Debug("Failed to reset buffer")
    EndIf
    
  EndIf
  
EndProcedure

ProcedureC ReadBuffer(*buffer, size.l, *ctx)
  
  Debug "ReadBuffer size: " + Str(size)
  
  CopyMemory(*buffer, @buf(0), size)
  
  If IsThread(ThreadParameter\Thread)
    SignalSemaphore(hSem)
  EndIf  
  
EndProcedure


Procedure ThreadProc(*Parameter.ThreadParameter_Structure)
  
  Protected len.i
  
  Repeat
    
    WaitSemaphore(hSem)
    
    If Not *Parameter\Exit
      
      Debug("Threadproc process loop")
      
	   CopyMemory(@buf(0), @thread_buf(0), #BUF_LENGTH)
      ; ADSB data processing
      len = magnitude(@thread_buf(0), #BUF_LENGTH)
      manchester(@thread_buf(0), len)
      outmessages(@thread_buf(0), len)

    EndIf
    
  Until *Parameter\Exit
  
  
EndProcedure


Procedure ReadAsyncThread(*Dummy)
  
  Protected r.i
  
  r = rtlsdr_read_async(*dev, @ReadBuffer(), 0, 32, #BUF_LENGTH)
  
EndProcedure



Define.i event, ReadThread

; Main code with thread creation 

OpenWindow(0, 0, 0, 800, 600, "ADSB SDR Async Reading", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

; SDR environment initialization
initSDR()
pythagore_precompute()

; Thread and semaphore creation
hSem = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@ThreadProc(), @ThreadParameter)

Debug("ThreadProc() running. Thread reference:"+ThreadParameter\Thread)


ReadThread = CreateThread(@ReadAsyncThread(), #Null)

; Window loop event management
Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        ; Gadget event
      EndSelect
  EndSelect    
      
Until Event = #PB_Event_CloseWindow

; Cancel Async reading
rtlsdr_cancel_async(*dev)
If WaitThread(ReadThread, 3000) = 0
  KillThread(ReadThread)
EndIf

; Cleaning ressources after shutdown
ThreadParameter\Exit = #True

If IsThread(ThreadParameter\Thread)
  SignalSemaphore(hSem)
EndIf

If WaitThread(ThreadParameter\Thread, 1000) = 0
  KillThread(ThreadParameter\Thread)
  Debug ("Thread Timeout")
EndIf

If hSem
  FreeSemaphore(hSem)
EndIf

Debug ("Ressources cleaned")

rtlsdr_close(*dev)
Debug ("rtlsdr closed")

Re: DLL wrapper for RTL-SDR

Posted: Sat Jun 21, 2025 6:07 pm
by vertexview
Thank you infratec.
No more Window hang, and now can softly close the application.

Code: Select all

Threadproc process loop
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
ReadBuffer size: 262144
Threadproc process loop
ReadBuffer size: 262144
ADSB Frame:  25/06/21 18:56:18
   8F44CE6CF82300060049B8C0E927
Ressources cleaned
rtlsdr closed

Re: DLL wrapper for RTL-SDR

Posted: Sun Jun 22, 2025 5:57 pm
by vertexview
Here is the window version with the last code evolution:
. Stable asynchronous rtl-sdr data reading (threads, semaphore)
. ADSB results are shown in a ListIcon gadget
. Long frame signification is added in the ListIcon Comment column.
. RTL-SDR status is in Application StatusBar

Code: Select all

EnableExplicit

XIncludeFile "librtlsdr.pbi"

#SDR_SRATE		  = 2000000
#ADSB_FREQ		  = 1090000000
#SDR_GAIN       = 372
#PREAMBLE_LEN   = 16
#LONG_FRAME     = 112
#SHORT_FRAME    = 56
#MESSAGEGO      = 253
#OVERWRITE      = 254
#BAD_SAMPLE     = 255
#BUF_LENGTH     = 16*16384
#ALLOWED_ERRORS = 5

Enumeration FormWindow
  #Window_Main
EndEnumeration

Enumeration FormGadget
  #ListIcon_Frames
EndEnumeration

Structure ByteArray_Structure
  a.a[0]
EndStructure

Structure ThreadParameter_Structure
  Thread.i
  Exit.i
EndStructure

Global *dev.rtlsdr_dev_t
Global *buf
Global ThreadParameter.ThreadParameter_Structure
Global hSem.i

Global Dim pythagore.a(128, 128)
Global Dim adsb_frame.a(13)
Global Dim buf.a(#BUF_LENGTH)
Global Dim thread_buf.a(#BUF_LENGTH)

Global font.i
Global lineCounter.i
Global lineMax.i = 300
Global framesCounter.i
Global ShowLongOnly.b = #False

Procedure pythagore_precompute()
  
  Protected.i x, y
  Protected.d scale
  
  scale = 1.408
  For x = 0 To 128
    For y = 0 To 128
      pythagore(x, y) = Round(scale * Sqr(x*x + y*y), #PB_Round_Nearest)
    Next y  
  Next x
  
EndProcedure

Procedure.a abs8(x.a)
  
  If x >= 128
    ProcedureReturn x - 128
  Else  
    ProcedureReturn 128 - x
  EndIf
  
EndProcedure

Procedure.i magnitude(*buf.ByteArray_Structure, len.i)
  
  Protected.i i
  
  ; takes IQ samples. Changes in place with the magnitude value (Char/uint8_t) 
  While i < len
    *buf\a[i / 2] = pythagore(abs8(*buf\a[i]), abs8(*buf\a[i + 1]))
    i + 2
  Wend
  
  ;returns new buffer length
  ProcedureReturn len / 2
  
EndProcedure

Procedure.i single_manchester(a.i, b.i, c.i, d.i)
  
  Protected.i bit, bit_p
  
  ; takes 4 consecutive samples, return 0 or 1, #BADSAMPLE on error
  If a > b
    bit_p = 1
  Else
    bit_p = 0
  EndIf
  
  If c > d
    bit = 1
  Else
    bit = 0
  EndIf

  ; Sanity check two bits
  If ((bit = 1) And (bit_p = 1) And (c > b) And (d < a))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 1) And (bit_p = 0) And (c > a) And (d < b))
    ProcedureReturn 1
  EndIf
  
  If ((bit = 0) And (bit_p = 1) And (c < a) And (d > b))
    ProcedureReturn 0
  EndIf
  
  If ((bit = 0) And (bit_p = 0) And (c < b) And (d > a))
    ProcedureReturn 0
  EndIf
  
  ProcedureReturn #BAD_SAMPLE
  
EndProcedure

Procedure.i preamble(*buf.ByteArray_Structure, len.i, i.i)
  
  Protected.i i2
  Protected.a low, high
  
  low.a = 0
  high.a = 255
  ; Sequence Check. Returns 0/1 for preamble at index i
  For i2 = 0 To #PREAMBLE_LEN - 1
    Select i2
      Case 0,2,7,9
        high = *buf\a[i + i2]
      Default
        low = *buf\a[i + i2]
    EndSelect

    If high <= low
      ; No preamble sequence identified
      ProcedureReturn 0
    EndIf
    
  Next i2
  
  ; Preamble sequence found
  ProcedureReturn 1
  
EndProcedure

Procedure manchester(*buf.ByteArray_Structure, len.i)
  
  Protected a.a, b.a, bit.a
  Protected i.i, i2.i, errors.i
  
  ; Overwrites magnitude buffer with valid bits (#BADSAMPLE on errors)
  While (i < len)
    ; Find preamble sequence
    While i < len - #PREAMBLE_LEN
      If preamble(*buf, len, i) = 0
        i + 1
        Continue
      EndIf
      
      a = *buf\a[i]
      b = *buf\a[i + 1]
      For i2 = 0 To #PREAMBLE_LEN - 1
        *buf\a[i + i2] = #MESSAGEGO
      Next i2
      
      i = i + #PREAMBLE_LEN
      Break
      
      i + 1
    Wend
    
    i2 = i
    errors = 0
    ; Mark bits until encoding breaks
    While i < len
      bit = single_manchester(a, b, *buf\a[i], *buf\a[i + 1])
      a = *buf\a[i]
      b = *buf\a[i + 1]
      If bit = #BAD_SAMPLE
        errors + 1
        If errors > #ALLOWED_ERRORS
          *buf\a[i2] = #BAD_SAMPLE
          Break
        Else
          If a > b
            bit = 1
          Else
            bit = 0
          EndIf
          a = 0
          b = 255
        EndIf
      EndIf
      
      *buf\a[i] = #OVERWRITE
      *buf\a[i + 1] = #OVERWRITE
      *buf\a[i2] = bit
      i + 2
      i2 + 1
    Wend
  Wend
  
EndProcedure

Procedure display(*frame.ByteArray_Structure, len.i)
  
  Protected.i i, df, tc
  Protected.s timeStamp, hexFrame, DFs, CA, AA, ICAO, PI, TCs, ST, Comment
  Protected.s gadgetLine
  
  ; Downlink Format
  df = (*frame\a[0] >> 3) & $1F

  ; All-call Reply (DF11) or Extended Squitter frames (DF17, DF18, DF19)
  If Not ((df = 11) Or (df = 17) Or (df = 18) Or (df = 19))
    ProcedureReturn
  EndIf  
  
  timeStamp=FormatDate(" %yy/%mm/%dd %hh:%ii:%ss", Date())
  For i = 0 To ((len + 7) / 8) - 1
    hexFrame=hexFrame+RSet(Hex(adsb_frame(i),#PB_Ascii),2,"0")
  Next i
  DFs=Str(df)
  ; Mode S Transpondeur. CApability
  CA=Str(*frame\a[0] & $07)
  gadgetLine=timeStamp+Chr(10)+hexFrame+Chr(10)+DFs+Chr(10)+CA+Chr(10)
  
  If df=11
    ; Short frame. All-call Reply
    ; Anounced Adress
    AA=RSet(Hex(*frame\a[1] << 16 | (*frame\a[2] << 8) | *frame\a[3]), 6, "0")
    ; Parity/Interogator ID for short frame
    PI=RSet(Hex(*frame\a[4] << 16 | (*frame\a[5] << 8) | *frame\a[5]), 6, "0")
    
    gadgetLine=gadgetLine+AA+Chr(10)+PI
    If ShowLongOnly= #False
      AddGadgetItem(#ListIcon_Frames, -1, gadgetLine)
      lineCounter+1
    EndIf  
  Else
    ; Extended Squitter long frame (DF17, DF18, DF19)
    ; Aircraft Adress
    ICAO=RSet(Hex(*frame\a[1] << 16 | (*frame\a[2] << 8) | *frame\a[3]), 6, "0")
    ; Parity/Interogator ID for long frame
    PI=RSet(Hex(*frame\a[11] << 16 | (*frame\a[12] << 8) | *frame\a[13]), 6, "0")
    ; Type Code. Data frame content
    tc=(*frame\a[4] >> 3) & $1F
    TCs=Str(tc)
    ; SubType code
    ST=Str(*frame\a[4] & $07)
    ; Comment - From Type Code value
    Select tc
      Case 1, 2, 3, 4
        Comment="Aircraft identification"
      Case 5, 6, 7, 8
        Comment="Surface position"
      Case 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
        Comment="Airborne position - Barometric altitude"
      Case 19
        Comment="Airborne velocities"
      Case 20, 21, 22
        Comment="Airborne position - GNSS altitude"
      Case 23, 24, 25, 26, 27
        Comment="Reserved"
      Case 28
        Comment="Aircraft status"
      Case 29
        Comment="Target state and status"
      Case 31
        Comment="Aircraft operation status"
    EndSelect
    
    gadgetLine=gadgetLine+ICAO+Chr(10)+PI+Chr(10)+TCs+Chr(10)+ST+Chr(10)+Comment
    AddGadgetItem(#ListIcon_Frames, -1, gadgetLine)
    lineCounter+1
  EndIf  

  framesCounter+1
  StatusBarText(0, 2, " Frames Counter (Short + Long):  "+Str(framesCounter+1))
  
  ; Windows API call. Automatic ListIcon gadget scrolling  
  SendMessage_(GadgetID(#ListIcon_Frames), #LVM_ENSUREVISIBLE, lineCounter-1, #True)
  If lineCounter=lineMax
    ClearGadgetItems(#ListIcon_Frames)
    lineCounter=0
  EndIf
  
EndProcedure

Procedure outmessages(*buf.ByteArray_Structure, len.i)
  
  Protected.i i, data_i, index, shift, frame_len
  Protected.a val_shift
  
  While i < len
    If *buf\a[i] > 1
      i + 1
      Continue
    EndIf
    frame_len = #LONG_FRAME
    data_i = 0
    
    For index = 0 To 13
      adsb_frame(index) = 0
    Next index

    While (i < len) And (*buf\a[i] <= 1) And (data_i < frame_len)
      If *buf\a[i] = 1
        index = data_i / 8
        shift = 7 - (data_i % 8)
        adsb_frame(index) = adsb_frame(index) | 1 << shift
      EndIf
      
      If data_i = 7
        If adsb_frame(0) = 0
          Break
        EndIf  
        
        If adsb_frame(0) & $80 <> 0
          frame_len = #LONG_FRAME
        Else
          frame_len = #SHORT_FRAME
        EndIf  
      EndIf

      i + 1
      data_i + 1
    Wend
    
    If data_i < frame_len - 1
      i + 1
      Continue
    EndIf
    display(@adsb_frame(0), frame_len)
    
    i + 1
  Wend
  
EndProcedure

Procedure initSDR()

  Protected.i i, r, device_count
  Protected.l dev_index
  Protected.s notification
  
  If UseLibrtlsdr()
  
    ; RTL-SDR System dll must be in application directory
    device_count = rtlsdr_get_device_count()
    
    If device_count = 0
      notification="No SDR device found. Application shutdown"
      StatusBarText(0, 3, notification)
      MessageRequester("PlaneTracker", notification, #PB_MessageRequester_Warning)
      Delay(3000)
      End 1
    EndIf
    
    notification="Found " + Str(device_count) + " device(s)"
    StatusBarText(0, 3, notification)
    
    For i = 0 To device_count - 1
      Debug("SDR Scan. "+Str(i) + ": " + PeekS(rtlsdr_get_device_name(i), -1, #PB_UTF8))
    Next i
    
    notification="SDR ID used " + Str(dev_index) + ": " + PeekS(rtlsdr_get_device_name(dev_index), -1, #PB_UTF8)
    StatusBarText(0, 0, " "+notification)
    
    r = rtlsdr_open(@*dev, dev_index)
    If r < 0
      notification="Failed to open SDR device #" + Str(dev_index)
      StatusBarText(0, 3, notification)
      Delay(3000)
      End 1
    EndIf
    
    rtlsdr_set_center_freq(*dev, #ADSB_FREQ)
    rtlsdr_set_sample_rate(*dev, #SDR_SRATE)
    rtlsdr_set_tuner_gain(*dev, #SDR_GAIN) 
    rtlsdr_set_agc_mode(*dev, 0)
   
    r = rtlsdr_reset_buffer(*dev)
    If r < 0
      notification="Failed to reset buffer"
      StatusBarText(0, 3, notification)
      Delay(3000)
    EndIf
    
  EndIf
  
EndProcedure

ProcedureC ReadBuffer(*buffer, size.l, *ctx)
  
  CopyMemory(*buffer, @buf(0), size)
  
  If IsThread(ThreadParameter\Thread)
    SignalSemaphore(hSem)
  EndIf  
  
EndProcedure


Procedure ThreadProc(*Parameter.ThreadParameter_Structure)
  
  Protected len.i
  
  Repeat
    
    WaitSemaphore(hSem)
    
    If Not *Parameter\Exit
      
      CopyMemory(@buf(0), @thread_buf(0), #BUF_LENGTH)
      ; ADSB data processing
      len = magnitude(@thread_buf(0), #BUF_LENGTH)
      manchester(@thread_buf(0), len)
      outmessages(@thread_buf(0), len)

    EndIf
    
  Until *Parameter\Exit
  
EndProcedure

Procedure ReadAsyncThread(*Dummy)
  
  Protected r.i
  
  r = rtlsdr_read_async(*dev, @ReadBuffer(), 0, 32, #BUF_LENGTH)
  
EndProcedure

Define.i event, ReadThread

; Main code with thread/semaphore creation 

OpenWindow(#Window_Main, 0, 0, 1024, 600, "ADSB SDR Async Reading", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

font=LoadFont(#PB_Any, "Consolas", 10)

CreateStatusBar(0, WindowID(#Window_Main))
AddStatusBarField(240)
StatusBarText(0, 0, "SDR Status")
AddStatusBarField(240)
StatusBarText(0, 1, "General information")
AddStatusBarField(240)
StatusBarText(0, 2, "Counter information")
AddStatusBarField(300)
StatusBarText(0, 3, "Status Detail")

ListIconGadget(#ListIcon_Frames, 2, 6, 1020, 566, "Time Stamp", 150)
AddGadgetColumn(#ListIcon_Frames, 1, "ADSB Frame", 220)
AddGadgetColumn(#ListIcon_Frames, 2, "DF", 30)
AddGadgetColumn(#ListIcon_Frames, 3, "CA", 30)
AddGadgetColumn(#ListIcon_Frames, 4, "AA-ICAO", 70)
AddGadgetColumn(#ListIcon_Frames, 5, "PI", 60)
AddGadgetColumn(#ListIcon_Frames, 6, "TC", 30)
AddGadgetColumn(#ListIcon_Frames, 7, "ST", 30)
AddGadgetColumn(#ListIcon_Frames, 8, "Comment", 300)
SetGadgetColor(#ListIcon_Frames, #PB_Gadget_FrontColor,RGB(0,64,128))
SetGadgetColor(#ListIcon_Frames, #PB_Gadget_BackColor,RGB(248,248,248))
SetGadgetFont(#ListIcon_Frames, FontID(font))

; SDR environment initialization
initSDR()
pythagore_precompute()

; Thread and semaphore creation
hSem = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@ThreadProc(), @ThreadParameter)
ReadThread = CreateThread(@ReadAsyncThread(), #Null)

; Window loop event management
Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        ; Gadget event
      EndSelect
  EndSelect    
      
Until Event = #PB_Event_CloseWindow

; Cancel Async reading
rtlsdr_cancel_async(*dev)
If WaitThread(ReadThread, 3000) = 0
  KillThread(ReadThread)
EndIf

; Cleaning ressources after shutdown
ThreadParameter\Exit = #True

If IsThread(ThreadParameter\Thread)
  SignalSemaphore(hSem)
EndIf

If WaitThread(ThreadParameter\Thread, 1000) = 0
  KillThread(ThreadParameter\Thread)
  Debug ("ThreadProc killed")
EndIf

If hSem
  FreeSemaphore(hSem)
EndIf

rtlsdr_close(*dev)
Debug ("Ressources cleaned and rtlsdr closed")

End