Capture Raw Bluetooth LE Frames
The Bluetooth Framework can turn a cheap CC2540 USB dongle into a full BLE sniffer. It captures raw advertising and connection packets, decodes common PDUs (ADV_IND, CONNECT_IND, etc.), and lets you analyze unknown protocols. Setup only requires replacing the default Cebal driver with WinUSB via Zadig.
When developing Bluetooth software or hardware, you often need to see the raw data communication between your application and a device. While Microsoft provides a Bluetooth Test Platform software for Windows, it offers very limited features and requires expensive hardware. That is where the CC2540 USB dongle comes in. This Bluetooth USB dongle can act as a BLE sniffer, providing an accessible and affordable solution. It is cheap and widely available.
A BLE sniffer (also known as a Bluetooth protocol analyzer) acts as a passive listening device that captures BLE packets sent over the air from various devices within the direct radio range. This is very helpful when you need to debug communication between devices without interfering with and affecting the communication between them. This tool is also very useful when you have a device with an unknown protocol and a mobile application. The sniffer helps to understand the device's protocol and to implement it in your application.
While the CSR dongle comes with its own software for capturing Bluetooth packets, deeper tasks like custom analysis, data logging, and protocol decoding typically require integrating BLE sniffing functionality directly into your application. The Bluetooth Framework provides this capability. The library supports the CC2540 dongle in BLE sniffing mode and includes a class that enables your application to capture all Bluetooth LE communication between devices.
How it works
The first thing you need is a CC2540-based USB Bluetooth dongle. We recommend using the CC2540EMK-USB dongle from Texas Instruments, as it ships with the required firmware pre-installed. However, you can also use any CC2540-based clone from AliExpress or other suppliers. The required firmware is available on the Texas Instruments website.
Drivers installation
Because the dongle uses a proprietary (Cebal) driver by default, you need to change it to WinUSB. This is very easy to do by following the steps below:
- Download Zadig.
- Start the Zadig application.

- Make sure that the List All Devices menu item is checked in the Options menu.

- Select CC2540 USB Dongle in the drop down list box.

- Make sure that the source driver is Cebal.
- Make sure that the target driver is WinUSB.
- Click the Replace Driver button.

- Once driver installation finished you will see the The driver was installed successfully message.

Now all is ready to use the CC2540 USB dongle with the Bluetooth Framework to capture Bluetooth LE communications. To revert the original driver back:
- Open the Device Manager.
- Find the CC2540 USB Dongle device under the USB Devices node.
- Delete the device.
- Refresh devices. This re-found the device and installs the original Cebal driver.
Sample application
The Bluetooth Framework includes the BleSniffer sample application,
which can be used as a starting point for developing your own Bluetooth LE sniffing application and for testing the Framework's BLE sniffer
solution. A BLE sniffer captures packets in two main modes (scenarios):
- Advertising mode
The dongle captures advertising packets, mainly on the primary advertising channels (37, 38, 39). In this mode, you can locate a target Bluetooth LE device and select it for capturing its communication. - Connection mode
It captures raw packet data exchanged between two Bluetooth LE devices during a connection (the remaining 37 channels: 0 through 36).
After starting the BleSniffer sample application, you need to provide the advertising channel number,
which can be 37, 38, or 39. Once the capture process begins,
the application will report all received BLE packets. When a connection request is captured, the wclBleSniffer class
automatically follows the channel changes to capture control and data frames. The Bluetooth Framework
BLE sniffer can decode the following advertising PDUs:
ADV_INDADV_DIRECT_INDADV_NONCONN_INDSCAN_REQSCAN_RSPCONNECT_INDADV_SCAN_IND
The data PDUs and any other unknown packets are reported as raw byte streams and can be decoded by your application. If you need to add any other PDUs decoding, please contact us, and we will do our best to add the required decoding. Please note that BLE packet capturing produces a high-volume data stream. Process received data frames as quickly as possible. For better performance, consider copying a captured frame and analyzing it in a separate thread. Avoid using asynchronous operations within the capture event handlers.
Using the wclBleSniffer
Starting and Stopping the Sniffer
The wclBleSniffer class needs to be created, its events hooked, and then started with a chosen
advertising channel (37, 38, or 39). The following minimal example shows the initialization, start, and stop logic for
each language, taken directly from the BleSniffer sample.
procedure TfmMain.btStartClick(Sender: TObject);
var
	Res: Integer;
begin
	Res := wclBleSniffer.Start(cbChannel.ItemIndex + 37);
	if Res <> WCL_E_SUCCESS then
		ShowMessage('Start failed: 0x' + IntToHex(Res, 8));
end;
procedure TfmMain.btStopClick(Sender: TObject);
var
	Res: Integer;
begin
	Res := wclBleSniffer.Stop;
	if Res <> WCL_E_SUCCESS then
		ShowMessage('Stop failed: 0x' + IntToHex(Res, 8));
end;
procedure TfmMain.FormDestroy(Sender: TObject);
begin
	wclBleSniffer.Stop;
