Wireless Connectivity Knowledge Base

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

Wireless Connectivity Knowledge Base

Discussions

Sort by:
The FRDM-KW40Z 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 1 pF (which is effectively two 2 pF capacitors in series) all the way to 15 pF (30 pF capacitors in series). These values are obtained by combining the enabled capacitors. The values available are 2 pF, 4 pF, 8 pF and 16 pF. Any combination between these four can be done. It is recommended that these internal capacitors are disabled if the external capacitors are available. Figure 1. External capacitors for the 32 kHz crystal 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: 1. Since the RTC module is going to be used to output the oscillator frequency, the RTC_CLKOUT will be the output signal. The output pin for RTC_CLKOUT is PTB3. To configure PTB3 as the oscillator output use the function configure_rtc_pins(RTC instance). Port B clock must be enabled first. Figure 2. PTB3 pin mux   /* hardware_init.c */     /* enable clock for PORTs */   CLOCK_SYS_EnablePortClock(PORTA_IDX);   CLOCK_SYS_EnablePortClock(PORTB_IDX);   CLOCK_SYS_EnablePortClock(PORTC_IDX);   /* RTC CLKOUT */   configure_rtc_pins(0);     //This function changes the pin mux to select RTC_CLKOUT. It is included in the pin_mux.c file. 2.  Modify the System Options Register 1 (SIM_SOPT1) to select the clock source and to allow the selected clock source to be output in PTB3. The field you should change is OSC32KOUT. To select the clock source to be output on PTB3, use the function CLOCK_HAL_SetExternalRefClock32kSrc(). Remember to include the fsl_sim_hal.h file. Figure 3. SIM_SOPT1 register fields /* hardware_init.c */ /* RTC CLKOUT */ configure_rtc_pins(0); SIM_Type *simBase = g_simBase[0];     SIM_SOPT1 = SIM_SOPT1_OSC32KOUT(kClockRtcoutSrc32kHz);                // This field in register SIM_SOPT1 allows a clock source to be output to PTB3 CLOCK_HAL_SetExternalRefClock32kSrc(simBase, kClockEr32kSrcOsc0);     // This function chooses the clock source for the RTC 3.  Make sure the oscillator is enabled. The RTC external clock configurations can be found in the board.h file. This is also where the internal capacitors are enabled. /* board.h */ /* RTC external clock configuration. */ #define RTC_XTAL_FREQ                32768U #define RTC_SC2P_ENABLE_CONFIG       false      // 2 pF capacitors enable #define RTC_SC4P_ENABLE_CONFIG       false      // 4 pF capacitors enable #define RTC_SC8P_ENABLE_CONFIG       false      // 8 pF capacitors enable #define RTC_SC16P_ENABLE_CONFIG      false      // 16 pF capacitors enable #define RTC_OSC_ENABLE_CONFIG        true       // Oscillator enable 4.  You should now be able to measure the oscillator frequency through the PTB3 pin. Measurements should be done with a frequency counter, as change in the output can be very subtle, and an oscilloscope might not be able to pick it up. Remember that these capacitors give you the option to use the 32 kHz oscillator when you do not have the external capacitors. They are not supposed to be used when the external capacitors are being used. Now, to change the internal capacitance, you can simply change the macros contained in the board.h file (step 4). It is important that the oscillator is disabled before making any changes to the enabled capacitors. The fsl_clock_manager.c file already contains the function CLOCK_SYS_RtcOscInit() that configures the oscillator with the values established in the previously mentioned macros, however, the oscillator is not disabled before attempting to change the capacitors (and therefore, no changes are made). To fix this, you can use the RTC_HAL_SetOscillatorCmd() function to disable the oscillator before making changes with the capacitors. /* fsl_clock_manager.c */     // Disable the oscillator in case any changes are made to the capacitors RTC_HAL_SetOscillatorCmd(RTC, false);     // If the oscillator is not enabled and should be enabled. if ((!RTC_HAL_IsOscillatorEnabled(RTC)) && (config->enableOsc)) {     /* Enable the desired capacitors */     RTC_HAL_SetOsc2pfLoadCmd(RTC, config->enableCapacitor2p);     RTC_HAL_SetOsc4pfLoadCmd(RTC, config->enableCapacitor4p);     RTC_HAL_SetOsc8pfLoadCmd(RTC, config->enableCapacitor8p);     RTC_HAL_SetOsc16pfLoadCmd(RTC, config->enableCapacitor16p);         /* Re-enable the oscillator */     RTC_HAL_SetOscillatorCmd(RTC, config->enableOsc); } The fsl_clock_manager.c file can be found in: <KW40Z_connSw_install_dir>\KSDK_1.3.0\platform\system\src\clock Some reference measurements with different values for the internal capacitance: With external capacitors (internal capacitors disabled) Board Revision C LOAD Measured Frequency KW40Z – rev. C C46 & C47 32,769.03 Hz KW40Z – rev. B C46 & C47 32,769.27 Hz With internal capacitors (external capacitors removed) Enabled Capacitors C LOAD Measured Frequency SC2P 1 pF 32,773.21 Hz SC4P 2 pF 32,772.06 Hz SC2P, SC4P 3 pF 32,771.2 Hz SC4P, SC8P 6 pF 32,769.39 Hz SC2P, SC4P, SC8P 7 pF 32,769 Hz SC16P 8 pF 32,768.6 Hz SC2P, SC16P 9 pF 32,768.31 Hz SC4P, SC16P 10 pF 32,768.05 Hz SC2P, SC4P, SC16P 11 pF 32,767.83 Hz SC4P, SC8P, SC16P 14 pF 32,767.27 Hz SC2P, SC4P, SC8P, SC16P 15 pF 32,767.11 Hz 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 before 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.
View full article
The Thread Low Power End Device is preconfigured to have both the MCU in low power state and the radio turned off most of the time to preserve battery life. The device wakes up periodically and polls its parent router for data addressed to it or optionally initiates sending data to the network by means of the parent router. The low-power module (LPM) from the connectivity framework simplifies the process of putting a Kinetis-based wireless network node into the low-power or sleep modes. For the MKW41Z there are six low-power modes available. By default, the Thread Low Power End Device uses Deep sleep mode 3, where: MCU in LLS3 mode. Link layers remain idle. RAM is retained. The wake-up sources are: GPIO (push buttons). DCDC power switch (In buck mode). LPTMR with the 32kHz oscillator as clock source. The LPTMR timer is also used to measure the time that the MCU spends in deep sleep to synchronize low-power timers at wake-up. See the Connectivity Framework Reference Manual and PWR_Configuration.h for more information about the sleep deep modes. To change the polling time on deep sleep mode 3, we need to understand two macros:    1. The cPWR_DeepSleepDurationMs macro in \framework\LowPower\Interface\MKW41Z\PWR_Configuration.h. #ifndef cPWR_DeepSleepDurationMs   #define cPWR_DeepSleepDurationMs                3000 #endif ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ This macro determines how long the MCU will go to low power mode (deep sleep). The maximum value is 65535000 ms (18.2 h). 2. The THR_SED_POLLING_INTERVAL_MS macro in \source\config.h. /*! The default value for sleepy end device (SED) polling interval */ #ifndef THR_SED_POLLING_INTERVAL_MS     #define THR_SED_POLLING_INTERVAL_MS                     3000     /* Milliseconds */ #endif ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ This macro determines how often the Low Power End Device will send a poll message to its parent. NOTE: This value does not determine how often the MCU wakes up. The polling interval should be a multiple of the Deep sleep duration value, otherwise the poll will be sent at the next deep sleep time out. As an example, let's say we configure the polling interval to 4000ms and the deep sleep duration to 3000ms. The MCU will wake up every 3000ms but the poll message will be sent every 2 deep sleep timeouts = 6000ms because the timers are synchronized when the MCU wakes up. The following figure shows the behavior of this example. It is recommended that the polling interval is the same as the deep sleep duration, so the MCU doesn't wake up unnecessarily. The following figure shows this behavior. Another macro to keep in mind is THR_SED_TIMEOUT_PERIOD_SEC in app_thread_config.h. #ifndef THR_SED_TIMEOUT_PERIOD_SEC     #define THR_SED_TIMEOUT_PERIOD_SEC                 ((4*THR_SED_POLLING_INTERVAL_MS)/1000 + 3) #endif ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ This value is the timeout period used by the parent to consider a sleepy end device (SED) disconnected. By default, this value is configured to be 4 times the polling interval + 3s. It is recommended to leave this macro as it is. This value is sent to the parent node during the commissioning.
View full article
In this document we will be seeing how to create a BLE demo application for an adopted BLE profile based on another demo application with a different profile. In this demo, the Pulse Oximeter Profile will be implemented.  The PLX (Pulse Oximeter) Profile was adopted by the Bluetooth SIG on 14th of July 2015. You can download the adopted profile and services specifications on https://www.bluetooth.org/en-us/specification/adopted-specifications. The files that will be modified in this post are, app.c,  app_config.c, app_preinclude.h, gatt_db.h, pulse_oximeter_service.c and pulse_oximeter_interface.h. A profile can have many services, the specification for the PLX profile defines which services need to be instantiated. The following table shows the Sensor Service Requirements. Service Sensor Pulse Oximeter Service Mandatory Device Information Service Mandatory Current Time Service Optional Bond Management Service Optional Battery Service Optional Table 1. Sensor Service Requirements For this demo we will instantiate the PLX service, the Device Information Service and the Battery Service. Each service has a source file and an interface file, the device information and battery services are already implemented, so we will only need to create the pulse_oximeter_interface.h file and the pulse_oximeter_service.c file. The PLX Service also has some requirements, these can be seen in the PLX service specification. The characteristic requirements for this service are shown in the table below. Characteristic Name Requirement Mandatory Properties Security Permissions PLX Spot-check Measurement C1 Indicate None PLX Continuous Measurement C1 Notify None PLX Features Mandatory Read None Record Access Control Point C2 Indicate, Write None Table 2. Pulse Oximeter Service Characteristics C1: Mandatory to support at least one of these characteristics. C2: Mandatory if measurement storage is supported for Spot-check measurements. For this demo, all the characteristics will be supported. Create a folder for the pulse oximeter service in  \ConnSw\bluetooth\profiles named pulse_oximeter and create the pulse_oximeter_service.c file. Next, go to the interface folder in \ConnSw\bluetooth\profiles and create the pulse_oximeter_interface.h file. At this point these files will be blank, but as we advance in the document we will be adding the service implementation and the interface macros and declarations. Clonate a BLE project with the cloner tool. For this demo the heart rate sensor project was clonated. You can choose an RTOS between bare metal or FreeRTOS. You will need to change some workspace configuration.  In the bluetooth->profiles->interface group, remove the interface file for the heart rate service and add the interface file that we just created. Rename the group named heart_rate in the bluetooth->profiles group to pulse_oximeter and remove the heart rate service source file and add the pulse_oximeter_service.c source file. These changes will be saved on the actual workspace, so if you change your RTOS you need to reconfigure your workspace. To change the device name that will be advertised you have to change the advertising structure located in app_config.h. /* Scanning and Advertising Data */ static const uint8_t adData0[1] =  { (gapAdTypeFlags_t)(gLeGeneralDiscoverableMode_c | gBrEdrNotSupported_c) }; static const uint8_t adData1[2] = { UuidArray(gBleSig_PulseOximeterService_d)}; static const gapAdStructure_t advScanStruct[] = { { .length = NumberOfElements(adData0) + 1, .adType = gAdFlags_c, .aData = (void *)adData0 }, { .length = NumberOfElements(adData1) + 1, .adType = gAdIncomplete16bitServiceList_c, .aData = (void *)adData1 }, { .adType = gAdShortenedLocalName_c, .length = 8, .aData = "FSL_PLX" } }; We also need to change the address of the device so we do not have conflicts with another device with the same address. The definition for the address is located in app_preinclude.h and is called BD_ADDR. In the demo it was changed to: #define BD_ADDR 0xBE,0x00,0x00,0x9F,0x04,0x00 Add the definitions in ble_sig_defines.h located in Bluetooth->host->interface for the UUID’s of the PLX service and its characteristics. /*! Pulse Oximeter Service UUID */ #define gBleSig_PulseOximeterService_d         0x1822 /*! PLX Spot-Check Measurement Characteristic UUID */ #define gBleSig_PLXSpotCheckMeasurement_d      0x2A5E /*! PLX Continuous Measurement Characteristic UUID */ #define gBleSig_PLXContinuousMeasurement_d     0x2A5F /*! PLX Features Characteristic UUID */ #define gBleSig_PLXFeatures_d                  0x2A60 /*! Record Access Control Point Characteristic UUID */ #define gBleSig_RecordAccessControlPoint_d     0x2A52 We need to create the GATT database for the pulse oximeter service. The requirements for the service can be found in the PLX Service specification. The database is created at compile time and is defined in the gatt_db.h.  Each characteristic can have certain properties such as read, write, notify, indicate, etc. We will modify the existing database according to our needs. The database for the pulse oximeter service should look something like this. PRIMARY_SERVICE(service_pulse_oximeter, gBleSig_PulseOximeterService_d)     CHARACTERISTIC(char_plx_spotcheck_measurement, gBleSig_PLXSpotCheckMeasurement_d, (gGattCharPropIndicate_c))         VALUE_VARLEN(value_PLX_spotcheck_measurement, gBleSig_PLXSpotCheckMeasurement_d, (gPermissionNone_c), 19, 3, 0x00, 0x00, 0x00)         CCCD(cccd_PLX_spotcheck_measurement)     CHARACTERISTIC(char_plx_continuous_measurement, gBleSig_PLXContinuousMeasurement_d, (gGattCharPropNotify_c))         VALUE_VARLEN(value_PLX_continuous_measurement, gBleSig_PLXContinuousMeasurement_d, (gPermissionNone_c), 20, 3, 0x00, 0x00, 0x00)         CCCD(cccd_PLX_continuous_measurement)     CHARACTERISTIC(char_plx_features, gBleSig_PLXFeatures_d, (gGattCharPropRead_c))         VALUE_VARLEN(value_plx_features, gBleSig_PLXFeatures_d, (gPermissionFlagReadable_c), 7, 2, 0x00, 0x00)     CHARACTERISTIC(char_RACP, gBleSig_RecordAccessControlPoint_d, (gGattCharPropIndicate_c | gGattCharPropWrite_c))         VALUE_VARLEN(value_RACP, gBleSig_RecordAccessControlPoint_d, (gPermissionNone_c), 4, 3, 0x00, 0x00, 0x00)         CCCD(cccd_RACP) For more information on how to create a GATT database you can check the BLE Application Developer’s Guide chapter 7. Now we need to make the interface file that contains all the macros and declarations of the structures needed by the PLX service. Enumerated types need to be created for each of the flags field or status field of every characteristic of the service. For example, the PLX Spot-check measurement field has a flags field, so we declare an enumerated type that will help us keep the program organized and well structured. The enum should look something like this: /*! Pulse Oximeter Service - PLX Spotcheck Measurement Flags */ typedef enum {     gPlx_TimestampPresent_c                      = BIT0,     /* C1 */     gPlx_SpotcheckMeasurementStatusPresent_c     = BIT1,     /* C2 */     gPlx_SpotcheckDeviceAndSensorStatusPresent_c = BIT2,     /* C3 */     gPlx_SpotcheckPulseAmplitudeIndexPresent_c   = BIT3,     /* C4 */     gPlx_DeviceClockNotSet_c                     = BIT4 } plxSpotcheckMeasurementFlags_tag; The characteristics that will be indicated or notified need to have a structure type that contains all the fields that need to be transmitted to the client. Some characteristics will not always notify or indicate the same fields, this varies depending on the flags field and the requirements for each field. In order to notify a characteristic we need to check the flags in the measurement structure to know which fields need to be transmitted. The structure for the PLX Spot-check measurement should look something like this: /*! Pulse Oximeter Service - Spotcheck Measurement */ typedef struct plxSpotcheckMeasurement_tag {     ctsDateTime_t              timestamp;             /* C1 */     plxSpO2PR_t                SpO2PRSpotcheck;       /* M */     uint32_t                   deviceAndSensorStatus; /* C3 */     uint16_t                   measurementStatus;     /* C2 */     ieee11073_16BitFloat_t     pulseAmplitudeIndex;   /* C4 */     uint8_t                    flags;                 /* M */ }plxSpotcheckMeasurement_t; The service has a configuration structure that contains the service handle, the initial features of the PLX Features characteristic and a pointer to an allocated space in memory to store spot-check measurements. The interface will also declare some functions such as Start, Stop, Subscribe, Unsubscribe, Record Measurements and the control point handler. /*! Pulse Oximeter Service - Configuration */ typedef struct plxConfig_tag {     uint16_t      serviceHandle;     plxFeatures_t plxFeatureFlags;     plxUserData_t *pUserData;     bool_t        procInProgress; } plxConfig_t; The service source file implements the service specific functionality. For example, in the PLX service, there are functions to record the different types of measurements, store a spot-check measurement in the database, execute a procedure for the RACP characteristic, validate a RACP procedure, etc. It implements the functions declared in the interface and some static functions that are needed to perform service specific tasks. To initialize the service you use the start function. This function initializes some characteristic values. In the PLX profile, the Features characteristic is initialized and a timer is allocated to indicate the spot-check measurements periodically when the Report Stored Records procedure is written to the RACP characteristic. The subscribe and unsubscribe functions are used to update the device identification when a device is connected to the server or disconnected. bleResult_t Plx_Start (plxConfig_t *pServiceConfig) {         mReportTimerId = TMR_AllocateTimer();         return Plx_SetPLXFeatures(pServiceConfig->serviceHandle, pServiceConfig->plxFeatureFlags); } All of the services implementations follow a similar template, each service can have certain characteristics that need to implement its own custom functions. In the case of the PLX service, the Record Access Control Point characteristic will need many functions to provide the full functionality of this characteristic. It needs a control point handler, a function for each of the possible procedures, a function to validate the procedures, etc. When the application makes a measurement it must fill the corresponding structure and call a function that will write the attribute in the database with the correct fields and then send an indication or notification. This function is called RecordMeasurement and is similar between the majority of the services. It receives the measurement structure and depending on the flags of the measurement, it writes the attribute in the GATT database in the correct format. One way to update a characteristic is to create an array of the maximum length of the characteristic and check which fields need to be added and keep an index to know how many bytes will be written to the characteristic by using the function GattDb_WriteAttribute(handle, index, &charValue[0]). The following function shows an example of how a characteristic can be updated. In the demo the function contains more fields, but the logic is the same. static bleResult_t Plx_UpdatePLXContinuousMeasurementCharacteristic ( uint16_t handle, plxContinuousMeasurement_t *pMeasurement ) {     uint8_t charValue[20];     uint8_t index = 0;     /* Add flags */     charValue[0] = pMeasurement->flags;     index++;     /* Add SpO2PR-Normal */     FLib_MemCpy(&charValue[index], &pMeasurement->SpO2PRNormal, sizeof(plxSpO2PR_t));     index += sizeof(plxSpO2PR_t);         /* Add SpO2PR-Fast */     if (pMeasurement->flags & gPlx_SpO2PRFastPresent_c)     {       FLib_MemCpy(&charValue[index], &pMeasurement->SpO2PRFast, sizeof(plxSpO2PR_t));       index += sizeof(plxSpO2PR_t);     }        return GattDb_WriteAttribute(handle, index, &charValue[0]); } The app.c handles the application specific functionality. In the PLX demo it handles the timer callback to make a PLX continuous measurement every second. It handles the key presses and makes a spot-check measurement each time the SW3 pushbutton is pressed. The GATT server callback receives an event when an attribute is written, and in our application the RACP characteristic is the only one that can be written by the client. When this event occurs, we call the Control Point Handler function. This function makes sure the indications are properly configured and check if another procedure is in progress. Then it calls the Send Procedure Response function, this function validates the procedure and calls the Execute Procedure function. This function will call one of the 4 possible procedures. It can call Report Stored Records, Report Number of Stored Records, Abort Operation or Delete Stored Records. When the project is running, the 4 LEDs will blink indicating an idle state. To start advertising, press the SW4 button and the LED1 will start flashing. When the device has connected to a client the LED1 will stop flashing and turn on. To disconnect the device, hold the SW4 button for some seconds. The device will return to an advertising state. In this demo, the spot-check measurement is made when the SW3 is pressed, and the continuous measurement is made every second. The spot-check measurement can be stored by the application if the Measurement Storage for spot-check measurements is supported (bit 2 of Supported Features Field in the PLX Features characteristic). The RACP characteristic lets the client control the database of the spot-check measurements, you can request the existing records, delete them, request the number of stored records or abort a procedure. To test the demo you can download and install the nRF Master Control application by Nordic Semiconductor on an Android Smartphone that supports BLE. This app lets you discover the services in the sensor and interact with each characteristic. The application will parse known characteristics, but because the PLX profile is relatively new, these characteristics will not be parsed and the values will be displayed in a raw format. Figure 1. nRF Master Control app
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
In developing a Zigbee application, certain static configuration is required before the application is built. Configuring the network size, adding a new cluster, making the device discoverable and adding a new endpoint can be done by changing parameters in the following files: app_zps_cfg.h app_zcl_cfg.h app_zcl_global.c These files are responsible for setting up network parameters like device type and associated parameters, mainly related to the APS and NWK layers of the ZigBee PRO stack. Network Configuration The ZigBee device can be configured to be a coordinator, router and end device. The following section details the way in which the user can configure each device type. The app_zps_cfg header file lets the user configure the ZPS ZDO parameters of the node. The following macros are necessary for the corresponding device types: For coordinator in a ZigBee network #define ZPS_COORDINATOR #define ZPS_ZDO_DEVICE_TYPE                              ZPS_ZDO_DEVICE_COORD For router in a ZigBee network #define ZPS_ROUTER #define ZPS_ZDO_DEVICE_TYPE                              ZPS_ZDO_DEVICE_ROUTER For enddevice in a ZigBee network #define ZPS_ENDDEVICE #define ZPS_ZDO_DEVICE_TYPE                              ZPS_ZDO_DEVICE_ENDDEVICE Other ZPS ZDO configurations which are defined using macro are explained in comments inside the header file (app_zps_cfg.h). These macros provide the user with the ability to configure the device according to their network needs. The type of security for the ZigBee network can also be configured by the macro ZPS_ZDO_NWK_KEY_STATE. The user can change the security type to no network security (ZPS_ZDO_NO_NETWORK_KEY), pre-configured link key security (ZPS_ZDO_PRECONFIGURED_LINK_KEY), distributed link key security (ZPS_ZDO_DISTRIBUTED_LINK_KEY) or pre-configured installation code security (ZPS_ZDO_PRCONFIGURED_INSTALLATION_CODE). /* Specify No network Security */ #define ZPS_ZDO_NWK_KEY_STATE                               ZPS_ZDO_NO_NETWORK_KEY The application allows through this header file to configure ZPS APS AIB parameters, like extended PANID (ZPS_APS_AIB_INIT_USE_EXTENDED_PANID) or channel mask (ZPS_APS_AIB_INIT_CHANNEL_MASK). /* NWK EXTENDED PANID (EPID) that the device will use.*/ #define ZPS_APS_AIB_INIT_USE_EXTENDED_PANID                 0x0000000000000000ULL /*! CHANNEL MASK : Define all channels from 11 to 26*/ #define ZPS_APS_AIB_INIT_CHANNEL_MASK                       0x07fff800UL User can also configure the simple descriptor table size (AF_SIMPLE_DESCRIPTOR_TABLE_SIZE) as part of the ZPS AF Layer configuration parameters.The value depends on number of endpoints defined in application, one endpoint is always reserved for ZDO . So, for a device with one endpoint, the value would be 2 (1 ZDO + 1 application endpoint) #define AF_SIMPLE_DESCRIPTOR_TABLE_SIZE                     2 Among other ZPS network configuration parameters that can be changed by the user are scan duration (ZPS_SCAN_DURATION), default permit joining time (ZPS_DEFAULT_PERMIT_JOINING_TIME) and the maximum number of simultaneous key requests (ZPS_MAX_NUM_SIMULTANEOUS_REQUEST_KEY_REQS). Also, NIB values can be changed, like for example, the maximum number of routers in the network (ZPS_NWK_NIB_INIT_MAX_ROUTERS), the maximum number of children for a node (ZPS_NWK_NIB_INIT_MAX_CHILDREN), the maximum network depth (ZPS_NWK_NIB_INIT_MAX_DEPTH) or the network security level (ZPS_NWK_NIB_INIT_SECURITY_LEVEL). Different ZigBee network table sizes can be adjusted by the user from this header file. The important tables are mentioned below: The active neighbor table size (ZPS_NEIGHBOUR_TABLE_SIZE). The neighbor discovery table size, used to keep a list of the neighboring devices associated with the node (ZPS_NEIGHBOUR_DISCOVERY_TABLE_SIZE). The network address map table size, which represents the size of the address map that maps 64-bit IEEE addresses to 16-bit network (short) addresses (ZPS_ADDRESS_MAP_TABLE_SIZE). The network security material set size (ZPS_SECURITY_MATERIAL_SETS). The broadcast transaction table size, which stores the records of the broadcast messages received by the node (ZPS_BROADCAST_TRANSACTION_TABLE_SIZE). The route record table size (ZPS_ROUTE_RECORD_TABLE_SIZE) for the table that records each route, storing the destination network address, a count of the number of relay nodes to reach the destination and a list of the network addresses of the relay nodes. The route discovery table size (ZPS_ROUTE_DISCOVERY_TABLE_SIZE), used by the node to store temporary information used during route discovery. The MAC address table size (ZPS_MAC_ADDRESS_TABLE_SIZE). The binding table size (ZPS_BINDING_TABLE_SIZE). The group table size (ZPS_GROUP_TABLE_SIZE). The number of supported network keys, known also as the security material sets (ZPS_KEY_TABLE_SIZE). The child table size (ZPS_CHILD_TABLE_SIZE), that gives the size of the persisted sub-table of the active neighbor table. The stored entries are for the node’s parent and immediate children. The trust center device table size (ZPS_TRUST_CENTER_DEVICE_TABLE_SIZE). ZCL Configuration The app_zcl_cfg header file is used by the application to configure the ZigBee Cluster library. This file contains the definition for the application profile and cluster ids. The default application profiles are ZDP, HA, ZLO, GP. The ZDP (ZigBee Device Profile) id is identified by the following line: #define ZDP_PROFILE_ID             (0x0000) ZDP provides services for the following categories as cluster Ids: Device discovery services (for example, ZDP_DISCOVERY_CACHE_REQ_CLUSTER_ID) Service discovery services (for example, ZDP_IEEE_ADDR_REQ_CLUSTER_ID) Binding services (for example, ZDP_BIND_RSP_CLUSTER_ID) Management services (for example, ZDP_MGMT_NWK_DISC_REQ_CLUSTER_ID) The HA (Home Automation) profile id is identified by the following line: #define HA_PROFILE_ID             (0x0104) HA provides services for the following categories as cluster Ids: Generic devices (for example, HA_BASIC_CLUSTER_ID) Lighting devices (for example, HA_LEVELCONTROL_CLUSTER_ID) Intruder Alarm System (IAS) devices (for example, HA_IASZONE_CLUSTER_ID) The ZLO (ZigBee Lighting and Occupancy) profile is not an application profile but devices in this collection use the same application profile id as for the Home Automation application profile. This ensures backward compatibility with applications for devices based on the Home Automation 1.2 profile. ZigBee Green Power (GP) is an optional cluster with the aim of minimizing the power demands on a network node in order to support: Nodes that are completely self-powered through energy harvesting Battery-powered nodes that require ultra-long battery life The GP profile id is identified by the following line: #define GP_PROFILE_ID               (0xa1e0) The Zigbee GP cluster ID is defined as following: #define GP_GREENPOWER_CLUSTER_ID    (0x0021) Depending on the application, the app_zcl_cfg header file also contains the defines for the node endpoints. For example, the occupancy_sensor application contains the following endpoints: /* Node 'Coordinator' */ /* Endpoints */ #define COORDINATOR_ZDO_ENDPOINT    (0) #define COORDINATOR_COORD_ENDPOINT    (1) /* Node 'OccupancySensor' */ /* Endpoints */ #define OCCUPANCYSENSOR_ZDO_ENDPOINT    (0) #define OCCUPANCYSENSOR_SENSOR_ENDPOINT    (1)   /* Node 'LightSensor' */ /* Endpoints */ #define LIGHTSENSOR_ZDO_ENDPOINT    (0) #define LIGHTSENSOR_SENSOR_ENDPOINT    (1)   /* Node 'LightTemperatureOccupancySensor' */ /* Endpoints */ #define LIGHTTEMPERATUREOCCUPANCYSENSOR_ZDO_ENDPOINT    (0) #define LIGHTTEMPERATUREOCCUPANCYSENSOR_SENSOR_ENDPOINT    (1) The source file app_zcl_globals.c is used to declare the cluster lists for each endpoint. These act as simple descriptors for the node. Each endpoint has two cluster lists, containing uint16_t data. One is for input and one for output. The sizes of these two lists must be equal. For example, for endpoint 0, the declared lists will be the following: PRIVATE const uint16 s_au16Endpoint0InputClusterList[16]  =  { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006 , 0x0007, \                                                               0x0008, 0x0010, 0x0011, 0x0012, 0x0012, 0x0013, 0x0014 , 0x0015}; PRIVATE const uint16 s_au16Endpoint0OutputClusterList[16] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006 , 0x0007, \                                                              0x0008, 0x0010, 0x0011, 0x0012, 0x0012, 0x0013, 0x0014 , 0x0015}; The input list must also have a corresponding cluster APDU list, matching in size. For the endpoint 0 example, this will look like: PRIVATE const PDUM_thAPdu s_ahEndpoint0InputClusterAPdus[16] = { apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP,\                                                                  apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP, apduZDP}; Each output and input cluster list has a corresponding cluster discovery enabled flags list. As each bit inside the Cluster Disc Flag corresponds to cluster , for 16 clusters declared in Input and Output cluster list, one needs 2 bytes for Discoverable flag. In this example, the declaration is the following: PRIVATE uint8 s_au8Endpoint0InputClusterDiscFlags[2] = {0x1F, 0x08}; PRIVATE uint8 s_au8Endpoint0OutputClusterDiscFlags[2] = {0x08, 0x1B}; These parameters are registered in the node’s endpoints simple descriptor structure. The declared variable for the structure is s_asSimpleDescConts and its size depends on the number of endpoints available on the node. For example, for two endpoints, the declaration will be as below: PUBLIC zps_tsAplAfSimpleDescCont s_asSimpleDescConts[2] = {     {         {             0x0000,             0,             0,             0,             84,             84,             s_au16Endpoint0InputClusterList,             s_au16Endpoint0OutputClusterList,             s_au8Endpoint0InputClusterDiscFlags,             s_au8Endpoint0OutputClusterDiscFlags,         },         s_ahEndpoint0InputClusterAPdus,         1     },     {         {             0x0104,             0,             0,             1,             6,             4,             s_au16Endpoint1InputClusterList,             s_au16Endpoint1OutputClusterList,             s_au8Endpoint1InputClusterDiscFlags,             s_au8Endpoint1OutputClusterDiscFlags,         },         s_ahEndpoint1InputClusterAPdus,         1     }, }; The AF Context definition is as below: typedef struct _zps_tsAplAfSimpleDescCont {     ZPS_tsAplAfSimpleDescriptor sSimpleDesc;     const PDUM_thAPdu *phAPduInClusters;     bool_t bEnabled; } zps_tsAplAfSimpleDescCont; And the endpoint simple descriptor has the following structure definition: typedef struct {     uint16 u16ApplicationProfileId;     uint16 u16DeviceId;     uint8  u8DeviceVersion;     uint8  u8Endpoint;     uint8  u8InClusterCount;     uint8  u8OutClusterCount;     const uint16 *pu16InClusterList;     const uint16 *pu16OutClusterList;     uint8 *au8InDiscoveryEnabledFlags;     uint8 *au8OutDiscoveryEnabledFlags; } ZPS_tsAplAfSimpleDescriptor;
View full article
This document describes how to add additional endpoints to the Router application in the AN12061-MKW41Z-AN-Zigbee-3-0-Base-Device Application Note.   The Router application's main endpoint acts as a light controlled by the On/Off cluster acting as a Server. The steps below describe how to add two new endpoints with On/Off clusters acting as clients.   Note that these changes only go as far as making the new endpoints discoverable, no functionality has been added to read inputs and transmit commands from the new endpoints. Router/app_zcl_cfg.h The first step is to add the new endpoints (Switch1, Switch2) into ZCL configuration file. /* Endpoints */ #define ROUTER_ZDO_ENDPOINT         (0) #define ROUTER_APPLICATION_ENDPOINT (1) #define ROUTER_SWITCH1_ENDPOINT     (2) #define ROUTER_SWITCH2_ENDPOINT     (3) Router/app_zps_cfg.h The second step is to update the ZigBee Configuration file to increase the simple descriptor table size from 2 to 4, as it is the number of application endpoints (3 in our case) + 1 (ZDO endpoint).  : /*****************************************************************************/ /* ZPS AF Layer Configuration Parameters */ /*****************************************************************************/ #define AF_SIMPLE_DESCRIPTOR_TABLE_SIZE 4 Router/app_zcl_globals.c The third step is to update the ZigBee cluster Configuration file to add the new endpoints (Switch1, Switch2) and their clusters to the Router application. For that one need to change the Configured endpoint from 1 to 3 and also the Endpoint Map list present as below: PUBLIC uint8 u8MaxZpsConfigEp = 3; PUBLIC uint8 au8EpMapPresent[3] = { ROUTER_APPLICATION_ENDPOINT,ROUTER_SWITCH1_ENDPOINT,ROUTER_SWITCH2_ENDPOINT }; The Switch 1 and Switch 2 contains Basic Cluster (0x0000) Server and Client, Identify Cluster (0x0003) Server and Client, OnOff Cluster (0x0006) Client, Group Cluster (0x004) Client. The clusters are added to the Input cluster list (Server side) and output cluster list (Client side) but made discoverable using DiscFlag only for the cluster list which is enabled. So, assuming you need to add OnOff cluster client, you would need to use add the cluster id (0x0006 for OnOff) into input cluster list (Server side of cluster) and output cluster list (Client side of the cluster) and make it discoverable for output cluster list as it is a client cluster. PRIVATE const uint16 s_au16Endpoint2InputClusterList[5] = { HA_BASIC_CLUSTER_ID, HA_GROUPS_CLUSTER_ID, HA_IDENTIFY_CLUSTER_ID,\ HA_ONOFF_CLUSTER_ID, HA_DEFAULT_CLUSTER_ID, }; PRIVATE const PDUM_thAPdu s_ahEndpoint2InputClusterAPdus[5] = { apduZCL, apduZCL, apduZCL, apduZCL, apduZCL, }; PRIVATE uint8 s_au8Endpoint2InputClusterDiscFlags[1] = { 0x05 }; PRIVATE const uint16 s_au16Endpoint2OutputClusterList[4] = { HA_BASIC_CLUSTER_ID, HA_GROUPS_CLUSTER_ID, HA_IDENTIFY_CLUSTER_ID,\ HA_ONOFF_CLUSTER_ID, }; PRIVATE uint8 s_au8Endpoint2OutputClusterDiscFlags[1] = { 0x0f }; PRIVATE const uint16 s_au16Endpoint3InputClusterList[5] = { HA_BASIC_CLUSTER_ID, HA_GROUPS_CLUSTER_ID, HA_IDENTIFY_CLUSTER_ID,\ HA_ONOFF_CLUSTER_ID, HA_DEFAULT_CLUSTER_ID, }; PRIVATE const PDUM_thAPdu s_ahEndpoint3InputClusterAPdus[5] = { apduZCL, apduZCL, apduZCL, apduZCL, apduZCL, }; PRIVATE uint8 s_au8Endpoint3InputClusterDiscFlags[1] = { 0x05 }; PRIVATE const uint16 s_au16Endpoint3OutputClusterList[4] = { HA_BASIC_CLUSTER_ID, HA_GROUPS_CLUSTER_ID, HA_IDENTIFY_CLUSTER_ID,\ HA_ONOFF_CLUSTER_ID, }; PRIVATE uint8 s_au8Endpoint3OutputClusterDiscFlags[1] = { 0x0f }; Now add these newly added endpoints as part of Simple Descriptor structure and initialize the structure (see the declaration of zps_tsAplAfSimpleDescCont and ZPS_tsAplAfSimpleDescriptor structures to understand how to correctly fill the various parameters) correctly as below : PUBLIC zps_tsAplAfSimpleDescCont s_asSimpleDescConts[AF_SIMPLE_DESCRIPTOR_TABLE_SIZE] = { {    {       0x0000,       0,       0,       0,       84,       84,       s_au16Endpoint0InputClusterList,       s_au16Endpoint0OutputClusterList,       s_au8Endpoint0InputClusterDiscFlags,       s_au8Endpoint0OutputClusterDiscFlags,    },    s_ahEndpoint0InputClusterAPdus,    1 }, {    {       0x0104,       0,       1,       1,       5,       4,       s_au16Endpoint1InputClusterList,       s_au16Endpoint1OutputClusterList,       s_au8Endpoint1InputClusterDiscFlags,       s_au8Endpoint1OutputClusterDiscFlags,    },    s_ahEndpoint1InputClusterAPdus,    1 }, {    {       0x0104,       0,       1,       2,       5,       4,       s_au16Endpoint2InputClusterList,       s_au16Endpoint2OutputClusterList,       s_au8Endpoint2InputClusterDiscFlags,       s_au8Endpoint2OutputClusterDiscFlags,     },     s_ahEndpoint2InputClusterAPdus,    1 }, {    {       0x0104,       0,       1,       3,       5,       4,       s_au16Endpoint3InputClusterList,       s_au16Endpoint3OutputClusterList,       s_au8Endpoint3InputClusterDiscFlags,       s_au8Endpoint3OutputClusterDiscFlags,    },    s_ahEndpoint3InputClusterAPdus,    1 }, }; Router/zcl_options.h This file is used to set the options used by the ZCL.   Number of Endpoints The number of endpoints is increased from 1 to 3: /* Number of endpoints supported by this device */ #define ZCL_NUMBER_OF_ENDPOINTS                              3   Enable Client Clusters The client cluster functionality for the new endpoints is enabled: /****************************************************************************/ /*                             Enable Cluster                               */ /*                                                                          */ /* Add the following #define's to your zcl_options.h file to enable         */ /* cluster and their client or server instances                             */ /****************************************************************************/ #define CLD_BASIC #define BASIC_SERVER #define BASIC_CLIENT #define CLD_IDENTIFY #define IDENTIFY_SERVER #define IDENTIFY_CLIENT #define CLD_GROUPS #define GROUPS_SERVER #define GROUPS_CLIENT #define CLD_ONOFF #define ONOFF_SERVER #define ONOFF_CLIENT   Router/app_zcl_task.c Base Device Data Structures The structures that store data for the new Base Devices associated with the new endpoints are created: /****************************************************************************/ /***        Exported Variables                                            ***/ /****************************************************************************/ tsZHA_BaseDevice sBaseDevice; tsZHA_BaseDevice sBaseDeviceSwitch1; tsZHA_BaseDevice sBaseDeviceSwitch2;   Register Base Device Endpoints - APP_ZCL_vInitialise() The two new Base Devices and their endpoints are registered with the stack to make them available: if (eZCL_Status != E_ZCL_SUCCESS) {           DBG_vPrintf(TRACE_ZCL, "Error: eZHA_RegisterBaseDeviceEndPoint(Light): %02x\r\n", eZCL_Status); } /* Register Switch1 EndPoint */ eZCL_Status =  eZHA_RegisterBaseDeviceEndPoint(ROUTER_SWITCH1_ENDPOINT,                                                           &APP_ZCL_cbEndpointCallback,                                                           &sBaseDeviceSwitch1); if (eZCL_Status != E_ZCL_SUCCESS) {           DBG_vPrintf(TRACE_ZCL, "Error: eZHA_RegisterBaseDeviceEndPoint(Switch1): %02x\r\n", eZCL_Status); } /* Register Switch2 EndPoint */ eZCL_Status =  eZHA_RegisterBaseDeviceEndPoint(ROUTER_SWITCH2_ENDPOINT,                                                           &APP_ZCL_cbEndpointCallback,                                                           &sBaseDeviceSwitch2); if (eZCL_Status != E_ZCL_SUCCESS) {           DBG_vPrintf(TRACE_ZCL, "Error: eZHA_RegisterBaseDeviceEndPoint(Switch2): %02x\r\n", eZCL_Status); }   Factory Reset Functionality - vHandleClusterCustomCommands() The two new Base Devices are factory reset by re-registering them when the Reset To Factory Defaults command is received by the Basic cluster server: case GENERAL_CLUSTER_ID_BASIC: {      tsCLD_BasicCallBackMessage *psCallBackMessage = (tsCLD_BasicCallBackMessage*)psEvent->uMessage.sClusterCustomMessage.pvCustomData;      if (psCallBackMessage->u8CommandId == E_CLD_BASIC_CMD_RESET_TO_FACTORY_DEFAULTS )      {           DBG_vPrintf(TRACE_ZCL, "Basic Factory Reset Received\n");           FLib_MemSet(&sBaseDevice,0,sizeof(tsZHA_BaseDevice));           APP_vZCL_DeviceSpecific_Init();           eZHA_RegisterBaseDeviceEndPoint(ROUTER_APPLICATION_ENDPOINT,                                                   &APP_ZCL_cbEndpointCallback,                                                   &sBaseDevice);           eZHA_RegisterBaseDeviceEndPoint(ROUTER_SWITCH1_ENDPOINT,                                                   &APP_ZCL_cbEndpointCallback,                                                   &sBaseDeviceSwitch1);           eZHA_RegisterBaseDeviceEndPoint(ROUTER_SWITCH2_ENDPOINT,                                                   &APP_ZCL_cbEndpointCallback,                                                   &sBaseDeviceSwitch2);      } } break;   Basic Server Cluster Data Initialisation - APP_vZCL_DeviceSpecific_Init() The default attribute values for the Basic clusters are initialized: sBaseDevice.sOnOffServerCluster.bOnOff = FALSE; FLib_MemCpy(sBaseDevice.sBasicServerCluster.au8ManufacturerName, "NXP", CLD_BAS_MANUF_NAME_SIZE); FLib_MemCpy(sBaseDevice.sBasicServerCluster.au8ModelIdentifier, "BDB-Router", CLD_BAS_MODEL_ID_SIZE); FLib_MemCpy(sBaseDevice.sBasicServerCluster.au8DateCode, "20150212", CLD_BAS_DATE_SIZE); FLib_MemCpy(sBaseDevice.sBasicServerCluster.au8SWBuildID, "1000-0001", CLD_BAS_SW_BUILD_SIZE); sBaseDeviceSwitch1.sOnOffServerCluster.bOnOff = FALSE; FLib_MemCpy(sBaseDeviceSwitch1.sBasicServerCluster.au8ManufacturerName, "NXP", CLD_BAS_MANUF_NAME_SIZE); FLib_MemCpy(sBaseDeviceSwitch1.sBasicServerCluster.au8ModelIdentifier, "BDB-Sw1", CLD_BAS_MODEL_ID_SIZE); FLib_MemCpy(sBaseDeviceSwitch1.sBasicServerCluster.au8DateCode, "20170310", CLD_BAS_DATE_SIZE); FLib_MemCpy(sBaseDeviceSwitch1.sBasicServerCluster.au8SWBuildID, "1000-0001", CLD_BAS_SW_BUILD_SIZE); sBaseDeviceSwitch2.sOnOffServerCluster.bOnOff = FALSE; FLib_MemCpy(sBaseDeviceSwitch2.sBasicServerCluster.au8ManufacturerName, "NXP", CLD_BAS_MANUF_NAME_SIZE); FLib_MemCpy(sBaseDeviceSwitch2.sBasicServerCluster.au8ModelIdentifier, "BDB-Sw2", CLD_BAS_MODEL_ID_SIZE); FLib_MemCpy(sBaseDeviceSwitch2.sBasicServerCluster.au8DateCode, "20170310", CLD_BAS_DATE_SIZE); FLib_MemCpy(sBaseDeviceSwitch2.sBasicServerCluster.au8SWBuildID, "1000-0001", CLD_BAS_SW_BUILD_SIZE);   Router/app_zcl_task.h The Base Device Data structures are made available to other modules: /****************************************************************************/ /***        Exported Variables                                            ***/ /****************************************************************************/ extern tsZHA_BaseDevice sBaseDevice; extern tsZHA_BaseDevice sBaseDeviceSwitch1; extern tsZHA_BaseDevice sBaseDeviceSwitch2;   Router/app_router_node.c Enable ZCL Event Handler - vAppHandleAfEvent() Data messages addressed to the two new endpoints are passed to the ZCL for processing: if (psZpsAfEvent->u8EndPoint == ROUTER_APPLICATION_ENDPOINT ||  psZpsAfEvent->u8EndPoint == ROUTER_SWITCH1_ENDPOINT ||  psZpsAfEvent->u8EndPoint == ROUTER_SWITCH2_ENDPOINT) {      DBG_vPrintf(TRACE_APP, "Pass to ZCL\n");      if ((psZpsAfEvent->sStackEvent.eType == ZPS_EVENT_APS_DATA_INDICATION) ||           (psZpsAfEvent->sStackEvent.eType == ZPS_EVENT_APS_INTERPAN_DATA_INDICATION))      {           APP_ZCL_vEventHandler( &psZpsAfEvent->sStackEvent);       } }
View full article
In this document we will be seeing how to create a BLE demo application for an adopted BLE profile based on another demo application with a different profile. In this demo, the Pulse Oximeter Profile will be implemented.  The PLX (Pulse Oximeter) Profile was adopted by the Bluetooth SIG on 14th of July 2015. You can download the adopted profile and services specifications on https://www.bluetooth.org/en-us/specification/adopted-specifications. The files that will be modified in this post are, app.c,  app_config.c, app_preinclude.h, gatt_db.h, pulse_oximeter_service.c and pulse_oximeter_interface.h. A profile can have many services, the specification for the PLX profile defines which services need to be instantiated. The following table shows the Sensor Service Requirements. Service Sensor Pulse Oximeter Service Mandatory Device Information Service Mandatory Current Time Service Optional Bond Management Service Optional Battery Service Optional Table 1. Sensor Service Requirements For this demo we will instantiate the PLX service, the Device Information Service and the Battery Service. Each service has a source file and an interface file, the device information and battery services are already implemented, so we will only need to create the pulse_oximeter_interface.h file and the pulse_oximeter_service.c file. The PLX Service also has some requirements, these can be seen in the PLX service specification. The characteristic requirements for this service are shown in the table below. Characteristic Name Requirement Mandatory Properties Security Permissions PLX Spot-check Measurement C1 Indicate None PLX Continuous Measurement C1 Notify None PLX Features Mandatory Read None Record Access Control Point C2 Indicate, Write None Table 2. Pulse Oximeter Service Characteristics C1: Mandatory to support at least one of these characteristics. C2: Mandatory if measurement storage is supported for Spot-check measurements. For this demo, all the characteristics will be supported. Create a folder for the pulse oximeter service in  \ConnSw\bluetooth\profiles named pulse_oximeter and create the pulse_oximeter_service.c file. Next, go to the interface folder in \ConnSw\bluetooth\profiles and create the pulse_oximeter_interface.h file. At this point these files will be blank, but as we advance in the document we will be adding the service implementation and the interface macros and declarations. Clonate a BLE project with the cloner tool. For this demo the heart rate sensor project was clonated. You can choose an RTOS between bare metal or FreeRTOS. You will need to change some workspace configuration.  In the bluetooth->profiles->interface group, remove the interface file for the heart rate service and add the interface file that we just created. Rename the group named heart_rate in the bluetooth->profiles group to pulse_oximeter and remove the heart rate service source file and add the pulse_oximeter_service.c source file. These changes will be saved on the actual workspace, so if you change your RTOS you need to reconfigure your workspace. To change the device name that will be advertised you have to change the advertising structure located in app_config.h. /* Scanning and Advertising Data */ static const uint8_t adData0[1] = { (gapAdTypeFlags_t)(gLeGeneralDiscoverableMode_c | gBrEdrNotSupported_c) }; static const uint8_t adData1[2] = { UuidArray(gBleSig_PulseOximeterService_d)}; static const gapAdStructure_t advScanStruct[] = { { .length = NumberOfElements(adData0) + 1, .adType = gAdFlags_c, .aData = (void *)adData0 }, { .length = NumberOfElements(adData1) + 1, .adType = gAdIncomplete16bitServiceList_c, .aData = (void *)adData1 }, { .adType = gAdShortenedLocalName_c, .length = 8, .aData = "FSL_PLX" } }; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ We also need to change the address of the device so we do not have conflicts with another device with the same address. The definition for the address is located in app_preinclude.h and is called BD_ADDR. In the demo it was changed to: #define BD_ADDR 0xBE,0x00,0x00,0x9F,0x04,0x00 ‍‍‍ Add the definitions in ble_sig_defines.h located in Bluetooth->host->interface for the UUID’s of the PLX service and its characteristics. /*! Pulse Oximeter Service UUID */ #define gBleSig_PulseOximeterService_d 0x1822 /*! PLX Spot-Check Measurement Characteristic UUID */ #define gBleSig_PLXSpotCheckMeasurement_d 0x2A5E /*! PLX Continuous Measurement Characteristic UUID */ #define gBleSig_PLXContinuousMeasurement_d 0x2A5F /*! PLX Features Characteristic UUID */ #define gBleSig_PLXFeatures_d 0x2A60 ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ We need to create the GATT database for the pulse oximeter service. The requirements for the service can be found in the PLX Service specification. The database is created at compile time and is defined in the gatt_db.h.  Each characteristic can have certain properties such as read, write, notify, indicate, etc. We will modify the existing database according to our needs. The database for the pulse oximeter service should look something like this. PRIMARY_SERVICE(service_pulse_oximeter, gBleSig_PulseOximeterService_d) CHARACTERISTIC(char_plx_spotcheck_measurement, gBleSig_PLXSpotCheckMeasurement_d, (gGattCharPropIndicate_c)) VALUE_VARLEN(value_PLX_spotcheck_measurement, gBleSig_PLXSpotCheckMeasurement_d, (gPermissionNone_c), 19, 3, 0x00, 0x00, 0x00) CCCD(cccd_PLX_spotcheck_measurement) CHARACTERISTIC(char_plx_continuous_measurement, gBleSig_PLXContinuousMeasurement_d, (gGattCharPropNotify_c)) VALUE_VARLEN(value_PLX_continuous_measurement, gBleSig_PLXContinuousMeasurement_d, (gPermissionNone_c), 20, 3, 0x00, 0x00, 0x00) CCCD(cccd_PLX_continuous_measurement) CHARACTERISTIC(char_plx_features, gBleSig_PLXFeatures_d, (gGattCharPropRead_c)) VALUE_VARLEN(value_plx_features, gBleSig_PLXFeatures_d, (gPermissionFlagReadable_c), 7, 2, 0x00, 0x00) CHARACTERISTIC(char_RACP, gBleSig_RaCtrlPoint_d, (gGattCharPropIndicate_c | gGattCharPropWrite_c)) VALUE_VARLEN(value_RACP, gBleSig_RaCtrlPoint_d, (gPermissionFlagWritable_c), 4, 3, 0x00, 0x00, 0x00) CCCD(cccd_RACP) ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ For more information on how to create a GATT database you can check the BLE Application Developer’s Guide chapter 7. Now we need to make the interface file that contains all the macros and declarations of the structures needed by the PLX service. Enumerated types need to be created for each of the flags field or status field of every characteristic of the service. For example, the PLX Spot-check measurement field has a flags field, so we declare an enumerated type that will help us keep the program organized and well structured. The enum should look something like this: /*! Pulse Oximeter Service - PLX Spotcheck Measurement Flags */ typedef enum { gPlx_TimestampPresent_c = BIT0, /* C1 */ gPlx_SpotcheckMeasurementStatusPresent_c = BIT1, /* C2 */ gPlx_SpotcheckDeviceAndSensorStatusPresent_c = BIT2, /* C3 */ gPlx_SpotcheckPulseAmplitudeIndexPresent_c = BIT3, /* C4 */ gPlx_DeviceClockNotSet_c = BIT4 } plxSpotcheckMeasurementFlags_tag; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ The characteristics that will be indicated or notified need to have a structure type that contains all the fields that need to be transmitted to the client. Some characteristics will not always notify or indicate the same fields, this varies depending on the flags field and the requirements for each field. In order to notify a characteristic we need to check the flags in the measurement structure to know which fields need to be transmitted. The structure for the PLX Spot-check measurement should look something like this: /*! Pulse Oximeter Service - Spotcheck Measurement */ typedef struct plxSpotcheckMeasurement_tag { ctsDateTime_t timestamp; /* C1 */ plxSpO2PR_t SpO2PRSpotcheck; /* M */ uint32_t deviceAndSensorStatus; /* C3 */ uint16_t measurementStatus; /* C2 */ ieee11073_16BitFloat_t pulseAmplitudeIndex; /* C4 */ uint8_t flags; /* M */ }plxSpotcheckMeasurement_t; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ The service has a configuration structure that contains the service handle, the initial features of the PLX Features characteristic and a pointer to an allocated space in memory to store spot-check measurements. The interface will also declare some functions such as Start, Stop, Subscribe, Unsubscribe, Record Measurements and the control point handler. /*! Pulse Oximeter Service - Configuration */ typedef struct plxConfig_tag { uint16_t serviceHandle; plxFeatures_t plxFeatureFlags; plxUserData_t *pUserData; bool_t procInProgress; } plxConfig_t; ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ The service source file implements the service specific functionality. For example, in the PLX service, there are functions to record the different types of measurements, store a spot-check measurement in the database, execute a procedure for the RACP characteristic, validate a RACP procedure, etc. It implements the functions declared in the interface and some static functions that are needed to perform service specific tasks. To initialize the service you use the start function. This function initializes some characteristic values. In the PLX profile, the Features characteristic is initialized and a timer is allocated to indicate the spot-check measurements periodically when the Report Stored Records procedure is written to the RACP characteristic. The subscribe and unsubscribe functions are used to update the device identification when a device is connected to the server or disconnected. bleResult_t Plx_Start (plxConfig_t *pServiceConfig) { mReportTimerId = TMR_AllocateTimer(); return Plx_SetPLXFeatures(pServiceConfig->serviceHandle, pServiceConfig->plxFeatureFlags); } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ All of the services implementations follow a similar template, each service can have certain characteristics that need to implement its own custom functions. In the case of the PLX service, the Record Access Control Point characteristic will need many functions to provide the full functionality of this characteristic. It needs a control point handler, a function for each of the possible procedures, a function to validate the procedures, etc. When the application makes a measurement it must fill the corresponding structure and call a function that will write the attribute in the database with the correct fields and then send an indication or notification. This function is called RecordMeasurement and is similar between the majority of the services. It receives the measurement structure and depending on the flags of the measurement, it writes the attribute in the GATT database in the correct format. One way to update a characteristic is to create an array of the maximum length of the characteristic and check which fields need to be added and keep an index to know how many bytes will be written to the characteristic by using the function GattDb_WriteAttribute(handle, index, &charValue[0]). The following function shows an example of how a characteristic can be updated. In the demo the function contains more fields, but the logic is the same. static bleResult_t Plx_UpdatePLXContinuousMeasurementCharacteristic ( uint16_t handle, plxContinuousMeasurement_t *pMeasurement ) { uint8_t charValue[20]; uint8_t index = 0; /* Add flags */ charValue[0] = pMeasurement->flags; index++; /* Add SpO2PR-Normal */ FLib_MemCpy(&charValue[index], &pMeasurement->SpO2PRNormal, sizeof(plxSpO2PR_t)); index += sizeof(plxSpO2PR_t); /* Add SpO2PR-Fast */ if (pMeasurement->flags & gPlx_SpO2PRFastPresent_c) { FLib_MemCpy(&charValue[index], &pMeasurement->SpO2PRFast, sizeof(plxSpO2PR_t)); index += sizeof(plxSpO2PR_t); } return GattDb_WriteAttribute(handle, index, &charValue[0]); } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ The app.c handles the application specific functionality. In the PLX demo it handles the timer callback to make a PLX continuous measurement every second. It handles the key presses and makes a spot-check measurement each time the SW3 pushbutton is pressed. The GATT server callback receives an event when an attribute is written, and in our application the RACP characteristic is the only one that can be written by the client. When this event occurs, we call the Control Point Handler function. This function makes sure the indications are properly configured and check if another procedure is in progress. Then it calls the Send Procedure Response function, this function validates the procedure and calls the Execute Procedure function. This function will call one of the 4 possible procedures. It can call Report Stored Records, Report Number of Stored Records, Abort Operation or Delete Stored Records. When the project is running, the 4 LEDs will blink indicating an idle state. To start advertising, press the SW4 button and the LED1 will start flashing. When the device has connected to a client the LED1 will stop flashing and turn on. To disconnect the device, hold the SW4 button for some seconds. The device will return to an advertising state. In this demo, the spot-check measurement is made when the SW3 is pressed, and the continuous measurement is made every second. The spot-check measurement can be stored by the application if the Measurement Storage for spot-check measurements is supported (bit 2 of Supported Features Field in the PLX Features characteristic). The RACP characteristic lets the client control the database of the spot-check measurements, you can request the existing records, delete them, request the number of stored records or abort a procedure. To test the demo you can download and install a BLE Scanner application to your smartphone that supports BLE. Whit this app you should be able to discover the services in the sensor and interact with each characteristic. Depending on the app that you installed, it will parse known characteristics, but because the PLX profile is relatively new, these characteristics will not be parsed and the values will be displayed in a raw format. In Figure 1, the USB-KW40Z was used with the sniffer application to analyze the data exchange between the PLX sensor and the client. You can see how the sensor sends the measurements, and how the client interacts with the RACP characteristic. Figure 1. Sniffer log from USB-KW40Z
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
I´m going to explain how configure the RTC_CLKOUT pin and the different outputs that you can get with the KW40Z board. First it must be clear that the next configuration are based to use any demo of the KW40Z_Connectivity_Software_1.0.1 and also must to use the IAR Embedded Workbench. Now that you have all the software installed follow the next instructions. Configure the pin In the Reference Manual you will realize that each pin has different ways to configure it, in our case the pin that we are going to use is the PTB3 with a MUX = 7. The mux 7 is the RTC_CLKOUT. Figure 1. PTB3 mux configuration The KSDK have many functions that initializes the ports and the different peripherals. The configure_rtc_pins() function initialize the RTC_CLKOUT pin, you can find it in the pin_mux.h file. You must add the two functions in the hardware_init() function, that is declared in hardware_init.c file. The hardware_init() function must be like show next: void hardware_init(void) {      ...      ...      NV_ReadHWParameters(&gHardwareParameters); configure_rtc_pins(0); } Enable the RTC module. Now that the pin is already configure, you have to initialize the RTC module and the 32 KHz oscillator. You must understand that the RTC module can work with different clock sources (LPO,EXTAL_32K and OSC32KCLK) and it can be reflected through the RTC_CLKOUT pin. The register that change the clock source is the SIM_SOPT1 with OSC32KOUT(17-16) and OSC32KSEL(19-18) these are the names of the register bits. The OSC32KOUT(17-16) enable/disable the output of ERCLK32K on the selected pin in our case is the PTB3. You can configure with two options. 00     ERCLK32K is not output. 01     ERCLK32K is output on PTB3. The OSC32KSEL(19-18) selects the output clock, they have 3 option like show in the next image. Figure 2. Mux of the register SIM_SOPT1 The follow table show the different outputs that you can get in the RTC_CLKOUT pin, you only have to modify the OSC32KOUT and OSC32KSEL in the register SIM_SOPT1. Figure 3. Output of RTC_CLKOUT pin. Like the configuration of the pin, KSDK have function that initialize the RTC module and the 32 KHz oscillator. The RTC_DRV_Init(0) function initialize the RTC module and is declared in fsl_rtc_driver.h file, the BOARD_InitRtcOsc() function enable the RTC oscillator and is in the board.h file, the RTC_HAL_EnableCounter() enable the TCE(Timer Counter Enable) that is in the fsl_rtc_hal.h file and finally the SIM_SOPT1_OSC32KOUT() enable/disable the ERCLK32K for the RTC_CLKOUT(PTB3) and SIM_SOPT1_OSC32KSEL() selects the output clock. To enable the RTC module copy the next code: RTC_Type *rtcBase = g_rtcBase[0];//The RTC base address BOARD_InitRtcOsc(); RTC_DRV_Init(0); RTC_HAL_EnableCounter(rtcBase, true); SIM_SOPT1 = SIM_SOPT1_OSC32KOUT(0)|SIM_SOPT1_OSC32KSEL(0);      //Your RTC_CLKOUT is 1Hz with this configuration NOTE: Don’t forget to add the header necessary in the file that you are using. Enjoy it! :smileygrin:
View full article
The MCU in the KW40/30Z has various available very low power modes. In these power modes, the chip goes to sleep to save power, and it is not usable during this time (it can however receive different kinds of interruptions that could wake it up). The very low power modes supported by the microcontroller are: The KW40Z connectivity software stack has 6 predetermined deep sleep modes. These deep sleep modes have different configurations for the microcontroller low power mode, the BLE Link Layer state and in which ways the device can be awaken. These predetermined DSM (Deep Sleep Modes) are: * VLLS0 if DCDC is bypassed. VLLS1 with either Buck or Boost. ** Available in Buck mode only. Having said that, if you want the lowest possible consumption by the MCU, while also being able to wake up your application automatically with a timer (achieved with VLSS1), there is no DSM available. You can, however, create your own Deep Sleep Mode with low power timers enabled. Please note that VLSS1 has the lowest possible consumption when using a DCDC converter. When in bypass mode, the lowest possible consumption is achieved with VLSS0. To create your Deep Sleep Mode, you should start with the function that will actually handle the board going into deep sleep. This should be done in the PWR.c file, along with the rest of the DSM handler functions. This function is quite similar to the ones already made for the other deep sleep modes. Link layer interruptions, timer settings and the low power mode for the MCU are handled here. /* PWR.c */ #if (cPWR_UsePowerDownMode) static void PWR_HandleDeepSleepMode_7(void) {   #if cPWR_BLE_LL_Enable   uint16_t bleEnabledInt; #endif   uint8_t clkMode;   uint32_t lptmrTicks;   uint32_t lptmrFreq;     PWRLib_MCU_WakeupReason.AllBits = 0;   #if cPWR_BLE_LL_Enable    if(RSIM_BRD_CONTROL_BLE_RF_OSC_REQ_STAT(RSIM)== 0) // BLL in DSM   {     return;   bleEnabledInt = PWRLib_BLL_GetIntEn();   PWRLib_BLL_ClearInterrupts(bleEnabledInt);      PWRLib_BLL_DisableInterrupts(bleEnabledInt); #endif     if(gpfPWR_LowPowerEnterCb != NULL)   {     gpfPWR_LowPowerEnterCb();   }     /* Put the device in deep sleep mode */ #if cPWR_DCDC_InBypass    PWRLib_MCU_Enter_VLLS0(); #else   PWRLib_MCU_Enter_VLLS1(); #endif     if(gpfPWR_LowPowerExitCb != NULL)   {     gpfPWR_LowPowerExitCb();   }   #if cPWR_BLE_LL_Enable    PWRLib_BLL_EnableInterrupts(bleEnabledInt);        #endif      PWRLib_LPTMR_ClockStop();   } #endif /* #if (cPWR_UsePowerDownMode) */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Remember to add this function to the deep sleep handler function array: /* PWR.c */ const pfHandleDeepSleepFunc_t maHandleDeepSleep[]={PWR_HandleDeepSleepMode_1,                                                     PWR_HandleDeepSleepMode_2,                                                     PWR_HandleDeepSleepMode_3,                                                     PWR_HandleDeepSleepMode_4,                                                     PWR_HandleDeepSleepMode_5,                                                     PWR_HandleDeepSleepMode_6,                                                     PWR_HandleDeepSleepMode_7                                                    }; ‍‍‍‍‍‍‍‍‍‍ This function should allow your device to go to sleep. It does the strictly necessary things before the device goes to sleep: disables link layer interruptions, gets the configuration for the low power timer and it starts the timer. Please note that when the board is in either Buck or Boost DCDC mode, only VLSS1 is supported. When the device is in bypass mode, VLSS0 can be chosen. Now that the deep sleep handler is done, there are some changes that have to be made to have a proper execution. In the PWR_Configuration.h file, for example, there is an error message when the parameter cPWR_DeepSleepMode is larger than 6 (the default DSM modes), but, since you have added a new deep sleep mode, this number should be changed to 7: #if (cPWR_DeepSleepMode > 7 )  // default: 6   #error "*** ERROR: Illegal value in cPWR_DeepSleepMode" #endif ‍‍‍ Other changes that have to be made are the Low Leakage Wake Up unit and the deep sleep mode configurations. To change the LLWU configuration, you should add a case for the new deep sleep mode in the PWRLib_ConfigLLWU() function: /* PWRLib.c */ void PWRLib_ConfigLLWU( uint8_t lpMode ) {   switch(lpMode)   {   case 1:     LLWU_ME = gPWRLib_LLWU_WakeupModuleEnable_BTLL_c | gPWRLib_LLWU_WakeupModuleEnable_LPTMR_c;   break;   case 2:     LLWU_ME = gPWRLib_LLWU_WakeupModuleEnable_BTLL_c;   break;   case 3:     LLWU_ME = gPWRLib_LLWU_WakeupModuleEnable_LPTMR_c | gPWRLib_LLWU_WakeupModuleEnable_DCDC_c;   break;   case 4:   case 5:     LLWU_ME = gPWRLib_LLWU_WakeupModuleEnable_DCDC_c;    break;   case 6:     LLWU_ME = 0;   break;   case 7: /* The new deep sleep mode can be awaken through a Low Power Timer timeout */     LLWU_ME = gPWRLib_LLWU_WakeupModuleEnable_LPTMR_c;   break;   } }  } #endif /* #if (cPWR_UsePowerDownMode) */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Once this case has been added, you should change the function that calls PWRLib_ConfigLLWU(), PWRChangeDeepSleepMode(): /* PWR.c */ bool_t PWR_ChangeDeepSleepMode (uint8_t dsMode) { #if (cPWR_UsePowerDownMode)   if(dsMode > 7) //Since you’ve added an extra DSM, this is now 7 (default: 6)   {      return FALSE;   }    PWRLib_SetDeepSleepMode(dsMode); PWRLib_ConfigLLWU(dsMode); #if (cPWR_BLE_LL_Enable)    PWRLib_BLL_ConfigDSM(dsMode);   PWRLib_ConfigRSIM(dsMode); #endif    return TRUE;   #else   return TRUE; #endif  /* #if (cPWR_UsePowerDownMode) */ } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Now, since you’ll be using a Low Power Timer, you should modify the maLPModeUseLPTMR[] constant in the PWRLib.c file, indicating that you will use a low power timer: /* PWRLib.c */ const uint8_t maLPModeUseLPTMR[]={0,1,1,1,0,0,1,1}; //We add the last 1. default: {0,1,1,1,0,0,1} ‍‍‍ You should add a case for your new low power mode in the PWRLib_ConfigRSIM(). Here you will handle the BLE link layer whilst the device is in low power mode. This function can be found in the PWRLib.c file: /* PWRLib.c */ void PWRLib_ConfigRSIM( uint8_t lpMode ) {   switch(lpMode)   {   case 1:   case 2:       RSIM_BWR_CONTROL_STOP_ACK_OVRD_EN(RSIM, 0);       RSIM_CONTROL |= RSIM_CONTROL_BLE_RF_OSC_REQ_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_INT_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_INT_MASK;     break;   case 3:   case 4:   case 5:       RSIM_CONTROL &= ~(RSIM_CONTROL_STOP_ACK_OVRD_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_INT_EN_MASK);       RSIM_CONTROL |= RSIM_CONTROL_BLE_RF_OSC_REQ_INT_MASK;     break;   case 6:       RSIM_CONTROL &= ~(RSIM_CONTROL_STOP_ACK_OVRD_EN_MASK  | RSIM_CONTROL_BLE_RF_OSC_REQ_INT_EN_MASK);       RSIM_CONTROL |= RSIM_CONTROL_BLE_RF_OSC_REQ_INT_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_EN_MASK;     break;   case 7: //@PNN       RSIM_CONTROL &= ~(RSIM_CONTROL_STOP_ACK_OVRD_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_EN_MASK | RSIM_CONTROL_BLE_RF_OSC_REQ_INT_EN_MASK);       RSIM_CONTROL |= RSIM_CONTROL_BLE_RF_OSC_REQ_INT_MASK;     break;   } } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Your low power mode awaken by a low power timer should now be ready. To change the deep sleep mode and the time the device will be in deep sleep mode before it is awaken, use these functions in your application: PWR_ChangeDeepSleepMode(7);                                     /* Change deep sleep mode */ PWR_SetDeepSleepTimeInMs(YOUR_DEEP_SLEEP_TIME_IN_MS);           /* Time the device will spend on deep sleep mode */ PWR_AllowDeviceToSleep();                                      /* Allows the device to go to deep sleep mode */ PWR_DisallowDeviceToSleep();                                   /* Does not allows the device to go to deep sleep mode */ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
View full article
During last week I receive a question about identify a way to notice when the 32kHz oscillator is ready to be able to use it as clock source. In other words, when the 32kHz oscillator is stable to be used as reference clock for the MCG module. Then, system can switch over it. Kinetis devices starts up using its internal clock which is called FEI mode, then, the applications change to a different mode which require an external clock source (i.e. FEE mode). Normally, we have two options which are: Main reference clock. Talking specifically to KW devices, there is the 32MHz external crystal for the main reference clock. The 32kHz external crystal for the 32kHz oscillator which is driven through the RTC registers.  For the first option, there is a register which could be monitored to know when oscillator is ready (RSIM_CONTROL_RF_OSC_READY). So, it can be polled to notice when the oscillator is up and ready.  About the second option, there is no “OSCINIT” bit for the 32 kHz oscillator. However, there is a way to know when the 32kHz Oscillator is running, it is to simply enable the RTC OSC, and configure the RTC to count. After doing that immediately poll (read) the RTC_TPR register and check it until it is greater than 4096. It will roll over once it reaches 32767 so, it is important to the poll this register in a loop doing nothing else or the register could potentially be read when it is less than 4096 but had already rolled over. Once it is greater than 4096, it can be determined that oscillator is running well. If the RTC is not required, the counter can be disabled. Now that the 32kHz clock is available, the application can switch to FEE mode. So, in order to perform the above description, I modified the “CLOCK_CONFIG_EnableRtcOsc()” as shown next: static void CLOCK_CONFIG_EnableRtcOsc(uint32_t capLoad) {        rtc_config_t rtc_basic_config;        uint32_t u32cTPR_counter=0;        /* RTC clock gate enable */     CLOCK_EnableClock(kCLOCK_Rtc0);     if ((RTC->CR & RTC_CR_OSCE_MASK) == 0u)     {       /* Only if the Rtc oscillator is not already enabled */       /* Set the specified capacitor configuration for the RTC oscillator, "capLoad" parameter shall be set          to the value specific to the customer board requirement*/       RTC_SetOscCapLoad(RTC, capLoad);          /*Init the RTC with default configuration*/       RTC_GetDefaultConfig(&rtc_basic_config);       RTC_Init(RTC, &rtc_basic_config);       /* Enable the RTC 32KHz oscillator */       RTC->CR |= RTC_CR_OSCE_MASK;       /* Start the RTC time counter */       RTC_StartTimer(RTC);             /* Verify TPR register reaches 4096 counts */       while(u32cTPR_counter < 4096)       {          u32cTPR_counter= RTC->TPR;       }       /* 32kHz Oscillator is ready. Based on the application requirements, it can let the RTC enabled or disabled.           In this case, we can disable RTC since it is not needed by this application */       RTC_Deinit(RTC);     }     /* RTC clock gate disable  since RTC is not needed anymore*/     CLOCK_DisableClock(kCLOCK_Rtc0); }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Then, using the above function, it can be noticed when the 32kHz oscillator is ready to be used. Hope this helps....
View full article
For this example, the BLE stack VERSION was configure to create a Custom Profile with the KW40Z. The Custom to create is the Humidity Sensor and is based on the Temperature Sensor. The First thing to know is that the Generic Attribute Profile (GATT) establishes in detail how to exchange all profile and user data over a BLE connection. GATT deals only with actual data transfer procedures and formats. All standard BLE profiles are based on GATT and must comply with it to operate correctly. This makes GATT a key section of the BLE specification, because every single item of data relevant to applications and users must be formatted, packed, and sent according to the rules. GATT defines two roles: Server and Client. The GATT server stores the data transported over the Attribute Protocol (ATT) and accepts Attribute Protocol requests, commands and confirmations from the GATT client. The GATT client accesses data on the remote GATT server via read, write, notify, or indicate operations.    Figure 1. GATT Client-Server       GATT Database establishes a hierarchy to organize attributes. These are the Profile, Service, Characteristic and Descriptor. Profiles are high level definitions that define how services can be used to enable an application and Services are collections of characteristics. Descriptors are defined attributes that describe a characteristic value. To define a GATT Database several macros are provided by the GATT_DB API. Figure 2. GATT database      To know if the Profile or service is already defined on the specification, you have to look for on Bluetooth SIG profiles and check on the ble_sig_define module if is already declared on the code. In our case the Service is not declared(because is a Custom Profile) but the characteristic of the humidity it is on the specification but not on ble_sig_define. /*! Humidity Charactristic UUID */ #define gBleSig_Humidity_d                      0x2A6F The Humidity Sensor is going to have the GATT Server, because is going to be the device that has all the information for the GATT Client. The Application works like the Temperature Sensor, every time that you press the SW1 on USB is going to send the value. On the Temperature Sensor demo have the Battery Service and Device Information, so you only have to change the Temperature Service to Humidity Service. Figure 3. GATT database of Humidity Sensor      First thing to do is define the Humidity Server that has 16 bytes. To define a new Server or a Characteristic is in gatt_uuid128.h which is located in the application folder. All macros, function or structure in SDK have a common template which helps the application to act accordingly. /* Humidity */ UUID128(uuid_service_humidity, 0xfe ,0x34 ,0x9b ,0x5f ,0x80 ,0x00 ,0x00 ,0x80 ,0x00 ,0x10 ,0x00 ,0x02 ,0x00 ,0xfa ,0x10 ,0x10)      All the Service and Characteristics is declared in gattdb.h. Descriptors are declared after the Characteristic Value declaration but before the next Characteristic declaration. In this case the permission is the CharPresFormatDescriptor that have specific description by the standard. The Units of the Humidity Characteristic is on Percentage that is 0x27AD. Client Characteristic Configuration Descriptor(CCCD) is a descriptor where clients write some of the bits to activate Server notifications and/or indications PRIMARY_SERVICE_UUID128(service_humidity, uuid_service_humidity) CHARACTERISTIC(char_humidity, gBleSig_Humidity_d, (gGattCharPropNotify_c)) VALUE(value_humidity, gBleSig_Humidity_d, (gPermissionNone_c), 2, 0x00, 0x25) DESCRIPTOR(desc_humidity, gBleSig_CharPresFormatDescriptor_d, (gPermissionFlagReadable_c), 7, 0x0E, 0x00, 0xAD, 0x27, 0x00, 0x00, 0x00) CCCD(cccd_humidity)      After that, create a folder humidity in the next path C:\....\KW40Z_BLE_Software_1.1.2\ConnSw\bluetooth\profiles. Found the temperature folder, copy the temperature_service and paste inside of the humidity folder with another name (humidity_service) Then go back and look for the interface folder, copy temperature_interface and change the name (humidity_interface) in the same path.      On the humidity_interface file should have the following code. The Service structure has the service handle, and the initialization value. /*! Humidity Service - Configuration */ typedef struct humsConfig_tag {     uint16_t serviceHandle;     int16_t initialHumidity;        } humsConfig_t; The next configuration structure is for the Client; in this case we don’t need it. /*! Humidity Client - Configuration */ typedef struct humcConfig_tag {     uint16_t    hService;     uint16_t    hHumidity;     uint16_t    hHumCccd;     uint16_t    hHumDesc;     gattDbCharPresFormat_t  humFormat; } humcConfig_t;      At minimum on humidity_service file, should have the following code. The service stores the device identification for the connected client. This value is changed on subscription and non-subscription events. /*! Humidity Service - Subscribed Client*/ static deviceId_t mHums_SubscribedClientId;      The initialization of the service is made by calling the start procedure. This function is usually called when the application is initialized. In this case is on the BleApp_Config(). On stop function, the unsubscribe function is called. bleResult_t Hums_Start (humsConfig_t *pServiceConfig) {        mHums_SubscribedClientId = gInvalidDeviceId_c;         return Hums_RecordHumidityMeasurement (pServiceConfig->serviceHandle, pServiceConfig->initialHumidity); } bleResult_t Hums_Stop (humsConfig_t *pServiceConfig) {     return Hums_Unsubscribe(); }      Depending on the complexity of the service, the API will implement additional functions. For the Humidity Sensor only have a one characteristic. The measurement will be saving on the GATT database and send the notification to the client. This function will need the service handle and the new value as input parameters. bleResult_t Hums_RecordHumidityMeasurement (uint16_t serviceHandle, int16_t humidity) {     uint16_t handle;     bleResult_t result;     bleUuid_t uuid = Uuid16(gBleSig_Humidity_d);         /* Get handle of Humidity characteristic */     result = GattDb_FindCharValueHandleInService(serviceHandle,         gBleUuidType16_c, &uuid, &handle);     if (result != gBleSuccess_c)         return result;     /* Update characteristic value */     result = GattDb_WriteAttribute(handle, sizeof(uint16_t), (uint8_t*)&humidity);     if (result != gBleSuccess_c)         return result; Hts_SendHumidityMeasurementNotification(handle);     return gBleSuccess_c; }      After save the measurement on the GATT database with GattDb_WriteAttribute function we send the notification. To send the notification, first have to get the CCCD and after check if the notification is active, if is active send the notification. static void Hts_SendHumidityMeasurementNotification (   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         (mHums_SubscribedClientId, hCccd, &isNotificationActive) &&         TRUE == isNotificationActive)     {           GattServer_SendNotification(mHums_SubscribedClientId, handle);     } }      Steps to include the files into the demo. 1. Create a clone of the Temperature_Sensor with the name of Humidity_Sensor 2. Unzip the Humidity_Sensor folder. 3. In the fallowing path <kw40zConnSoft_intall_dir>\ConnSw\bluetooth\profiles\interface save the humidity_interface file. 4. In the <kw40zConnSoft_intall_dir>\ConnSw\bluetooth\profiles save the humidity folder 5. In the next directory <kw40zConnSoft_intall_dir>\ConnSw\examples\bluetooth\humidity_sensor\common replaces with the common folder.           Steps to include the paths into the demo using IAR Embedded Workbench​ Once you already save the folders in the corresponding path you must to indicate in the demo where are they. 1. Drag the files into the corresponding folder. The principal menu is going to see like this. Figure 4. Principal Menu 2. Then click Option Figure 5. Option 3. Click on the C/C++ Compiler and then on the Preprocessor     Figure 6. Preposcessor Window 4. After that click on  "..." button to edit the include directories and then click to add a new path.      Add the <kw40zConnSoft_intall_dir>\ConnSw\bluetooth\profile\humidity path. Figure 7. Add a path Finally compile and enjoy the demo! NOTE: If you want to probe the demo using another board you must to run the humidity_collector demo too. Figure 8. Example of the Humidity Sensor using the Humidity Collector demo.
View full article
OVERVIEW This document shows how to include the PowerLib to enable low power functionality in connectivity software projects that does not include it. It shows step by step instructions on how to import, configure and use this module. ADD POWER LIBRARY INTO A NEW PROJECT Once you have installed the “Connectivity Software” package, browse for the extracted files (typically located in C:\Freescale\KW40Z_Connectivity_Software_1.0.0). In this location search for the LowPower folder, then copy and paste it into your new project folder. Open your IAR project and create a new group called “Low Power”. Inside this group add two new groups called “Interface” and “Source”. In the Windows explorer, open the LowPower folder copied in the previous step. Drag and drop the contents of the "Interface" folder to the "Interface" group in IAR. Do the same for the "Source" folder. You can also use the option "Add Files" in the group menu to add the files. Note: Do not copy the “PWR_Platform.c” and “PWR_Platform.h” files. Once you have copied the files in their respective folders, you need to add the paths of these files in the project environment. Right click on the project name and select "Options". In Options go to “C/C++Compiler”, select “Preprocessor” and click on the red square. The next window will appear. Click on <Click to add>  to open the windows explorer. Navigate to the folder PowerLib/Interface in your project to add the "Interface" folder path. Repeat this step with the "Source" folder. HOW TO CONFIGURE LOW POWER To use low power in your project you need to define the following macros in the “app_preinclude.h” file: /* Enable/Disable PowerDown functionality in PwrLib */ #define cPWR_UsePowerDownMode           1 /* Enable/Disable BLE Link Layer DSM */ #define cPWR_BLE_LL_Enable              1 /* Default Deep Sleep Mode*/ #define cPWR_DeepSleepMode              4 cPWR_UsePowerDownMode enables the necessary functions to use low power in your project. cPWR_BLE_LL_Enable configures the link layer to work in doze mode when in low power, and cPWR-DeepSleepMode defines the deep sleep mode the MCU will enter when the low power function is executed. There are the six different modes that can be used.   Mode 1: MCU/Radio low power modes:         MCU in LLS3 mode.         BLE_LL in DSM.       Wakeup sources:       GPIO (push button) interrupt using LLWU module.        BLE_LL wake up interrupt(BLE_LL reference clock reaches wake up instance register)  using LLWU module.              - BTE_LL wakeup timeout: controlled by the BLE stack(SoC must be awake before next BLE action).              - BTE_LL reference clock source:   32Khz oscillator              - BTE_LL reference clock resolution:     625us                            Mode 2: MCU/Radio low power modes:         MCU in LLS3 mode.         BLE_LL in DSM.       Wakeup sources:         GPIO (push button) interrupt using LLWU module.         BLE_LL wake up interrupt(BLE_LL reference clock reaches wake up instance register)  using LLWU module.                - BTE_LL wakeup timeout: cPWR_DeepSleepDurationMs by default. Use PWR_SetDeepSleepTimeInMs  to change it at run time. Maximum timeout is 40959 ms. BLE suppose to be idle.                - BTE_LL reference clock source:   32Khz oscillator                - BTE_LL reference clock resolution:     625us   Mode  3: MCU/Radio low power modes:         MCU in LLS3 mode.         BLE_LL in idle.       Wakeup sources:        GPIO (push button) interrupt using LLWU module.        DCDC PowerSwitch - available in buck mode only.        LPTMR interrupt using LLWU module           - LPTMR wakeup timeout: cPWR_DeepSleepDurationMs by default. Use PWR_SetDeepSleepTimeInMs to change it at run time. Maximum timeout is 65535000 ms (18.2 h).           - LPTMR clock source:   32Khz oscillator           - LPTMR resolution:     modified at run time to meet timeout value. Mode 4: MCU/Radio low power modes:         MCU in VLLS0/1 mode(VLLS0 if DCDC bypassed/ VLLS1 otherwise ).        BLE_LL in idle.       Wakeup sources:        GPIO (push button) interrupt using LLWU module.         DCDC PowerSwitch - available in buck mode only. Mode 5: MCU/Radio low power modes:        MCU in VLLS2 (4k Ram retention (0x20000000- 0x20000fff)).        BLE_LL in idle.       Wakeup sources:         GPIO (push button) interrupt using LLWU module.         DCDC PowerSwitch - available in buck mode only.   Mode 6: MCU/Radio low power modes:         MCU in STOP.       Wakeup sources:         GPIO (push button) interrupt using LLWU module.         DCDC PowerSwitch - available in buck mode only.         LPTMR wakeup timeout: cPWR_DeepSleepDurationMs by default. Use PWR_SetDeepSleepTimeInMs to change it at run time. Maximum timeout is 65535000 ms (18.2 h).          - LPTMR clock source:   32Khz oscillator           - LPTMR resolution:     modified at run time to meet timeout value.           - LPTMR resolution:     modified at run time to meet timeout value.         Radio interrupt LL or 802.15.4         UART Configuring Wakeup Source The PowerLib software includes preconfigured wakeup methods for low power. These methods are described below and a couple of examples are included. From Reset: Comming from Reset From PSwitch_UART: Wakeup by UART interrupt From KeyBoard: Wakeup by TSI/Push button interrupt From LPTMR: Wakeup by LPTMR timer interrupt From Radio:  Wakeup by RTC timer interrupt From BLE_LLTimer:  Wakeup by BLE_LL Timer DeepSleepTimeout:  DeepSleep timer overflow. SleepTimeout: Sleep timer overflow. Configure Module Wakeup using LPTMR This example explains how to configure the third deep sleep mode using the LPTMR as wakeup source. The desired low power mode must be configured in the file app_preinclude.h. /* Default Deep Sleep Mode*/ #define cPWR_DeepSleepMode            3 On the same file, the macro cPWR_DeepSleepDurationMs macro must be added. It defines the time the MCU will be in low power mode before being waken by the low power timer. By default it it set to 10 seconds (10000 milliseconds). #define cPWR_DeepSleepDurationMs     10000 This defines the time that the device will remain asleep by default. The PWR_SetDeepSleepTimeInMs function can be used to change this period at run time. Consider that the maximum time period is 65535000 ms (18.2 hours). PWR_SetDeepSleepTimeInMs(10000); Also the deep sleep mode can be changed at run time with the following function. PWR_ChangeDeepSleepMode(3); For further power reduction, all the modules not in use must be turned off . To run in this mode, all the timers except the LPTMR must be turned off. The device enters in low power mode with the following code lines in the main application. PWR_SetDeepSleepTimeInMs(cPWR_DeepSleepDurationMs); PWR_ChangeDeepSleepMode(3); PWR_AllowDeviceToSleep(); Configure GPIO (Push Button) wakeup. In the “PWRLib.c” file, find the “PWRLib_Init” function. It contains the code to initialize the LLWU pins to be used for wakeup. Chip configuration Reference Manual chapter contains information on which LLWU pins are tied to GPIOs on the MCU. For this example LLWU pins 6 and 7 (which are tied to PTA18 and PTA19 in the MCU) are used.   LLWU_PE1 = 0x00;   LLWU_PE2 = LLWU_PE2_WUPE7(0x03) | LLWU_PE2_WUPE6(0x03);   LLWU_PE3 = 0x00;   LLWU_PE4 = 0x00; Since the LLWU pin sources work as GPIO interrupts, the propper ports in the MCU must be configured. Following code shows howthese pins are configured in the MCU.   /* PORTA_PCR18: ISF=0,MUX=1 */   PORTA_PCR18 = (uint32_t)((PORTA_PCR18 & (uint32_t)~(uint32_t)(                                                                 PORT_PCR_ISF_MASK |                                                                   PORT_PCR_MUX(0x06)                                                                     )) | (uint32_t)(                                                                                     PORT_PCR_MUX(0x01)                                                                                       ));   PORTA_PCR19 = (uint32_t)((PORTA_PCR19 & (uint32_t)~(uint32_t)(                                                                 PORT_PCR_ISF_MASK |                                                                   PORT_PCR_MUX(0x06)                                                                     )) | (uint32_t)(                                                                                     PORT_PCR_MUX(0x01)                                                                                       )); Once the pins have been defined, it is neccesary to configure them as Keyboard inputs for the Power Lib. Go to "PWRLib.h" and find the next define: #define  gPWRLib_LLWU_KeyboardFlagMask_c (gPWRLib_LLWU_WakeupPin_PTA18_c | gPWRLib_LLWU_WakeupPin_PTA19_c ) In this define you must place the pins that were configured previously as wakeup sources. Using Low Power in the Project When you define "cPWR_UsePowerDownMode"  in app_preinclude.h, it automatically creates a task in "ApplMain.c" called "App_Idle_Task". When executed by the OS scheduler, this task verifies if the device can go to sleep. This statement is always false unless the next function is called. PWR_AllowDeviceToSleep(); This function indicates the program that the device can enter in low power and will execute the neccesary code to enter in the power mode configured at that time. Note: Before you allow the device to sleep, disable all uneccessary modules and turn off all leds. When the device is ready to enter in low power (all the application layers allows it and the device is in an iddle state) function PWR_EnterLowPower() must be called. This function will enter the MCU into the selected low power mode. On the HID example this is done into the iddle task as shown below. #if (cPWR_UsePowerDownMode) 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 gKeyBoardSupported_d              /* Woke up on Keyboard Press */         if(wakeupReason.Bits.FromKeyBoard)         {             KBD_SwitchPressedOnWakeUp();             PWR_DisallowDeviceToSleep();         } #endif                  if(wakeupReason.Bits.DeepSleepTimeout)         {           Led1On();           for(;;)           {}         }     } } #endif /* cPWR_UsePowerDownMode */ PWR_CheckIfDeviceCanGoToSleep() function checks that all the application layers are agree on entering in low power mode (checking that PWR_DisallowDeviceToSleep() function hasn't been called). If everything is ok, function PWR_EnterLowPower() enters the device in low power and waits for a wakeup event.
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
HCI Application is a Host Controller Interface application which provides a serial communication to interface with the KW40/KW41/KW35/KW36/QN9080 BLE radio part.   It enables the user to have a way to control the radio through serial commands.   The format of the HCI Command Packet it’s composed by the following parts. Figure 1. HCI Command Packet   Each command is assigned a 2 byte Opcode which it’s divided into two fields, called the OpCode Group Field (OGF) and OpCode Command Field (OCF).   The OGF uses the upper 6 bits of the Opcode, while the OCF corresponds to the remaining 10 bits.   The OGF of 0x3F is reserved for vendor-specific debug commands. The organization of the opcodes allows additional information to be inferred without fully decoding the entire Opcode.  For further information regarding this, please check the BLUETOOTH SPECIFICATION Version 5.0 | Vol 2, Part E, 5.4 EXCHANGE OF HCI-SPECIFIC INFORMATION.    This document will guide you through the implementation of custom HCI commands in the KW36, but it can be applied as well for the rest of the NXP Bluetooth LE MCU’s that support HCI.   The following changes were made and tested in the FREEDOM KW36 and will generate a continuous with both channel and power configurable.      You will need to perform the following changes to the HCI black box demo. Modify the hci_transport.h public constants and macros section by adding: #define gHciCustomCommandOpcodeUpper (0xFC50) #define gHciCustomCommandOpcodeLower (0xFC00) #define gHciInCustomVendorCommandsRange(x) (((x) <= gHciCustomCommandOpcodeUpper) && \ ((x) >= gHciCustomCommandOpcodeLower))‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   In this case, the opcodes 0xFC50 to 0xFC00 are not used by our stack. These opcodes meet the requirements for vendor specific commands (OCF = 3F).   Then you will need to declare the handler for the custom command.   void Hcit_InstallCustomCommandHandler(hciTransportInterface_t mCustomInterfaceHandler);‍‍‍‍‍     In the  hcit_serial_interface.c modify the following : Add in the private memory declarations section static hciTransportInterface_t mCustomTransportInterface = NULL;‍‍‍‍‍ Change the Hcit_SendMessage as it is shown:   static inline void Hcit_SendMessage(void) {     uint16_t opcode = 0;     /* verify if this is an event packet */     if(mHcitData.pktHeader.packetTypeMarker == gHciEventPacket_c)     {        /* verify if this is a command complete event */        if(mHcitData.pPacket->raw[0] == gHciCommandCompleteEvent_c)        {           /* extract the first opcode to verify if it is a custom command */           opcode = mHcitData.pPacket->raw[3] + (mHcitData.pPacket->raw[4] << 8);        }     }     /* verify if command packet */     else if(mHcitData.pktHeader.packetTypeMarker == gHciCommandPacket_c)     {        /* extract opcode */        opcode = mHcitData.pPacket->raw[0] + (mHcitData.pPacket->raw[1] << 8);     }     if(gHciInCustomVendorCommandsRange(opcode))     {        if(mCustomTransportInterface)        {           mCustomTransportInterface( mHcitData.pktHeader.packetTypeMarker,                                                          mHcitData.pPacket,                                                          mHcitData.bytesReceived);        }     }     else     {        /* Send the message to HCI */        mTransportInterface( mHcitData.pktHeader.packetTypeMarker,                                           mHcitData.pPacket,                                           mHcitData.bytesReceived);     }    MEM_BufferFree( mHcitData.pPacket );    mHcitData.pPacket = NULL;     mPacketDetectStep = mDetectMarker_c; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Implement the registration of the handler void Hcit_InstallCustomCommandHandler(hciTransportInterface_t mCustomInterfaceHandler) {    OSA_EXT_InterruptDisable();    mCustomTransportInterface = mCustomInterfaceHandler;    OSA_EXT_InterruptEnable();    return; }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍    Once those changes are done, we will need to modify the  hci_black_box.c with the following changes.    Add the files to support HCI Custom commands. #include "hci_transport.h" #include "fsl_xcvr.h"‍‍‍‍‍‍‍‍‍‍   Define the custom commands, in this case, we will create some to turn ON/OFF the continuous wave as well as to set up the channel and power.  //@CC custom command #define CUSTOM_HCI_CW_ON (0xFC50) #define CUSTOM_HCI_CW_OFF (0xFC4F) #define CUSTOM_HCI_CW_SET_CHN_0 (0xFC00) /*Channel 0 Freq 2402 MHz*/ #define CUSTOM_HCI_CW_SET_CHN_19 (0xFC01) /*Channel 19 Freq 2440 MHz*/ #define CUSTOM_HCI_CW_SET_CHN_39 (0xFC02) /*Channel 39 Freq 2480 MHz*/ #define CUSTOM_HCI_CW_SET_PA_PWR_1 (0xFC10) /*PA_POWER 1 */ #define CUSTOM_HCI_CW_SET_PA_PWR_32 (0xFC11) /*PA_POWER 32 */ #define CUSTOM_HCI_CW_SET_PA_PWR_62 (0xFC12) /*PA_POWER 62 */ #define CUSTOM_HCI_CW_EVENT_SIZE (0x04) #define CUSTOM_HCI_EVENT_SUCCESS (0x00) #define CUSTOM_HCI_EVENT_FAIL (0x01) ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ Also, adding some app auxiliar variables  static uint16_t channelCC = 2402; static uint8_t powerCC = 0x3E; static uint8_t state_CC = 0; /*0 OFF 1 ON */‍‍‍‍‍‍‍‍‍‍‍‍ Create the custom event packet  uint8_t  eventPacket[6] = {gHciCommandCompleteEvent_c, CUSTOM_HCI_CW_EVENT_SIZE, 1, 0, 0, 0 };‍‍‍‍‍   In the main_task() after the BleApp_Init() register the callback for the custom commands.   /* Initialize peripheral drivers specific to the application */ BleApp_Init(); //Register the callback for the custom commands. Hcit_InstallCustomCommandHandler(BleApp_CustomCommandsHandle);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Once that it’s added, add the handler for the command   bleResult_t BleApp_CustomCommandsHandle(hciPacketType_t packetType, void* pPacket, uint16_t packetSize) {    uint16_t opcode = 0;    uint8_t error=0;    switch(packetType)    {       case gHciCommandPacket_c:       opcode = ((uint8_t*)pPacket)[0] + (((uint8_t*)pPacket)[1] << 8);       switch(opcode)       {             /*@CC: Set Channel   */             case CUSTOM_HCI_CW_SET_CHN_0:                  /*@CC: Set Channel 0 Freq 2402 MHz */                  channelCC=2402;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;             case CUSTOM_HCI_CW_SET_CHN_19:                  /*@CC: Channel 19 Freq 2440 MHz*/                  channelCC=2440;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;             case CUSTOM_HCI_CW_SET_CHN_39:                  /*@CC: Channel 39 Freq 2480 MHz */                  channelCC=2480;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;               /*@CC: Set PA_POWER  */             case CUSTOM_HCI_CW_SET_PA_PWR_1:                  /*@CC: Set PA_POWER 1 */                  powerCC=0x01;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;             case CUSTOM_HCI_CW_SET_PA_PWR_32:                  /*@CC: Set PA_POWER 32 */                  powerCC=0x20;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;             case CUSTOM_HCI_CW_SET_PA_PWR_62:                  /*@CC:  Set PA_POWER 62 */                  powerCC=0x3E;                  eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;             break;               /*@CC: Generate a Continuous Unmodulated Signal ON / OFF  */              case CUSTOM_HCI_CW_ON:                   /*@CC: Generate a Continuous Unmodulated Signal when pressing SW3 */                   XCVR_DftTxCW(channelCC, 6);                   XCVR_ForcePAPower(powerCC);                   state_CC = 1;                   eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;              break;              case CUSTOM_HCI_CW_OFF:                   /*@CC: Turn OFF the transmitter */                   XCVR_ForceTxWd();                   /* Initialize the PHY as BLE */                   XCVR_Init(BLE_MODE, DR_1MBPS);                   state_CC = 0;                   eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;              break;              default:               eventPacket[5] = CUSTOM_HCI_EVENT_FAIL;              break;       }       if(state_CC && (opcode==CUSTOM_HCI_CW_ON))        {             eventPacket[5] = CUSTOM_HCI_EVENT_SUCCESS;        }        else        {             eventPacket[5] = CUSTOM_HCI_EVENT_FAIL;        }       eventPacket[3] = (uint8_t)opcode;       eventPacket[4] = (uint8_t)(opcode >> 8);       Hcit_SendPacket(gHciEventPacket_c, eventPacket, sizeof(eventPacket));       break;       default:       break; } ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ To test it out in your side you will need to send the following raw commands through test tool:   01 4F FC 00 – This is to stop the CW and configure the radio in BLE mode again. This way you can continue sending HCI commands. 01 50 FC 00 – This is to send a CW signal in channel 0  with the defined channel and output power (default: frequency 2.402GHz and PA_POWER register with value of 0x3E).  01 FC 00 00 – Set the Channel 0 Freq 2402 MHz 01 FC 01 00 – Set the Channel 1 Freq 2440 MHz 01 FC 02 00 – Set the Channel 2 Freq 2480 MHz 01 FC 10 00 – Set the PA_POWER 1 01 FC 11 00 – Set the PA_POWER 32 01 FC 12 00 – Set the PA_POWER 62 If you want to add some parameter to it, please consider that the fourth byte of the packet will correspond to the number of parameters to enter and you will need to indicate it there. 
View full article
As mentioned in this other post, its important to have the correct trim on the external XTAL. However, once you have found the required trim to properly adjust the frequency, it is not very practical to manually adjust it in every device. So here is where changing the default XTAL trim comes in handy. KW41Z With the KW41 Connecitivity Software 1.0.2, it is a pretty straightforward process. In the hardware_init.c file, there is a global variable mXtalTrimDefault. To change the default XTAL trim, simply change the value of this variable. Remember it is an 8 bit register, so the maximum value would be 0xFF. KW40Z With the KW40, it is a similar process; however, it is not implemented by default on the KW40 Connectivity Software 1.0.1, so it should be implemented manually, following these steps: Create a global variable in hardware_init.c to store the default XTAL trim, similar to the KW41:  /* Default XTAL trim value */ static const uint8_t mXtalTrimDefault = 0xBE;‍‍‍‍‍‍‍‍ Overwrite the default XTAL trim in the hardware_init() function, adding these lines after NV_ReadHWParameters(&gHardwareParameters):    if(0xFFFFFFFF == gHardwareParameters.xtalTrim)   {       gHardwareParameters.xtalTrim = mXtalTrimDefault;   }‍‍‍‍‍‍‍‍‍‍‍‍ Add this define to allow XTAL trimming in the app_preinclude.h file:  /* Allows XTAL trimming */ #define gXcvrXtalTrimEnabled_d  1‍‍‍‍‍‍‍ Once you have found the appropriate XTAL trim value, simply change it in the global variable declared in step 1.
View full article
The KW40Z has support for a 32 MHz reference oscillator. This oscillator is used, among other things, as the clock for the RF operation. To properly adjust the frequency provided by this oscillator, there is a register that can be written, and this register (XTAL_TRIM) adjusts the capacitance provided by the internal capacitor bank to which the oscillator is connected. The KW40Z comes preprogramed with a default value (0x77) in the XTAL_TRIM register. However, since there is probably some variance when using different HW, the central frequency should be verified using a spectrum analyzer. Depending on the value measured, the XTAL_TRIM register can be modified to adjust the frequency. The Connectivity Test application provided here was modified, adding support to change the XTAL_TRIM register. In this case, the Agilent Technologies N9020A MXA Signal Analyzer was used, configured with the following parameters: FREQ (central frequency): 2405 MHz (test will be conducted on channel 11) SPAN (x-axis): 100 KHz AMPTD (amplitude, y-axis): 5 dBm   To perform the test, program the KW40Z device with the Connectivity Test application, using the provided .bin file, or using IAR and replacing the files in the project with the ones provided. To replace the files, unzip the provided .zip file in the KW40Z Connectivity Software folder and when asked, select to replace the existing files with the new ones. To measure and adjust the trimming, run the Connectivity Test application. Press ENTER to start the application. Press 1 to select the continuous tests mode. Press 4 to start a continuous unmodulated transmission. Once the test is running, you should be able to see the unmodulated signal in the spectrum analyzer. Use D and F to change the XTAL_TRIM value, thus changing the central frequency. Now, considering the test is being performed in channel 11, the central frequency should be centered exactly in 2.405 GHz, but on this board, for example, it is slightly above (2.4050259 GHz) by default. In order to fix this, you will need to adjust the value of the XTAL_TRIM register. As you change the XTAL_TRIM value, the central frequency changes too. Adjust the trim value until you find a value that moves the frequency to where it should be centered. For this particular board, a trimming value of 189 (0xBD) was used. Once you have found the trimming value that best adjusts the frequency, you can use it in other projects using the following function, included in the KW4xXcvrDrv.h file: XcvrSetXtalTrim(<YOUR 8 BIT XTAL_TRIM VALUE>)‍‍‍‍‍‍
View full article
In addition of the Design Guideline, PCB hardware package find here the design in check list to build sucessfully your own PCB
View full article