• ### Just Keep Simulating

In the previous log I finally managed to get IQ data, and with that the next set of problems. Firstly it seems that my data is very noisy and while I have made some improvements on that front, for this log I want to talk a bit more about simulations. I also promised to talk about running the MUSIC algorithm in real time on the nRF52833. However, that will also be in a later log, probably when I have direction finding working (so hopefully the next one but who knows).

Firstly I would like to note that at the end of the previous log I suggested that one of the issues is that because the IQ data should have a frequency of 250kHz, then I should have been using this frequency in the MUSIC algorithm. This is wrong. At some point I found this page which eventual (look for a post near the end by robwasab) points out that a phase shift in the 250kHz baseband frequency will be equivalent to a phase shift at the 2.4GHz radio frequency. Thus, while the IQ data propagates at the lower frequency, the initial phase shift seen between sets of samples from each antenna is still based on the higher frequency. I think this is poorly explained by the documentation but it at least means we may not need a much larger array size to get AoA data. This also made me think about the what might be the best radius. I was initially thinking that I should probably have a larger radius anyway, but as we shall soon see from simulations, I may need to do the opposite and make the radius smaller.

## Simulated Signals

For running simulations I am using a Python implementation of MUSIC. This not only performs the actual calculations, but also generates the simulated data from each antenna. This simulated signal is crucial to being able to make useful predictions from the simulations. If it is done wrong or poorly then I might as well not bother with simulations at all.

To get a fairly accurate phase value we can consider a spherical wave from a source point R, traveling to each antenna at positions r_i. Then we can found the number of times the wave will cycle over the distance to get w_c. This will be some integer multiple of the wavelength plus an additional fraction to get a relative phase ψ_i. This phase isn't really relative to anything in particular, but can be made so by subtracting the phase from some reference point. As the phase of the data we measure is fairly arbitrary anyway this can probably be ignored.

Using this, we can then add some amount of Gaussian white noise and we get a sample. To match what we can measure we can then make a number of samples by progressively offsetting the phase by 2π*250000*dt. Where dt is the time step between samples. I should note that each sample should have its own noise, rather than applying the same noise to all samples for each antenna. This should give a fairly accurate simulated signal base on the position of the source which we can vary over time. The only issue being that the noise being added is probably not really the noise that we actually see, but it will do for now.

We can also consider some examples for the typical motion we might see The first of these is some simple projectile motion based on an initial velocity and position. This is roughly what you might expect from throwing the beacon, or putting it on a model rocket (which was sort of the original aim of this project). This is the motion I have used for the simulated data shown previously. For the graph shown we use a radius of 45mm and a 1% phase error.

Rather than going though and manually generating the graph for a number of values of r, it is much better to run lots of simulations and directly generate a graph for quite a range of values. To get a good idea of the full range of possible values, lets start at a 5mm radius and go all the way to a 500mm radius. As a note, these also still have a low pass filter over the AoA data.

This data is quite noisy, but it very clearly shows what we sort of expected. The data gets better as the radius increased, but at a certain point it then gets worse again. Having a lower noise also gives a lower error, but also seems to limit the maximum radius before the increase in error. Interestingly above about r=100mm, the error is independent of the noise in the measurement, clearly due to this error being from the algorithm no longer being valid. This behaviour is present in both the parabolic and sinusoidal test case. However in the slower moving parabolic motion, the radius can be quite a bit higher. This is likely due to the low pass filter being used on the data.

What we really want is the worse case scenario as a test case to find the maximum radius, but clearly I can't test every possible motion. However we can at least see that the radius of 45mm currently being used is often above the maximum. I also hypothesise that the the diameter of the array must not exceed half the wavelength. For 2.4GHz this gives a maximum radius of 31mm, which is below an limit we have seen in the test cases used. However this has not been tested at other frequencies and so may just be a coincidence. It is also not tested on actual hardware, but hopefully I can do that at some point in the future.

That will do for this log. I thought this was quite an interesting way to further investigate the properties of my system through simulation. Of course it is only a simulation and so may not apply directly, but it is still a useful tool, and I think it should be used when possible. I have also made some progress with the hardware side, notably finding that I was not using the correct control signals when selecting which antenna to use. I also may need to add a -3V3 supply to allow the RF switches to switch between antennas faster, but I still need to look into how exactly I will do that. Finally, my hope to find a low cost VNA have been significantly improved after the nanoVNA V2 was pointed out to me on Twitter. I have not yet ordered one, as it seems a new version should be released in a week or two, which should have lower noise and sweep frequencies faster for the same price.

