打印
[原创]

CC254x蓝牙4.0BLE协议栈开发(连载)

[复制链接]
楼主: zzq宁静致远
手机看帖
扫描二维码
随时随地手机跟帖
21
zzq宁静致远|  楼主 | 2014-7-25 10:54 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
第十七节  协议栈UART实验
    协议栈中已经用了串口的驱动,我们要做的只是对串口进行初始化,然后就可以进行串口数据的收发了。
    用使用串口,第一步,需要打开使能串口功能,通过配置工程来实现,这里注意,我们现在不使用USB的CDC类来实现串口,所以HAL_UART_USB=FALSE。
HAL_UART=TRUE
HAL_UART_USB=FALSE
    要使用串口必须先初始化相应的串口,那该如何初始化呢?在Hal_uart.h文件中我们可以看到如下函数。
uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config);
    这个函数就是用来初始化串口的,这个函数有两个参数,第一个指定串口号,第二个是串口的配置参数。我们来看看这个结构体的定义:
typedef struct
{
  bool                configured;               // 配置与否
  uint8               baudRate;                 // 波特率
  bool                flowControl;              // 流控制
  uint16              flowControlThreshold;
  uint8               idleTimeout;              // 空闲时间
  halUARTBufControl_t rx;                       // 接收
  halUARTBufControl_t tx;                       // 发送
  bool                intEnable;                // 中断使能
  uint32              rxChRvdTime;              // 接收数据时间
  halUARTCBack_t      callBackFunc;             // 回调函数     
}halUARTCfg_t;
    这个结构体成员很多,但是我们在使用串口的时候并不需要使用所有的成员。
void Serial_Init(void)
{
    halUARTCfg_t SerialCfg = {0};

    SerialCfg.baudRate = HAL_UART_BR_115200;    // 波特率
    SerialCfg.flowControl = HAL_UART_FLOW_OFF;  // 流控制

    SerialCfg.callBackFunc = SerialCb;          // 回调函数
    SerialCfg.intEnable    = TRUE;
    SerialCfg.configured   = TRUE;
    HalLcdWriteString( "Open Uart0", HAL_LCD_LINE_5 );    // 在第5行显示启动信息
    HalUARTOpen(HAL_UART_PORT_0, &SerialCfg);
    HalUARTWrite(HAL_UART_PORT_0, "Hello MT254xBoard\r\n", osal_strlen("Hello MT254xBoard\r\n"));
}
    在串口回调函数中我们只做一件事,将串口接收到的数据显示到LCD中并且原样的从串口输出。回调函数的实现如下:
static void SerialCb( uint8 port, uint8 events )
{
    uint8 RxBuf[64]={0};
    if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL ))  // 发送区满或者空
    {
        return;
    }

    uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0);  // 读取接收据量
    usRxBufLen = MIN(64,usRxBufLen);
    uint16 readLen = HalUARTRead(HAL_UART_PORT_0, RxBuf, usRxBufLen);
    HalUARTWrite(HAL_UART_PORT_0, RxBuf, usRxBufLen);
}
    实验现象,从实验现象中可以看到,一开始在串口中输出了一个标志字符串,然后我们通过串口发送了0123456789,然后数据原样的从串口输出了,这和我们预期的结果是一样的。


    但是我们发现LCD上的显示和我们预期的不一样,LCD上只显示了6789,前面的数据并没有显示,这是怎么一回事呢?进行单步调试可以发现,我们发送一次数据,回调函数被回调了两次,第一次回调只接受到了012345,第二次回调接收到了6789,而在LCD上的显示第二次覆盖了第一次的显示,所以我们会看到这种现象,解决的办法,我们需要定义一个数据帧的时间间隔,当接收数据的间隔超过了此间隔就认为接收结束。


    下面我们改写接收处理,我们在接收到数据后开启定时器,定时5ms这样,当接收间隔大于5ms后,我们就可以在定时事件中处理串口接收到的数据。
