一、NRF51/NRF52系統低功耗表
(淺睡)
型号 | 電流 |
nrf51822/nrf51422 | 2.2uA-3uA |
nRF52832 | 1.9uA-2uA |
nRF52840 | 1.5uA-1.6uA |
其他 | 2uA左右 |
(深睡,喚醒就複位重新開機)
型号 | 電流 |
nrf51822/nrf51422 | <1uA |
nRF52832 | 300nA |
nRF52840 | 400nA |
其他 | <1uA |
二、實測産品沒有達到低功耗怎麼辦?
- 測量有100uA,這個時候需要檢查[調試器]是否斷開,需要掉電一次,再測量。
- 測量有幾百uA,這一定是外設沒有關閉。如下清單:
外設 | 電流 |
UART/UARTE | 55uA |
高頻時鐘電路 | 250uA |
easyDMA(52810強制打開) | 2mA |
Timer0/1/2/3/4 | 70uA(建議裸機用app_timer,os用軟定時器) |
FPU | >7mA |
GPIOTE | 1.3mA(IN_EVENT) / <5uA(Port event) |
SPI/TWI | mA級别 |
SAADC | mA級别 |
J-Link模式 | 100uA |
引用官方手冊:
System ON模式,就是CPU可以不工作而外設可以繼續工作的一種低功耗模式。
idle模式下,當CPU和所有外設都不工作時,系統電流也就有2uA左右。
(注:除了idle模式,nRF5晶片還支援一種更低功耗的低功耗模式:sleep模式(Nordic晶片手冊稱為System OFF模式),sleep模式下,CPU和所有外設都強制關閉,是以功耗非常低:隻有零點幾微安。由于sleep模式下,晶片無法發出廣播包或者與手機保持藍牙連接配接,是以sleep模式在BLE應用中運用得并不是很多)。
Idle模式可以被任何中斷喚醒(sleep模式隻能被IO口喚醒),是以idle模式在實際應用中使用得比較多。
在idle模式下,晶片仍然可以正常發出廣播或者與手機保持藍牙連接配接,是以大部分BLE應用都是工作在idle模式下,這樣既保持了BLE功能又可以實作低功耗。
三、如何進入低功耗?
1. 軟體部分優化
1.1 廣播優化
1.1.1 發射功率
設定發射功率具有 9 個發射等級。系統預設的發射功率是 0dbm,發射功率越大,發射距離就越遠,相應的電流消耗就越大。
在0dBm時,打開DC/DC, nRF52832發射電流5.3mA。将TX功率增加到+4dBm隻增加2.2mA。減少到- 40dbm隻節省2.6mA。
1.1.2 廣播間隔
就是廣播包發出的頻率,廣播間隔越長,功耗越低。
1.1.3 廣播負載
藍牙的廣播包普通包長度在 31 位元組,掃描響應包也有 31 位元組。如果藍牙 5.0 下的第二廣播包長度更長,越長的廣播負載,會造成越大的電流消耗。
1.2 連接配接狀态優化
1.2.1 連接配接間隔和從機潛伏周期
連接配接間隔是保證主從機維持連接配接,互相發空包的時間間隔。連接配接間隔可以在 GAP 初始化中進行設定。當設定的連接配接間隔越長,裝置的功耗越低。是以,可以在維持連接配接狀态下,保證資料正常通信的基礎下,設定盡可能長的連接配接間隔。
從機潛伏周期和連接配接間隔是同時進行配置的,從機潛伏周期允許藍牙裝置一定次數的周期不對藍牙主機資料進行回複。在這個周期次數範圍内,藍牙主機即使沒有收到藍牙從機裝置的回複确認資訊包,也會認為裝置正常。這種方式也可以降低藍牙裝置的功耗。
連接配接間隔是保證主從機維持連接配接,互相發空包的時間間隔。連接配接間隔可以在 GAP 初始化中進行設定。當設定的連接配接間隔越長,裝置的功耗越低。是以,可以在維持連接配接狀态下,保證資料正常通信的基礎下,設定盡可能長的連接配接間隔。
從機潛伏周期和連接配接間隔是同時進行配置的,從機潛伏周期允許藍牙裝置一定次數的周期不對藍牙主機資料進行回複。在這個周期次數範圍内,藍牙主機即使沒有收到藍牙從機裝置的回複确認資訊包,也會認為裝置正常。這種方式也可以降低藍牙裝置的功耗。
确認理論功耗值。BLE功耗跟廣播間隔或者連接配接間隔是成正比關系的,是以20ms連接配接間隔下的功耗幾乎是1s狀态下的50倍!,單純地問“1mA功耗高不高?”是沒有意義的,必須結合特定的應用場景才有意義。
不管是廣播還是連接配接,特定的使用場景會有一個理論功耗值,NRF功耗網址,以獲得你的使用場景下理論功耗多少,比如連接配接模式下,每1秒鐘發20個位元組的資料包,這種模式下理論功耗為:7.6uA
1.2.2 發射和接收的資料量
藍牙資料發送和接收的資料量大小,直覺的影響到了功耗。資料吞吐量越大,功耗越高。
1.3 系統及外設功耗優化
1.3.1 協定棧時鐘選擇
協定棧時鐘可以選擇外部低速時鐘和内部低速時鐘。選取外部低速時鐘具有更低的功耗,使能外部 32kHz 晶振,通常可以節省 1-2% 的電能。預設使用外部低速晶振。在 main.c 檔案,ble_stack_init() 函數中 nrf_sdh_enable_request() 找到
nrf_clock_lf_cfg_t const clock_lf_cfg =
{
.source = NRF_SDH_CLOCK_LF_SRC, // <0=> NRF_CLOCK_LF_SRC_RC // 内部時鐘源
// <1=> NRF_CLOCK_LF_SRC_XTAL // 外部晶振源
// <2=> NRF_CLOCK_LF_SRC_SYNTH // 合成時鐘源
.rc_ctiv = NRF_SDH_CLOCK_LF_RC_CTIV,
.rc_temp_ctiv = NRF_SDH_CLOCK_LF_RC_TEMP_CTIV,
.accuracy = NRF_SDH_CLOCK_LF_ACCURACY
};
sdk_config.h
#ifndef NRF_SDH_CLOCK_LF_SRC
#define NRF_SDH_CLOCK_LF_SRC 1
#endif
[nrf51][nrf52][S130][SDK12.3] 修改協定棧時鐘
1.3.2 關閉日志列印
裸機情況下,除非jlink調試器已連接配接,否則 RTT 不會使用電流。
os(freertos)情況:
- 單獨開一個線程刷log,功能大約在2.2uA和40uA跳動。
- 将log放在空閑線程中重新整理,功耗大約在11uA。
- 空閑線程每1000個滴答,重新整理一次,功耗大約在3uA。(失去實時性)
void sys_sleep(void)
{
#ifdef SOFTDEVICE_PRESENT
#if NRF51
sd_app_evt_wait();
#else
nrf_pwr_mgmt_run();
#endif
#else
// Wait for an event.
__WFE();
// Clear the internal event register.
__SEV();
__WFE();
#endif
}
/**
* @brief 線程空閑處理函數
*/
void idle_state_handle(void)
{
static uint16_t tick = 0;
tick ++;
if (get_ble_conn_state())
{
ble_data_send_dispose();
tick = 0;
}
if (get_ble_is_bus())
{
return;
}
if (tick > 1000)
{
// 重新整理RTT LOG緩存區
if (NRF_LOG_PROCESS() == false)
{
sys_sleep();
}
}
}
/**
* @brief [弱函數] 線程空閑
* @param *argument: NULL
*/
void vApplicationIdleHook(void)
{
idle_state_handle();
}
1.3.3 動态使用UART
首先 UART 子產品本身隻需要 55uA 的工作電流,同時會自動打開高頻時鐘電路,也需要消耗 250uA 左右電流。如果使能了 UARTE 的 EasyDMA,那麼 DMA 還需要消耗額外的 2mA 電流。這樣 UARTE 工作消耗的電流會很高。是以在 UART 沒有資料傳輸的時候建議将 UART 關掉,以節省功耗。
注:為了達到低功耗和實時性雙重目的,在設計 UART 通信的時候,我們經常會額外再加 2 個 GPIO 口用來通知對方 UART要傳送資料了。
關閉 UART 的 API 為:nrf_drv_uart_uninit() 或者 app_uart_close()。
如果隻是單向發送,無需接收,可以采用隊列的形式,當隊列空閑1000滴答,則關閉序列槽。隊列不為空,則初始化序列槽,開始發送。
[單片機架構] [queue] 實作一個簡易的消息隊列
void idle_state_handle(void)
{
static uint16_t tick = 0;
tick ++;
if (get_ble_conn_state())
{
ble_data_send_dispose();
tick = 0;
}
if (get_ble_is_bus())
{
return;
}
if (queue_de(&m_urat_send_q, g_uart_send_data))
{
tick = 0;
bsp_uart_init(BSP_UART_0);
bsp_uart_send_nbyte(BSP_UART_0, g_uart_send_data);
return;
}
if (tick > 1000)
{
bsp_uart_deinit(BSP_UART_0); // 内置标志位,不會每次都操作關閉
// 重新整理RTT LOG緩存區
if (NRF_LOG_PROCESS() == false)
{
sys_sleep();
}
}
}
1.3.4 動态使用SPI/TWI
在不使用的時候建議采用 uninit 函數進行關閉,這部分的外設也消耗電流。需要使用的時候進行 init 初始化開啟。
SPI 開啟和關閉:nrf_drv_spi_init 和 nrf_drv_spi_uninit
TWI 開啟和關閉:nrf_drv_twi_enable 和 nrf_drv_twi_disable
1.3.5 動态使用SAADC
在不使用的時候建議采用 uninit 函數進行關閉,需要使用的時候進行 init 初始化開啟。
ADC 開啟和關閉:nrfx_saadc_init 和 nrfx_saadc_uninit
如果你發現 uninit ADC 後,功耗還是很高,建議打開這個宏 NRFX_SAADC_CONFIG_LP_MODE,再試一下,功耗有可能就降下來了。
官方SAADC低功耗例子
1.3.5 建議使用GPIOE低精度模式
GPIOE 事件模式下具有兩鐘模式:高精度模式(hi_accuracy 為 true)和低精度模式(hi_accuracy 為 false)。高精度模式 IN event 中斷比低精度模式 Port event 中斷消耗更多的電流 10~20uA。如果隻是檢測 IO 口電平,建議使用低精度模式,也就是所有的輸入信号都使用一個中斷申請,庫函數調用配置:
GPIOTE_CONFIG_IN_SENSE_HITOLO(false);
1.3.5 Timer–>不建議使用硬體定時器
Timer0/1/2/3/4。Timer 的工作電流大概為 5~50uA 左右(nRF51功耗會更高),對低功耗應用來說,已經非常大了。如果你的定時精度要求不高,而且是毫秒的倍數,那麼強烈建議你使用 RTC 來實作定時功能。協定棧下為 app_timer 軟體定時器,app_timer 的功耗隻有 0.2uA 左右。
1.3.6 FPU
由于 nRF52x 系列處理器不同于 nRF51 系列,其核心為 ARM Cortex M4 處理器。ARM Cortex M4 處理器 帶 FPU 浮點運算單元。每當程式要執行浮點運算的時候,核心就會自動把 FPU 打開i。FPU 将消耗 7mA 以上的電流,此種情況下,進入 idle 模式之前必須手動關閉 FPU,手動關閉 FPU 代碼如下所示:
/* Clear FPSCR register and clear pending FPU interrupts. This code is base on
* nRF5x_release_notes.txt in documentation folder. It is necessary part of code when
* application using power saving mode and after handling FPU errors in polling mode.
*/
__set_FPSCR(__get_FPSCR() & ~(FPU_EXCEPTION_MASK));
(void) __get_FPSCR();
NVIC_ClearPendingIRQ(FPU_IRQn);
在新版本 SDK17 中已經預設添加了
x\nRF5_SDK_17.0.2_d674dde\components\libraries\pwr_mgmt\nrf_pwr_mgmt.c
函數:nrf_pwr_mgmt_run()
1.3.7 nrf_pwr_mgmt_run()函數解析
打開 nrf_pwr_mgmt_run() 函數,BLE 狀态下,如果 CPU 處于空閑狀态就會進入 sd_app_evt_wait() 函數,這個函數是進入低功耗的關鍵,是協定棧提供的一個等待事件函數。
void nrf_pwr_mgmt_run(void)
{
PWR_MGMT_FPU_SLEEP_PREPARE(); // 清除FDU異常,避免FDU中斷被挂起
PWR_MGMT_SLEEP_LOCK_ACQUIRE(); // 鎖定臨界區
PWR_MGMT_CPU_USAGE_MONITOR_SECTION_ENTER(); // 使用者監視段進入,監聽進入低功耗的時間
PWR_MGMT_DEBUG_PIN_SET(); // 置位仿真引腳
// Wait for an event.
#ifdef SOFTDEVICE_PRESENT // 帶協定棧狀态下
if (nrf_sdh_is_enabled()) // 如果協定棧被使能
{
ret_code_t ret_code = sd_app_evt_wait(); //調用協定棧等待函數
ASSERT((ret_code == NRF_SUCCESS) || (ret_code == NRF_ERROR_SOFTDEVICE_NOT_ENABLED));
UNUSED_VARIABLE(ret_code);
}
else
#endif // SOFTDEVICE_PRESENT // 否則,不帶協定棧狀态
{
// Wait for an event.
__WFE();
// Clear the internal event register.
__SEV();
__WFE();
}
PWR_MGMT_DEBUG_PIN_CLEAR(); // 清除仿真引腳
PWR_MGMT_CPU_USAGE_MONITOR_SECTION_EXIT(); // 使用者監視段退出
PWR_MGMT_SLEEP_LOCK_RELEASE(); // 鎖定臨界區釋放
}
1.4 軟體進入低功耗需要調用的函數
static void idle_state_handle(void)
{
if(NRF_LOG_PROCESS() == false) // 如果調試緩沖區沒有更多日志
{
nrf_pwr_mgmt_run();
}
}
int main(void)
{
···
···
for(;;)
{
idle_state_handle();
}
}
2. 硬體部分優化
不同的内部穩壓器選擇,會造成不同的電路消耗。可以通過選擇不同的硬體電路配置,來選取下面兩種内部穩壓器:
- 内部 LDO 穩壓器
- 内部 DC/DC 穩壓器
LDO 是系統預設的穩壓器,而 DC/DC 穩壓器可用作 LDO 穩壓器的替代産品。與使用 LDO 穩壓器相比,使用 DC/DC 穩壓器具有更低的電流消耗,但 DC/DC 穩壓器需要連接配接外部 LC 濾波器:
其中關于 DC/DC 穩壓器所連接配接的 外部 LC 濾波器電路上的電感和電容參數,請參看晶片手冊 53 節 Reference circuitry 所提供的參考電路。
由于預設選擇的是内部 LDO 穩壓器,是以如果需要切換到使用内部 DC/DC 穩壓器,還需要在軟體上進行配置。
首先需要在主函數 main.c 中,初始化 softDevice 協定棧前,執行 NRF_POWER->DCDCEN=1。
或者在初始化softDevice 協定棧後,執行 sd_power_dcdc_mode_set(1)。
sdk_config.h 配置檔案中勾選 NRFX_POWER_ENABLED 使能選項,同時把選項下的 DC/DC 使能選項 NRFX_POWER_CONFIG_DEFAULT_DCDCEN 進行勾選。
在選取電源電壓為 3.0 V ,廣播間隔為 500ms,發射功率為 0dbm 的情況下,選擇 DC/DC 穩壓方式的總平均功耗電流為 20uA,而選擇 LDO 穩壓方式的總平均電流在 32uA 左右。是以,選擇 DC/DC 穩壓方式可以大幅度的降低功耗。
電池壽命預估
已知裝置的平均電流消耗和電池的容量,計算電池壽命。以下是不同能量容量電池的電池壽命計算執行個體:
例1:
裝置的平均電流消耗 :20uA
電池容量:220mAh (标準CR2032紐扣電池 )
電池壽命:0.22Ah/0.00002A=11000hours=458days