基於cc2540的電池電量服務。
基於藍芽協議棧的電池電量服務
摘要:在理解協議棧和ADC的基礎上,就可以講講藍芽低功耗裝置如何新增電池服務(這裡的電池指的是:鈕釦電池CR2032, 3V ),讓藍芽主機可以知道藍芽裝置的電池電量。可以設定一個電池低電量的臨界值,當電池的電量低於這個臨界值時,就提醒使用者更換電池。為了實現這個功能,需要用到HAL層的halAdc.c與halAdc.h兩個檔案來配置處理器的ADC模組,然後還需要在此基礎上用到電池電量的配置檔案:Battservice.c與Battservice.h。通過CC2540 的ADC模組來測量外接電池的電量:VADD5/3作為ADC的取樣通道,參考電壓選擇晶片內部1.25V的參考電壓,以10位精度取樣,有效位為9位,最大的AD值為511,也就是說1.25V對應的AD值為511。對於鈕釦電池3V來說,電池的電壓在2V~3V之間可以正常給晶片供電,電池電壓與電池電量之間的關係為:
2V <==> 0%
3V <==> 100%
因為選擇AVDD5/3通道,正常使用的電池電壓輸入到ADC上實際上為2/3V~3/3V,即0.66V~1V,計算得到AD值為273~409(0.66/1.25*511 ~ 1/1.25*511)。最後可以得到取樣到的AD值與電量的關係:
273 <==> 0%
409 <==> 100%
計算公式:Battery Level = (adc - 273) / (409 - 273) * 100
根據上面的公式,就可以計算得到藍芽裝置的電池電量。
檢測到電池電量後,需要通過藍芽上傳。這時則需要向GATT增加電池服務,藍芽裝置會發送電池電量的通告給主機,這樣的話藍芽主機端(如手機等)看到藍芽裝置的電池電量。電池電量的通告只有在下面2種情況下才會發送:1、第一次連線時;2、電池電量較少時。可以設定一個電池電量的臨界提醒值,當電池電量達到這個臨界值時,就會給藍芽主機發送通告,提醒使用者更換藍芽裝置的電池。
下面就正式開始講講藍芽的電池服務。
1、跟ADC相關的幾個變數
(1)電池電壓3V對應的AD值BATT_ADC_LEVEL_3V=409
define BATT_ADC_LEVEL_3V 409
(2)電池電壓2V對應的AD值BATT_ADC_LEVEL_2V=273
define BATT_ADC_LEVEL_2V 273
(c)最低電量所對應的AD值battMinLevel=273
static uint16 battMinLevel = BATT_ADC_LEVEL_2V;
(d)最高電量所對應的AD值battMaxLevel=409
static uint16 battMaxLevel = BATT_ADC_LEVEL_3V;
(e)電池電量的臨界值(提醒換電池的電量)battCriticalLevel
static uint8 battCriticalLevel;
(f)電池電壓的AD值取樣通道設定成VDD/3通道battServiceAdcCh
static uint8 battServiceAdcCh = HAL_ADC_CHANNEL_VDD;
2、跟ADC取樣相關的幾個回撥函式
(1)AD測量設定回撥函式battServiceSetupCB,在AD取樣之前呼叫。
typedef void (*battServiceSetupCB_t)(void);
static battServiceSetupCB_t battServiceSetupCB = NULL;
(2)AD測量結束回撥函式battServiceTeardownCB,在AD測量結束時呼叫。
typedef void (*battServiceTeardownCB_t)(void);
static battServiceTeardownCB_t battServiceTeardownCB = NULL;
(3)AD測量計算回撥函式battServiceCalcCB,在AD測量後呼叫這個函式用來計算電池電量。
typedef uint8 (*battServiceCalcCB_t)(uint16 adcVal);
static battServiceCalcCB_t battServiceCalcCB = NULL;
3、使用者自定義的電池服務應用回撥函式battServiceCB,在操作電池服務寫屬性時呼叫這個函式。
typedef void (*battServiceCB_t)(uint8 event);
static battServiceCB_t battServiceCB;
4、電池服務的UUID定義
(1)電池服務的UUID
define BATT_SERVICE_UUID 0x180F
CONST uint8 battServUUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(BATT_SERVICE_UUID), HI_UINT16(BATT_SERVICE_UUID)
};
(2)電池電量的UUID
define BATT_LEVEL_UUID 0x2A19
CONST uint8 battLevelUUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(BATT_LEVEL_UUID), HI_UINT16(BATT_LEVEL_UUID)
};
5、電池電量服務的配置檔案(Profiles)相關的變數
(1)電池服務屬性的GATT屬性型別格式
static CONST gattAttrType_t battService = { ATT_BT_UUID_SIZE, battServUUID };
(2)電池電量特性許可權battLevelProps 設定為可讀、可通告
static uint8 battLevelProps = GATT_PROP_READ | GATT_PROP_NOTIFY;
(3)電池電量值預設設定成battLevel=100
static uint8 battLevel = 100;
(4)儲存電池客戶端連線的屬性配置陣列battLevelClientCharCfg[]
static gattCharCfg_t battLevelClientCharCfg[GATT_MAX_NUM_CONN];
(5)HID報告參考特性描述
static uint8 hidReportRefBattLevel[HID_REPORT_REF_LEN] ={HID_RPT_ID_BATT_LEVEL_IN, HID_REPORT_TYPE_INPUT };
6、電池服務中的幾個變數
(1)電池電量值變數地址BATT_PARAM_LEVEL=0
define BATT_PARAM_LEVEL 0
(2)電池電量臨界值變數地址BATT_PARAM_CRITICAL_LEVEL=1
define BATT_PARAM_CRITICAL_LEVEL 1
(3)電池服務控制代碼變數地址BATT_PARAM_SERVICE_HANDLE = 2
define BATT_PARAM_SERVICE_HANDLE 2
(4)電池電量報告地址BATT_PARAM_BATT_LEVEL_IN_REPORT=3
define BATT_PARAM_BATT_LEVEL_IN_REPORT 3
7、電池電量服務在GATT層上的屬性配置陣列battAttrTbl[]
static gattAttribute_t battAttrTbl[] ={};
這個屬性配置陣列共有5個元素。
(1)電池服務宣告
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID },/* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 )&battService / pValue */
},
(2)電池電量宣告
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&battLevelProps
},
(3)電池電量值
{
{ ATT_BT_UUID_SIZE, battLevelUUID },
GATT_PERMIT_READ,
0,
&battLevel
},
(4)電池電量客戶端特徵配置
{
{ ATT_BT_UUID_SIZE, clientCharCfgUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
(uint8 *) &battLevelClientCharCfg
},
(5)HID報告參考特徵描述
{
ATT_BT_UUID_SIZE, reportRefUUID },
GATT_PERMIT_READ,
0,
hidReportRefBattLevel
}
8、電池服務屬性操作回撥函式,有讀、寫、授權三個回撥函式,這是隻使用讀、寫屬性兩種。
CONST gattServiceCBs_t battCBs =
{
battReadAttrCB, // Read callback function pointer
battWriteAttrCB, // Write callback function pointer
NULL // Authorization callback function pointer
};
Batt_AddService() 向藍芽協議新增電池服務
1、初始化客戶端特徵配置屬性
GATTServApp_InitCharCfg( INVALID_CONNHANDLE, battLevelClientCharCfg );
2、向GATT註冊電池服務的屬性列表和屬性操作回撥函式
status = GATTServApp_RegisterService( battAttrTbl,GATT_NUM_ATTRS( battAttrTbl ),&battCBs );
Batt_SetParameter() 設定電池服務的變數
引數:
param-變數地址
len-資料的長度
*value-指向要設定的資料
在之前定義過4個變數地址BATT_PARAM_LEVEL、BATT_PARAM_CRITICAL_LEVEL、BATT_PARAM_SERVICE_HANDLE、BATT_PARAM_BATT_LEVEL_IN_REPORT,其中只有BATT_PARAM_CRITICAL_LEVEL這個變數的值是可以設定的。
1、設定電池電量臨界值
case BATT_PARAM_CRITICAL_LEVEL:
battCriticalLevel = ((uint8)value);
break;
2、如果當前電池的電量小於這個臨界值,則發出一則通告
if ( battLevel < battCriticalLevel )
{
battNotifyLevel();
}
Batt_GetParameter() 獲取電池服務的變數
引數:
param-引數的地址
*value-用於儲存獲取到變數的值
在之前定義過4個變數地址BATT_PARAM_LEVEL、BATT_PARAM_CRITICAL_LEVEL、BATT_PARAM_SERVICE_HANDLE、BATT_PARAM_BATT_LEVEL_IN_REPORT。這4個變數都可以獲取其值。
1、獲取當前電量變數
case BATT_PARAM_LEVEL:
((uint8)value) = battLevel;
break;
2、獲取電池電量臨界值變數
case BATT_PARAM_CRITICAL_LEVEL:
((uint8)value) = battCriticalLevel;
break;
3、獲取電池服務控制代碼變數
case BATT_PARAM_SERVICE_HANDLE:
((uint16)value) = GATT_SERVICE_HANDLE( battAttrTbl );
break;
4、獲取電池電量服務報告
case BATT_PARAM_BATT_LEVEL_IN_REPORT:
{
hidRptMap_t *pRpt = (hidRptMap_t *)value;
pRpt->id = hidReportRefBattLevel[0];
pRpt->type = hidReportRefBattLevel[1];
pRpt->handle = battAttrTbl[BATT_LEVEL_VALUE_IDX].handle;
pRpt->cccdHandle=battAttrTbl[BATT_LEVEL_VALUE_CCCD_IDX].handle;
pRpt->mode = HID_PROTOCOL_MODE_REPORT;
}
break;
5、其他非法引數
default:
ret = INVALIDPARAMETER;
break;
battReadAttrCB() 電池服務讀屬性操作的回撥函式
引數:
connHandle-客戶端連線的控制代碼
*pAttr-要操作的屬性
*pValue-儲存屬性值元素的值
pLen-屬性的值元素長度
offset-偏移
maxLen-讀取資料值的最大長度
電池服務屬性陣列battAttrTbl[]共有5種屬性,其中電池電量屬性和HID報告特徵參考屬性可以讀操作,他們對應的UUID分別為BATT_LEVEL_UUID、GATT_REPORT_REF_UUID。
1、獲取屬性對應的UUID
uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1] );
2、如果讀取電池電量值
(1)讀取電量的值
level = battMeasure();
(2)如果電量減少了,則更新當前的電量
battLevel = level;
(3)儲存讀出的資料與長度
*pLen = 1;
pValue[0] = battLevel;
3、如果讀取HID報告特徵參考屬性
*pLen = HID_REPORT_REF_LEN;
osal_memcpy( pValue, pAttr->pValue, HID_REPORT_REF_LEN );
battWriteAttrCB() 電池服務寫屬性回撥函式
引數:
connHandle-客戶端連線的控制代碼
*pAttr-要操作的屬性
*pValue-要寫入的屬性值元素的值
pLen-屬性的值元素長度
offset-偏移
電池服務屬性陣列battAttrTbl[]共有5種屬性,其中只有電池電量客戶端特徵配置屬性可以寫操作,對應的UUID為GATT_CLIENT_CHAR_CFG_UUID。
1、獲取GATT屬性對應的UUID
uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
2、如歸哦是電池電量客戶端特徵配置屬性所對應的UUID。則處理這個寫請求
status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,offset, GATT_CLIENT_CFG_NOTIFY );
3、如果定義了電池服務的應用回撥函式battServiceCB,則執行這個回撥函式
if ( battServiceCB )
{
(*battServiceCB)( (charCfg == GATT_CFG_NO_OPERATION) ?
BATT_LEVEL_NOTI_DISABLED :
BATT_LEVEL_NOTI_ENABLED);
}
Batt_HandleConnStatusCB() 電池服務連線狀態改變處理函式
引數:
connHandle-連線的控制代碼
changeType-改變的狀態
當連線斷開時,則復位客戶端特徵配置。
if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED ) ||
( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) &&
( !linkDB_Up( connHandle ) ) ) )
{
GATTServApp_InitCharCfg( connHandle, battLevelClientCharCfg );
}
Batt_Register() 註冊電池服務的應用回撥函式
引數:
pfnServiceCB-註冊的函式指標
battServiceCB = pfnServiceCB;
Batt_Setup() 設定ADC模組
引數:
adc_ch-AD的取樣通道
minVal-電池的最低電量AD值
maxVal-電池最高電量的AD值
sCB-ADC取樣前的配置回撥函式
tCB-ADC取樣結束時的回撥函式
cCB-ADC取樣結束後的電量值計算的回撥函式
1、設定ADC的配置
battServiceAdcCh = adc_ch;
battMinLevel = minVal;
battMaxLevel = maxVal;
2、註冊ADC取樣前後的需要的回撥函式
battServiceSetupCB = sCB;/* 測量設定回撥函式 */
battServiceTeardownCB = tCB;/* 測量結束回撥函式 */
battServiceCalcCB = cCB;/* 測量後的計算回撥函式 */
battMeasure() 測量電池電量
1、如果註冊了測量設定回撥函式,則在AD取樣前執行該函式
if (battServiceSetupCB != NULL)
{
battServiceSetupCB();
}
2、設定ADC的參考電壓,並以10位解析度讀取VDD/3通道的AD值
HalAdcSetReference( HAL_ADC_REF_125V );
adc = HalAdcRead( battServiceAdcCh, HAL_ADC_RESOLUTION_10 );
3、AD值讀取完後,如果註冊了測量結束回電函式,則執行該函式。
if (battServiceTeardownCB != NULL)
{
battServiceTeardownCB();
}
4、檢測讀取到的AD值是否有效,無效則設定上下限值
if (adc >= battMaxLevel)
{
percent = 100;
}
else if (adc <= battMinLevel)
{
percent = 0;
}
5、開始計算電池電量
(1)如果註冊了計算回撥函式,則執行這個回撥函式來計算電池電量
if (battServiceCalcCB != NULL)
{
percent = battServiceCalcCB(adc);
}
(2)如果沒有註冊計算回撥函式,則使用預設公式極端電池電量
{
uint16 range = battMaxLevel - battMinLevel + 1;
range >>= 2;
percent = (uint8) ((((adc - battMinLevel) * 25) + (range - 1)) / range);
}
這裡計算公式跟文章最開始將的公式(adc - 273) / (409 - 273) * 100有一點出入。但實際上差別不大。程式碼中只不過把計算好的電量值取頂操作,例如電量計算得為21.1%,則取頂為22%。
Batt_MeasLevel() 測量電池電量
1、測量電池電量
level = battMeasure();
2、如果電量下降了,則更新電量值,併發送一個通告。
if (level < battLevel)
{
battLevel = level;
battNotifyLevel();
}
battNotifyLevel() 註冊通告發送回調函式
linkDB_PerformFunc( battNotifyCB );
battNotifyCB() 傳送通告
引數:
pLinkItem-條目
1、如果連線狀態為連線著,則讀取客戶端的特徵屬性
value = GATTServApp_ReadCharCfg( pLinkItem->connectionHandle, battLevelClientCharCfg );
2、如果讀取到的值為客戶端的屬性配置為通告,則傳送通告
if ( value & GATT_CLIENT_CFG_NOTIFY )
{
attHandleValueNoti_t noti;
noti.handle = battAttrTbl[BATT_LEVEL_VALUE_IDX].handle;
noti.len = 1;
noti.value[0] = battLevel;
GATT_Notification( pLinkItem->connectionHandle, ¬i, FALSE );
}
這裡涉及到很多回調函式的,下面講講它們的關係如下所示:
BLE工程——電池電量服務 - ziye334 - ziye334的部落格