持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
本文介绍在 BLE 协议栈下如何使用看门狗wdt和读取电压值(使用saadc接口)。
概述
硬件产品中看门狗有非常重要的作用,在程序跑飞时能够复位,防止程序进入死循环状态。
对于 BLE 产品,电池的检测也十分重要,SDK 就提供了电池服务。
文中代码仅对外设的底层做封装,不涉及到 BLE 服务。由于两者的接口较简单,因此一并介绍。
操作看门狗和读取电压值,SDK均有相关代码,本文即使用这些代码进行二次封装。总体看需要做的事:
- 在sdk_config.h头文件开启相关的宏。
- 添加相关的文件。
- 封装接口(可选)。
看门狗WDT
对外提供如下:
void dev_wdt_init(void);
void dev_wdt_feed(void);
接口较简单,初始化后,在较耗时的地方调用喂狗接口dev_wdt_feed
即可,也可以单独开一定时器,定时喂狗。
实现如下:
#if NRFX_WDT_ENABLED == 1
#include "nrf_drv_wdt.h"
static nrf_drv_wdt_channel_id m_channel_id;
void wdt_event_handler(void)
{
bsp_board_leds_off();
//NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock - after that, reset occurs
}
void dev_wdt_init()
{
uint32_t err_code = NRF_SUCCESS;
//Configure WDT.
nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
config.reload_value = 3000; // 超时时间 默认2s
err_code = nrf_drv_wdt_init(&config, NULL); // wdt_event_handler
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
APP_ERROR_CHECK(err_code);
nrf_drv_wdt_enable();
}
void dev_wdt_feed()
{
nrf_drv_wdt_channel_feed(m_channel_id);
}
#endif
config.reload_value
用于设置超时时间,单位为 ms,代码中设置为 3 秒。默认为 2 秒,由如下宏确定:
// <o> WDT_CONFIG_RELOAD_VALUE - Reload value <15-4294967295>
#ifndef WDT_CONFIG_RELOAD_VALUE
#define WDT_CONFIG_RELOAD_VALUE 2000
#endif
读取电压
对外提供接口如下:
void dev_voltage_init(void);
void dev_voltage_get(uint16_t * p_vbatt);
接口较简单,初始化后,直接调用dev_voltage_get
即可获取电压值。
代码如下:
#if NRFX_SAADC_ENABLED == 1
#include "nrf_drv_saadc.h"
#include "sdk_macros.h"
#define ADC_REF_VOLTAGE_IN_MILLIVOLTS 600 //!< Reference voltage (in milli volts) used by ADC while doing conversion.
#define DIODE_FWD_VOLT_DROP_MILLIVOLTS 270 //!< Typical forward voltage drop of the diode (Part no: SD103ATW-7-F) that is connected in series with the voltage supply. This is the voltage drop when the forward current is 1mA. Source: Data sheet of 'SURFACE MOUNT SCHOTTKY BARRIER DIODE ARRAY' available at www.diodes.com.
#define ADC_RES_10BIT 1024 //!< Maximum digital value for 10-bit ADC conversion.
#define ADC_PRE_SCALING_COMPENSATION 6 //!< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.
#define ADC_RESULT_IN_MILLI_VOLTS(ADC_VALUE) \
((((ADC_VALUE) *ADC_REF_VOLTAGE_IN_MILLIVOLTS) / ADC_RES_10BIT) * ADC_PRE_SCALING_COMPENSATION)
static nrf_saadc_value_t adc_buf; //!< Buffer used for storing ADC value.
static uint16_t m_batt_lvl_in_milli_volts; //!< Current battery level.
/**@brief Function handling events from 'nrf_drv_saadc.c'.
*
* @param[in] p_evt SAADC event.
*/
static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_evt)
{
if (p_evt->type == NRF_DRV_SAADC_EVT_DONE)
{
nrf_saadc_value_t adc_result;
adc_result = p_evt->data.done.p_buffer[0];
m_batt_lvl_in_milli_volts =
ADC_RESULT_IN_MILLI_VOLTS(adc_result) + DIODE_FWD_VOLT_DROP_MILLIVOLTS;
// NRF_LOG_INFO("--- vol: %d", m_batt_lvl_in_milli_volts);
}
}
void dev_voltage_init(void)
{
ret_code_t err_code = nrf_drv_saadc_init(NULL, saadc_event_handler);
APP_ERROR_CHECK(err_code);
nrf_saadc_channel_config_t config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void dev_voltage_get(uint16_t * p_vbatt)
{
VERIFY_PARAM_NOT_NULL_VOID(p_vbatt);
*p_vbatt = m_batt_lvl_in_milli_volts;
if (!nrf_drv_saadc_is_busy())
{
ret_code_t err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
}
#endif
dev_voltage_get
获取的值是电压数值,但实际中,需要提供电池百分比,SDK 中实现了battery_level_in_percent
,用于将电压值转换成百分比。实现代码如下:
/** @brief Function for converting the input voltage (in milli volts) into percentage of 3.0 Volts.
*
* @details The calculation is based on a linearized version of the battery's discharge
* curve. 3.0V returns 100% battery level. The limit for power failure is 2.1V and
* is considered to be the lower boundary.
*
* The discharge curve for CR2032 is non-linear. In this model it is split into
* 4 linear sections:
* - Section 1: 3.0V - 2.9V = 100% - 42% (58% drop on 100 mV)
* - Section 2: 2.9V - 2.74V = 42% - 18% (24% drop on 160 mV)
* - Section 3: 2.74V - 2.44V = 18% - 6% (12% drop on 300 mV)
* - Section 4: 2.44V - 2.1V = 6% - 0% (6% drop on 340 mV)
*
* These numbers are by no means accurate. Temperature and
* load in the actual application is not accounted for!
*
* @param[in] mvolts The voltage in mV
*
* @return Battery level in percent.
*/
static __INLINE uint8_t battery_level_in_percent(const uint16_t mvolts)
{
uint8_t battery_level;
if (mvolts >= 3000)
{
battery_level = 100;
}
else if (mvolts > 2900)
{
battery_level = 100 - ((3000 - mvolts) * 58) / 100;
}
else if (mvolts > 2740)
{
battery_level = 42 - ((2900 - mvolts) * 24) / 160;
}
else if (mvolts > 2440)
{
battery_level = 18 - ((2740 - mvolts) * 12) / 300;
}
else if (mvolts > 2100)
{
battery_level = 6 - ((2440 - mvolts) * 6) / 340;
}
else
{
battery_level = 0;
}
return battery_level;
}
根据注释,是针对CR2032
纽扣电池的,电压和百分比的转换并非线性关系。
小结
nRF52 的 SDK 例程中,很多函数调用都会使用断言判断返回值,当出错时,会调用到 assert_nrf_callback
函数。笔者调试过程中,有时会出现莫名的错误,也没有过多的日志输出。此时使能看门狗板子会复位,但往往还是继续出现相同的错误。其实这种还好解决,毕竟是在调试中发现的,而在实际应用时,可能没有串口或 JTAG ,因此比较难分析,因此需要充分的测试。无论如何,看门狗还是建议使用上的。
使用串口线或 JTAG 线向板子供电,得到的电池百分比一直是100%
。为了测试实际的百分比,笔者在某宝上买了块CR2032
电池,放到板子上,但发现驱动不了板子,因此无法测试,想到只花了几毛钱,也就作罢了。