简介面向单片机与嵌入式开发者的CC2541蓝牙模块全套开发资料涵盖芯片/模块开发手册、寄存器配置说明以及官方BLE通信例程。资源整合了从单片机端固件到Android端APP的完整链路可直接实现两端蓝牙数据交互适合需快速搭建低功耗蓝牙透传方案的工程师或学习BLE协议栈的学生。压缩包共121个文件包含PDF开发手册、Java/class源码、Android工程gradle配置、APK安装包以及用于固件烧录的bin和rar工具等整体大小8.49MB文件按功能归档目录层级清晰便于按需取用。官方Demo涵盖GATT服务、扫描、连接、数据传输等核心功能可直接安装APK体验也可阅读源码学习BLE通信细节。初学者可从开发手册入手了解引脚定义与AT指令有经验者则可直接移植例程并对照App修改业务逻辑。已有3204人学习下载凭借这些资料可减少踩坑、缩短开发周期是进行CC2541蓝牙应用开发的实用参考。1. 项目范围与整体思路折腾 CC2541 有一阵子了这个芯片在 BLE 4.0 时代属于绝对的主力选手TI 当年的 SimpleLink 系列里它凭借低成本、低功耗、外设丰富的特点被大量用在智能手环、防丢器、ibeacon、医疗贴片、无线传感器这些产品上。就算放到现在来看依然有大量量产的模块沿用这颗芯片的方案某宝上几块钱一片的 CC2541 模块遍地都是对于想入门 BLE 开发或者需要快速出原型的人来说它依然是性价比极高的选择。我手里这套资料最核心的价值在于包含了官方 BLE 协议栈的完整工程、芯片手册、硬件原理图参考、以及可直接编译烧录的通信例程。换句话说你不必从零啃 1500 页的 datasheet也不用自己搭协议栈只要按部就班地把官方例程跑起来就能在一天之内让两块模块互相通信。这套东西适合谁来用我的判断是刚接触 BLE 开发、想搞懂广播、扫描、连接、GATT 服务这些概念的人手里有 CC2541 模块但不知道怎么下程序、怎么调通透传的嵌入式工程师需要快速评估低功耗蓝牙方案可行性的产品经理或硬件工程师。先说清楚一个底层逻辑CC2541 不是一颗单纯的主控 MCU而是把 8051 内核和 2.4GHz 射频前端集成在一起的 SoC。这意味着它既能跑应用逻辑又能直接收发蓝牙数据包。所以开发它的时候你面对的是两套体系一套是 8051 的 C 语言编程另一套是 TI 封装好的 BLE 协议栈 API。官方例程的厉害之处就在于它把这两套体系如何协同工作给你演示得明明白白。2. 开发环境搭建与工程结构2.1 工具链选型CC2541 的官方开发环境是 IAR Embedded Workbench for 8051这里有个大坑必须先说别用 Keil。虽然 Keil 也支持 8051 内核但 TI 的 BLE 协议栈工程只提供 IAR 版本的项目文件你用 Keil 打开就是一堆乱码和报错。我去年的一个项目就是在这个上面耽误了整整一天后来老老实实装了 IAR 才解决。工具清单如下工具版本说明用途IAR Embedded Workbench for 80518.10 或 8.20 均可向下兼容编译、下载、调试SmartRF Flash Programmer1.12.5 以上烧录固件、读取芯片信息BLE Device Monitor1.4.2PC 端调试 BLE 通信Android 手机 nRF Connect最新版即可移动端扫描、连接、读写特征值CC Debugger 或 USB 转串口模块烧录器必备连接模块与电脑强调一点IAR 安装完成后必须打上 8051 编译器补丁否则编译 SimpleBLEPeripheral 工程时会报Error[Pe095]: the linker requires an argument这类奇怪问题。TI 官方的安装包里其实已经帮你集成好了但如果你是从别的渠道搞到的 IAR 精简版那就得自己去补。2.2 官方 BLE 协议栈工程结构把官方 SDKBLE-CC254x-1.4.0解压后你会看到下面这种目录结构我建议你一定要花五分钟把每个目录过一遍因为后续找例程、找 API、找 bug 全都仰仗它Texas Instruments/BLE-CC254x-1.4.0/ ├── Accessories/ ├── Components/ │ ├── hal/ │ ├── osal/ │ ├── stack/ │ └── target/ ├── Documents/ ├── Projects/ │ ├── ble/ │ │ ├── SimpleBLEPeripheral/ │ │ ├── SimpleBLECentral/ │ │ ├── SimpleBLEBroadcaster/ │ │ └── SimpleBLEObserver/ │ └── ... └── Tools/这里面的关键路径解释一下Components/stack/存放 BLE 协议栈主体包括 L2CAP、GAP、GATT、SM安全管理等层的实现代码这些代码被封装成库你不需要修改只需要调用 API。Components/osal/是 TI 的 OSAL 操作系统抽象层它提供任务调度、消息传递、内存管理机制。简单理解它就是一颗微型实时操作系统你的应用代码都跑在 OSAL 的任务循环里。Projects/ble/下面就是各种官方例程工程目录最常用的两个是SimpleBLEPeripheral外设也就是从机和SimpleBLECentral主机。初次接触的人最容易困惑的点是为什么官方例程里看不到main()函数一层层调下去的业务逻辑因为 OSAL 把入口封装了。你真正要关注的地方是simple_peripheral.c这个文件里面就是你写业务逻辑的主战场。3. BLE 核心机制与官方例程拆解3.1 GAP 角色与广播参数BLE 协议栈里的 GAP 层是负责设备发现和连接管理的它定义了四种角色Broadcaster、Observer、Peripheral、Central。说人话就是Broadcaster 和 Peripheral 都可以理解为被发现的设备前者只能发广播不带连接功能后者可以被连接Observer 和 Central 都是发现别人的设备前者只接收广播不发起连接后者会主动连接。CC2541 的官方例程里SimpleBLEPeripheral 默认是 Peripheral 角色SimpleBLECentral 默认是 Central 角色。这两个例程一主一从正好凑成一套完整的通信链路。看 SimpleBLEPeripheral 的代码广播参数的设置在这个函数里static uint8_t advertData[] { 0x02, GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, 0x03, GAP_ADTYPE_APPEARANCE, LO_UINT16(GAP_APPEARE_UNKNOWN), HI_UINT16(GAP_APPEARE_UNKNOWN), 0x05, GAP_ADTYPE_MANUFACTURER_SPECIFIC, 0xAA, 0x55, 0x00, 0x01 };这里解释一下广播包的组成规则每一段广播数据的第一个字节表示本段长度包含长度字节自身吗不包含长度是指后面跟着的 AD Type AD Data 的总长度第二个字节是 AD Type广播数据类型后面是数据。上面这段代码配置了三段广播数据Flags设备能力标志、Appearance外观类型、Manufacturer Specific厂商自定义数据。这个结构是 BLE 广播的标准格式你用 nRF Connect 扫描时看到的就是这些信息的解析结果。广播间隔在GAPRole_SetParameter里设置uint16_t advInt 160; // 单位是 0.625ms160 * 0.625 100ms GAPRole_SetParameter(GAPROLE_ADVERT_OFF_TIME, sizeof(uint16_t), advInt);扫描到设备是小事真正让新手抓狂的是为什么我修改了广播数据手机上看不到变化。这里有个关键点修改代码后必须 clean 重新编译并且确认烧录成功。我在实际开发中遇到过一次改了设备名称之后手机端怎么刷新都是旧名字后来才发现是 IAR 增量编译没有把修改的源文件编译进去clean 之后重新 build 就好了。3.2 GATT 服务与特征值GAP 管怎么连接GATT 管连接之后能干什么。GATT 服务的核心概念是 Service服务和 Characteristic特征值特征值就是实际数据传输的载体。SimpleBLEPeripheral 例程里定义了一个自定义服务UUID 是0xFFF0里面包含一个可读可写的特征值0xFFF1。这段逻辑在simpleProfile_AddService这个函数里初始化。数据传输的核心代码在simpleProfile_WriteAttrCB回调函数里当手机端往特征值写数据时这个回调会被触发static bStatus_t simpleProfile_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset) { if (pAttr-type.len ATT_BT_UUID_SIZE pAttr-type.uuid[0] LO_UINT16(SIMPLEPROFILE_CHAR1_UUID)) { // 把收到的数据拷贝到特征值缓冲区 uint8_t *pValue2 GATT_UUID_128_TO_16(pAttr-type.uuid); if (memcmp(pValue2, simpleProfileChar1, SIMPLEPROFILE_CHAR1_LEN) 0) { // 数据校验通过 } osal_memcpy(simpleProfileChar1, pValue, len); // 通知对端数据已更新 if (simpleProfile_Notify) { simpleProfile_Notify(connHandle); } } return SUCCESS; }这段代码看起来很绕但实际逻辑很简单手机端往特征值0xFFF1写入一段数据芯片收到后触发回调数据被存储到simpleProfileChar1这个数组里。反过来如果芯片想主动给手机发数据就调用simpleProfile_Notify触发通知Notification手机端就能通过订阅收到数据。这里有个新手最容易犯的错误特征值的权限设置。在simpleProfileAttrTbl这个属性表里每个特征值都定义了访问权限static gattAttribute_t simpleProfileAttrTbl[] { // 服务声明 { {ATT_BT_UUID_SIZE, primaryServiceUUID}, GATT_PERMIT_READ, 0, (uint8_t *)simpleProfileService }, // 特征值声明 { {ATT_BT_UUID_SIZE, characterUUID}, GATT_PERMIT_READ, 0, simpleProfileChar1Props }, // 特征值数据 { {ATT_BT_UUID_SIZE, simpleProfilechar1UUID}, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, simpleProfileChar1 }, };如果你在simpleProfileChar1Props里没有置位GATT_PROP_NOTIFY标志位那手机端就无法订阅这个特征值的通知数据就只能被动读、不能主动推。这个权限位和属性表里的GATT_PERMIT_*是两套独立的权限体系一个管能不能操作一个管操作方式是什么经常有人搞混。3.3 配对绑定机制CC2541 默认是支持配对绑定的官方例程里通过GAPBondMgr_SetParameter配置了绑定策略uint8_t pairMode GATT_PAIRING_NO_PAIRING; GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), pairMode);默认情况下pairMode是GATT_PAIRING_NO_PAIRING也就是说模块不会主动发起配对请求。如果你需要设备支持绑定Bond比如需要加密连接才能读写特征值那就得把配对模式改成GATT_PAIRING_INITIATE或者GATT_PAIRING_WAIT_FOR_REQ同时还要设置GAPBOND_IO_CAPABILITIESIO 能力和GAPBOND_OOB_ENABLED是否启用带外配对等参数。很多人在用 ble 调试助手的时候遇到连接上了但读不到服务的情况多半就是因为设备要求配对但手机端没处理配对请求或者配对方式不匹配。我调试时遇到过一种典型情况CC2541 端把 IO 能力设置为GAPIO_CAP_NO_INPUT_NO_OUTPUT手机端则是 Just Works 方式这种组合是可以无感配对的。但如果模块端设置了 PIN 码输入比如GAPIO_CAP_KEYBOARD_ONLY手机弹窗输入 PIN 后两边的密钥对不上连接会直接断开。4. 两块模块相互通信的实现路径4.1 主从角色配置官方例程里SimpleBLECentral 默认扫描0xFFF0服务并尝试连接 SimpleBLEPeripheral。如果你手头有两块 CC2541 模块想实现模块 A 发数、模块 B 收数配置思路非常清晰一块烧 SimpleBLEPeripheral另一块烧 SimpleBLECentral。先看 Central 端的关键代码。扫描到外设之后它会在simpleBLECentralEventCB回调里处理连接事件case GAPROLE_CONNECTED: // 连接建立成功保存连接句柄 simpleBLECentralConnHandle pEvent-deviceInfo.connHandle; // 开始发现服务 simpleBLECentralStartDiscovery(simpleBLECentralConnHandle); break;服务发现完成之后就能找到 GATT 句柄然后就可以直接读写对方服务的特征值。官方例程里simpleBLECentral_WriteChar函数负责发数据核心逻辑是static void simpleBLECentral_WriteChar(uint8_t value) { uint8_t writeData value; attWriteReq_t req; req.handle simpleBLECentralCharHdl; req.len 1; req.value[0] writeData; req.sig 0; req.cmd 0; GATT_WriteCharValue(simpleBLECentralConnHandle, req, simpleBLETaskId); }这里有个参数值得注意req.cmd 0表示这是带响应的写请求Write Request对端处理后会回复确认可靠性高但速度慢如果把cmd设为 1则变成无响应的写命令Write Command速度快但不保证送达。简单应用建议用cmd 0等收到回调确认后再发下一条避免丢数据。4.2 数据透传优化技巧官方例程默认的 MTU最大传输单元是 23 字节其中包含 3 字节的 ATT 头所以单次实际能传输的用户数据只有 20 字节。如果应用层需要发大包比如 OTA 固件升级、文件传输就必须协商更大 MTU。CC2541 上设置 MTU 的接口是GATT_ExchangeMTU但这颗芯片的协议栈版本最高只支持到 247 字节 MTU。实际测试下来把 MTU 调到 247 后单次能传 244 字节吞吐量能提升不少。不过要提醒一句CC2541 有 256 字节的包缓冲区上限调 MTU 时别忘了检查协议栈堆内存配置否则很容易出现死机或内存溢出。如果你不想搞 MTU 协商还有一个土办法应用层做拆包重组。比如把 100 字节的数据分成 5 个 20 字节的小包按序发送接收端再根据协议拼接。这样做的好处是不需要改协议栈配置坏处是逻辑复杂度转移到应用层还得处理丢包重传。我的建议是如果能改 MTU 就改 MTU改不了的场景再用拆包方案。4.3 串口透传组合拳CC2541 最常见的落地形态是串口透传模块也就是上面跑一个 UART BLE 桥接程序。手机蓝牙发数据模块通过串口转给 MCUMCU 通过串口把数据发给模块模块再通过 BLE 通知发给手机。实现思路在官方例程基础上改就行把 UART 的 RX 中断使能收到数据后存到环形缓冲区在 OSAL 任务里定时检查环形缓冲区有数据就调用GATT_Notification发出去收到 BLE 写回调后把数据从 UART TX 发出。这块有一个很关键的细节CC2541 的 UART 波特率默认是 115200但要注意它的时钟源。如果用内部 RC 振荡器波特率误差会比较大和 PC 串口对不上。建议配置成外部 32MHz 晶振并且开启流控或者降低波特率到 9600实测下来会稳定很多。5. 调试工具链与问题排查5.1 BLE 调试助手的使用要点我用的是手机上 nRF Connect 这个 App它免费且功能完整。基本调试流程打开 App点击SCANNER扫描设备确认广播包内容找到对应设备默认名称是SimpleBLEPeripheral点击CONNECT连接连接成功后进入GATT Client页面可以看到服务列表点击0xFFF0服务下的0xFFF1特征值选择Read读数据点击Notify订阅通知模块主动发数据时手机就能实时收到。整个流程走通之后你的开发环境就算彻底 OK 了。后续调试就按这个套路来改代码、编译烧录、手机看现象效率非常高。还有一个非常实用的技巧用 ble 调试助手模拟主机。有时候模块端逻辑写错了连接上就崩这时候可以把手机当发号施令方用 App 手动写特征值触发模块上的回调看它有没有正确响应。这一步能帮你把硬件问题和软件问题快速隔离出来。5.2 常见故障速查表我把这一年多来在网上和微信群里看到的高频问题整理成了表格方便大家对照排查现象可能原因排查方法模块广播不出来电源供电不足、晶振未起振、固件未烧录量电压是否稳定 3.3V用示波器看 32MHz 晶振波形重烧官方例程能扫描到但连不上连接参数冲突、已有连接占满连接槽检查GAPROLE_MAX_CONN_INTERVAL是否合适确认模块没被其他主设备连着连接上但读不到服务服务 UUID 过滤条件不匹配检查主设备的 UUID 白名单换成0xFFF0或者删除过滤数据收发不稳定波特率误差大、MTU 不匹配确认串口波特率、确认 MTU 协商结果功耗下不来没有配置睡眠模式、外设未释放确认HAL_SLEEP宏是否打开检查osal_pwrmgr_device设置配对总失败IO 能力不匹配、密钥协商失败统一两端 IO 能力关闭自动配对改为手动触发这里面最值得单独说的是睡眠功耗问题。CC2541 标称的待机电流可以到 1μA 以下但这个数字的前提是协议栈允许进入 PM3 深度睡眠且所有外设尤其是 UART 和 ADC都被正确关闭。如果你只是下载了官方例程不做任何配置默认是 PM1 轻度睡眠电流大概在毫安级别和 datasheet 说的低功耗差了三个数量级。这也是很多人测完电流之后来骂 TI 的原因——不是芯片不行是没配置对。要真正跑出低功耗需要做的关键步骤// 1. 在编译选项中定义 POWER_SAVING // 2. 调整 OSAL 的睡眠模式 uint8_t sleepMode HAL_SLEEP_MODE_PM3; osal_pwrmgr_device(PWRMGR_BATTERY); HAL_SLEEP_TIMER_START();同时还要把simple_peripheral.c里GAPROLE_ADVERT_ENABLED设为 FALSE广播耗电很大等有数据要发的时候再打开广播或维持连接。这套配置跑下来实测待机电流能到 8μA 左右比默认配置低了至少两个数量级。5.3 两个影响开发效率的隐藏坑第一个坑CC Debugger 连接不上芯片。这个问题 90% 是调试器固件版本太旧用 SmartRF Flash Programmer 升级一下 CC Debugger 固件就行。另外 CC2541 的 Debug 引脚和普通 IO 复用P2.1/P2.2如果你在代码里把这些引脚配置成了 GPIO 输出也会导致调试器连不上解决办法是按住模块的复位键在 SmartRF 软件点连接瞬间松开复位。第二个坑IAR 编译报错找不到头文件。这个大概率是工程路径没有配好。BLE 协议栈的头文件分散在多个目录IAR 工程里通过$PROJ_DIR$相对路径引用的。如果把工程复制到中文路径下或者目录层级变了相对路径就失效了。解决办法在 IAR 的 Option - C/C Compiler - Preprocessor 里重新添加所有包含路径注意用英文路径别带空格。6. 基于官方例程的扩展方向如果你已经把 SimpleBLEPeripheral 跑通了官方例程的使命就算完成了。下一步往哪走、怎么扩展这里给两个方向参考。6.1 改造成 iBeacon 发射器iBeacon 的本质就是不断广播一段特定格式的数据。苹果定义的 iBeacon 广播格式是厂商 ID 是0x004C数据类型是0x02里面包含 UUID、Major、Minor 三个字段和校准的 RSSI 值。在 CC2541 上实现起来非常简单只需要修改广播数据static uint8_t beaconData[] { 0x02, // 长度 GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, 0x1A, // 长度26 字节 GAP_ADTYPE_MANUFACTURER_SPECIFIC, 0x4C, 0x00, // Apple Company ID 0x02, // iBeacon 类型 0x15, // 剩余数据长度 // UUID: 你可以自定义 16 字节 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, // Major: 2 字节例如 0x0001 0x00, 0x01, // Minor: 2 字节例如 0x000A 0x00, 0x0A, // 校准 RSSI: 1 字节例如 -59dBm 对应 0xC5 0xC5 };改完广播数据后把连接相关的代码注释掉只保留广播就变成一个标准的 iBeacon 基站了。这种模式常被用在室内定位、展馆导览、商超推送等场景。6.2 接传感器做低功耗采集节点CC2541 的另一个经典玩法是接传感器定期上报数据。比如接一个温湿度传感器SHT30 或 DHT22CC2541 每 10 秒醒来一次读传感器数据通过 BLE 通知发给手机然后继续睡。实现上要注意两点传感器供电要可控把传感器的 VCC 接在 CC2541 的一个 GPIO 上读数据前开电读完关电。直接接在 VCC 上会导致传感器常供电静态电流几微安到几十微安对低功耗影响很大。定期断开连接如果是电池供电的应用不要一直保持 BLE 连接。连接广播的功耗远大于广播断开模式。建议做按需连接——平时只广播手机需要数据时再连上去拿数据拿完就断开。我自己做过一个手环原型上的温湿度采集节点用 CR2032 纽扣电池供电配置 10 秒广播间隔 传感器只在唤醒后供电读取实测平均电流能做到 30μA 左右一颗电池用大半年没问题。7. 实操心得与踩坑记录CC2541 这套方案我用过很多次从最初的移植官方例程到后来给产品写完整的上位机协议每一步都有值得记录的细节。这里挑几个印象最深的点分享。第一官方例程的代码风格和普通嵌入式项目差别很大OSAL 的消息驱动机制让很多人刚接触时摸不着北。我的建议是别急着改代码先花半天时间把 SimpleBLEPeripheral 工程从头到尾读一遍搞清楚osal_start_system()这个循环里发生了什么。一旦理解了任务注册、事件触发、消息传递这套机制后面加功能就顺了。这个前期投入非常值能帮你省掉后面大量排查问题的时间。第二调试 BLE 一定要先打印、后通信。代码里任何关键节点都加上串口日志输出比如广播启动成功、连接建立、收到数据、发送完成全部打出来。很多问题看一眼日志就知道出在哪一层。我之前遇到过模块不明原因重启反复排查无果后在osal_start_system()入口加了个串口打印才发现是协议栈内部 assert 触发重启——再结合 assert 信息定位到是内存堆溢出问题瞬间解决的。第三博文里提到的 MTU 协商、配对绑定、低功耗配置不建议在产品上线下反复改提前设计好这些参数的默认值。因为后期改动这些参数很容易引入隐藏的兼容性问题比如旧设备和新固件的配对密钥重置、MTU 大小不一致导致的数据截断。官方例程只给你能跑起来的版本离能量产还差很多趁早就把配置固化下来。第四也是最重要的一条芯片选型时千万别只看功耗数字。CC2541 的 1μA 待机电流是深度睡眠下拿掉 SRAM 内容换来的唤醒后重新初始化外设的时间可能长达几百微秒。如果应用对唤醒响应时间敏感比如门锁被触碰后要立刻响应你就要在功耗和唤醒速度之间做取舍。这种决策层面的东西不是跑通一个例程就能学会的得实际做项目才有体感。最后再补充一个实用技巧在调试 CC2541 的 BLE 通信时建议把手机和模块的距离控制在一米以内。蓝牙 4.0 的射频灵敏度标称 -94dBm但实际测试中隔一堵墙就会出现重传率明显上升的情况。近距离调试能帮你排除大量环境干扰因素让问题定位更精准。等基本调通了再拉距离测真实性能也不迟。本文还有配套的精品资源点击获取