-
1Building the Antenna
I mostly copied the design from Here.
Using the calculator for a frequency of 2440 MHz (Bluetooth goes from 2400 to 2483) and 13 turns to get a beam angle of 30 degrees.
I fixed the PVC tube to the center of the PCB copper clad plate with a few tie wraps. The copper side is on the same side as the tube, but I only had double sided plate so that's what I used.
I put a mark on the tube at 1-2 mm from the plate near the hole for the cable, then every 27.6 mm.
I recomment coiling the #12 AWG wire on a slightly smaller tube to shape it before putting it over the PCV tube.
I placed the wire to approximately the correct spacing on the tube, then glued the bottom of the wire near the plate, leaving a small bit to be soldered on the coax UF.L cable. The cable core is soldered to the helix, the cable shield is soldered to the copper plate.
After this I aligned the wire on the marks and put some electrical tape and hot glue to hold it in place. Then drilled two holes in the plate to fix it to a piece of 1.5"x1.5" wood.
-
2Selecting the external antenna on the Esp32 board
This step is a bit tricky, some boards already come with an external antenna, but most Esp32 board are by default connected to the small onboard pcb antenna. You need to remove the small resistor (it's a 0 ohm resistor) and solder the two other traces instead.
I used a very small copper wire and a magnifying glass to get it done, solder alone didn't want to connect the two dots.
There are some Esp32-CAM kits that already have the correct external antenna connection.
-
3Code for single Esp32 Board
This is the code for everything on a single Esp32 Board.
Since I didn't have the correct board, I needed to use two of them, and they communicate through the serial port.
The Bluetooth packets address and raw data get printed to the Arduino Serial Monitor.
The starting point was the BLE_Beacon_Scanner example code.
#include <Arduino.h> #include <BLEDevice.h> #include <BLEUtils.h> #include <BLEScan.h> #include <BLEAdvertisedDevice.h> #include <BLEEddystoneURL.h> #include <BLEEddystoneTLM.h> #include <BLEBeacon.h> #include <MCUFRIEND_kbv.h> // Connections between 3.5" TFT and ESP32-WROOM /*#define LCD_RD 2 //LED #define LCD_WR 4 #define LCD_RS 15 //hard-wired to A2 (GPIO35) #define LCD_CS 33 //hard-wired to A3 (GPIO34) #define LCD_RESET 32 //hard-wired to A4 (GPIO36) #define LCD_D0 12 #define LCD_D1 13 #define LCD_D2 26 #define LCD_D3 25 #define LCD_D4 17 #define LCD_D5 16 #define LCD_D6 27 #define LCD_D7 14*/ #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF #define MinRSSI -100 // Ignore devices below this signal strength, -80 is better if there's too many signals #define MaxRSSI -30 // Full length rssi bar will be displayed at this rssi value #define RangeRSSI (MaxRSSI-MinRSSI) #define MaxBarLength 100 // 100 pixel rssi bar #define BarCol 210 // Bar x position on screen #define MaxAge 20 // Forget device after some time of inactivity = MaxAge*LoopTime #define NTrackMax 30 // Max number of devices displayed depending on screen and text size #define LoopTime 500 // display refresh period in milliseconds MCUFRIEND_kbv tft; SemaphoreHandle_t dataSemaphore; // For data exchange between Bluetooth and Display threads uint32_t previousT, currentT; #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8)) int scanTime = 10; //In seconds, not important since it's in a loop anyway BLEScan *pBLEScan; struct Device{ char Address[18]; int rssi; uint8_t V; uint8_t age; bool allocated; bool updated; // uint8_t mData[10]; // uint8_t Nbytes; }; Device DeviceList[NTrackMax]; // List of displayed devices Device DeviceList2[NTrackMax]; class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) // Executed every time a BLE device is detected { char Address[18]; int rssi = -200; uint8_t mData[100]; uint8_t V = 0; sprintf(Address, "%s", advertisedDevice.getAddress().toString().c_str()); Serial.print(Address); Serial.print(" "); if(advertisedDevice.haveRSSI() == true){ rssi = advertisedDevice.getRSSI(); Serial.printf("RSSI: %d ", rssi); } uint8_t *payLoad = advertisedDevice.getPayload(); size_t payLoadLen = advertisedDevice.getPayloadLength(); Serial.print("DATA: "); for (int idx = 0; idx < payLoadLen; idx++) { Serial.printf("%X ", payLoad[idx]); } /* if(advertisedDevice.haveServiceUUID()){ BLEUUID devUUID = advertisedDevice.getServiceUUID(); Serial.print("UUID "); Serial.print(devUUID.toString().c_str()); Serial.print(" "); } if(advertisedDevice.haveName()){ Serial.println(advertisedDevice.getName().c_str()); Serial.print(" "); } */ if(advertisedDevice.haveManufacturerData() == true){ // Manufacturer data is the same as the payload but without the first two bytes std::string strManufacturerData = advertisedDevice.getManufacturerData(); int Nbytes = strManufacturerData.length(); strManufacturerData.copy((char *)mData, Nbytes, 0); /* if(Nbytes == 25 && mData[0] == 0x4C && mData[1] == 0x00){ Serial.print("iBeacon "); } for (int i = 0; i < Nbytes; i++){ Serial.printf("%X ", mData[i]); } */ if(Nbytes <= 10 && mData[0] == 0x4C){ // Might depends on country V = 1; } } Serial.println(); if(rssi >= MinRSSI){ // && V == 1){ // If true, add device to the list, if it's not full already, && V to only display V xSemaphoreTake(dataSemaphore, portMAX_DELAY); bool found = false; for(int i=0; i<NTrackMax; i++){ if(strcmp(DeviceList[i].Address, Address) == 0){ // If address found in list found = true; DeviceList[i].rssi = rssi; DeviceList[i].V = V; DeviceList[i].age = 0; DeviceList[i].allocated = true; // In case we are in a deleted slot of the same device DeviceList[i].updated = true; } } if(!found){ // New device not found in the list placed in first unallocated slot for(int i=0; i<NTrackMax; i++){ if(DeviceList[i].allocated == 0){ strcpy(DeviceList[i].Address, Address); DeviceList[i].rssi = rssi; DeviceList[i].V = V; DeviceList[i].age = 0; DeviceList[i].allocated = true; DeviceList[i].updated = true; break; } } } xSemaphoreGive(dataSemaphore); } } }; void setup() { Serial.begin(115200); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); //create new scan pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true); // true = don't ignore duplicate device packet within a scan pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster pBLEScan->setInterval(100); pBLEScan->setWindow(99); // less or equal setInterval value uint16_t ID = tft.readID(); tft.begin(ID); tft.fillScreen(BLACK); tft.setTextColor(WHITE, BLACK); tft.setTextSize(2); tft.setTextWrap(false); dataSemaphore = xSemaphoreCreateMutex(); xSemaphoreGive(dataSemaphore); previousT = millis(); xTaskCreatePinnedToCore(DisplayTask, "DisplayTask", 10000, NULL, 2, NULL, 0); // Create a task on core 0 for the display delay(500); } void loop() { BLEScanResults foundDevices = pBLEScan->start(scanTime, false); // flase = clear previously scanned devices } void DisplayTask( void * pvParameters ){ while(true){ // do{ // Maintain constant Loop Time // currentT = millis(); // }while(currentT-previousT < LoopTime); // previousT = currentT; delay(500); // Copy the data to be displayed, Bluetooth thread can write in DeviceList anytime. xSemaphoreTake(dataSemaphore, portMAX_DELAY); memcpy(DeviceList2, DeviceList, sizeof(DeviceList)); for(int i=0; i<NTrackMax; i++){ if(DeviceList[i].allocated){ DeviceList[i].age++; if(DeviceList[i].age > MaxAge){ DeviceList[i].allocated = false; // Delete devices who didn't emit in past 10 second } DeviceList[i].updated = false; } } xSemaphoreGive(dataSemaphore); uint16_t row, barLength, color; for(int i = 0; i < NTrackMax; i++){ // Concaténer des string à la place row = i*16; if(DeviceList2[i].allocated){ if(DeviceList2[i].updated){ tft.setCursor(0, row); tft.print(DeviceList2[i].Address); barLength = (DeviceList2[i].rssi-MinRSSI)*MaxBarLength; barLength = constrain(barLength/RangeRSSI, 1, MaxBarLength); if(DeviceList2[i].V == 0){ tft.fillRect(BarCol, row+2, barLength, 12, GREEN); }else{ tft.fillRect(BarCol, row+2, barLength, 12, RED); } tft.fillRect(BarCol+barLength, row+2, MaxBarLength-barLength, 12, BLACK); } }else{ tft.fillRect(0, row, tft.width(), 16, BLACK); // Erase line } } } }
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.