I used to play quiz game with my friends, but when we needed to determine who was the first, who put his hand after a question arose, we get into disagreement. For this I was long thinking about a simple wireless system, what is able to decide who will be the responder, taking the responsibility (and our annoyance as well) out. This system should consist of one central device and any number of nodes, enhancing the network scalability, and the synchronization process should be simple and quick as well (capable to be done even for every new question), enabling simultaneous synchronization. This task may be done through existing solutions like Bluetooth or Wi-Fi, but I want accuracy in the order of microseconds, to be as precise as possible. RAIL time base is suitable for this, as it is in microseconds. It's not the best example to show the strength of synchronization, but I will cover here in this KBA some other examples.
This KBA presents a solution to synchronize networks containing one Master device and one or more Slave devices.
Application examples
There are many examples around us to everyday use of time synchronization including but not limited to the following.
GPS and other localization examples
Probably the most known application example of time synchronization is GPS (Global Positioning System). The synchronization is necessary to measure distance with wireless devices, as the distance is derived from the amount of time, since we know the velocity of light spreading through the air (vacuum). And if we can measure distance the next step is the positioning or localization.
Multihop network synchronization
If a computer connects to the internet, there is an opportunity right away to synchronize it to the global time (NTP). Since it is a multihop network, the delay from the reference time does increase, so the accuracy decrease with the number of hops.
Similarly, if the task is the scheduling a bunch of sensor nodes spanning a large geographical area it can be solved with time synchronization, where the nodes synchronize their neighbor nodes.
Low duty cycle
If you are not familiar with the term of Low duty cycle (LDC) take a look at this article.
This technology helps to save energy using lower power energy modes with a less accurate timer. There may be need for regular synchronization between the nodes, to minimize the active time along with the consumption, though this process handled automatically due to the communication.
The Theory of Operation
In the context of time synchronization it is good enough for the most application to have one reference time, which is known to all device, and in most cases this is the time of one particular device which dictates the system time. It gives the opportunity to schedule events in a common time base. We will refer to this device, which serves the reference time, as Master, and to the others as Slaves.
The synchronization means that one Slave device tries to estimate the Master's time, and then its operation scheduled in that assumed time base. We intentionally used the 'try' verb, because a synchronization is always inaccurate and requires regular adjustments. Though a simple synchronization can be done very quickly, the hardest part of the process is the evaluation of this inaccuracy, and the (sequential/continuous) correction, especially in a dense network, with a couple of Slave devices.
Time synchronization involves a several new terms which are describing delay times. Without completeness those are:
Warmup time (including channel access time): the time from assembling packet, through requesting the radio module to send, to the start of the first bit's transmission.
Transmission time: the time while the packet is on-the-air, from the first bit (end of Warmup time) to the last.
Propagation time: the time starting from related parts of the packet leave the transmitter and reach receiver (relative: doesn't have beginning nor end).
Reception time: similarly to the Transmission time, the time while the packet is on-the-air, till the end of the last bit of packet have been totally received.
Processing time: time to process received packet.
Settling time: time to set the time base at the receiver to the assumed time base of the transmitter (have not found this time in the literature, but the example application use this).
Using these delay terms and the terms describing difference of two clocking system the inaccuracy can be quantized.
Errors in/during/after synchronization
In terms of Time synchronization two types of error could occur. One is the offset error and the other is the dynamic or slope error (clock drift). Synchronization is nothing more than the elimination of these errors.
The offset error is very straightforward, it is the error measured between two devices right after the synchronization. This kind of error come from the incorrect calculation of delay times. It is a zero-order (constant) error.
The dynamic error is the speed difference between the clocks of the two devices. This is caused by the speed difference of the two oscillator. The dynamic error can be reduced by calibrating the clocking systems to a common reference (e.g. CTune calibration). The quality of this error is in first-order.
These errors might change during the lifetime of a system: as the temperature changes, the oscillators' frequencies change as well. This may be compensable with CTune calibration.
Measuring the errors
The task of synchronizing is very similar to a geometry exercise, finding a straight line in a coordinate system. This task requires a point and a slope or two points in a defined coordinate system. In this metaphor the coordinate system is defined by the "real time" and the Master's time base (assuming the Master device uses a 0ppm clock).
In the figures below the x axis represents the real time, and the y is the (discrete) value of time with the devices' metric (RAIL Time base, which is actually 1μs), the solid line represents the Master's time while the dotted line is the Slave's time.
Offset error compensation
Let's assume first that the slopes are the same (the clocks run at the same speed), therefore we need only a well defined point for the line fitting. With this assumption the offset error can be compensated in the following way:
Generate an event perceived by the master and slave devices
Get the event's timestamps on both side (~get to know the coordinate system)
Share Master's timestamp with the Slave(s) (~get to know the point on the coordinate system)
Compensate offset error on Slave(s)' side (~place the line on the coordinate system)
The task is to be able to figure out the what is the time on both the Master and the Slave at a given time, using the Slave's time base - It will be different, since they weren't powered up at the same time, thus the counters hold different values.
As a first step it should happen such an event which is perceived on both the Master and the Slave device. It is evident that this event may be a packet transmission and reception, therefore the delay times have to be known. As a result two timestamp generated on each device. Such an event will provide enough information to get the offset error. The clearest way to send a message from the Master, which is received by the Slave(s).
The next step is to share the Master's timestamp with the Slave device(s). It means that Master should send an another message, which holds the previous message's timestamp. From this message, the Slave(s) will know much its local clock differs from the Master's clock - the offset error - and will know more or less what time is it on the other at present (with the initial assumption).
Dynamic error compensation
In the real world there is always some difference between the clock systems. This is where the dynamic error takes place, and we should compensate it as well.
This can be done in a very similar way as we saw it in the previous section: we should repeat the process. This means getting an another point on the coordinate system, and from this information the to get the first-order error (the slope, or we can say that the velocity of the offset error's increase) can be defined and the dynamic error can be eliminated.
This process requires only 3 packet to be sent from a Master device, as the second packet, which holds the first packet's timestamp has it's own timestamp, which can be sent in the third packet.
Once these errors are known, they can be eliminated by calculation (software) or by modifying the clocking system (hardware) to be synchronized.
Implementation in RAIL
Possibilities
RAIL supports timestamping for both the transmitted and the received packet. The enum RAIL_PacketTimePosition_t defines the possibilities timestamp positions. The available positions are:
the start of the preamble (first preamble bit sent or received)
end of the syncword (last syncword bit sent or received),
end of the packet (last bit of the packet sent or received).
In fact, at TX side only the end of the packet will be recorded as timestamp, and the other timestamps calculated relatively from this value. This implies additive error due to dynamic error in case of a very long packet (though it is almost negligible). Furthermore this means that there is no way to timestamp the packet, then send out the timestamp in that same packet.
NOTE: This will change in the future on some devices!
Since we won't use long packets in such an application, and this difference from dynamic error would be eliminated with the offset error compensation, we chose the timestamp position to the end of syncword (though it does not have any benefit).
The synchronization process
This image shows the synchronization process:
The red part is the preamble and the syncword of the packet. The Slave must save the timestamp of first packet's end (T_s) as well as the Master (T_m). The difference between the two timestamp is the propagation time (T_prop). Then in the second packet the Master device should send T_m, which is in its own time base. Finally the Slave device receiving T_m will know the difference between its own and the Master's time base eliminating the offset error, whic is the first step of synchronization.
But then, after a while, the error between the time assumed by the Slave, and the real time according to the Master's time base will differ due to HFXO clock deviation. This can be reduced repeating the previous step and "predicting" the masters new T_m from the last known value. The difference of the actual and the predicted T_m shows whether the Slave clocks are slower or the Master's. That way the slope error can be estimated. The accuracy of the estimation improves with the increasing delay between the synchronizations.
One can add the question: what about the delay times? We did not care about most of them (ones have definite beginning and end), due to the timestamp system of RAIL eliminates them. We didn't care about the propagation time either, since it is less than 1 us (what is the resolution of our timer) if the distance between the two device is less than 300 m.
Explanation of the code
The application is based on the example "simple-rail-with-hal" for BRD4169B and BRD4164A radio boards (EFR32MG14/MG12) which was used with WSTK. The application is using the Command interpreter Plugin, thus "Virtual COM Port" has to be enabled in the hardware configurator. No other modification should be made in the radio and hardware configurator.
The application was extended with two additional packet: One from the Slave with an assumed value (T_predict) to the Master's second packet's timestamp, and with a compensation feedback (T_comp) from the Master, which tells the Salve how accurate the prediction was.
Caution: This is only a demonstration project, we want to show the synchronization process in the simplest way, so our implementation can be improved. Furthermore it contains features assisting debug. Here are some notes what you should avoid in your design.
Don't use the RAIL_SetTime() API in that way, as it is used here.
The blocking delay implementation is not recommended. A timer interrupt routine may be more elegant in your project.
Starting Master without Slave (or vice versa) will stuck while it won't get any packet. You should reset the device to exit this state.
States and roles
The application operates upon a state machine, equipped with command line interface, GPIO interrupt handlers for push buttons, and with radio event handling functionality. 5 state was implemented for both Master and Slave(s). Those are matching to each other. These are:
Waiting for start (pushbutton or CLI command),
Sending/receiving first packet from Master (produce timestamps T_m_1 and T_s_1),
Sending/receiving second packet from Master (produce T_m_2 and T_s_2 and send/get T_m_1),
Sending/receiving first packet from Slave (send T_predict for predicting T_m_2),
Sending/receiving third packet from Master (get T_comp).
A single application has been developed with two roles: there is a run-time variable to select whether a device is Master (default) or Slave.
cliSetRole selects between Master (0) and Slave (1) roles,
cliStart starts a synchronization process (if the argument is 1),
cliSetCtune sets the CTune value (second argument); if the first argument is 0 the second argument will be used as CTune, if 1 the second argument will be saved to the User Page first, if 2 the CTune value will be fetched from the User Page and the second argument will not take any effect,
cliGetTime prints the current time,
cliGetVal prints one of the timestamps or calculations (for debug use only).
The interrupt handler routines
The interrupt handling routines are mostly setting flags which are monitored in the main loop.
static volatile bool sent = false;
static volatile bool received = false;
static volatile bool start = false;
The left pushbutton (PB1) does exactly the same thing as cliStart command does, while the right (PB0) operates as cliGetTime.
The enabled radio events are the RX and TX related ones. Only for the successful events are handled in this application, RAIL_EVENT_TX_PACKET_SENT and RAIL_EVENT_RX_PACKET_RECEIVED. Every packet transmission and reception refreshes the timestamp value bound to the syncword's last bit.
static volatile RAIL_PacketTimeStamp_t timeStamp;
...
RAIL_Events_t events = (RAIL_EVENTS_TX_COMPLETION | RAIL_EVENTS_RX_COMPLETION);
RAIL_ConfigEvents(railHandle, events, events);
...
void RAILCb_Generic(RAIL_Handle_t railHandle, RAIL_Events_t events) {
if (events & RAIL_EVENT_TX_PACKET_SENT) {
// Set flag for the main loop
sent = true;
RAIL_TxPacketDetails_t packetDetails = { .timeSent = timeStamp .isAck = false };
RAIL_GetTxPacketDetails(railHandle, &packetDetails);
timeStamp = packetDetails.timeSent;
// Don't print in the event handler! We do that for demonstration only.
printf("packet sent\n");
}
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) {
// Set flag for the main loop
received = true;
// Get the newest packet's information from RX FIFO
RAIL_RxPacketInfo_t packetInfo;
RAIL_RxPacketHandle_t packetHandle = RAIL_RX_PACKET_HANDLE_NEWEST;
RAIL_GetRxPacketInfo(railHandle, packetHandle, &packetInfo);
// Set the position of the packet where the timestamp should be acquired,
// and the packet's length to calculate it correctly
timeStamp.timePosition = RAIL_PACKET_TIME_AT_PACKET_END;
timeStamp.totalPacketBytes = packetInfo.packetBytes;
RAIL_RxPacketDetails_t packetDetails = { .timeReceived = timeStamp };
RAIL_GetRxPacketDetails(railHandle, packetHandle, &packetDetails);
RAIL_CopyRxPacket(rxPacket, &packetInfo);
RAIL_ReleaseRxPacket(railHandle, packetHandle);
timeStamp = packetDetails.timeReceived;
RAIL_ResetFifo(railHandle, false, true);
// Don't print in the event handler! We do that for demonstration only.
printf("packet received %d\n", timeStamp.packetTime);
}
}
Manual calibration
In this section we will present how the application works.
After flashing the binary to two device we connected their serial ports to a PC. In the serial terminal we should only issue a setRole 1 command to select the slave device. Push the PB1 first on the slave (this will enable RX) and then on the master (send the first message). After some milliseconds the two device will be synchronized.
We can prove that the the devices are synchronized by getting the current time from both device in the same time. This can be done with pushing PB0, while the two GPIO lines are connected. The button's line can be found at the seventh pin of the external pin header connector on the WSTK (using the BRD4169B radio boards).
I pushed PB0 per ~1 second, and the two terminal windows printed the following timestamps (in microseconds):
The first time pair shows that the slave counts 3 more us than the master device, and in ~29 seconds the difference increased to 16 us. It means that the slave's clock ran faster causing 1 us drift per ~2.25 second (slope error).
Adjusting CTune (with the SetCtune command) this kind of error can be minimized (increasing on the slave to slow down, or decreasing on the master to speed up the oscillator).
This accuracy is now good enough for my quiz application.
The Connect stack supports OTA distribution of firmware images, both server, and client available as plugins. The usual method of uploading the image to the server device is to connect the J-Link adapter and upload the image the same way as in case of any other firmware images - which requires to stop the server device during the upload process and restart after the images are uploaded. However exploiting the capabilities of the Gecko bootloader, it is possible to upload images during normal operation. The following example implements an XMODEM protocol based uploader using built-in APIs.
Prerequisites
The described method and the provided method applicable for any Connect based application with any EFR32 device which supports Connect, this article uses Connect (SoC): Empty Example. This project also needs a properly configured Gecko bootloader which will provide the necessary API for the flash operations. In this example, we will use BRD4255A radio board. The user has to have a serial terminal emulator software that supports sending files using the XMODEM protocol.
Prepare the bootloader
There are multiple bootloaders available through the Simplicity Studio wizard - we need to use one which matches the flash memory size of the chosen device:
The default configuration of the bootloader is suitable for the current project, we only need to compile and upload to the device. Note: Gecko bootloader is a two stage bootloader always use the firmware image which contains -combined in its filename (bootloader-storage-internal-single-512k-combined.s37 in this case).
Prepare the application
Open the Connect (SoC): Empty Example through Simplicity Studio project wizard:
Select the Application bootloader in the AppBuilder on the HAL tab:
The complete example enables several plugins and options for working with CLI which are not detailed here as their description is out of the scope of this document, but the attached files configured properly. However, to be able to interact with the bootloader the following plugins should be enabled:
When the setting is done in AppBuilder and the files are generated, the next step is implementing the code in the flex-callbacks.c. Just as the settings in AppBuilder, the complete code is not described in this document, but further Connect documentation and resources can be found in Connect online documentation and Connect tutorial series.
The XMODEM protocol handling and the bootloader / flash memory related operations implemented in the xmodem-load-image.c and xmodem-load-image.h files (attached). These files should be added to the project (in the xmodem-load-image directory, but it can be placed anywhere but the header file must be in the include path).
The implementation contains only one public function: xmodemLoadImage(). This function handles both XMODEM upload and writing the incoming data chunks into the flash. The implementation follows the XMODEM protocol, but it does not support re-sending packets in case of failure. If any failure happens during upload, the whole process must be started from the beginning (including erasing the flash memory).
Completing the steps above, compile and run the firmware.
Note: do not use full erase on the device, just upload the application firmware, otherwise the bootloader may be erased as well.
Testing the application
If everything went well, a CLI interface should be available where the user can issue the necessary commands.
The first step is executing the flash initialization by issuing the command bootloader init. Initialization should be called once, when the application started / restarted, no subsequent initialization is necessary.
Image storage always has to be erased before uploading an image. This means, if an upload was unsuccessful the erase command must be executed prior to any further uploads.
If the initialization was successful and the image storage is erased, the image upload can be started by the xmodem-load-image command. It has no parameters, as the image filename has to be specified at the host (PC) side and the end of the upload process (i.e. the length of the image) is determined by the host as well.
So, issue the xmodem-load-image command and select the file to upload.
If the upload was successful, the following messages should appear on the console:
Proprietary Knowledge Base
Time synchronization with RAIL - Example project demonstration
Content
Motivation
I used to play quiz game with my friends, but when we needed to determine who was the first, who put his hand after a question arose, we get into disagreement. For this I was long thinking about a simple wireless system, what is able to decide who will be the responder, taking the responsibility (and our annoyance as well) out. This system should consist of one central device and any number of nodes, enhancing the network scalability, and the synchronization process should be simple and quick as well (capable to be done even for every new question), enabling simultaneous synchronization. This task may be done through existing solutions like Bluetooth or Wi-Fi, but I want accuracy in the order of microseconds, to be as precise as possible. RAIL time base is suitable for this, as it is in microseconds. It's not the best example to show the strength of synchronization, but I will cover here in this KBA some other examples.
This KBA presents a solution to synchronize networks containing one Master device and one or more Slave devices.
Application examples
There are many examples around us to everyday use of time synchronization including but not limited to the following.
GPS and other localization examples
Probably the most known application example of time synchronization is GPS (Global Positioning System). The synchronization is necessary to measure distance with wireless devices, as the distance is derived from the amount of time, since we know the velocity of light spreading through the air (vacuum). And if we can measure distance the next step is the positioning or localization.
Multihop network synchronization
If a computer connects to the internet, there is an opportunity right away to synchronize it to the global time (NTP). Since it is a multihop network, the delay from the reference time does increase, so the accuracy decrease with the number of hops.
Similarly, if the task is the scheduling a bunch of sensor nodes spanning a large geographical area it can be solved with time synchronization, where the nodes synchronize their neighbor nodes.
Low duty cycle
If you are not familiar with the term of Low duty cycle (LDC) take a look at this article.
This technology helps to save energy using lower power energy modes with a less accurate timer. There may be need for regular synchronization between the nodes, to minimize the active time along with the consumption, though this process handled automatically due to the communication.
The Theory of Operation
In the context of time synchronization it is good enough for the most application to have one reference time, which is known to all device, and in most cases this is the time of one particular device which dictates the system time. It gives the opportunity to schedule events in a common time base. We will refer to this device, which serves the reference time, as Master, and to the others as Slaves.
The synchronization means that one Slave device tries to estimate the Master's time, and then its operation scheduled in that assumed time base. We intentionally used the 'try' verb, because a synchronization is always inaccurate and requires regular adjustments. Though a simple synchronization can be done very quickly, the hardest part of the process is the evaluation of this inaccuracy, and the (sequential/continuous) correction, especially in a dense network, with a couple of Slave devices.
Time synchronization involves a several new terms which are describing delay times. Without completeness those are:
Using these delay terms and the terms describing difference of two clocking system the inaccuracy can be quantized.
Errors in/during/after synchronization
In terms of Time synchronization two types of error could occur. One is the offset error and the other is the dynamic or slope error (clock drift). Synchronization is nothing more than the elimination of these errors.
The offset error is very straightforward, it is the error measured between two devices right after the synchronization. This kind of error come from the incorrect calculation of delay times. It is a zero-order (constant) error.
The dynamic error is the speed difference between the clocks of the two devices. This is caused by the speed difference of the two oscillator. The dynamic error can be reduced by calibrating the clocking systems to a common reference (e.g. CTune calibration). The quality of this error is in first-order.
These errors might change during the lifetime of a system: as the temperature changes, the oscillators' frequencies change as well. This may be compensable with CTune calibration.
Measuring the errors
The task of synchronizing is very similar to a geometry exercise, finding a straight line in a coordinate system. This task requires a point and a slope or two points in a defined coordinate system. In this metaphor the coordinate system is defined by the "real time" and the Master's time base (assuming the Master device uses a 0ppm clock).
In the figures below the x axis represents the real time, and the y is the (discrete) value of time with the devices' metric (RAIL Time base, which is actually 1μs), the solid line represents the Master's time while the dotted line is the Slave's time.
Offset error compensation
Let's assume first that the slopes are the same (the clocks run at the same speed), therefore we need only a well defined point for the line fitting. With this assumption the offset error can be compensated in the following way:
The task is to be able to figure out the what is the time on both the Master and the Slave at a given time, using the Slave's time base - It will be different, since they weren't powered up at the same time, thus the counters hold different values.
As a first step it should happen such an event which is perceived on both the Master and the Slave device. It is evident that this event may be a packet transmission and reception, therefore the delay times have to be known. As a result two timestamp generated on each device. Such an event will provide enough information to get the offset error. The clearest way to send a message from the Master, which is received by the Slave(s).
The next step is to share the Master's timestamp with the Slave device(s). It means that Master should send an another message, which holds the previous message's timestamp. From this message, the Slave(s) will know much its local clock differs from the Master's clock - the offset error - and will know more or less what time is it on the other at present (with the initial assumption).
Dynamic error compensation
In the real world there is always some difference between the clock systems. This is where the dynamic error takes place, and we should compensate it as well.
This can be done in a very similar way as we saw it in the previous section: we should repeat the process. This means getting an another point on the coordinate system, and from this information the to get the first-order error (the slope, or we can say that the velocity of the offset error's increase) can be defined and the dynamic error can be eliminated.
This process requires only 3 packet to be sent from a Master device, as the second packet, which holds the first packet's timestamp has it's own timestamp, which can be sent in the third packet.
Once these errors are known, they can be eliminated by calculation (software) or by modifying the clocking system (hardware) to be synchronized.
Implementation in RAIL
Possibilities
RAIL supports timestamping for both the transmitted and the received packet. The enum
RAIL_PacketTimePosition_t
defines the possibilities timestamp positions. The available positions are:In fact, at TX side only the end of the packet will be recorded as timestamp, and the other timestamps calculated relatively from this value. This implies additive error due to dynamic error in case of a very long packet (though it is almost negligible). Furthermore this means that there is no way to timestamp the packet, then send out the timestamp in that same packet.
NOTE: This will change in the future on some devices!
Since we won't use long packets in such an application, and this difference from dynamic error would be eliminated with the offset error compensation, we chose the timestamp position to the end of syncword (though it does not have any benefit).
The synchronization process
This image shows the synchronization process:
The red part is the preamble and the syncword of the packet. The Slave must save the timestamp of first packet's end (T_s) as well as the Master (T_m). The difference between the two timestamp is the propagation time (T_prop). Then in the second packet the Master device should send T_m, which is in its own time base. Finally the Slave device receiving T_m will know the difference between its own and the Master's time base eliminating the offset error, whic is the first step of synchronization.
But then, after a while, the error between the time assumed by the Slave, and the real time according to the Master's time base will differ due to HFXO clock deviation. This can be reduced repeating the previous step and "predicting" the masters new T_m from the last known value. The difference of the actual and the predicted T_m shows whether the Slave clocks are slower or the Master's. That way the slope error can be estimated. The accuracy of the estimation improves with the increasing delay between the synchronizations.
One can add the question: what about the delay times? We did not care about most of them (ones have definite beginning and end), due to the timestamp system of RAIL eliminates them. We didn't care about the propagation time either, since it is less than 1 us (what is the resolution of our timer) if the distance between the two device is less than 300 m.
Explanation of the code
The application is based on the example "simple-rail-with-hal" for BRD4169B and BRD4164A radio boards (EFR32MG14/MG12) which was used with WSTK. The application is using the Command interpreter Plugin, thus "Virtual COM Port" has to be enabled in the hardware configurator. No other modification should be made in the radio and hardware configurator.
The application was extended with two additional packet: One from the Slave with an assumed value (T_predict) to the Master's second packet's timestamp, and with a compensation feedback (T_comp) from the Master, which tells the Salve how accurate the prediction was.
Caution: This is only a demonstration project, we want to show the synchronization process in the simplest way, so our implementation can be improved. Furthermore it contains features assisting debug. Here are some notes what you should avoid in your design.
RAIL_SetTime()
API in that way, as it is used here.States and roles
The application operates upon a state machine, equipped with command line interface, GPIO interrupt handlers for push buttons, and with radio event handling functionality. 5 state was implemented for both Master and Slave(s). Those are matching to each other. These are:
A single application has been developed with two roles: there is a run-time variable to select whether a device is Master (default) or Slave.
The CLI commands
The application implements 5 cli commands:
cliSetRole
selects between Master (0) and Slave (1) roles,cliStart
starts a synchronization process (if the argument is 1),cliSetCtune
sets the CTune value (second argument); if the first argument is 0 the second argument will be used as CTune, if 1 the second argument will be saved to the User Page first, if 2 the CTune value will be fetched from the User Page and the second argument will not take any effect,cliGetTime
prints the current time,cliGetVal
prints one of the timestamps or calculations (for debug use only).The interrupt handler routines
The interrupt handling routines are mostly setting flags which are monitored in the main loop.
The left pushbutton (PB1) does exactly the same thing as
cliStart
command does, while the right (PB0) operates ascliGetTime
.The enabled radio events are the RX and TX related ones. Only for the successful events are handled in this application,
RAIL_EVENT_TX_PACKET_SENT
andRAIL_EVENT_RX_PACKET_RECEIVED
. Every packet transmission and reception refreshes the timestamp value bound to the syncword's last bit.Manual calibration
In this section we will present how the application works.
After flashing the binary to two device we connected their serial ports to a PC. In the serial terminal we should only issue a
setRole 1
command to select the slave device. Push the PB1 first on the slave (this will enable RX) and then on the master (send the first message). After some milliseconds the two device will be synchronized.We can prove that the the devices are synchronized by getting the current time from both device in the same time. This can be done with pushing PB0, while the two GPIO lines are connected. The button's line can be found at the seventh pin of the external pin header connector on the WSTK (using the BRD4169B radio boards).
I pushed PB0 per ~1 second, and the two terminal windows printed the following timestamps (in microseconds):
The first time pair shows that the slave counts 3 more us than the master device, and in ~29 seconds the difference increased to 16 us. It means that the slave's clock ran faster causing 1 us drift per ~2.25 second (slope error).
Adjusting CTune (with the
SetCtune
command) this kind of error can be minimized (increasing on the slave to slow down, or decreasing on the master to speed up the oscillator).This accuracy is now good enough for my quiz application.
Connect: Upload firmware image for OTA distribution over XMODEM (UART)
Introduction
The Connect stack supports OTA distribution of firmware images, both server, and client available as plugins. The usual method of uploading the image to the server device is to connect the J-Link adapter and upload the image the same way as in case of any other firmware images - which requires to stop the server device during the upload process and restart after the images are uploaded. However exploiting the capabilities of the Gecko bootloader, it is possible to upload images during normal operation. The following example implements an XMODEM protocol based uploader using built-in APIs.
Prerequisites
The described method and the provided method applicable for any Connect based application with any EFR32 device which supports Connect, this article uses Connect (SoC): Empty Example. This project also needs a properly configured Gecko bootloader which will provide the necessary API for the flash operations. In this example, we will use BRD4255A radio board. The user has to have a serial terminal emulator software that supports sending files using the XMODEM protocol.
Prepare the bootloader
There are multiple bootloaders available through the Simplicity Studio wizard - we need to use one which matches the flash memory size of the chosen device:
The default configuration of the bootloader is suitable for the current project, we only need to compile and upload to the device. Note: Gecko bootloader is a two stage bootloader always use the firmware image which contains
-combined
in its filename (bootloader-storage-internal-single-512k-combined.s37
in this case).Prepare the application
Open the Connect (SoC): Empty Example through Simplicity Studio project wizard:
Select the Application bootloader in the AppBuilder on the HAL tab:
The complete example enables several plugins and options for working with CLI which are not detailed here as their description is out of the scope of this document, but the attached files configured properly. However, to be able to interact with the bootloader the following plugins should be enabled:
When the setting is done in AppBuilder and the files are generated, the next step is implementing the code in the
flex-callbacks.c
. Just as the settings in AppBuilder, the complete code is not described in this document, but further Connect documentation and resources can be found in Connect online documentation and Connect tutorial series.The XMODEM protocol handling and the bootloader / flash memory related operations implemented in the
xmodem-load-image.c
andxmodem-load-image.h
files (attached). These files should be added to the project (in thexmodem-load-image
directory, but it can be placed anywhere but the header file must be in the include path).The implementation contains only one public function:
xmodemLoadImage()
. This function handles both XMODEM upload and writing the incoming data chunks into the flash. The implementation follows the XMODEM protocol, but it does not support re-sending packets in case of failure. If any failure happens during upload, the whole process must be started from the beginning (including erasing the flash memory).Completing the steps above, compile and run the firmware.
Note: do not use full erase on the device, just upload the application firmware, otherwise the bootloader may be erased as well.
Testing the application
If everything went well, a CLI interface should be available where the user can issue the necessary commands.
The first step is executing the flash initialization by issuing the command
bootloader init
. Initialization should be called once, when the application started / restarted, no subsequent initialization is necessary.Then, if the image storage is not in the erased state, it must be erased by
bootloader flash-erase
:Image storage always has to be erased before uploading an image. This means, if an upload was unsuccessful the erase command must be executed prior to any further uploads.
If the initialization was successful and the image storage is erased, the image upload can be started by the
xmodem-load-image
command. It has no parameters, as the image filename has to be specified at the host (PC) side and the end of the upload process (i.e. the length of the image) is determined by the host as well.So, issue the
xmodem-load-image
command and select the file to upload.If the upload was successful, the following messages should appear on the console:
The XMODEM uploader does not check if the uploaded image is valid. Issue the
bootloader validate-image
command validates the image:Conclusion
Although, the Connect does not support uploading OTA images from application directly, the way to implement is fairly easy and straightforward.