static void SerialCb( uint8 port, uint8 events )
{
    if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL ))  // 发送区满或者空
    {
        return;
    }
    uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0);  // 读取接收据量
    if(usRxBufLen)
    {
        usRxBufLen = MIN(128,usRxBufLen);
        uint16 readLen = HalUARTRead(HAL_UART_PORT_0, &SerialRxBuf[RxIndex], usRxBufLen);   // 读取数据到缓冲区
        RxIndex += readLen;
        readLen %= 128;
        osal_start_timerEx(simpleBLEPeripheral_TaskID, UART_EVENT, 5);  // 启动定时器
    }
}
事件处理代码:
  if ( events & UART_EVENT )
  {
    HalLcdWriteString( (char*)SerialRxBuf, HAL_LCD_LINE_6 );    // 在第5行显示启动信息
    HalUARTWrite(HAL_UART_PORT_0, SerialRxBuf, osal_strlen(SerialRxBuf));
    osal_memset(SerialRxBuf, 0, 128);
    return (events ^ UART_EVENT);
  }
    经过这样的处理后,可以发现我们刚刚的问题已经解决了。


    到这里串口已经可以正常使用了,为了更加方便的使用串口,我在这里添加一个函数实现标准C中printf,这样更有利于我们输出。
int SerialPrintf(const char*fmt, ...)
{
    uint32  ulLen;
    va_list ap;

    char *pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN);  // 开辟缓冲区
    va_start(ap, fmt);
    ulLen = vsprintf(pBuf, fmt, ap);        // 用虚拟打印函数实现
    va_end(ap);

    HalUARTWrite(HAL_UART_PORT_0, (uint8*)pBuf, ulLen); // 从串口0输出
    osal_mem_free(pBuf);    // 释放内存空间
    return ulLen;
}
    我们可以像使用C标准中的printf来使用这个函数,例如我们将LCD的输出全部导向串口的输出,在HalLcdWriteString的实现中添加串口输出代码,如下图:


    重新编译并且烧录后可以看到LCD的输出和串口的输出是一样的了。




使用特权

评论回复
22
zzq宁静致远|  楼主 | 2014-7-25 10:57 | 只看该作者
第十八节  协议栈五向按键
    和前面几个一样,按键的驱动在协议栈中也已经有了,我们只需要做一些小的修改,使它适应我们的开发板即可。
1. 修改工程配置,使能按键功能。


2. 在我们的工程中要使用按键功能,仅仅打开配置选项是不够的。因为协议栈代码默认只有MINIDK开发板才有按键。


    从这里可以看到(类似的地方有很多),如果要使能按键功能还需要定义CC2540_MINIDK,但是阅读整个协议栈你会发现,定义CC2540_MINIDK后还会打开其它的功能,而那些功能并不是我们想要的,所以在这里我们使用另外一种方法来实现。我们定义我们的开发板也能使用按键功能,所以在工程配置中添加MT254xboard=TRUE,然后在按键功能有宏开关的地方加入这个条件。具体位置参见代码。


    按下相应的按键后可以看到串口输出相应的按键值。五向按键的工作原理在裸机开发的时候已经讲过了,在协议栈中已经有相应的驱动代码了,无需我们编写,只需要按照实际情况改写即可。例如我们的开发板每个按键对应的电压值和原来的值并不一样,所以我们这里改写了每个按键值的电压范围。
uint8 halGetJoyKeyInput(void)
{
  /* The joystick control is encoded as an analog voltage.
   * Read the JOY_LEVEL analog value and map it to joy movement.
   */
  uint16 adc;
  uint8 ksave0 = 0;
  uint8 ksave1;

  /* Keep on reading the ADC until two consecutive key decisions are the same. */
  do
  {
    ksave1 = ksave0;    /* save previouse key reading */

    adc = HalAdcRead (HAL_KEY_JOY_CHN, HAL_ADC_RESOLUTION_10);

    if ((adc >= 2) && (adc <= 95))  // 85 right
    {
      ksave0 |= HAL_KEY_RIGHT;
    }
    else if ((adc >= 96) && (adc <= 110))    // 101 cent
    {
       ksave0 |= HAL_KEY_CENTER;
    }
    else if ((adc >= 111) && (adc <= 140))    // 127 up
    {
       ksave0 |= HAL_KEY_UP;
    }
    else if ((adc >= 141) && (adc <= 200))    // 170 left
    {
      ksave0 |= HAL_KEY_LEFT;
    }
    else if ((adc >= 201) && (adc <= 300))   // 257 down
    {
     ksave0 |= HAL_KEY_DOWN;
    }
  } while (ksave0 != ksave1);

  return ksave0;
}


使用特权

