Wireless Connectivity Knowledge Base

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Wireless Connectivity Knowledge Base

Discussions

Sort by:
Introduction This document provides guidance to load a new software image in a KW35 device through OTAP (Over The Air Programming) bootloader for KW35. This article also provides the steps needed to download and install the SDK used in the tutorial. Software Requirements IAR Embedded Workbench IDE or MCUXpresso IDE. SDK MKW36A512xxx4 RC4 or further. Hardware Requirements MKW35A512xxx4 device. KW35 Flash Memory Used for the OTAP Software Deployment The KW35 Flash is partitioned into: 2x256 KB Program Flash (P-Flash) array divided into 2 KB sectors with a flash address range from 0x0000_0000 to 0x0007_FFFF.     The statements to comprehend how the OTAP Client software and his features works are: The OTAP Client software is split into two parts, the OTAP bootloader and the OTAP client service. The OTAP bootloader verifies if there is a new image already available to reprogram the device. The OTAP client service software provides the Bluetooth LE custom services needed to communicate with the server that contains the new image file. Therefore, before to start the test, the device has been programmed twice, first with the OTAP bootloader then with the OTAP client service project. The mechanism used to have two different software in the same device is to store each one in different memory regions and this is implemented by the linker file. In the KW35 device, the bootloader application has reserved a 16KB slot of memory starting from the 0x0 address (0x0 to 0x3FFF) thus, the left memory of the first P-Flash memory bank is reserved, among other things, by the OTAP client service application.   To create a new image file for the client device, the developer needs to specify to the linker file that the code will be stored with an offset of 16KB since the first addresses are reserved for the bootloader. At connection event, the server sends all the chunks of code to the client via Bluetooth LE. The client stores the code at the second P-Flash memory bank but is not able to run yet.   When the broadcast has finished, and all chunks were sent, the OTAP bootloader detects this situation and triggers a command to reprogram the device with the new application. Due the new application was built with an offset of 16KB, the OTAP bootloader program the device starting from the 0x3FFF address and the OTAP client service application is overwritten by the new image. Then the OTAP bootloader triggers the new application, starting the execution of the code.   Software Development Kit download and install   Go to MCUXpresso web page. Log in with your registered account. Search for “MKW36A” device. Then click on the suggested processor and click on “Build MCUXpresso SDK” The next page is displayed. Select “All toolchains” in the “Toolchain / IDE” combo box and provide the name to identify the package. Click on “Add software component”, then deploy the combo box and click on “Select All” option. Save the changes. Click on “Download SDK” button and accept the license agreement. If MCUXpresso IDE is used, drag and drop the SDK zip folder in “Installed SDK’s” perspective to install the package.     Preparing the software to test the OTAP for KW35 device using IAR Embedded Workbench   This section provides the steps needed to test the OTAP software on the KW35. Program the OTAP bootloader on the KW35. 1.1 Open the OTAP_bootloader project located at the following path: <SDK_download_root>\boards\virtual-board-kw35\wireless_examples\framework\bootloader_otap\bm\iar\bootloader_otap_bm.eww     1.2 Flash the project (Ctrl + D). Stop the debug session (Ctrl + Shift + D). Program the OTAP client application on the KW35.         2.1 Open the OTAP client project located in the path below.          <SDK_download_root>\boards\frdmkw36\wireless_examples\bluetooth\otac_att\freertos\iar\otac_att_freertos.eww          2.2 Follow the steps 2 to 12 described in the “4.1. Changes Required in Project Options and Settings” section of the AN12252 “Migration Guide from               MKW36Z512xxx4 to MKW35Z512xxx4” application note.            2.3 Open the app_preinclude.h file under the source directory in the workspace. Find the “gEepromType_d” definition and update the value to                                 “gEepromDevice_InternalFlash_c” as shown below.   #define gEepromType_d gEepromDevice_InternalFlash_c‍‍‍‍‍            2.4 Save the MKW35Z512xxx4_connectivity.icf file located at:                <SDK_download_root>\middleware\wireless\framework_5.4.4\Common\devices\MKW35Z4\iar                               Into the folder of the OTAP Client ATT project:                <SDK_download_root>\boards\frdmkw36\wireless_examples\bluetooth\otac_att\freertos\iar            2.5 Open the project options window (Alt+F7). In Linker/Config window click the icon next to linker path and select the linker configuration file “MKW35Z512xxx4_connectivity.icf”. Set the "gUseInternalStorageLink_d” flag to 1.              2.6 Click the OK button in the project options window to save the new configuration.          2.7 Flash the project (Ctrl + D). Stop the debug session (Ctrl + Shift + D).    Preparing the software to test the OTAP for KW35 device using MCUXpresso IDE   This section provides the steps needed to test the OTAP software on the KW35. Program the OTAP bootloader on the KW35.          1.1 Open MCUXpresso IDE. Click on “Import SDK example(s)” option in the “Quickstart Panel” view.                        1.2 Click on virtual-board-kw35 SDK icon.          1.3 Deploy the wireless_examples\framework\bootloader_otap folders and select bm project. Click Finish button.                                                                           1.4 Select “Debug” option in the Quickstart Panel. Once the project is already loaded on the device, stop the debug session.      2. Program the OTAP client application on the KW35.          2.1 Open MCUXpresso IDE. Click on “Import SDK example(s)” option in the “Quickstart Panel” view.                          2.2 Click twice on the frdmkw36 icon.                                                                            2.3 Type “otac_att” in the examples textbox and select the freertos project at wireless_examples\bluetooth\otac_att\freertos. Finally, click on Finish button.              2.4 Follow the steps 5 to 17 described in the “5.1. Changes Required in Project Options and Settings” section of the AN12252 “Migration Guide from MKW36Z512xxx4 to MKW35Z512xxx4” application note.            2.5. Open the app_preinclude.h file under the source directory in the workspace. Find the “gEepromType_d” definition and update the value to                “gEepromDevice_InternalFlash_c” as shown below. #define gEepromType_d gEepromDevice_InternalFlash_c‍‍‍‍‍            2.6 Save the MKW35Z512xxx4_connectivity.ld file located at:                <SDK_download_root>\middleware\wireless\framework_5.4.4\Common\devices\MKW35Z4\gcc                Into the source folder in the workspace.              2.7 Open the Project/Properties window. Next, go to the MCU Linker/Managed Linker Script perspective and edit the Linker Script name to “MKW35Z512xxx4_connectivity.ld”.              2.8 Go to MCU Linker/Miscellaneous view. Press the icon below, a new window will be deployed. Add the following definition in the “Other options” box: --defsym=gUseInternalStorageLink_d=1.              2.9 Click the “Apply and Close” button in the project options window to save the new configuration.          2.10 Select “Debug” option in the Quickstart Panel. Once the project is already loaded on the device, stop the debug session.   Running OTAP demo with the IoT Toolbox App Save the S-Record file created with the steps in Appendix A or Appendix B in your smartphone at a known location. Open the IoT Toolbox App and select OTAP demo. Press “SCAN” to start scanning for a suitable advertiser. Perform a falling edge on the PTB18 in the KW35 to start advertising. Create a connection with the founded device. Press “Open” and search the S-Record file. Press “Upload” to start the transfer. Once the transfer is complete, wait a few seconds until the bootloader has finished programming the new image. The new application will start automatically.    Appendix A. Creating an S-Record image file for KW35 client using IAR Embedded Workbench Open the connectivity project that you want to program using the OTAP bootloader from your SDK. This example will make use of the glucose sensor project. <SDK_download_root>\boards\frdmkw36\wireless_examples\bluetooth\glucose_s\freertos\iar\glucose_s_freertos.eww Follow the steps 2 to 12 described in the “4.1. Changes Required in Project Options and Settings” section of the AN12252 “Migration Guide from              MKW36Z512xxx4 to MKW35Z512xxx4” application note. Save the MKW35Z512xxx4_connectivity.icf file located at: <SDK_download_root>\middleware\wireless\framework_5.4.4\Common\devices\MKW35Z4\iar                In the containing folder of your project. <SDK_download_root>\boards\frdmkw36\wireless_examples\bluetooth\glucose_s\freertos\iar Open the project options window (Alt+F7). In Linker/Config window click the icon next to linker path and select the linker configuration file MKW35Z512xxx4_connectivity.icf. Then, enable “gUseBootloaderLink_d” macro in the “Configuration file symbol definitions” textbox. Go to the “Output Converter” window. Deselect the “Override default" checkbox, expand the “Output format” combo box and select Motorola S-records format. Click OK button.                                                                                                                                           Rebuild the project. Search the S-Record file in the following path: <SDK_download_root>\boards\frdmkw36\wireless_examples\bluetooth\glucose_s\freertos\iar\debug   Appendix B. Creating an S-Record image file for KW35 client using MCUXpresso IDE Open the connectivity project that you want to program using the OTAP bootloader from MCUXpresso IDE This example will make use of the glucose sensor project Follow the steps 5 to 17 described in the “5.1. Changes Required in Project Options and Settings” section of the AN12252 “Migration Guide from MKW36Z512xxx4 to MKW35Z512xxx4” application note. Save the MKW35Z512xxx4_connectivity.ld file located at: <SDK_download_root>\middleware\wireless\framework_5.4.4\Common\devices\MKW35Z4\gcc Into the source folder in the workspace.                                                                                                                  Open the Project/Properties window. Next, go to the MCU Linker/Managed Linker Script perspective and edit the Linker Script name to “MKW35Z512xxx4_connectivity.ld”.                                                                                  Go to MCU Linker/Miscellaneous view. Press the icon below, a new window will be deployed. Add the following definition in the “Other options” box: --defsym=gUseBootloaderLink_d=1. Click the “Apply and Close” button.                              Build the project. Deploy the “Binaries” icon in the workspace. Click the right mouse button on the “.axf” file. Select “Binary Utilities/Create S-Record” option. The S-Record file will be saved at “Debug” folder in the workspace with “.s19” extension.  
View full article
Introduction When a Bluetooth LE Central and Peripheral devices are in connection, data within the payload can be encrypted. Encryption of the channel can be achieved through pairing with others. Once the communication has been encrypted, the Bluetooth LE devices could distribute the keys to save it for future connections. The last is better known as bonding. When two Bluetooth LE devices are bonded, in a future connection, they do not need to exchange the keys since they already know the shared secret, thus, they can encrypt the channel directly, saving time and power. However, if an attacker is listening to the first time that both (Central and Peripheral) Bluetooth LE devices enter into a connection state, the security of the link could be vulnerated, since the attacker could decipher the original message. Fortunately, Out Of Band (OOB) provides the ability (obviously, if both devices support it) to share the keys on an unknown medium for an attacker listening Bluetooth LE (for instance, NFC, SPI, UART, CAN, etc), increasing the security of the communication. This document explains how to enable OOB pairing on Bluetooth LE connectivity examples, basing on FRDM-KW36 SDK HID Host and HID Device examples.   Dedicated Macros and APIs for OOB Pairing The connectivity software stack contains macros and APIs that developers should implement to interact with the host stack and handle the events necessary for OOB. The following sections explain the main macros, variables, and APIs that manage OOB in our software.   Definitions and Variables gAppUsePairing_d It is used to enable or disable pairing to encrypt the link. Values Result 0 Pairing Disabled 1 Pairing Enabled   gAppUseBonding_d It is used to enable or disable bonding to request and save the keys for future connections. Values Result 0 Bonding Disabled 1 Bonding Enabled   gBleLeScOobHasMitmProtection_c This flag must be set if the application requires Man In the Middle protection, in other words, if the link must be authenticated. You can determine whether your software needs to set or clear this flag from the GAP Security Mode and Level. Red instances of the following table indicate that gBleLeScOobHasMitmProtection_c must be set to 1.   gPairingParameters This struct contains the pairing request or the pairing response (depending on the device's GAP role) payload. To enable and configure OOB pairing, oobAvailable field of the struct must be set to 1.   APIs bleResult_t Gap_ProvideOob (deviceId_t deviceId, uint8_t* aOob) This API must be implemented in response of gConnEvtOobRequest_c event in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions (depending of the GAP role). This event only will be triggered if OOB is enabled and LE Legacy pairing is used. The gConnEvtOobRequest_c event occurs when the stack request the OOB data received from the peer device just after the gConnEvtPairingRequest_c or gConnEvtPairingResponse_c (depending of the GAP role). This API is valid only for LE Legacy pairing. Name of the Parameter Input/Output Description deviceId Input ID of the peer device aOob Input Pointer to OOB data previously received from the peer.   bleResult_t Gap_LeScGetLocalOobData (void) This API must be implemented either in response of gConnEvtPairingRequest_c or gConnEvtPairingResponse_c events  in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions (depending of the GAP role) to get the local OOB data generated from the controller and in response of gLeScPublicKeyRegenerated_c event at BleConnManager_GenericEvent. Each time that Gap_LeScGetLocalOobData is executed in the application to obtain the OOB data, it triggers the gLeScLocalOobData_c generic event to inform that OOB data must be read from pGenericEvent->eventData.localOobData to send it to the peer device. This API is valid only for LE Secure Connections pairing.   bleResult_t Gap_LeScSetPeerOobData (deviceId_t deviceId, gapLeScOobData_t* pPeerOobData) This API must be implemented in response of gConnEvtLeScOobDataRequest_c event in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions(depending of the GAP role). This event occurs when the stack requires the OOB data previously recieved from the peer. This API is valid only for LE Secure Connections pairing. Name of the Parameter Input/Output Description deviceId Input ID of the peer device aOob Input Pointer to gapLeScOobData_t struct that contains the OOB data received from the peer.   Enabling OOB on KW36 Bluetooth LE Peripheral Device The following example is based on the HID Device software included in the FRDM-KW36 SDK. It explains the minimum code needed to enable OOB. In the following sections, brown color indicates that such definition or API takes part in the stack and violet color indicates that such definition does not take part in the stack and its use is only for explanation purposes in this document.   Changes in app_preinclude.h file The app_preinclude.h header file contains definitions for the management of the application. To enable OOB pairing, you must ensure that gAppUseBonding_d and gAppUsePairing_d are set to 1. You can also set the value of the gBleLeScOobHasMitmProtection_c in this file, depending on the security mode and level needed in your application.  This example makes use of two custom definitions: gAppUseOob_d and gAppUseSecureConnections_d. Such definitions are used to explain how to enable/disable OOB and, if OOB is enabled, how to switch between LE Secure Connections pairing or LE Legacy paring.   /*! Enable/disable use of bonding capability */ #define gAppUseBonding_d 1 /*! Enable/disable use of pairing procedure */ #define gAppUsePairing_d 1 /*! Enable/disable use of privacy */ #define gAppUsePrivacy_d 0 #define gPasskeyValue_c 999999 /*! Enable/disable use of OOB pairing */ #define gAppUseOob_d 1 /*! Enable MITM protection when using OOB pairing */ #if (gAppUseOob_d) #define gBleLeScOobHasMitmProtection_c TRUE #endif /*! Enable/disable Secure Connections */ #define gAppUseSecureConnections_d 1‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Using the code above, you can enable or disable OOB using gAppUseOob_d, also you can decide whether to use LE Secure Connections (gAppUseSecureConnections_d = 1) or LE Legacy (gAppUseSecureConnections_d = 0)     Changes in app_config.c file The following portion fo code depicts how to fill gPairingParameters struct depending on which pairing method is used by the application.   /* SMP Data */ gapPairingParameters_t gPairingParameters = { .withBonding = (bool_t)gAppUseBonding_d, /* If Secure Connections pairing is supported, then set Security Mode 1 Level 4 */ /* If Legacy pairing is supported, then set Security Mode 1 Level 3 */ #if (gAppUseSecureConnections_d) .securityModeAndLevel = gSecurityMode_1_Level_4_c, #else .securityModeAndLevel = gSecurityMode_1_Level_3_c, #endif .maxEncryptionKeySize = mcEncryptionKeySize_c, .localIoCapabilities = gIoKeyboardDisplay_c, /* OOB Available enabled when app_preinclude.h file gAppUseOob_d macro is true */ .oobAvailable = (bool_t)gAppUseOob_d, #if (gAppUseSecureConnections_d) .centralKeys = (gapSmpKeyFlags_t) (gIrk_c), .peripheralKeys = (gapSmpKeyFlags_t) (gIrk_c), #else .centralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c), .peripheralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c), #endif /* Secure Connections enabled when app_preinclude.h file gAppUseSecureConnections_d macro is true */ .leSecureConnectionSupported = (bool_t)gAppUseSecureConnections_d, .useKeypressNotifications = FALSE, };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Additionally, the serviceSecurity struct registers which are the security mode and level of each Bluetooth LE service, so if Secure Connections is selected (gAppUseSecureConnections_d = 1), mode = 1 level = 4.   static const gapServiceSecurityRequirements_t serviceSecurity[3] = { { .requirements = { #if (gAppUseSecureConnections_d) .securityModeLevel = gSecurityMode_1_Level_4_c, #else .securityModeLevel = gSecurityMode_1_Level_3_c, #endif .authorization = FALSE, .minimumEncryptionKeySize = gDefaultEncryptionKeySize_d }, .serviceHandle = (uint16_t)service_hid }, { .requirements = { #if (gAppUseSecureConnections_d) .securityModeLevel = gSecurityMode_1_Level_4_c, #else .securityModeLevel = gSecurityMode_1_Level_3_c, #endif .authorization = FALSE, .minimumEncryptionKeySize = gDefaultEncryptionKeySize_d }, .serviceHandle = (uint16_t)service_battery }, { .requirements = { #if (gAppUseSecureConnections_d) .securityModeLevel = gSecurityMode_1_Level_4_c, #else .securityModeLevel = gSecurityMode_1_Level_3_c, #endif .authorization = FALSE, .minimumEncryptionKeySize = gDefaultEncryptionKeySize_d }, .serviceHandle = (uint16_t)service_device_info } };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     Changes in ble_conn_manager.c file LE Legacy Pairing If your application will use LE Legacy Pairing, then you have to implement Gap_ProvideOob in response to the gConnEvtOobRequest_c event at the BleConnManager_GapPeripheralEvent function. In this example, gOobReceivedTKDataFromPeer is an array that stores the data previously received OOB from the peer device (using SPI, UART, I2C, etc), therefore, the procedure to fill this array with the data received from the peer depends entirely on your application. Notice that gOobReceivedTKDataFromPeer must contain the data received from the peer before to execute Gap_ProvideOob.   static uint8_t gOobReceivedTKDataFromPeer[16]; void BleConnManager_GapPeripheralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) { switch (pConnectionEvent->eventType) { case gConnEvtConnected_c: { ... ... ... } break; ... ... ... #if (gAppUseOob_d && !gAppUseSecureConnections_d) case gConnEvtOobRequest_c: { /* The stack has requested the LE Legacy OOB data*/ (void)Gap_ProvideOob(peerDeviceId, &gOobReceivedTKDataFromPeer[0]); } break; #endif ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     LE Secure Connections Pairing When using Secure Connections Pairing, the application must handle two events at the BleConnManager_GapPeripheralEvent function. In gConnEvtPairingRequest_c event, you must implement Gap_LeScGetLocalOobData API to generate the local (r, Cr) values. The gConnEvtLeScOobDataRequest_c event indicates that the application is requesting the (r, Cr) values previously received OOB from the peer device (using SPI, UART, I2C, etc). Such values are contained into gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers. You must implement Gap_LeScSetPeerOobData in response to gConnEvtLeScOobDataRequest_c, This function has two parameters, the device ID of the peer and a pointer to a gapLeScOobData_t type struct. This struct is filled with the data contained in gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers.   gapLeScOobData_t gPeerOobData; static uint8_t gOobReceivedRandomValueFromPeer[gSmpLeScRandomValueSize_c]; /*!< LE SC OOB r (Random value) */ static uint8_t gOobReceivedConfirmValueFromPeer[gSmpLeScRandomConfirmValueSize_c]; /*!< LE SC OOB Cr (Random Confirm value) */ void BleConnManager_GapPeripheralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) { switch (pConnectionEvent->eventType) { case gConnEvtConnected_c: { ... ... ... } break; case gConnEvtPairingRequest_c: { #if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U)) gPairingParameters.centralKeys = pConnectionEvent->eventData.pairingEvent.centralKeys; (void)Gap_AcceptPairingRequest(peerDeviceId, &gPairingParameters); #if (gAppUseOob_d && gAppUseSecureConnections_d) /* The central has requested pairing, get local LE Secure Connections OOB data */ (void)Gap_LeScGetLocalOobData(); #endif #else (void)Gap_RejectPairing(peerDeviceId, gPairingNotSupported_c); #endif } break; ... ... ... #if (gAppUseOob_d && gAppUseSecureConnections_d) case gConnEvtLeScOobDataRequest_c: { /* The stack has requested the peer LE Secure Connections OOB data. Fill the gPeerOobData struct and provide it to the stack */ FLib_MemCpy(gPeerOobData.randomValue, &gOobReceivedRandomValueFromPeer[0], gSmpLeScRandomValueSize_c); FLib_MemCpy(gPeerOobData.confirmValue, &gOobReceivedConfirmValueFromPeer[0], gSmpLeScRandomConfirmValueSize_c); Gap_LeScSetPeerOobData(peerDeviceId, &gPeerOobData); } break; #endif ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   The gLeScPublicKeyRegenerated_c event in the BleConnManager_GenericEvent function must be handled using the Gap_LeScGetLocalOobData API as depicted below. Each time that Gap_LeScGetLocalOobData is executed by the software, it generates, asynchronously, the gLeScLocalOobData_c event (also handled in the BleConnManager_GenericEvent function) indicating that the local (r, Cr) values were successfully generated and you can read them using the pGenericEvent->eventData.localOobData pointer to send it OOB to the peer device. In this example, Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer  are custom synchronous functions that demonstrate how you can implement a custom API that sends the local (r, Cr) read from pGenericEvent->eventData.localOobData pointer to the peer device using other protocols (SPI, UART, I2C, etc).   void BleConnManager_GenericEvent(gapGenericEvent_t* pGenericEvent) { switch (pGenericEvent->eventType) { case gInitializationComplete_c: { ... ... ... } break; ... ... ... #if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U)) case gLeScPublicKeyRegenerated_c: { /* Key pair regenerated -> reset pairing counters */ mFailedPairings = mSuccessfulPairings = 0; /* Local Secure Connections OOB data must be refreshed whenever this event occurs */ #if (gAppUseOob_d && gAppUseSecureConnections_d) (void)Gap_LeScGetLocalOobData(); #endif } break; #endif ... ... ... #if (gAppUseOob_d && gAppUseSecureConnections_d) case gLeScLocalOobData_c: { /* Get the local Secure Connections OOB data and send to the peer */ Oob_SendLocalRandomValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.randomValue); Oob_SendLocalConfirmValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.confirmValue); } break; #endif ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     Enabling OOB on KW36 Bluetooth LE Central Device The following example is based on the HID Host software included in the FRDM-KW36 SDK. It explains the minimum code needed to enable OOB. In the following sections, brown color indicates that such definition or API takes part in the stack and violet color indicates that such definition does not take part in the stack and its use is only for explanation purposes in this document.   Changes in app_preinclude.h file The app_preinclude.h header file contains definitions for the management of the application. To enable OOB pairing, you must ensure that gAppUseBonding_d and gAppUsePairing_d are set to 1. You can also set the value of the gBleLeScOobHasMitmProtection_c in this file, depending on the security mode and level needed in your application.  This example makes use of two custom definitions: gAppUseOob_d and gAppUseSecureConnections_d. Such definitions are used to explain how to enable/disable OOB and, if OOB is enabled, how to switch between LE Secure Connections pairing or LE Legacy paring.   /*! Enable/disable use of bonding capability */ #define gAppUseBonding_d 1 /*! Enable/disable use of pairing procedure */ #define gAppUsePairing_d 1 /*! Enable/disable use of privacy */ #define gAppUsePrivacy_d 0 #define gPasskeyValue_c 999999 /*! Enable/disable use of OOB pairing */ #define gAppUseOob_d 1 /*! Enable MITM protection when using OOB pairing */ #if (gAppUseOob_d) #define gBleLeScOobHasMitmProtection_c TRUE #endif /*! Enable/disable Secure Connections */ #define gAppUseSecureConnections_d 1‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Using the code above, you can enable or disable OOB using gAppUseOob_d, also you can decide whether to use LE Secure Connections (gAppUseSecureConnections_d = 1) or LE Legacy (gAppUseSecureConnections_d = 0)     Changes in app_config.c file The following portion fo code depicts how to fill gPairingParameters struct depending on which pairing method is used by the application.   /* SMP Data */ gapPairingParameters_t gPairingParameters = { .withBonding = (bool_t)gAppUseBonding_d, /* If Secure Connections pairing is supported, then set Security Mode 1 Level 4 */ /* If Legacy pairing is supported, then set Security Mode 1 Level 3 */ #if (gAppUseSecureConnections_d) .securityModeAndLevel = gSecurityMode_1_Level_4_c, #else .securityModeAndLevel = gSecurityMode_1_Level_3_c, #endif .maxEncryptionKeySize = mcEncryptionKeySize_c, .localIoCapabilities = gIoKeyboardDisplay_c, /* OOB Available enabled when app_preinclude.h file gAppUseOob_d macro is true */ .oobAvailable = (bool_t)gAppUseOob_d, #if (gAppUseSecureConnections_d) .centralKeys = (gapSmpKeyFlags_t) (gIrk_c), .peripheralKeys = (gapSmpKeyFlags_t) (gIrk_c), #else .centralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c), .peripheralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c), #endif /* Secure Connections enabled when app_preinclude.h file gAppUseSecureConnections_d macro is true */ .leSecureConnectionSupported = (bool_t)gAppUseSecureConnections_d, .useKeypressNotifications = FALSE, };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     Changes in ble_conn_manager.c file LE Legacy Pairing If your application will use LE Legacy Pairing, then you have to implement Gap_ProvideOob in response to the gConnEvtOobRequest_c event at the BleConnManager_GapCentralEvent function. In this example, gOobOwnTKData is an array that stores the TK data which will be sent OOB to the peer device (using SPI, UART, I2C, etc)  and, at the same time, is the TK data that will be provided to the stack using Gap_ProvideOob. This data must be common on both Central and Peripheral devices, so the procedure to share the TK depends entirely on your application. Oob_SendLocalTKValueToPeer is a custom synchronous function that demonstrates how you can implement a custom API that sends the local TK to the peer device using other protocols (SPI, UART, I2C, etc).   static uint8_t gOobOwnTKData[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; void BleConnManager_GapCentralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) { switch (pConnectionEvent->eventType) { case gConnEvtConnected_c: { ... ... ... } break; ... ... ... case gConnEvtPairingResponse_c: { /* Send Legacy OOB data to the peer */ #if (gAppUseOob_d & !gAppUseSecureConnections_d) Oob_SendLocalTKValueToPeer(&gOobOwnTKData[0]); #endif } break; ... ... ... #if (gAppUseOob_d && !gAppUseSecureConnections_d) case gConnEvtOobRequest_c: { /* The stack has requested the LE Legacy OOB data*/ (void)Gap_ProvideOob(peerDeviceId, &gOobOwnTKData[0]); } break; #endif‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     LE Secure Connections Pairing When using Secure Connections Pairing, the application must handle two events at the BleConnManager_GapCentralEvent function. In gConnEvtPairingResponse_c event, you must implement Gap_LeScGetLocalOobData API to generate the local (r, Cr) values. The gConnEvtLeScOobDataRequest_c event indicates that the application is requesting the (r, Cr) values previously received OOB from the peer device (using SPI, UART, I2C, etc). Such values are contained into gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers. You must implement Gap_LeScSetPeerOobData in response to gConnEvtLeScOobDataRequest_c, This function has two parameters, the device ID of the peer and a pointer to a gapLeScOobData_t type struct. This struct is filled with the data contained in gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers.   gapLeScOobData_t gPeerOobData; static uint8_t gOobReceivedRandomValueFromPeer[gSmpLeScRandomValueSize_c]; /*!< LE SC OOB r (Random value) */ static uint8_t gOobReceivedConfirmValueFromPeer[gSmpLeScRandomConfirmValueSize_c]; /*!< LE SC OOB Cr (Random Confirm value) */ void BleConnManager_GapCentralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) { switch (pConnectionEvent->eventType) { case gConnEvtConnected_c: { ... ... ... } break; ... ... ... case gConnEvtPairingResponse_c: { /* The peripheral has acepted pairing, get local LE Secure Connections OOB data */ #if (gAppUseOob_d && gAppUseSecureConnections_d) (void)Gap_LeScGetLocalOobData(); #endif } break; ... ... ... #if (gAppUseOob_d && gAppUseSecureConnections_d) case gConnEvtLeScOobDataRequest_c: { /* The stack has requested the peer LE Secure Connections OOB data. Fill the gPeerOobData struct and provide it to the stack */ FLib_MemCpy(gPeerOobData.randomValue, &gOobReceivedRandomValueFromPeer[0], gSmpLeScRandomValueSize_c); FLib_MemCpy(gPeerOobData.confirmValue, &gOobReceivedConfirmValueFromPeer[0], gSmpLeScRandomConfirmValueSize_c); Gap_LeScSetPeerOobData(peerDeviceId, &gPeerOobData); } break; #endif ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   The gLeScPublicKeyRegenerated_c event in the BleConnManager_GenericEvent function must be handled using the Gap_LeScGetLocalOobData API as depicted below. Each time that Gap_LeScGetLocalOobData is executed by the software, it generates, asynchronously, the gLeScLocalOobData_c event (also handled in the BleConnManager_GenericEvent function) indicating that the local (r, Cr) values were successfully generated and you can read them using the pGenericEvent->eventData.localOobData pointer to send it OOB to the peer device. In this example, Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer  are custom synchronous functions that demonstrate how you can implement a custom API that sends the local (r, Cr) read from pGenericEvent->eventData.localOobData pointer to the peer device using other protocols (SPI, UART, I2C, etc).   void BleConnManager_GenericEvent(gapGenericEvent_t* pGenericEvent) { switch (pGenericEvent->eventType) { case gInitializationComplete_c: { ... ... ... } break; ... ... ... #if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U)) case gLeScPublicKeyRegenerated_c: { /* Key pair regenerated -> reset pairing counters */ mFailedPairings = mSuccessfulPairings = 0; /* Local LE Secure Connections OOB data must be refreshed whenever this event occurs */ #if (gAppUseOob_d && gAppUseSecureConnections_d) (void)Gap_LeScGetLocalOobData(); #endif } break; #endif ... ... ... #if (gAppUseOob_d && gAppUseSecureConnections_d) case gLeScLocalOobData_c: { /* Get the local LE Secure Connections OOB data and send to the peer */ Oob_SendLocalRandomValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.randomValue); Oob_SendLocalConfirmValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.confirmValue); } break; #endif ... ... ... } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     Simplified Flow Diagram of OOB Central and Peripheral Events LE Legacy Pairing The following figure shows a simplified flow diagram of the LE Legacy OOB pairing example in this document. The LE Central device is the one that contains the OOB TK data that will be shared OOB using the custom Oob_SendLocalTKValueToPeer function. It must be implemented at the gConnEvtPairingResponse_c event to ensure that both devices know the OOB TK before to execute Gap_ProvideOob since this function requests this data. If the OOB data is correct on both sides, the pairing procedure ends, and it is noticed through gConnEvtPairingComplete_c. LE Secure Connections Pairing The following figure shows a simplified flow diagram of the LE Secure Connections OOB pairing example in this document. After both devices enter in connection, the data that will be shared OOB using the custom Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer  functions is yielded by Gap_LeScGetLocalOobData on both sides. The last one must be implemented at gConnEvtPairingResponse_c and gConnEvtPairingRequest_c events to ensure that both devices know the Peripheral and Central (r, Cr) OOB data before to execute Gap_LeScSetPeerOobData since this function requests this data. If the OOB data is correct on both sides, the pairing procedure ends, and it is noticed through gConnEvtPairingComplete_c. This is how OOB pairing can be implemented in your project. I hope this document will be useful to you. Please, let us know any questions or comments. 
View full article
Introduction This document guides to load a new software image in a KW41 device through Over The Air Programming bootloader. Also, are explained the details of how to set up the client software to change the storage method of the image. Software Requirements IAR Embedded Workbench IDE or MCUXpresso IDE Download both, SDK FRDM-KW41Z and SDK USB-KW41Z. Hardware Requirements FRDM-KW41Z board OTAP Memory Management During the Update Process The KW41 has a 512KB Program Flash with a flash address range from 0x0000_0000 to 0x0007_FFFF.     The OTAP application splits the flash into two independent parts, the OTAP Bootloader, and the OTAP Client. The OTAP Bootloader verifies if there is a new image available at the OTAP Client to reprogram the device. The OTAP Client software provides the Bluetooth LE custom service needed to communicate the OTAP Client device with the OTAP Server that contains the new image file (The OTAP Server device could be another FRDM-KW41Z connected to a PC with Test Tool or a Smartphone with IoT Toolbox app). Therefore, the OTAP Client device needs to be programmed twice, first with the OTAP Bootloader, then with the Bluetooth LE application supporting OTAP Client. The mechanism created to have two different software coexisting in the same device is storing each one in different memory regions. This functionality is implemented by the linker file. In the KW41 device, the bootloader has reserved a 16 KB slot of memory from 0x0000_0000 to 0x0003_FFFF, thus the left memory is reserved among other things, by the OTAP Client demo. To create a new image file for the client device, the developer needs to specify to the linker file that the code will be built with an offset of 16 KB since the first addresses must be reserved for the OTAP Bootloader. In connection state, the OTAP server sends the image packets (known as chunks) to the OTAP Client device via Bluetooth LE. The OTAP Client device can store these chunks, in first instance, at the external SPI flash or the On-Chip Flash. The destination of the code is selectable in the OTAP Client software. When the connection has finished and all chunks were sent from the OTAP Server to the OTAP Client device, the OTAP Client software writes information, such as the source of the image update (external flash or internal flash) in a portion of memory known as Bootloader Flags and then resets the MCU to execute the OTAP Bootloader code. The OTAP Bootloader reads the Bootloader Flags to get the information needed to program the device and triggers a commando to reprogram the MCU with the new application. Due to the new application was built with an offset of 16 KB, the OTAP Bootloader programs the device starting from the 0x0000_4000 address and the OTAP Client application is overwritten by the new image, therefore, after the device has been reprogrammed through this method, cannot be programmed a second time as same. Finally, the OTAP Bootloader triggers a command to start the execution of the new code automatically.     Preparing the Software to Test the OTAP Client for KW41Z Device Using IAR Embedded Workbench Program the OTAP Bootloader on the FRDM-KW41Z. Program the OTAP Bootloader software from the project included in the SDK FRDM-KW41Z at the following path, or you can simply drag and drop the pre-built binary from the following path.           OTAP Bootloader Project:          <SDK_2.2.0_FRDM-KW41Z_download_path>\boards\frdmkw41z\wireless_examples\framework\bootloader_otap\bm\iar\bootloader_otap_bm.eww            OTAP Bootloader pre-built binary:            <SDK_2.2.0_FRDM-KW41Z_download_path>\tools\wireless\binaries\bootloader_otap_frdmkw41z.bin   Open the OTAP Client project included in the SDK FRDM-KW41Z located in the following path.          <SDK_2.2.0_FRDM-KW41Z_download_path>\boards\frdmkw41z\wireless_examples\bluetooth\otap_client_att\freertos\iar\otap_client_att_freertos.eww   Customize the OTAP Client software to select the storage method. Locate the app_preinclude.h header file inside the source folder at the workspace. To select the External Flash storage method, set the "gEepromType_d" define to "gEepromDevice_AT45DB041E_c"                      To select the Internal Flash storage method, set the "gEepromType_d" define to "gEepromDevice_InternalFlash_c"   Configure the linker flags. Open the project options window (Alt + F7). In "Linker->Config" window, locate the "Configuration file symbol definitions" pane. To select the External Flash storage method, remove the "gUseInternalStorageLink_d=1" linker flag To select the Internal Flash storage method, add the "gUseInternalStorageLink_d=1" linker flag     Load the OTAP Client software on the FRDM-KW41Z board (Ctrl + D). Stop the debug session (Ctrl + Shift + D). The default linker configurations of the project allow the OTAP Client application to be stored with the proper memory offset.   Preparing the Software to Test the OTAP Client for KW41Z Device Using MCUXpresso IDE Program the OTAP Bootloader on the FRDM-KW41Z. Program the OTAP Bootloader software from the project included in the SDK FRDM-KW41Z at the following path, or you can simply drag and drop the pre-built binary from the following path.           OTAP Bootloader Project:          wireless_examples->framework->bootloader_otap->bm            OTAP Bootloader pre-built binary:            <SDK_2.2.0_FRDM-KW41Z_download_path>\tools\wireless\binaries\bootloader_otap_frdmkw41z.bin   Click on "Import SDK examples(s)" option in the "Quickstart Panel" view. Click twice on the frdmkw41z icon.     Open the OTAP Client project included in the SDK FRDM-KW41Z located in the following path.wireless_examples->bluetooth->otap_client_att->freertos     Customize the OTAP Client software to select the storage method. Locate the app_preinclude.h header file inside the source folder at the workspace. To select the External Flash storage method, set the "gEepromType_d" define to "gEepromDevice_AT45DB041E_c"                      To select the Internal Flash storage method, set the "gEepromType_d" define to "gEepromDevice_InternalFlash_c"   Configure the linker file. To select the External Flash storage method, are not required any changes in the project from this point. You can skip this step. To select the Internal Flash storage method, search the linker file located in the SDK USB-KW41Z at the following path and replace instead of the default linker file at the source folder in the OTAP Client project. You can copy (Ctrl + C) the linker file from SDK USB-KW41Z and paste (Ctrl + V) on the workspace directly. A warning message will be displayed, select "Overwrite".           Linker file at the SDK USB-KW41Z:        <SDK_2.2.0_USB-KW41Z_download_path>\boards\usbkw41z_kw41z\wireless_examples\bluetooth\otap_client_att\freertos\MKW41Z512xxx4_connectivity.ld     Save the changes in the project. Select "Debug" in the "Quickstart Panel". Once the project is already loaded on the device, stop the debug session.   Creating an S-Record Image File for FRDM-KW41Z OTAP Client in IAR Embedded Workbench Open the connectivity project that you want to program using the OTAP Bootloader from your SDK FRDM-KW41Z. This example will make use of the glucose sensor project, this is located at the following path. <SDK_2.2.0_FRDM-KW41Z_download_path>\boards\frdmkw41z\wireless_examples\bluetooth\glucose_sensor\freertos\iar\glucose_sensor_freertos.eww   Open the project options window (Alt+F7). In Linker->Config window, add the following linker flag in the “Configuration file symbol definitions” textbox.         gUseBootloaderLink_d=1     Go to the “Output Converter” window. Deselect the “Override default" checkbox, expand the “Output format” combo box and select Motorola S-records format. Click the OK button.     Rebuild the project. Search the S-Record file (.srec) in the following path.<SDK_2.2.0_FRDM-KW41Z_download_path>\boards\frdmkw41z\wireless_examples\bluetooth\glucose_sensor\freertos\iar\debug   Creating an S-Record Image File for FRDM-KW41Z OTAP Client in MCUXpresso IDE Open the connectivity project that you want to program using the OTAP Bootloader from MCUXpresso IDE. This example will make use of the glucose sensor project, this is located at the following path.        wireless_examples->bluetooth->glucose_sensor->freertos   Search the linker file located in the SDK FRDM-KW41Z at the path below and replace instead of the default linker file at the source folder in the Glucose Sensor project. You can copy (Ctrl + C) the linker file from SDK FRDM-KW41Z and paste (Ctrl + V) on the workspace directly. A warning message will be displayed, select "Overwrite".          Linker file at the SDK FRDM-KW41Z:        <SDK_2.2.0_FRDM-KW41Z_download_path>\boards\frdmkw41z\wireless_examples\bluetooth\otap_client_att\freertos\MKW41Z512xxx4_connectivity.ld     Open the new "MKW41Z512xxx4_connectivity.ld" linker file. Locate the section placement of the figure below and remove the "FILL" and the "BYTE" statements.         Build the project. Deploy the “Binaries” icon in the workspace. Click the right mouse button on the “.axf” file. Select the “Binary Utilities/Create S-Record” option. The S-Record file will be saved at “Debug” folder in the workspace with “.s19” extension.     Testing OTAP Client Demo Using IoT Toolbox App Save the S-Record file created with the steps in the last section in your smartphone at a known location. Open the IoT Toolbox App and select OTAP demo. Press “SCAN” to start scanning for a suitable advertiser. Press the “SW4” button on the FRDM-KW41Z board to start advertising. Create a connection with the found device. Press “Open” and search the S-Record file. Press “Upload” to start the transfer. Once the transfer is complete, wait a few seconds until the bootloader has finished programming the new image. The new application will start automatically. 
View full article
Hello everyone, Over The Air Programming (OTAP) NXP's custom Bluetooth LE service provides the developer a solution to upgrade the software that the MCU contains. It removes the need for cables and a physical link between the OTAP client (the device that is reprogrammed) and the OTAP server (the device that contains the software update). This post explains how to run the OTAP Client Software that comes within the FRDM-KW36 package: Reprogramming a KW36 device using the OTAP Client Software. As it is mentioned in the last post, the OTAP Client can reprogram the KW36 while it is running, with new software using Bluetooth LE. However, this implementation for most of the applications is not enough since once you have reprogrammed the new image, the KW36 can not be reprogramed a second time using this method. For these applications that require to be updated many times using Bluetooth LE during run-time, we have created the following application note, that comes with a functional example of how to implement the OTAP Client software, taking advantage of this service. You can download the software clicking on the link in blue and the documentation is in the link in green. Please visit the following link: DOCUMENTS and Application Notes for KW36 In the "DOCUMENTS" section, you can found more information of the KW36. In the "Application Note" section, you can found more software and documentation of interesting topics like this.        Best Regards.
View full article
Introduction The FRDM-KW36 includes an RSIM (Radio System Integration Module) module with an external 32 MHz crystal oscillator. This clock source reference is mainly intended to supply the Bluetooth LE Radio peripheral, but it can be used as the main clock source of the MCU as well. This oscillator includes a set of programmable capacitors to support crystals with different load capacitance needs. Changing the value of these capacitors can modify the frequency the oscillator provides, that way, the central frequency can be tuned to meet the wireless protocol standards. This configurable capacitance range is from C1: 5.7pF - C2: 7.1pF to C1: 22.6pF - C2: 28.2pF and it is configured through the BB_XTAL_TRIM field at the ANA_TRIM. The KW36 comes preprogrammed with a default load capacitance value. However, since there is variance in devices due to tolerances and parasite effects, the correct load capacitance should be checked by verifying that the optimal central frequency is attained.  You will need a spectrum analyzer to measure the central frequency. To find the most accurate value for the load capacitance, it is recommended to use the Connectivity Test demo application. Adjusting Frequency Example Program the KW36 Connectivity Test software on the device. This example can be found in wireless_examples -> genfsk -> conn_test folder from your SDK package. Baremetal and FreeRTOS versions are available. In case that FRDM-KW36 board is being used to perform the test, you should move the 10pF capacitor populated in C55 to C57, to direct the RF signal on the SMA connector. Connect the board to a serial terminal software. When you start the application, you will be greeted by the NXP logo screen:  Press the enter key to start the test. Then press "1" to select "Continuous tests": Finally, select "6" to start a continuous unmodulated RF test. At this point, you should be able to measure the signal in the spectrum analyzer. You can change the RF channel from 0 to 127 ("q" Ch+ and "w" Ch- keys), which represents the bandwidth from 2.360GHz to 2.487GHz, stepping of 1MHz between two consecutive channels. To demonstrate the trimming procedure, this document will make use of channel 42 (2.402GHz) which corresponds to the Bluetooth LE channel 37. In this case, with the default capacitance value, our oscillator is not exactly placed at the center of the 2.402GHz, instead, it is slightly deflected to 2.40200155 GHz, as depicted in the following figure: The capacitance can be adjusted with the "d" XtalTrim+ and "f" XtalTrim- keys. Increasing the capacitance bank means a lower frequency. In our case, we need to increase the capacitance to decrease the frequency. The nearest frequency of 2.402 GHz was 2.40199940 GHz  Once the appropriate XTAL trim value has been found, it can be programmed as default in any Bluetooth LE example, changing the mXtalTrimDefault constant located in the board.c file: static const uint8_t mXtalTrimDefault‍ = 0x36;‍‍‍
View full article
Introduction In some applications, is it necessary to keep updated the software running in many MCU's that take part in the system, fortunately, Over The Air Programming, it's a custom Bluetooth LE service developed to send "over the air" software updates for the KW MCU series. FRDM-KW36 SDK already provides the "otap_client" software, that can be used together with the "otap_bootloader" such as it is described in the following community post: Reprogramming a KW36 device using the OTAP Client Software to reprogram the KW36. This example can be modified to store code for another MCU and later send the software update to this device as depicted in the figure below. This post guides you on modifying the OTAP client software to support software updates for other MCU's. Preparing the OTAP client software The starting point of the following modifications is supposing that there is no need to perform over the air updates for the KW36 MCU, so the use of the "otap_bootloader" is obsolete and will be removed in this example. In other words, KW36 will be programmed only with the "otap_client" code. Open the MCUXpresso settings window (Project->Properties->"C/C++ Build->MCU settings") and configure the following fields. Save the changes. For external storage: For internal storage: Locate the "app_preinclude.h" file, and set the storage method, as follows: For external storage: #define gEepromType_d       gEepromDevice_AT45DB041E_c For internal storage: #define gEepromType_d        gEepromDevice_InternalFlash_c Locate the "main_text_section.ldt" linker script into the "linkscripts" folder, and delete it from the project.  Search in the project for "OTA_SetNewImageFlag();" and "ResetMCU();" functions in the "otap_client.c" file (source->common->otap_client->otap_client.c) and delete or comment. (For reference, there are 4 in total). Locate the following code in "OtaSupport.h" (framework->OtaSupport->Interface) and delete or comment. extern uint16_t gBootFlagsSectorBitNo;‍‍‍‍‍‍ void OTA_SetNewImageFlag(void);‍‍‍‍‍‍‍ Locate the following code in "OtaSupport.c" (framework->OtaSupport->Source) and delete or comment. extern uint32_t __BootFlags_Start__[]; #define gBootImageFlagsAddress_c ((uint32_t)__BootFlags_Start__)‍‍‍‍‍‍‍‍‍‍‍‍ #if !gEnableOTAServer_d || (gEnableOTAServer_d && gUpgradeImageOnCurrentDevice_d) /*! Variables used by the Bootloader */ #if defined(__IAR_SYSTEMS_ICC__) #pragma location = "BootloaderFlags" const bootInfo_t gBootFlags = #elif defined(__GNUC__) const bootInfo_t gBootFlags __attribute__ ((section(".BootloaderFlags"))) = #elif defined(__CC_ARM) volatile const bootInfo_t gBootFlags __attribute__ ((section(".BootloaderFlags"))) = #else #error "Compiler unknown!" #endif { {gBootFlagUnprogrammed_c}, {gBootValueForTRUE_c}, {0x00, 0x02}, {gBootFlagUnprogrammed_c}, #if defined(CPU_K32W032S1M2VPJ_cm4) && (CPU_K32W032S1M2VPJ_cm4 == 1) {PLACEHOLDER_SBKEK}, {BOOT_MAGIC_WORD} #endif }; #endif /* !gEnableOTAServer_d || (gEnableOTAServer_d && gUpgradeImageOnCurrentDevice_d) */‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ uint16_t gBootFlagsSectorBitNo; gBootFlagsSectorBitNo = gBootImageFlagsAddress_c/(uint32_t)((uint8_t*)FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE);‍‍‍‍ gBootFlagsSectorBitNo = gBootImageFlagsAddress_c/(uint32_t)((uint8_t*)FSL_FEATURE_FLASH_PAGE_SIZE_BYTES);‍‍‍‍ void OTA_SetNewImageFlag(void) { #if (gEepromType_d != gEepromDevice_None_c) && (!gEnableOTAServer_d || (gEnableOTAServer_d && gUpgradeImageOnCurrentDevice_d)) /* OTA image successfully written into the non-volatile storage. Set the boot flag to trigger the Bootloader at the next CPU Reset. */ union{ uint32_t value; uint8_t aValue[FSL_FEATURE_FLASH_PFLASH_BLOCK_WRITE_UNIT_SIZE]; }bootFlag; #if defined(CPU_K32W032S1M2VPJ_cm4) && (CPU_K32W032S1M2VPJ_cm4 == 1) uint8_t defaultSBKEK[SBKEK_SIZE] = {DEFAULT_DEMO_SBKEK}; #endif uint32_t status; if( mNewImageReady ) { NV_Init(); bootFlag.value = gBootValueForTRUE_c; status = NV_FlashProgramUnaligned((uint32_t)&gBootFlags.newBootImageAvailable, sizeof(bootFlag), bootFlag.aValue); if( (status == kStatus_FLASH_Success) && FLib_MemCmpToVal(gBootFlags.internalStorageAddr, 0xFF, sizeof(gBootFlags.internalStorageAddr)) ) { bootFlag.value = gEepromParams_StartOffset_c + gBootData_ImageLength_Offset_c; status = NV_FlashProgramUnaligned((uint32_t)&gBootFlags.internalStorageAddr, sizeof(bootFlag), bootFlag.aValue); } #if defined(CPU_K32W032S1M2VPJ_cm4) && (CPU_K32W032S1M2VPJ_cm4 == 1) if( status == kStatus_FLASH_Success ) { /* Write the default SBKEK for secured OTA */ status = NV_FlashProgramUnaligned((uint32_t)&gBootFlags.sbkek, SBKEK_SIZE, defaultSBKEK); } #endif if( status == kStatus_FLASH_Success ) { mNewImageReady = FALSE; } } #endif }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   At this point, the FRDM-KW36 can receive and store any image for any MCU and can request a further software update from the OTAP server device.    Adding API's to reprogram the "MCU X" on OTAP client software Once the software update has been downloaded from the OTAP Server into the OTAP Client, the developer should request the software update from the OTAP Client to the "MCU X" through a serial protocol such as UART, SPI, CAN, etc. You should develop the API's and the protocol according to the requirements for your system to send the software update to the "MCU X" (as well as the bootloader for the MCU X). The handling your protocol can be integrated into the OTAP client code replacing "ResetMCU()" (The same code removed in step 4) in the code by "APISendSoftwareUpdateToMCUX()" for instance, since at this point the image was successfully sent over the air and stored in the memory of the KW36. 
View full article
Introduction The FRDM-KW36 includes an RTC module with a 32 kHz crystal oscillator. This module generates a 32 kHz clock source for the MCU whilst running on very low power mode. This oscillator includes a set of programmable capacitors used as the C LOAD . Changing the value of these capacitors can modify the frequency the oscillator provides. This configurable capacitance ranges from 0 pF (capacitor bank disabled) to 30 pF in steps of 2 pF. These values are obtained by combining the enabled capacitors. The values available are 2 pF, 4 pF, 8 pF, and 16 pF. Any combination of these four can be done. It is recommended that these internal capacitors are disabled if the external capacitors are available (clearing SC2P, SC4P, SCS8, and SC16 bits in RTC Control Register SFR). To adjust the frequency provided by the oscillator, you must first be able to measure the frequency. Using a frequency counter would be ideal, as it provides a more precise measurement than an oscilloscope. You will also need to output the oscillator frequency. To output the oscillator frequency, using any of the Bluetooth demo applications as an example, you should do the following: Adjusting Frequency Example This example will make use of the Heart Rate Sensor demo (freertos version) from the Connectivity Software Stack and assumes that the developer has the knowledge of import or open projects from the SDK to IDE. Open or clone the Heart Rate Sensor project from your SDK. Find the board.c and board.h files in the board folder at the workspace.                                                                                Declare a void function on the board.h file as shown below. This function will be in order to mux the RTC clock out to the PTB3 and be able to measure the frequency.  /* Function to mux PTB3 to RTC_CLKOUT */ void BOARD_EnableRtcClkOut (void);‍‍ Develop the BOARD_EnableRtcClkOut function inside the board.c file as below. void BOARD_EnableRtcClkOut(void) { /* Enable PORTB clock gating */ CLOCK_EnableClock(kCLOCK_PortB); /* Mux the RTC_CLKOUT to PTB3 */ PORT_SetPinMux(PORTB, 3u, kPORT_MuxAlt7); /* Select the 32kHz reference for RTC_CLKOUT signal */ SIM->SOPT1 |= SIM_SOPT1_OSC32KOUT(1); } Call the BOARD_EnableRtcClkOut function in hardware_init function just after BOARD_BootClockRUN (board.c file). Find clock_config.c file in the board folder at the workspace. Add the following defines at the top of the file. #define RTC_OSC_CAP_LOAD_0 0x0U /*!< RTC oscillator, capacitance 0pF */ #define RTC_OSC_CAP_LOAD_2 0x2000U /*!< RTC oscillator, capacitance 2pF */ #define RTC_OSC_CAP_LOAD_4 0x1000U /*!< RTC oscillator, capacitance 4pF */ #define RTC_OSC_CAP_LOAD_6 0x3000U /*!< RTC oscillator, capacitance 6pF */ #define RTC_OSC_CAP_LOAD_8 0x800U /*!< RTC oscillator, capacitance 8pF */ #define RTC_OSC_CAP_LOAD_10 0x2800U /*!< RTC oscillator, capacitance 10pF */ #define RTC_OSC_CAP_LOAD_12 0x1800U /*!< RTC oscillator, capacitance 12pF */ #define RTC_OSC_CAP_LOAD_14 0x3800U /*!< RTC oscillator, capacitance 14pF */ #define RTC_OSC_CAP_LOAD_16 0x400U /*!< RTC oscillator, capacitance 16pF */ #define RTC_OSC_CAP_LOAD_18 0x2400U /*!< RTC oscillator, capacitance 18pF */ #define RTC_OSC_CAP_LOAD_20 0x1400U /*!< RTC oscillator, capacitance 20pF */ #define RTC_OSC_CAP_LOAD_22 0x3400U /*!< RTC oscillator, capacitance 22pF */ #define RTC_OSC_CAP_LOAD_24 0xC00U /*!< RTC oscillator, capacitance 24pF */ #define RTC_OSC_CAP_LOAD_26 0x2C00U /*!< RTC oscillator, capacitance 26pF */ #define RTC_OSC_CAP_LOAD_28 0x1C00U /*!< RTC oscillator, capacitance 28pF */ #define RTC_OSC_CAP_LOAD_30 0x3C00U /*!< RTC oscillator, capacitance 30pF */ Search the CLOCK_CONFIG_EnableRtcOsc call to a function inside the BOARD_BootClockRUN function (also in the clock_config.c file), and edit the argument by any of the defines above. Finally, disable the low power options and led support in the "preinclude.h" file located in the source folder of the project: #define cPWR_UsePowerDownMode 0 #define gLEDSupported_d 0 At this point, you can measure in PTB3 and play with the frequency adjust using your frequency counter. Each time that the board is programmed, you need to perform a POR to get the correct measure. The following table was obtained from an FRDM-KW36 board rev B and it can be used as a reference to adjust the frequency. Please note that the capacitance is not only composed of the enabled internal capacitance, but also the parasitic capacitances found in the package, bond wires, bond pad, and the PCB traces. So, while the reference measurements given below should be close to the actual value, you should also make measurements with your board, to ensure that the frequency is trimmed specifically to your board and layout.   Enabled Capacitors CLOAD Capacitance Definition Frequency - 0pF RTC_OSC_CAP_LOAD_0 (bank disabled) 32772.980Hz SC2P 2pF RTC_OSC_CAP_LOAD_2 32771.330Hz SC4P 4pF RTC_OSC_CAP_LOAD_4 32770.050Hz SC2P, SC4P 6pF RTC_OSC_CAP_LOAD_6 32769.122Hz SC8P 8pF RTC_OSC_CAP_LOAD_8 32768.289Hz SC2P, SC8P 10pF RTC_OSC_CAP_LOAD_10 32767.701Hz SC4P, SC8P 12pF RTC_OSC_CAP_LOAD_12 32767.182Hz SC2P, SC4P, SC8P 14pF RTC_OSC_CAP_LOAD_14 32766.766Hz SC16P 16pF RTC_OSC_CAP_LOAD_16 32766.338Hz SC2P, SC16P 18pF RTC_OSC_CAP_LOAD_18 32766.038Hz SC4P, SC16P 20pF RTC_OSC_CAP_LOAD_20 32765.762Hz SC2P, SC4P, SC16P 22pF RTC_OSC_CAP_LOAD_22 32765.532Hz SC8P, SC16P 24pF RTC_OSC_CAP_LOAD_24 32765.297Hz SC2P, SC8P, SC16P 26pF RTC_OSC_CAP_LOAD_26 32765.117Hz SC4P, SC8P, SC16P 28pF RTC_OSC_CAP_LOAD_28 32764.940Hz SC2P, SC4P, SC8P, SC16P 30pF RTC_OSC_CAP_LOAD_30 32764.764Hz
View full article
Overview Bluetooth Low Energy offers the ability to broadcast data in format of non-connectable advertising packets while not being in a connection. This GAP Advertisement is widely known as a beacon and is used in today’s IoT applications in different forms. This article will present the current beacon format in our demo application from the KW40Z software package and how to create the most popular beacon formats on the market. The advertising packet format and payload are declared in the gAppAdvertisingData structure from app_config.c. This structure points to an array of AD elements, advScanStruct: static const gapAdStructure_t advScanStruct[] = {   {     .length = NumberOfElements(adData0) + 1,     .adType = gAdFlags_c,     .aData = (void *)adData0   },    {     .length = NumberOfElements(adData1) + 1,     .adType = gAdManufacturerSpecificData_c,     .aData = (void *)adData1   } }; Due to the fact that all beacons use the advertising flags structure and that the advertising PDU is 31 bytes in length (Bluetooth Low Energy v4.1), the maximum payload length is 28 bytes, including length and type for the AD elements. The AD Flags element is declared as it follows: static const uint8_t adData0[1] =  { (gapAdTypeFlags_t)(gLeGeneralDiscoverableMode_c | gBrEdrNotSupported_c) }; The demo application uses a hash function to generate a random UUID for the KW40Z default beacon. This is done in BleApp_Init: void BleApp_Init(void) {     sha1Context_t ctx;         /* Initialize sha buffer with values from SIM_UID */     FLib_MemCopy32Unaligned(&ctx.buffer[0], SIM_UIDL);     FLib_MemCopy32Unaligned(&ctx.buffer[4], SIM_UIDML);     FLib_MemCopy32Unaligned(&ctx.buffer[8], SIM_UIDMH);     FLib_MemCopy32Unaligned(&ctx.buffer[12], 0);          SHA1_Hash(&ctx, ctx.buffer, 16);         /* Updated UUID value from advertising data with the hashed value */     FLib_MemCpy(&gAppAdvertisingData.aAdStructures[1].aData[3], ctx.hash, 16); } When implementing a constant beacon payload, please bear in mind to disable this code section. KW40Z Default Beacon The KW40Z software implements a proprietary beacon with the maximum ADV payload and uses the following Manufacturer Specific Advertising Data structure of 26 bytes. This is the default implementation of the beacon demo example from the KW40Z Connectivity Software package. static uint8_t adData1[26] = {     /* Company Identifier*/     0xFF, 0x01     /* Beacon Identifier */     0xBC,     /* UUID */                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                                   /* A */                     0x00, 0x00,     /* B */                     0x00, 0x00,     /* C */                     0x00, 0x00,     /* RSSI at 1m */            0x1E}; iBeacon iBeacon is a protocol designed by Apple. It uses a 20 byte payload that consists of the following identifying information [1] : To advertise an iBeacon packet, the user needs to change the second AD element, adData1, like below: static uint8_t adData1[25] = {                                0x4C, 0x00,                                   0x02, 0x15,         /* UUID */             0xD9, 0xB9, 0xEC, 0x1F, 0x39, 0x25, 0x43, 0xD0, 0x80, 0xA9, 0x1E, 0x39, 0xD4, 0xCE, 0xA9, 0x5C,         /* Major Version */    0x00, 0x01         /* Minor Version */    0x00, 0x0A,                                0xC5}; AltBeacon AltBeacon is an open specification designed for proximity beacon advertisements [2]. It also uses a Manufacturer Specific Advertising Data structure: To advertise an AltBeacon packet, the user needs to change the second AD element, like below: static uint8_t adData1[26] = {     /* MFG ID*/         0xFF, 0x01,     /* Beacon Code */   0xBE, 0xAC,     /* Beacon ID */     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,     /* Ref RSSI*/       0xC5,     /* MFG RSVD*/       0x00}; Eddystone™ Eddystone™ is an open Bluetooth® Smart beacon format from Google [3]. It offers three data type packets: Eddystone™-UID Eddystone™-URL Eddystone™-TLM Eddystone™ uses two advertising structures: Complete List of 16-bit Service UUIDs structure, which contains the Eddystone Service UUID (0xFEAA). Service Data structure, which also contains the Eddystone™ Service UUID (0xFEAA). Thus, advScanStruct will now have 3 elements: static const gapAdStructure_t advScanStruct[] = {   {     .length = NumberOfElements(adData0) + 1,     .adType = gAdFlags_c,     .aData = (void *)adData0   },    {     .length = NumberOfElements(adData1) + 1,     .adType = gAdComplete16bitServiceList_c,     .aData = (void *)adData1   },   {     .length = NumberOfElements(adData2) + 1,     .adType = gAdServiceData16bit_c,     .aData = (void *)adData2   } }; The complete List of 16-bit Service UUIDs element will look like: static const uint8_t adData1[2] =  { 0xAA, 0xFE }; Eddystone™-UID Eddystone™-UID broadcasts a unique 16-bit Beacon ID to identify a particular device in a group. The Service Data block has the following structure: To implement this, the user needs to add a third AD element, as follows: static uint8_t adData2[22] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x00,     /* Ranging Data */  0xEE,     /* Namespace */     0x8B, 0x0C, 0xA7, 0x50, 0x09, 0x54, 0x77, 0xCB, 0x3E, 0x77,     /* Instance */      0x00, 0x00, 0x00, 0x00, 0x00, 0x01,     /* RFU */           0x00, 0x00}; Eddystone™-URL Eddystone™-URL broadcasts a compressed URL. The Service Data block has the following structure: In this example, we will implement a beacon which will advertise NXP’s webpage, http://www.nxp.com. To implement this, the user needs to add a third AD element, as follows: static const uint8_t adData2[9] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x10,     /* TX Power */      0xEE,     /* URL scheme */    0x00,     /* Encode URL */    'n', 'x, 'p', 0x07}; Eddystone™-TLM Eddystone™-TLM broadcasts telemetry data about the beacon device operation. The Service Data block has the following structure: To implement this, the user needs to add a third AD element, as follows: static uint8_t adData2[16] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x20,     /* TLM Version */   0x00,     /* VBATT */        0x00, 0x00,     /* TEMP */         0x00, 0x00,     /* ADV_CNT */      0x00, 0x00, 0x00, 0x00,     /* SEC_CNT */      0x00, 0x00, 0x00, 0x00};
View full article
Overview The Bluetooth specification defines 4 Generic Access Profile (GAP) roles for devices operating over a Low Energy physical transport [1]: Peripheral Central Broadcaster Observer The Bluetooth Low Energy Host Stack implementation on the Kinetis KW40Z offers devices the possibility to change between any of the 4 roles at run time. This article will present the interaction with the Bluetooth Low Energy Host API needed to implement a GAP multiple role device. General Procedure instructions Running the GAP roles requires the application to go through the following 3 steps: Configuration - Stack configuration for the desired GAP role The application needs to configure the stack parameters, e.g. advertising parameters, advertising data, scan parameters, callbacks. Note that configuration of the advertising parameters or scanning response and advertising data can be done only once if the values don’t change at runtime. The configuration is always made in the Link Layer Standby state. Start - Running the role The application needs to start advertising, scanning or initiate connection. Stop - Return to Standby state When changing between roles, the Link layer must always go through the Link Layer Standby state. Running as a GAP Broadcaster or GAP Peripheral The GAP Broadcaster or Peripheral sends advertising events. Additionally, the GAP Peripheral will accept the establishment of a LE link. This is why the GAP Observer will only support the Non Connectable Advertising mode (gAdvNonConnectable_c). Both roles requires configuration of advertising data, advertising parameters. The configuration (gAppAdvertisingData, gAppScanRspData and gAdvParams) usually resides in app_config.c. The confirmation events for setting these parameters is received in BleApp_GenericCallback. The confirmation event for the changing state of advertising is received in BleApp_AdvertisingCallback. Configuration /* Setup Advertising and scanning data */ Gap_SetAdvertisingData(&gAppAdvertisingData, &gAppScanRspData); /* Setting only for GAP Broadcaster role */ gAdvParams. advertisingType = gAdvNonConnectable_c; /* Set advertising parameters*/ Gap_SetAdvertisingParameters(&gAdvParams); Start App_StartAdvertising(BleApp_AdvertisingCallback, BleApp_ConnectionCallback); Stop Gap_StopAdvertising(); Running as a GAP Observer The GAP Observer receives advertising events. Unlike the GAP Peripheral or Broadcaster, it does not need to set scanning parameters separately. It passes the configuration with the start procedure. The configuration (gAppScanParams) usually resides in app_config.c. The confirmation event for the changing state of scanning is received in BleApp_ScanningCallback. Configuration and Start App_StartScanning(&gAppScanParams, BleApp_ScanningCallback); Stop Gap_StopScanning (); Running as a GAP Central The GAP Central initiates the establishment of the LE link. Like the GAP Observer, it passes the configuration with the start procedure. The configuration (gConnReqParams) usually resides in app_config.c. The confirmation event for the changing state of link is received in BleApp_ConnectionCallback. Configuration and Start Gap_Connect(&gConnReqParams, BleApp_ConnectionCallback); Stop Gap_Disconnect(deviceId); Example An out-of-the box example for multiple role is attached. The application named blood_pressure_multi_role implements a Blood Pressure GATT client and server and can switch between the following GAP roles: Peripheral, Observer and Central. The contents of the archive needs to be copied to the following location: <Installer Path>\KW40Z_Connectivity_Software_1.0.1\ConnSw\examples\bluetooth\ The application can be found at: <Install Path specified>\KW40Z_Connectivity_Software_1.0.1\ConnSw\examples\bluetooth\blood_pressure_multi_role\frdmkw40z\bare_metal\build\iar\blood_pressure_multi_role.eww Running as GAP Peripheral Press SW4. LED1 will start flashing and the console will show that the Link Layer enters Advertising. If the Link Layer was in a previous state, it will go through Standby. static void BleApp_Advertise(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Peripheral\n\r");     mGapRole = gGapPeripheral_c;         /* Start GAP Peripheral */     App_StartAdvertising(BleApp_AdvertisingCallback, BleApp_ConnectionCallback); } Running as GAP Observer Press SW3. A chasing LED pattern will start and the console will show that the Link Layer enters Scanning. If the Link Layer was in a previous state, it will go through Standby. static void BleApp_Scan(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Observer\n\r");     mGapRole = gGapObserver_c;         /* Start GAP Observer */     App_StartScanning(&gAppScanParams, BleApp_ScanningCallback); } Running as GAP Central If the Link Layer is in scanning and finds a Blood Pressure Sensor, it will go through Standby and initiate connection. static void BleApp_Connect(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Central\n\r");     mGapRole = gGapCentral_c;         /* Start GAP Central */     Gap_Connect(&gConnReqParams, BleApp_ConnectionCallback); } Returning to Standby Pressing SW3 for more than 2 seconds, brings the Link Layer back in Standby. static void BleApp_GoToStandby(void) {     /* Check if connection is on */     if (mPeerInformation.deviceId != gInvalidDeviceId_c)     {         /* Stop GAP Central or Peripheral */         Gap_Disconnect(mPeerInformation.deviceId);     }     if (mAdvOn)     {         /* Stop GAP Peripheral or Bradcaster */         Gap_StopAdvertising();     }         if (mScanningOn)     {         /* Stop GAP Observer */         Gap_StopScanning();     } } References [1] BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part C], 2.2 PROFILE ROLES
View full article
Many applications make use of 32 kHz clocks to keep tracking of real-time or to have a low power time reference for the system. Most of the systems might use a 32.768 kHz XTAL for this purpose. However, there might be some exceptions in which the application requires compensate the frequency of this clock due to temperature changes, aging, or just because the clock provides from a source which frequency is close to the ideal 32.768 kHz, but it presents some variations. QN908x devices require a 32 kHz clock source for some applications like running the BLE stack in low power. 32.768 kHz XTALs are more accurate so they are used to generate a 32 kHz source by compensating for the ppm difference. This provides us with tools to compensate for any external 32 kHz source by first obtaining the ppm difference from the ideal frequency. The solution consists in determining how off is the external clock input frequency from the ideal 32 kHz by making a comparison with a trusted clock in the system, typically the 32 MHz / 16 MHz XTAL. This process is executed in the background in an interrupt-based system so the application can initialize or run other processes in the meantime. Then, the results of the ppm calculation are reported to the main application to compensate for the changes and provide for a more accurate clock source. This example makes use of the following peripherals in the QN908x RTC Seconds Timer CTIMER DMA The RTC Seconds Timer uses the 32 kHz clock source as a reference. It contains an internal 32000 cycles counter that increases each 32 kHz clock period. On overflow, it rises the SEC_INT flag to indicate that one second has elapsed. The CTIMER makes use of the APB clock which derives from the 32 MHz / 16 MHz clock. This timer is used in free-running mode with a Prescaler value of 1 to count the number of APB pulses. The algorithm consists of counting the amount of APB pulses (trusted clock reference) elapsed by the time the RTC SEC_INT flag is set. Ideally, if both clocks are in the right frequency, the number of APB pulses must be equal to the reference clock frequency. For example, if the APB clock is 16 MHz, by the time the SEC_INT flag sets, the CTIMER counter must have a value of 16 x 10 6 counts. Assuming our reference clock is ideal, the difference between the CTIMER counter value and 16 x 10 6 can be used to obtain the ppm difference given the following formula. Where f 0 is the ideal APB frequency (16 MHz) and f 1 is the real measured frequency (CTIMER counter value). Since the pulses counted using the CTIMER correspond to the time it took to the RTC Seconds Timer to count one second, we can extrapolate the obtained ppm value as a difference in the 32 kHz clock source from the ideal 32 kHz. To prevent from any application or task servicing latency, the algorithm makes use of the DMA to automatically capture the CTIMER Counter value when the SEC_INT flag is set. The program flow is described in the diagram below. As a way of demonstrating this algorithm, two APIs were implemented to calculate the ppm value and apply the compensation to the system. Both APIs are included in the file fsl_osc_32k_cal .c and .h files. OSC_32K_CAL_GetPpm (osc_32k_cal_callback_t pCallbackFnc): Initializes the required timers and DMA and starts with the CTIMER capture. A callback is passed so it can be executed once the ppm calculation sequence completes. OSC_32_CAL_ApplyPpm (int32_t ppmMeasurement): Uses the previously calculated ppm passed as an input parameter to compensate the RTC and the BLE timer used during sleep mode. OSC_32K_CAL_GetPpm is called every time the ppm value of the 32 kHz source clock needs to be calculated. It takes around one second to complete (depending on how off the 32 kHz source clock is from the ideal frequency) and the application cannot enter into low power state during this time. The registered callback function is executed once the calculation is complete. The ppm calculation is performed into the DMA callback. It consists of obtaining the CTIMER counter difference and use it as f 1 in the formula shown before. The ppm values are calculated using floating point unit. /* Calculate PPMs */ ppmResult = (float)((float)1-((float)ApbClockFreq/(float)ApbCountDiff)); ppmResult *= (float)1048576;‍‍‍‍‍‍ Then OSC_32_CAL_ApplyPpm must be called using the ppm value obtained after calling OSC_32K_CAL_GetPpm. This API programs the necessary values in the RTCàCAL register and the BLE Sleep timer registers to compensate for the differences in the 32 kHz source clock. Finally, the user must account for all those other APIs that make use of the 32 kHz clock frequency and update the values accordingly. For the particular case of the NXP BLE Stack, there are two APIs that need to be updated to return the clock frequency after the calibration has been applied. uint32_t PWRLib_LPTMR_GetInputFrequency(void) { uint32_t result = 32000; int32_t ppm = 0; if ( RTC->CTRL & RTC_CTRL_CAL_EN_MASK) /* is calibration enabled ? */ { /* Get the current calibration value */ if (RTC->CAL & RTC_CAL_DIR_MASK) { /* Backward calibration */ ppm -= (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK); } else { /* Forward calibration */ ppm += (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK); } /* Obtain the uncalibrated clock frequency using the formula * fUncal = 32000 - (ppm*0.03125) where 0.03125 is the number * of Hz per PPM digit obtained from (768 Hz/0x6000 PPM) */ result -= (float) ppm * (float) 0.03125; } else { #if (defined(BOARD_XTAL1_CLK_HZ) && (BOARD_XTAL1_CLK_HZ == CLK_XTAL_32KHZ)) result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,768Khz crystal is used */ #else result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,000Khz internal RCO is used */ #endif } return result; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ uint32_t StackTimer_GetInputFrequency(void) { uint32_t prescaller = 0; uint32_t refClk = 0; uint32_t result = 0; #if FSL_FEATURE_SOC_FTM_COUNT refClk = BOARD_GetFtmClock(gStackTimerInstance_c); prescaller = mFtmConfig.prescale; result = refClk / (1 << prescaller); #elif FSL_FEATURE_RTC_HAS_FRC int32_t ppm = 0; (void)prescaller; /* unused variables */ (void)refClk; /* suppress warnings */ result = 32000; if ( RTC->CTRL & RTC_CTRL_CAL_EN_MASK) /* is calibration enabled ? */ { /* Get the current calibration value */ if (RTC->CAL & RTC_CAL_DIR_MASK) { /* Backward calibration */ ppm -= (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK); } else { /* Forward calibration */ ppm += (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK); } /* Obtain the uncalibrated clock frequency using the formula * fUncal = 32000 - (ppm*0.03125) where 0.03125 is the number * of Hz per PPM digit obtained from (768 Hz/0x6000 PPM) */ result -= (float) ppm * (float) 0.03125; } else { #if (defined(BOARD_XTAL1_CLK_HZ) && (BOARD_XTAL1_CLK_HZ == CLK_XTAL_32KHZ)) result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,768Khz crystal is used */ #else result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,000Khz internal RCO is used */ #endif } #elif FSL_FEATURE_SOC_CTIMER_COUNT refClk = BOARD_GetCtimerClock(mCtimerBase[gStackTimerInstance_c]); prescaller = mCtimerConfig[gStackTimerInstance_c].prescale; result = refClk / (prescaller + 1); #else refClk = BOARD_GetTpmClock(gStackTimerInstance_c); prescaller = mTpmConfig.prescale; result = refClk / (1 << prescaller); #endif return result; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
View full article
Bluetooth Low Energy offers the ability to broadcast data in format of non-connectable advertising packets while not being in a connection. This GAP Advertisement is widely known as a beacon and there are currently different beacon formats on the market.   This guide will help you to create your own beacon scanner to detect from which type of device is the beacon received from. This guide it’s based on the frdmkw41z_wireless_examples_bluetooth_temperature_collector_freertos  demo in MCUXpresso  The first thing we will do it’s to disable the low power to make the development easier in the app_preinclude.h /* Enable/Disable PowerDown functionality in PwrLib */ #define cPWR_UsePowerDownMode 0‍‍‍‍‍‍   The following changes will be all performed in the temperature_collector.c file We will disable the timer so it keeps scanning the packets received   /* Start advertising timer TMR_StartLowPowerTimer(mAppTimerId, gTmrLowPowerSecondTimer_c, TmrSeconds(gScanningTime_c), ScanningTimeoutTimerCallback, NULL); */‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Then we will define some of the data we want to use as a reference. static uint8_t NXPAd[3] = { /* Company Identifier*/ mAdvCompanyId, /* Beacon Identifier */ 0xBC }; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   static uint8_t iBeaconAd[2] = { 0x4C, 0x00 };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ static uint8_t EddyStoneUIDAd2[3] = { /* ID */ 0xAA, 0xFE, /* Frame Type */ 0x00 }; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     static const uint8_t EddyStoneURLAd2[3] = { /* ID */ 0xAA, 0xFE, /* Frame Type */ 0x10 };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍     static const uint8_t EddyStoneTLMAd2[3] = { /* ID */ 0xAA, 0xFE, /* Frame Type */ 0x20 };‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Once we have those definitions of the beacon structure of each of the types wanted we will change the function static bool_t CheckScanEvent(gapScannedDevice_t* pData) static bool_t CheckScanEvent(gapScannedDevice_t* pData) { uint8_t index = 0; bool_t foundMatch = FALSE; bool_t EddyfoundMatch = FALSE; while (index < pData->dataLength) { gapAdStructure_t adElement; adElement.length = pData->data[index]; adElement.adType = (gapAdType_t)pData->data[index + 1]; adElement.aData = &pData->data[index + 2]; /*DESIRED BEACON SCANNER PARSER CODE */ /* Move on to the next AD elemnt type */ index += adElement.length + sizeof(uint8_t); } if (foundMatch) { SHELL_NEWLINE(); shell_write("\r\Address : "); shell_writeHex(pData->aAddress, 6); } return foundMatch; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   As you can see, there is a comment in the function that mentions the need to add the scanner parser code, depending on the beacon you want to see  will be the code to use there  NXP if (FLib_MemCmp(NXPAD, (adElement.aData), 2)) { shell_write("\r\nFound NXP device!"); SHELL_NEWLINE(); shell_write("\r\nData Received: "); shell_writeHex(adElement.aData, adElement.length); foundMatch=TRUE; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   iBeacon if (FLib_MemCmp(iBeaconAd, (adElement.aData), 2)) { shell_write("\r\nFound iBeacon device!"); SHELL_NEWLINE(); shell_write("\r\nData Received: "); shell_writeHex(adElement.aData, adElement.length); foundMatch=TRUE; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Eddystone if (FLib_MemCmp(EddyStoneUIDAd1, (adElement.aData), 2)) { shell_write("\r\nFound EddyStone device!"); if (!EddyfoundMatch) { EddyfoundMatch=TRUE; } else{ if(TRUE==EddyfoundMatch && FLib_MemCmp(EddyStoneUIDAd2, (adElement.aData), 3)) { SHELL_NEWLINE(); shell_write("\r\n[UID type] Data Received: "); shell_writeHex(adElement.aData, adElement.length); foundMatch=TRUE; EddyfoundMatch=FALSE; } else if(TRUE==EddyfoundMatch && FLib_MemCmp(EddyStoneURLAd2, (adElement.aData), 3)) { SHELL_NEWLINE(); shell_write("\r\n[URL type] Data Received: "); hell_writeHex(adElement.aData, adElement.length); foundMatch=TRUE; EddyfoundMatch=FALSE; } else if(TRUE==EddyfoundMatch && FLib_MemCmp(EddyStoneTLMAd2, (adElement.aData), 3)) { SHELL_NEWLINE(); shell_write("\r\n[TLM type] Data Received: "); shell_writeHex(adElement.aData, adElement.length); foundMatch=TRUE; EddyfoundMatch=FALSE; } else { EddyfoundMatch=TRUE; } } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
View full article
Bluetooth® Low-Energy (BLE) RF PHY tests can be done by using the Direct Test Mode (DTM).  This document will help as a guide to perform the test using a device from the KW family.     Direct Test Mode Direct Test Mode (DTM) is used to control the Device Under Test (DUT) and provides a report with the results from the tests performed by the Tester.   There are two ways to perform those tests:   HCI Through a 2-wire UART interface   The packet format from the DTM is different from the HCI commands.   For further information of the commands and this type of tests, please refer to the Bluetooth Core Specifications, Vol 6, Part F: Direct Test Mode   Software This guide will use the KW41Z as example, but the same changes must be applicable for the rest of the devices Download and install the software SDK of the device to use by following the getting started in the device page. In this case the SDK of the KW41Z will be downloaded from the MCUXpresso Builder. Setup for DTM using HCI Import the hci_black_box example to the IDE according to the getting started of the device.   Download and open the latest version of the Test Tool available in the page of the device under Lab & Test Software in the Software and Tools Tab. Open the Command console of the board, please be sure that you have the correct baud rate set for the example (Default: 115200) Select one of the available commands to either start or finish tests    Setup for DTM using DTM pins You can choose from any example available while making sure that the pins chosen for DTM  are not occupied or used. Import the beacon example to the IDE according to the getting started of the device.   The DTM pins behave as a UART interface; in order to enable them in our KW devices there is the need to follow the next steps Look at the Reference manual of the device and locate the pins that support the DTM_TX and the DTM_RX. In this case, we will select the PTB1 and PTB2.  Set the ALT_MUX of the pin in the code to work as DTM   PORT_SetPinMux(PORTB, PIN1_IDX, kPORT_MuxAlt2);            /* PORTB1 (pin 17) is configured as DTM_RX */   PORT_SetPinMux(PORTB, PIN2_IDX, kPORT_MuxAlt3);            /* PORTB2 (pin 18) is configured as DTM_TX */‍‍‍‍‍‍‍‍‍‍ Configure the baud rate of the DTM pins, this can be achieved by writing to the DTM_2WIRE_CONFIG register. This register is not available in the header file by default, so there is the need to create a pointer to such address. To verify this data, you can check the chapter 45.2.3.1.4 Test and Debug Registers Descriptions for the Bluetooth Link Layer of the reference manual. #define DTM_2WIRE_CONFIG                0x580 #define BTLE_RF_DTM_2WIRE_CONFIG        (*(volatile uint16_t *) (BTLE_RF_BASE+DTM_2WIRE_CONFIG)) BTLE_RF_DTM_2WIRE_CONFIG = 0x0042;  /*Configure DTM pins baud rate of 115200*/‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
View full article
Bluetooth® Low Energy (or BLE) is a wireless technology that allows the exchange of information between a device that contains data (Server) and a device that requests that data (Client). Servers are usually small battery powered devices connected to sensors or actuators to gather data or perform some actions while clients are usually devices that use that information in a system or for display to a user (most common client devices are the Smartphones). When creating a custom BLE profile, we need to consider that it will need to be implemented on both Server and Client. Server will include the database of all the information that can be accessed or modified while the Client will require drivers to access and handle the data provided by the server. This post explains how to implement a custom profile in the server side using the NXP BLE stack. As example, a custom Potentiometer reporter is implemented on a MKW40Z160. Generic Attribute Profile Before implementing a custom profile, we need to be familiarized with the way BLE exchanges information. The Generic Attribute Profile (GATT) establishes how to exchange all profile and user data over a BLE connection. All standard BLE profiles are based on GATT and must comply with it to operate correctly. GATT defines two communication roles: Server and Client. The GATT Server stores the data to be transported and accepts GATT requests, commands and confirmations from the client. The GATT Client accesses data on the remote GATT server via read, write, notify or indicate operations. Figure 1 GATT Client-Server GATT data is exposed using attributes that are organized to describe the information accessible in a GATT server. These are Profile, Service, Characteristic and Descriptor. Profiles are high level definitions that determine the behavior of the application as a whole (i.e. Heart Rate Monitor, or Temperature Sensor). Profiles are integrated by one or more Services that define individual functionalities (i.e. a Heart Rate Monitor requires a Heart Rate Sensor and a Battery Measurement Unit). Services are integrated by one or more characteristics that hold individual measurements, control points or other data for a service (i.e. Heart Rate Sensor might have a characteristic for Heart Rate and other for Sensor Location). Finally Descriptors define how characteristics must be accessed. Figure 2 GATT database structure Adding a New Service to the GATT Database The GATT database in a server only includes attributes that describe services, characteristics and descriptors. Profiles are implied since they are a set of predefined services. In the NXP Connectivity Software, macros are used to define each of the attributes present in the database in an easier way. Each service and characteristic in a GATT database has a Universally Unique Identifier (UUID). These UUID are assigned by Bluetooth Org on adopted services and characteristics. When working with custom profiles, a proprietary UUID must be assigned. In the NXP connectivity Software, custom UUIDs are defined in the file gatt_uuid128.h. Each new UUID must be defined using the macro UUID128 (name, bytes) where name is an identifier that will help us to reference the UUID later in the code. Byte is a sequence of 16-bytes (128-bits) which are the custom UUID. Following is an example of the definition of the Potentiometer service and the Potentiometer Relative Value characteristic associated to it. /* Potentiometer Service */ UUID128(uuid_service_potentiometer, 0xE0, 0x1C, 0x4B, 0x5E, 0x1E, 0xEB, 0xA1, 0x5C, 0xEE, 0xF4, 0x5E, 0xBA, 0x04, 0x56, 0xFF, 0x02) /* Potentiometer Characteristic */ UUID128(uuid_characteristic_potentiometer_relative_value, 0xE0, 0x1C, 0x4B, 0x5E, 0x1E, 0xEB, 0xA1, 0x5C, 0xEE, 0xF4, 0x5E, 0xBA, 0x04, 0x57, 0xFF, 0x02) ‍‍‍‍‍‍‍‍‍‍‍ Once proper UUIDs have been stablished, the new service must be added to the GATT database. It is defined in the file gatt_db.h. Simple macros are used to include each of the attributes in the proper order. Following code shows the implementation of the potentiometer service in gatt_db file. PRIMARY_SERVICE_UUID128(service_potentiometer, uuid_service_potentiometer)     CHARACTERISTIC_UUID128(char_potentiometer_relative_value, uuid_characteristic_potentiometer_relative_value, (gGattCharPropRead_c | gGattCharPropNotify_c))         VALUE_UUID128(value_potentiometer_relative_value, uuid_characteristic_potentiometer_relative_value, (gPermissionFlagReadable_c ), 1, 0x00)         CCCD(cccd_potentiometer)         DESCRIPTOR(cpfd_potentiometer, gBleSig_CharPresFormatDescriptor_d, (gPermissionFlagReadable_c), 7, gCpfdUnsigned8BitInteger, 0x00,                    0xAD/*Unit precentage UUID in Little Endian (Lower byte)*/,                    0x27/*Unit precentage UUID in Little Endian (Higher byte)*/,                    0x01, 0x00, 0x00) ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ PRIMARY_SERVICE_UUID128 (service_name, service_uuid) defines a new service in the GATT database with a custom 128-bit UUID. It requires two parameters; service_name is the name of this service in the code and it is used later during the program implementation. Service_uuid is the identifier for the service UUID previously defined in gatt_uuid128.h. CHARACTERISTIC_UUID128 (characteristic_name, characteristic_uuid, flags) defines a new characteristic inside the previously defined service with a custom 128-bit UUID. It requires three parameters; characteristic_name is the name of the characteristic in the code, characteristic_uuid is the identifier for the characteristic UUID previously defined in gatt_uuid128.h. Finally, flags is a concatenation of all the characteristic properties (read, write, notify, etc.). VALUE_UUID128 (value_name, characteristic_uuid, permission_flags, number_of_bytes, initial_values…) defines the value in the database of the previously defined characteristic. Value_name is an identifier used later in the code to read or modify the characteristic value. Characteristic_uuid is the same UUID identifier for the previously defined characteristic. Permission_flags determine how the value can be accessed (read, write or both). Number of bytes define the size of the value followed by the initial value of each of those bytes. CCCD (cccd_name) defines a new Client Characteristic Configuration Descriptor for the previously defined characteristic. Cccd_name is the name of the CCCD for use later in the code. This value is optional depending on the characteristic flags. DESCRIPTOR (descriptor_name, descriptor_format, permissions, size, descriptor_bytes…) defines a descriptor for the previously defined characteristic. Descriptor_name defines the name for this descriptor. Descriptor_format determines the type of descriptor. Permissions stablishes how the descriptor is accessed. Finally the size and descriptor bytes are added. All the macros used to fill the GATT database are properly described in the BLEADG (included in the NXP Connectivity Software documentation) under chapter 7 “Creating a GATT Database”. Implementing Drivers for New Service Once the new service has been defined in gatt_db.h, drivers are required to handle the service and properly respond to client requests. To do this, two new files need to be created per every service added to the application; (service name)_service.c and (service name)_interface.h. The service.c file will include all the functions required to handle the service data, and the interface.h file will include all the definitions used by the application to refer to the recently created service. It is recommended to take an existing file for reference. Interface header file shall include the following. Service configuration structure that includes a 16-bit variable for Service Handle and a variable per each characteristic value in the service. /*! Potentiometer Service - Configuration */ typedef struct psConfig_tag {     uint16_t    serviceHandle;                 /*!<Service handle */     uint8_t     potentiometerValue;            /*!<Input report field */ } psConfig_t; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Function declarations for the start service and stop service functions. These are required to initialize/deinitialize a service. bleResult_t Ps_Start(psConfig_t *pServiceConfig); bleResult_t Ps_Stop(psConfig_t *pServiceConfig); ‍‍‍‍‍‍ Function declarations for subscribe and unsubscribe functions required to subscribe/unsubscribe a specific client to a service. bleResult_t Ps_Subscribe(deviceId_t clientDeviceId); bleResult_t Ps_Unsubscribe(); ‍‍‍‍‍‍ Depending on your application, functions to read, write, update a specific characteristic or a set of them. bleResult_t Ps_RecordPotentiometerMeasurement (uint16_t serviceHandle, uint8_t newPotentiometerValue);‍‍ Service source file shall include the following. A deviceId_t variable to store the ID for the subscribed client. /*! Potentiometer Service - Subscribed Client*/ static deviceId_t mPs_SubscribedClientId; ‍‍‍‍‍‍ Function definitions for the Start, Stop, Subscribe and Unsubscribe functions. The Start function may include code to set an initial value to the service characteristic values. bleResult_t Ps_Start (psConfig_t *pServiceConfig) {        /* Clear subscibed clien ID (if any) */     mPs_SubscribedClientId = gInvalidDeviceId_c;         /* Set the initial value defined in pServiceConfig to the characteristic values */     return Ps_RecordPotentiometerMeasurement (pServiceConfig->serviceHandle,                                              pServiceConfig->potentiometerValue); } bleResult_t Ps_Stop (psConfig_t *pServiceConfig) {   /* Unsubscribe current client */     return Ps_Unsubscribe(); } bleResult_t Ps_Subscribe(deviceId_t deviceId) {    /* Subscribe a new client to this service */     mPs_SubscribedClientId = deviceId;     return gBleSuccess_c; } bleResult_t Ps_Unsubscribe() {    /* Clear current subscribed client ID */     mPs_SubscribedClientId = gInvalidDeviceId_c;     return gBleSuccess_c; } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Definition of the service specific functions. It is, the functions used to write, read or notify characteristic values. Our example only implements two; a public function to update a characteristic value in the GATT database, and a local function to issue a notification with the recently updated value to the client. bleResult_t Ps_RecordPotentiometerMeasurement (uint16_t serviceHandle, uint8_t newPotentiometerValue) {     uint16_t  handle;     bleResult_t result;     /* Get handle of Potentiometer characteristic */     result = GattDb_FindCharValueHandleInService(serviceHandle,         gBleUuidType128_c, (bleUuid_t*)&potentiometerCharacteristicUuid128, &handle);     if (result != gBleSuccess_c)         return result;     /* Update characteristic value */     result = GattDb_WriteAttribute(handle, sizeof(uint8_t), (uint8_t*)&newPotentiometerValue);     if (result != gBleSuccess_c)         return result;     Ps_SendPotentiometerMeasurementNotification(handle);     return gBleSuccess_c; } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Previous function first obtains the handle value of the characteristic value we want to modify. Handle values are like an index used by the application to access attributes in the database. The UUID for the Potentiometer Relative Value is used to obtain the proper handle by calling GattDb_FindCharValueHandleInService function. Once handle has been obtained, is used in the GattDb_WriteAttribute function to write the new value into the GATT database and it can be accessed by the client. Finally our second function is called to issue a notification. static void Ps_SendPotentiometerMeasurementNotification (   uint16_t handle ) {     uint16_t  hCccd;     bool_t isNotificationActive;     /* Get handle of CCCD */     if (GattDb_FindCccdHandleForCharValueHandle(handle, &hCccd) != gBleSuccess_c)         return;     if (gBleSuccess_c == Gap_CheckNotificationStatus         (mPs_SubscribedClientId, hCccd, &isNotificationActive) &&         TRUE == isNotificationActive)     {         GattServer_SendNotification(mPs_SubscribedClientId, handle);     } } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ SendPotentiometerMeasurementNotification sends a notification to the client. It first obtain the handle value of the CCCD we defined in the GATT database for this characteristic. Then, it checks that the CCCD has been written by the client for notifications. If it has, then it sends the notification so the client can perform a read to the characteristic value. All the functions used to access the GATT database and use the GATT server are better explained in the BLEADG document under chapters 6 and 7. Also instructions on how to create a custom profile are included in chapter 8. BLEADG is part of the NXP Connectivity Software documentation. Integrating a New Service to an Existing BLE Project So far a new service has been created in the database and functions to handle it have been defined. Now this new project must be integrated so it can be managed by the NXP Connectivity Stack. Folder structure of an NXP Connectivity Software project is divided in five different modules. App includes all the application files. Bluetooth contains files related with BLE communications. Framework contains auxiliary software used by the stack for the handling of memory, low power etcetera. KSDK contains the Kinetis SDK drivers for low level modules (ADC, GPIO…) and RTOS include files associated with the operating system. Figure 3 Folder structure Service files must be added to the project under the Bluetooth folder, inside the profiles sub-folder. A new folder must be created for the service.c file and the interface.h file must be added under the interface sub-folder. Figure 4 Service files included Once the files are included in the project, the service must be initialized in the stack. File app.c is the main application file for the NXP BLE stack. It calls all the BLE initializations and application callbacks. The service_interface.h file must be included in this file. Figure 5 Interface header inclusion Then in the local variables definition, a new service configuration variable must be defined for the new service. The type of this variable is the one defined in the service interface file and must be initialized with the service name (defined in gattdb.h) and the initial values for all the characteristic values. Figure 6 Service configuration struct The service now must be initialized. It is performed inside the BleApp_Config function by calling the Start function for the recently added service. static void BleApp_Config() {      /* Read public address from controller */     Gap_ReadPublicDeviceAddress();     /* Register for callbacks*/     App_RegisterGattServerCallback(BleApp_GattServerCallback);       .    .    .    mAdvState.advOn = FALSE;     /* Start services */     Lcs_Start(&lcsServiceConfig);     Dis_Start(&disServiceConfig);     Irs_Start(&irsServiceConfig);     Bcs_Start(&bcsServiceConfig);     Ps_Start(&psServiceConfig); ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Finally, subscribe and unsubscribe functions must be added to the proper host callback. In the BleApp_ConnectionCallback function the subscribe function must be called after the gConnEvtConnected_c (device connected) case, and the unsubscribe function must be called after the gConnEvtDisconnected_c (device disconnected) case. static void BleApp_ConnectionCallback (deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) {     switch (pConnectionEvent->eventType)     {         case gConnEvtConnected_c:         {         .         .         .             /* Subscribe client*/             mPeerDeviceId = peerDeviceId;             Lcs_Subscribe(peerDeviceId);             Irs_Subscribe(peerDeviceId);             Bcs_Subscribe(peerDeviceId);             Cts_Subscribe(peerDeviceId);             Ps_Subscribe(peerDeviceId);             Acs_Subscribe(peerDeviceId);             Cps_Subscribe(peerDeviceId);             Rcs_Subscribe(peerDeviceId);         .         .         .         case gConnEvtDisconnected_c:         {         /* UI */           Led1Off();                     /* Unsubscribe client */           mPeerDeviceId = gInvalidDeviceId_c;           Lcs_Unsubscribe();           Irs_Unsubscribe();           Bcs_Unsubscribe();           Cts_Unsubscribe();           Ps_Unsubscribe();           Acs_Unsubscribe();           Cps_Unsubscribe();           Rcs_Unsubscribe(); ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ After this, services can be accessed by a client application. Handling Notifications and Write Requests Once the new service has been initialized, it is possible for the client to access GATT database attributes and issue commands (read, write, notify…). Nevertheless, when an attribute is written or a CCCD is set to start notifications, program must be aware of these requests to handle them if required. Handling Notifications When a characteristic has been configured as notifiable, the client expects to receive messages from it every time in a while depending on the pre-configured parameters. To indicate this, the client writes the specific CCCD for the characteristic indicating that notifications must start/stop being sent. When this occurs, BleApp_GattServerCallback is executed in the main program. All the application CCCDs must be monitored when the gEvtCharacteristicCccdWritten_c event is set. This event indicates that a CCCD has been written. A conditional structure must be programmed to determine which CCCD was modified and act accordingly. static void BleApp_GattServerCallback (deviceId_t deviceId, gattServerEvent_t* pServerEvent) {     switch (pServerEvent->eventType)     {       case gEvtCharacteristicCccdWritten_c:         {             /*             Attribute CCCD write handler: Create a case for your registered attribute and             execute callback action accordingly             */             switch(pServerEvent->eventData.charCccdWrittenEvent.handle)             {             case cccd_input_report:{               //Determine if the timer must be started or stopped               if (pServerEvent->eventData.charCccdWrittenEvent.newCccd){                 // CCCD set, start timer                 TMR_StartTimer(tsiTimerId, gTmrIntervalTimer_c, gTsiUpdateTime_c ,BleApp_TsiSensorTimer, NULL); #if gAllowUartDebug                 Serial_Print(debugUartId, "Input Report notifications enabled \n\r", gNoBlock_d); #endif               }               else{                 // CCCD cleared, stop timer                 TMR_StopTimer(tsiTimerId); #if gAllowUartDebug                 Serial_Print(debugUartId, "Input Report notifications disabled \n\r", gNoBlock_d); #endif               }             }               break;                           case cccd_potentiometer:{               //Determine if the timer must be started or stopped               if (pServerEvent->eventData.charCccdWrittenEvent.newCccd){                 // CCCD set, start timer                 TMR_StartTimer(potTimerId, gTmrIntervalTimer_c, gPotentiometerUpdateTime_c ,BleApp_PotentiometerTimer, NULL); #if gAllowUartDebug                 Serial_Print(debugUartId, "Potentiometer notifications enabled \n\r", gNoBlock_d); #endif               }               else{                 // CCCD cleared, stop timer                 TMR_StopTimer(potTimerId); #if gAllowUartDebug                 Serial_Print(debugUartId, "Potentiometer notifications disabled \n\r", gNoBlock_d); #endif               }             }               break; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ In this example, when the gEvtCharacteristicCccdWritten_c is set a switch-case selector is executed to determine the written CCCD. This is done by reading the pServerEvent structure in the eventData.charCccdWrittenEvent.handle field. The obtained handle must be compared with the name of the CCCD defined in gatt_db.h for each notifiable characteristic. Figure 7 CCCD name Once the correct CCCD has been detected, the program must determine if it was set or clear. This is done by reading the pServerEvent structure in the eventData.charCccdWrittenEvent.newCccd and executing an action accordingly. In the example code, a timer is started or stopped. Once this timer reaches its modulo value, a new notification is sent using the Ps_RecordPotentiometerMeasurement function previously defined in the service files (see Implementing Drivers for New Service). Handling Write Requests Write request callbacks are not automatically generated like the notification ones. They must be registered during the application initialization. Something to take into account is when this feature is enabled, the written value is not automatically stored in the GATT database. Developers must implement code to do this and perform other application actions if needed.To do this, the GattServer_RegisterHandlesForWriteNotifications function must be called including the handles of all the characteristics that are wanted to generate a callback when written. * Configure writtable attributes that require a callback action */     uint16_t notifiableHandleArray[] = {value_led_control, value_buzzer, value_accelerometer_scale, value_controller_command, value_controller_configuration};     uint8_t notifiableHandleCount = sizeof(notifiableHandleArray)/2;     bleResult_t initializationResult = GattServer_RegisterHandlesForWriteNotifications(notifiableHandleCount, (uint16_t*)&notifiableHandleArray[0]); ‍‍‍‍‍‍‍‍‍ In this example, an array with all the writable characteristics was created. The function that register callbacks requires the quantity of characteristic handles to be registered and the pointer to an array with all the handles. After a client has connected, the gEvtAttributeWritten_c will be executed inside the function BleApp_GattServerCallback every time one of the configured characteristics has been written. Variable pServerEvent->eventData.attributeWrittenEvent.handle must be read to determine the handle of the written characteristic and perform an action accordingly. Depending on the user application, the GATT database must be updated with the new value. To do this, function GattDb_WriteAttribute must be executed. It is recommended to create a function inside the service.c file that updates the attribute in database. case gEvtAttributeWritten_c:         {             /*             Attribute write handler: Create a case for your registered attribute and             execute callback action accordingly             */             switch(pServerEvent->eventData.attributeWrittenEvent.handle){               case value_led_control:{                 bleResult_t result;                                 //Get written value                 uint8_t* pAttWrittenValue = pServerEvent->eventData.attributeWrittenEvent.aValue;                                 //Create a new instance of the LED configurator structure                 lcsConfig_t lcs_LedConfigurator = {                   .serviceHandle = service_led_control,                   .ledControl.ledNumber = (uint8_t)*pAttWrittenValue,                   .ledControl.ledCommand = (uint8_t)*(pAttWrittenValue + sizeof(uint8_t)),                 };                                 //Call LED update function                 result = Lcs_SetNewLedValue(&lcs_LedConfigurator);                                 //Send response to client                 BleApp_SendAttWriteResponse(&deviceId, pServerEvent, &result);                               }               break; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ After all the required actions have been executed, server must send a response to the client. To do this, function GattServer_SendAttributeWrittenStatus is called including the handle and the error code for the client (OK or any other error status). static void BleApp_SendAttWriteResponse (deviceId_t* pDeviceId, gattServerEvent_t* pGattServerEvent, bleResult_t* pResult){   attErrorCode_t attErrorCode;     // Determine response to send (OK or Error)   if(*pResult == gBleSuccess_c)     attErrorCode = gAttErrCodeNoError_c;   else{     attErrorCode = (attErrorCode_t)(*pResult & 0x00FF);   }   // Send response to client    GattServer_SendAttributeWrittenStatus(*pDeviceId, pGattServerEvent->eventData.attributeWrittenEvent.handle, attErrorCode); } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ More information on how to handle writable characteristics can be found in the BLEADG Chapter 5 (Included in the NXP Connectivity Software documentation). References Bluetooth® Low Energy Application Developer’s Guide (BLEADG)– Included in the NXP Connectivity Software Documentation FRDM-KW40Z Demo Application - Link
View full article
FreeRTOS keeps track of the elapsed time in the system by counting ticks. The tick count increases inside a periodic interrupt routine generated by one of the timers available in the host MCU. When FreeRTOS is running the Idle task hook, the microcontroller can be placed into a low power mode. Depending on the low power mode, one or more peripherals can be disabled in order to save the maximum amount of energy possible. The FreeRTOS tickless idle mode allows stopping the tick interruption during the idle periods. Stopping the tick interrupt allows the microcontroller to remain in a deep power saving state until a wake-up event occurs. The application needs to configure the module (timer, ADC, etc…) that will wake up the microcontroller before the next FreeRTOS task needs to be executed. For this purpose, during the execution of vPortSuppressTicksAndSleep, a function called by FreeRTOS when tickless idle is enabled, the maximum amount of time the MCU can remain asleep is passed as an input parameter in order to properly configure the wake-up module. Once the MCU wakes up and the FreeRTOS tick interrupt is restarted, the number of tick counts lost while the MCU was asleep must be restored. Tickless mode is not enabled by default in the Connectivity Software FreeRTOS demos. In this post, we will show how to enable it. For this example, we will use QN9080x to demonstrate the implementation. lowpower‌ freertos tickless‌ tickless‌ Changes where implemented in the following files: \framework\LowPower\Source\QN908XC\PWR.c \framework\LowPower\Interface\QN908XC\PWR_Interface.h \freertos\fsl_tickless_generic.h \source\common\ApplMain.c The following file was removed from the project fsl_tickless_qn_rtc.c PWR.C and PWR_Interface.h Changes in this files are intended to prepare the QN9080 for waking up using the RTC timer. Other parts, like MKW41Z, might enable other modules for this purpose (like LPTMR) and changes on this files might not be necessary. *** PWR.c *** Add the driver for RTC. This is the timer we will use to wake up the QN908x /*Tickless: Add RTC driver for tickless support */ #include "fsl_rtc.h"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Add local variables uint64_t mLpmTotalSleepDuration;        //Tickless uint8_t mPWR_DeepSleepTimeUpdated = 0;  //Tickless: Coexistence with TMR manager‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Add private functions uint32_t PWR_RTCGetMsTimeUntilNextTick (void);         //Tickless void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs);   //Tickless void PWR_RTCWakeupStart (void);                        //Tickless‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Make the following changes in PWR.C. All the required changes are marked as comments with "Start" where the change starts, and with "End where the change ends" #if (cPWR_UsePowerDownMode && (cPWR_EnableDeepSleepMode_1 || cPWR_EnableDeepSleepMode_2 || cPWR_EnableDeepSleepMode_3 || cPWR_EnableDeepSleepMode_4)) static void PWR_HandleDeepSleepMode_1_2_3_4(void) { #if cPWR_BLE_LL_Enable     uint8_t   power_down_mode = 0xff;     bool_t    enterLowPower = TRUE;     __disable_irq(); /****************START***********************************/     /*Tickless: Configure wakeup timer */     if(mPWR_DeepSleepTimeUpdated){       PWR_RTCSetWakeupTimeMs(mPWR_DeepSleepTimeMs);       mPWR_DeepSleepTimeUpdated = FALSE;        // Coexistence with TMR Manager     }         PWR_RTCWakeupStart(); /*****************END**************************************/     PWRLib_ClearWakeupReason();     //Try to put BLE in deep sleep mode     power_down_mode = BLE_sleep();     if (power_down_mode < kPmPowerDown0)     {         enterLowPower = false; // BLE doesn't allow deep sleep     }     //no else - enterLowPower is already true     if(enterLowPower)     { /****************START**************************/         uint32_t freeRunningRtcPriority; /****************END****************************/         NVIC_ClearPendingIRQ(OSC_INT_LOW_IRQn);         NVIC_EnableIRQ(OSC_INT_LOW_IRQn);         while (SYSCON_SYS_STAT_OSC_EN_MASK & SYSCON->SYS_STAT) //wait for BLE to enter sleep         {             POWER_EnterSleep();         }         NVIC_DisableIRQ(OSC_INT_LOW_IRQn);         if(gpfPWR_LowPowerEnterCb != NULL)         {             gpfPWR_LowPowerEnterCb();         } /* Disable SysTick counter and interrupt */         sysTickCtrl = SysTick->CTRL & (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);         SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);         ICSR |= (1 << 25); // clear PendSysTick bit in ICSR, if set /************************START***********************************/         NVIC_ClearPendingIRQ(RTC_FR_IRQn);         freeRunningRtcPriority = NVIC_GetPriority(RTC_FR_IRQn);         NVIC_SetPriority(RTC_FR_IRQn,0); /***********************END***************************************/         POWER_EnterPowerDown(0); //Nighty night! /************************START**********************************/         NVIC_SetPriority(RTC_FR_IRQn,freeRunningRtcPriority); /************************END************************************/         if(gpfPWR_LowPowerExitCb != NULL)         {             gpfPWR_LowPowerExitCb();         }         /* Restore the state of SysTick */         SysTick->CTRL |= sysTickCtrl;         PWRLib_UpdateWakeupReason();     }     __enable_irq(); #else     PWRLib_ClearWakeupReason(); #endif /* cPWR_BLE_LL_Enable */ } #endif /* (cPWR_UsePowerDownMode && cPWR_EnableDeepSleepMode_1) */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ void PWR_SetDeepSleepTimeInMs(uint32_t deepSleepTimeMs) { #if (cPWR_UsePowerDownMode)     if(deepSleepTimeMs == 0)     {         return;     }     mPWR_DeepSleepTimeMs = deepSleepTimeMs; /****************START******************/     mPWR_DeepSleepTimeUpdated = TRUE; /****************END*********************/ #else     (void) deepSleepTimeMs; #endif /* (cPWR_UsePowerDownMode) */ }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Add/replace the following function definitions at the end of the file /*--------------------------------------------------------------------------- * Name: PWR_GetTotalSleepDurationMS * Description: - * Parameters: - * Return: - *---------------------------------------------------------------------------*/ uint32_t PWR_GetTotalSleepDurationMS(void) {     uint32_t time;     uint32_t currentSleepTime;     OSA_InterruptDisable();     currentSleepTime = RTC_GetFreeRunningInterruptThreshold(RTC);     if(currentSleepTime >= mLpmTotalSleepDuration){     time = (currentSleepTime-mLpmTotalSleepDuration)*1000/CLOCK_GetFreq(kCLOCK_32KClk);     }     else{     time = ((0x100000000-mLpmTotalSleepDuration)+currentSleepTime)*1000/CLOCK_GetFreq(kCLOCK_32KClk);     }     OSA_InterruptEnable();     return time; } /*--------------------------------------------------------------------------- * Name: PWR_ResetTotalSleepDuration * Description: - * Parameters: - * Return: - *---------------------------------------------------------------------------*/ void PWR_ResetTotalSleepDuration(void) {     OSA_InterruptDisable();     mLpmTotalSleepDuration = RTC_GetFreeRunningCount(RTC);     OSA_InterruptEnable(); } /*--------------------------------------------------------------------------- * Name: PWR_RTCGetMsTimeUntilNextTick * Description: - * Parameters: - * Return: Time until next tick in mS *---------------------------------------------------------------------------*/ uint32_t PWR_RTCGetMsTimeUntilNextTick (void) {     uint32_t time;     uint32_t currentRtcCounts, thresholdRtcCounts;     OSA_InterruptDisable();     currentRtcCounts = RTC_GetFreeRunningCount(RTC);     thresholdRtcCounts = RTC_GetFreeRunningResetThreshold(RTC);     if(thresholdRtcCounts > currentRtcCounts){     time = (thresholdRtcCounts-currentRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);     }     else{     time = ((0x100000000-currentRtcCounts)+thresholdRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);     }     OSA_InterruptEnable();     return time; } /*--------------------------------------------------------------------------- * Name: PWR_RTCSetWakeupTimeMs * Description: - * Parameters: wakeupTimeMs: New wakeup time in milliseconds * Return: - *---------------------------------------------------------------------------*/ void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs){     uint32_t wakeupTimeTicks;     uint32_t thresholdValue;     wakeupTimeTicks = (wakeupTimeMs*CLOCK_GetFreq(kCLOCK_32KClk))/1000;     thresholdValue = RTC_GetFreeRunningCount(RTC);     thresholdValue += wakeupTimeTicks;     RTC_SetFreeRunningInterruptThreshold(RTC, thresholdValue); } /*--------------------------------------------------------------------------- * Name: PWR_RTCWakeupStart * Description: - * Parameters: - * Return: - *---------------------------------------------------------------------------*/ void PWR_RTCWakeupStart (void){   if(!(RTC->CNT2_CTRL & RTC_CNT2_CTRL_CNT2_EN_MASK)){     RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_EN_MASK | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;   }   else{     RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;   } } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍  *** PWR_Interface.h *** Add the following function declarations at the end of the file /*--------------------------------------------------------------------------- * Name: PWR_GetTotalSleepDurationMS * Description: - * Parameters: - * Return: - *---------------------------------------------------------------------------*/ uint32_t PWR_GetTotalSleepDurationMS(void); /*--------------------------------------------------------------------------- * Name: PWR_ResetTotalSleepDuration * Description: - * Parameters: - * Return: - *---------------------------------------------------------------------------*/ void PWR_ResetTotalSleepDuration(void); #ifdef __cplusplus } #endif #endif /* _PWR_INTERFACE_H_ */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ FSL_TICKLESS_GENERIC The following changes have the purpose of preparing the system for recovering the missed ticks during the low power period. Make the following changes in fsl_tickless_generic.h. All the required changes are marked as comments with "Start" where the change starts, and with "End where the change ends" /* QN_RTC: The RTC free running is a 32-bit counter. */ #define portMAX_32_BIT_NUMBER (0xffffffffUL) #define portRTC_CLK_HZ (0x8000UL) /* A fiddle factor to estimate the number of SysTick counts that would have occurred while the SysTick counter is stopped during tickless idle calculations. */ #define portMISSED_COUNTS_FACTOR (45UL) /* * The number of SysTick increments that make up one tick period. */ /****************************START**************************/ #if configUSE_TICKLESS_IDLE == 1     static uint32_t ulTimerCountsForOneTick; #endif /* configUSE_TICKLESS_IDLE */ /************************END*********************************/ /* * Setup the timer to generate the tick interrupts. */ void vPortSetupTimerInterrupt(void); #ifdef __cplusplus } #endif #endif /* FSL_TICKLESS_GENERIC_H */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ ApplMain.c This is the main application file. Here is where we will call the proper APIs to enter the MCU in low power mode and perform the tick recovery sequence. Include RTC and FreeRTOS header files needed /*Tickless: Include RTC and FreeRTOS header files */ #include "fsl_rtc.h" #include "fsl_tickless_generic.h" #include "FreeRTOS.h" #include "task.h"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ QN9080 includes several low power modes. Sleep mode maintains most of the modules active. Power Down modes turn off most of the modules but allow the user to configure some modules to remain active to wake the MCU up when necessary. Using tickless FreeRTOS involves having to wake-up by some timer before the next ready task has to execute. For QN908x this timer will be the RTC which requires the 32.768kHz oscillator to remain active. We will change the Connectivity Software Power Lib to use Deep Sleep mode 3 (Power Down mode 0 for QN908x) which maintains the 32.768kHz oscillator on. This change is implemented in the main_task function. #if !defined(MULTICORE_BLACKBOX)         /* BLE Host Stack Init */         if (Ble_Initialize(App_GenericCallback) != gBleSuccess_c)         {             panic(0,0,0,0);             return;         } #endif /* MULTICORE_BLACKBOX */ /*************** Start ****************/ #if (cPWR_UsePowerDownMode)     PWR_ChangeDeepSleepMode(3); #endif /*************** End ****************/     }         /* Call application task */     App_Thread( param ); }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Also, tickless FreeRTOS requires a special Idle function which takes as an input parameter the amount of RTOS ticks the MCU can remain asleep before the next task needs to be executed. The following changes disable the default Idle function provided in the Connectivity Software demos when the tickless mode is enabled. /************************************************************************************ ************************************************************************************* * Private prototypes ************************************************************************************* ************************************************************************************/ #if (cPWR_UsePowerDownMode || gAppUseNvm_d) #if (mAppIdleHook_c)     #define AppIdle_TaskInit()     #define App_Idle_Task() #else #if (!configUSE_TICKLESS_IDLE)     static osaStatus_t AppIdle_TaskInit(void);     static void App_Idle_Task(osaTaskParam_t argument); #endif // configUSE_TICKLESS_IDLE #endif #endif‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ /************************************************************************************ ************************************************************************************* * Private memory declarations ************************************************************************************* ************************************************************************************/ /******************************** Start ******************************/ #if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE) /******************************** End ******************************/ #if (!mAppIdleHook_c) OSA_TASK_DEFINE( App_Idle_Task, gAppIdleTaskPriority_c, 1, gAppIdleTaskStackSize_c, FALSE ); osaTaskId_t gAppIdleTaskId = 0; #endif #endif  /* cPWR_UsePowerDownMode */‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ #if !gUseHciTransportDownward_d         pfBLE_SignalFromISR = BLE_SignalFromISRCallback; #endif /* !gUseHciTransportDownward_d */ /**************************** Start ************************/ #if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE) /**************************** End ************************/ #if (!mAppIdleHook_c)         AppIdle_TaskInit(); #endif #endif‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ /***************************START**************************/ #if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE) /******************************END***************************/ static void App_Idle(void) {     PWRLib_WakeupReason_t wakeupReason;     if( PWR_CheckIfDeviceCanGoToSleep() )     {         /* Enter Low Power */         wakeupReason = PWR_EnterLowPower(); #if gFSCI_IncludeLpmCommands_c         /* Send Wake Up indication to FSCI */         FSCI_SendWakeUpIndication(); #endif #if gKBD_KeysCount_c > 0         /* Woke up on Keyboard Press */         if(wakeupReason.Bits.FromKeyBoard)         {             KBD_SwitchPressedOnWakeUp();             PWR_DisallowDeviceToSleep();         } #endif     }     else     {         /* Enter MCU Sleep */         PWR_EnterSleep();     } } #endif /* cPWR_UsePowerDownMode */ #if (mAppIdleHook_c) void vApplicationIdleHook(void) { #if (gAppUseNvm_d)     NvIdle(); #endif /*******************************START****************************/ #if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE) /*********************************END*******************************/     App_Idle(); #endif } #else /* mAppIdleHook_c */ /******************************* START ****************************/ #if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE) /******************************* END ****************************/ static void App_Idle_Task(osaTaskParam_t argument) {     while(1)     {   #if gAppUseNvm_d         NvIdle(); #endif         #if (cPWR_UsePowerDownMode)         App_Idle(); #endif         /* For BareMetal break the while(1) after 1 run */         if (gUseRtos_c == 0)         {             break;         }     } } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Once the default Idle function has been disabled, the special Idle function must be implemented. Add the following code at the end of the ApplMain.c file. /*Tickless: Implement Tickless Idle */ #if (cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE) extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) {     uint32_t time_ms = xExpectedIdleTime * portTICK_PERIOD_MS;     uint32_t tmrMgrExpiryTimeMs;     ulTimerCountsForOneTick = 160000;//VALUE OF THE SYSTICK 10 ms #if (cPWR_UsePowerDownMode)     PWRLib_WakeupReason_t wakeupReason;         //TMR_MGR: Get next timer manager expiry time     tmrMgrExpiryTimeMs = TMR_GetFirstExpireTime(gTmrAllTypes_c);     // TMR_MGR: Update RTC Threshold only if RTOS needs to wakeup earlier     if(time_ms<tmrMgrExpiryTimeMs){       PWR_SetDeepSleepTimeInMs(time_ms);     }         PWR_ResetTotalSleepDuration();     if( PWR_CheckIfDeviceCanGoToSleep() )     {         wakeupReason = PWR_EnterLowPower();                 //Fix: All the tick recovery stuff should only happen if device entered in DSM         xExpectedIdleTime = PWR_GetTotalSleepDurationMS() / portTICK_PERIOD_MS;     // Fix: ticks = time in mS asleep / mS per each tick (portTICK_PERIOD_MS)         /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG         again, then set portNVIC_SYSTICK_LOAD_REG back to its standard         value. The critical section is used to ensure the tick interrupt         can only execute once in the case that the reload register is near         zero. */         portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;         portENTER_CRITICAL();         portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;         vTaskStepTick( xExpectedIdleTime );         portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;         portEXIT_CRITICAL(); #if gKBD_KeysCount_c > 0         /* Woke up on Keyboard Press */         if(wakeupReason.Bits.FromKeyBoard)         {           KBD_SwitchPressedOnWakeUp();           PWR_DisallowDeviceToSleep();         } #endif     }     else     {       /* Enter MCU Sleep */       PWR_EnterSleep();     } #endif /* cPWR_UsePowerDownMode */ } #endif  //cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ From the previous function, the value of ulTimerCountsForOneTick is used to restore the count of the RTOS tick timer after waking up. This value depends on the RTOS Tick interval defined in FreeRTOSConfig.h and is calculated using the following formula: SYST_RNR  =  F_Systick_CLK(Hz) * T_FreeRTOS_Ticks(ms) Where:       F_Systick_CLK(Hz) = AHB or 32KHz of the SYST_CSR selection       T_FreeRTOS_Ticks(ms) = tick count value. FreeRTOSConfig.h Finally, on the FreeRTOSConfig.h file, make sure that configUSE_TICKLESS_IDLE is set to 1 * See http://www.freertos.org/a00110.html. *----------------------------------------------------------*/ #define configUSE_PREEMPTION                    1 #define configUSE_TICKLESS_IDLE                 1 //<--- /***** Start *****/ #define configCPU_CLOCK_HZ                      (SystemCoreClock) #define configTICK_RATE_HZ                      ((TickType_t)100) #define configMAX_PRIORITIES                    (18) #define configMINIMAL_STACK_SIZE                ((unsigned short)90)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Testing Tickless RTOS In order to test if tickless support was successfully added, an example application that toggles an LED is implemented. This application configures an RTOS timer to toggle the LED once every 500mS and enter the MCU in DSM3 during the idle time. The Power Profiling demo was used for this purpose. power_profiling.c Make sure you have included the following header files #include "FreeRTOS.h" #include "task.h"‍‍‍‍ Create an RTOS task for blinking the LED every 500mS. First, declare the task function, task ID and the task itself. void vfnTaskLedBlinkTest(void* param); //New Task Definition OSA_TASK_DEFINE(vfnTaskLedBlinkTest, 1, 1, 500, FALSE ); osaTaskId_t gAppTestTask1Id = 0; // TestTask1 Id‍‍‍‍‍‍ Create the new task inside the BleApp_Init function void BleApp_Init(void) {     PWR_AllowDeviceToSleep();     mPowerState = 0;   // Board starts with PD1 enabled     /******************* Start *****************/     gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskLedBlinkTest), NULL); //Task Creation     /*******************  End  *****************/ }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Finally, add the task function definition at the end of the file. void vfnTaskLedBlinkTest(void* param) {     uint16_t wTimeValue = 500; //500ms     while(1)     {         LED_BLUE_TOGGLE();         vTaskDelay(pdMS_TO_TICKS(wTimeValue));     } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ We can monitor the power consumption in MCUXpresso IDE, with the Power Measurement Tool. With it, we can see the current that is been consumed and prove that the implementation is working as the expected. Configure the Power Measurement Tool Consumed current
View full article
Bluetooth Low Energy, through the Generic Attribute Profile (GATT), supports various ways to send and receive data between clients and servers. Data can be transmitted through indications, notifications, write requests and read requests. Data can also be transmitted through the Generic Access Profile (GAP) by using broadcasts. Here however, I'll focus on write and read requests. Write and read requests are made by a client to a server, to ask for data (read request) or to send data (write request). In these cases, the client first makes the request, and the server then responds, by either acknowledging the write request (and thus, writing the data) or by sending back the value requested by the client. To be able to make write and read requests, we must first understand how BLE handles the data it transmits. To transmit data back and forth between devices, BLE uses the GATT protocol. The GATT protocol handles data using a GATT database. A GATT database implements profiles, and each profile is made from a collection of services. These services each contain one or more characteristics. A BLE characteristic is made of attributes. These attributes constitute the data itself, and the handle to reference, access or modify said data. To have a characteristic that is able to be both written and read, it must be first created. This is done precisely in the GATT database file ( gatt_db.h 😞 /* gatt_db.h */ /* Custom service*/ PRIMARY_SERVICE_UUID128(service_custom, uuid_custom_service)     /* Custom characteristic with read and write properties */     CHARACTERISTIC_UUID128(char_custom, uuid_custom_char, (gGattCharPropRead_c | gGattCharPropWrite_c))         /* Custom length attribute with read and write permissions*/         VALUE_UUID128_VARLEN(value_custom, uuid_custom_char, (gPermissionFlagReadable_c | gPermissionFlagWritable_c), 50, 1, 0x00) The custom UUIDs are defined in the gatt_uuid128.h file: /* gatt_uuid128.h */ /* Custom 128 bit UUIDs*/ UUID128(uuid_custom_service, 0xE0, 0x1C, 0x4B, 0x5E, 0x1E, 0xEB, 0xA1, 0x5C, 0xEE, 0xF4, 0x5E, 0xBA, 0x00, 0x01, 0xFF, 0x01) UUID128(uuid_custom_char, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x17, 0x28, 0x39, 0x4A, 0x5B, 0x6C, 0x7D, 0x8E, 0x9F, 0x00) With this custom characteristic, we can write and read a value of up to 50 bytes (as defined by the variable length value declared in the gatt_db.h file, see code above). Remember that you also need to implement the interface and functions for the service. For further information and guidance in how to make a custom profile, please refer to the BLE application developer's guide (BLEDAG.pdf, located in <KW40Z_connSw_install_dir>\ConnSw\doc\BLEADG.pdf. Once a connection has been made, and you've got two (or more) devices connected, read and write requests can be made. I'll first cover how to make a write and read request from the client side, then from the server side. Client To make a write request to a server, you'll need to have the handle for the characteristic you want to modify. This handle should be stored once the characteristic discovery is done. Obviously, you also need the data that is going to be written. The following function needs a pointer to the data and the size of the data. It also uses the handle to tell the server what characteristic is going to be written: static void SendWriteReq(uint8_t* data, uint8_t dataSize) {       gattCharacteristic_t characteristic;     characteristic.value.handle = charHandle;     // Previously stored characteristic handle     GattClient_WriteCharacteristicValue( mPeerInformation.deviceId, &characteristic,                                          dataSize, data, FALSE,                                          FALSE, FALSE, NULL); } uint8_t wdata[15] = {"Hello world!\r"}; uint8_t size = sizeof(wdata); SendWriteReq(wdata, size); The data is send with the GattClient_WriteCharacteristicValue() API. This function has various configurable parameters to establish how to send the data. The function's parameters are described with detail on the application developer's guide, but basically, you can determine whether you need or not a response for the server, whether the data is signed or not, etc. Whenever a client makes a read or write request to the server, there is a callback procedure triggered,  to which the program then goes. This callback function has to be registered though. You can register the client callback function using the App_RegisterGattClientProcedureCallback() API: App_RegisterGattClientProcedureCallback(gattClientProcedureCallback); void gattClientProcedureCallback ( deviceId_t deviceId,                                    gattProcedureType_t procedureType,                                    gattProcedureResult_t procedureResult,                                    bleResult_t error ) {   switch (procedureType)   {        /* ... */        case gGattProcWriteCharacteristicValue_c:             if (gGattProcSuccess_c == procedureResult)             {                  /* Continue */             }             else             {                  /* Handle error */             }             break;        /* ... */   } } Reading an attribute is somewhat similar to writing an attribute, you still need the handle for the characteristic, and a buffer in which to store the read value: #define size 17 static void SendReadReq(uint8_t* data, uint8_t dataSize) {     /* Memory has to be allocated for the characteristic because the        GattClient_ReadCharacteristicValue() API runs in a different task, so        it has a different stack. If memory were not allocated, the pointer to        the characteristic would point to junk. */     characteristic = MEM_BufferAlloc(sizeof(gattCharacteristic_t));     data = MEM_BufferAlloc(dataSize);         characteristic->value.handle = charHandle;     characteristic->value.paValue = data;     bleResult_t result = GattClient_ReadCharacteristicValue(mPeerInformation.deviceId, characteristic, dataSize); } uint8_t rdata[size];         SendReadReq(rdata, size); As mentioned before, a callback procedure is triggered whenever there is a write or read request. This is the same client callback procedure used for the write request, but the event generates a different procedure type: void gattClientProcedureCallback ( deviceId_t deviceId,                                    gattProcedureType_t procedureType,                                    gattProcedureResult_t procedureResult,                                    bleResult_t error ) {   switch (procedureType)   {        /* ... */        case gGattProcReadCharacteristicValue_c:             if (gGattProcSuccess_c == procedureResult)             {                  /* Read value length */                  PRINT(characteristic.value.valueLength);                  /* Read data */                  for (uint16_t j = 0; j < characteristic.value.valueLength; j++)                  {                       PRINT(characteristic.value.paValue[j]);                  }             }             else             {               /* Handle error */             }             break;       /* ... */   } } There are some other methods to read an attribute. For further information, refer to the application developer's guide chapter 5, section 5.1.4 Reading and Writing Characteristics. Server Naturally, every time there is a request to either read or write by a client, there must be a response from the server. Similar to the callback procedure from the client, with the server there is also a callback procedure triggered when the client makes a request. This callback function will handle both the write and read requests, but the procedure type changes. This function should also be registered using the  App_RegisterGattServerCallback() API. When there is a read request from a client, the server responds with the read status: App_RegisterGattServerCallback( gattServerProcedureCallback ); void gattServerProcedureCallback ( deviceId_t deviceId,                                    gattServerEvent_t* pServerEvent ) {     switch (pServerEvent->eventType)     {         /* ... */         case gEvtAttributeRead_c:             GattServer_SendAttributeReadStatus(deviceId, value_custom, gAttErrCodeNoError_c);                             break;         /* ... */     } } When there is a write request however, the server should write the received data in the corresponding attribute in the GATT database. To do this, the function GattDb_WriteAttribute() can be used: void gattServerProcedureCallback ( deviceId_t deviceId,                                    gattServerEvent_t* pServerEvent ) {     switch (pServerEvent->eventType)     {         /* ... */         case gEvtAttributeWritten_c:             if (pServerEvent->eventData.attributeWrittenEvent.handle == value_custom)             {                 GattDb_WriteAttribute( pServerEvent->eventData.attributeWrittenEvent.handle,                                        pServerEvent->eventData.attributeWrittenEvent.cValueLength,                                        pServerEvent->eventData.attributeWrittenEvent.aValue );                              GattServer_SendAttributeWrittenStatus(deviceId, value_custom, gAttErrCodeNoError_c);             }             break;         /* ... */     } } If you do not register the server callback function, the attribute can still be written in the GATT database (it is actually done automatically), however, if you want something else to happen when you receive a request (turning on a LED, for example), you will need the server callback procedure.
View full article
       My customer asks if QN9080 can be tested with MT887x. We co-work with Anritsu Taiwan to integrate QN9080 and MT887x to perform 1M bps, 2M bps and Frame error rate test. This document will address the QN9080 setup and MT887x connection setup. We show the 1M bps, 2M bps and frame error rate results. The Anritsu equipment is applied to MT8870, MT8872 model name.        If you would like to perform the same test environment. You may contact Anritsu to get the latest "Auto-test tool " released by Anritsu and follow their SOP document to install "Auto-test tool" into PC to perform this RF validation test. 
View full article
[中文翻译版] 见附件   原文链接: https://community.nxp.com/docs/DOC-332703
View full article
With the release of the Bluetooth LE core erratum 10734, two new Host test cases (SM/SLA/KDU/BI-01-C and SM/MAS/KDU/BI-01-C) were added to the Test Case Reference List (TCRL) and are active since 24-Jan-19. This has an impact on new product qualifications based on Component (Tested) QDIDs that used an older TCRL when the test cases for this erratum were not required. Products that rely on NXP HOST QDIDs have 2 options for covering the erratum 10734 in order to complete the qualification: NXP provides a new qualification/QDID that includes these 2 tests. This is scheduled for later this year for QN908x, KW35/36 and KW41/31 products. NXP provides the test evidence/logs for these 2 tests and the test house reviews them before completing the product qualification. Right now, option 2 can be followed using the test evidence/logs provided by NXP. Later in the year, option 1 can be followed with an updated QDID. To obtain the test evidence/logs, please submit a support request.
View full article
Regarding to the "Reprogramming a KW36 device using the OTAP Client Software" and "Reprogramming a KW35 device using the OTAP Client Software" documents, there are some additional steps to debug the OTAP client software in the specific case when you use MCUXpresso together with a P&E micro debug probe. Just before to program the OTAP client project (the second software), the user must do the following: Open the "Debug Configurations" view clicking on the green bug as depicted below. Go to the "Debugger" perspective and search the "Advanced Options" button. Enable the "Preserve this range (Memory Range 0)" checkbox, and edit the textbox "From: 0" To: 1fff" for the KW36 device or "From: 0 To: 3fff" for the KW35 device. After to flash the device, disconnect and connect again. If everything it's OK, the RGB LED must blink (If you are using an FRDM board). Then, test the demo as described in the document.
View full article
The FRDM-KW36 comes with the OpenSDA circuit which allows users to program and debug the evaluation board. There are different solutions to support such OpenSDA circuits: 1. The J-Link (SEGGER) firmware.  2. The CMSIS-DAP (mbed) firmware. The FRDM-KW36 comes pre-programmed with the CMSIS-DAP firmware. However, if you want to update the firmware version, you need to perform the next steps.  Press and hold the Reset button (SW1 push button in the board).  Unplug and plug the FRDM-KW36 again to the PC.  The board will be enumerated as "DAPLINKBOOT" device. Drag and drop the binary file to update the OpenSDA firmware.  If the J-Link version is programmed, the board will be enumerated as "FRDM-KW36J". On the other hand, if the CMSIS-DAP version is programmed, the board will be enumerated as "FRDM-KW36". The binary for the J-link version can be downloaded from the next link: SEGGER - The Embedded Experts - Downloads - J-Link / J-Trace  The binary for the CMSIS-DAP version can be found in the next link: OpenSDA Serial and Debug Adapter|NXP    Hope this helps... 
View full article