CC2640R2F低功耗蓝牙芯片相关设计分享

电路设计简介

  1. CC2640 的 RF 差分线越短越好,做差分 100Ω 阻抗匹配。
  2. 天线部分阻抗 50 欧姆匹配,本次设计采用了陶瓷天线 AN9520-245 减少天线面积。
  3. 蓝牙芯片在底层,阻抗参考平面第三层,在天线部分下方覆铜(GND),使用嘉立创的阻抗匹配计算器计算线宽。
  4. 巴伦采用分立设计,参考官方文档。ST 公司的 BlueNRG-2 BLE 芯片可采用 BALF-NRG-02D3 巴伦封装。
  5. 天线部分转角做弧线。
  6. 可将天线线路做包地处理,减少信号干扰。

嵌入式开发

这里说的所有内容都是基于 ProjectZero 项目进行二次开发的

关于在 RTOS 中创建 Task 的一点灵感

CC2640 中分配内存主要使用的是:

void *ICall_malloc(uint_least16_t size);

配套用于释放内存的函数是:

void ICall_free(void *msg);

在这个灵感中会用到动态内存的创建和释放,所以可以稍微包装下:

mem.c 文件

#include "mem.h"
#include <icall.h>

void* c_malloc(uint_least16_t size) {
    return ICall_malloc(size);
}

void c_free(void* ptr) {
    ICall_free(ptr);
}

因为很多时候创建任务的一些结构体还有 Task 所需的缓存大小都差不多,主要是想偷偷懒,所以提前分配好一些 Task 的结构体和相关的缓冲区是否能少写一点代码呢?(我不确定这是不是个好办法但确实可以偷懒)

这是 task.c 的实现方式

#include "task.h"
#include "mem.h"
//#include <uartlog/UartLog.h>

static TASK_FACTORY _G_TASK_FACTORY = {0, 0, 0, 0, 0, false};

void task_init(uint32_t task_num, uint32_t stack_size) {
    if (_G_TASK_FACTORY.is_init) return;
    _G_TASK_FACTORY.is_init = true;
    _G_TASK_FACTORY.task_capacity = task_num;
    _G_TASK_FACTORY.stack_capacity = stack_size;
    _G_TASK_FACTORY.tasks = (Task_Struct*)c_malloc(task_num * sizeof(Task_Struct));
    _G_TASK_FACTORY.stack = (uint8_t*)c_malloc(task_num * stack_size);
}

int task_run(uint8_t priority, Task_FuncPtr func) {
    if (!(_G_TASK_FACTORY.is_init) || _G_TASK_FACTORY.task_size >= _G_TASK_FACTORY.task_capacity) {
        return -1;
    }
    Task_Params taskParams;
    Task_Params_init(&taskParams);
    taskParams.stack = _G_TASK_FACTORY.stack + _G_TASK_FACTORY.stack_capacity * _G_TASK_FACTORY.task_size;
    taskParams.stackSize = _G_TASK_FACTORY.stack_capacity;
    taskParams.priority = priority - 1;
    Task_construct(_G_TASK_FACTORY.tasks + _G_TASK_FACTORY.task_size, func, &taskParams, NULL);
    _G_TASK_FACTORY.task_size++;
    return 0;
}

以闪烁 LED 灯为例使用这两个函数:

led_task.c

#include "led_task.h"
#include "../core/task.h"
#include "ti/drivers/PIN.h"
#include "driverlib/ioc.h"
#include <ti/sysbios/knl/Clock.h>
#include "../events/event.h"
#include <ti/sysbios/knl/Event.h>
#include <uartlog/UartLog.h>

static PIN_State ledPin;
static PIN_Handle hLedPin = NULL;

static PIN_Config ledPinCfg[] =
{
    IOID_16 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    PIN_TERMINATE
};