评论回复
23
zzq宁静致远|  楼主 | 2014-7-25 11:02 | 只看该作者
第十九节  协议栈Flash数据存储
    CC254x自带了256K Flash,这256K的储存空间不仅可以储存代码,也可以储存用户的数据,协议栈自带了SNV管理代码,我们只需要学会使用即可。
    SNV的使用只有两个函数,分别是读函数osal_snv_read和写函数osal_snv_write,在SNV的储存中,储存的每个数据都有一个唯一的ID,SNV也正是利用这个ID来管理储存在Flash中的数据,在BLE的协议栈中,蓝牙自身数据储存用了一部分ID,我们储存的数据ID不可使用这些ID,在bcomdef.h中有这些ID的定义。


    下面我们往SNV中存入串口接收到的数据,然后开发板断电重启后读取出这串字符串并通过串口发送出去,来演示SNV的断电保存。

    首先我们定义一个我们储存数据的ID,注意不能和已经有的定义冲突。

#define BLE_NVID_USER_CFG_START         0x80  //!< Start of the USER Configuration NV IDs
#define BLE_NVID_USER_CFG_END           0x89  //!< End of the USER Configuration NV IDs
    我们在启动事件中读取SNV中0x80的值并通过串口输出读取结果,如果读取成功,则会将读取结果打印到PC端,如果读取失败,则会提示读取失败。


    在串口接收事件中将接收到的数据存入SNV中,并且也进行相应的提示。


    将工程编译下载后,可以看到现象如下:


    第一次上电可以看到,提示读取数据失败了,说明第一次运行时是没有存储数据的,接下来我们通过串口发送字符串 MT254xboard SNV Test字符串。


    可以看到成功的将我们发送过去的字符存入了SNV中,那是否成功存入呢?我们将开发板断电后重启,看看第二次上电是否能够读取出我们存入的数据。


    重启后可以发现我们成功的读取出了第一次存入的数据,说明我们成功的将数据存入了SNV中。

使用特权

评论回复
24
zzq宁静致远|  楼主 | 2014-7-25 11:08 | 只看该作者
第二十节  DHT11温湿度传感器
DHT11简介
    DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测型号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为给类应用甚至最为苛刻的应用场合的最佳选择。产品为4针单排引脚封装,连接方便。
技术参数
Ø 供电电压: 3.3~5.5V DC
Ø 输 出: 单总线数字信号
Ø 测量范围: 湿度20-90%RH, 温度0~50℃
Ø 测量精度: 湿度+-5%RH, 温度+-2℃
Ø 分 辨 率: 湿度1%RH,温度1℃
Ø 互 换 性: 可完全互换 ,
Ø 长期稳定性: <±1%RH/年
    DHT11 数字湿温度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 5Byte(40Bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。DHT11 的数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和。其中校验和数据为前四个字节相加。传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如图所示:



协议栈DHT11测试

    打开DHT11Example工程,我们在启动事件中对DHT11进行初始化。如果初始化失败则说明没有接传感器。


    然后在定时事件中定时的读取温湿度的值。并将结果通过UART显示到PC端。




    从其中可以看到当前的温度为29摄氏度,湿度为30%,往传感器器哈一口气可以看到温湿度都上升了。


使用特权

评论回复
25
zzq宁静致远|  楼主 | 2014-7-25 11:21 | 只看该作者
第二十一节  蓝牙协议栈之从机通讯
    之前都是外围模块的驱动程序,这一节开始,我们进入蓝牙4.0协议栈的核心部分,从机通讯的程序设计。接下来的章节是蓝牙4.0协议栈最为核心的程序设计部分。
    前面的大都是外围器件的实验,这节我们介绍蓝牙通讯中从机的角色,从机的主要工作是对外广播,接受主机的连接,并且接受主机发送过来的数据。这里介绍两个函数:

bStatus_t GAPRole_SetParameter( uint16 param, uint8 len, void *pValue );
    这个函数主要是用来配置从机的一些参数,第一个参数表示需要配置哪个参数,例如我们需要时能从机广播,则需要这样调用:
    uint8 initial_advertising_enable = TRUE;
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );
    第二个函数是特征值改变时的回调函数,当主机给从机发送数据时,从机就会回调这个函数来告知应用层有数据送达。
static void simpleProfileChangeCB( uint8 paramID );
在低功耗蓝牙中,数据的传输是通过特征值的读写来实现的。
    BLE协议栈的GATT层用于应用程序在两个连接设备之间的数据通信的。从GATT层的角度看,当设备连接后,将充当一下两种角色中的一个:
    GATT Client  —— 从GATT服务器读/写数据的设备。
    GATT Server —— 包含客户端需要读/写的数据的设备。
    重要的是要注意,GATTClient和Server 的角色完全独立于BLE的链路层的 slave和master的角色,或GAP层peripheral和central的角色。一个slave 可以是GATT Client或GATT Server,一个master同样可以是GATT Client或GATT Server。 一个GATT Server可以有多个完成一个特定的功能或特性GATT Server组成。
