bluetooth
Kom igång med TI: s BLE Stack
Sök…
Ansluter till BLE-slavenheter
Introduktion
Texas Instruments (TI) CC26XX- serien SoC: er är tillgängliga trådlösa MCU: er som riktar sig till Bluetooth Low Energy (BLE) -applikationer. Tillsammans med MCU: erna erbjuder TI en fullfjädrad programvaruback som tillhandahåller nödvändiga API- och provkoder för att snabbt komma igång med utvecklare med verktygskedjan. Men för nybörjare är det alltid frågan om var man ska börja framför en lång lista med referensdokument och koder. Denna anmärkning syftar till att registrera de nödvändiga stegen som krävs för att sparka det första projektet i gång.
Den enkla perifera profilen är "Hello World" -exemplet på BLE-stacken, där MCU fungerar som en BLE-kringutrustning för uppströmsvärdar, eller BLE-serviceklienter, som PC och smartphones. Vanliga verkliga applikationer inkluderar: Bluetooth-hörlurar, Bluetooth-temperatursensor, etc.
Innan vi börjar måste vi först samla grundläggande programvara och hårdvaruverktyg för att programmera och felsöka.
BLE stack
Ladda ner och installera TI: s BLE-STACK-2-2-0 från den officiella webbplatsen. Antag att den är installerad på standardplatsen 'C: \ ti'.
IDE - det finns två alternativ:
IAR inbäddad arbetsbänk för ARM. Detta är ett kommersiellt verktyg med en utvärderingsperiod på 30 dagar.
TI: s Code Composer Studio (CCS). TI: s officiella IDE och erbjuder gratis licens. I det här exemplet kommer vi att använda CCS V6.1.3
Programvara för hårdvara
Rekommendera TI: s XDS100 USB-gränssnitt JTAG-enhet.
Importera exempelprojekt i CCS
Exempelkoden Simple Peripheral Profile levereras med BLE-Stack-installationen. Följ stegen nedan för att importera detta exempelprojekt till CCS.
- Starta CCS, skapa en arbetsyta-mapp. Sedan File-> Import. Under 'Välj en importkälla' väljer du alternativet 'Code Compose Studio -> CCS Projects' och klickar på 'Next'.
- Bläddra till 'C: \ ti \ simplelink \ ble_sdk_2_02_00_31 \ exempel \ cc2650em \ simple_peripheral \ ccs'. Två projekt kommer att upptäckas. Markera alla och kryssa för båda alternativen nedan. Klicka sedan på "Slutför". Genom att kopiera projekt till arbetsyta lämnar du den ursprungliga projektinställningen oförändrad för alla följande ändringar.
Exemplet Simple Peripheral Profile inkluderar två projekt:
- simple_peripheral_cc2650em_app
- simple_peripheral_cc2650em_stack
'cc2650em' är kodnamnet för TI: s utvärderingskort för cc2650. Projektet _stack innehåller koderna och binärerna för TI: s BEL-Stack-2-2-0, som hanterar Bluetooth-reklam, handskakning, frekvenssynkronisering osv. Detta är den del av koden som är relativt stabil och inte vill vara berörda av utvecklarna för det mesta. _App-projektet är där utvecklare implementerar sina egna uppgifter och BLE-tjänster.
Bygg och ladda ner
Klicka på menyerna 'Projekt-> Bygg alla' för att bygga båda projekten. Om kompilatorn rapporterar om ett slags internt fel vid länkning kan du försöka inaktivera alternativet 'compress_dwarf' för länken genom att:
- högerklicka på projektet och välj 'Propoerties'.
- i "Build-> ARM Linker", klicka på "Edit Flags" -knappen.
- ändra det sista alternativet till '--compress_dwarf = off'.
När båda projekten har byggts framgångsrikt, klicka på 'Kör-> felsökning' separat för att ladda ner både stack- och app-bilder till MCU.
Tryck på koden
För att kunna göra aggressiva ändringar av provkoden måste utvecklare få detaljerad kunskap om den skiktade strukturen i BLE-stacken. För elementära uppgifter som temperaturläsning / avisering kan vi fokusera på endast två filer: PROFILES / simple_gatt_profile.c (.h) och Application / simple_peripheral.c (.h)
simple_gatt_profile.c
Alla Bluetooth-applikationer erbjuder en viss typ av tjänst, var och en består av en uppsättning egenskaper. Den enkla perifera profilen definierar en enkel tjänst, med UUID: 0xFFF0, som består av 5 egenskaper. Den här tjänsten anges i simple_gatt_profile.c. En sammanfattning av den enkla tjänsten listas enligt följande.
namn | Datastorlek | UUID | Beskrivning | Fast egendom |
---|---|---|---|---|
simplePeripheralChar1 | 1 | 0xFFF1 | Egenskaper 1 | Läsa skriva |
simplePeripheralChar2 | 1 | 0xFFF2 | Egenskaper 2 | Endast läsning |
simplePeripheralChar3 | 1 | 0xFFF3 | Egenskaper 3 | Skriv bara |
simplePeripheralChar4 | 1 | 0xFFF4 | Egenskaper 4 | Meddela |
simplePeripheralChar5 | 5 | 0xFFF5 | Egenskaper 5 | Endast läsning |
De fem egenskaperna har olika egenskaper och fungerar som exempel för olika användarfall. Till exempel kan MCU använda simplePeripheralChar4 för att meddela sina klienter, uppströmsvärdar, om förändring av information.
För att definiera en Bluetooth-tjänst måste man konstruera en attributstabell.
/*********************************************************************
* Profile Attributes - Table
*/
static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
// Simple Profile Service
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 *)&simpleProfileService /* pValue */
},
// Characteristic 1 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar1Props
},
// Characteristic Value 1
{
{ ATT_UUID_SIZE, simpleProfilechar1UUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
&simpleProfileChar1
},
// Characteristic 1 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
simpleProfileChar1UserDesp
},
...
};
Attributstabellen börjar med en standard 'primärserviceUUID', som anger tjänstens UUID (0xFFF0 i detta fall). Därefter följs deklarationer av alla egenskaper som består av tjänsten. Varje egenskaper har flera attribut, nämligen åtkomsttillstånd, värde och användarbeskrivning, etc. Denna tabell registreras senare med BLE-stacken.
// Register GATT attribute list and CBs with GATT Server App
status = GATTServApp_RegisterService( simpleProfileAttrTbl,
GATT_NUM_ATTRS( simpleProfileAttrTbl ),
GATT_MAX_ENCRYPT_KEY_SIZE,
&simpleProfileCBs );
Vid registrering av tjänsten måste utvecklare tillhandahålla tre återuppringningsfunktioner för 'Läs', 'Skriv' och 'Autorisation' av egenskaperna. Vi hittar i provkoden listan över återuppringningsfunktioner.
/*********************************************************************
* PROFILE CALLBACKS
*/
// Simple Profile Service Callbacks
// Note: When an operation on a characteristic requires authorization and
// pfnAuthorizeAttrCB is not defined for that characteristic's service, the
// Stack will report a status of ATT_ERR_UNLIKELY to the client. When an
// operation on a characteristic requires authorization the Stack will call
// pfnAuthorizeAttrCB to check a client's authorization prior to calling
// pfnReadAttrCB or pfnWriteAttrCB, so no checks for authorization need to be
// made within these functions.
CONST gattServiceCBs_t simpleProfileCBs =
{
simpleProfile_ReadAttrCB, // Read callback function pointer
simpleProfile_WriteAttrCB, // Write callback function pointer
NULL // Authorization callback function pointer
};
Så simpleProfile_ReadAttrCB kommer att ringas när serviceklienten skickar en läsbegäran via Bluetooth-anslutningen. På samma sätt kommer simpleProfile_WriteAttrCB att ringas när en skrivbegäran görs. Att förstå dessa två funktioner är nyckeln till framgång för anpassning av projekt.
Nedan visas funktionen för återuppringning.
/*********************************************************************
* @fn simpleProfile_ReadAttrCB
*
* @brief Read an attribute.
*
* @param connHandle - connection message was received on
* @param pAttr - pointer to attribute
* @param pValue - pointer to data to be read
* @param pLen - length of data to be read
* @param offset - offset of the first octet to be read
* @param maxLen - maximum length of data to be read
* @param method - type of read message
*
* @return SUCCESS, blePending or Failure
*/
static bStatus_t simpleProfile_ReadAttrCB(uint16_t connHandle,
gattAttribute_t *pAttr,
uint8_t *pValue, uint16_t *pLen,
uint16_t offset, uint16_t maxLen,
uint8_t method)
{
bStatus_t status = SUCCESS;
// If attribute permissions require authorization to read, return error
if ( gattPermitAuthorRead( pAttr->permissions ) )
{
// Insufficient authorization
return ( ATT_ERR_INSUFFICIENT_AUTHOR );
}
// Make sure it's not a blob operation (no attributes in the profile are long)
if ( offset > 0 )
{
return ( ATT_ERR_ATTR_NOT_LONG );
}
uint16 uuid = 0;
if ( pAttr->type.len == ATT_UUID_SIZE )
// 128-bit UUID
uuid = BUILD_UINT16( pAttr->type.uuid[12], pAttr->type.uuid[13]);
else
uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
switch ( uuid )
{
// No need for "GATT_SERVICE_UUID" or "GATT_CLIENT_CHAR_CFG_UUID" cases;
// gattserverapp handles those reads
// characteristics 1 and 2 have read permissions
// characteritisc 3 does not have read permissions; therefore it is not
// included here
// characteristic 4 does not have read permissions, but because it
// can be sent as a notification, it is included here
case SIMPLEPROFILE_CHAR2_UUID:
*pLen = SIMPLEPROFILE_CHAR2_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR2_LEN );
break;
case SIMPLEPROFILE_CHAR1_UUID:
*pLen = SIMPLEPROFILE_CHAR1_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR1_LEN );
break;
case SIMPLEPROFILE_CHAR4_UUID:
*pLen = SIMPLEPROFILE_CHAR4_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR4_LEN );
break;
case SIMPLEPROFILE_CHAR5_UUID:
*pLen = SIMPLEPROFILE_CHAR5_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN );
break;
default:
// Should never get here! (characteristics 3 and 4 do not have read permissions)
*pLen = 0;
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
return ( status );
}
Jag har ändrat koden något från sin ursprungliga version. Denna funktion tar 7 parametrar, som förklaras i rubrikkommentarerna. Funktionen börjar med att kontrollera attributets åtkomsttillstånd, t.ex. om det har lästillstånd. Därefter kontrollerar det om detta är ett segmentläsning av en större begäran om läsning av klump genom att testa villkoret 'if (offset> 0)'. Uppenbarligen stöder funktionen inte klumpläsning för tillfället. Därefter extraheras UUID för det begärda attributet. Det finns två typer av UUID: 16-bitars och 128-bitars. Medan provkoden definierar alla egenskaper med 16-bitars UUID, är 128-bitars UUID mer universell och mer vanligt förekommande i uppströmsvärdar som PC och smartphones. Därför används flera kodrader för att konvertera 128-bitars UUID till 16-bitars UUID.
uint16 uuid = 0;
if ( pAttr->type.len == ATT_UUID_SIZE )
// 128-bit UUID
uuid = BUILD_UINT16( pAttr->type.uuid[12], pAttr->type.uuid[13]);
else
uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
Slutligen, efter att vi har fått UUID, kan vi bestämma vilket attribut som begärs. Sedan är det återstående jobbet på utvecklarens sida att kopiera värdet på det begärda attributet till destinationspekaren 'pValue'.
switch ( uuid )
{
case SIMPLEPROFILE_CHAR1_UUID:
*pLen = SIMPLEPROFILE_CHAR1_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR1_LEN );
break;
case SIMPLEPROFILE_CHAR2_UUID:
*pLen = SIMPLEPROFILE_CHAR2_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR2_LEN );
break;
case SIMPLEPROFILE_CHAR4_UUID:
*pLen = SIMPLEPROFILE_CHAR4_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR4_LEN );
break;
case SIMPLEPROFILE_CHAR5_UUID:
*pLen = SIMPLEPROFILE_CHAR5_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN );
break;
default:
*pLen = 0;
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
Funktionen för återuppringning är liknande förutom att det finns en speciell typ av skrivning med UUID av GATT_CLIENT_CHAR_CFG_UUID. Detta är uppströms värdens begäran om att registrera sig för anmälan eller indikation för egenskaper. Bara ringa API GATTServApp_ProcessCCCWriteReq för att skicka förfrågan till BLE-stacken.
case GATT_CLIENT_CHAR_CFG_UUID:
status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
offset, GATT_CLIENT_CFG_NOTIFY | GATT_CLIENT_CFG_INDICATE ); // allow client to request notification or indication features
break;
Applikationssidan av koden på MCU kanske vill bli meddelad med alla ändringar av skrivtillåtna egenskaper. Utvecklare kan implementera detta meddelande som de vill. I exempelskoden används återuppringningsfunktionen.
// If a charactersitic value changed then callback function to notify application of change
if ( (notifyApp != 0xFF ) && simpleProfile_AppCBs && simpleProfile_AppCBs->pfnSimpleProfileChange )
{
simpleProfile_AppCBs->pfnSimpleProfileChange( notifyApp );
}
Å andra sidan, om BLE-kringutrustningen vill meddela uppströmsvärdar om alla förändringar i dess karakteristika, kan den ringa API GATTServApp_ProcessCharCfg. Denna API visas i funktionen SimpleProfile_SetParameter.
/*********************************************************************
* @fn SimpleProfile_SetParameter
*
* @brief Set a Simple Profile parameter.
*
* @param param - Profile parameter ID
* @param len - length of data to write
* @param value - pointer to data to write. This is dependent on
* the parameter ID and WILL be cast to the appropriate
* data type (example: data type of uint16 will be cast to
* uint16 pointer).
*
* @return bStatus_t
*/
bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value )
{
bStatus_t ret = SUCCESS;
switch ( param )
{
case SIMPLEPROFILE_CHAR2:
if ( len == SIMPLEPROFILE_CHAR2_LEN )
{
VOID memcpy( simpleProfileChar2, value, SIMPLEPROFILE_CHAR2_LEN );
}
else
{
ret = bleInvalidRange;
}
break;
case SIMPLEPROFILE_CHAR3:
if ( len == sizeof ( uint8 ) )
{
simpleProfileChar3 = *((uint8*)value);
}
else
{
ret = bleInvalidRange;
}
break;
case SIMPLEPROFILE_CHAR1:
if ( len == SIMPLEPROFILE_CHAR1_LEN )
{
VOID memcpy( simpleProfileChar1, value, SIMPLEPROFILE_CHAR1_LEN );
}
else
{
ret = bleInvalidRange;
}
break;
case SIMPLEPROFILE_CHAR4:
if ( len == SIMPLEPROFILE_CHAR4_LEN )
{
//simpleProfileChar4 = *((uint8*)value);
VOID memcpy( simpleProfileChar4, value, SIMPLEPROFILE_CHAR4_LEN );
// See if Notification has been enabled
GATTServApp_ProcessCharCfg( simpleProfileChar4Config, simpleProfileChar4, FALSE,
simpleProfileAttrTbl, GATT_NUM_ATTRS( simpleProfileAttrTbl ),
INVALID_TASK_ID, simpleProfile_ReadAttrCB );
}
else
{
ret = bleInvalidRange;
}
break;
case SIMPLEPROFILE_CHAR5:
if ( len == SIMPLEPROFILE_CHAR5_LEN )
{
VOID memcpy( simpleProfileChar5, value, SIMPLEPROFILE_CHAR5_LEN );
}
else
{
ret = bleInvalidRange;
}
break;
default:
ret = INVALIDPARAMETER;
break;
}
return ( ret );
}
Så om den enkla kringutrustningen vill anmäla det aktuella värdet för SIMPLEPROFILE_CHAR4 till peer-enheter, kan det helt enkelt ringa funktionen SimpleProfile_SetParameter.
Sammanfattningsvis definierar PROFILES / simple_gatt_profile.c (.h) innehållet i tjänsten som BLE-kringutrustningen vill presentera för sina kunder, liksom de sätt på vilka egenskaperna i tjänsten nås.
simple_peripheral.c
TI: s BLE-stack körs ovanpå ett lite flertrådigt OS-lager. För att lägga till en arbetsbelastning i MCU måste utvecklarna skapa en uppgift först. simple_peripheral.c demonstrerar grundstrukturen för en anpassad uppgift, som inkluderar skapandet, initieringen och hushållning av uppgiften. Till att börja med de grundläggande uppgifterna som temperaturavläsning och avisering kommer vi att fokusera på några viktiga funktioner nedan.
I början av filen definieras en uppsättning parametrar som kan påverka Bluetooth-anslutningsbeteenden.
// Advertising interval when device is discoverable (units of 625us, 160=100ms)
#define DEFAULT_ADVERTISING_INTERVAL 160
// Limited discoverable mode advertises for 30.72s, and then stops
// General discoverable mode advertises indefinitely
#define DEFAULT_DISCOVERABLE_MODE GAP_ADTYPE_FLAGS_GENERAL
// Minimum connection interval (units of 1.25ms, 80=100ms) if automatic
// parameter update request is enabled
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 80
// Maximum connection interval (units of 1.25ms, 800=1000ms) if automatic
// parameter update request is enabled
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL 400
// Slave latency to use if automatic parameter update request is enabled
#define DEFAULT_DESIRED_SLAVE_LATENCY 0
// Supervision timeout value (units of 10ms, 1000=10s) if automatic parameter
// update request is enabled
#define DEFAULT_DESIRED_CONN_TIMEOUT 1000
// Whether to enable automatic parameter update request when a connection is
// formed
#define DEFAULT_ENABLE_UPDATE_REQUEST TRUE
// Connection Pause Peripheral time value (in seconds)
#define DEFAULT_CONN_PAUSE_PERIPHERAL 6
// How often to perform periodic event (in msec)
#define SBP_PERIODIC_EVT_PERIOD 1000
Parametrar DEFAULT_DESIRED_MIN_CONN_INTERVAL, DEFAULT_DESIRED_MAX_CONN_INTERVAL och DEFAULT_DESIRED_SLAVE_LATENCY definierar tillsammans anslutningsintervallet för en Bluetooth-anslutning, vilket är hur ofta ett par enheter utbyter information. Ett lägre anslutningsintervall innebär ett mer lyhörd beteende men också högre strömförbrukning.
Parameter DEFAULT_DESIRED_CONN_TIMEOUT definierar hur länge du ska få ett peer-svar innan en anslutning anses förlorad. Parameter DEFAULT_ENABLE_UPDATE_REQUEST definierar om slavenheten får ändra anslutningsintervall under körning. Det är användbart när det gäller energibesparing att ha olika anslutningsparametrar för upptagen och inaktiva faser.
Parametern SBP_PERIODIC_EVT_PERIOD definierar perioden för en klockhändelse som tillåter uppgiften att utföra ett funktionssamtal med jämna mellanrum. Detta är den perfekta platsen för oss att lägga till koden för att läsa temperatur och meddela serviceklienterna.
Den periodiska klockan initieras i funktionen SimpleBLEPeripheral_init.
// Create one-shot clocks for internal periodic events.
Util_constructClock(&periodicClock, SimpleBLEPeripheral_clockHandler,
SBP_PERIODIC_EVT_PERIOD, 0, false, SBP_PERIODIC_EVT);
Detta skapar en klocka med en period av SBP_PERIODIC_EVT_PERIOD. Och vid timeout kommer den att kalla funktionen SimpleBLEPeripheral_clockHandler med parametern SBP_PERIODIC_EVT. Klockhändelsen kan sedan triggas av
Util_startClock(&periodicClock);
Genom att söka efter nyckelordet Util_startClock kan vi konstatera att denna periodiska klocka först utlöses på GAPROLE_CONNECTED-händelsen (inuti funktionen SimpleBLEPeripheral_processStateChangeEvt), vilket innebär att uppgiften startar en periodisk rutin när den upprättar en anslutning till en värd.
När den periodiska klockan avslutas kommer den registrerade återuppringningsfunktionen att anropas.
/*********************************************************************
* @fn SimpleBLEPeripheral_clockHandler
*
* @brief Handler function for clock timeouts.
*
* @param arg - event type
*
* @return None.
*/
static void SimpleBLEPeripheral_clockHandler(UArg arg)
{
// Store the event.
events |= arg;
// Wake up the application.
Semaphore_post(sem);
}
Denna funktion ställer in en flagga i händelsesvektorn och aktiverar applikationen från OS: s uppgiftslista. Observera att vi inte gör någon specifik användararbete i denna återuppringningsfunktion, eftersom den INTE rekommenderas. Användarens arbetsbelastning innebär ofta samtal till BLE-stack-API: er. Att göra BLE-stack API-samtal i en återuppringningsfunktion resulterar ofta i systemundantag. Istället sätter vi en flagga i händelsevektorn i uppgiften och väntar på att den ska behandlas senare i applikationskonteksten. Ingångspunkten för exempelfunktionen är simpleBLEPeripheral_taskFxn ().
/*********************************************************************
* @fn SimpleBLEPeripheral_taskFxn
*
* @brief Application task entry point for the Simple BLE Peripheral.
*
* @param a0, a1 - not used.
*
* @return None.
*/
static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{
// Initialize application
SimpleBLEPeripheral_init();
// Application main loop
for (;;)
{
// Waits for a signal to the semaphore associated with the calling thread.
// Note that the semaphore associated with a thread is signaled when a
// message is queued to the message receive queue of the thread or when
// ICall_signal() function is called onto the semaphore.
ICall_Errno errno = ICall_wait(ICALL_TIMEOUT_FOREVER);
if (errno == ICALL_ERRNO_SUCCESS)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
if (ICall_fetchServiceMsg(&src, &dest,
(void **)&pMsg) == ICALL_ERRNO_SUCCESS)
{
uint8 safeToDealloc = TRUE;
if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
{
ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
// Check for BLE stack events first
if (pEvt->signature == 0xffff)
{
if (pEvt->event_flag & SBP_CONN_EVT_END_EVT)
{
// Try to retransmit pending ATT Response (if any)
SimpleBLEPeripheral_sendAttRsp();
}
}
else
{
// Process inter-task message
safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
}
}
if (pMsg && safeToDealloc)
{
ICall_freeMsg(pMsg);
}
}
// If RTOS queue is not empty, process app message.
while (!Queue_empty(appMsgQueue))
{
sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);
if (pMsg)
{
// Process message.
SimpleBLEPeripheral_processAppMsg(pMsg);
// Free the space from the message.
ICall_free(pMsg);
}
}
}
if (events & SBP_PERIODIC_EVT)
{
events &= ~SBP_PERIODIC_EVT;
Util_startClock(&periodicClock);
// Perform periodic application task
SimpleBLEPeripheral_performPeriodicTask();
}
}
}
Det är en oändlig slinga som fortsätter att polla uppgifternas stack- och applikationsmeddelandeköer. Den kontrollerar också sin händelsesvektor för olika flaggor. Det är där den periodiska rutinen faktiskt utförs. Vid upptäckten av en SBP_PERIODIC_EVT, rensar uppgiften först flaggan, startar samma timer omedelbart och anropar rutinfunktionen SimpleBLEPeripheral_performPeriodicTask ();
/*********************************************************************
* @fn SimpleBLEPeripheral_performPeriodicTask
*
* @brief Perform a periodic application task. This function gets called
* every five seconds (SBP_PERIODIC_EVT_PERIOD). In this example,
* the value of the third characteristic in the SimpleGATTProfile
* service is retrieved from the profile, and then copied into the
* value of the fourth characteristic.
*
* @param None.
*
* @return None.
*/
static void SimpleBLEPeripheral_performPeriodicTask(void)
{
uint8_t newValue[SIMPLEPROFILE_CHAR4_LEN];
// user codes to do specific work like reading the temperature
// .....
SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, SIMPLEPROFILE_CHAR4_LEN,
newValue);
}
Inom den periodiska funktionen kör vi vårt mycket specifika jobb med att läsa temperatur, generera UART-förfrågningar osv. Sedan ringer vi SimpleProfile_SetParameter () API för att kommunicera informationen till serviceklienter via Bluetooth-anslutning. BLE-stacken tar hand om alla jobb på låg nivå från att behålla den trådlösa anslutningen till att sända meddelanden via Bluetooth-länken. Allt utvecklare behöver göra är att samla in applikationsspecifik data och uppdatera dem till motsvarande egenskaper i en servicetabell.
Slutligen, när en skrivbegäran utförs på en skrivtillåtna egenskaper, framkallas en återuppringningsfunktion.
static void SimpleBLEPeripheral_charValueChangeCB(uint8_t paramID)
{
SimpleBLEPeripheral_enqueueMsg(SBP_CHAR_CHANGE_EVT, paramID);
}
Återigen ger denna återuppringningsfunktion endast ett applikationsmeddelande för användaruppgiften, som kommer att hanteras senare i applikationskonteksten.
static void SimpleBLEPeripheral_processCharValueChangeEvt(uint8_t paramID)
{
uint8_t newValue[SIMPLEPROFILE_CHAR1_LEN];
switch(paramID)
{
case SIMPLEPROFILE_CHAR1:
SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR1, &newValue[0]);
ProcessUserCmd(newValue[0], NULL);
break;
case SIMPLEPROFILE_CHAR3:
break;
default:
// should not reach here!
break;
}
}
I exemplet ovan, när SIMPLEPROFILE_CHAR1 skrivs, hämtar användarkoden först det nya värdet genom att ringa SimpleProfile_GetParameter () och analyserar sedan data för användardefinierade kommandon.
Sammanfattningsvis visar simple_peripheral.c ett exempel på hur man skapar användaruppgifter för anpassade arbetsbelastningar. Ett grundläggande sätt att schemalägga applikationens arbetsbelastning är genom periodisk klockhändelse. Utvecklare behöver bara bearbeta information till / från egenskaperna i servicetabellen medan BLE-stacken tar hand om resten av att kommunicera informationen från servicetabellen till peer-enheter (eller vice versa) via Bluetooth-anslutning.
Ansluter verkliga sensorer
För att BLE-slavenheter kan göra något användbart arbete är GPIO: erna för den trådlösa MCU nästan alltid involverade. För att till exempel läsa temperatur från en extern sensor kan ADC-funktionaliteten hos GPIO-stift krävas. TIs CC2640 MCU har högst 31 GPIO, med tanke på olika förpackningstyper.
På hårdvarusidan ger CC2640 en rik uppsättning av perifera funktioner såsom ADC, UARTS, SPI, SSI, I2C etc. På mjukvarusidan försöker TIs BLE-stack att erbjuda ett enhetligt enhetsoberoende drivrutin för olika kringutrustning. Ett enhetligt drivrutingränssnitt kan förbättra risken för kodanvändbarhet, men å andra sidan ökar det också lutningskurvan. I den här noten använder vi SPI-kontrollen som ett exempel och visar hur man integrerar programvarudrivrutinen i användarapplikationer.
Grundläggande SPI-drivrutin
I TI: s BLE-stack består en kringutrustning ofta av tre delar: en enhetsoberoende specifikation av drivrutins-API: er; en enhetsspecifik implementering av drivrutins-API: erna och en kartläggning av hårdvaruressursen.
För SPI-controllern innebär dess drivrutinimplementering tre filer:
- <ti / drivers / SPI.h> - detta är den enhetsoberoende API-specifikationen
- <ti / drivers / spi / SPICC26XXDMA.h> - detta är CC2640-specifik API-implementering
- <ti / drivers / dma / UDMACC26XX.h> - detta är uDMA-drivrutinen som krävs av SPI-drivrutinen
(Observera: det bästa dokumentet för de perifera drivrutinerna för TI: s BLE-stack finns mestadels på deras sidhuvudfiler, t.ex. SPICC26XXDMA.h i detta fall)
För att börja använda SPI-regulatorn, låt oss först skapa en anpassad c-fil, nämligen sbp_spi.c, som innehåller de tre sidhuvudfilerna ovan. Det naturliga nästa steget är att skapa en instans av föraren och initiera den. Driverinstansen är inkapslad i datastrukturen - SPI_Handle. En annan datastruktur - SPI_Params används för att specificera nyckelparametrarna för SPI-styrenheten, såsom bithastighet, överföringsläge, etc.
#include <ti/drivers/SPI.h>
#include <ti/drivers/spi/SPICC26XXDMA.h>
#include <ti/drivers/dma/UDMACC26XX.h>
static void sbp_spiInit();
static SPI_Handle spiHandle;
static SPI_Params spiParams;
void sbp_spiInit(){
SPI_init();
SPI_Params_init(&spiParams);
spiParams.mode = SPI_MASTER;
spiParams.transferMode = SPI_MODE_CALLBACK;
spiParams.transferCallbackFxn = sbp_spiCallback;
spiParams.bitRate = 800000;
spiParams.frameFormat = SPI_POL0_PHA0;
spiHandle = SPI_open(CC2650DK_7ID_SPI0, &spiParams);
}
Ovanstående exempelkod exemplifierar hur man initialiserar SPI_Handle-instansen. API SPI_init () måste kallas först för att initiera interna datastrukturer. Funktionssamtalet SPI_Params_init (& spiParams) ställer in alla fält i SPI_Params-strukturen till standardvärden. Då kan utvecklare ändra nyckelparametrar för att passa deras specifika fall. Exempelvis sätter ovanstående kod SPI-styrenheten att arbeta i masterläge med en bithastighet på 800 kbps och använder en icke-blockerande metod för att behandla varje transaktion, så att när en transaktion är klar kommer återuppringningsfunktionen sbp_spiCallback att kallas.
Slutligen öppnar ett samtal till SPI_open () hårdvarans SPI-controller och returnerar ett handtag för senare SPI-transaktioner. SPI_open () tar två argument, det första är SPI-controllerens ID. CC2640 har två hårdvaru-SPI-kontroller on-chip, varför ID-argumenten är antingen 0 eller 1 enligt definitionen nedan. Det andra argumentet är de önskade parametrarna för SPI-styrenheten.
/*!
* @def CC2650DK_7ID_SPIName
* @brief Enum of SPI names on the CC2650 dev board
*/
typedef enum CC2650DK_7ID_SPIName {
CC2650DK_7ID_SPI0 = 0,
CC2650DK_7ID_SPI1,
CC2650DK_7ID_SPICOUNT
} CC2650DK_7ID_SPIName;
Efter en lyckad öppning av SPI_Handle kan utvecklare initiera SPI-transaktioner omedelbart. Varje SPI-transaktion beskrivs med hjälp av datastrukturen - SPI_Transaction.
/*!
* @brief
* A ::SPI_Transaction data structure is used with SPI_transfer(). It indicates
* how many ::SPI_FrameFormat frames are sent and received from the buffers
* pointed to txBuf and rxBuf.
* The arg variable is an user-definable argument which gets passed to the
* ::SPI_CallbackFxn when the SPI driver is in ::SPI_MODE_CALLBACK.
*/
typedef struct SPI_Transaction {
/* User input (write-only) fields */
size_t count; /*!< Number of frames for this transaction */
void *txBuf; /*!< void * to a buffer with data to be transmitted */
void *rxBuf; /*!< void * to a buffer to receive data */
void *arg; /*!< Argument to be passed to the callback function */
/* User output (read-only) fields */
SPI_Status status; /*!< Status code set by SPI_transfer */
/* Driver-use only fields */
} SPI_Transaction;
Till exempel, för att starta en skrivtransaktion på SPI-bussen, måste utvecklare förbereda en 'txBuf' fylld med data som ska överföras och ställa in variabeln 'count' på längden på databyte som ska skickas. Slutligen signalerar ett samtal till SPI_transfer (spiHandle, spiTrans) SPI-regulatorn att starta transaktionen.
static SPI_Transaction spiTrans;
bool sbp_spiTransfer(uint8_t len, uint8_t * txBuf, uint8_t rxBuf, uint8_t * args)
{
spiTrans.count = len;
spiTrans.txBuf = txBuf;
spiTrans.rxBuf = rxBuf;
spiTrans.arg = args;
return SPI_transfer(spiHandle, &spiTrans);
}
Eftersom SPI är ett duplexprotokoll som både sändning och mottagning sker samtidigt, när en skrivtransaktion är klar, är dess motsvarande svardata redan tillgängliga på 'rxBuf'.
Eftersom vi ställer överföringsläget till återuppringningsläge kommer den registrerade återuppringningsfunktionen att ringas närhelst en transaktion genomförs. Det är här vi hanterar svardata eller initierar nästa transaktion. (Obs! Kom alltid ihåg att inte göra mer än nödvändiga API-samtal i en återuppringningsfunktion).
void sbp_spiCallback(SPI_Handle handle, SPI_Transaction * transaction){
uint8_t * args = (uint8_t *)transaction->arg;
// may want to disable the interrupt first
key = Hwi_disable();
if(transaction->status == SPI_TRANSFER_COMPLETED){
// do something here for successful transaction...
}
Hwi_restore(key);
}
I / O-stiftkonfiguration
Hittills verkar det ganska enkelt att använda SPI-drivrutinen. Men vänta, hur kan man ansluta programvaru-API-samtal till fysiska SPI-signaler? Detta görs genom tre datastrukturer: SPICC26XXDMA_Object, SPICC26XXDMA_HWAttrsV1 och SPI_Config. De instanseras normalt på en annan plats som 'board.c'.
/* SPI objects */
SPICC26XXDMA_Object spiCC26XXDMAObjects[CC2650DK_7ID_SPICOUNT];
/* SPI configuration structure, describing which pins are to be used */
const SPICC26XXDMA_HWAttrsV1 spiCC26XXDMAHWAttrs[CC2650DK_7ID_SPICOUNT] = {
{
.baseAddr = SSI0_BASE,
.intNum = INT_SSI0_COMB,
.intPriority = ~0,
.swiPriority = 0,
.powerMngrId = PowerCC26XX_PERIPH_SSI0,
.defaultTxBufValue = 0,
.rxChannelBitMask = 1<<UDMA_CHAN_SSI0_RX,
.txChannelBitMask = 1<<UDMA_CHAN_SSI0_TX,
.mosiPin = ADC_MOSI_0,
.misoPin = ADC_MISO_0,
.clkPin = ADC_SCK_0,
.csnPin = ADC_CSN_0
},
{
.baseAddr = SSI1_BASE,
.intNum = INT_SSI1_COMB,
.intPriority = ~0,
.swiPriority = 0,
.powerMngrId = PowerCC26XX_PERIPH_SSI1,
.defaultTxBufValue = 0,
.rxChannelBitMask = 1<<UDMA_CHAN_SSI1_RX,
.txChannelBitMask = 1<<UDMA_CHAN_SSI1_TX,
.mosiPin = ADC_MOSI_1,
.misoPin = ADC_MISO_1,
.clkPin = ADC_SCK_1,
.csnPin = ADC_CSN_1
}
};
/* SPI configuration structure */
const SPI_Config SPI_config[] = {
{
.fxnTablePtr = &SPICC26XXDMA_fxnTable,
.object = &spiCC26XXDMAObjects[0],
.hwAttrs = &spiCC26XXDMAHWAttrs[0]
},
{
.fxnTablePtr = &SPICC26XXDMA_fxnTable,
.object = &spiCC26XXDMAObjects[1],
.hwAttrs = &spiCC26XXDMAHWAttrs[1]
},
{NULL, NULL, NULL}
};
SPI_Config-matrisen har en separat post för varje SPI-styrenhet för hårdvara. Varje post har tre fält: fxnTablePtr, object och hwAttrs. 'FxnTablePtr' är en poängtabell som pekar på de enhetsspecifika implementeringarna av drivrutins API.
"Objektet" håller reda på information som drivrutinstillstånd, överföringsläge, återuppringningsfunktion för föraren. Detta "objekt" upprätthålls automatiskt av föraren.
'HwAttrs' lagrar den verkliga kartläggningsdata för hårdvara resurs, t.ex. IO-stiften för SPI-signalerna, hårdvaruavbrottsnumret, basadressen för SPI-kontrollen etc. De flesta fält i 'hwAttrs' är fördefinierade och kan inte modifieras. Medan IO-stiften på gränssnittet kan fritt tilldelas baserade användarfall. Obs: CC26XX MCU: er avkopplar IO-stiften från specifik kringutrustning som någon av IO-stiften kan tilldelas till någon kringfunktion.
Naturligtvis måste de faktiska IO-stiften definieras först i "board.h".
#define ADC_CSN_1 IOID_1
#define ADC_SCK_1 IOID_2
#define ADC_MISO_1 IOID_3
#define ADC_MOSI_1 IOID_4
#define ADC_CSN_0 IOID_5
#define ADC_SCK_0 IOID_6
#define ADC_MISO_0 IOID_7
#define ADC_MOSI_0 IOID_8
Som ett resultat, efter konfigurering av hårdvaruressurskartläggning, kan utvecklare äntligen kommunicera med externa sensorchips via SPI-gränssnitt.