static void led_task_fxn(UArg a0, UArg a1)
{
    hLedPin = PIN_open(&ledPin, ledPinCfg);
    for(;;) {
        PIN_setOutputValue(hLedPin, IOID_16, 0);
        Task_sleep(((500) * 1000) / Clock_tickPeriod);
        PIN_setOutputValue(hLedPin, IOID_16, 1);
        Task_sleep(((500) * 1000) / Clock_tickPeriod);
    }
}

void led_task_run() {
    task_run(2, led_task_fxn);
}

然后在 app.c 中初始化 Task 任务池(Task Pool,管理任务相关的结构体和缓存)

#include "tasks/led_task.h"

task_init(3, 512);
led_task_run();

关于多任务开发需要注意的问题:

中断级别的一点误解:有时候发现任务无法正常运行,但是调高中断级别后能够正常运行,这时候如果多创建几个任务又不正常了。这种情况可能不是中断级别的问题,而是缓冲区配置太大造成的。

ICALL_MAX_NUM_ENTITIESICALL_MAX_NUM_TASKS的配置,其中ICALL_MAX_NUM_TASKS(默认值:2),定义位置在应用的ICall/icall.c文件中,有这样一段描述:

#ifndef ICALL_MAX_NUM_ENTITIES
/**
 * Maximum number of entities that use ICall, including service entities
 * and application entities.
 * The value may be overridden by a compile option.
 * Note that there are at least,
 * Primitive service, Stack services along with potentially generic
 * framework service for the stack thread.
 */
#define ICALL_MAX_NUM_ENTITIES     6
#endif

#ifndef ICALL_MAX_NUM_TASKS
/**
 * Maximum number of threads which include entities.
 * The value may be overridden by a compile option.
 */
#define ICALL_MAX_NUM_TASKS        6
#endif
 */

所以如果需要创建多个 Task 的话最好将ICALL_MAX_NUM_TASKS设大一些,目前我配置的是:6

还有另一处在:TOOLS/defines/ble5_project_zero_cc2640r2lp_app_FlashROM_StackLibrary.opt,里边有个配置项:-DICALL_MAX_NUM_TASKS=6,这里我也把它设大一些。

定时器的使用:

步骤如下:

定义定时器事件和结构体变量定义

#define CAW_TIMEOUT_EVT            Event_Id_10    // 超时事件
#define CAW_TIMEOUT_EVT_INERVAL    500            // 定义超时时间
static Clock_Struct periodicClock;                // 定时器结构体

创建定时器处理函数

static void _timeoutHandler(UArg arg)
{
    // 自定义参数arg中存放CAW_TIMEOUT_EVT值
    if (arg == CAW_TIMEOUT_EVT) {
        // 触发CAW_TIMEOUT_EVT事件
        Event_post(syncEvent, arg);
    }
}

编写事件处理函数

if(events) {
    // 如果当前事件中含有SBP_CAW_PERIODIC_EVT则进入处理
    if (events & SBP_CAW_PERIODIC_EVT) {
        //! TODO
        // 加入需要超时处理的代码
        // ......
        // 这里需要重启定时器,否则定时器只工作一次
        Util_startClock(&periodicClock);
    }
}

配置定时器并启动,进入超时处理流程

// 配置定时器
Util_constructClock(&periodicClock, _timeoutHandler,
    CAW_TIMEOUT_EVT_INERVAL, 0, false, CAW_TIMEOUT_EVT);

// 初次启动
Util_startClock(&periodicClock);

队列的使用(Queue)

步骤如下:

创建队列消息结构和队列结构体定义

// 队列消息结构体
typedef struct {
    uint8_t event;
    void    *pData;
} pzMsg_t;

// 创建队列变量
static Queue_Struct msgQueue;
static Queue_Handle msgQueueHandle;

创建队列处理函数

if(events) {
    // 通过while循环将队列中的消息消耗完
    while(!Queue_empty(msgQueueHandle)) {
        pzMsg_t *pMsg = (pzMsg_t *)Util_dequeueMsg(msgQueueHandle);
        if(pMsg) {
            //! TODO
            // 处理消息
            ICall_free(pMsg);
        }
    }
}

初始化队列