在SimpleBLEPeripheral应用程序中有三个GATT服务:
    Mandatory GAP Service:这个服务包含设备和访问信息,比如设备名称、供应商和产品标识。
    Mandatory GATT Service :这个服务包含有关服务UUID相关信息。
SimpleGATTProfile Service——这个服务是一个示例配置文件,供测试和演示。

Profile简介
    为了更容易的保持Bluetooth 设备之间的兼容,Bluetooth规范中定义了 Profile。Profile 定义了设备如何实现一种连接或者应用,你可以把 Profile 理解为连接层或者应用层协议。Bluetooth 的一个很重要特性,就是所有的 Bluetooth 产品都无须实现全部的 Bluetooth 规范,你可根据所需要的产品实现需要的Profile,不必给开发带来更大的开销。这就是说当需要利用蓝牙提供数据传输功能时就必须建立对应的Profile,TI的BLE协议栈为我们提供了部分Profile,其中一部分是非标准的Profile。其中非标准的有SimpleGATTProfile和SimpleKeysProfile,我们将通过对这两个Profile的介绍及实验来了解Profile的特性和使用。每个 Profile 初始化其响应的服务和内部寄存器。GATT 服务器将整个服务加到属性表中,并为每个属性分配唯一的句柄。 GATTProfile用于存储和处理GATT服务器中的数据。在下面的实验中需要用到的都是我们自己新建的Profile,即非标准的Profile。其中主要要注意Profile、UUID、handle、CharacteristicValues。

SimpleGATTProfile及Btool的使用
SimpleGATTProfile中包含5个特征值,每一个的属性都不同:
    SimpleGATTProfile 特征值属性:


    Btool是PC端工具,使用特定的HCI命令与CC2540通信,PC端需要通过串口或 USB 连接 CC2540,CC2540 使用 HostTestRelease 工程,硬件可以使用 USBDongle(对应CC2540USB)或我们提供的USBDongle。

USBDongle连接从机
    使用馒头科技有限公司的USBDongle,烧写HostTestRelease固件,连接电脑后就可以用Btool软件来连接从机设备。
    将从机工程编译下载到开发板,连接串口到PC端,我们通过串口来观察设备的运行,运行后可以看到设备处于广播。


    这是我们插入USBDongle到电脑,可以看到识别到一个串口插入,如图,这就是USBDongle用CDC的方式实现的串口。


    打开Btool,按左图配置,可以看到右图的信息,这是说明Btool已经识别到了USBDongle。


Btool的界面可以分为4个区:
1. 设备信息展示
2. 历史记录
3. 设备控制
4. 连接信息


    确保周围存在设备可发现,点击Discover/Connect标签的scan按钮,CC2540 就会进行10s的扫描过程,在这期间可通过Cancle按钮停止扫描。


    可以看到,我们周边有两个设备,其中一个就是我们的开发板,根据串口输出的信息我们知道我们设备的地址是0X7C669D9F6297,下面我们点击establish来连接我们的开发板。


连接后可以看到两边都同时显示了连接信息。
    开发板输出连接:



Btool连接的设备信息:


特征值的读写
    接下来我们用Btool对SimpleProfile 进行使用操作。刚刚我们已经列出了SimpleProfile中的各个特征值。

    使用UUID读取特征值,CHAR1具有读写属性,这里对 SimpleProfile 的第一特征值 CHAR1进行读取操作,UUID 为0xfff1。选择 Read/Write 选项页并选择 ReadUsing Characteristic UUID 功能,在Characteristic UUID选项填入f1:ff(高字节在前),点击Read按钮。
    读取特征值成功:



    下面对此特征值进行写入操作,写入操作必须使用Handle值进行,而无法使用UUID来操作,那CHAR1的Handle值的什么呢?其实刚刚在我们读取CHAR1的值的时候就已经获取到了它的Handle。如图,CHAR1的Handle为0x0025。
    CHAR1的Handle值:



    下面我们通过这个Handle对CHAR1写入十进制的10,如图,我们写入成功了。
    写入成功:



    在SimpleBLEPeripheral设备的串口输出中可以看到设备提示CHAR1的值变为了10。


    下面来验证我们是否成功的将CHAR1改为了10,按照刚刚读取CHAR1的步骤,重新读取CHAR1的值。
    CHAR1的值改为了10:



蓝牙点灯
    上面我们已经能够成功的改写一个特征值,那我们是不是可以通过发送特定的值来控制一个灯的亮灭呢?答案是肯定的。下面我们来实现这个功能。

    从机工程已经有5个特征值了,我们现在增加一个特征值来控制灯的亮灭。那我们该如何来添加特征值呢?特征值的管理是在profile中实现的。所以我们需要对profile进行修改。



(1)修改simpleGATTProfile.h

    在simpleGATTProfile.h中可以看到现在定义的5个特征值的标示符和UUID,我们添加一个1Byte的特征值来控制灯的亮灭



    因为simpleGATTProfile是共用的文件,为了不影响其它工程,我们使用一个宏来控制新增加的属性。


    接下来我们需要修改simpleGATTProfile.c,这个文件需要修改的地方较多,下面我们一步一步来修改。
(2)添加UUID


(3)添加属性


(4)属性表


(5)属性设置操作


(6)属性获取操作


(7)属性读操作


(8)属性写操作


    Profile的改造完成后,我们将这个宏打开,配置工程。


    接着我们在staticvoid simpleProfileChangeCB( uint8 paramID )函数的switch中加入CHAR6的判断即可。


    编译烧录后,按照我们前面说的在Btool中对FFF6的UUID进行读写操作即可实现对LED的控制。



使用特权

评论回复
26
andy_mqy| | 2014-7-25 14:22 | 只看该作者
感谢馒头,顶起,学习

使用特权

评论回复
27
firstblood| | 2014-7-25 16:42 | 只看该作者
蓝牙低能耗架构共有两种芯片构成:单模芯片和双模芯片。蓝牙单模器件是蓝牙规范中新出现的一种只支持蓝牙低能耗技术的芯片——是专门针对ULP操作优化的技术的一部分。

使用特权

评论回复
28
gyh974| | 2014-7-25 18:00 | 只看该作者

使用特权

评论回复
29
l4157| | 2014-7-25 22:45 | 只看该作者
看了很久官方的例程,都不得法,谁能救我?

使用特权

评论回复
30
zzq宁静致远|  楼主 | 2014-7-26 08:56 | 只看该作者
l4157 发表于 2014-7-25 22:45
看了很久官方的例程,都不得法,谁能救我?

后面将持续更新协议栈开发案例,蓝牙4.0BLE协议栈开发不会是问题,

使用特权

评论回复
31
zzq宁静致远|  楼主 | 2014-7-26 08:57 | 只看该作者
andy_mqy 发表于 2014-7-25 14:22
感谢馒头,顶起,学习

感谢支持。相信能给你的蓝牙4.0学习带来帮助。

使用特权

评论回复
32
zzq宁静致远|  楼主 | 2014-7-26 09:23 | 只看该作者
第二十二节  蓝牙协议栈之主机通讯
    随着蓝牙4.0模块的大量使用,为了很多从未接触过蓝牙的工程师也能快速便捷地开发蓝牙项目或者使用蓝牙,主从一体、远控IO等等特性也成为蓝牙模块必备的条件。其实,联合第二十一节和本节(第二十二节),我们就能将一个本无固件的裸片蓝牙,使其开发为具备主从一体功能的蓝牙模块。这两节的内容,也是本连载篇的重点部分之一。
    上一节我们对从机的工作流程有了一个整体的把握。我们现在接着来看主机的工作流程。
    主机的工作主要是扫描设备,对发现的设备发起连接,然后就是对特征值的读写操作了。

手动连接
    从机的对外广播是在初始化的时候完成的,那主机的扫描是在哪里开始的呢?阅读源码可以发现主机的操作都在按键处理中完成的。主机通过五向按键中的五个按键实现不同的功能。
