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

您所在的位置:网站首页 蓝牙a3芯片针脚 CC2640R2F低功耗蓝牙芯片相关设计分享

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

2023-06-13 10:09| 来源: 网络整理| 查看: 265

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

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

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

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

void *ICall_malloc(uint_least16_t size);

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

void ICall_free(void *msg);

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

mem.c 文件

#include "mem.h" #include 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 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 #include "../events/event.h" #include #include 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_ENTITIES和ICALL_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

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3