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

2023-11-04

电路设计简介

  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.

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

调试器引脚 芯片引脚
1 TMS 引脚
11 TCK 时钟
15 SRSTN 芯片复位引脚
4 GND 接地
5 3.3V
8 GND 接地

使用 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 的文件,所以本文的变量名和函数名是我零时改的可能会出现差异。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 【华为OD机试】荒岛求生【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 有一个荒岛 只有左右两个港口 只有一座桥连接这两个港口 现在有一群人需要从两个港口逃生 有的人往右逃生 有的往左逃生 如果两个人相遇 则PK 体力值大的能够打赢体力值
  • html中hover有静止的命令,我可以通过JavaScript禁用CSS:hover效果吗?

    恐怕没有一个纯JavaScript的通用解决scheme JavaScript不能closuresCSS hover状态本身 不过你可以尝试下面的替代方法 如果您不介意在HTML和CSS中进行一些操作 则无需通过JavaScript手动重置
  • linux分区方案 1t,linux CentOS WEB服务器分区方案

    分区类型 分区的实际大小 解析 SWAP分区 2G 内存为1G 一般为内存的2倍 1G 2G 最少要150 250MB boot 32M 100M 启动分区 最多只要100M左右 opt 100M 1G 附加应用程序 tmp 40M 100
  • APT组织Lazarus近期攻击变化阐述

    Lazarus是来自朝鲜的APT组织 该组织长期对韩国 美国进行渗透攻击 此外还对全球的金融机构进行攻击 堪称全球金融机构的最大威胁 下面为近半年该组织的一些最新动态以及所使用的技术手段 Manuscrypt是该组织最常用的恶意软件家族 此

