This is a project I've been working on for a while, but finally decided to put it on here.
The basic idea is that Bluetooth 5.1 added AoA direction finding functionality, which several Nordic chip implement. This is done by measuring relative phase and amplitude (IQ) data from each antenna in an array receiving the same signal, or at least part of a continuous tone as only one can be measured at a time. This IQ data can then be used to calculate the angle(s) of arrival of the signal. Depending on the shape of the array different angles can be measured. For example a uniform linear array can only measure a single angle, from the axis the array progresses along. However, of full direction finding you need either a grid array or a circular array. For the purposes of this I will be using a circular array as it should be the most consistent from all directions.
The difficult part comes in calculating the angles from IQ data. I will mostly be looking at Multiple Signal Classification (MUSIC). This is actually more capable than what I need, as it can find the directions for multiple signals at the same time. However simplifying to only a single source signal removes some of the downsides to the algorithm, and is all this system will be capable of anyway. To summarise this algorithm a spectrum value can be calculated as a function of a test direction, this value should peak where the test direction matches the actual direction the signal comes from. Thus, we just need to find the peak in this function to get the AoA. However, even this isn't all that simple, as for a circular antenna, the spectrum value looks a bit like this:
The issue being that the peak is very localised. While this is really good in terms of the uncertainty in the value, it is rather difficult to find quickly. We can't just measure all the values on a grid, as we would need to measure far to many of them, while things like gradient assent (gradient descent for the negative of our value) are also fairly slow as the gradient is practically flat for most of the region. I've found that an algorithm called COBYLA works quite well (and there is a version written in C which I can run on a microprocessor).
So now we are able to find the peak value, how well does this actually work. Well, for now we can simulate everything. As I'm planning on putting the source on a model rocket for camera tracking at some point, lets use that as an example. Just using simple projectile motion and a circular array of 8 antennas with radius 4cm we can get:
This also has 1% Gaussian noise in the "measured" phase as well as a simple low pass filter. It is likely this noise is far more than we will eventually get, but at this stage I don't know. Either way this looks really promising. For this we have used an update rate of 50Hz (ideally we want to measure at this or even faster). However, for the 20 seconds covered by this, it took roughly 25 seconds to calculate. Clearly this is a problem, but then this uses Python so really re should expect it to be rather slow.
The next step was to get MUSIC working in C/C++, for the time being this is still on my computer as its easier to test things. For this I set up a Unity project for some visuals and then used a native plugin for the C++ part. This has two parts, firstly I simulate the signal from a source points which can be moved around. Then I can render the spectrum plot on my graphics card, while also sending the simulated antenna data to the native plugin to calculate the AoA:
Here the blue line is the actual direction, while the red line is the calculated direction. This can already run in real time at about 50fps, and is also slower than we can get, due to passing data between Unity and the native plugin.
Finally we should get this running on a microprocessor. We can send the simulated IQ data to a MCU via serial. I have so far tested two different MCUs, firstly a Teensy 3.6 which takes about 10ms to calculate, and secondly a Teensy 4.1 which takes about 1ms to calculate. Pretty impressive seems like we might be able to get to 100Hz even on a Teensy 3.6. Well not really as there is some other overhead, but it is likely we can do 50Hz on it.
So it seems that everything should work, but this has all been simulation so far. We now need to actually build the system.