end;
void __fastcall TfmMain::btStartClick(TObject *Sender)
{
	int Res = wclBleSniffer->Start(cbChannel->ItemIndex + 37);
	if (Res != WCL_E_SUCCESS)
		ShowMessage("Start failed: 0x" + IntToHex(Res, 8));
}
void __fastcall TfmMain::btStopClick(TObject *Sender)
{
	int Res = wclBleSniffer->Stop();
	if (Res != WCL_E_SUCCESS)
		ShowMessage("Stop failed: 0x" + IntToHex(Res, 8));
}
void __fastcall TfmMain::FormDestroy(TObject *Sender)
{
	wclBleSniffer->Stop();
}
// In form's Load event:
FSniffer = new wclBleSniffer();
FSniffer.OnAdvIndReceived += FSniffer_OnAdvIndReceived;
// ... hook other events similarly ...
private void btStart_Click(Object sender, EventArgs e)
{
	Int32 Res = FSniffer.Start((Byte)(cbChannel.SelectedIndex + 37));
	if (Res != wclErrors.WCL_E_SUCCESS)
		MessageBox.Show("Start failed: 0x" + Res.ToString("X8"));
}
private void btStop_Click(Object sender, EventArgs e)
{
	Int32 Res = FSniffer.Stop();
	if (Res != wclErrors.WCL_E_SUCCESS)
		MessageBox.Show("Stop failed: 0x" + Res.ToString("X8"));
}
' In form's Load event:
FSniffer = New wclBleSniffer()
' Hook events using Handles clauses on the method declarations.
Private Sub btStart_Click(sender As Object, e As EventArgs) Handles btStart.Click
	Dim Res As Int32 = FSniffer.Start(cbChannel.SelectedIndex + 37)
	If Res <> wclErrors.WCL_E_SUCCESS Then
		MessageBox.Show("Start failed: 0x" + Res.ToString("X8"))
	End If
End Sub
Private Sub btStop_Click(sender As Object, e As EventArgs) Handles btStop.Click
	Dim Res As Int32 = FSniffer.Stop()
	If Res <> wclErrors.WCL_E_SUCCESS Then
		MessageBox.Show("Stop failed: 0x" + Res.ToString("X8"))
	End If
End Sub
// In OnInitDialog:
__hook(&CwclBleSniffer::OnAdvIndReceived, &FSniffer, &CBleSnifferDlg::AdvIndReceived);
// ... hook other events ...
void CBleSnifferDlg::OnBnClickedButtonStart()
{
	int Res = FSniffer.Start((const unsigned char)(cbChannel.GetCurSel() + 37));
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Start failed: 0x") + IntToHex(Res));
}
void CBleSnifferDlg::OnBnClickedButtonStop()
{
	int Res = FSniffer.Stop();
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Stop failed: 0x") + IntToHex(Res));
}
Handling Captured Packets
Each decoded PDU type has a dedicated event handler. The example below shows how to handle the ADV_IND packet - one of the most common advertising PDUs. The same pattern applies to the other six PDU types.
procedure TfmMain.wclBleSnifferAdvIndReceived(Sender: TObject;
	const PduHeader: TwclBluetoothLeAdvertisingPduHeader; const AdvA: Int64;
	const AdvData: Pointer; const AdvDataLen: Byte);
begin
	lbPackets.Items.Add('ADV_IND RECEIVED');
	lbPackets.Items.Add(' AdvA: ' + IntToHex(AdvA, 12));
	DumpPduHeader(PduHeader);
	DumpPayload(AdvData, AdvDataLen);
end;
void __fastcall TfmMain::wclBleSnifferAdvIndReceived(TObject *Sender,
	const TwclBluetoothLeAdvertisingPduHeader &PduHeader,
	const __int64 AdvA, const Pointer AdvData, const BYTE AdvDataLen)
{
	lbPackets->Items->Add("ADV_IND RECEIVED");
	lbPackets->Items->Add(" AdvA: " + IntToHex(AdvA, 12));
	DumpPduHeader(PduHeader);
	DumpPayload(AdvData, AdvDataLen);
}
void FSniffer_OnAdvIndReceived(Object Sender, wclBluetoothLeAdvertisingPduHeader PduHeader, Int64 AdvA, Byte[] AdvData)
{
	lbPackets.Items.Add("ADV_IND RECEIVED");
	lbPackets.Items.Add(" AdvA: " + AdvA.ToString("X12"));
	DumpPduHeader(PduHeader);
	DumpPayload(AdvData);
}
Private Sub FSniffer_OnAdvIndReceived(Sender As Object, PduHeader As wclBluetoothLeAdvertisingPduHeader,
	AdvA As Long, AdvData As Byte()) Handles FSniffer.OnAdvIndReceived
	lbPackets.Items.Add("ADV_IND RECEIVED")
	lbPackets.Items.Add(" AdvA: " + AdvA.ToString("X12"))
	DumpPduHeader(PduHeader)
	DumpPayload(AdvData)
End Sub
void CBleSnifferDlg::AdvIndReceived(void* Sender, const wclBluetoothLeAdvertisingPduHeader& PduHeader,
	const __int64 AdvA, const unsigned char* const AdvData, const unsigned char AdvDataLen)
{
	UNREFERENCED_PARAMETER(Sender);
	lbPackets.AddString(_T("ADV_IND RECEIVED"));
	lbPackets.AddString(_T(" AdvA: ") + IntToHex(AdvA));
	DumpPduHeader(PduHeader);
	DumpPayload(AdvData, AdvDataLen);
}
Frequently Asked Questions
- What is a BLE sniffer?
- A BLE sniffer (Bluetooth protocol analyzer) is a passive listening device that captures BLE packets sent over the air from nearby devices, useful for debugging and protocol reverse engineering.
- How do I set up the CC2540 dongle for BLE sniffing?
- Replace the default Cebal driver with WinUSB using the Zadig tool. Then the Bluetooth Framework can use the dongle to capture BLE packets.
- What capture modes does the BLE sniffer support?
- Advertising mode captures packets on primary advertising channels (37, 38, 39). Connection mode captures raw data exchanged during a connection on the remaining 37 channels.