static void simpleBLECentral_HandleKeys( uint8 shift, uint8 keys )
{
  (void)shift;  // Intentionally unreferenced parameter

  if ( keys & HAL_KEY_UP )      // 向上
  {
    // Start or stop discovery
    if ( simpleBLEState != BLE_STATE_CONNECTED )    // 如果没有连接,开始扫描
    {
      if ( !simpleBLEScanning )
      {
        simpleBLEScanning = TRUE;
        simpleBLEScanRes = 0;

        LCD_WRITE_STRING( "Discovering...", HAL_LCD_LINE_1 );
        LCD_WRITE_STRING( "", HAL_LCD_LINE_2 );

        GAPCentralRole_StartDiscovery( DEFAULT_DISCOVERY_MODE,
                                       DEFAULT_DISCOVERY_ACTIVE_SCAN,
                                       DEFAULT_DISCOVERY_WHITE_LIST );
      }
      else
      {
        GAPCentralRole_CancelDiscovery();
      }
    }
    else if ( simpleBLEState == BLE_STATE_CONNECTED && // 如果连接并且发现Handle进行读写操作
              simpleBLECharHdl != 0 &&
              simpleBLEProcedureInProgress == FALSE )
    {
      uint8 status;

      // Do a read or write as long as no other read or write is in progress
      if ( simpleBLEDoWrite )
      {
        // Do a write
        attWriteReq_t req;

        req.handle = simpleBLECharHdl;
        req.len = 1;
        req.value[0] = simpleBLECharVal;
        req.sig = 0;
        req.cmd = 0;
        status = GATT_WriteCharValue( simpleBLEConnHandle, &req, simpleBLETaskId );
      }
      else
      {
        // Do a read
        attReadReq_t req;

        req.handle = simpleBLECharHdl;
        status = GATT_ReadCharValue( simpleBLEConnHandle, &req, simpleBLETaskId );
      }

      if ( status == SUCCESS )
      {
        simpleBLEProcedureInProgress = TRUE;
        simpleBLEDoWrite = !simpleBLEDoWrite;
      }
    }
  }

  if ( keys & HAL_KEY_LEFT )    // 左
  {
    // Display discovery results
    if ( !simpleBLEScanning && simpleBLEScanRes > 0 )   // 显示扫描到的设备
    {
        // Increment index of current result (with wraparound)
        simpleBLEScanIdx++;
        if ( simpleBLEScanIdx >= simpleBLEScanRes )
        {
          simpleBLEScanIdx = 0;
        }

        LCD_WRITE_STRING_VALUE( "Device", simpleBLEScanIdx + 1,
                                10, HAL_LCD_LINE_1 );
        LCD_WRITE_STRING( bdAddr2Str( simpleBLEDevList[simpleBLEScanIdx].addr ),
                          HAL_LCD_LINE_2 );
    }
  }

  if ( keys & HAL_KEY_RIGHT )   // 右
  {
    // Connection update
    if ( simpleBLEState == BLE_STATE_CONNECTED )    // 如果连接,则更新连接
    {
      GAPCentralRole_UpdateLink( simpleBLEConnHandle,
                                 DEFAULT_UPDATE_MIN_CONN_INTERVAL,
                                 DEFAULT_UPDATE_MAX_CONN_INTERVAL,
                                 DEFAULT_UPDATE_SLAVE_LATENCY,
                                 DEFAULT_UPDATE_CONN_TIMEOUT );
    }
  }

  if ( keys & HAL_KEY_CENTER )  // 中间键
  {
    uint8 addrType;
    uint8 *peerAddr;

    // Connect or disconnect
    if ( simpleBLEState == BLE_STATE_IDLE )     // 空闲则连接
    {
      // if there is a scan result
      if ( simpleBLEScanRes > 0 )
      {
        // connect to current device in scan result
        peerAddr = simpleBLEDevList[simpleBLEScanIdx].addr;
        addrType = simpleBLEDevList[simpleBLEScanIdx].addrType;

        simpleBLEState = BLE_STATE_CONNECTING;

        GAPCentralRole_EstablishLink( DEFAULT_LINK_HIGH_DUTY_CYCLE,
                                      DEFAULT_LINK_WHITE_LIST,
                                      addrType, peerAddr );

        LCD_WRITE_STRING( "Connecting", HAL_LCD_LINE_1 );
        LCD_WRITE_STRING( bdAddr2Str( peerAddr ), HAL_LCD_LINE_2 );
      }
    }
    else if ( simpleBLEState == BLE_STATE_CONNECTING ||     // 连接则断开连接
              simpleBLEState == BLE_STATE_CONNECTED )
    {
      // disconnect
      simpleBLEState = BLE_STATE_DISCONNECTING;

      gStatus = GAPCentralRole_TerminateLink( simpleBLEConnHandle );

      LCD_WRITE_STRING( "Disconnecting", HAL_LCD_LINE_1 );
    }
  }

  if ( keys & HAL_KEY_DOWN )        // 下
  {
    // Start or cancel RSSI polling
    if ( simpleBLEState == BLE_STATE_CONNECTED )    // 连接则读取RSSi的值
    {
      if ( !simpleBLERssi )
      {
        simpleBLERssi = TRUE;
        GAPCentralRole_StartRssi( simpleBLEConnHandle, DEFAULT_RSSI_PERIOD );
      }
      else
      {
        simpleBLERssi = FALSE;
        GAPCentralRole_CancelRssi( simpleBLEConnHandle );

        LCD_WRITE_STRING( "RSSI Cancelled", HAL_LCD_LINE_1 );
      }
    }
  }
}
    因为从机一直处于广播状态,所以秩序将上一节中的从机程序烧录进开发板即可,然后将主机程序烧录到另外一快开发板,通过五向按键来实现和从机的连接和读写功能。