Queue_construct(&msgQueue, NULL);
msgQueueHandle = Queue_handle(&msgQueue);
  • 将消息插入队列,其中第一个参数可以是自定义的事件,第二个参数是一个数据结构体的指针变量

    static status_t enqueueMsg(uint8_t event, void *pData) {
        uint8_t success;
        pzMsg_t *pMsg = ICall_malloc(sizeof(pzMsg_t));
    
        if(pMsg) {
            pMsg->event = event;
            pMsg->pData = pData;
    
            success = Util_enqueueMsg(g_msgQueueHandle, g_syncEvent, (uint8_t *)pMsg);
            return (success) ? SUCCESS : FAILURE;
        }
        return(bleMemAllocError);
    }
    

处理蓝牙接收到的数据

Application/services目录下存放了很多服务,其中data_service.c提供了数据传输的基本能力,在project_zero.c中注册了一些服务的回调函数,比如

static DataServiceCBs_t Message_ServiceCBs =
{
    .pfnChangeCb = DataService_ValueChangeCB,  // Characteristic value change callback handler
    .pfnCfgChangeCb = DataService_CfgChangeCB, // Noti/ind configuration callback handler
};

因为我们需要处理的是接收到的数据,所以只关注DataService_ValueChangeCB回调函数就足够了,看看DataService_ValueChangeCB长什么样的:

static void DataService_ValueChangeCB(uint16_t connHandle,
                                                  uint8_t paramID, uint16_t len,
                                                  uint8_t *pValue)
{
    // See the service header file to compare paramID with characteristic.
    Log_info1("(CB) Data Svc Characteristic value change: paramID(%d). "
              "Sending msg to app.", paramID);

    pzCharacteristicData_t *pValChange =
        ICall_malloc(sizeof(pzCharacteristicData_t) + len);

    if(pValChange != NULL) {
        pValChange->svcUUID = MESSAGE_SERVICE_SERV_UUID;
        pValChange->paramID = paramID;
        memcpy(pValChange->data, pValue, len);
        pValChange->dataLen = len;

        // 此处会向消息队列中投递一个PZ_SERVICE_WRITE_EVT的事件,并且带着
        // pValChange值
        if(enqueueMsg(PZ_SERVICE_WRITE_EVT, pValChange) != SUCCESS) {
          ICall_free(pValChange);
        }
    }
}

所以应该去ProjectZero_processApplicationMessage队列处理函数中看看发生了什么:

static void ProjectZero_processApplicationMessage(pzMsg_t *pMsg) {
    // ......
    switch(pMsg->event) {
        // 因为投递的到队列中的消息使用的事件是:PZ_SERVICE_WRITE_EVT
        // 所以这里我们只关注PZ_SERVICE_WRITE_EVT就可以了
        case PZ_SERVICE_WRITE_EVT:
            switch(pCharData->svcUUID) {
                case DATA_SERVICE_SERV_UUID:
                    // 这个函数就是处理蓝牙接收到消息的函数了
                    DataService_ValueChangeHandler(pCharData);
                    break;
            }
            break;
        // ......
    }
    // ......
}

可以看到最终调用了DataService_ValueChangeHandler函数处理数据,继续跟踪:

void DataService_ValueChangeHandler(
    pzCharacteristicData_t *pCharData)
{
    static uint8_t received_string[DS_STRING_LEN] = {0};
    switch (pCharData->paramID)
    {
    case DS_STRING_ID:
        memset(received_string, 0, DS_STRING_LEN);
        memcpy(received_string, pCharData->data,
               MIN(pCharData->dataLen, DS_STRING_LEN - 1));
        // 将数据通过串口发送出去
        Log_info3("Value Change msg: %s %s: %s",
                  (uintptr_t) "Data Service",
                  (uintptr_t) "String",
                  (uintptr_t)received_string);
        //! TODO
        // 这里可以添加消息处理流程
        break;

    // 此处省略Stream(流)处理
    default:
        return;
    }
}

串口日志