随机推荐

  • OpenCV VideoCapture.get()参数详解

    param define cv2 VideoCapture get 0 视频文件的当前位置 播放 以毫秒为单位 cv2 VideoCapture get 1 基于以0开始的被捕获或解码的帧索引 cv2 VideoCapture get 2
  • 4个点让你彻底明白Redis的各项功能

    4个点让你彻底明白Redis的各项功能 前言 先看一下Redis是一个什么东西 官方简介解释到 Redis是一个基于BSD开源的项目 是一个把结构化的数据放在内存中的一个存储系统 你可以把它作为数据库 缓存和消息中间件来使用 同时支持str
  • SIGPIPE的设计意图

    SIGPIPE的设计意图 SIGPIPE 是为以下这种情况设计的 grep pattern lt reallyhugefile head grep可能会输出成千上万行文本 但 head 只会读取前10行然后就退出 一旦head退出 grep
  • 《Pytorch深度学习和图神经网络(卷 1)》学习笔记——第八章

    本书之后的内容与当前需求不符合不再学习 信息熵与概率的计算关系 联合熵 条件熵 交叉熵 相对熵 KL散度 JS散度 互信息 无监督学习 监督训练中 模型能根据预测结果与标签差值来计算损失 并向损失最小的方向进行收敛 无监督训练中 无法通过样
  • 如何添加PYNQ-Z2板文件到Vivado

    添加板文件到vivado 先下载pynq z2板文件 PYNQZ2板文件 含约束文件 原理图 zip 下载后将文件复制到 Vivado安装目录 2018 3 data boards board files 重启vivado 完成
  • 重磅!AI与区块链技术知识分享交流会!特邀贾志刚老师、双一流211高校研究生!

    重磅 AI与区块链技术第一次知识交流分享会即将拉开帷幕 本交流会旨在分享交流人工智能 区块链相关内容 包括基础知识分享 前沿论文分享 具体项目实战 提供一个相同领域学者 工作人员在线交流机会 更多精彩内容 尽在微信公众号 AI与区块链技术
  • PCA算法

    https www cnblogs com dengdan890730 p 5495078 html PCA算法是怎么跟协方差矩阵 特征值 特征向量勾搭起来的 PCA Principle Component Analysis 主成份分析 是
  • 2023年7月31日-8月6日,(上午熟悉公司代码,周一到周五晚上优先工作所急视频教程,其他业余时间进行ue视频教程,为独立游戏做准备,本周10小时,合计2199小时,剩余7801小时)

    按照规划 上午熟悉公司源码 下午进行filament和ue渲染 晚上写工作代码 回家后泛读pbrt或者其他书籍催眠 业余学习ue的各种视频教程 为独立游戏做准备 累了就学其他视频教程 随意 可以按照ue 渲染 gt ue osg gt ue
  • testdbg-测试调试器

    http baidutech blog 51cto com 4114344 743464 testdbg 测试调试器 2011 02 24 14 07 00 标签 测试 休闲 调试器 职场 testdbg 原创作品 允许转载 转载时请务必以
  • C#基础与Java的对比

    一 C 简介 C 是微软公司发布的一种面向对象的 运行于 NET Framework之上的高级程序设计语言 C 看起来与Java有着惊人的相似 它包括了诸如单一继承 接口 与Java几乎同样的语法和编译成中间代码再运行的过程 但是C 与Ja
  • REGEXP基础语法

    个人使用https regex101 com 这个比较多一些 大家可以使用 基础用法 限定符 Quantlfier a a出现0次或者多次 a a出现1次或者多次 a a出现0次或者1次 a 6 a出现6次 a 2 6 a出现2 6次 a
  • Labview设计计算机--组合逻辑与时序逻辑(5)

    在写接下来的内容之前 我要强烈推荐一部书 编码 隐匿在计算机软硬件背后的语言 这是一本由浅入深的书 我也是借鉴了书中的很多想法才有勇气尝试在Labview上构建一个简单的计算机 看这本书的门槛不高 大概有高中知识就差不多了 ok 进入正题
  • angular下载文件

    1 window open 打开新页面下载文件 window open url self 优点 最简洁 缺点 当参数错误时 或其它原因导致接口请求失败 这时页面会停留在新打开的页面中 无法监听到接口返回的错误信息 只在页面中直接输出错误 尽
  • 查看linux安装了哪些编译器,如何查看linux已安装的编译器及其版本

    在Linux环境下 新建一个终端 之后在终端直接输入命令 arm linux gcc v 回车之后 即可出现如下的一些信息 此处安装的是交叉编译器gcc 版本4 2 2 Using built in specs Target arm unk
  • 超详细,AI绘画里你不得不知道的SD算法详解

    前言 哈喽 各位小伙伴们大家好 说到AI绘画 可谓是近几年来异军突起 犹如洪水猛兽一般 各种的本土化 商业化 但是相信也有很多朋友跟我一样 对AI绘画的原理一知半解 甚至根本不知道它是怎么工作的 这样只靠着在网上复制粘贴别人的prompt
  • java自学笔记6:UML简介+阶段练习

    1 UML概念 Unified Modeling language UML 又称统一建模语音或标准建模语言 是一个支持模型化和软件系统开发的图形化语言 为软件开发的所有阶段提供模型化和可视化支持 2 UML图示 UML2 2中一共定义了14
  • 使vue组件居中、中下

    position absolute left 50 top 50 transform translate 50 50 中下 居中往下 login form position absolute 绝对定位 bottom 0px 最底下 widt
  • 相机成像 - ISP之RGB域处理

    在之前的文章中 介绍了ISP 的基本流程和RAW域中的处理方法 完美成像中最重要的环节 ISP之RGB域处理 今天延续之前的内容 讲解中间的部分 RGB域的处理 ISP的流程具体如图所示 我们继续围绕这个图把RGB域中每个部分的处理给大家讲
  • 【最远点采样FPS】点云采样方式(一) — 最远点采样

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 点云最远点采样FPS
  • CC2640R2F低功耗蓝牙芯片相关设计分享

    电路设计简介 CC2640 的 RF 差分线越短越好 做差分 100 阻抗匹配 天线部分阻抗 50 欧姆匹配 本次设计采用了陶瓷天线 AN9520 245 减少天线面积 蓝牙芯片在底层 阻抗参考平面第三层 在天线部分下方覆铜 GND 使用嘉