Introduction
As a continuation of our series on Bluetooth beacons (part 1 here and part 2 here), today we cover Apple’s iBeacon standard in a bit more detail.
iBeacon is especially useful for deploying location-awareness applications and monitoring user behavior within an area. It was introduced in iOS 7 and utilizes Bluetooth Low Energy (BLE) to send out advertisements that get discovered and utilized by other BLE-capable devices (especially smartphones).
It’s an application layer implementation on top of BLE, so it requires awareness on both sides:
- Implementing the iBeacon format on a BLE device (normally battery-operated)
- Implementing awareness of the iBeacon format on another device capable of acting as a BLE scanner (normally a smartphone app)
The three main parts in an iBeacon advertising packet:
- UUID (16 bytes): specific to the app (e.g. one per manufacturer)
- Major (2 bytes): defines sub-region with UUID
- Minor (2 bytes): defines a smaller region within Major area
Here’s an example showing how this can be implemented in the field. The example is for a chain of international stores.
- UUID is common across all stores and defined in the app.
- Major identifier is assigned to each of the different store locations.
- Minor identifiers are assigned for regions within the stores
- UUID, Major, and Minor identifiers are not registered with Apple and are chosen at the developer/manufacturer discretion.
Keep in mind that iBeacon is not exclusive to iOS applications and can be implemented in any BLE scanner/app, including in Android applications and on devices such as a Raspberry Pi. All the app needs is awareness of the iBeacon BLE advertisement format, and Apple simply defined the format.
Here are some examples of how iBeacon can be utilized for location-based actions, specifically on iOS:
- If the mobile app defines only the UUID, then the app can detect whenever a user enters any of the stores (with the condition that the user has the app installed and has given it the appropriate permissions).
- If the mobile app registers a UUID + Major, then the app can detect whenever a user enters a specific store (with the condition that the user has the app installed and has given it the appropriate permissions).
- If the mobile app registers a UUID + Major + Minor, then the app can detect whenever a user enters a specific region within a specific user (with the condition that the user has the app installed and given the appropriate permissions).
- In all of the above cases, the app will also get notified when a user exits the defined region (any store, a specific store, or a region with a specific store).
These operations are referred to as “region monitoring”. In each case, the app may be implemented in a way such that the user is presented with relevant information to the region entered/exited.
iOS also supports the concept of “ranging”. This refers to the operation of estimating proximity to a beacon.
Apple details a calibration process that can be used to make the ranging more accurate. This involves measuring the RSSI of the advertising beacon at a constant distance of 1 meter at multiple positions, then applying the average measured RSSI value to the beacon’s transmit power. This is particularly useful during the deployment phase of the beacons.
One misconception about beacons is that they can track users. The beacon devices themselves only transmit packets and do not generally accept connections and incoming packets from a mobile device. This means that only the app can detect the user’s location and not the beacons themselves.
Having said that, though, if an app reports the region a user enters to a cloud server or records these locations locally, then a user’s privacy may be compromised. In the end, apps are generally required to disclose this collection of user data from the user.
Implementing iBeacon on nRF52 devices
To learn more about how to implement iBeacon devices, we’ll be using an example provided within the nRF5 SDK (version 16.0.0 in our case).
The different hardware and software requirements are:
- Segger Embedded Studio (SES)
- nRF5 SDK version 16.0.0 or later
- An nRF52 development board (I’m using the nRF52840 DK)
- nRF command-line tools
- A development PC for developing and flashing the development board
- A mobile phone running a BLE scanner app such as LightBlue or nRF Connect
The example we’ll be using is located at <nRF5 SDK Folder>/examples/ble_peripheral/ble_app_beacon/
. The example by default implements the following:
- iBeacon device
- Beacon UUID: 01122334-4556-6778-899A-ABBCCDDEEFF0
- Major: 0x0102
- Minor: 0x0304
- Advertising Interval: 100 msec
- Company Identifier: 0x0059 (Nordic Semiconductor)
- Beacon’s measured RSSI at a 1-meter distance in dBm: 0xC3
- Device Type: 0x02 (Beacon device)
- Total advertisement size: 0x17 == 23 bytes (which is the maximum advertisement packet size in BLE including the length value (2 bytes))
Notice that the device name is not included in the advertisement data. In this implementation, this is not necessary and would not possible since the advertisement data is already at maximum size.
Note: the example also implements an option for setting the Major and Minor values in the UICR (User information configuration registers) section. These registers are non-volatile memory (NVM) registers for configuring user-specific settings. This is especially helpful in cases where you need to program multiple devices with specific Major and Minor values without having to reprogram the devices each time those values are changed. Consider using this method for cases where you need to assign a number of beacons with specific Major and Minor values in preparation for deployment in the field.
For more information on the UICR section: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fuicr.html&cp=4_0_0_3_4
Of the parameters listed above that you will likely be interested in modifying the following to match your application needs:
- Advertising Interval
- UUID value
- Major value
- Minor value
- Company Identifier (to match your own Bluetooth assigned Company Identifier
- Measured RSSI (depending on your own measurements for your hardware)
iBeacon Implementation and Source Code
Let’s take a look at the different parts of the example source and better understand how the iBeacon example is implemented. We’ll take a look at only the parts of the code which interest us the most.
The source code file we’re interested in is main.c
.
main.c
Setting the Advertising Interval:
#define NON_CONNECTABLE_ADV_INTERVAL MSEC_TO_UNITS(100, UNIT_0_625_MS)
Setting the total iBeacon advertisement data length (including length value):
#define APP_BEACON_INFO_LENGTH 0x17
Setting the advertisement data/payload length:
#define APP_ADV_DATA_LENGTH 0x15
Setting the device type valute (beacon device type = 0x02):
#define APP_DEVICE_TYPE 0x02
The beacon measured RSSI value (at 1 meter, and in dBm):
#define APP_MEASURED_RSSI 0xC3
The Nordic Semiconductor company identifier:
#define APP_COMPANY_IDENTIFIER 0x0059
The Major value:
#define APP_MAJOR_VALUE 0x01, 0x02
The Minor value:
#define APP_MINOR_VALUE 0x03, 0x04
The beacon UUID:
#define APP_BEACON_UUID 0x01, 0x12, 0x23, 0x34, \ 0x45, 0x56, 0x67, 0x78, \ 0x89, 0x9a, 0xab, 0xbc, \ 0xcd, 0xde, 0xef, 0xf0
Defining the beacon info (based on the values defined above):
static uint8_t m_beacon_info[APP_BEACON_INFO_LENGTH] = /**< Information advertised by the Beacon. */ { APP_DEVICE_TYPE, // Manufacturer specific information. Specifies the device type in this // implementation. APP_ADV_DATA_LENGTH, // Manufacturer specific information. Specifies the length of the // manufacturer specific data in this implementation. APP_BEACON_UUID, // 128 bit UUID value. APP_MAJOR_VALUE, // Major arbitrary value that can be used to distinguish between Beacons. APP_MINOR_VALUE, // Minor arbitrary value that can be used to distinguish between Beacons. APP_MEASURED_RSSI // Manufacturer specific information. The Beacon's measured TX power in // this implementation. };
The advertising_init() function:
static void advertising_init(void) { uint32_t err_code; ble_advdata_t advdata; uint8_t flags = BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED; ble_advdata_manuf_data_t manuf_specific_data; manuf_specific_data.company_identifier = APP_COMPANY_IDENTIFIER; .... .... manuf_specific_data.data.p_data = (uint8_t *) m_beacon_info; manuf_specific_data.data.size = APP_BEACON_INFO_LENGTH; // Build and set advertising data. memset(&advdata, 0, sizeof(advdata)); advdata.name_type = BLE_ADVDATA_NO_NAME; advdata.flags = flags; advdata.p_manuf_specific_data = &manuf_specific_data; // Initialize advertising parameters (used when starting advertising). memset(&m_adv_params, 0, sizeof(m_adv_params)); m_adv_params.properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED; m_adv_params.p_peer_addr = NULL; // Undirected advertisement. m_adv_params.filter_policy = BLE_GAP_ADV_FP_ANY; m_adv_params.interval = NON_CONNECTABLE_ADV_INTERVAL; m_adv_params.duration = 0; // Never time out. err_code = ble_advdata_encode(&advdata, m_adv_data.adv_data.p_data, &m_adv_data.adv_data.len); APP_ERROR_CHECK(err_code); err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params); APP_ERROR_CHECK(err_code); }
The advertising_start() function:
static void advertising_start(void) { ret_code_t err_code; err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG); APP_ERROR_CHECK(err_code); err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING); APP_ERROR_CHECK(err_code); }
The main() function:
int main(void) { // Initialize. log_init(); timers_init(); leds_init(); power_management_init(); ble_stack_init(); advertising_init(); // Start execution. NRF_LOG_INFO("Beacon example started."); advertising_start(); // Enter main loop. for (;; ) { idle_state_handle(); } }
Let’s open the beacon example from the nRF5 SDK folder and compile it. To open this project, you can simply double click the file located at <nRF5 SDK Folder>/examples/ble_peripheral/ble_app_beacon/pca10056/s140/ses/ble_app_beacon_pca10056_s140.emProject
:



Once we’ve compiled the example successfully, we can flash it to our development board (but first make sure it is connected to your development PC). You can do so by navigating to the Target file menu, clicking “Connect J-Link“, then “Download ble_app_beacon_pca10056_s140“:

Once you have the example flashed and running on the board, you should see LED 1 on the board start flashing:

Now, you can open a BLE scanner app such as nRF Connect (for iOS or Android or Desktop) and you’ll be able to see the beacon and the advertising data.
Note: you can ignore the device name displayed as our device does not set a device name, and if a previously cached device name may be shown by the app.

As you can see, the app recognized and parsed all the advertisement data for us in a friendly readable format. Keep in mind that the app displays the Major and Minor values in decimal whereas we set them in hex format (Major = 0x0102 == 258, Minor = 0x0304 == 772).
Let’s go ahead and change some of the parameters to see if the changes get reflected in the app. We’ll go ahead and make the following changes:
- Change Major from 0x0102 to 0x1122 == 4386
- Change Minor version from 0x0304 to 0x3344 == 13124
In source code (main.c):
#define APP_MAJOR_VALUE 0x11, 0x22 #define APP_MINOR_VALUE 0x33, 0x44
Compile the application and re-flash it to the device. Once you’ve done so, rescan for BLE devices in the mobile app (I’m using nRF Connect for iOS here):

As you can see, the Major and Minor have been updated and match what we expected.
Flashing the Major and Minor values to UICR section
NOTE: As we mentioned previously, this method is very useful in cases where you want to modify the Major and Minor values for multiple devices (or the same device) without having to reprogram the device. This is especially useful in preparing a large number of beacons for deployment in the field.
As a final exercise, let’s try modifying the Major and Minor values by writing them to the UICR section. This method required the use of the nrfjprog
command-line tool.
But first, we need to enable this functionality in the code (just once).
Look for the function advertising_init()
and right above it, define the following macros:
#define USE_UICR_FOR_MAJ_MIN_VALUES
#define UICR_ADDRESS 0x10001080
#define MAJ_VAL_OFFSET_IN_BEACON_INFO 0x12
The address defined above is the first available “customer” address per the Nordic documentation (https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fuicr.html&cp=4_0_0_3_4).
The MAJ_VAL_OFFSET_IN_BEACON_INFO
value is the offset of the Major value within the m_beacon_info
data structure.
This will enable the following code section in advertising_init()
:
#if defined(USE_UICR_FOR_MAJ_MIN_VALUES) // If USE_UICR_FOR_MAJ_MIN_VALUES is defined, the major and minor values will be read from the // UICR instead of using the default values. The major and minor values obtained from the UICR // are encoded into advertising data in big endian order (MSB First). // To set the UICR used by this example to a desired value, write to the address 0x10001080 // using the nrfjprog tool. The command to be used is as follows. // nrfjprog --snr <Segger-chip-Serial-Number> --memwr 0x10001080 --val <your major/minor value> // For example, for a major value and minor value of 0xabcd and 0x0102 respectively, the // the following command should be used. // nrfjprog --snr <Segger-chip-Serial-Number> --memwr 0x10001080 --val 0xabcd0102 uint16_t major_value = ((*(uint32_t *)UICR_ADDRESS) & 0xFFFF0000) >> 16; uint16_t minor_value = ((*(uint32_t *)UICR_ADDRESS) & 0x0000FFFF); uint8_t index = MAJ_VAL_OFFSET_IN_BEACON_INFO; m_beacon_info[index++] = MSB_16(major_value); m_beacon_info[index++] = LSB_16(major_value); m_beacon_info[index++] = MSB_16(minor_value); m_beacon_info[index++] = LSB_16(minor_value); #endif
Once you compile and flash the updated application to your development board, you should see the Major and Minor values both equal 65535 (if you have previously not written to the UICR address specified above). This is because the application now reads the values from UICR instead of from the source code.

Now, let’s update the values by writing new ones (Major = 0x0001 and Minor = 0x0002) to the UICR address we defined (via nrfjprog
).
To do this, run the following command from a command-line terminal/shell prompt:
nrfjprog --memwr 0x10001080 --val 0x00010002
In this case, we didn’t specify the serial number of the Segger chip since we made sure we only had one development board connected. If you have multiple devices connected to your development PC, then you’d have to specify the serial number.
All we need now is to reset the development board and perform a new scan in the BLE scanner app:

As you can see, Major = 1 and Minor = 2, which matches what we set them as.
NOTE: You will have to erase the UICR area before writing again to update the values. You can learn more about how to do so here: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf5x_cltools%2FUG%2Fcltools%2Fnrf5x_nrfjprogexe_reference.html
Summary & Closing
In this tutorial, we covered Apple’s iBeacon standard in a bit more detail than in the previous two tutorials on beacons (part 1 & part 2).
We took a look at the details of the iBeacon advertising packet format, covered different use cases for different scenarios, and went over implementing an iBeacon example on the nRF52 platform.
In a future post, we will cover the Eddystone standard in a similar fashion.
Take your BLE knowledge to the next level
If you’re interested in staying up-to-date on the latest developments in BLE including an upcoming complete course on Beacon Development, then check out the Bluetooth Developer Academy.