ワイヤレス接続に関するナレッジベース

キャンセル
次の結果を表示 
表示  限定  | 次の代わりに検索 
もしかして: 

Wireless Connectivity Knowledge Base

ディスカッション

ソート順:
I want to share with you the information that I found about Indication and notification. I hope this information help you to understand more about these topics. Indication and notifications are commands that could be send through the attribute(ATT) protocol. So, there are two roles defined at the ATT layer: Client devices access remote resources over a BLE link using the GATT protocol. Usually, the master is also the client but this is not required or mandatory. Server devices have the GATT database, access control methods, and provide resources to the remote client. Usually, the slave is also the server. BLE standard define two ways to transfer data for the server to the client: notification and indication. Maximum data payload size defined by the specification in each message is 20 bytes. Notifications and indications are initiated by the Server but enabled by the Client. Notification don't need acknowledged, so they are faster. Hence, server does not know if the message reach to the client. Indication need acknowledged to communicated. The client sent a confirmation message back to the server, this way server knows that message reached the client. One interesting thing defined by the ATT protocol is that a Server can't send two consecutive indications if the confirmation was not received. In other words, you have to wait for the confirmation of each indication in order to send the next indication. Figure 1. Indication/Notification Nevertheless, server are not able to send indications or notifications at the beginning of the communication. First, client must enable notifications and indications permissions on the server side, so, the server is allowed to send indications or notifications. This procedure involves the client to write the Client Characteristic Configuration Descriptor (CCCD) of the characteristic that will be notified/indicated. In other words, the client may request a notification for a particular characteristic from the server. Once the client enabled the notifications for such characteristic in the server, server can send the value to the client whenever it becomes available. For example, thinking in a heart rate sensor application connecting to Heart Rate smartphone application. Heart Rate Service can notify its Heart Rate Measurement Characteristic.  In this case, the sensor is the server while the smartphone is the client. Once devices are connected, smartphone application must set the notifications permissions of the Heart Rate Measurement Characteristic through its CCCD. Then, when smartphone application(client) set the CCCD withe notifications enabled, Heart Rate Sensor (server) is able to send notifications whenever a heart rate measurement is available. This same procedure is needed if the characteristic has indication properties.  At the end, the client is the one that allow the server to indicate or notify a characteristic. Finally, it is worth to comment that unlike notification, the indication is more reliable, but slower, because the server sends the data but the client must to confirm when data is received.
記事全体を表示
Overview Bluetooth Low Energy offers the ability to broadcast data in format of non-connectable advertising packets while not being in a connection. This GAP Advertisement is widely known as a beacon and is used in today’s IoT applications in different forms. This article will present the current beacon format in our demo application from the KW40Z software package and how to create the most popular beacon formats on the market. The advertising packet format and payload are declared in the gAppAdvertisingData structure from app_config.c. This structure points to an array of AD elements, advScanStruct: static const gapAdStructure_t advScanStruct[] = {   {     .length = NumberOfElements(adData0) + 1,     .adType = gAdFlags_c,     .aData = (void *)adData0   },    {     .length = NumberOfElements(adData1) + 1,     .adType = gAdManufacturerSpecificData_c,     .aData = (void *)adData1   } }; Due to the fact that all beacons use the advertising flags structure and that the advertising PDU is 31 bytes in length (Bluetooth Low Energy v4.1), the maximum payload length is 28 bytes, including length and type for the AD elements. The AD Flags element is declared as it follows: static const uint8_t adData0[1] =  { (gapAdTypeFlags_t)(gLeGeneralDiscoverableMode_c | gBrEdrNotSupported_c) }; The demo application uses a hash function to generate a random UUID for the KW40Z default beacon. This is done in BleApp_Init: void BleApp_Init(void) {     sha1Context_t ctx;         /* Initialize sha buffer with values from SIM_UID */     FLib_MemCopy32Unaligned(&ctx.buffer[0], SIM_UIDL);     FLib_MemCopy32Unaligned(&ctx.buffer[4], SIM_UIDML);     FLib_MemCopy32Unaligned(&ctx.buffer[8], SIM_UIDMH);     FLib_MemCopy32Unaligned(&ctx.buffer[12], 0);          SHA1_Hash(&ctx, ctx.buffer, 16);         /* Updated UUID value from advertising data with the hashed value */     FLib_MemCpy(&gAppAdvertisingData.aAdStructures[1].aData[3], ctx.hash, 16); } When implementing a constant beacon payload, please bear in mind to disable this code section. KW40Z Default Beacon The KW40Z software implements a proprietary beacon with the maximum ADV payload and uses the following Manufacturer Specific Advertising Data structure of 26 bytes. This is the default implementation of the beacon demo example from the KW40Z Connectivity Software package. static uint8_t adData1[26] = {     /* Company Identifier*/     0xFF, 0x01     /* Beacon Identifier */     0xBC,     /* UUID */                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                                   /* A */                     0x00, 0x00,     /* B */                     0x00, 0x00,     /* C */                     0x00, 0x00,     /* RSSI at 1m */            0x1E}; iBeacon iBeacon is a protocol designed by Apple. It uses a 20 byte payload that consists of the following identifying information [1] : To advertise an iBeacon packet, the user needs to change the second AD element, adData1, like below: static uint8_t adData1[25] = {                                0x4C, 0x00,                                   0x02, 0x15,         /* UUID */             0xD9, 0xB9, 0xEC, 0x1F, 0x39, 0x25, 0x43, 0xD0, 0x80, 0xA9, 0x1E, 0x39, 0xD4, 0xCE, 0xA9, 0x5C,         /* Major Version */    0x00, 0x01         /* Minor Version */    0x00, 0x0A,                                0xC5}; AltBeacon AltBeacon is an open specification designed for proximity beacon advertisements [2]. It also uses a Manufacturer Specific Advertising Data structure: To advertise an AltBeacon packet, the user needs to change the second AD element, like below: static uint8_t adData1[26] = {     /* MFG ID*/         0xFF, 0x01,     /* Beacon Code */   0xBE, 0xAC,     /* Beacon ID */     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,     /* Ref RSSI*/       0xC5,     /* MFG RSVD*/       0x00}; Eddystone™ Eddystone™ is an open Bluetooth® Smart beacon format from Google [3]. It offers three data type packets: Eddystone™-UID Eddystone™-URL Eddystone™-TLM Eddystone™ uses two advertising structures: Complete List of 16-bit Service UUIDs structure, which contains the Eddystone Service UUID (0xFEAA). Service Data structure, which also contains the Eddystone™ Service UUID (0xFEAA). Thus, advScanStruct will now have 3 elements: static const gapAdStructure_t advScanStruct[] = {   {     .length = NumberOfElements(adData0) + 1,     .adType = gAdFlags_c,     .aData = (void *)adData0   },    {     .length = NumberOfElements(adData1) + 1,     .adType = gAdComplete16bitServiceList_c,     .aData = (void *)adData1   },   {     .length = NumberOfElements(adData2) + 1,     .adType = gAdServiceData16bit_c,     .aData = (void *)adData2   } }; The complete List of 16-bit Service UUIDs element will look like: static const uint8_t adData1[2] =  { 0xAA, 0xFE }; Eddystone™-UID Eddystone™-UID broadcasts a unique 16-bit Beacon ID to identify a particular device in a group. The Service Data block has the following structure: To implement this, the user needs to add a third AD element, as follows: static uint8_t adData2[22] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x00,     /* Ranging Data */  0xEE,     /* Namespace */     0x8B, 0x0C, 0xA7, 0x50, 0x09, 0x54, 0x77, 0xCB, 0x3E, 0x77,     /* Instance */      0x00, 0x00, 0x00, 0x00, 0x00, 0x01,     /* RFU */           0x00, 0x00}; Eddystone™-URL Eddystone™-URL broadcasts a compressed URL. The Service Data block has the following structure: In this example, we will implement a beacon which will advertise NXP’s webpage, http://www.nxp.com. To implement this, the user needs to add a third AD element, as follows: static const uint8_t adData2[9] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x10,     /* TX Power */      0xEE,     /* URL scheme */    0x00,     /* Encode URL */    'n', 'x, 'p', 0x07}; Eddystone™-TLM Eddystone™-TLM broadcasts telemetry data about the beacon device operation. The Service Data block has the following structure: To implement this, the user needs to add a third AD element, as follows: static uint8_t adData2[16] = {     /* ID */ 0xAA, 0xFE,     /* Frame Type */    0x20,     /* TLM Version */   0x00,     /* VBATT */        0x00, 0x00,     /* TEMP */         0x00, 0x00,     /* ADV_CNT */      0x00, 0x00, 0x00, 0x00,     /* SEC_CNT */      0x00, 0x00, 0x00, 0x00};
記事全体を表示
One of the most difficult part of creating connected medical applications is, actually, keep it connected. Different protocols are available to transmit information from a medical device to a database or user interface. Sometimes integrating our application to the current communication protocols can be as difficult as developing the device itself. Freescale has launched its Bluetooth® Low Energy (BLE) chips, and with them, a complete software stack that integrates most of the available profiles for BLE oriented applications. Using this set, it becomes easy to integrate your current medical application to use BLE as communications method. Freescale Connectivity Software Examples The connectivity software includes examples to demonstrate BLE communications with a smartphone device. Using these examples as a base facilitates the integration with an existing application and reduces the required time it takes to have a fully connected application. This post uses as an example the Heart Rate Monitor demo to show how these applications can be customized. Modifying general device information The BLE services information reported by the device is stored in a file named “gatt_db.h”. This services information is what is shown on a smartphone when the device has connected. The Generic Access Profile service includes the device name reported when advertising. To change it just replace the device name between “” and update the character count. Detailed device information is accessed via the Device Information Service including the manufacturer name, model and serial number etcetera. This information can also be adjusted to the custom device requirements by modifying the string between “” and updating the character number. Adapting example code to report application data The connectivity software includes some predefined services that can be used to customize the server to report our application data. These predefined services already include structures with the information that needs to be reported to the client. On the application example file app.c some of these services are configured. For the heart rate service, a variable of type hrsConfig_t is created containing configuration information of the heart rate sensor such as the supported characteristics and sensor location. All of these characteristics are described in the heart rate service file heart_rate_interface.h /* Service Data*/ static basConfig_t      basServiceConfig = {service_battery, 0}; static disConfig_t      disServiceConfig = {service_device_info}; static hrsUserData_t    hrsUserData; static hrsConfig_t hrsServiceConfig = {service_heart_rate, TRUE, TRUE, TRUE, gHrs_BodySensorLocChest_c, &hrsUserData}; static uint16_t cpHandles[1] = { value_hr_ctrl_point }; /*! Heart Rate Service - Configuration */ typedef struct hrsConfig_tag {     uint16_t             serviceHandle;     bool_t               sensorContactSupported;     bool_t               sensorContactDetected;     bool_t               energyExpandedEnabled;     hrsBodySensorLoc_t   bodySensorLocation;     hrsUserData_t        *pUserData; } hrsConfig_t; This information is used to configure the server when the function BleApp_Config is called. /* Start services */ hrsServiceConfig.sensorContactDetected = mContactStatus; #if gHrs_EnableRRIntervalMeasurements_d    hrsServiceConfig.pUserData->pStoredRrIntervals = MEM_BufferAlloc(sizeof(uint16_t) * gHrs_NumOfRRIntervalsRecorded_c); #endif    Hrs_Start(&hrsServiceConfig); basServiceConfig.batteryLevel = BOARD_GetBatteryLevel(); Bas_Start(&basServiceConfig); /* Allocate application timers */ mAdvTimerId = TMR_AllocateTimer(); Once the server is configured, the application is stated by entering the device in advertising state in order to make it visible for clients. This is done by calling the function BleApp_Advertise that configures the server to start advertising. void BleApp_Start(void) { /* Device is not connected and not advertising*/ if (!mAdvState.advOn) { #if gBondingSupported_d if (mcBondedDevices > 0) { mAdvState.advType = fastWhiteListAdvState_c; } else { #endif mAdvState.advType = fastAdvState_c; #if gBondingSupported_d } #endif BleApp_Advertise(); } #if (cPWR_UsePowerDownMode)    PWR_ChangeDeepSleepMode(1); /* MCU=LLS3, LL=DSM, wakeup on GPIO/LL */ PWR_AllowDeviceToSleep(); #endif       } Once the server has been found and a connection has been stablished with the client, the configured services must be started. This is done by calling the “subscribe” function for each service. For heart rate sensor, the function Hrs_Suscribe must be called. This function is available from the heart_rate_interface files. /* Subscribe client*/ Bas_Subscribe(peerDeviceId);        Hrs_Subscribe(peerDeviceId); #if (!cPWR_UsePowerDownMode)  /* UI */            During connection, the application measurements can be reported to the client by using the “record measurement” functions included in the service interfaces. For the heart rate sensor this is the Hrs_RecordHeartRateMeasurement function. static void TimerMeasurementCallback(void * pParam) { uint16_t hr = BOARD_GetPotentiometerLevel(); hr = (hr * mHeartRateRange_c) >> 12; #if gHrs_EnableRRIntervalMeasurements_d    Hrs_RecordRRInterval(&hrsUserData, (hr & 0x0F)); Hrs_RecordRRInterval(&hrsUserData,(hr & 0xF0)); #endif if (mToggle16BitHeartRate) { Hrs_RecordHeartRateMeasurement(service_heart_rate, 0x0100 + (hr & 0xFF), &hrsUserData); } else { Hrs_RecordHeartRateMeasurement(service_heart_rate, mHeartRateLowerLimit_c + hr, &hrsUserData); } Hrs_AddExpendedEnergy(&hrsUserData, 100); #if (cPWR_UsePowerDownMode) PWR_SetDeepSleepTimeInMs(900); PWR_ChangeDeepSleepMode(6); PWR_AllowDeviceToSleep();    #endif } This updates the current measurement and sends a notification to the client indicating that a new measurement report is ready. Many profiles are implemented in the connectivity software to enable already developed medical applications with BLE connectivity. APIs are easy to use and can significantly reduce the development times.
記事全体を表示
This is some information of Bluetooth Low Energy about the White List. I hope this information help you to understand the White List. The device to connect is saved on the white list located in the LL block of the controller. This enumerates the remote devices that are allowed to communicate with the local device. Since device filtering occurs in the LL it can have a significant impact on power consumption by filtering (or ignoring) advertising packets, scan requests or connection requests from being sent to the higher layers for handling. The Withe List can restrict which device are allowed to connect to other device. If is not, is not going to connect.      Once the address was saved, the connection with that device is going to be an auto connection establishment procedure.This means that the Controller autonomously establishes a connection with the device address that matches the address stored in the While List. Figure 1. White List Procedure NOTE: For more details download the Specification of the Ble​
記事全体を表示
802.15.4 wireless sniffers like the USB-KW41Z are capable of capturing over-the-air traffic. The captured packets are passed to a network protocol decoder like Wireshark over a network interface tunnel built by the Kinetis Protocol Analyzer.   Hardware  One USB-KW41Z preloaded with sniffer firmware ( instructions found at www.nxp.com/usb-kw41z )  Software Download & Install Thread Wireshark from wireshark.org which is an open-source network protocol analyzer capable of debugging over the air communication between Thread devices. Kinetis Protocol Analyzer is a software that provides a bridge between the USB-KW41 and Wireshark.  Wireshark Configuration  Open Wireshark from the Program Files Click Edit and select Preferences  Click Protocols to expand a list of protocols Select IEEE 802.15.4, click the Decryption Keys Edit... button Create a new key entry by pressing the plus button, then set the following values and click OK       Decryption key = 00112233445566778899aabbccddeeff      Decryption key index = 1      Key hash = Thread hash Find CoAP and configure it with CoAP UDP port number = 5683 Click Thread and select Decode CoAP for Thread  with Thread sequence counter = 00000000 as shown below At the 6LoWPAN preferences, add the Context 0 value of fd00:0db8::/64 Click OK and close Wireshark Configure Kinetis Protocol Analyzer  Connect the USB-KW41Z to one of the USB ports on your computer Open the device manager and look for the device connected port Open the "Kinetis Protocol Analyzer Adapter" program Make sure, you have a USB-KW41Z connected to your PC when opening the program because the Kinetis Protocol Adapter will start looking for kinetis sniffer hardware. Once the USB-KW41Z is detected, the previously identify COM port will be displayed Select the desired IEEE 802.15.4 channel to scan in the Kinetis Protocol Analyzer window. This guide selects channel 12 as an example  Click on the Wireshark icon to open Wireshark Network Protocol Analyzer An error may appear while opening Wireshark, click OK and continue Wireshark Sniffing Wireshark Network Analyzer will be opened. On the "Capture" option of the main window, select the Local Area Connection that was created by the Kinetis Protocol Analyzer, in this example, Kinetis Protocol Analyzer created "Local Area Connection 2", then click "Start" button. USB-KW41Z will start to sniff and upcoming data will be displayed in the "Capture" window of the Wireshark Network Protocol Analyzer.
記事全体を表示
This document describes how to add additional cluster to the router application in the AN12061-MKW41Z-AN-Zigbee-3-0-Base-Device Application Note.   The Router application's main endpoint contains Basic, Groups, Identify and OnOff server. The steps below describe how to add two clusters to Router: Temperature Measurement server and OnOff client. Note that these changes only go as far as making the new clusters added and discoverable, no functionality has been added to these clusters. Router/app_zcl_cfg.h The first step is to update the application ZCL Configuration file to add the new clusters (OnOff Client, Temperature Measurement Server) to the Router application endpoint. The HA profile already contains few clusters but Temperature Measurement cluster was added:   /* Profile 'HA' */ #define HA_ILLUMINANCEMEASUREMENT_CLUSTER_ID (0x0400) #define HA_DEFAULT_CLUSTER_ID                (0xffff) #define HA_OTA_CLUSTER_ID                    (0x0019) #define HA_TEMPMEASUREMENT_CLUSTER_ID        (0x0402) Router/app_zcl_globals.c The OnOff client was already present in Router endpoint but made discoverable and the Temperature Measurement cluster was added and made discoverable into Router application endpoint.The clusters are added to the Input cluster list (Server side) and output cluster list (Client side) and made discoverable using DiscFlag only for the cluster list for which it 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. For temperature measurement, you need to make it discoverable for input Cluster list as below: PRIVATE const uint16 s_au16Endpoint1InputClusterList[6] = { 0x0000, 0x0004, 0x0003, 0x0006, HA_TEMPMEASUREMENT_CLUSTER_ID , 0xffff, }; PRIVATE const PDUM_thAPdu s_ahEndpoint1InputClusterAPdus[6] = { apduZCL, apduZCL, apduZCL, apduZCL, apduZCL, apduZCL, }; PRIVATE uint8 s_au8Endpoint1InputClusterDiscFlags[1] = { 0x1f }; PRIVATE const uint16 s_au16Endpoint1OutputClusterList[5] = { 0x0000, 0x0004, 0x0003, 0x0006, HA_TEMPMEASUREMENT_CLUSTER_ID, }; PRIVATE uint8 s_au8Endpoint1OutputClusterDiscFlags[1] = { 0x08 }; Now update Simple Descriptor structure (see the declaration of zps_tsAplAfSimpleDescCont and ZPS_tsAplAfSimpleDescriptor structures to understand how to correctly fill the various parameters) to reflect the input cluster and output cluster list correctly 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,       1,       1,       6,       5,       s_au16Endpoint1InputClusterList,       s_au16Endpoint1OutputClusterList,       s_au8Endpoint1InputClusterDiscFlags,       s_au8Endpoint1OutputClusterDiscFlags,    },    s_ahEndpoint1InputClusterAPdus,    1 }, }; Router/zcl_options.h This file is used to set the options used by the ZCL. Enable Clusters The cluster functionality for the router endpoint was 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 CLD_IDENTIFY #define IDENTIFY_SERVER #define CLD_GROUPS #define GROUPS_SERVER #define CLD_ONOFF #define ONOFF_SERVER #define ONOFF_CLIENT #define CLD_TEMPERATURE_MEASUREMENT #define TEMPERATURE_MEASUREMENT_SERVER Enable any optional Attributes and Commands for the clusters /****************************************************************************/ /* Temperature Measurement Cluster - Optional Attributes */ /* */ /* Add the following #define's to your zcl_options.h file to add optional */ /* attributes to the time cluster. */ /****************************************************************************/ #define CLD_TEMPMEAS_ATTR_TOLERANCE /****************************************************************************/ /* Basic Cluster - Optional Commands */ /* */ /* Add the following #define's to your zcl_options.h file to add optional */ /* commands to the basic cluster. */ /****************************************************************************/ #define CLD_BAS_CMD_RESET_TO_FACTORY_DEFAULTS /****************************************************************************/ /* OnOff Cluster - Optional Commands */ /* */ /* Add the following #define's to your zcl_options.h file to add optional */ /* commands to the OnOff cluster. */ /****************************************************************************/ #define CLD_ONOFF_CMD_OFF_WITH_EFFECT  Add the cluster creation and initialization into ZigBee Base device definitions The cluster functionality for some of the clusters (like OnOff Client) is already present on ZigBee Base Device. For Temperature Measurement cluster the functionality was added into ZigBee Base Device. <SDK>/middleware/wireless/Zigbee_3_0_6.0.6/core/ZCL/Devices/ZHA/Generic/Include/base_device.h The first step was including the Temperature Measurement header files into base device header file as shown below:  #ifdef CLD_TEMPERATURE_MEASUREMENT #include "TemperatureMeasurement.h" #endif The second step was adding cluster instance (tsZHA_BaseDeviceClusterInstances) into base device Instance as shown below: /* Temperature Measurement Instance */ #if (defined CLD_TEMPERATURE_MEASUREMENT) && (defined TEMPERATURE_MEASUREMENT_SERVER) tsZCL_ClusterInstance sTemperatureMeasurementServer; #endif The next step was to define the cluster into the base device structure (tsZHA_BaseDevice) as below: #if (defined CLD_TEMPERATURE_MEASUREMENT) && (defined TEMPERATURE_MEASUREMENT_SERVER) tsCLD_TemperatureMeasurement sTemperatureMeasurementServerCluster; #endif <SDK>/middleware/wireless/Zigbee_3_0_6.0.6/core/ZCL/Devices/ZHA/Generic/Include/base_device.c The cluster create function for Temperature Measurement cluster for server was called in ZigBee base device registration function:   #if (defined CLD_TEMPERATURE_MEASUREMENT) && (defined TEMPERATURE_MEASUREMENT_SERVER)    /* Create an instance of a Temperature Measurement cluster as a server */    if(eCLD_TemperatureMeasurementCreateTemperatureMeasurement(&psDeviceInfo->sClusterInstance.sTemperatureMeasurementServer,                                                    TRUE,                                                    &sCLD_TemperatureMeasurement,                                                    &psDeviceInfo->sTemperatureMeasurementServerCluster,                                                    &au8TemperatureMeasurementAttributeControlBits[0]) != E_ZCL_SUCCESS)   {       return E_ZCL_FAIL;    } #endif Router/app_zcl_task.c Temperature Measurement Server Cluster Data Initialization - APP_vZCL_DeviceSpecific_Init() The default attribute values for the Temperature Measurement clusters are initialized: PRIVATE void APP_vZCL_DeviceSpecific_Init(void) {    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);    sBaseDevice.sTemperatureMeasurementServerCluster.i16MeasuredValue = 0;    sBaseDevice.sTemperatureMeasurementServerCluster.i16MinMeasuredValue = 0;    sBaseDevice.sTemperatureMeasurementServerCluster.i16MaxMeasuredValue = 0; }
記事全体を表示
I was investigating about how to create a current profile and I found interesting information I would like to share with the community. So, I decided to create an example to accomplish this task using BLE stack included in the MKW40Z Connectivity Software package. The demo to create is an Humidity Collector which make use of the Humidity custom profile and is based on the Temperature Collector demonstration application. 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. Notify and indicate operations are enabled by the client but initiated by the server, providing a way to push data to the client. Notifications are unacknowledged, while indications are acknowledged. Notifications are therefore faster, but less reliable. 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 defined attributes that describe a characteristic value. To define a GATT Database several macros are provided by the GATT_DB API in the Freescale BLE Stack, which is part KW40Z Connectivity Software package. Figure 2. GATT database      To know if the Profile or service is already defined in the specification, you have to look for in Bluetooth SIG profiles and check in the ble_sig_defines.h file if this is already declared in the code. In our case, the service is not declared, but the characteristic of the humidity is declared in the specification. Then, we need to check if the characteristic is already included in ble_sig_defines.h. Since, the characteristic is not included, we need to define it as shown next: /*! Humidity Charactristic UUID */ #define gBleSig_Humidity_d                      0x2A6F      The Humidity Collector is going to have the GATT client; this is the device that will receive all information from  the GATT server. Demo provided in this post works like the Temperature Collector. When the Collector enables the notifications from the sensor, received notifications will be printed in the serial terminal. In order to create the demo we need to define or develop a service that has to be the same as in the GATT Server, this is declared in the gatt_uuid128.h.If the new service is not the same, they will never be able to communicate each other. All macros function or structures in BLE stack of KW40Z Connectivity Software have a common template. Hence, we need to define this service in the gatt_uuid128.h as shown next: /* Humidity */ UUID128(uuid_service_humidity, 0xfe ,0x34 ,0x9b ,0x5f ,0x80 ,0x00 ,0x00 ,0x80 ,0x00 ,0x10 ,0x00 ,0x02 ,0x00 ,0xfa ,0x10 ,0x10)      During the scanning process is when the client is going to connect with the Server. Hence, function CheckScanEvent can help us to ensure that peer device or server device support the specified service, in this case, it will be the humidity service we just created in the previous step. Then, CheckScanEvent needs to check which device is on advertising mode and with MatchDataInAdvElementList to verify if it is the same uuid_service_humidity, if the service is not in the list, client is not going to connect. CheckScanEvent function should look as shown next: static bool_t CheckScanEvent(gapScannedDevice_t* pData) { uint8_t index = 0; uint8_t name[10]; uint8_t nameLength; bool_t foundMatch = 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];          /* Search for Humidity Custom Service */         if ((adElement.adType == gAdIncomplete128bitServiceList_c) ||           (adElement.adType == gAdComplete128bitServiceList_c))         {             foundMatch = MatchDataInAdvElementList(&adElement, &uuid_service_humidity, 16);         }                 if ((adElement.adType == gAdShortenedLocalName_c) ||           (adElement.adType == gAdCompleteLocalName_c))         {             nameLength = MIN(adElement.length, 10);             FLib_MemCpy(name, adElement.aData, nameLength);         }                 /* Move on to the next AD elemnt type */         index += adElement.length + sizeof(uint8_t); } if (foundMatch) {         /* UI */         shell_write("\r\nFound device: \r\n");         shell_writeN((char*)name, nameLength-1);         SHELL_NEWLINE();         shell_writeHex(pData->aAddress, 6); } return foundMatch; } The humidity_interface.h file should define the client structure and the server structure. For this demo, we only need the client structure, however, both are defined for reference. The Client Structure has all the data of the Humidity Service, in this case is a Service, characteristic, descriptor and CCCD handle and the format of the value. /*! Humidity Client - Configuration */ typedef struct humcConfig_tag { uint16_t    hService; uint16_t    hHumidity; uint16_t    hHumCccd; uint16_t    hHumDesc; gattDbCharPresFormat_t  humFormat; } humcConfig_t; The next configuration structure is for the Server; in this case we don’t need it. /*! Humidity Service - Configuration */ typedef struct humsConfig_tag { uint16_t    serviceHandle; int16_t     initialHumidity;        } humsConfig_t;     Now that the Client Structure is declared, go to the app.c and modify some functions. There are functions that help to store all the data of the humidity service. In our case they are 3 functions for the service, characteristic and descriptor. You have to be sure that the service that you create and the characteristics of humidity are in the functions. The Handle of each data is stored in the structure of the client. The three functions that need to be modify are the next: BleApp_StoreServiceHandles() stores handles for the specified service and characteristic. static void BleApp_StoreServiceHandles (     gattService_t   *pService ) {     uint8_t i;           if ((pService->uuidType == gBleUuidType128_c) &&         FLib_MemCmp(pService->uuid.uuid128, uuid_service_humidity, 16))     {         /* Found Humidity Service */         mPeerInformation.customInfo.humClientConfig.hService = pService->startHandle;                 for (i = 0; i < pService->cNumCharacteristics; i++)         {             if ((pService->aCharacteristics[i].value.uuidType == gBleUuidType16_c) &&                 (pService->aCharacteristics[i].value.uuid.uuid16 == gBleSig_Humidity_d))             {                 /* Found Humidity Char */                 mPeerInformation.customInfo.humClientConfig.hHumidity = pService->aCharacteristics[i].value.handle;             }         }     } } BleApp_StoreCharHandles() handles the descriptors. static void BleApp_StoreCharHandles (     gattCharacteristic_t   *pChar ) {     uint8_t i;         if ((pChar->value.uuidType == gBleUuidType16_c) &&         (pChar->value.uuid.uuid16 == gBleSig_Humidity_d))     {            for (i = 0; i < pChar->cNumDescriptors; i++)         {             if (pChar->aDescriptors[i].uuidType == gBleUuidType16_c)             {                 switch (pChar->aDescriptors[i].uuid.uuid16)                 {                     case gBleSig_CharPresFormatDescriptor_d:                     {                         mPeerInformation.customInfo.humClientConfig.hHumDesc = pChar->aDescriptors[i].handle;                         break;                     }                     case gBleSig_CCCD_d:                     {                         mPeerInformation.customInfo.humClientConfig.hHumCccd = pChar->aDescriptors[i].handle;                         break;                     }                     default:                         break;                 }             }         }     } } BleApp_StoreDescValues() stores the format of the value. static void BleApp_StoreDescValues (     gattAttribute_t     *pDesc ) {     if (pDesc->handle == mPeerInformation.customInfo.humClientConfig.hHumDesc)     {         /* Store Humidity format*/         FLib_MemCpy(&mPeerInformation.customInfo.humClientConfig.humFormat,                     pDesc->paValue,                     pDesc->valueLength);     }   }      After we store all the data of the Humidity Service, we need to check the notification callback. Every time the Client receives a notification with the BleApp_GattNotificationCallback(),  call the BleApp_PrintHumidity() function and check the Format Value; in this case is 0x27AD  that mean percentage and also have to be the same on the GATT server. static void BleApp_GattNotificationCallback (     deviceId_t serverDeviceId,     uint16_t characteristicValueHandle,     uint8_t* aValue,     uint16_t valueLength ) { /*Compare if the characteristics handle Server is the same of the GATT Server*/     if (characteristicValueHandle == mPeerInformation.customInfo.humClientConfig.hHumidity)     {            BleApp_PrintTemperature(*(uint16_t*)aValue);     }  } BleApp_PrintHumidity() print the value of the Humidity, but first check if the format value is the same. static void BleApp_PrintHumidity (     uint16_t humidity ) {     shell_write("Humidity: ");     shell_writeDec(humidity);      /*If the format value is the same, print the value*/     if (mPeerInformation.customInfo.humClientConfig.humFormat.unitUuid16 == 0x27AD)     {         shell_write(" %\r\n");     }     else     {         shell_write("\r\n");     } } Step to include the file to the demo. 1. Create a clone of the Temperature_Collector with the name of Humidity_Collector 2. Unzip the Humidity_Collector.zip file attached to this post. 3. Save the humidity folder in the fallowing path: <kw40zConnSoft_install_dir>\ConnSw\bluetooth\profiles . 4. Replaces the common folder in the next path: <kw40zConnSoft_install_dir>\ConnSw\examples\bluetooth\humidity_sensor\common . Once you already save the folders in the corresponding path you must to indicate in the demo where they are and drag the file in the humidity folder to the workspace. For test the demo fallow the next steps: Compile the project and run. Press SW1 for the advertising/scanning mode, and wait to connect it. Once the connection finish, press the SW1 of the Humidity Sensor board to get and print the data. Enjoy the demo! NOTE: This demo works with the Humidity Sensor demo. This means that you need one board programmed with the Humidity Sensor application and a second board with the Humidity Collector explained in this post. Figure 3. Example of the Humidity Collector using the Humidity Sensor.
記事全体を表示
Overview The Bluetooth specification defines 4 Generic Access Profile (GAP) roles for devices operating over a Low Energy physical transport [1]: Peripheral Central Broadcaster Observer The Bluetooth Low Energy Host Stack implementation on the Kinetis KW40Z offers devices the possibility to change between any of the 4 roles at run time. This article will present the interaction with the Bluetooth Low Energy Host API needed to implement a GAP multiple role device. General Procedure instructions Running the GAP roles requires the application to go through the following 3 steps: Configuration - Stack configuration for the desired GAP role The application needs to configure the stack parameters, e.g. advertising parameters, advertising data, scan parameters, callbacks. Note that configuration of the advertising parameters or scanning response and advertising data can be done only once if the values don’t change at runtime. The configuration is always made in the Link Layer Standby state. Start - Running the role The application needs to start advertising, scanning or initiate connection. Stop - Return to Standby state When changing between roles, the Link layer must always go through the Link Layer Standby state. Running as a GAP Broadcaster or GAP Peripheral The GAP Broadcaster or Peripheral sends advertising events. Additionally, the GAP Peripheral will accept the establishment of a LE link. This is why the GAP Observer will only support the Non Connectable Advertising mode (gAdvNonConnectable_c). Both roles requires configuration of advertising data, advertising parameters. The configuration (gAppAdvertisingData, gAppScanRspData and gAdvParams) usually resides in app_config.c. The confirmation events for setting these parameters is received in BleApp_GenericCallback. The confirmation event for the changing state of advertising is received in BleApp_AdvertisingCallback. Configuration /* Setup Advertising and scanning data */ Gap_SetAdvertisingData(&gAppAdvertisingData, &gAppScanRspData); /* Setting only for GAP Broadcaster role */ gAdvParams. advertisingType = gAdvNonConnectable_c; /* Set advertising parameters*/ Gap_SetAdvertisingParameters(&gAdvParams); Start App_StartAdvertising(BleApp_AdvertisingCallback, BleApp_ConnectionCallback); Stop Gap_StopAdvertising(); Running as a GAP Observer The GAP Observer receives advertising events. Unlike the GAP Peripheral or Broadcaster, it does not need to set scanning parameters separately. It passes the configuration with the start procedure. The configuration (gAppScanParams) usually resides in app_config.c. The confirmation event for the changing state of scanning is received in BleApp_ScanningCallback. Configuration and Start App_StartScanning(&gAppScanParams, BleApp_ScanningCallback); Stop Gap_StopScanning (); Running as a GAP Central The GAP Central initiates the establishment of the LE link. Like the GAP Observer, it passes the configuration with the start procedure. The configuration (gConnReqParams) usually resides in app_config.c. The confirmation event for the changing state of link is received in BleApp_ConnectionCallback. Configuration and Start Gap_Connect(&gConnReqParams, BleApp_ConnectionCallback); Stop Gap_Disconnect(deviceId); Example An out-of-the box example for multiple role is attached. The application named blood_pressure_multi_role implements a Blood Pressure GATT client and server and can switch between the following GAP roles: Peripheral, Observer and Central. The contents of the archive needs to be copied to the following location: <Installer Path>\KW40Z_Connectivity_Software_1.0.1\ConnSw\examples\bluetooth\ The application can be found at: <Install Path specified>\KW40Z_Connectivity_Software_1.0.1\ConnSw\examples\bluetooth\blood_pressure_multi_role\frdmkw40z\bare_metal\build\iar\blood_pressure_multi_role.eww Running as GAP Peripheral Press SW4. LED1 will start flashing and the console will show that the Link Layer enters Advertising. If the Link Layer was in a previous state, it will go through Standby. static void BleApp_Advertise(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Peripheral\n\r");     mGapRole = gGapPeripheral_c;         /* Start GAP Peripheral */     App_StartAdvertising(BleApp_AdvertisingCallback, BleApp_ConnectionCallback); } Running as GAP Observer Press SW3. A chasing LED pattern will start and the console will show that the Link Layer enters Scanning. If the Link Layer was in a previous state, it will go through Standby. static void BleApp_Scan(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Observer\n\r");     mGapRole = gGapObserver_c;         /* Start GAP Observer */     App_StartScanning(&gAppScanParams, BleApp_ScanningCallback); } Running as GAP Central If the Link Layer is in scanning and finds a Blood Pressure Sensor, it will go through Standby and initiate connection. static void BleApp_Connect(void) {     /* Ensure Link Layer is in Standby */     BleApp_GoToStandby();         shell_write(" GAP Role: Central\n\r");     mGapRole = gGapCentral_c;         /* Start GAP Central */     Gap_Connect(&gConnReqParams, BleApp_ConnectionCallback); } Returning to Standby Pressing SW3 for more than 2 seconds, brings the Link Layer back in Standby. static void BleApp_GoToStandby(void) {     /* Check if connection is on */     if (mPeerInformation.deviceId != gInvalidDeviceId_c)     {         /* Stop GAP Central or Peripheral */         Gap_Disconnect(mPeerInformation.deviceId);     }     if (mAdvOn)     {         /* Stop GAP Peripheral or Bradcaster */         Gap_StopAdvertising();     }         if (mScanningOn)     {         /* Stop GAP Observer */         Gap_StopScanning();     } } References [1] BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part C], 2.2 PROFILE ROLES
記事全体を表示
High level description to enable a Linux + KW41Z Border Router. Similar to how it’s shown for the K64 solution in the Kinetis Thread Stack Application Development Guide.   Configure the OpenWrt router to assign the IPv6 ULA prefix 2001:2002:2003::/48. On the LAN network, the router distributes addresses from range 2001:2002:2003::/60 Plug an Ethernet cable between the OpenWrt router and the Linux box. Before creating the Thread network, the Linux box has a global address on its eth interface from range 2001:2002:2003::/60. After creating the Thread network, the BR configures on its Serial TAP interface an address from range 2001:2002:2003::/60. On its 6LoWPAN interface, the BR configures an address from range 2001:2002:2003:c::/64. This is achieved with DHCPv6 prefix delegation - the router is requested to assign a new prefix space to be used by the Thread network. The forth segment in the IPv6 range might be 2, 4, 8 or c, depending of the number of DHCP-PD requests made to the router. After 4 attempts, the router will not lease any other prefixes for some time. In order to force that, you'd require to restart the odhcpd deamon in the OpenWrt router with the following command: /etc/init.d/odhcpd restart . Join the router eligible device, which configures an address in 2001:2002:2003::1/60. We then ping the "Internet" (the LAN interface on the OpenWrt router) and it works. “threadtap0” interface must be bridged with an uplink interface connected to an OpenWrt DHCPv6-PD enabled router; it will act identically as the K64F solution.   Setup Linux PC (Ubuntu) OpenWrt AP/Router with DHCPv6-PD support (OpenWrt version used in this guide: OpenWrt Chaos Calmer 15.05.1) For reference, hardware used on this guide: TP-Link Model TL-WR741ND 150Mbps Wireless N Router OpenWRT firmware supports multiple hardware available at https://openwrt.org/ 1 FRDM-KW41Z (Host Controlled Device, connected to Linux) 1 FRDM-KW41Z (Router Eligible Device or any joiner device) Thread version 1.1.1.20 (from SDK builder at mcuxpresso.nxp.com)   Host Controlled Device firmware, make sure the following macros are enabled: THR_SERIAL_TUN_ROUTER                       /source/config.h     -> Enables TAP interface by default (not TUN) THR_SERIAL_TUN_ENABLE_ND_HOST     /app/common/app_serial_tun.h   OpenWRT router Configure IPv6 ULA-Prefix:   Linux Copy HSDK folder Create 'threadtap0' TAP interface: …/host_sdk/hsdk/demo#   sudo bash make_tap.sh Use "Thread_Shell" or modify “Thread_KW_Tun” demo to enable the SERIAL_TAP macro …/host_sdk/hsdk/demo#   nano Thread_KW_Tun.c #define SERIAL_TAP 0   modify to:  #define SERIAL_TAP  1        Note: For demo purposes, the "Thread_Shell" demo is recommended, it already uses TAP by default and allows input commands. If this is not required and only the TAP bridge is to be used, use the Thread_KW_Tun demo. Bridge the interfaces; assuming eno1 is the interface connected directly to OpenWrt: # brctl addbr br0 # brctl addif br0 eno1 # brctl addif br0 threadtap0 # ifconfig br0 up Note: (Optional) Addresses on the bridged interfaces are lost and need to be reconfigured on the actual bridge. In this example, after bridging eno1 (interface to OpenWrt router), you’d have to run #dhclient br0 to get an IPv4 address on br0 for SSH to the router and/or #dhclient -6 br0 to get an IPv6 address to the br0 interface. There's a note here https://wiki.archlinux.org/index.php/Network_bridge#With_bridge-utils  about this.   Build C demos …/host_sdk/hsdk/demo#   make Run Thread_Shell or Thread_KW_Tun demo. …/host_sdk/hsdk/demo#   sudo ./bin/Thread_Shell /dev/ttyACM0 threadtap0 25 or …/host_sdk/hsdk/demo#   sudo ./bin/Thread_KW_Tun /dev/ttyACM0 threadtap0         Note: Try to run the demo without parameters to get some help on the input parameters   ifconfig Thread_Shell demo Thread_KW_Tun demo Joiner FRDM-KW41Z (shell) Join the Thread network Verify IP addresses Ping Eth LAN interface on OpenWrt router to verify “Internet” connectivity  Regards, JC
記事全体を表示
When developing portable applications using batteries, it is important to keep track of the remaining battery level to inform the user and take action when it drops to a level that might be critical for the correct device functionality. A common measurement method consists of taking a sample of the current battery voltage and correlate it to a percentage depending on its capacity. Then this value is reported to the user in a visual manner. MKW40 is a system on chip (SoC) that embeds a processor and a Bluetooth® Low Energy (BLE)/802.15.4 radio for wireless communications. This posts describes how to obtain the current battery level and report it via BLE using this part. Hardware considerations Typically, the battery voltage is regulated so the MCU has a stable power supply across the whole battery life. This causes the ADC supply voltage to be lower than the actual battery voltage. To address this, a voltage divider is used to adequate the battery voltage to levels that can be read by the ADC. Figure 1 Typical battery level divider circuit The MKW40 includes a voltage divider on its embedded DC-DC converter removing the need to add this voltage divider externally. It is internally connected to the ADC0 channel 23 so reading this channel obtains the current level of the power source connected to the DC-DC converter in. Figure 2 DC-DC converter with battery voltage monitor Software implementation Software implementation consists in acquiring the ADC value, correlate it with a percentage level and transmit it using BLE. The connectivity software includes functions to perform all those actions. A voltage divider connected to the battery and internally wired to an ADC channel is embedded in the SoC. For the MKW40Z it is the ADC0 single ended channel 23 (ADC0_CH23) . There are some examples in the Kinetis Software Development Kit (KSDK) documentation explaining how to configure the ADC module. The connectivity software includes a function named BOARD_InitAdc in the file app.c where this is initialized. void BleApp_Init(void) {     /* Initialize application support for drivers */     BOARD_InitAdc(); <-- Initialization function         /* Initialize Software-timer */     SwTimer_Init();           /* Status Indicator fade initialization */     status_indicator_fade_init();     /* Initialise ECG Acquisition system */     ecg_acquisition_init();     /* Initialize battery charger pin configuration */     power_manager_init();     /* Create Advertising Timer */     advertisingTimerId = SwTimer_CreateTimer(TimerAdvertisingCallback); } Once initialized, this ADC channel must be read to obtain the current voltage present in the divisor. There are also some cool examples in the KSDK documentation on how to do this. The obtained value is a digital representation of the voltage in the divisor and is relative to the VDDA or VREFH voltage (depending on what is used as reference for the ADC). Since the battery voltage varies over the time (unless you use a voltage regulator or use the DC-DC converter in buck mode), the most accurate way to get the battery voltage is to obtain the actual voltage that is referencing the ADC first. For this, the MKW40Z includes a 1V reference voltage channel wired to another ADC channel: ADC0_CH27. Reading this ADC channel obtains the number of counts that correspond to 1V, and VREF can be calculated using the next formula. Once the VRef voltage has been obtained, it is possible to determine the voltage present in the battery voltage divisor by using the following formula. The obtained voltage is still only a portion of the actual battery voltage. To obtain the full voltage, the obtained value must be multiplied by the divisor relation. This relation is selected in the DC-DC register DCDC_REG0:DCDC_VBAT_DIV_CTRL and can be: VBATT, VBATT/2 or VBATT/4. After the full VBAT voltage is obtained, it must be correlated to a percentage depending on the values set for 0% and 100%. Using the slope method is a good approach to correlate the voltage value. For this, the slope m must be calculated using the formula. Where V100% is the voltage in the battery when it is fully charged, and V0% is the voltage in the battery when it is empty. Once m has been calculated, the battery percentage can be obtained using the formula: The Connectivity Software includes a function that obtains the current battery voltage connected to the DC-DC input and returns the battery percentage. It is included in the board.c file. uint8_t BOARD_GetBatteryLevel(void) {     uint16_t batVal, bgVal, batLvl, batVolt, bgVolt = 100; /*cV*/         bgVal = ADC16_BgLvl();     DCDC_AdjustVbatDiv4(); /* Bat voltage  divided by 4 */     batVal = ADC16_BatLvl() * 4; /* Need to multiply the value by 4 because the measured voltage is divided by 4*/         batVolt = bgVolt * batVal / bgVal;         batLvl = (batVolt - MIN_VOLT_BUCK) * (FULL_BAT - EMPTY_BAT) / (MAX_VOLT_BUCK - MIN_VOLT_BUCK);     return ((batLvl <= 100) ? batLvl:100);    } Reporting battery level After the battery level has been determined it can be now reported via BLE. The Connectivity Software includes predefined profile files to be included in custom applications. Battery profile is included in the files battery_service.c and .h, under the Bluetooth folder structure. To make use of them, make sure that they are included in your project. Then, include the file battery_interface.h in your BLE application file. An example using the included BLE applications is shown. In app.c (Connectivity Software examples application file) include the battery service interface #include "battery_interface.h" In the variable declaration section, create a new basConfig_t variable. This is needed to configure a new service. static basConfig_t      basServiceConfig = {service_battery, 0}; The new service needs to be created after the BLE stack has been initialized. When this happens, the function BleApp_Config is executed. Inside this function, the battery service is started.    /* Start services */ hrsServiceConfig.sensorContactDetected = mContactStatus; #if gHrs_EnableRRIntervalMeasurements_d    hrsServiceConfig.pUserData->pStoredRrIntervals = MEM_BufferAlloc(sizeof(uint16_t) * gHrs_NumOfRRIntervalsRecorded_c); #endif    Hrs_Start(&hrsServiceConfig);     Bas_Start(&basServiceConfig);         /* Allocate application timers */     mAdvTimerId = TMR_AllocateTimer(); mMeasurementTimerId = TMR_AllocateTimer(); mBatteryMeasurementTimerId = TMR_AllocateTimer(); Function Bas_Start is used for this purpose. This function starts the battery service functionality indicating that it needs to be reported by the BLE application. After a central has successfully connected to the peripheral device, the battery service must be subscribed so it’ measurements can be reported to the central. For this, the function Bas_Suscribe is used inside the connection callback. Following code shows its implementation in the connectivity software. It takes place in the connection callback function BleApp_ConnectionCallback in app.c switch (pConnectionEvent->eventType) {         case gConnEvtConnected_c:         {             mPeerDeviceId = peerDeviceId;             /* Advertising stops when connected */             mAdvState.advOn = FALSE; #if gBondingSupported_d                /* Copy peer device address information */             mPeerDeviceAddressType = pConnectionEvent->eventData.connectedEvent.peerAddressType;             FLib_MemCpy(maPeerDeviceAddress, pConnectionEvent->eventData.connectedEvent.peerAddress, sizeof(bleDeviceAddress_t)); #endif  #if gUseServiceSecurity_d                        {                 bool_t isBonded = FALSE ;                                if (gBleSuccess_c == Gap_CheckIfBonded(peerDeviceId, &isBonded) &&                     FALSE == isBonded)                 { Gap_SendSlaveSecurityRequest(peerDeviceId, TRUE, gSecurityMode_1_Level_3_c);                 }             } #endif                        /* Subscribe client*/             Bas_Subscribe(peerDeviceId);                    Hrs_Subscribe(peerDeviceId);                                                         /* UI */                        LED_StopFlashingAllLeds();                         /* Stop Advertising Timer*/             mAdvState.advOn = FALSE;             TMR_StopTimer(mAdvTimerId);                        /* Start measurements */ TMR_StartLowPowerTimer(mMeasurementTimerId, gTmrLowPowerIntervalMillisTimer_c, TmrSeconds(mHeartRateReportInterval_c), TimerMeasurementCallback, NULL);               } Once the service has been subscribed, a new measurements can be registered by using the Bas_RecordBatteryMeasurement function at any time. Bas_RecordBatteryMeasurement(basServiceConfig.serviceHandle, basServiceConfig.batteryLevel); This function receives the battery service handler previously defined (static basConfig_t basServiceConfig = {service_battery, 0}) and the current battery level percentage as input parameters. When a disconnection occurs, the battery service must be unsubscribed so no further updates are performed during disconnection. For this, the Bas_Unsuscribe function must be used after a disconnection event. case gConnEvtDisconnected_c:         {             /* Unsubscribe client */             Bas_Unsubscribe();             Hrs_Unsubscribe();             mPeerDeviceId = gInvalidDeviceId_c; Now, updated battery levels can be reported by the BLE device letting the user know when a battery must be replaced or recharged.
記事全体を表示
If the application running in the KW41Z does not operate in low power mode, the device could work without the 32 kHz oscillator. However, for it to work correctly, the clock configuration must be changed. Figure 1.  KW41Z clock distribution By default, the software stack running on the KW41Z selects a clock source that requires the 32 kHz oscillator. However, if the 32 kHz oscillator is not available, the clock configuration must be changed. Fortunately, the option to change it is already implemented, it is only required to change the clock configuration to the desired one. To do this, change the value for the CLOCK_INIT_CONFIG macro located in the app_preinclude.h file. /* Define Clock Configuration */ #define CLOCK_INIT_CONFIG           CLOCK_RUN_32‍‍‍‍‍‍‍‍‍‍ In the selected mode in this example, CLOCK_RUN_32, the selected clock mode is BLPE (Bypassed Low Power External). In this mode, the FLL is bypassed entirely, and clock is derived from the external RF oscillator, in this case, the 32 MHz external clock. These macros are the default available options to change the clock configuration, they are located in the board.h file. It is up to the application and the developer to chose the most appropriate configuration. #define CLOCK_VLPR       1U #define CLOCK_RUN_16     2U #define CLOCK_RUN_32     3U #define CLOCK_RUN_13     4U #define CLOCK_RUN_26     5U #define CLOCK_RUN_48_24  6U #define CLOCK_RUN_48_16  7U #define CLOCK_RUN_20_FLL 8U‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ More information regarding the different clock modes and their clock sources are available in the KW41Z reference manual, Chapter 5: Clock Distribution, section 5.4 Clock definitions, and Chapter 24: Multipurpose Clock Generator.
記事全体を表示
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.
記事全体を表示
The KW41Z has support for an external 26 MHz or 32 MHz reference oscillator. This oscillator is used, among other things, as the clock for the RF operation. This means that the oscillator plays an important role in the RF operation and must be tuned properly to meet wireless protocol standards. The KW41Z has adjustable internal load capacitors to support crystals with different load capacitance needs. For proper oscillator function, it is important that these load capacitors be adjusted such that the oscillator frequency is as close to the center frequency of the connected crystal (either 26 MHz or 32 MHz in this case). The load capacitance is adjusted via the BB_XTAL_TRIM bit field in the ANA_TRIM register of the Radio block. The KW41Z comes preprogrammed with a default load capacitance value. However, since there is variance in devices due to device tolerances, the correct load capacitance should be verified by verifying that the optimal central frequency is attained.  You will need a spectrum analyzer to verify the central frequency. To find the most accurate value for the load capacitance, it is recommended to use the Connectivity Test demo application. This post is aimed at showing you just how to do that.   In this case, the Agilent Technologies N9020A MXA Signal Analyzer was used to measure, 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 KW41Z with the Connectivity Test application. The project, for both IAR and KDS, for this demo application can be found in the following folder: <KW41Z_connSw_1.0.2_install_dir>\boards\frdmkw41z\wireless_examples\smac\connectivity_test\FreeRTOS NOTE:  If you need help programming this application onto your board, consult your Getting Started material for the SMAC applications.  For the FRDM-KW41Z, it is located here. Once the device is programmed, make sure the device is connected to a terminal application in your PC. When you start the application, you're greeted by this screen: 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. Press 'd' and 'f' to change the XTAL trim value, thus changing the central frequency. Now, considering the test in this example is being performed in 802.15.4 channel 11, the central frequency should be centered exactly in 2.405 GHz, but on this board, it is slightly above (2.4050259 GHz) by default. In order to fix this, the XTAL trim value was adjusted to a value that moves the frequency to where it should be centered. Once the adequate XTAL trim value is found, it can be programmed to be used by default. This other post explains how to do this process.
記事全体を表示
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
記事全体を表示
Bluetooth Low Energy is a standard for Low Power Wireless Networks introduced in the Bluetooth specification 4.0. Its target application domains include medical, sports & fitness, home automation and others. The adoption and development rates of this technology are growing fast helped by the wide availability of hardware support in most modern mobile phones and mobile operating systems. The purpose of this application note is to show how the Freescale FRDM-KW40Z can board with BLE Controller software can be used with the hcitool from the Linux Bluetooth stack over the HCI interface. 1. Introduction The Bluetooth specification has a very well defined interface between the Controller and the Host called the HCI (Host Controller Interface). This interface is defined for and can be used with various transport layers including an asynchronous serial transport layer. A typical scenario of Bluetooth Low Energy hardware use is a development board which has a BLE Controller accessible via serial transport HCI connected to a device on which the BLE Host runs. The device which runs the BLE Host can be any type of embedded device or a PC. PCs running a Linux type OS can use the hcitool from the Linux Bluetooth Stack to interact with a BLE Controller via the HCI interface. The particular use case of  FRDM-KW40Z board with a serial transport HCI interface running over USB CDC and connected to a PC running the Linux Bluetooth stack is shown in the diagram below and will be detailed din the following sections. Figure 1FRDM-KW40Z (BLE Controller) connected to Linux PC (Bluetooth Host Stack) via HCI Serial Transport 2. Loading the HCI Application onto the FRDM-KW40Z First load the hci_app on the FRDM-KW40Z board. The hci_app aplication can be found in the <ConnectivitySwInstallationPath>\ConnSw\examples\bluetooth\hci_app folder. 3. Connecting the FRDM-KW40Z to the Computer via a Serial Port After the app is downloaded to the board plug the board into a free USB port of your Linux computer. The following instructions, commands and their output is typical to a Debian based Linux OS. After the board is plugged in run the following command to list the serial ports available. >> dmesg | grep tty [ 0.000000] console [tty0] enabled [ 2374.118201] cdc_acm 1-2:1.1: ttyACM0: USB ACM device In our example the FRDM-KW40Z board serial port is ttyACM0. To test the connection some HCI commands can be sent in hex format from any terminal application to the serial HCI on the FRDM-KW40Z board. In the figure below an HCI_Read_BD_ADDR command and its corresponding Command Complete Event are shown as they were sent and received in hexadecimal format from the moserial serial terminal GUI application. Figure 2: HCI command and response event in hexadecimal format (HCI UART Transport) 4. Connecting the HCI Serial Interface to the Bluetooth Stack To connect the Linux Bluetooth stack to a serial HCI interface the hciattach command must be run as shown below. >> hciattach /dev/ttyACM0 any 115200 noflow nosleep Device setup complete If the the HCI serial interface is successfully attached to the Bluetooth stack then the "Device setup complete" message is shown. The any parameter specifies a generic Bluetooth device. The 115200 parameter is the UART baudrate. The noflow parameter diasables serial flow control. The nosleep parameter disables hardware specific power managment. Run the hciconfig command with no parameters to check the HCI interface id of the newly attached HCI serial device. >> hciconfig hci1:    Type: BR/EDR  Bus: UART     BD Address: 00:04:9F:00:00:15  ACL MTU: 27:4 SCO MTU: 0:0     UP RUNNING     RX bytes:205 acl:0 sco:0 events:14 errors:0     TX bytes:112 acl:0 sco:0 commands:14 errors:0 hci0:    Type: BR/EDR  Bus: USB     BD Address: 90:00:4E:A4:70:97  ACL MTU: 310:10  SCO MTU: 64:8     UP RUNNING     RX bytes:595 acl:0 sco:0 events:37 errors:0     TX bytes:2564 acl:0 sco:0 commands:36 errors:0 In this example the FRDM-KW40Z is assigned the hci1 interface as can be seen from the bus type (Type: BR/EDR  Bus: UART). The hci0 interface is the example shown corresponds to the on-board Bluetooth module from the machine. On some systems the interface might need to be manually started by using the hciconfig interfaceId up command. hciconfig hci1 up 5. Configuring the Bluetooth Device and Listing its Capabilities The hciconfig command offers the possibility of configuring the device and listing the device capabilities. To find all commands supported by the hciconfig tool type the following command. >> hciconfig –h ...display supported commands... Each individual hciconfig command must be addressed to the correct HCI interface as reported above. In our example we use the hci1 interface. Some hciconfig commands require root privileges and must be run with sudo (the "Operation not permitted(1)" error will be returned if a command needs to be run with root privileges). Some useful hci config commands: >> hciconfig hci1 version    -> lists hci device verison information >> hciconfig hci1 revision    -> lists hci device revision information >> hciconfig hci1 features    -> lists the features supported by the device >> hciconfig hci1 commands    -> lists the hci commands supported by the device >> sudo hciconfig hci1 lestates    -> lists the BLE states supported by the device >> sudo hciconfig hci1 lerandaddr 11:22:33:44:55:66    -> set a random address on the device >> sudo hciconfig hci1 leadv 3    -> enable LE advertising of the specified type >> sudo hciconfig hci1 noleadv    -> disable LE advertising Now the newly connected board with a serial HCI is attached to a HCI interface of the Bluetooth stack and is ready to use. 6.    Controlling the Bluetooth Device using the hcitool The hcitool can be used to send HCI commands to the Bluetooth device. A command is available which lists all available hcitool actions. >> hcitool -h ...display supported commands... To target a specific HCI interface use the -i hciX option for an hcitool command. We will use -i hci1 in our examples. The hcitool supports commands for common BLE HCI operations some of which are shown below and also supports sending generic HCI commands using a dedicated option which uses hexadecimal numbers for the OGF (Command Group), OCF (Command Code) and the parameters. The 6 bit OGF and the 10 bit OCF compose the 16 bit HCI Command Opcode. The command parameters are specific to each command. 6.1.  Listing Devices Available to the hcitool An hcitool command can list all available device interfaces. >> hcitool dev Devices: hci1    00:04:9F:00:00:15 hci0    90:00:4E:A4:70:97 The device we are working with is connected to the hci1 interface as seen from the output of the hciconfig command used above. 6.2.  Scanning for Advertising LE Devices The hcitool can be used to perform a LE Device scan. This command requires root privileges. Press Ctrl+C to stop the scan at any time. >> sudo hcitool -i hci1 lescan LE Scan ... 00:04:9F:00:00:13 (FSL_OTAC) ^C A list of addresses and device names will be shown if advertised (<<Shortened Local Name>> or <<Complete Local Name>> as define din the specification). 6.3.  Obtaining Remote LE Device Information Using the hcitool To obtain information about a remote LE device a special hcitool command can be used. The hcitool leinfo command creates a connection, extracts information from the remote device and then disconnects. The remote device information is shown at the command prompt. >> sudo hcitool -i hci1 leinfo 00:04:9F:00:00:13 Requesting information ...        Handle: 32 (0x0020)        LMP Version: 4.1 (0x7) LMP Subversion: 0x113        Manufacturer: Freescale Semiconductor, Inc. (511)        Features: 0x1f 0x00 0x00 0x00 0x00 0x00 0x00 0x00 In this example information about a device previously discovered using the hcitool lescan command is shown. 6.4.  Connecting and Disconnecting from a Remote LE Device Connecting to a remote LE device is done using the hcitool lecc command. >> sudo hcitool -i hci1 lecc 00:04:9F:00:00:13 Connection handle 32 As before a previously discovered device address is used. If the connection is successful then the Connection Handle is returned and in our case the Connection Handle is 32. The hcitool con command shows active connections information: address, connection handle, role, etc. >> hcitool con Connections: < LE 00:04:9F:00:00:13 handle 32 state 1 lm MASTER To end a LE connection the hcitool ledc command can be used. It must be provided with the Connection Handle to be terminated, and optionally the reason. The device handle obtained after the connection and shown in the connected devices list is used. >> hcitool –I hci1 ledc 32 >> Listing the connections after all connections are terminated will show an empty connection list. >> hcitool con Connections: >> 6.5.  Sending Arbitrary HCI Commands To send arbitrary HCI commands to a device using the Command CopCode (OGF and OCF) the hcitool cmd command can be used. As an example the HCI_Read_BD_ADDR command is used which has the 0x1009 OpCode (OGF=0x04, OCF=0x009) and no parameters. It is the same command shown in the direct serial port to HCI communication example above. hcitool -i hci0 cmd 0x04 0x0009 < HCI Command: ogf 0x04, ocf 0x0009, plen 0 > HCI Event: 0x0e plen 10   01 09 10 00 15 00 00 9F 04 00 The OpCode OGF (0x04) and OCF (0x009) and no parameters are passed to the hcitool cmd command all in hexadecimal format. The parameters length (plen) is 0 for the command. The response is a Command Complete event (0x03) with the parameters length (plen) 10. The parameters are 01 09 10 00 15 00 00 9F 04 00: 01 is the Num_HCI_Command_Packets parameter 09 10 is the Command OpCode for which this Command Complete Event is returned (in little endian format) 00 is the status – Success in this case 15 00 00 9F 04 00 is the BD_ADDR of the device as listed by the hcitool dev command
記事全体を表示
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;
記事全体を表示
General summary MCUBOOT, fsci_bootloader and otap_bootloader are 3 different bootloader applications that can be used depending on the use case. The MCU Flashloader is a separate implementation but it's also mentioned to avoid misunderstanding.   MCUBOOT The MCU bootloader provides support for multiple communication protocols (UART, SPI, I2C, CAN) and multiple applications to interface with it. Summary: - It's a configurable flash programming utility that operates over a serial connection on several Kinetis MCUs. - Host-side command line (blhost) and GUI tools are available to communicate with the bootloader.  -  By default, application starts at address 0xa000. - MCU Bootloader|NXP website - MCU Bootloader Reference Manual - MCU Bootloader Demo Application User's Guide   fsci_bootloader Framework Serial Connectivity Interface (FSCI) is an NXP propietary protocol that allows interfacing the Kinetis protocol stack with a host system or PC tool using a serial communication interface. The FSCI bootloader enables the FSCI module to communicate with the PC and transfer the image using the FSCI protocol. Summary: - It relies on the FSCI protocol to transfer the binary from a PC connected via UART, using a python and C applications. - To enter into bootloader mode (in FRDM-KW41Z), hold SW1 (Reset) and press SW4, then release SW1 first and SW4 second. Please refer to demo user's guide to get the specific steps for your platform. - By default, application starts at 0x4000. - FSCI Bootloader Manual   otap_bootloader The Connectivity SDK contains Over-the-Air firmware upgrade examples. The OTAP bootloader loads an image obtained from wireless communication, the OTAP bootloader only enters after an image was successfully transferred to the client device (internal or external flash). Summary: - It's used by over the air programmed devices. - The bootloader mode only enters if a flag is set after reset triggered by a successful reception of an image over the air. - By default, application starts at 0x4000. - Kinetis Thread Stack Over-the-Air (OTA) Firmware Update User’s Guide   mcu_flashloader The MCU flashloader is a specific implementation of the MCU bootloader. For the flashloader implementation, the MCU bootloader command interface is packaged as an executable that is loaded from flash and executed from RAM. This configuration allows the user application to be placed at the beginning of the on-chip flash where it is automatically launched upon boot from flash. Using the MCU flashloader to program a user application to the beginning of the flash makes this implementation of the bootloader a one-time programming aid. The MCU flashloader doesn't allow to jump to a different section after a timeout or button press like the other bootloaders, it's main purpose is to flash an application without the need of an external debugger, mainly used for factory programming. Summary: - It is pre-programmed into many Kinetis flash devices during manufacturing and enables flash programming without the need for a debugger. - After the user application is programmed into flash memory, the Kinetis flashloader is no longer available. - Documentation: Getting Started with the MCU Flashloader   You can select from the MCU Bootloader, FSCI_Bootloader and OTAP Bootloader, depending on your needs. JC
記事全体を表示
This Application Note provides guidance on migrating ZigBee 3.0 Base device application designed for the NXP JN516x wireless microcontrollers to the KW41Z with the help of attached PDF.
記事全体を表示
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);       } }
記事全体を表示
What you need: USB-KW40Z boards (at least 3 recommended) Kinetis KW40Z Connectivity Software Kinetis Protocol Analyzer Adapter Wireshark Consult the USB-KW40Z getting started guide for an in depth tutorial on how to program the boards with the sniffer software and how to install and use the Kinetis Protocol Analyzer Adapter and Wireshark. For best performance at least 3 boards are needed to continuously monitor all 3 BLE advertising channels: 37, 38 and 39. If you have more then it’s even better. Having less than 3 sniffer boards will lead to the BLE sniffer setup missing some advertising packets and connection events. If only 1 or 2 boards are present they will have to jump between the 3 advertising channels. After the initial setup is complete make sure the boards are plugged into USB ports and then start the Kinetis Protocol Analyzer Adapter software. Immediately after the application is started it will start looking for the sniffers: After the sniffers are detected the application window should look like the screenshot below. There should be a separate row shown for each sniffer board which is plugged in (3 in the example below – COM32, COM34, and COM33). Set each sniffer on a different advertising channel and (37, 38 and 39) and if you’re looking to sniff a specific device enable the Address Filter checkbox and enter the device’s address in the adjacent field as shown in the screenshot below. Use the same device address for all sniffer devices. Press the “shark fin” button in the upper right of the window to start Wireshark. After Wireshark starts select the PCAP IF shown in the Kinetis Protocol Analyzer Adapter window and start capturing packets. Local Area Connection 2 is the PCAP IF in the example. Wireshark will start showing the captured packets and the sniffers will catch Connection Request packets sent to the target device on any of the advertising channels. Useful tip: You can use the btle.advertising_header.length != 0 or btle.data_header.length != 0 filter in Wireshark to filter out empty BLE packets.
記事全体を表示