AoA Receiver
Table of Contents
Introduction
The aoa_receiver software project implements a simple Bluetooth low energy central device with GATT client and angle of arrival(AoA) functionality. This project needs to be run on the CC2640R2 Launchpad with BOOSTXL-AOA and aoa_sender software project in order to have a meaningful demo.
There are two projects/configurations:
| App Build Configuration | Description |
|---|---|
| FlashROM_Angle_StackLibrary | AoA angle is calculated on the device will be output in JSON format. This is compatible with GUI composer |
| FlashROM_Stream_StackLibrary | AoA raw IQ samples will be displayed in your terminal application |
Hardware Prerequisites
The default AoA Receiver configuration uses the LAUNCHXL-CC2640R2 and BOOSTXL-AOA. The required hardwares and setup are shown in the below images:
Software Prerequisites
For information on what versions of Code Composer Studio and IAR Embedded Workbench to use, see the SimpleLink CC2640R2 SDK release note located in the docs folder. For information on how to import this project into your IDE workspace and build/run, please refer to The CC2640R2F SDK Platform section in the BLE-Stack User’s Guide for Bluetooth 4.2.
Functional Description
Hardware Overview
On the BOOSTXL-AOA board, you can find 2 antenna arrays which composes with 3 antennas each.
Antenna array 1 are named A1.x on the board while antenna array 2 are named A2.x as shown below:
There are switches on the board which are connected to GPIOs on CC2640R2F through LaunchPad headers. The switch to set antenna array 1 or antenna array 2 as active is done with IOID_27. When IOID_27 is high, then antenna array 1 is selected; when the IOID_27 is set to low, then antenna array 2 is selected.
Sub-antenna switches are connected to IOID_28, IOID_29 and IOID_30.
In order to use the antennas from BOOSTXL-AOA, we need to move the capacitor C51(picture on the left) on the CC2640R2F LaunchPad to the position C58(DNM, picture on the right).
Now that we can connect CC2640R2F LaunchPad and BOOSTXL-AOA with the antenna cable that comes with BOOSTXL-AOA.
Final hardware setup will look like this:
Software Overview
Application
| Project Files | Description |
|---|---|
| aoa_receiver.c | Top level application. Initialization of hardware, connection settings, button handling and AoA packets scanning. |
| AOA.c | Implements the AoA control functions. Extracts IQ data, rssi value and channel from the received packets and translates the phase difference into angle. |
| ant_array1_config_boostxl_rev1v1.c | Antenna array 1 switching pattern. |
| ant_array2_config_boostxl_rev1v1.c | Antenna array 2 switching pattern. |
Antenna Switching
Control pattern is placed on GPIOs in ant_array1_config_boostxl_rev1v1.c and ant_array2_config_boostxl_rev1v1.c: We will focus on code for antenna array 1. The same theory applies for antenna array 2 as well.
// User defined nice-names for the pins
#define AOA_A1_SEL AOA_PIN(IOID_27)
#define AOA_Ax_ANT1 AOA_PIN(IOID_28)
#define AOA_Ax_ANT2 AOA_PIN(IOID_29)
#define AOA_Ax_ANT3 AOA_PIN(IOID_30)The ‘AOA_PIN()’ is just a bitwise operation. If you want to use your own design(different IOs), you will need to change the parameter passed on to AOA_PIN function.
#define AOA_PIN(x) (1 << (x&0xff))The initial pattern suggested that in our example second antenna under antenna array group 1 is used to receive the incoming packets at the beginning.
AoA_Pattern antennaPattern_A1 = {
.numPatterns = 32,
.initialPattern = AOA_A1_SEL | AOA_Ax_ANT2,
.toggles =
{
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT1, // A1.1
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT3, // A1.3
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
AOA_A1_SEL | AOA_Ax_ANT2, // A1.2
}
};After that, once AoA packet is detected, we will start toggling IOs to choose different antenna in order to gather the phase difference between each antenna. Here we use GPIO:DOUTTGL31_0 register to enable the toggling function. For more information, please see Driverlib –> CC26x0R2 –> CPU Domain Register Descrption –> GPIO
The first antenna used is second antenna which is controlled by IOID_29 and then we want to change to a different antenna(assuming antenna 1 IOID_28). This means that we need to toggle both IOID_29 and IOID_28. Then we need to set both bit[29] and bit[28] to 1 in register GPIO:DOUTTGL31_0 in order to toggle.
In our software solution, the following function is taking care of toggling pattern generation. It takes the previous selected IOID_x and XOR with the current selected IOID_x.
void AOA_toggleMaker(const uint32_t *in, uint32_t initState, uint32_t len, uint32_t *out)
{
uint32_t currState = initState;
for(int i=0; i<len; ++i)
{
uint32_t tgl = currState ^ in[i];
currState ^= tgl;
out[i] = tgl;
}
}Therefore all you need is to decide the pattern and then call the following two functions to initialize the patterns. These two functions are called in aoa_receiver.c :: static void AoAReceiver_init(void).
BOOSTXL_AoA_AntennaPattern_A1_init();
BOOSTXL_AoA_AntennaPattern_A2_init();Angle Compensation
Under AoA_getPairAngles(), we will acquired the angle based on I and Q data. After that, angle compensation is added. Please see the code below. This is because angle estimation is affected by antenna pairs and frequency. The values p->gain, p->offset and AoA_A1_freqComp/AoA_A2_freqComp are based on lab measurements. Different antenna board design and frequency will give you different p->gain, p->offset and AoA_A1_freqComp/AoA_A2_freqComp.
The following code can be found in AOA.c :: AoA_getPairAngles(), this is antenna pairs compensation.
// Write back result for antenna pairs
for (int i = 0; i < numPairs; ++i)
{
const AoA_AntennaPair *p = &antConfig->pairs[i];
antResult->pairAngle[i] = (int16_t)((p->sign * antenna_versus_avg[p->a][p->b] + p->offset) * p->gain);
}The compensation values for antenna array 1 can be found in ant_array1_config_boostxl_rev1v1.c :: AoA_AntennaPair pair_A1[]
AoA_AntennaPair pair_A1[] =
{
{// v12
.a = 0, // First antenna in pair
.b = 1, // Second antenna in pair
.sign = 1, // Sign for the result
.offset = 5, // Measurement offset compensation
.gain = 1, // Measurement gain compensation
},
{// v23
.a = 1,
.b = 2,
.sign = 1,
.offset = 0,
.gain = 1,
},
{// v13
.a = 0,
.b = 2,
.sign = 1,
.offset = 10,
.gain = 0.50,
},
};The following code can be found in aoa_receiver.c :: AoAReceiver_estimateAngle, this is for frequency compensation.
// Calculate AoA for each antenna array
const int16_t AoA_A1 = ((AoAReceiver_antA1Result->pairAngle[0] + AoAReceiver_antA1Result->pairAngle[1]) / 2) + 45 + AoA_A1_freqComp;
const int16_t AoA_A2 = ((AoAReceiver_antA2Result->pairAngle[0] + AoAReceiver_antA2Result->pairAngle[1]) / 2) - 45 - AoA_A2_freqComp;Data Collection Flow
Setup radio timer (RAT) channel to do capture when a AoA packet is received, the RAT current count is captured in RATCH7VAL(AOA.c).
// CMD_SET_RAT_CPT
// Set up RAT capture: RAT Channel 7, Rising edge, Single capture, and InputSrc 11
rfc_CMD_SET_RAT_CPT_t RF_cmdSetRatCpt =
{
.commandNo = CMD_SET_RAT_CPT,
.config.inputSrc = 11,
.config.ratCh = 7,
.config.bRepeated = 0,
.config.inputMode = 0,
};When RATCH7VAL compare matches, the radio patch will trigger the RAT_GPO4 event.
// CMD_SET_RAT_OUTPUT
// Setup IO configuration for RAT_GPO4, Pulse mode, RAT Channel 7
rfc_CMD_SET_RAT_OUTPUT_t RF_cmdSetRatOutput =
{
.commandNo = CMD_SET_RAT_OUTPUT,
.config.outputSel = 4,
.config.outputMode = 0,
.config.ratCh = 7,
};Route the RAT_GPO4 event to DMACH14.
///// 1. RFC_IN_EV4 route to DMACH14 ///////
HWREG(EVENT_BASE + EVENT_O_UDMACH14BSEL) = EVENT_UDMACH14BSEL_EV_RFC_IN_EV4;
///// 2. Set up DMACH14, 1 to GPTn:CTL:TnEN register ///////
if (NULL == udmaHandle) udmaHandle = UDMACC26XX_open();
uDMAChannelControlSet(UDMA0_BASE, UDMA_CHAN_DMA_PROG | UDMA_PRI_SELECT, //ch #14
UDMA_SIZE_32 | UDMA_SRC_INC_NONE |
UDMA_DST_INC_NONE |
UDMA_ARB_1);Enable DMA to trigger on GPTA timeout and route the event to DMA channel 9.
///// 3. Enable DMA trigger on GPT compare match ///////
//HWREGBITW(GPT0_BASE + GPT_O_DMAEV, GPT_DMAEV_TAMDMAEN_BITN) = 1;
HWREGBITW(GPT0_BASE + GPT_O_DMAEV, GPT_DMAEV_TATODMAEN_BITN) = 1;
///// 4. Connect GPT0 DMA req to Channel 9 / DMA_CHAN_TIMER0_A ///////
//HWREG(EVENT_BASE + EVENT_O_UDMACH9BSEL) = EVENT_UDMACH9BSEL_EV_GPT0A_DMABREQ;
HWREG(EVENT_BASE + EVENT_O_UDMACH9SSEL) = EVENT_UDMACH9SSEL_EV_GPT0A_DMABREQ;
///// 5. Copy a toggle entry into GPIOTGL on timer match ///////
uDMAChannelControlSet(UDMA0_BASE, UDMA_CHAN_TIMER0_A | UDMA_PRI_SELECT, //ch #9
UDMA_SIZE_32 | UDMA_SRC_INC_32 |
UDMA_DST_INC_NONE |
UDMA_ARB_1);When there is AoA packet received (AOA.c), DMA will transfer patterns to toggle the antennas on the BOOSTXL-AOA board. The g_initialPeriod is delay for GPTimer. The reason for adding the delay is that when radio core notifies the application that an AoA packet is received, the actual tone has not arrived yet. Therefore, there is delay added to the timer start to ensure that the antenna only toggles when the tone arrives.
The delay number was found in lab measurement. However, this number should not be changed unless you want to start your antenna toggling later.
uint32_t g_initialPeriod = 1185; // 1385
static void patternSetupDmaTransfer(AoA_Pattern *pattern)
{
uDMAChannelTransferSet(UDMA0_BASE, UDMA_CHAN_TIMER0_A | UDMA_PRI_SELECT, //ch #9
UDMA_MODE_BASIC, //single transfer
(void *)pattern->toggles,//source address
(void *)(GPIO_BASE + GPIO_O_DOUTTGL31_0), //destination address
pattern->numPatterns);
uDMAChannelEnable(UDMA0_BASE, UDMA_CHAN_TIMER0_A);
// Re-enable timer start
static volatile uint32_t timerEnableWord = 1;
uDMAChannelTransferSet(UDMA0_BASE, UDMA_CHAN_DMA_PROG | UDMA_PRI_SELECT, //ch #14
UDMA_MODE_BASIC, //single transfer
(void *)&timerEnableWord,//source address
//(void *)&HWREGBITW(GPT0_BASE + GPT_O_CTL, GPT_CTL_TAEN_BITN), //destination address
(void *)(GPT0_BASE + GPT_O_CTL),
1);
uDMAChannelEnable(UDMA0_BASE, UDMA_CHAN_DMA_PROG);
// First period is 4µs + 4µs * 6 - 2µs
HWREG(GPT0_BASE + GPT_O_TAV) = g_initialPeriod;
}After antenna toggling, the IQ samples from the received packet are extracted by the following function(AOA.c):
void AOA_getRxIQ(uint8_t *packetId, AoA_IQSample **samples)Then the data get passed into the AOA_getPairAngles() to get the estimated angle between receiver and sender.
void AOA_getPairAngles(const uint8_t channel,
const AoA_AntennaConfig *antConfig,
AoA_AntennaResult *antResult,
AoA_IQSample *sample)Usage
This section will describe the FlashROM_Angle_StackLibrary configuration. The FlashROM_Angle_StackLibrary is also compatible with a GUI composer demo, however at first we will cover the UART terminal output.
This application uses the UART peripheral to provide an interface for the application. This document will use PuTTY to serve as the display for the output of the CC2640R2 LaunchPad. The following default parameters are used for the UART peripheral for display:
| UART Param | Default Values |
|---|---|
| Baud Rate | 115200 |
| Data length | 8 bits |
| Parity | None |
| Stop bits | 1 bit |
| Flow Control | None |
Once the AoA Receiver sample application starts, the output to the terminal will report its address and the fact that it is initialized and ready to begin discovery, as shown below:
As shown, the left button (BTN-1 on the CC2640R2 LaunchPad) can be pressed to swtich from discover devices to scan for AoA packets.
Then press the right button (BTN-2 on the CC2640R2 LaunchPad) after switching to AoA Scan, if there is any AoA Packets transmitting, you will see the following from terminal.
How to interpret the numbers :
| Variable | Value Range | Description |
|---|---|---|
| aoa | -90 <= x <= 90 | The angle between AoA Receiver and AoA Transmitter |
| rssi | N/A | Rssi value between AoA Receiver and AoA Transmitter |
| antenna | 1 or 2 | Antenna array that shows higher rssi value |
| channel | 37 or 38 or 39 | Advertising channel that sends AoA packets |
Accessing the IQ data directly
You can also stream the IQ data to the terminal by using the FlashROM_Stream_StackLibrary configuration. The UART terminal will display the I and Q data. First value is I and the second value is Q.
Note: This configuration is not compatible with GUI composer
The aoa_receiver project can stay in a connection with a AoA_Sender(or other peripheral) device while scanning for AoA packets based on rssi value and locating the AoA sender device. Here we are going to walk through how to setup aoa_receiver to connect with aoa_sender and do AoA packets scan in the same time.
Following are the steps you need to accomplish this:
First select Discover and then connect to
aoa_sender.After connection is established, you will see the following picture.
aoa_receiverwill discover AoA Service once connection is established.Press right button(BTN-2) to select Toggle Auto AoA. Then
aoa_receiverwill then write to AoA start characteristic in AoA service implemented inaoa_sender. As you can see that Write sent:1, meansaoa_receiverhas enable theaoa_senderto send out AoA packets.If the rssi value measured is lower than the rssi threadshold set in the program. Then the
aoa_receiverwill disable theaoa_senderand also stop scanning for AoA packets. The logic can be found in aoa_receiver.c :: static void AoAReceiver_processConnEvt()The rssi threshold is set at the beginning of the aoa_receiver.c.
// AOA RSSI Threshold
#define AOA_RSSI_THRESHOLD -80GUI Composer Integration
When using the FlashOnly_Angle_Display configuration, you can connect to the BOOSTXL AoA GUI.
The GUI will take the JSON angle information and plot it using gauge and graph views.
Before connecting to the GUI, we will show what a JSON report looks like on the terminal. You can access this by connecting to the user/UART COM port associated with LaunchPad with BOOSTXL-AOA connected.
Now let’s evaluate the GUI, close the terminal emulator .
- close the terminal emulator connected to the LaunchPad with BOOSTXL-AOA
- Navigate to BOOSTXL AoA GUI
- Select options –> serial port
- In the serial port window, select the COM port associated with the user/UART of the LaunchPad with BOOSTXL-AOA attached, use 115200 as the baud rate
- The GUI will plot the received angle of the AoA packets.
See the picture below for steps on how to configure and expected output