需要包含头文件:#include <uartlog/UartLog.h>

#  define Log_info0(fmt)
#  define Log_info1(fmt, a0)
#  define Log_info2(fmt, a0, a1)
#  define Log_info3(fmt, a0, a1, a2)
#  define Log_info4(fmt, a0, a1, a2, a3)
#  define Log_info5(fmt, a0, a1, a2, a3, a4)

#  define Log_warning0(fmt)
#  define Log_warning1(fmt, a0)
#  define Log_warning2(fmt, a0, a1)
#  define Log_warning3(fmt, a0, a1, a2)
#  define Log_warning4(fmt, a0, a1, a2, a3)
#  define Log_warning5(fmt, a0, a1, a2, a3, a4)

#  define Log_error0(fmt)
#  define Log_error1(fmt, a0)
#  define Log_error2(fmt, a0, a1)
#  define Log_error3(fmt, a0, a1, a2)
#  define Log_error4(fmt, a0, a1, a2, a3)
#  define Log_error5(fmt, a0, a1, a2, a3, a4)

可以清楚的看到支持 3 个日志级别分别是:info, warning 和 error,宏名最后的数字代表的是可变参数的数量,如果没有可变参数,那就选择Log_info0就可以了。

如何拦截 ProjectZero 中的事件处理

定义如下一个函数

xdc_UInt Caw_Event_pend(ti_sysbios_knl_Event_Handle __inst, xdc_UInt andMask, xdc_UInt orMask, xdc_UInt32 timeout) {
    xdc_UInt events = ti_sysbios_knl_Event_pend(__inst, andMask, orMask | SBP_CAW_ALL_EVENTS, timeout);
    // 此处为自定义事件处理函数
    Event_processer(events);
    return events;
}

然后在project_zero.c文件中搜索Event_pend,并使用Caw_Event_pend函数替换Event_pend函数调用,就完成了拦截

static void ProjectZero_taskFxn(UArg a0, UArg a1)
{
    // Initialize application
    ProjectZero_init();
    Caw_Event_init(selfEntity, syncEvent);
    // Application main loop
    for(;; ) {
        uint32_t events;
        // 拦截ProjectZero的事件处理
        events = Caw_Event_pend(syncEvent, Event_Id_NONE, PZ_ALL_EVENTS,
                            ICALL_TIMEOUT_FOREVER);
        if(events) {
            // ......
        }
    }

其他问题:

使用 Code Composer Studio + XDS100 V3.0 仿真时报错:

错误如下:

An error occurred while hard opening the controller.

-----[An error has occurred and this utility has aborted]--------------------

This error is generated by TI's USCIF driver or utilities.

The value is '-183' (0xffffff49).
The title is 'SC_ERR_CTL_CBL_BREAK_FAR'.

The explanation is:
The controller has detected a cable break far-from itself.
The user must connect the cable/pod to the target.

解决方式:连线错误,正确的接线方案如下:

调试器引脚芯片引脚
1TMS 引脚
11TCK 时钟
15SRSTN 芯片复位引脚
4GND 接地
53.3V
8GND 接地
使用 UniFlash 或 SmartRF Flash Programmer 2 烧录后,将 XDS100 V3.0 的 USB 线拔掉,电路板断电重新上电后 CC2640 芯片不工作,但是将 XDS100 V3.0 插上 USB 供电后 CC2640 就能够正常工作

解决方式:不要拔 USB 线,直接将 XDS100 V3.0 与电路板的连接线拔掉,这时候电路板断电再上电后 CC2640 能够正常工作。

CC2640 插着调试器能够正常运行,复位后不能正常运行

解决方式:切换老版本的 SimpleLink CC2640R2 SDK 的版本

CC2540R2L 不支持 Sensor controller

注意:

本人刚入门硬件设计和嵌入式开发也刚接触 CC2640 蓝牙芯片不久,文中可能会出现一些问题,我自己在项目中修改了很多 ProjectZero 的文件,所以本文的变量名和函数名是我零时改的可能会出现差异。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值