我本仁慈,奈何苍天不许
分类: 嵌入式
2016-07-21 14:12:48
原文地址:BLE-NRF51822教程14-adc和电池电量服务 作者:ifndef
51822ADC有8个通道。AIN0-AIN7.这几个通道对应的引脚是固定的。可以在 51822的产品说明书中找到。
通过CONFIG寄存器 选择哪一个通道作为ADC的输入。
如上图,ADC不仅可以测量8个通道的输入进过比例转换后的模拟电压(比如选择1/3,则实际adc转换的数据为外部输入电压的1/3),还可以测量 VDD/3 和VDD*2/3的电压值,也是通过CONFIG寄存器来设置。
CONFIG中的RES位用来控制adc的精度。
CONFIG中的REFSEL用来选择参考电压,同样参考电压也可以经过一定比列缩放。由上所述,设置10bit精度adc, 测量外部输入电压的1/3,参考电压使用1.2 并且测量通道用AIN2则设置如下。
NRF_ADC->CONFIG = (2 << 0) //adc转换精度为10位
| (2 << 2) //adc测量值为输入的1/3
| (0 << 5) //选择内部1.2为参考电压
| (4 << 8);//选择AIN2为adc的输入
51822的ADC的配置组合有很灵活。但是51822 ADC有两条限制规则
1,引脚上的输入电压经过缩放后不能大于2.4V,
2, GPIO上的输入电压不能超过VDD+0.3V.
所以具体的各种配置下所允许测量的 输入电压有一定限制,需要看手册确定。
ADC的工作方式可以是轮训或者中断方式。
51822 ADC本身只支持一次转换,所以每次转换完成后需要再测量时,都要再次设置启动转换。如果需要保持周期测量,则需要用一个定时器来周期启动adc。
INTEN:改寄存器用来设置 转换完成后是否触发中断。
BUSY: 该寄存机用来查看ADC转换是否已经完成。通常在轮训方式中使用。
ENABLE: 使能adc
RESULT:存储 adc 转换后的结果。
中断方式就是使能 INTEN寄存器中的转换完成触发中断。并开启MCU的adc中断,每次启动后等到中断到来,在中断中读取RESULT寄存器的值就好了。
这里为了方便,下面的例子直接用轮训方式,启动转换后原地轮训等待转换完成(通过BUSY寄存器判断)。
PS:针对电池电量检测这种应用,原地轮训等待转换完成基本不会影响 整体效率,因为电量检测这种应用本身测量周期比较长,通常都是 以 秒为单位, 同时51822的adc转换也比较快,10位精度的转换68us就可以完成 8位精度转换20us就可以完成。
轮训方式的简单例子
Adc设置初始化函数如下:
void nrf_adc_init(void){
NRF_ADC->CONFIG = (2 << 0) //adc转换精度为10位
| (2 << 2) //adc测量值为输入的1/3
| (0 << 5) //选择内部1.2为参考电压
| (4 << 8);//选择AIN2为adc的输入
//使能adc END事件触发中断
// NRF_ADC->INTENSET = 0x01;
NRF_ADC->ENABLE = 0x01;
}
启动adc:
void nrf_adc_start(){
NRF_ADC->TASKS_START = 0X01;
}
在ble_uart的例子中加一个简单的轮训测量。直接在广播启动代码前面加一个死循环测量。
XXX:\Keil_v5\ARM\Pack\NordicSemiconductor\nRF_Examples\9.0.0\ble_peripheral\ble_app_uart
在这个例子里加就是为了利用一下工程中的printf函数。然后死循环轮训测量adc。同时电池服务我们在这个工程里面添加实现
下面实现一个周期发送电量值的 电池服务:
因为只是演示如何实现这个功能,所以例子编写的比较粗糙,实际中编写的时候需要根据自己的需求完善。
首先在 ble_nus结构体中添加 记录电池服务特性 中相关的特征值和描述符句柄 的域。
添加 电量特征值uuid的定义,这里只是随便定义了一个。并没有使用标准的电池服务的uuid。
实现 battery 特征值的添加函数,这里电池服务实现是定时周期使用notify方式将电量发送给手机,所以通信方式是跟nus服务中的rx特征值一样的,所以直接拷贝ble_nus.c中的rx_char_add 函数做一下简单修改就可以了
static uint32_t battery_char_add(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init)
{
ble_gatts_char_md_t char_md;
ble_gatts_attr_md_t cccd_md;
ble_gatts_attr_t attr_char_value;
ble_uuid_t ble_uuid;
ble_gatts_attr_md_t attr_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.notify = 1;
char_md.p_char_user_desc = NULL;
char_md.p_char_pf = NULL;
char_md.p_user_desc_md = NULL;
char_md.p_cccd_md = &cccd_md;
char_md.p_sccd_md = NULL;
ble_uuid.type = p_nus->uuid_type;
//替换uuid
ble_uuid.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;
memset(&attr_md, 0, sizeof(attr_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
attr_md.vloc = BLE_GATTS_VLOC_STACK;
attr_md.rd_auth = 0;
attr_md.wr_auth = 0;
attr_md.vlen = 1;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &ble_uuid;
attr_char_value.p_attr_md = &attr_md;
attr_char_value.init_len = sizeof(uint8_t);
attr_char_value.init_offs = 0;
attr_char_value.max_len = BLE_NUS_MAX_RX_CHAR_LEN;
//替换存储 特性的结构体指针
return sd_ble_gatts_characteristic_add(p_nus->service_handle,
&char_md,
&attr_char_value,
&p_nus->rx_handles);
}
然后修改ble_nus.c中的
uint32_t ble_nus_init(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init) 函数 添加 电池特特性创建的 函数调用
uint32_t ble_nus_init(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init)
{
…………………………
…………………………
…………………………
// Add the TX Characteristic.
err_code = tx_char_add(p_nus, p_nus_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
//添加电池特性创建服务的调用
err_code = battery_char_add(p_nus, p_nus_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
return NRF_SUCCESS;
}
然后实现 电池电量的发送函数。直接拷贝ble_nus.c中的ble_nus_string_send函数并作如下修改。
uint32_t battery_send(ble_nus_t * p_nus, uint8_t *battery_level,uint16_t length)
{
ble_gatts_hvx_params_t hvx_params;
if (p_nus == NULL)
{
return NRF_ERROR_NULL;
}
//去掉是否使能notify的标记判断,因为这里用的是电池通道,
//本来也应该想rx一样做一个是否notify过的标志,例子里为了方便就
//没做
if (p_nus->conn_handle == BLE_CONN_HANDLE_INVALID)
{
return NRF_ERROR_INVALID_STATE;
}
if (length > BLE_NUS_MAX_DATA_LEN)
{
return NRF_ERROR_INVALID_PARAM;
}
memset(&hvx_params, 0, sizeof(hvx_params));
hvx_params.handle = p_nus->battery_handles.value_handle;
hvx_params.p_data = battery_level;
hvx_params.p_len = &length;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);
}
之后再创建一个定时器来定时发送电量值
app_timer_create(&battery_timer,APP_TIMER_MODE_REPEATED, battery_timer_handler);
注意 main.c中的宏要响应增加1 #define APP_TIMER_MAX_TIMERS (3 + BSP_APP_TIMERS_NUMBER)
定时到期处理函数
void battery_timer_handler(void *p_context){
static uint8_t battery_level[4];
uint32_t temp;
nrf_adc_start();
while(NRF_ADC->BUSY&1);
float value = (NRF_ADC->RESULT)*1.0;
value = value*1.2/1024.0;
value *= 3;
//将电压值扩大100倍,好发送整数出去。
temp = value * 100;
battery_level[0] = (temp>>24)&0xff;
battery_level[1] = (temp>>16)&0xff;
battery_level[2] = (temp>>8)&0xff;
battery_level[3] = (temp>>0)&0xff;
battery_send(&m_nus, battery_level, 4);
}
并在连接上后就启动定时器。开始 1s 周期发送电量。
Ble_nus.c中修改
extern app_timer_id_t battery_timer;
void ble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)
{
if ((p_nus == NULL) || (p_ble_evt == NULL))
{
return;
}
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connect(p_nus, p_ble_evt);
app_timer_start(battery_timer, APP_TIMER_TICKS(1000, 0), NULL);
break;
case BLE_GAP_EVT_DISCONNECTED:
on_disconnect(p_nus, p_ble_evt);
break;
case BLE_GATTS_EVT_WRITE:
on_write(p_nus, p_ble_evt);
break;
default:
// No implementation needed.
break;
}
}
这里写的例子比较粗糙。正确的做法应该是,在手机使能电量发送特征值的notify后,从机才开始定时发送。不过这里这样做并不影响现象,因为虽然连接上后就开始周期发送电量,但是因为notify并未被使能,所以发送会失败,但是程序可以继续正常运作。
烧写程序,手机连接上后,找到电池的那个特征值,使能Notify就能收到周期性的电量报告了。
Main函数如下:
int main(void)
{
uint32_t err_code;
bool erase_bonds;
uint8_t start_string[] = START_STRING;
// Initialize.
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
uart_init();
app_timer_create(&battery_timer,APP_TIMER_MODE_REPEATED, battery_timer_handler);
buttons_leds_init(&erase_bonds);
ble_stack_init();
gap_params_init();
services_init();
advertising_init();
conn_params_init();
printf("start\r\n");
nrf_adc_init();
err_code = ble_advertising_start(BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
// Enter main loop.
for (;;)
{
power_manage();
}
return 0;
}