-
Code on Github
04/24/2017 at 13:06 • 1 commentstarting to put some code up on GitHub - warning - very messy, crude, uncommented, but works for me. YMMV
-
UPS / 12v car power for raspberry pi
12/01/2016 at 16:11 • 0 commentsEvery man and his dog needs to power their Pi from a battery, and deal with filesafe shutdown, but I can't get one that works!
I'm now on Pi UPS number 3 in trying to find a system that will work - I want 'idiot proof' power - plug it in to cigarette lighter socket and it starts up. unplug it and it gracefully shuts down without corruption. Plug it back in and it reboots automatically.
With the LoRa board and the LCD display as well as fairly processor intensive operation and wifi, the power requirements of the setup are over 1.5A@5V.
I tried first an early generation UPSPico from modmypi, but this had pin conflicts with the LoRa module.
I then tried a LiFePO4weredPi UPS, but the version available at November 2016 was limited to 450mA. I could just about get it to work if I isolated the LCD backlight 5V power from the Pi power, but then the battery still drained as there was about a 50mA deficit. I might wait for the 18650 version... the form factor is great.
I then tried an ebay CPT 12V to 5V 15W (3A max) power supply, but this was atrocious - voltage at no-load was a healthy 5.15V but dropped to around 4.75V under >1A load.
I then tried a new UPSPico from modmypi (HV3.0A Stack). This works fine with an official mains-to-micro USB adapter, but with anything else I struggle with current draw brownout over any of the USB to micro USB cables I have tried, and any of the 12V to 5V USB cigarette lighter adapters I use, even though they claim 2.1A output. When the UPS Pico charger kicks in, even the 2.1A output droops to ~4.7V and causes the UPS pico to brownout.
Next to try:
1) homemade USB micro cable to reduce cable loss
2) LM2596 adjustable buck regulator to homemade USB socket to boost USB voltage available.
3) UPS Pico HV3.0A stack PLUS, which has an external 7-24V input that I will wire to a 12V cigarette lighter socket directly (fused!)
4) LiFePO4wered 18650 version.
-
optimised interrupt handler for less jitter in TDMA window
08/23/2016 at 11:13 • 0 comments//pseudocode void loop() { //if the current timestamp 'seconds' matches our device's transmission slot, // then do nothing else other than sit in a super-tight loop awaiting the interrupt if (systemSeconds == txSlotSeconds) { //do nothing else but loop tightly, awaiting PPS interrupt while (PPSflag !=True) { } //PPS flag is True - go and transmit straight away! //delay if necessary for inter-second timeslots delay(txSlotMillis); //or micros if needed! LoRa.Transmit(packet); PPSflag=False; } else { //we are not due to transmit in the next second, so go and do other things - readVoltage(); updateTime(); updatePosition(); constructDataPacket(); } }
I think this will work for devices that need to transmit on the second (i.e. txSlotMillis==0), as our code will start its tight loop in the second PRIOR to the scheduled txSlotSeconds, and wait in the tight loop for up to 1 second for the following PPS to come along.
Doing a really crude measurement of a loop with digitalWrite(high);digitalWrite(low), the Cortex M0 with arduino compiler executes a loop (including 2 GPIO writes) in 3.6 microseconds. Each GPIO write takes16 clock cycles (various google sources differ for this), giving a corrected loop duration of 3 microseconds
I need to work out how to do the loop in assembler perhaps, then our loop just needs to be a few clock cycles: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0432c/CHDCICDF.html
tight while loop:
//does our register (pps flag in r0) eq 0? mov r1,#1 loop:cmp r0,r1; // compare pps register to 1 brneloop ; //branch if not equal nop; //exit loop
cost: 1 cycle if not equal, 3 cycles if equal (from ARM reference material)
Interrupt latency: 16 cycles (various sources)
ISR: 1 cycle ( mov r0,#1 )
this gives a tight loop with interrupt-exit in about 18 cycles (probably missing something here)... or 0.375 microseconds at 48MHz
I'm sure the limit on TDMA will now lie with either the HopeRF LoRa transmission timing or in the receiver.
-
TDMA slot timing - more detail
08/23/2016 at 09:56 • 0 commentsSomebody asked how the pseudocode worked out what timeslot it should use.
Each tracker device is assigned an ID, which in turn we use to calculate the device's timeslot for transmission.
We have an access slot window of 333ms (packet on-air duration is ~280ms)
We require transmissions once every minute, which gives 180 devices per radio channel.
We have a precise 1 pulse-per-second (PPS) reference signal from the GPS
Therefore each device is assigned a 'seconds past the minute' timeslot, AND a 'milliseconds past the s30
econd' timeslot.
- Device 1 transmits on 'second timeslot' = 00 and 'millisecondtimeslot' = 000
- Device 2 transmits on second 00 and millisecond 333
- ...
- Device 9 transmits on second 02 and millisecond 666
etc...
On each PPS we set an interrupt flag.
in the main LOOP() program, we only perform other actions (get GPS position, get battery voltage etc) if the PPS interrupt flag is NOT set. We also keep these other actions really short - so that if a PPS interrupt comes along whilst we are performing an action, we can exit quickly and service the interrupt.
In the main LOOP() we are continually fetching and updating the the current time from the GPS (hh:mm:ss) - and we store the SECONDS (ss) in a local variable each time it is updated.
When our PPS interrupt is flagged, we ADD ONE SECOND to the current time, and if this second value matches our device's 'second timeslot', we then DELAY() by our 'millisecond timeslot' value (possibly zero), then transmit our packet.
Why do we add one second to the current time?
Because on the exact moment that the PPS interrupt is triggered, our system has not yet had chance to fetch the the new timestamp from the GPS, but we know it has advanced by one second as we have just received the PPS pulse from the GPS! The value for the current timestamp is exactly 1 second out of date.
The worst that will happen is that the PPS interrupt flag is set whilst we have just initiated a serial read from the GPS to update the time. We only receive one byte from the serial port before we check for a PPS flag, so there is no danger of updating the microcontroller's memory with the current timestamp before we act on it - system time is only updated once a complete NMEA sentence is received and parsed by the microcontroller and certainly not after just one byte.
Keeping a fast LOOP() is crucial, so we must perform any actions very quicky (so they complete quickly and we iterate around the loop quickly back to servicing the PPS routine), and can be skipped if a PPS flag comes along.
What is the minimum window time we can use?
Our main loop() code completes in 7.8 microseconds (worst case scenario).
Jitter - the difference in start time between subsequent transmissions in the best and worst case scenarios - varies between 0 and 7.8 microseconds
If our PPS flag happens to be set when we are in the point in the loop() code that checks for the PPS flag, then we have the minimum delay before it is acted upon.
If we have just finished checking for a PPS flag, and are now busy reading a byte from the GPS over serial - we have to wait for this to finish, then skip over all other checks before the loop() starts again and we service the interrupt flag. This is measured at taking 7.8 microseconds.
Future Optimisation
We can then theoretically have timeslots with, say, 10 microsecond guard period, if we have a precise delay(microseconds) function which itself does not have any jitter, and our activity (transmitting) within each window itself is not subject to jitter.
In our scenario, if our LoRa transmission takes 280ms +- 1 ms, we could easily define TDMA access slots 282ms apart. i.e. with a 1ms guard period either side of a transmission window that easily accounts for the jitter in our loop() timing
Moving from a TDMA slot of 333ms to 282ms gives us 212 slots per minute - an extra 32 slots/devices we can squeeze onto our 1 minute cycle.
Clearly the limiting factor for the number of devices per radio channel is the time-on-air of each device - and any limitation in our receiver (perhaps the receiver AGC recovery time would not allow us to make such rapid successive receptions - especially if a distant, weak tracker followed a nearby strong signal which made the receiver 'deaf' for a few milliseconds.
What is the limit of the hardware?
We can no doubt be more elegant with this solution - for example only read serial from the GPS for the 900ms AFTER a PPS has taken place, then for the following 100ms sit patiently in a very tight loop() doing nothing but waiting to service the PPS interrupt - written in assembly, our jitter can be significantly reduced down to a few clock cycles (loop iteration (~6 cycles), write variable to memory (1-2 cycles), compare two variables in memory (1-2 cycles), interrupt latency (16 cycles), ~ 30 cycles ~0.6 microseconds at 48MHz
-
Tracker Payload and LoRa parameters
08/22/2016 at 20:37 • 0 commentsPayload
Device ID: 1 byte (0-255)
(Latitude: 3 bytes (0-90 degrees represented by a signed (3 byte integer) gives ~1.1m resolution at 56 degrees
Longitude:4 bytes, ( 0-180 degrees represented by 4-byte signed integer)
battery level: 1 byte (3.3-4.5v mapped onto 0-255) - probably overkill resolution
Speed over ground 1 byte (0-63.75kph mapped on 0-255)
COG - 1 byte 0-360 mapped on 0-255
#satellites (4 bits) of one byte
#valid fix (1 bit) of one byte
#panic button (1 bit) of on byte
# gpio flags 2 bits spare
Total 12 bytes
LoRa Parameters
With a set of 'long range' LoRa parameters (SF=10, BW=125kHz,CR=4/8), implicit header and a 12 Byte payload, the time on air is 280ms (610bps equivalent bit rate).
-
microsecond resolution TDMA using GPS PPS on Arduino
08/22/2016 at 20:35 • 0 commentsTL;DR
GPS Pulse-Per-Second drives hardware interrupt. Tight loop() can give 10 microsecond TDMA time slots whilst still fetching and parsing GPS data. (48MHz Cortex M0)
I need a way of synchronising multiple tracker units so their transmissions do not overlap. The obvious way I can think of is just to assign each device a timeslot - a number of milliseconds past the minute that each device should transmit.
We know some rough figures - we want position updates about once per minute, we want to support > 100 devices per RF channel, and one LoRa 'packet' takes ~ 280ms on-air (See the other project log entry for how these values were calculated).
In its most simple form, we could have 60 devices, and each one transmits on their designated second-past-the-minute. We have a fantastically precise "Pulse Per Second" (PPS) output from the GPS that will synchronise the start of every second across all devices.
But I want more than 60 devices. So I need to come up with a way to accurately time the transmissions to fit in, say, a 1/3 second (333ms) transmit window, to give 3*60=180 possible devices per radio channel (or 90 devices at 30 second refresh rate etc).
Fudging millisecond Time Division Multiple Access in Arduino IDE C
Small real-time-clock modules are available for around £1, which we could add to the Feather, initialise with the time from the GPS, then use a tight loop to reference the time and get the transmission to take place.
I decided against the added hardware complexity and managed it all in software, using the Pulse Per Second output from the GPS in an interrupt routine to trigger the transmission.
In an ideal world, we would have the PPS signal from the GPS trigger a transmission from within the Interrupt Service Routine (ISR). This isn't possible for lots of reasons I could not fix, and isn't advisable as the amount of code, 'delay' functions etc that goes into transmitting a LoRa packet cannot be accomplished inside an ISR (Delay function uses interrupts itself).
The main hurdle I had was accurately triggering a transmission on the sub-second "+333ms" or "second+666ms" timeslots.
I think the most precise way to do this would be for the I/O triggered PPS interrupt to then trigger a timer-based interrupt for the sub-millisecond slot (333/666ms), whose own ISR would then trigger the LoRa transmission, however I couldnt get my head around how to code this up with the Cortex M0 core on the feather.
Instead, I just use the main PPS interrupt to set a flag, then inside the main LOOP() function, keep the function steps small and skippable - so that if a PPS interrupt comes along, our code can skip right to sending the LoRa packet - utilising the perfectly adequate DELAY() function to achieve the inter-second slot timings, since interrupts are once again enabled outside the ISR itself.
Pseudocode below.
Inside the main program loop I perform a number of functions - get serial data from GPS, construct a NMEA sentence, parse into a GPS Object (to store current position, time etc), measure the battery level etc. So long as I can skip over each one, and am not tied up inside each function for any significant time, then we can keep our loop execution duration small and our jitter in potential slot timings low enough.
Is this adequate? For measurement processes I stick a simple digitalWrite(debugpin,HIGH) at the beginning of void loop(), and a matching digitalWrite(debugpin,LOW) at the end, then measure the repetition rate of a loop on a scope when feeding it a constant stream of NMEA data (to represent worst case scenario).
I measure a loop speed of ~128KHz - or a period of 7.8 microseconds, which is a conservative measure of the jitter in the timing of transmission slots when using this method. More than adequate for our purposes! We need to fit a 280ms packet inside a 333ms transmission window, so have many orders of magnitude better precision on our slot timing than is needed. We could easily drop to, say 140ms packet lengths (BW 250 SF 0 CR4/8, implicit), 150ms slots and have 400 devices transmitting once per minute and still have plenty of headroom. ( I suspect our receiver code might then start to struggle!)
This figure is with many additional debug steps and Serial.Print lines added, so the actual jitter will be much less.
Given that all devices will have the same code for the actual LoRa transmission. There will be a constant and consistent delay across all devices when actually triggering the LoRa transmission, so this will not affect our slot synchronisations.
void setup() { //determine our transmission slot based on our device ID //id1 transmits ON the 00 second of the minute //id2 transmits at 00 seconds+333ms //id3 transmits at 00 seconds + 666ms //id4 transmits at 01 seconds exactly //lets use ID5: slotSecond = 01; slotMillis = 333; //setup interrupt service routine and attach to a pin. Link io pin to GPS PPS output attachinterrupt(ppsPin,PPS_ISR()
} void loop() { //for timing the loop on oscilloscope: digitalWrite(debugpin,HIGH) //get a byte from serial port, skip if PPS flag is set if (ppsFlag==FALSE) { Serial.Read (1 byte)...etc } //parse a NMEA sentence - skip if PPS flag is set if (ppsFlag == FALSE and lastserialbyte=0x10) { //parse GPS Object to update position and current time etc skip if PPS flag is set //currentSecond = gps.time.timeseconds() } // read battery level every 10 minutes, skip if PPS flag is set if (ppsFlag==False and timesincelastbatterylevel>60000) { vin = AnalogRead(vbat); //etc } //construct LoRa packet ready for transmittion, skip if PPS flag is set if(ppsFlag==False) { dataPacket = deviceID+ gps.Latitude + gps.Longitude + vBat... } //finally, if our PPS flag IS set (via the ISR), and the FOLLOWING second matches our timeslot, then transmit //note that ON the PPS, the NMEA sentence with the newly lapsed second value will not have been received and parsed yet by the main loop, therefore the current second value held in RAM when the PPS is triggered is the PREVIOUS second value. We must therefore add 1 to our current second value to compare against our slot second number. Code below needs correcting for rollover from 59-00 if (ppsFlag==True && currentSecond==(slotSecond-1)) { //this is OUR timeslot. We are not in an ISR, so we can now use delay(ms) function to give adequate timing for the sub-second time slots. delay(slotMillis); //now trigger our transmission LoRa.Transmit(dataPacket); ppsFlag=False; } //write debugpin Low for timing digitalWrite(debugpin,LOW); }//end loop void PPS_ISR() { //our interrupt service routine, triggered once per second by the GPS PPS which will be common across all devices to within <100ns ppsFlag=True; }
-
Tracker unit Code
08/22/2016 at 18:40 • 0 commentsAdafruit Feather M0 LoRa is programmed using Arduino IDE
TinyGPS library talks to the Adafruit Ultimate GPS featherwing to retrieve position and current timestamp.
Pulse Per Second hardware output from GPS triggers an interrupt routine on the M0 to synchronise transmission slots across all devices.
Code outline:
On Interrupt:
- Set flag for 'transmit required=TRUE'
Setup:
- Configure LoRa and GPS serial
- Determine our timeslot for sending telemetry.
Await valid GPS fix before continuing
Loop:
- If 'transmit require' flag is set AND the current timestamp is within our timeslot
- transmit our packet.
- Else:
- update device location when bytes on serial from GPS are available
- construct a data packet ready for transmission
- Device ID - Voltage (from onboard resistor divider) - position - fix status
- get the current (number of seconds past the minute) from GPS
-
Tracker server software configuration
08/22/2016 at 16:36 • 0 commentsServer software:
traccar.org open source GPS tracking server
running modified 'traccar-web' UI to allow auto-login as guest on mobile devices:
traccar.litvak.su/installation.html
hostapd on Pi sets up the Wifi hotspot
dnsmasq on Raspberry pi acts as DNS and DHCP server for connected clients.
dnsmasq also catches all HTTP requests and redirects to localhost - end users just connect to the hostpot then browse to any (non-https) web address - good for catchy marketing! (e.g. hackadaytracker.com or any URL would work when connected to the Pi AP)
Apache2 on Pi gives some flexibility in redirecting http requests to localhost to our auto-logon guest URL
The off-the-shelf traccar.org tracking portal can accept our offline tilestache maps quite easily (need to define the custom map source in the Config section AND configure the user accounts to use the custom map source), but the litvak.su traccar-web mod required some butchering. See here for rough instructions (basically overwrite any reference to openstreetmap.org servers with your Pi tilestache server - editing the traccar.war file in ubuntu File Roller (archive manager) works quite well)
http://electron-tinker.blogspot.co.uk/2016/08/traccar-web-with-custom-offline-maps.html
Offline openstreetmap tiles are created with maperitive.com software and saved to the Pi
OSM tiles are served to Traccar viatilestache.org
Python code receives position and battery reports via the LoRa radio (For the HopeRf RFM98 lora module you have to adapt the DIO and SPI port definitions - instructions here:
http://electron-tinker.blogspot.co.uk/2016/08/raspberry-pi-lora-hoperf-python-code.html
, and reports to traccar software via OSMAND HTTP protocol (all done on loopback interface)
-
GPS tracker server
08/04/2016 at 21:29 • 0 commentsHopeRF RFM98W modem on uputronics shield is mounted on a Raspberry Pi.
Python code on the Pi adapted from https://github.com/mayeranalytics/pySX127x receives LoRa telemetry, formats and fowards it over loopback connection to GPS tracker server running on the same device.
Open-source GPS tracker software traccar.org or http://opengts.sourceforge.net/ receives the reconstructed GPS position reports and displays on webpage map interface.
Openstreetmap tiles or other custom map tiles are downloaded to the Pi ahead of time, and served on the Pi via Tilestache.org installed on the Pi. Map tiles in .mbtile format are created beforehand using maperitive.net and Openstreetmap data; overlayed with GPX trails of race route if required, and then stored on the Pi SD card.
The Raspberry pi is set up as a WiFi hotspot with DHCP and DNS, and serves the GPS map server to any connected devices (mobile phones, tablets) as a captive portal. This way no internet connection is needed at any point for tracking, logging or displaying positions on a map.
-
GPS tracker unit details
08/04/2016 at 21:21 • 0 commentsGPS tracker unit philosophy
Adafruit Feather M0 LoRa 433MHz board
Adafruit GPS featherwing board
small <1000mAh lipo, or possible power feed from bike torch battery (4x18650)
Code is based around Radiohead RFM95 library for handling (currently) one-way telemetry.
Code to be uploaded when ready. Current status of code is that it will transmit position, speed, heading, altitude, battery level and 'panic button' status back to the server every 30 seconds, with a 500ms transmission slot per device, synchronised to the GPS clock and PPS output to prevent collisions with other devices.
LoRa transmission parameters need to balance reception distance, update frequency, battery life etc with the fact that the device is likely to be moving, and the pseudo time-division multiple access (TDMA) regime for avoiding collisions.