(1) 上电提示
    从机上电提示:


    主机上电提示:


(2)根据主机的按键功能,我们按“UP”键,开始搜索周边设备。搜索完成后,可以看到,扫描到了一个设备。
    扫描到一个设备:


(3)接着我们查看扫描到的设备地址,按左键。可以看到扫描到的设备地址为0x7C669D9F638A。这个地址正是我们的从机地址。
    显示从机地址:


(4)按中间键连接从机,可以看到主机提示连接成功,从机也提示连接成功。
    主机提示连接成功:


    从机提示连接成功:


(5)接着我们开始读取从机的RSSI值,按下键。


(6)再次按下键,取消RSSI值的读取。


(7)对从机的CHAR1进行读写,再次按上键读取到CHAR1的值为1。



(8)接着按上键,对CHAR1写入0,同时看到从机提示CHAR1的值被修改为0。
    主机写入成功:


    从机提示CHAR1被改变:



上电自动连接
    上一节中我们通过五向按键实现了主机连接从机的功能,这一节中们来实现主机上电后自动搜索连接从机。
    要实现连接,从机必须处于广播状态,剩下的工作全部由主机完成,扫描、发起连接。
    主机的状态也有回调函数,主机启动后,第一个状态是初始化,所以我们在初始化完成时开始扫描,


    这样开机后主机就会开始扫描周边设备,接下来我们在扫描完成后对扫描到的设备发起连接。


    将工程编译下载后通过串口助手观察主机和从机的输出可以发现主机上电后自动的完成了一系列的操作。

    主机提示:


    从机提示:




使用特权

评论回复
33
LingTian| | 2014-7-26 15:34 | 只看该作者

使用特权

评论回复
34
飞言走笔| | 2014-7-28 11:07 | 只看该作者
不错,MARK

使用特权

评论回复
35
飞言走笔| | 2014-7-28 11:08 | 只看该作者
LingTian 发表于 2014-7-26 15:34

看您签名,也在苏州吗?

使用特权

评论回复
36
ecoren| | 2014-7-28 12:43 | 只看该作者
好长的贴

使用特权

评论回复
37
zzq宁静致远|  楼主 | 2014-7-28 18:25 | 只看该作者
第二十三节  OAD空中升级

    通过仿真器更新程序或者通过USB更新固件那都是一般人都可以实现的操作,但是要想实现OAD空中升级,这还是需要一定的技术能力。这一节我就带着大家完善这一能力。
    OAD:on air download,指空中下载模式。当我们的产品发布以后,有时需要对固件进行升级,OAD是升级方式中的一种。

配置BIM(Boot Image Manger)
    打开IAR,打开BLE-CC254x-1.4.0下的工程C:\TexasInstruments\BLE-CC254x-1.4.0\Projects\ble\util\BIM\cc254x\BIM.eww,然后编译,下载到开发板中。

配置Image A
    打开OADExample工程,配置工程,添加如下几个宏
    FEATURE_OAD_BIM
    HAL_IMAGE_A
    FEATURE_OAD
    OAD_KEEP_NV_PAGES


(1)  打开simplePeripheral.c找到宏定义DEFAULT_ENABLE_UPDATE_REQUEST,将其改为FLASE。否则会影响BLEDevice Monitor对其进行空中升级,到时可以再改回来。



(2)  IAR左侧导航中找到Profile文件夹,点击右键添加oad_target.c和oad_target.h两个文件,二文件位于C:\TexasInstruments\BLE-CC254x-1.4.0\Projects\ble\Profiles\OAD中。