Thank you for continuing to follow this project, hopefully I have some more exciting updates soon. But in the meantime feel free to leave any questions you have, and I will do my best to answer them in future logs.

• ### We have data

The progress up to the end of the previous log is that I have two functioning radio boards and quite likely a functioning RF switch board. The next step being to actually measure in phase and quadrature (IQ) data. In the Nordic SDK, this is done by setting several registers on the radio peripheral to configure automatic constant tone extension (CTE) whenever a packet is sent, and then take IQ samples during the CTE of a packet being received. The IQ samples are then placed into memory to be processed with the MUSIC algorithm (or anything else).

## Transmitter

So fist step is the code to transmit data. Most radio settings are the same as what are needed to transfer data normally. For now I don't actually care about the data being sent in each packet (but this could later be some sort of telemetry or similar). As such I can reuse my radio test code, based on the radio test from the Nordic SDK. I did modify this slightly to work with by board, as well as adding my USB logging backend. We then have to set two registers to enable CTE.

```#define RADIO_DFEMODE RADIO_DFEMODE_DFEOPMODE_AoA

The next step is setting up the receiver. This has several extra values to set. Firstly the RF switch control pins need to be setup. These will automatically be taken control of by the radio when needed (so shouldn't be used for anything other than the RF switch). The first step is to assign which pins are to be used. How many of these are set will depend on the array and RF switches being used, but can be any GPIO pins (I have seen some claims that they need to be high-drive only but this doesn't seem to be the case, at least not for my switching chips), do note that these should be set as outputs first using nrf_gpio_cfg_output.

```NRF_RADIO->PSEL.DFEGPIO[0] = PIN_A;

Next the switching pattern needs to be set, this is done by clearing the pattern register, and then setting the default antenna to use, followed by the reference antenna and then each of the antennas to sample from. Generally the default and reference antenna will be the same, and then each antenna will be sampled in turn. If the RF switches being used switch too slowly (take longer than 2μs) you can add each antenna twice, then only use samples from every other slot.

```// Clear the current pattern

// Normal antenna to use

// Reference antenna

// The remaining antenna
for (uint8_t i = 0; i < 8; i++) NRF_RADIO->SWITCHPATTERN = (uint32_t)i;```

Finally we need to setup the radio configuration registers. There is quite a bit more to set, and some of these settings may not be needed, but should be included. Firstly we need to set a pointer in memory for where to store IQ samples, this can be done with DFEPACKET. Setting the array location to DFEPACKET.PTR and max size to DFEPACKET.MAXCNT. We will then be able to use DFEPACKET.AMOUNT to check how many samples have been taken. Next we again need to set the DFEMODE to be AoA. For this example we know the DFE settings on both the receiver and transmitter, as such we can disable reading this data from the packet by setting the CTEINLINECONF register. Lastly we again need to set the DFECTRL1 register. This has the same settings as before (make sure that the CTE length matches that of the transmitter), as well as extra settings for how to take samples.

