Chinaunix首页 | 论坛 | 博客
  • 博客访问: 605538
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-23 22:57
个人简介

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: 嵌入式

2016-07-21 14:12:48


51822的adc 模块图如下所示:

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);//选择AIN2adc的输入



51822的ADC的配置组合有很灵活。但是51822 ADC有两条限制规则

    1,引脚上的输入电压经过缩放后不能大于2.4V

    2, GPIO上的输入电压不能超过VDD+0.3V.

    所以具体的各种配置下所允许测量的 输入电压有一定限制,需要看手册确定。



ADC的工作方式可以是轮训或者中断方式。

51822 ADC本身只支持一次转换,所以每次转换完成后需要再测量时,都要再次设置启动转换。如果需要保持周期测量,则需要用一个定时器来周期启动adc


INTEN:改寄存器用来设置 转换完成后是否触发中断。

BUSY: 该寄存机用来查看ADC转换是否已经完成。通常在轮训方式中使用。

ENABLE: 使能adc

RESULT:存储 adc 转换后的结果。


中断方式就是使能 INTEN寄存器中的转换完成触发中断。并开启MCUadc中断,每次启动后等到中断到来,在中断中读取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);//选择AIN2adc的输入

    //使能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;

}







阅读(2211) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

yunjie1672017-06-13 11:28:05

这个是一个非常好借力打力的例子,回去我也试下,再次先谢谢博主~