Normally, to send RF signals, you would use radio
chips, or microcontrollers with radios in them, or maybe get creative with your electrical
engineering degree. But really, any time there is a change in electrical potential along
a conductor, an RF field is made. It can even be as simple as turning a lightswitch on and off.
This is a ch32v203. It’s a 35 cent microcontroller without a radio of any kind. But, by noodling
one of its pin around at couple of megahertz juuuuuust right, it’s sending LoRaWAN messages
to this commercial LoRa gateway at 904.5 MHz, and that gateway is forwarding those packets to
the things network where they can be accessed around the world… And it can do this from
over 400 feet away! I’ve worked on unusual RF projects with microcontrollers before. But none
like this. It’s, one, kind of ridiculous to get protocols running on microcontrollers that have
no business running them. It’s another altogether when the results are orders of magnitude more
ridiculous than what any of the engineers I talked to about this imagined. 400 feet just
barely scratches the surfaces of some of the insane tests we’re going to show later on.
As a general disclaimer, I wanted to point out that most of the power we are transmitting is
being blorched all over the spectrum. While the total radiated power is only a few microwatts,
only a few nanowatts is actually being put into the intended LoRa transmission. This is not
something you could commercially use. This isn’t even really something you should make one or
two units of to test a few minutes at a time.
A microcontroller is like a small computer. It’s
got a computing core, RAM, permanent memory and I/O - but in all of these areas, microcontrollers
are very limited. This one has only about 64 kilobytes of flash and 20 kilobytes of RAM and is
typical of something like a USB keyboard or mouse or other USB connected device.
I love playing with these limited resources because it forces me to think much
more creatively. Microcontrollers ride the line between hardware, being mostly defined
by rules and laws, and software, where there are no rules. Where software and physics mix
some very interesting things can happen.
With software running on a microcontroller, we
can turn switches on and off very quickly and very precisely. Even though we can’t turn them on
or off any faster than a few tens of megahertz, by turning them on and off very carefully, we
can create little bits of noise wayyyyyy up the spectrum in places the chips have no business
sending anything. And that little bit of energy can be created carefully and clearly enough to
be received by LoRa receivers. The solutions I found to do this don’t just work on this one
microcontroller because of some hardware quirk, but these solutions work on lots of
other microcontrollers as well!
Several weeks back, my friend Frank was
chatting in my discord server about various RF technologies. When they explained some of
the technical details of LoRa, a long-range, low-data-rate RF protocol, and that it was able
to traverse tens of kilometers with only a few milliwatts of power, it was time for me to take
note. We chatted for a bit, and the thought of sending LoRa packets without a radio (even just a
few inches or feet apart), it seemed outlandish, but within the margin of possibility. And, if
there is anything that captivates my interest, it’s the things that only have
a tiny chance of success.
The path this project took was circuitous,
and I’d really like to tell that story. So, join me in the story of the most surprising
result from any of my projects I’ve ever had.
LoRa, although it’s very low-data-rate (on
the order of 2 kbits/s for most of our tests), is specifically designed to operate over long
ranges at low power, which is pretty unique among most wireless protocols. There’s only
a few available transceivers on the market, and the protocol itself is still proprietary.
There is a lot of information online about LoRa, and a lot of it is wrong. That’s because it’s all
been derived by various people through reverse engineering, and with limited information, making
a functioning transmitter was… difficult.
One of the first difficulties was that
the operational frequency that inexpensive microcontrollers can control IO is limited.
Typically from around 20 to 80 MSPS. Sometimes core clocks are higher, like the ESP32-S2’s 240MHz
core clock, but there’s no way to route that anywhere near output pins. So, the frequencies
we have to work with are all relatively low. But if you’ve ever been involved in making a product
that must pass EMI/EMC Tests, you begin to fear even these lower frequencies because they can
cause interference at higher frequencies.
That’s because when transistors turn on and
off, they don’t just make a principal frequency, unless they are making a perfect sinewave. When
they make things that look more like square waves, they generate “harmonics'' higher up the radio
spectrum. You can hear this in the difference between a sinewave and a squarewave.
Let’s lower the frequency much, much lower. Even though it’s unlikely your speakers can
produce this 27.5Hz sinewave, you can hear a 27.5Hz square wave! That’s because square
waves actually generate “odd” harmonics. That is,.a square wave is made up of the fundamental
frequency as well as 3× the frequency, 5× the frequency, 7× the frequency, and so on.
Each of these harmonics up the spectrum is much smaller, as a function of the physics
of being a square wave. When microcontrollers produce square waves, there are also natural
physical limitations causing slew rate limitation on the GPIO, further shrinking these
overtones. While the power might become very, very small, these harmonics do exist.
Armed with this knowledge, and a dream, I wrote some code for the ESP32-S2 since it has an
Audio PLL or Phase Locked Loop that is extremely versatile, able to generate fractionalized
multiples of the 40 MHz on-board crystal.
For instance, we could tune the numerator to
13.884, which it multiplies by the 40 MHz to create a 555.36 MHz numerator which the PLL
internally divides by 4, to generate an APLL clock of 138.84 MHz. Sadly, the highest I’ve
been able to get this out of the chip is through another divide-by-two. So in this example,
that final output signal is 69.420 MHz.
Because this is a square-ish wave, it generates
odd harmonics, including the 13th harmonic, at 902.46 MHz! Because no signal is a perfect
square, and odd harmonics ’power falls off with frequency, there’s very little power left up
at 900 MHz, but it is there, which we can see by moving the SDR antenna right next to the
wire pair on this swadge from MAGFest.
This swadge is a game system that uses an ESP32-S2
as the main processor, and it doesn’t really use the built-in 2.4GHz radio for much. It also
has an SAO port for hobbyists to use as they please. There’s nothing special about any
of the hardware on this swadge. But, we can route both that 69.420 MHz signal and its inverse
to the SAO port here! And we can also tune that just a little bit lower or a little bit higher,
and that’s all we need to transmit LoRa frames.
The reason we need to move the frequency around
a little is that LoRa uses “chirps” to actually send data. Instead of just having a tone turn
on and off, like with OOK or On-Off keying, AM or amplitude modulation, or PSK (phase shift
keying),. LoRa uses chirps. These chirps create an up- or down- flowing tone, and depending on
where they start, they can convey information.
When I started this project, what I didn’t know
was just how hard it was to create these chirps, outputting exactly the right frequencies at
exactly the right times. But I knew it was going to take an enormous amount of trial and
error. In fact, my sandbox counts the number of recompiles and uploads code to the ESP.
It took 1900 tries before I got everything working robustly on the ESP32-S2.
Thankfully, I was doing the development in my esp32s2-cookbook idf-sandbox which
lets me change code, recompile, upload, and run it in about 1 second instead of the 8 to
20 seconds that it takes for the IDF to reboot the ESP into programming mode, build the firmware,
and reflash it and test it. It only took about 15 hours to get the ESP32-S2 transmitting readable
LoRa packets, whereas if I had been using the IDF, it would have taken half that time just
recompiling and flashing process, and with an attention span as short as mine, I probably
would never have finished this project.
My code simply reads the current time in
processor cycles, determines what the PLL settings should be at that exact instant,
and writes them into the PLL’s registers.
I can create any frequency in the 903.9 MHz,
125kHz wide channel at any time by adjusting the APLL’s output on the pin from 69.526
to 69.536 MHz, a spread of just 9.6 kHz.
With this, I can create several up chirps, as
a preamble, two more up chirps to define the network type (0x43 for LoRaWAN), two and a quarter
down chirps, and a payload. The length of each chirp depends on something called the spreading
factor, where for SF7 for instance, each chirp takes 1,024us. For SF8, which goes slower,
each chirp takes 2,048us. Each one of these chirps can encode seven or eight bits of data.
If you think this sounds complicated, boy howdy do I have some bad news for you. This is just the
tip of the iceberg. LoRa uses a very sophisticated combination of Gray codes, interleaving, and error
correction in order to maximize the recoverability of transmitted messages, even changing the number
of bits encoded into each chirp depending on the spreading factor. And, with it not being a
standard, all of this information had to be gleaned from reverse engineering efforts.
I was able to adapt some code from MyriadRF from one of those efforts. And with the help
of Frank getting some real packets captured, after only about 500 attempts, I was
able to get a packet to be received by this Mikrotik wAP LR9 commercial gateway!
This was fun, and highly unusual, using a GPIO to send signals up at 900 MHz that had no business
being there. The signal was really ragged, producing a lot of spurious noise around
the intended frequency, and its tracking on the actual intended frequency was rather
poor. The APLL is a rather analog system.
It was a bit of a dead end. Nothing I
did was able to clean up the signal, and while it was technically working, on the
13th harmonic, I had to give up just accepted my (well I thought) mediocre success. On top of
that, it just wasn’t impressive enough because after all there was a Wi-Fi radio sitting in
this ESP32-S2 (even though I wasn’t using it), and I was curious if there were any other ways and
other directions that might yield more fruit.
I moved onto the ESP8266 because it has a very
potent glorified shift register in the form of an I2S bus that can be fed with DMA. This is
what I used in a previous video to broadcast color Channel 3 NTSC video at 62.5MHz.
For ESP8266 development, I didn’t need the wifi, and so I used my nosdk8266 project. Really there
are two reasons behind this. One is on many systems I struggle to get the python packages
properly installed to be able to use the whole toolchain with its dependencies for esptool. The
other is it would take several seconds to compile and flash the ESP every time I wanted to test even
a few lines of code difference. I just recently removed esptool’s usage from nosdk8266 in
favor of Dr. Wolf’s esputil (a completely C, multi-platform esp programming tool with zero
dependencies and downloadable binaries), so that I completely remove the need to fool around
with python dependencies, and, because there’s no massive SDK, I can develop quickly… and do things
with the ESP8266 that you ordinarily can’t do.
For one, normally the ESP8266 can only be
run with an 80MHz AHB, limiting the maximum you can twiddle a pin at being 40MHz… But
with NOSDK8266, we can overclock the AHB to around 216% of its original speed, shifting
data out on an IO pin at 173MSPS, able to generate a max frequency of around 86.5MHz.
It’s a fixed 173MSPS. So you can’t play the same game as we were on the ESP32-S2. We’re locked into
that 173. We talked about harmonics, but there’s another trick I have up my sleeve. This one is one
I learned from that color television project that I previously mentioned. You can create arbitrary
signals, and through the magic of aliasing and images, a signal can get reflected around half
of the sampling frequency, as well as 3×, 5×, 7× (and, well, all the odd harmonics)
of that sampling frequency as well.
When I was transmitting color video on 61.25MHz,
my actual signal was actually at 18.75MHz but reflected around 40MHz. What’s weirder is that
it’s a reflection. So a tone at 39MHz has an impostor at 41MHz, and a tone at 20MHz (much
lower) has an impostor at 60MHz (much higher). The Wikipedia article on undersampling is a pretty
good introduction to the kind of thinking that’s needed when working in this domain.
When we enter this regime of digital synthesis, things get … weird.
A single tone can be reflected and aliased around several ways before it lands where I want it to,
and there’s weird restrictions. For instance, as you synthesize frequencies whose base is very
close to the base sampling frequency, the amount of power that can actually be transmitted
is very low because there are very few bit transitions - to get power you have to turn the
IO on and off a lot - so you have to be careful what frequency you select and shift around.
While it would be a good thing if you were to understand exactly what’s going here, as
the RF signal is shifted around and mixed, to understand why there are so many weird
images all over the spectrum, it’s not crucial. There is a shortcut which I haven’t
really found people talking about anywhere. So if it doesn’t already have a name, I’m gonna
call it the lohrcut. You can just ignore all the fancy math and write some code to “imagine”
the signal you want at the frequency you want, and you just sample it where you would output
a bit. Heck, you can even skip bits if you know where they will be skipped. Maybe this is just
undersampling? And to go from analog to digital, if the signal is >0 output a 1, if it’s <0, output
a 0… Or if you ever did want to change amplitude, you can just shift it up or down. That’s… it.
When you do this shortcut, it generally seems to produce pretty good results causing the desired
signal to be produced where you want it produced (though it does come with other garbage up and
down on the spectrum). And I haven’t found any ways of producing more of the signal when the
only output is a sub-harmonic bitstream.
Also - when precomputing the signal, you can take
as long as you want, creating it ahead of time, then burn the pattern into the part’s flash. So
the microcontroller can produce what would be a very difficult to produce signal just
by blasting through a lookup table.
I can hear some of you Nyquisters out there
saying, “But you can only receive or send signals at half the sampling rate.” Having
tasted and seen intentional aliasing, I hope all engineers let go of the ½fs safety blanket
and develop a more nuanced view of sampling.
The approach I took was to pre-compute a
bitstream for an up-chirp and a down-chirp for a given target channel, bandwidth, spreading
factor, and clock frequency. I flash this on the ESP8266 at a specific place in FLASH and read
into RAM at boot since the FLASH stops working when we overclock the ESP anyway. That
way I could iterate very quickly on my code without needing to reflash the table.
I originally got it working when overclocking the ESP to 173MHz, then after some tweaks,
I got it working down to the factory speed of 80MHz. Each one of the transitions of the RX
pin on the ESP causing just a small disturbance just very carefully placed where I want it. I
was able to push the bit rate all the way down to 7.2Msps before I just could not get any 900
MHz LoRa messages through. With a requirement of only 7.2Msps, this could be ported to
all sorts of much cheaper microcontrollers, use less memory resources or other
resources inside the processor.
Even though the disturbances this is creating
up at 900 MHz is microscopic when the bitstream is only 7.2 MSPS, its output less than
3.6MHz, LoRa receivers tuned to the 900 MHz still get a miniscule signal and
somehow can understand the messages. One of LoRa’s tricks is that it can understand
messages below, way below the noise floor.
Once I had all the code set to precompute
the bitstreams to generate LoRa messages, I then ported the project to the CH32V203
I showed at the beginning of this video, a totally radio-less part. It has a DMA-controlled
SPI bus that can shift bits out at one half of the system clock. So, for a part operating at the
factory speed of 144MHz, we can shift bits out at 72Mbit/s. It still took several hundred cycles
of programming to get the bitstream reasonable and I had to iterate through several different
patterns. After I ran into difficulties with the crystal control circuitry and the and PLL, and
had to have my ESP32-S2 programmer generate a 24MHz reference to work off of, clocking through
various configurations. Eventually I was able to get the signal working at 144 MHz and using the
normal crystal oscillator. In doing this port I found a few minor issues with the code as it
stood on the 8266, and so I fixed that port, by updating a common header file.
I then ported the whole project to the CH32V003, the 10 cent little brother to the 203, and after
a lot of trouble getting it to produce a workable signal, I found a few bugs that were present in
the 203 codebase and even one or two in the core LoRa code, I eventually found out the compiler
replaced some of my carefully written code with a `memcpy` which was not specifically
tuned to the low-end RISC-V processors, and had strange side-effects resulting in slightly
mangled (but understandable) bistreams because of the timing of each assembly instruction in its
loop. I had to rewrite the code in assembly to get it to go fast enough on the 003. After moving
this improvement back to the 203, it performed even better! I think `memcpy` was right on the
edge of keeping up on the 203, but now using a custom copy it’s able to easily keep up.
As a side-note, I never could figure out how to make the SPI of the 203 perfect, and
I’m really sad I couldn’t get its i2s engine working because that probably would have
been perfect - if it even has an i2s engine.
You can see a little bit of junk around
our signal, whereas something like the ESP8266 really is perfect. I’d like to try doing
a timer- or DMA-fed-timer approach some day, since that might be perfect and use less storage.
Now that I had the ability to send LoRa messages, let’s take this to the next level. With
LoRaWAN packets! If one of these bad boys gets received by a LoRa gateway connected to
the things network or the helium network, it can be relayed through the internet and around the
world. So a microcontroller “printf” on someone’s desk in another country could be sent to an app
running on the server in my parents’ basement.
Thankfully, there’s a clean separation where the
underlying LoRa protocol is mostly encapsulated away from the higher level LoRaWAN packets.
To receive these packets from the things network via my MikroTik LR9, I had to set up a new app
on the things network. From there I created a new device using the base LoRaWAN 1.0.0 Spec and
selected Activation by personalization (ABP). This makes it possible to share the secret
encryption keys with the device without it ever needing to receive any packets at all!
Setting the Device ID, NwkSKey, AppSKey and programming them into a commercial device, Frank
was able to generate some valid LoRaWAN packets, and I was able to snag on-air and decode the raw
encrypted data with lora-grc running gnuradio.
I just had to figure out how to make my packets
encrypt using the correct AES key and compute the right AES-CMAC so that my code would have the same
output as the commercial code which worked. This took a lot of trial and error and re-reading the
LoRaWAN spec (At least that one is documented.), trying to navigate one-indexed, zero-indexed
arrays and example code, writing my own CMAC code from the ground up (which is now licensed
under the unlicense if anyone wants it)... When transmitting the same payload as Frank, I
was finally able to get the same output.
After hooking it up and finding out I needed to
check the box that allows frame counter resets on the things network, packets were flowing!
I cleaned up the LoRaWAN generation into a single function call then ported to the
CH32V203, and the project was complete.
In downtown Bellevue, my girlfriend took
a ch32v203 with a short wire plugged into the MOSI pin and connected it to a USB
power bank. We started at just a few feet, since that's all I ever expected. But we kept
having to re-adjust the test as we kept going further and further. Eventually we got to the
farthest places where we could get line of sight, and we were just able to find some orientations
where packets would flow. We were transmitting valid LoRaWAN frames 440 feet or 134 meters.
We re-did the test at both 125kHz and 500kHz channel widths. When going to 500kHz to maintain
the same airtime, I increased the SF number by 2. This has the effect of sending the same amount
of data through, but with a signal spread wider and lower. When operating at 500kHz channels, the
receiver was getting signals down at -18dB SNR!
18dB SNR means that for every one part signal,
there’s almost a hundred parts noise. So on these waterfall plots you can’t see the transmitted
signal at all. Decibels or dB are a logarithmic scale. For every 3dB change, that corresponds
to a halving or doubling power. Every 10dB, to a ten times increase or decrease. Every 6dB
up corresponds to a doubling of range and 6dB down a halving. And when dealing with absolute
power like dBm, 0dBm corresponds to 1mW of power. -10dBm corresponds to 100uW, -30dBm
corresponds to just one microwatt of power. When we look at this SDR waterfall view, and we
see the extra junk hanging around our graphs, it’s important to note that this image right here
is actually 1/10th the power of this peak right here. Don’t let the log scale fool you.
The little wire we plugged into the MOSI pin could have been a trace on a PCB going to a
touch button, LED, switch, or going somewhere else inside of an actual product. People call the
misused connection inside a product as an antenna a “funtenna.” While having a wire connected
to nothing is as optimal as you can get, short of having a proper antenna, it’s by no means
unrealistic in principle as we’ll see later.
For the sake of the main thesis of the video -
using a regular devboard, an analog for a consumer product like a camera, audio digitizer,
or the like… Without any hardware mods, just different firmware, we could send messages
to a commercial off-the-shelf gateway in a crowded downtown area and have these same messages
forwarded messages to the internet. And it worked at around 435 feet or 132 meters away. I would
consider this a madcap success. It completely blew my mind. I asked my dad about this problem,
sketching it out. His original expectation is that we’d be able to transmit no more than
20 feet. And even that I was dubious of.
But why stop here? What if we go
somewhere with a longer line of sight and somewhere without tens of thousands
of electronic devices blaring in my block?
Frank and I went to a park in a more suburban
area to do a test, this time with a receiver with an external antenna. We completely exhausted
the length of the park before he had to go into the woods, and we lost track after 550 feet.
We then went to a paragliding strip figuring there’d be no way we’d max out the 1,100 feet.
But nope. We still got packets through loud and clear. We then found a straight road before
we got a fair max distance reading of 2,200 feet or 669 meters. And at -133dB receive power!
This is more of a testament to LoRa and not just this project that my little transmitter,
transmitting at probably 50 nanowatts, could make messages that could be received
and decoded over a third of a mile away.
There was some foliage between the transmitter
and receiver, and we wanted to push this and give the parts the best opportunity it could get!
Frank took his drone, and my girlfriend and I went out to a huge park to do a real, clear
line of sight range test. We strapped the little radio-less CH32V203 to his quadcopter and
sent it on its way. First with a 125kHz channel, then with a 500kHz channel, and we were able to
get a definitive answer. The little ch32v203 was able to go 2220’ or 677m at 125kHz channel width
or 1752' or 534m at 500kHz channel width.
Then, for kicks, we decided to do a little
hardware mod and overvolt the ch32v203 to 5V instead of the 3.3V that it’s rated for.
And were able to get it to talk 3996' or 1218m! Admittedly this is “cheating,” but
it’s incredible that we were able to get packets to flow over a whole kilometer without
ANY external passive or active components.
After some discussion, we made a new, slightly
shorter antenna based on some VNA tests, switched back to 3.3V, and sent the 203 off
to finally get our definitive number.
Our little CH32V203, with factory settings,
was able to reach 2719' 829m with only a little wire hanging off the MOSI pin. We had
our final answer. We were able to transmit valid LoRa packets over half a mile.
While we were at the park we also tested an ESP8266 and found that it was able
to send lora messages 2789’ or 850m.
We packed up to go home, but then Frank noticed
the swadge I had in my bag and asked about it. I thought about how I started with an ESP32-S2
initially, but that its signal was really rough, and we wouldn’t be able to get more than
a few feet with it. But that was before I uncovered all the bugs discovered with the other
processors. So, we decided to just do a quick test by walking on the ground.
I’m at a thousand feet.
Whelp.
Still comin’ through.
I had to go to work, but we returned later that
day, where it was a bit more rainy, a little snowy and a lot more filled with elk. We programmed the
ESP32-S2 in the swadge to make 125kHz wide SF10 coded packets, strapped it to the quadcopter,
put on a bitenna, and sent it on its way. Frank got 1429 meters (or 4895 feet) away til he had to
fly back, but all our packets were still coming through at almost a kilometer and a half away!
Just to do a test with a funtenna, we decided to remove all the wires from the swadge and
used it as it comes, without any hardware mods at all, and got 705’ or 215 meters!
Running out of daylight quickly, we estimated the max theoretical range based on the current
receive power and SNR and found a very, very straight trail nearby. Frank drove to one part
and sent up his drone with the swadge on it, and I drove to another part, parked and started
walking to where the trail would get straight.
It’s so hard to express how utterly deranged this
seemed. I was soaking wet. It was around freezing. I couldn’t feel my fingers, and I was walking
to a dot on a map, 2.5kM or 1.6 miles from where Frank sent up his drone and hoped that
by waving a LoRa receiver around in the air, I was going to capture a LoRa packet.
And… after a message from our sponsor. Just kidding. That’s… exactly… what… happened.
The ESP32-S2’s audio PLL, the marginal failure from earlier on in the project, which I thought
was too dirty to do anything useful with, was a complete sleeper success. That little Audio PLL
built into it turned out to be the GOAT. Sixteen decibels below the noise floor, we caught a packet
at -141 dBm, or less than one hundredth of a femtowatt. I have a whole new level of respect
for the engineers involved in the creation of LoRa and the designers of the receivers.
And Espressif, if you see this, pleeease bring back the audio pll for new chips. It’s so
powerful and can be used in so many novel ways.
Anyway,
It was over.
It was finally over.
Not only did an ESP32-S2 send a packet at 900MHz, a frequency that its radio can’t operate at, it
transmitted the packet farther than any ESP32-S2 has ever sent a packet of any kind without some
kind of directional antenna or signal booster.
This insane rabbit hole came to an end, and
on the other side was something even more outlandish and unhinged than wonderland.
There’s a lot of things I learned on this project and some interesting tools
I found I just wanted to mention.
One of those tools was the AirSpy or AirSpy Mini.
I’ve previously used RTL-SDRs to great success, but the AirSpys are massively better in both SNR,
dynamic range, and bandwidth. It was so helpful to see 10MHz of spectrum at once instead of being
limited to the 3.2MHz that the RTL-SDR able to.
Another really great tool is GQRX. It’s very
similar to SDR#, but for Linux and just feels a little bit simpler to use. It really helps
for getting a lay-of-the-land or a smoke-test to see if I really am transmitting where I
think I am. It helps me kinda scrub around and find the signal. It also helps me to get a
feeling for how powerful the signals that are being transmitted are and where they lie.
In conjunction with the AirSpy Mini and GQRX, you can can now clearly see a large chunk
of the spectrum to help get a more intuitive understanding of just what is going on in reality.
For instance, being able to look at this signal here and how moving the antenna around just a
few millimeters can so drastically change its receive pattern, causing even slightly different
frequencies to go in and out of destructive and constructive interference modes.
This actually shows a major point - that for signals like LoRa, and even wifi (for more
info on that see my video on High Res Wifi Signal Mapping), for reliable signals, diversity or
related technology is crucial. Diversity means receiving (or transmitting) signals from
two separate antennas spatially separated, that way while there are going to be few if any
dead zones. For narrow-band signals like LoRa, even a few millimeters difference in position
and orientation can mean humongous changes in effective range, where the signal can
go from very strong to invisible.
By having multiple antennas, you can apply
a swiss cheese model. While there are holes when using one antenna and holes when using
another antenna, or if 2 antennas on each side, all 4 combinations, the chances of you being
somewhere and in an orientation where all the combinations fail quickly approaches zero.
Another helpful tool was gnuradio. This app is designed to do some more advanced operations
with SDR, and there are multiple LoRa decoders available for it, though all of them are
somewhat buggy’, So I can’t recommend any. They were helpful none the less to sanity check
my work. One thing I did with it was I took the input data stream, ran a small FFT, and logged
the output of that FFT to a file. Then I could go in and use stb_image_write to output a .png
file - I’ve included this in the tools folder of this project’s repo, `lorasoft`. A sort of
very-high-time-resolution waterfall view.
This came in really handy when we tried cloning
my garage door opener with an ESP8266. I looked at the back of the opener, googled the FCC ID
which it said 310MHz. Checked it on GQRX and saw a signal right there at 310MHz. Then,
I ran the high resolution waterfall on it. I could see that the signal was using a
sort of OOK modulation, so I just cloned the pattern of on and off pulses and wrote
my bit table generator to output 310MHz.
Shockingly, I got about the same range
with my ESP8266 transmitting 310MHz on an GPIO as I did from the real opener!
Oh, there’s also another interesting wrinkle with the bit table generator. It matters that
one selects a good period for it. For instance, if you pick an arbitrary number of bits for
your table size, the wave might not line up when the one wave wraps around off the end
of the table back to the beginning. So it’s important to make sure you output your bits
so there’s as small of an error as possible when going off the end of the table back to the
beginning. Discontinuities will cause significant side-band noise messing with the signal.
One thing to note here is that if you do make it line up, you will get a very narrow and powerful
signal. Great if that’s what you want, but many times that may not be what you want.
Let’s look at something like the CH32V003. It has both an internal RC oscillator, or it can use an
external crystal. Even when using a crystal, it’s rough to use it for this project because the clock
wanders around some. On average the 003 runs 48 MHz, but over the course of several microseconds,
the clock rate can vary, pretty a lot. And when you compare with the RC oscillator, the clock
moves around so much that it spreads any noise that is generated all over the spectrum.
For most engineers this is a huge benefit. If you are hoping to create a product that
can pass FCC testing, this feature is a godsend. It just makes it so if you are trying
to abusively modulate signals out the SPI port, you’re gonna have a bad time.
Congratulations. You made it to the end of this video, I hope I’ve been able to
inspire you to do something new and fun that goes beyond the datasheet. Don’t just paint by
numbers using the existing arduino libraries. Explore just how wide the canvas goes.
While my code isn’t perfect, I’m sure there’s bugs. And it doesn’t work with spreading
factors other than 7-10. All of my code, as well as a detailed write up, is
available on github–link in description.
I wanted to say thanks to Matt Knight and for
their reverse engineering work, myriadrf and Arne Hennig for their work on writing code to work
with the LoRa protocol, gr-lora_sdr developers, Frank for his inspiration and depth of RF
knowledge, Willmore for the editing work, several other folks from my Discord for
their help, and, you. Thanks for watching.