• SAMPLETYPE: Either IQ or MagPhase depending on how the data should be given in memory.
• TSWITCHSPACING: Either 2μs or 4μs depending on the if 1μs or 2μs slot sizes are being used. To avoid confusion, this is the total length of a switching slot and sampling slot rather than just the size of a slot.
• TSAMPLESPACINGREF: How often to take IQ samples during the reference period.
• TSAMPLESPACING: How often to take IQ samples during a sampling slot. Generally this should be as fast as possible.
• REPEATPATTERN: How many times to take a repeat of all sampling slots. Generally longer sampling slots is better, but this may still be useful depending on the antenna array being used.
• AGCBACKOFFGAIN: How much to reduce the amplifier gain when taking IQ samples. Likely to be fine kept at 0, but may be useful if some antennas have a much stronger signal than others. It may also help reduce noise but this is untested.
```#define RADIO_DFEMODE RADIO_DFEMODE_DFEOPMODE_AoA

// Setup the packet to retrieve IQ data

// Enable recieving data to calculate AoA

With these settings I should expect 64 samples in the reference period, followed by 8 sample from each antenna. However we actually get samples from the switching periods as well, for a total of 16 samples per antenna. What is more, the radio seems to make repeats of all the antennas for the full length of the CTE period, even when set to not make any repeats (I can't see why it should do this, but it doesn't really matter). This can be seen for looking at the RF switch control pins (note I have changed the reference antenna to make it clear were the CTE starts):

This shows a 12μs use of the reference antenna (guard plus reference slot) followed by 14 combined switching and sampling slots lasting 2μs, giving a total 40μs. Which is the total length of the CTE as configured for no repeats (we can reduce the extra sampling by only using a 32μs CTE, but this still gives 32 extra samples). This means we end up with a total of  288 IQ samples. While this is more than it should be, we can just ignore the extra samples, with the only downside being it takes slightly longer to collect samples.

## Pitfalls

As usual, it wasn't all smooth sailing getting the IQ sampling working correctly. So I would like to include some of the main issues I ran into.

Logging floats

For some reason Nordic SDK needs you to treat floating point values differently than anything else when formatting them in a log. When logging you will likely use something like this:

`NRF_LOG_INFO ("Here is a uint %u", someUINT);`

However, for a float you have to instead use:

`NRF_LOG_INFO ("Here is a float " NRF_LOG_FLOAT_MARKER, NRF_LOG_FLOAT(someFloat));`

While this doesn't really matter, it does make it become confusing if you want to include multiple values. The Nordic SDK only supports up to 6 arguments when formatting, and while this might not seem a problem in the two examples above, the issue is that NRF_LOG_FLOAT_MARKER and NRF_LOG_FLOAT have the following definition:

```#define LOG_FLOAT_MARKER "%s%d.%02d"
#define NRF_LOG_FLOAT(val) (uint32_t)(((val) < 0 && (val) > -1.0) ? "-" : ""),   \
(int32_t)(val),                                       \
(int32_t)((((val) > 0) ? (val) - (int32_t)(val)       \
: (int32_t)(val) - (val))*100)```

So rather than just being the single value you might expect, the float gets split into 3 values, so makes it much easier to run into the maximum argument limit. Of course this isn't exactly a problem either, as you can just make multiple log messages (although you may need to use NRF_LOG_RAW_INFO).

The second, and more problematic, issue I ran into relates to how the radio gets disabled after use. The initial problem was that I was getting far fewer samples than expected, corresponding to just the reference period plus a couple more. It seemed like a problem with the length of the CTE. In searching for some solution I found this post which as a very simple way to test if the CTE is enabled or not. After sending a packet you wait till the CTE should start, and then increment a counter as fast as possible until when the CTE should have ended using the following code:

```uint32_t counter = 0;

{
counter++;
}