(3)  在IAR导航中找到HAL→Target→MT254xboard→Driver,右键添加文件hal_crc.c,该文件位于C:\TexasInstruments\BLE-CC254x-1.4.0\Components\hal\target\MT254xboard中。


(4)  在刚才的simplePeripheral.c文件中,找到simplePeripheral_Init()函数,在里面添加OADTarget_AddService()函数。


(5)  在simplePeripheral.c的前面,添加引用OAD的头文件,OAD.h和OAD_target.h。

(6)  点击Project→Option…,或直接按Alt+F7,选择BuildActions,在Post-buildcommand line中添加:
"$PROJ_DIR$\..\..\common\cc2540\cc254x_ubl_pp.bat""$PROJ_DIR$" "ProdUBL""$PROJ_DIR$\CC2540-OAD-ImgA\Exe\OADExample"
注意,各双引号之间只有一个空格。注意图中红框标的部分,Image_A是和之前第1步对应的。


(7)  点击Project→Option…,或直接按Alt+F7,选择Linker,选择Config,Linker Configurationfile中勾选Override default,添加C:\TexasInstruments\BLE-CC254x-1.4.0\Projects\ble\common\cc2540\cc254x_f256_imgA.xcl。

(8)  点击Project→Option…,或直接按Alt+F7,选择Linker,选择Extra Output。


(9)  Extra Option添加Hex文件的输出。


(10)  点击OK,编译下载,如果出现如下错误,是因为我们使用的是IAR8051 8.30版本,如果你使用的是IAR8.20版本就没有这问题。



(11)  这个问题是因为使用了虚拟寄存器导致的,我们找到如下文件。



(12)  对文件的115行进行如下修改,将虚拟寄存器注释掉。


(13)  编译后,可以看到我们生成的文件



(14)  我们将hex文件叠加到BIM后面



    这样ImageA就成功烧录进开发板了。



配置Image B
(1)  方法如Image A,其它要注意的几个地方:点击Project→Option…,或直接按Alt+F7,选择C/C++Compiler,选择Preprocessor,将Defined symbols中的HAL_IMAGE_A改成B。



(2)  点击Project→Option…,或直接按Alt+F7,选择Linker,选择Config,将Linker Configuration file中的文件改为B。



(3)  保存后编译,同样的可以看到生成的文件。


(4)  为了区分A和B镜像,我们将最终结果改名。



空中升级
    有了bin文件就可以进行空中升级了,打开BLE Device Monitor(没有安装的需要安装),打开后软件会自动扫描设备,如图,我们扫描到了开发板。



(2)  连接后,打开OAD



(3)  点击file,选择Progame(OAD),选择生成的bin文件,可以看到我们当前运行的是A固件



(4)  点击start,当进行到100%,就完成了固件空中升级。



(5)  升级后再次打开OAD选项,可以看到选择运行的固件已经是B版本的了。



注意:
要把 BLE协议栈BLE-CC254x-1.4.0 安装在C盘,在其他盘符下没有生成bin文件。



使用特权

评论回复
38
vavsz003| | 2014-7-29 17:09 | 只看该作者
好贴,顶了,深奥的知识我是来学习的。

使用特权

评论回复
39
zzq宁静致远|  楼主 | 2014-7-30 11:41 | 只看该作者
第二十四节  SBL串口升级
    SBL升级和OAD升级的配置步骤都是一样的,主要是配置的参数不一样,下面我们来配置一个SBL升级的固件。
配置SBL
    打开IAR,打开BLE-CC254x-1.4.0下的工程C:\TexasInstruments\BLE-CC254x-1.4.0\Projects\ble\util\SBL\iar\cc254x\sbl.eww,然后编译,下载到开发板中。


配置Bin文件
(1)  添加宏
    MAKE_CRC_SHDW
    FEATURE_SBL
    OAD_KEEP_NV_PAGES


(2)  添加build选项
"$PROJ_DIR$\..\..\common\cc2540\cc254x_ubl_pp.bat""$PROJ_DIR$" "ProdUBL" "$PROJ_DIR$\MT254xboard\Exe\SBLExample"


(3)  Config选项



(4)  Extra Output选项


(5)  保存编译


(6)  打开串口升级软件SerialBootTool.exe,选择SBLExample.bin文件。

(7)  Load Image



    这样我们的SBL固件就制作完毕了。

使用特权

评论回复
40
apolloalfred| | 2014-7-31 10:08 | 只看该作者
真是好资料,正好在学习这个,谢谢分享!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则