FlockLab How-To's
How to assign node IDs
Chances are that you need a unique node ID for each target device. One way to get such an ID is by defining the symbol FLOCKLAB_NODE_ID
in your code:
// must be global (variable will be placed into the .data section)
uint16_t FLOCKLAB_NODE_ID = 0xbeef; // any value is ok, will be overwritten by FlockLab
FlockLab will then automatically change this value to the target ID (if specified, otherwise the observer ID is used) before uploading the image to the targets.
You can use objdump
to check whether the symbol is included in the binary:
objdump -t [elf_file] | grep FLOCKLAB_NODE_ID
If you want to assign a specific node ID to each target, you can do this by using the targetIds
field in the XML configuration file.
Note that assigning node IDs is only supported if you submit your target images in the file format ELF. For Intel hex files, binary patching is currently not supported.
Depending on the compiler you use, the data section in the ELF may be empty, and thus binary patching will fail (this is e.g. the case if you use the SEGGER compiler for the platform nRF5). As a workaround, you need to place the variable into the read-only memory section by making it constant. To avoid constant propagation and ensure the symbol is included in the binary file, FLOCKLAB_NODE_ID
should be placed into a separate C file and declared as extern
in all files where you want to access it:
// add this to a separate C file (constant will be placed into the .rodata section)
const uint16_t FLOCKLAB_NODE_ID = 0xbeef;
// in your code, declare the constant with the keyword extern:
extern const uint16_t FLOCKLAB_NODE_ID;
As an alternative to placing the constant into a separate C file, you can define it anywhere in your code (global scope) and access its value by dereferencing the address:
// must be global (constant will be placed into the .rodata section)
const uint16_t FLOCKLAB_NODE_ID = 0xbeef;
// access the node ID (conversion to volatile avoids constant propagation)
node_id = *(volatile const uint16_t*)&FLOCKLAB_NODE_ID;
How to assign node IDs with hex files
If you upload the target binary in Intel hex format, the node ID cannot be assigned by the testbed. Instead, you need to rely on unique identifiers in the information registers of the microcontroller and map those to useful node IDs.
An example for the target platform nRF5:
uint16_t flocklab_node_id(void)
{
const uint32_t dev_addr[] = {
0x9866f68a, 0xfe694776, 0x4e14e2f8, 0x8045ddde, 0xea673b1f, 0x546931a7, 0x4db62047, 0x38057982, /* observers 1 - 8 */
0x322c95bb, 0x05840339, 0x6251e878, 0xe29d4310, 0x3dbb14a0, 0, 0, 0xa9bf0f2b, /* observers 9 - 16 */
0, 0, 0x73d0188a, 0xae33933c, 0x183d13fe, 0xd3e8a7ab, 0x0b59d912, 0x054fead2, /* observers 17 - 24 */
0, 0x7f15a6a9, 0x069fcd53, 0, 0xa271b29d, 0, 0xb86f91c3, 0 /* observers 25 - 32 */
};
uint32_t i;
for (i = 0; i < sizeof(dev_addr) / sizeof(uint32_t); i++) {
if (dev_addr[i] == NRF_FICR->DEVICEADDR[0]) {
return i + 1;
}
}
return 0;
}
For the DPP2LoRa platform:
uint16_t flocklab_node_id(void)
{
const uint32_t dev_id[] = {
0x62003e, 0x2b0059, 0x220039, 0x570026, 0x68003e, 0x41003c, 0x290059, 0x460027, /* observers 1 - 8 */
0x230040, 0x380040, 0x310060, 0x370025, 0x270040, 0, 0x520040, 0x2b002a, /* observers 9 - 16 */
0x43003a, 0, 0x57002c, 0x550029, 0x5b0060, 0x220060, 0x2b003c, 0x2c0070, /* observers 17 - 24 */
0x67004b, 0x270060, 0x3c0040, 0x250040, 0x590026, 0x420035, 0x18003f, 0x4c004a /* observers 25 - 32 */
};
uint32_t i;
for (i = 0; i < sizeof(dev_id) / sizeof(uint32_t); i++) {
if (dev_id[i] == *((volatile uint32_t*)0x1fff7590)) {
return i + 1;
}
}
return 0;
}
Remark: even though we generally don't swap-out targets on the observers, you should always double-check the serial output to verify that the node ID mapping works as intended.
How to embed target image files into the XML config file
Before embedding the target image into the XML file, it must be encoded in base64 format. There are several tools available to do this. If you work on a Linux machine, you can use this script to convert and embed the image into the XML file:
./embed_image.sh [target_image] [xml_config_file]
How to assign a different target image to each observer
As documented on the XML configuration help page, it is possible to include several <targetConf>
blocks in the XML config file. Assigning a separate target image to an observer is straight forward as shown in the following example:
<targetConf>
<obsIds>2</obsIds>
<embeddedImageId>Image_1</embeddedImageId>
</targetConf>
<targetConf>
<obsIds>4 5 6</obsIds>
<embeddedImageId>Image_2</embeddedImageId>
</targetConf>
How to connect to and interact with a target via the serial port (UART) during the test
In order to use the serial proxy service, you need to include the element <remoteIp>
in the XML config. Note that the field can be left blank, in which case the server will automatically use the IP address of the client that uploads the test configuration (IPv4 required!). Once the test has started, you can connect to the target, e.g. via the command line tool ncat
:
ncat flocklab.ethz.ch 50110
The port number defines which observer you access (port number = 50100 + observer ID). In the example above, you would connect to observer 10.
Alternative command line tools for ncat
are e.g. nc
, netcat
or socat
.
How to forward a debug session to your IDE (including SWV / SWO features)
Live debugging of target devices with an ARM MCU is supported via a GDB server. In order to start a GDB server on a specific observer, you need to include the following lines in your XML config file:
<debugConf>
<obsIds>02</obsIds>
<gdbPort>2331</gdbPort>
</debugConf>
Note that the port must be 2331 due to firewall restrictions.
Once the test has started (and an additional delay of 10 seconds has elapsed), you can connect to the debug server from your IDE. For this, you need to adjust the debug configuration in your IDE: instead of connecting to the localhost, you need to enter the URL of the observer. The observer URLs are as follows:
observer IDs | URL |
---|---|
01 - 12 | fl-XX.ethz.ch |
15, 17, 25 | fl-XX.flocklab-dyn.ethz.ch |
The exact settings and steps necessary to connect to a remote GDB server depends on the IDE you are using.
The SWO output (SWV feature) is available on port 2332.
How to use the data trace service to detect a stack overflow
The data trace service is typically used to keep track of a the value of a variable. However, it can also be used to monitor larger memory areas for unwanted accesses, for example to detect a stack overflow. For this to work, you first need to determine how large the heap is (statically allocated memory in SRAM, the .bss
+ .data
sections). Let's assume the heap is less than 12kB in size and the SRAM starts at address 0x20000000
. If the program works as intended, there should never be a memory access to an address right after the heap (e.g. 0x20003000
). Only if something goes wrong (e.g. the stack grows too large), this memory location will be access / written to. In this example, we could configure the data trace service as follows:
<debugConf>
<obsIds>1</obsIds>
<cpuSpeed>48000000</cpuSpeed>
<dataTraceConf>
<variable>0x20003000</variable>
<mode>W PC</mode>
<size>16</size>
</dataTraceConf>
</debugConf>
The data trace service will then watch the memory region from 0x20003000
to 0x20003010
and report any write access to this area. Note that we could also monitor a larger area. However, the tracing address (variable
) must always be aligned to the size. If for example the heap ends at 0x20003001
and you want to monitor an area of 256 bytes, you will have to round up the address to 0x20003100
in order to be aligned to 256 bytes.