NRF_LOG_INFO ("The packet was sent, counter had value %u", counter);```

Looking at the data sheet for when events trigger we can see that the END event triggers after CRC and before CTE, while the PHYEND event triggers after CTE:

If the CTE is enabled we should expect the counter to have a larger value for a longer CTE, while the counter should be very small when there is no CTE enabled (not quite zero as in practice there will be a short time between the two events being set). This also shows us the problem we are having. While this test showed that the the CTE was enabled, the issue is that in the radio test example the receiver and transmitter both wait till the END event before disabling the radio. But clearly this is before the CTE has finished so the radio will be disabled before all samples have been collected. The fix is to wait till the PHYEND event before disabling the radio (somehow I managed to realise this was the fix for the receiver and then spend a day or so wondering why all the samples taken after the reference period had a much smaller amplitude, almost like the transmitter was also being disabled too early).

## Processing

With that we now have IQ samples being taken  (if you want a good explanation of IQ sampling is, I found this as quite a good article) now all that needs doing is to put the data into the MUSIC algorithm which we already have working and the project should be almost complete... right. Well spoilers, no its not that simple. But first lets look at some of the data we now have.

Looks really good doesn't it. Except I forgot to plug my antenna array in for this, so its just from a single antenna. At least it seems the RF circuitry on my radio board is good. So what about some actual data:

Doesn't look quite as good does it. That's partly because I have chosen one of the worst examples but it highlights what the probably issue is. It looks like there are multiple copies of the wave, overlaid with each other. This would point to there being reflections of the signal within the radio switch board. Unfortunately I don't have a vector network analyser to find out (if anyone has any ideas about getting one I'm open to suggestions, but remember I am a student and can't afford anything expensive). There is also another issue with this data. I based my initial simulations on receiving IQ data for a 2.4GHz wave, and while that is the frequency being used it is not the frequency of the wave that the IQ data forms. Instead I should expect a 250kHz wave when using 1Mb/s. So lets look at a Fourier transformation of the data:

This has a peak about 182kHz. There is also a tiny peak at 250kHz, but it is insignificant in comparison. Now this could be that we are doing a Fourier transformation on data that has abrupt changes in phase but it still seems a slight issue. Ok, but does the MUSIC algorithm give anything useful:

I think that will be a solid no. If we look back to the initial testing, be where getting a peak with a value of about 20, compared to the ground noise of pretty much 0. Also, when doing this test the transmitter was held at an angle which should give a value of θ of about π/2 not π. So perhaps we need to go back to our simulation.

## Simulation Time Again

So with the new knowledge that we are using a 250kHz wave lets look at what we should expect from the MUSIC spectrum:

This seems to have a very similar shape to the actual data, but with peaks about 100 times larger. Lets also rerun our simulation of projectile motion:

Well that doesn't look good. Firstly θ is stuck at π/2 as we saw in the real data, while φ fluctuates around. But what can I do to fix this. Well its a bit difficult to tell. It possible that a larger radius would improve things, but also if the issue is with reflections the noise will be far too great to do anything. From playing about with my simulation the a 0.1% phase noise corresponds well with the real data. Reducing this by a factor of 100 starts give give some useful results. In combination with a larger radius array (~10cm rather than 4.5cm) and the data is almost what we had before, although possible a more aggressive low pass filter will be needed.

So what is the future plan. Increasing the antenna radius is relatively simple (with a current limit of about 12cm) as I am not using PCB antennas, but I absolutely need to reduce the noise, without the proper equipment it will be rather difficult to work out the problem. For example another possible issue is that the coax cable between the radio and switch board may not be properly shielded and so act as an antenna in its own right. Its also possible that I can filter the IQ data before passing to MUSIC. But currently I am unsure about how to do that effectively. While taking more samples seems to have little effect in simulation. I could also redesign the switching board, but I don't know what about it needs to be changed so would probably run into the same issue.

Another thing that needs work on is real time processing of IQ data. Currently this is being done by in Python, but really I should be able to get it working on the device. In fact I do have it working on the device, but it needs a little more work so will leave that to the next project log.

So as it currently stands I will continue to try out things as I get ideas, and feel free to make suggestions, I have probably missed something important.

Also I am aware that the code looks a little odd, but C++ code snippets seem to have a mind of their own when it comes to highlighting.

• ### Programming Time

In the previous log I build one of the radio boards and attempted to program it, running into difficulties about not having a programmer capable enough. I also mentioned about a new programmer called a Bumpy which has been designed to program nRF52 SoCs, specifically the nRF52832. As a reminder I'm using the nRF52833, a fairly similar chip, so it would seem that this programmer should work no problem. Well, as you may be able to guess, things are never quite that easy. The programmer firstly took quite a while to show up, coming from the US to the UK, but at least when I plugged it in everything seemed to be working. I would like to just include a picture of it here as I really like the look of the thing, I need to design more of my boards with interesting outlines:

But this is where I ran into issues, as before I'm using Black magic firmware with GDB to program over SWD, previously only a generic Arm Cortex-M device showed up in GDB. With the new programmer, I still had this device, but also had one labelled as "Nordic nRF52 Access Port". With some playing around trying to use each of these devices, I still couldn't get something to program such that the "compare-sections" command would validate that the memory was correct.

Eventually I discovered that this was down to the firmware of the Bumpy, and with some further digging through the source code discovered that in the nrf51.c file there is a method called "nrf51_probe". This gets called to check if a connected device is some valid Nordic MCU. However, in the Bumpy version of the firmware this is done by reading a device specific value from the MCUs memory to check against a list of known IDs to discover what the device is. Unfortunately, the nRF52833 is a fairly new chip, especially in the QFN40 package I am using. As such, the code for my chip is not in the list and so my board is not recognised as a Nordic device. While I was able to read this value from my chips memory, this isn't really the best solution to checking a particular device. One main reason why is that part of the check is so that the correct amount of memory can be assigned when setting up programming. However, the information for this is directly readable from the chip, so there is no need to use a reference table. What is more, the current up to date version of Black Magic does allow for any current and future Nordic SoCs to be programmed, buy checking if the device is in the nRF51 or nRF52 family before reading the memory sizes from the device. As I needed to reprogram the firmware on my Bumpy anyway, I thought it was probably best to use the more general form of the method in case I use any other chips. This was fairly simple, only having a slight issue of using dfu-util to update the firmware. From Linux the programmer wasn't recognised, while on Windows it claimed to successfully program it, but without actually programming it (which resulted in quite a lot of confusion as no change seemed to apply). Eventually I discovered I needed to use dfu-util with sudo permissions on Linux for the programmer to show up, and then everything just worked.

Toolchain
So having waited a month for the programmer to show up and probably another week just being able to use it I can finally get started. Well yes and no, before starting this project, I had only ever programmed things via the Arduino IDE. So it was quite a leap to be able to even set up the toolchain. This wasn't helped by wanting to do everything on Windows (as I only have Linux installed on a laptop and don't want to use that for everything). I found something called MinGW installed make, GDB and the compilers I need grabbed a copy of the Nordic SDK and managed to get everything set up. While I didn't really know how to use make files (and still don't fully), Nordic provided some really good templates and with only some small changes so I could setup project directories in a way I like I was able to compile a program. While I had been able to do this before (Linux makes things much easier), at the time I was still unable to program my board and so couldn't test the build. Thus I now went straight into the next metaphorical brick wall.

The first program you should run on any MCU is blink. I think getting an LED flashing just clearly shows that at least something in the MCU is working. However, no matter how I played around with my code (I wasn't quite sure I was using the correct pin numbers) the bi-colour LED on the board refused to change. Poking around with a multi-meter I found that other pins would work, but not my LED pins. After some amount of time I discovered found that the reason for this is that I had hooked my LED up to the NFC pins. While these can be used as GPIO, it seems that they can't by default (even if this seems a little odd). The solution is to add the compiler define "CONFIG_NFCT_PINS_AS_GPIOS" and at last I have blink. In fixing this I also found there is a define "CONFIG_GPIO_AS_PIN_RESET", so for the reset pin you have to enable it for the alternate function, whereas the NFC you have to disable the less general function. Anyway, this was a lucky find and meant I didn't spend far too long trying to get the reset button to work. I also want to make a comment that putting a breakpoint at the start of your program will cause it to stop at that point, even if you are running the device without the debugger. It took me more time than it should have done to work out why my program wouldn't run without the debugger (somehow I was forgetting that I had to tell the device to continue the code each time I ran it with the debugger).

USB

Logging
The Nordic SDK has a logging system built in. By calling NRF_LOG_INFO (or WARNING or ERROR) you can very easily send a debug message via a connected debugger. This can be done with either UART or RTT using pre-made logging backends. But I still like my USB, and wanted to get logging working over USB. Unfortunately, there is no USB logging backend. Fortunately, there is a very simple structure to how backends are programmed (and I could mostly copy bits from elsewhere). As such I got it all working rather easily. Starting from the UART backend, and renaming everything. Most code can just be removed, but you still need to be able to write data. Taking just the main parts of the code (VSCode is upset with int types for some reason):This used the same serial backend used for UART (which does the formatting and adds the log type) but changes where the how the data is sent. The only other part of this of note, is that you need to provide a usbd cdc acm reference, which needs to be setup before initialising the logging. This can be  defined as follows:Where m_app_cdc_acm is the reference (Nordic really likes these macros making everything a bit confusing to follow). Of course you still need to initialise the the USB (and the clock beforehand). Which is shown in the USB examples.

Antenna Array
So firstly I'm just going to put this image here:

After waiting a few weeks, the PCBs are now in my possession. I also have all the components I need to build them, even with some self sabotage by ordering the wrong antennas and forgetting to order a coax cable with the parts for Digikey. Firstly we have the radio board:

Initial inspection shows everything to be in order. I should have tented the bottom side of the thermal vias on the antenna board, but its not really a problem. I also should have a bit more imagination when it comes to silkscreens but that doesn't affect anything electrical.

I am very happy with the rounded corners on the PCBs, possibly I should have used a larger radius, but I think all my projects will use rounded corners from this points. Also I would highly recommend having mounding holes all over the place. It the board to be held much more easily, and without the possibility for damage to the board from what is being used to hold it. All you need is to put a bolt through a hole, attack it in place with a nut on the other side and then have something hold the bolt:
For larger boards you might want to do the same thing at the other end of the board to help support it, but even with just one, it is fairly stable.

Next is soldering everything on. I will gloss over this as there are plenty of guides on SMD soldering, and as I'm using neither a stencil or re-flow oven, I'm hardly a good influence. The main points of interest are that I'm glad I changed away from 0201 components that the Nordic design suggested, the USB was much easier to solder than the normal USB-C connectors, and that when soldering QFNs like this it is a good idea to re-flow the solder paste before butting the chip on to make sure there is enough on all the pads. An afternoon later gives me: