-
Just Keep Simulating
09/19/2020 at 18:02 • 0 commentsIn 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.
Changing The Radius
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
09/06/2020 at 01:07 • 0 commentsThe 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 #define RADIO_DFECTRL1_NUM_REPEATS 0 #define RADIO_DFECTRL1_NUMBEROF8US (3 + (2 * (RADIO_DFECTRL1_NUM_REPEATS + 1))) #define RADIO_DFECTRL1 ((RADIO_DFECTRL1_NUMBEROF8US_Msk & (RADIO_DFECTRL1_NUMBEROF8US << RADIO_DFECTRL1_NUMBEROF8US_Pos)) \ | (RADIO_DFECTRL1_DFEINEXTENSION_CRC << RADIO_DFECTRL1_DFEINEXTENSION_Pos)) NRF_RADIO->DFEMODE = RADIO_DFEMODE; NRF_RADIO->DFECTRL1 = RADIO_DFECTRL1;
Receiver
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; NRF_RADIO->PSEL.DFEGPIO[1] = PIN_B; NRF_RADIO->PSEL.DFEGPIO[2] = PIN_C;
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 NRF_RADIO->CLEARPATTERN = 1; // Normal antenna to use NRF_RADIO->SWITCHPATTERN = 0; // Reference antenna NRF_RADIO->SWITCHPATTERN = 0; // 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 #define RADIO_CTEINLINECONF ((RADIO_CTEINLINECONF_CTEINLINECTRLEN_Disabled << RADIO_CTEINLINECONF_CTEINLINECTRLEN_Pos) \ | (RADIO_CTEINLINECONF_CTEINFOINS1_NotInS1 << RADIO_CTEINLINECONF_CTEINFOINS1_Pos) \ | (RADIO_CTEINLINECONF_CTEERRORHANDLING_Yes << RADIO_CTEINLINECONF_CTEERRORHANDLING_Pos) \ | (RADIO_CTEINLINECONF_CTETIMEVALIDRANGE_20 << RADIO_CTEINLINECONF_CTETIMEVALIDRANGE_Pos)) #define RADIO_DFECTRL1_NUM_REPEATS 0 #define RADIO_DFECTRL1_NUMBEROF8US (3 + (2 * (RADIO_DFECTRL1_NUM_REPEATS + 1))) #define RADIO_DFECTRL1_AGCBACKOFFGAIN 0 #define RADIO_DFECTRL1 ((RADIO_DFECTRL1_NUMBEROF8US_Msk & (RADIO_DFECTRL1_NUMBEROF8US << RADIO_DFECTRL1_NUMBEROF8US_Pos)) \ | (RADIO_DFECTRL1_DFEINEXTENSION_CRC << RADIO_DFECTRL1_DFEINEXTENSION_Pos) \ | (RADIO_DFECTRL1_SAMPLETYPE_IQ << RADIO_DFECTRL1_SAMPLETYPE_Pos) \ | (RADIO_DFECTRL1_TSWITCHSPACING_2us << RADIO_DFECTRL1_TSWITCHSPACING_Pos) \ | (RADIO_DFECTRL1_TSAMPLESPACINGREF_125ns << RADIO_DFECTRL1_TSAMPLESPACINGREF_Pos) \ | (RADIO_DFECTRL1_TSAMPLESPACING_125ns << RADIO_DFECTRL1_TSAMPLESPACING_Pos) \ | (RADIO_DFECTRL1_REPEATPATTERN_Msk & (RADIO_DFECTRL1_NUM_REPEATS << RADIO_DFECTRL1_REPEATPATTERN_Pos)) \ | (RADIO_DFECTRL1_AGCBACKOFFGAIN_Msk & (RADIO_DFECTRL1_AGCBACKOFFGAIN << RADIO_DFECTRL1_AGCBACKOFFGAIN_Pos))) #define RADIO_DFEPACKET_MAXCNT 512 static uint32_t dfePacket[RADIO_DFEPACKET_MAXCNT]; // Setup the packet to retrieve IQ data NRF_RADIO->DFEPACKET.PTR = (uint32_t)dfePacket; NRF_RADIO->DFEPACKET.MAXCNT = RADIO_DFEPACKET_MAXCNT; // Enable recieving data to calculate AoA NRF_RADIO->DFEMODE = RADIO_DFEMODE; NRF_RADIO->CTEINLINECONF = RADIO_CTEINLINECONF; NRF_RADIO->DFECTRL1 = RADIO_DFECTRL1;
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).
Radio Disabling
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; while (NRF_RADIO->EVENTS_PHYEND == 0U) { while (NRF_RADIO->EVENTS_END == 0U) {} 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
08/26/2020 at 01:30 • 0 commentsIn 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.
Blink
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
Next on the list of parts to get working was USB. While I do have a fancy debugger/programmer, I quite like having debug messages and other assorted data sent via a USB to a serial port. This was yet another wild ride of hitting my head against a metaphorical brick wall. Nordic has quite a number of examples, however quite a few of these attempt to do everything you could possibly do in relation to what the example is for. As such they tend to have a lot more code than is strictly needed for a basic implementation (one reason most Arduino based examples are much better, as they often only have the code you need). As such it was somewhat difficult to work out what code I needed and what I didn't. When I finally thought I had it all working and could compile and clash my board, I ran into every programmers all time favourite, a segmentation fault. Now this is where things got a bit messy. GDB has a was reporting the error in a section of code which, as far as I could tell, was not being run. From researching this a bit I discovered the compiler optimisation flags where set high enough to make this sort of debugging not work properly. Reducing the optimisation level a bit at least made it so the segmentation fault wasn't in code not being run, instead it was at location 0x0. This didn't really seem much better. Additionally GDBs backtrace command failed to provide anything of use, and complained a lot about not recognising Arm as a valid architecture. With yet more time wasted getting confused about this I eventually found where I had gone wrong... MinGW has a version of GDB for debugging programs running on a full computer, not a MCU. After attempting to make my own build which supported Arm, and trying another toolchain, I eventually discovered one called CodeSourcery which worked (although it still had its own issues). And finally I had a version of GDB which supported what I was doing with it. The backtrace command worked, and showed that the segmentation fault was happening in an interrupt method relating to the clock. After digging around a bit more I found that the important part of the USB examples I had missed where to actually initialise the clock. After that I was able to write data over 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.
Radio
With that done the only other system which I needed to test was the radio. While previous parts have taken much longer to get working, this is by far the scariest to debug. I have none of the proper tools required to even look at what packets are being sent, let along analyse the performance of my RF traces and antennas (does anyone have any suggestions on a cheap but functional network analyser, or one seeking a new home). Originally I was going to try using a nRF24L01+ as a Rx/Tx which I know works. However, I found it a bit difficult working out how to get the same settings on both the nRF24 and the nRF52, so in the end I decided it was finally time to build a second of radio board and use that. The Nordic SDK has a fairly simple Rx/Tx test example for the radio, requiring only small modifications to work with my board (and to add the USB logging). Soldering together another board wasn't too difficult for the most part, and certainly a lot easier the second time round. I was even able to program blink onto it first try. However, the board failed to be detected over USB on Windows, and after looking at the pads through a microscope, its looked like the SoCs USB power was not connected. Re-flowing the solder seemed to fix the problem, but still had problems with the USB disconnecting and the chip seemed to fail to function when programmed with the radio test (not even a segmentation fault). After re-flowing and testing the board several more times, I still couldn't it to work. Whats more one of the LEDs stopped blinking, and still not USB. It seems that in all the re-working I managed to get bridging between the pads on the chip itself, and while the chip still functions, I have no way to hold it while simultaneously heating and removing the excess solder off, luckily I have some spare components, and soldering one of them on get everything blinking properly and the USB working. I will have to try removing the bridges at a later date, but for now I don't need to use the particular chips. So with the second board up and running and the radio test programmed, it just worked.I know the image is fairly meaningless, but the sheer lack of problems with the radio is quite amazing (if you ignore however many weeks its been just to get the USB on two boards to work). I did find that some packets where being missed, or had the CRC fail, but I think this is down to the polarisation of the antennas being used. With everything strewn across my desk, its quite likely the antennas are not parallel and so some packets get corrupted. When I make sure to have the antennas lined up there doesn't seem to be any problem at all.
Antenna Array
So firstly I'm just going to put this image here: -
Radio Board Assembly
08/06/2020 at 12:41 • 0 commentsAfter 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: -
PCB Design
08/06/2020 at 12:07 • 0 commentsWith everything simulated we now need to build it. I've decided to use one of the Nordic nRF52 MCUs. There are several which can implement direction finding: nRF52811, nRF52820 and the nRF52833. These all have a 64MHz Arm Cortex M4. However the nRF52833 has an FPU, while the nRF52811 doesn't have hardware USB. There are clearly other differences, such as memory, but these are the main ones. So far we have tested MUSIC on a Teensy 3.6 and 4.1, the 3.6 also has an Arm Cortex M4 with FPU, so the nRF52833 is likely needed, purely for performance reasons. Although we can always collect the data on the nRF and send it to a more powerful MCU such as a Teensy 4.1. I originally designed this for the nRF52820 as I thought it was more available, and is pin compatible with the nRF52833 so I can always upgrade it. However in the end I was unable to get either the nRF52820 or nRF52833 from normal component distributors, but was eventually able to get some nRF52833s directly from Nordic.
So I can use the nRF52833 for the receiver, but as much as I would like to use a pre-made nRF24L01+ for the transmitter, I unfortunately can't as the transmitter needs to be able to enable a continuous tine extension (CTE) at the end of each data packet. However, all this means is that I should design a single transmitter/receiver to be used at each side, and then allow for changing the antenna on the receiver to go to the antenna array. For this array to work, the MCU needs to switch between each antenna, so I also need to break out some GPIO, and while I'm at it I will break out all of it so the board can be used for any future projects:
This is a fairly simple design (it looks quite complicated as I labelled everything) but really it just implements a standard design from Nordic, while also having a complete overkill of a LM1117-3V3 regulator as I want it to run at 3.3V to make interfacing with anything else easier. I also added a bi-colour LED, reset switch and USB connector. For the USB, a friend keeps pestering me to use USB-C so I did, although I cheated slightly by using a USB 2.0 only one which has a single row of connectors making routing and soldering it a lot easier (Its a DX07S016JA1R1500 if anyone else wants to use it). I'm also using a U.FL connector for the antenna as they are much cheaper than SMA and you can get very cheap U.FL dipole antennas from eBay.
We also need a second PCB for the antenna array. For now I'm planning on using an 8 antenna circular array. As such I need to be able to switch between each of these antennas. Looking at the datasheet for the nRF52833 we find this:
We get a 1μs or 2μs switch slot, so ideally we need to be able to switch faster than this. This isn't a huge requirement, as we can continue switching during one sample slot, but then ignore the sample giving up to 6μs switching time. Of course this does make the entire operation take longer so isn't idea. One option for the RF switch is the HMC253AQS24E. This is SP8T so can switch 8 antennas all in a single chip, and has a switching speed in the tens of nanoseconds. However it is rather expensive and can't be used if we wanted to increase the number of antennas at a later date. An alternative option is a number of SP4T chips such as the PE42442. Of course we need 3 of these to get 8 antennas, but if at a later date I wanted to, I can use 4 of them to get 12 antennas, or 5 for 16 antennas. This PCB seems much simple, but is by far the most difficult one I've had to do so far: