沁恒蓝牙芯片CH58x系列学习与应用指南

文章目录

  • 前言
  • 一、CH58X_BLEInit
  • 二 、HAL_Init
  • ①SYS_EVENT_MSG
  • ②LED_BLINK_EVENT
  • ③HAL_KEY_EVENT
  • ④HAL_REG_INIT_EVENT
  • ⑤HAL_TEST_EVENT
  • 三、GAPRole_CentralInit
  • 四、Central_Init
  • ①SYS_EVENT_MSG
  • ②START_DEVICE_EVT
  • ③ESTABLISH_LINK_TIMEOUT_EVT
  • ④START_SVC_DISCOVERY_EVT
  • ⑤START_PARAM_UPDATE_EVT
  • ⑥START_PHY_UPDATE_EVT
  • ⑦START_READ_OR_WRITE_EVT
  • ⑧START_WRITE_CCCD_EVT
  • ⑨START_READ_RSSI_EVT

  • 前言

    沁恒蓝牙芯片CH58x系列学习与应用:

    在前人的基础上补充一个沁恒CH85x系列蓝牙central例程的记录
    注:我是用的是沁恒的CH582M芯片,IDE是Mounriver Studio
    因作者能力有限,文中难免存在错误,请不吝指正!

    沁恒的外设从机例程Peripheral原文:
    沁恒蓝牙芯片CH57x系列学习与应用


    头文件包含:

    #include "CONFIG.h"
    #include "hal.h"
    #include "central.h"
    

    全局

    //GLOBAL TYPEDEFS
    __attribute__((aligned(4))) uint32_t MEM_BUF[BLE_MEMHEAP_SIZE / 4];  // 4字节对齐
    #if(defined(BLE_MAC)) && (BLE_MAC == TRUE)                           // 不启用
    const uint8_t MacAddr[6] = {0x84, 0xC2, 0xE4, 0x03, 0x02, 0x02};
    #endif
    

    循环

    /*********************************************************************
     * @fn      Main_Circulation
     *
     * @brief   循环
     *
     * @return  none
     */
    __HIGH_CODE            // 将作用的函数或数据放入指定名为"section_name"输入段(没搞明白)
    __attribute__((noinline))             // 声明为非内联函数
    void Main_Circulation()
    {
        while(1)
        {
            TMOS_SystemProcess();         // 执行系统处理
        }
    }
    

    Central例程的主函数如下所示:

    int main(void)
    {
    #if(defined(DCDC_ENABLE)) && (DCDC_ENABLE == TRUE)
        PWR_DCDCCfg(ENABLE);
    #endif
        SetSysClock(CLK_SOURCE_PLL_60MHz);
    #if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
        GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
        GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
    #endif
    #ifdef DEBUG
        GPIOA_SetBits(bTXD1);
        GPIOA_ModeCfg(bTXD1, GPIO_ModeOut_PP_5mA);
        UART1_DefInit();
    #endif
        PRINT("%s\n", VER_LIB);
        CH58X_BLEInit();		// BLE库初始化
        HAL_Init();				// 硬件初始化
        GAPRole_CentralInit();
        Central_Init();
        Main_Circulation();
    }
    

    一、CH58X_BLEInit

    首先来看蓝牙的库初始化配置:

    void CH58X_BLEInit(void)
    {
        uint8_t     i;
        bleConfig_t cfg;
        if(tmos_memcmp(VER_LIB, VER_FILE, strlen(VER_FILE)) == FALSE) // 其中函数是对利用变量实现对当前芯片与库文件的是否匹配
        {
            PRINT("head file error...\n");
            while(1);
        }
        SysTick_Config(SysTick_LOAD_RELOAD_Msk);        // 系统时钟的配置
        PFIC_DisableIRQ(SysTick_IRQn);                  // 中断不使能
    
        tmos_memset(&cfg, 0, sizeof(bleConfig_t));      // 设置清0    通过配置参数bleConfig_t配置库的内存,时钟,发射功率等参数
        cfg.MEMAddr = (uint32_t)MEM_BUF;
        cfg.MEMLen = (uint32_t)BLE_MEMHEAP_SIZE;
        cfg.BufMaxLen = (uint32_t)BLE_BUFF_MAX_LEN;
        cfg.BufNumber = (uint32_t)BLE_BUFF_NUM;
        cfg.TxNumEvent = (uint32_t)BLE_TX_NUM_EVENT;
        cfg.TxPower = (uint32_t)BLE_TX_POWER;
    #if(defined(BLE_SNV)) && (BLE_SNV == TRUE)
        cfg.SNVAddr = (uint32_t)BLE_SNV_ADDR;
        cfg.readFlashCB = Lib_Read_Flash;
        cfg.writeFlashCB = Lib_Write_Flash;
    #endif
    #if(CLK_OSC32K)
        cfg.SelRTCClock = (uint32_t)CLK_OSC32K;
    #endif
        cfg.ConnectNumber = (PERIPHERAL_MAX_CONNECTION & 3) | (CENTRAL_MAX_CONNECTION << 2);
        cfg.srandCB = SYS_GetSysTickCnt;
    #if(defined TEM_SAMPLE) && (TEM_SAMPLE == TRUE)
        cfg.tsCB = HAL_GetInterTempValue; // 根据温度变化校准RF和内部RC( 大于7摄氏度 )
      #if(CLK_OSC32K)
        cfg.rcCB = Lib_Calibration_LSI; // 内部32K时钟校准
      #endif
    #endif
    #if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
        cfg.WakeUpTime = WAKE_UP_RTC_MAX_TIME;
        cfg.sleepCB = CH58X_LowPower; // 启用睡眠
    #endif
    #if(defined(BLE_MAC)) && (BLE_MAC == TRUE)
        for(i = 0; i < 6; i++)
        {
            cfg.MacAddr[i] = MacAddr[5 - i];
        }
    #else
        {
            uint8_t MacAddr[6];
            GetMACAddress(MacAddr);
            for(i = 0; i < 6; i++)
            {
                cfg.MacAddr[i] = MacAddr[i]; // 使用芯片mac地址
            }
        }
    #endif
        if(!cfg.MEMAddr || cfg.MEMLen < 4 * 1024)       // 蓝牙地址是否成功配置,否则进去空循环
        {
            while(1);
        }
        i = BLE_LibInit(&cfg);                // 通过BLE_LibInit()函数将配置参数传入库中
        if(i)                                 // 对总体的库配置是否完成进行判断,当配置成功,则返回0值。反之则返回1,通过串口报错,并陷入空循环之中。
        {
            PRINT("LIB init error code: %x ...\n", i);
            while(1);
        }
    }
    

    其中函数是对利用变量实现对当前芯片与库文件的是否匹配的检查,如果不匹配,则执行空循环,并打印“head file error…”,通过串口进行通报:

    tmos_memcmp( VER_LIB, VER_FILE, strlen( VER_FILE ) ) 
    

    后续两个函数分别是对系统时钟的配置和中断的不使能:

    SysTick_Config( SysTick_LOAD_RELOAD_Msk );
    PFIC_DisableIRQ( SysTick_IRQn );
    

    之后再对BLE的库进行配置。

    ​ 先是对所有变量进行清零,然后重新配置:

     tmos_memset(&cfg, 0, sizeof(bleConfig_t));      // 设置清0    通过配置参数bleConfig_t配置库的内存,时钟,发射功率等参数
        cfg.MEMAddr = (uint32_t)MEM_BUF;
        cfg.MEMLen = (uint32_t)BLE_MEMHEAP_SIZE;
        cfg.BufMaxLen = (uint32_t)BLE_BUFF_MAX_LEN;
        cfg.BufNumber = (uint32_t)BLE_BUFF_NUM;
        cfg.TxNumEvent = (uint32_t)BLE_TX_NUM_EVENT;
        cfg.TxPower = (uint32_t)BLE_TX_POWER;
    #if(defined(BLE_SNV)) && (BLE_SNV == TRUE)
        cfg.SNVAddr = (uint32_t)BLE_SNV_ADDR;
        cfg.readFlashCB = Lib_Read_Flash;
        cfg.writeFlashCB = Lib_Write_Flash;
    #endif
    #if(CLK_OSC32K)
        cfg.SelRTCClock = (uint32_t)CLK_OSC32K;
    #endif
        cfg.ConnectNumber = (PERIPHERAL_MAX_CONNECTION & 3) | (CENTRAL_MAX_CONNECTION << 2);
        cfg.srandCB = SYS_GetSysTickCnt;
    #if(defined TEM_SAMPLE) && (TEM_SAMPLE == TRUE)
        cfg.tsCB = HAL_GetInterTempValue; // 根据温度变化校准RF和内部RC( 大于7摄氏度 )
      #if(CLK_OSC32K)
        cfg.rcCB = Lib_Calibration_LSI; // 内部32K时钟校准
      #endif
    #endif
    #if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
        cfg.WakeUpTime = WAKE_UP_RTC_MAX_TIME;
        cfg.sleepCB = CH58X_LowPower; 	// 启用睡眠
    #endif
    

    其中,BLE库的结构体中的变量的具体含义如下所示:

    typedef struct tag_ble_config
    {
        uint32_t MEMAddr;               // 库内存起始地址__attribute __((at(0x20003800)))library memory start address
        uint16_t MEMLen;                // 库内存大小library memory size
        uint32_t SNVAddr;               // SNV闪存启动地址,必须为dataflash区域或NULL(绑定信息将不被保存)SNV flash start address( if NULL,bonding information will not be saved )
        uint16_t SNVBlock;              // SNV闪存块大小(默认256)SNV flash block size ( default 256 )
        uint8_t SNVNum;                 // SNV闪存块号(默认为1)SNV flash block number ( default 1 )
        uint8_t BufNumber;              // 控制器缓存的最大发送和接收包数(默认为5)Maximum number of sent and received packages cached by the controller( default 5 )
                                        // 必须大于连接数Must be greater than the number of connections.
        uint16_t BufMaxLen;             // 支持的最大八位字节,范围27-251,ATT_MTU = BufMaxLen-4(默认27)Maximum length (in octets) of the data portion of each HCI data packet( default 27 )
                                        // SC enable,must be greater than 69
                                        // ATT_MTU = BufMaxLen-4,Range[23,ATT_MAX_MTU_SIZE]
        uint8_t TxNumEvent;             // 连接事件中的最大TX数据数(默认为1)Maximum number of TX data in a connection event ( default 1 )
        uint8_t RxNumEvent;             // Maximum number of RX data in a connection event ( default equal to BufNumber )
        uint8_t TxPower;                // 发射功率Transmit power level( default LL_TX_POWEER_0_DBM(0dBm) )
        uint8_t WakeUpTime;             // 一个RTC计数中的唤醒时间值(默认值为80)Wake up time value in one system count
        uint8_t SelRTCClock;            // RTC时钟选择LSE,LSI(32768Hz或32000Hz)(默认值:0 LSE,1:LSI(32000Hz),2:LSI(32768Hz))system clock select
                                        // bit0-1 00: LSE(32768Hz) 01:LSI(32000Hz) 10:LSI(32768Hz)
                                        // bit7:  1: ble timer(HSE)(must disable sleep)
        uint8_t ConnectNumber;          // 连接号,后两位为外设号,后跟中心号Connect number,lower two bits are peripheral number,followed by central number
        uint8_t WindowWidening;         // 等待射频启动窗口Wait rf start window(us)
        uint8_t WaitWindow;             // 等待连接事件到达窗口Wait event arrive window in one system clock
        uint8_t MacAddr[6];             // MAC地址,小端(出厂默认)MAC address,little-endian
        pfnSrandCB srandCB;             // 注册生成随机种子的程序Register a program that generate a random seed
        pfnIdleCB sleepCB;              // 注册设置空闲模式的程序Register a program that set idle
        pfnTempSampleCB tsCB;           // 注册一个读取当前温度的程序,确定是否需要校准Register a program that read the current temperature,determine whether calibration is need
        pfnLSICalibrationCB rcCB;       // 注册一个程序以进行RC32K时钟校准Register a program that LSI clock calibration
        pfnLibStatusErrorCB staCB;      // 注册一个程序库状态回调Register a program that library status callback
        pfnFlashReadCB readFlashCB;     // 注册一个读取flash的程序Register a program that read flash
        pfnFlashWriteCB writeFlashCB;   // 注册写闪存的程序Register a program that write flash
    } bleConfig_t; // Library initialization call BLE_LibInit function
    

    其中,对芯片的蓝牙地址的定义有两种方法,一个是自己设置,另一个是使用芯片的mac地址,具体函数如下所示:

    #if(defined(BLE_MAC)) && (BLE_MAC == TRUE)
        for(i = 0; i < 6; i++)
        {
            cfg.MacAddr[i] = MacAddr[5 - i];
        }
    #else
        {
            uint8_t MacAddr[6];
            GetMACAddress(MacAddr);
            for(i = 0; i < 6; i++)
            {
                cfg.MacAddr[i] = MacAddr[i]; // 使用芯片mac地址
            }
        }
    

    通过对变量BLE_MAC的值来进行选择蓝牙模块使用的地址是自己设置的地址还是芯片的地址。同时,最后还有对蓝牙地址是否成功配置结束的判断,如果未成功,则陷入空循环之中。

    ​ 同样的,在配置完BLE的库之后,也要对总体的库配置是否完成进行判断,这里是利用了BLE_LibInit()函数,当配置成功,则返回0值。反之则返回1,通过串口报错,并陷入空循环之中

     if(!cfg.MEMAddr || cfg.MEMLen < 4 * 1024)       // 蓝牙地址是否成功配置,否则进去空循环
        {
            while(1);
        }
        i = BLE_LibInit(&cfg);                // 通过BLE_LibInit()函数将配置参数传入库中
        if(i)                                 // 对总体的库配置是否完成进行判断,当配置成功,则返回0值。反之则返回1,通过串口报错,并陷入空循环之中。
        {
            PRINT("LIB init error code: %x ...\n", i);
            while(1);
        }
    

    二 、HAL_Init

    HAL_Init()函数是硬件初始化函数。函数主体如下所示:

    void HAL_Init()   // 注册HAL层任务,对硬件参数进行初始化,比如RTC的时钟,睡眠唤醒,RF校准等
    {
        halTaskID = TMOS_ProcessEventRegister(HAL_ProcessEvent);
        HAL_TimeInit();                                             // 系统定时器初始化
    #if(defined HAL_SLEEP) && (HAL_SLEEP == TRUE)
        HAL_SleepInit();
    #endif
    #if(defined HAL_LED) && (HAL_LED == TRUE)
        HAL_LedInit();
    #endif
    #if(defined HAL_KEY) && (HAL_KEY == TRUE)
        HAL_KeyInit();
    #endif
    #if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE)
        tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD)); // 添加校准任务,单次校准耗时小于10ms
    #endif
        tmos_start_task( halTaskID, HAL_TEST_EVENT, 1600 );    // 添加一个测试任务
    }
    

    HAL_Init()函数主要只执行了系统时间配置和校准任务的添加。

    ​ HAL_TimeInit()为系统定时器初始化,函数如下所示:

    void HAL_TimeInit(void)
    {
    #if(CLK_OSC32K)
        sys_safe_access_enable();
        R8_CK32K_CONFIG &= ~(RB_CLK_OSC32K_XT | RB_CLK_XT32K_PON);
        sys_safe_access_enable();
        R8_CK32K_CONFIG |= RB_CLK_INT32K_PON;
        sys_safe_access_disable();
        Lib_Calibration_LSI();
    #else
        sys_safe_access_enable();
        R8_CK32K_CONFIG |= RB_CLK_OSC32K_XT | RB_CLK_INT32K_PON | RB_CLK_XT32K_PON;
        sys_safe_access_disable();
    #endif
        RTC_InitTime(2020, 1, 1, 0, 0, 0); //RTC时钟初始化当前时间
        TMOS_TimerInit(0);
    }
    

    剩下还有两个函数,主要如下:

    halTaskID = TMOS_ProcessEventRegister( HAL_ProcessEvent );
    	#if ( defined BLE_CALIBRATION_ENABLE ) && ( BLE_CALIBRATION_ENABLE == TRUE )
    tmos_start_task( halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD ) );    // 添加校准任务,单次校准耗时小于10ms
    	#endif
    

    通过利用TMOS_ProcessEventRegister()函数来获取目标时间内的任务ID。同时tmos_start_task()函数通过配置当前任务ID,校准任务事件以及事件的执行时间间隔来完成当前事件的开始。具体函数如下所示:

    tmosEvents HAL_ProcessEvent(tmosTaskID task_id, tmosEvents events)
    {
        uint8_t *msgPtr;
    
        if(events & SYS_EVENT_MSG)
        { // 处理HAL层消息,调用tmos_msg_receive读取消息,处理完成后删除消息。
            msgPtr = tmos_msg_receive(task_id);
            if(msgPtr)
            {
                /* De-allocate */
                tmos_msg_deallocate(msgPtr);
            }
            return events ^ SYS_EVENT_MSG;
        }
        if(events & LED_BLINK_EVENT)
        {
    #if(defined HAL_LED) && (HAL_LED == TRUE)
            HalLedUpdate();
    #endif // HAL_LED
            return events ^ LED_BLINK_EVENT;
        }
        if(events & HAL_KEY_EVENT)
        {
    #if(defined HAL_KEY) && (HAL_KEY == TRUE)
            HAL_KeyPoll(); /* Check for keys */
            tmos_start_task(halTaskID, HAL_KEY_EVENT, MS1_TO_SYSTEM_TIME(100));
            return events ^ HAL_KEY_EVENT;
    #endif
        }
        if(events & HAL_REG_INIT_EVENT)
        {
    #if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE) // 校准任务,单次校准耗时小于10ms
            BLE_RegInit();                                                  // 校准RF
      #if(CLK_OSC32K)
            Lib_Calibration_LSI(); // 校准内部RC
      #endif
            tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD)); // 同时执行了tmos_start_task()函数,使得HAL_REG_INIT_EVENT时间执行的间隔时间为0.192s。
            return events ^ HAL_REG_INIT_EVENT;
    #endif
        }
        if(events & HAL_TEST_EVENT)  // TEST事件只是通过串口打印作为通报,同时开始测试事件,并设置间隔时间为16ms。
        {
            PRINT("TEST事件  \r\n");
            tmos_start_task(halTaskID, HAL_TEST_EVENT, MS1_TO_SYSTEM_TIME(1000));
            return events ^ HAL_TEST_EVENT;
        }
        return 0;
    }
    

    每个task下拥有的event,16bit,每bit代表一个event,对于同一个task,所以一共会有16个事件。

    ①SYS_EVENT_MSG

        if(events & SYS_EVENT_MSG)
        { // 处理HAL层消息,调用tmos_msg_receive读取消息,处理完成后删除消息。
            msgPtr = tmos_msg_receive(task_id);
            if(msgPtr)
            {
                /* De-allocate */
                tmos_msg_deallocate(msgPtr);
            }
            return events ^ SYS_EVENT_MSG;
        }
    

    其中分别利用tmos_msg_receive()函数来获取消息,当读取到消息时,再利用tmos_msg_deallocate()来对信息进行删除。

    同时也可以发现,在整个函数中,判断条件是events与SYS_EVENT_MSG,对于SYS_EVENT_MSG这些事件而言,只有一位是1,其他位都为0。而最后又与该变量进行异或,则会对该位进行清零,达到只运行一次该事件的处理与配置。

    ②LED_BLINK_EVENT

      if ( events & LED_BLINK_EVENT )
      {
    #if (defined HAL_LED) && (HAL_LED == TRUE)
        HalLedUpdate( );
    #endif // HAL_LED
        return events ^ LED_BLINK_EVENT;
      }
    

    HalLedUpdate()是对LED设备时间的更新。

    但是本例程中,由于HAL_LED的宏定义为FALSE,使得该事件中并没有执行该函数。

    ③HAL_KEY_EVENT

    if(events & HAL_KEY_EVENT)
        {
    #if(defined HAL_KEY) && (HAL_KEY == TRUE)
            HAL_KeyPoll(); /* Check for keys */
            tmos_start_task(halTaskID, HAL_KEY_EVENT, MS1_TO_SYSTEM_TIME(100));
            return events ^ HAL_KEY_EVENT;
    #endif
        }
    

    其中HAL_KeyPoll()是按键检测。

    tmos_start_task()是在TMOS系统中很重要的函数,他共有三个参数,分别是taskID,event和time,也就是当前任务的ID,当前任务的事件和事件执行间隔时间。而本例中,就是执行硬件任务中的按键事件。

    MS1_TO_SYSTEM_TIME()函数则是一个时间计算函数,具体定义如下所示

    #define SYSTEM_TIME_MICROSEN     625                               // unit of process event timer is 625us
    #define MS1_TO_SYSTEM_TIME(x)    ((x)*1000/SYSTEM_TIME_MICROSEN)   // transform unit in ms to unit in 625us ( attentional bias )
    

    按照计算,则可以计算出按键事件的间隔时间时160us。

    ④HAL_REG_INIT_EVENT

    if(events & HAL_REG_INIT_EVENT)
        {
    #if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE) // 校准任务,单次校准耗时小于10ms
            BLE_RegInit();                                                  // 校准RF
      #if(CLK_OSC32K)
            Lib_Calibration_LSI(); // 校准内部RC
      #endif
            tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD)); // 同时执行了tmos_start_task()函数,使得HAL_REG_INIT_EVENT时间执行的间隔时间为0.192s。
            return events ^ HAL_REG_INIT_EVENT;
    

    BLE_RegInit()函数和Lib_Calibration_LSI()函数分别是BLE寄存器复位,射频校准和校准内部RC。

    不过由于CLK_OSC32K的宏定义,所以并没有执行内部校准函数。

    同时执行了tmos_start_task()函数,使得HAL_REG_INIT_EVENT时间执行的间隔时间为0.192s。

    ⑤HAL_TEST_EVENT

     if ( events & HAL_TEST_EVENT )
      {
        PRINT( "* " );
        tmos_start_task( halTaskID, HAL_TEST_EVENT, MS1_TO_SYSTEM_TIME( 10000 ) );
        return events ^ HAL_TEST_EVENT;
      }
    

    TEST事件只是通过串口打印“*”作为通报,同时开始测试事件,并设置间隔时间为16ms。

    三、GAPRole_CentralInit

    GAPRole_CentralInit()函数是主设备配置文件任务初始化功能。

    四、Central_Init

    Central_Init()函数是针对Peripheral工程的外围应用程序功能的初始化。函数主体如下所示:

    void Central_Init()
    {
        centralTaskId = TMOS_ProcessEventRegister(Central_ProcessEvent);
    
        // Setup GAP
        GAP_SetParamValue(TGAP_DISC_SCAN, DEFAULT_SCAN_DURATION);                       // 执行扫描的最短时间。将此参数设置为0将关闭超时。 1.5s
        GAP_SetParamValue(TGAP_CONN_EST_INT_MIN, DEFAULT_MIN_CONNECTION_INTERVAL);      // 最小链路层连接间隔   20*1.25ms
        GAP_SetParamValue(TGAP_CONN_EST_INT_MAX, DEFAULT_MAX_CONNECTION_INTERVAL);      // 最小链路层连接间隔   100*1.25ms
        GAP_SetParamValue(TGAP_CONN_EST_SUPERV_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);    // 连接监控超时              100*10ms
    
        // Setup the GAP Bond Manager
        {
            uint32_t passkey = DEFAULT_PASSCODE;
            uint8_t  pairMode = DEFAULT_PAIRING_MODE;
            uint8_t  mitm = DEFAULT_MITM_MODE;
            uint8_t  ioCap = DEFAULT_IO_CAPABILITIES;
            uint8_t  bonding = DEFAULT_BONDING_MODE;
    
            GAPBondMgr_SetParameter(GAPBOND_CENT_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey);
            GAPBondMgr_SetParameter(GAPBOND_CENT_PAIRING_MODE, sizeof(uint8_t), &pairMode);
            GAPBondMgr_SetParameter(GAPBOND_CENT_MITM_PROTECTION, sizeof(uint8_t), &mitm);
            GAPBondMgr_SetParameter(GAPBOND_CENT_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);
            GAPBondMgr_SetParameter(GAPBOND_CENT_BONDING_ENABLED, sizeof(uint8_t), &bonding);
        }
        // Initialize GATT Client
        GATT_InitClient();
        // 注册以接收传入的ATT指示/通知  Register to receive incoming ATT Indications/Notifications
        GATT_RegisterForInd(centralTaskId);
        // 设置延迟的配置文件启动 Setup a delayed profile startup
        tmos_set_event(centralTaskId, START_DEVICE_EVT);
    }
    

    与硬件初始化类似,先是利用TMOS_ProcessEventRegister()函数来获取当前任务的ID。

    其中任务函数Central_ProcessEvent()主要内容如下所示:

    uint16_t Central_ProcessEvent(uint8_t task_id, uint16_t events)
    {
        if(events & SYS_EVENT_MSG)
        {
            uint8_t *pMsg;
    
            if((pMsg = tmos_msg_receive(centralTaskId)) != NULL)
            {
                central_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg);                           
                // Release the TMOS message
                tmos_msg_deallocate(pMsg);
            }
            // return unprocessed events
            return (events ^ SYS_EVENT_MSG);
        }
    
        if(events & START_DEVICE_EVT)
        {
            // Start the Device
            GAPRole_CentralStartDevice(centralTaskId, &centralBondCB, &centralRoleCB);      
            return (events ^ START_DEVICE_EVT);
        }
    
        if(events & ESTABLISH_LINK_TIMEOUT_EVT)
        {
            GAPRole_TerminateLink(INVALID_CONNHANDLE);                                      // 终止现有链接
            return (events ^ ESTABLISH_LINK_TIMEOUT_EVT);
        }
    
        if(events & START_SVC_DISCOVERY_EVT)
        {
            // 启动服务发现 start service discovery
            centralStartDiscovery();
            return (events ^ START_SVC_DISCOVERY_EVT);
        }
    
        if(events & START_PARAM_UPDATE_EVT)
        {
            // start connect parameter update
            GAPRole_UpdateLink(centralConnHandle,
                               DEFAULT_UPDATE_MIN_CONN_INTERVAL,
                               DEFAULT_UPDATE_MAX_CONN_INTERVAL,
                               DEFAULT_UPDATE_SLAVE_LATENCY,
                               DEFAULT_UPDATE_CONN_TIMEOUT);
            return (events ^ START_PARAM_UPDATE_EVT);
        }
    
        if(events & START_PHY_UPDATE_EVT)
        {
            // start phy update
            PRINT("PHY Update %x...\n", GAPRole_UpdatePHY(centralConnHandle, 0, 
                        GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, GAP_PHY_OPTIONS_NOPRE));
    
            return (events ^ START_PHY_UPDATE_EVT);
        }
    
        if(events & START_READ_OR_WRITE_EVT)
        {
            if(centralProcedureInProgress == FALSE)
            {
                if(centralDoWrite)
                {
                    // Do a write
                    attWriteReq_t req;
    
                    req.cmd = FALSE;
                    req.sig = FALSE;
                    req.handle = centralCharHdl;
                    req.len = 1;
                    req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
                    if(req.pValue != NULL)
                    {
                        *req.pValue = centralCharVal;
    
                        if(GATT_WriteCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                        {
                            centralProcedureInProgress = TRUE;
                            centralDoWrite = !centralDoWrite;
                            tmos_start_task(centralTaskId, START_READ_OR_WRITE_EVT, DEFAULT_READ_OR_WRITE_DELAY);
                        }
                        else
                        {
                            GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
                        }
                    }
                }
                else
                {
                    // Do a read
                    attReadReq_t req;
    
                    req.handle = centralCharHdl;
                    if(GATT_ReadCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                    {
                        centralProcedureInProgress = TRUE;
                        centralDoWrite = !centralDoWrite;
                    }
                }
            }
            return (events ^ START_READ_OR_WRITE_EVT);
        }
    
        if(events & START_WRITE_CCCD_EVT)
        {
            if(centralProcedureInProgress == FALSE)
            {
                // Do a write
                attWriteReq_t req;
    
                req.cmd = FALSE;
                req.sig = FALSE;
                req.handle = centralCCCDHdl;
                req.len = 2;
                req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
                if(req.pValue != NULL)
                {
                    req.pValue[0] = 1;
                    req.pValue[1] = 0;
    
                    if(GATT_WriteCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                    {
                        centralProcedureInProgress = TRUE;
                    }
                    else
                    {
                        GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
                    }
                }
            }
            return (events ^ START_WRITE_CCCD_EVT);
        }
    
        if(events & START_READ_RSSI_EVT)
        {
            GAPRole_ReadRssiCmd(centralConnHandle);
            tmos_start_task(centralTaskId, START_READ_RSSI_EVT, DEFAULT_RSSI_PERIOD);
            return (events ^ START_READ_RSSI_EVT);
        }
    
        // Discard unknown events
        return 0;
    }
    

    ①SYS_EVENT_MSG

    SYS_EVENT_MSG也是处理相应消息,读取消息和删除消息。

     if(events & SYS_EVENT_MSG)
        {
            uint8_t *pMsg;
    
            if((pMsg = tmos_msg_receive(centralTaskId)) != NULL)
            {
                central_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg); 
                // Release the TMOS message
                tmos_msg_deallocate(pMsg);
            }
            // return unprocessed events
            return (events ^ SYS_EVENT_MSG);
        }
    

    数据发送成功后,SYS_EVENT_MSG被置为有效,此时系统将执行SYS_EVENT_MSG事件,在事件中通过调用tmos_msg_receive()函数检索数据。在数据处理完成之后,必须使用tmos_msg_deallocate函数释放内存。

    假设将消息发送给central的任务ID,central的系统事件将会接收此消息。

    查询到GATT_MSG_EVENT的事件:

    static void central_ProcessTMOSMsg(tmos_event_hdr_t *pMsg)
    {
        switch(pMsg->event)
        {
            case GATT_MSG_EVENT:
                centralProcessGATTMsg((gattMsgEvent_t *)pMsg);
                break;
        }
    }
    

    ②START_DEVICE_EVT

    这个事件只有一个主要的函数,就是GAPRole_CentralStartDevice()函数,他的作用是判断是否对设备进行了初始化,同时GAPRole_CentralStartDevice()函数也调用了其他的函数来进行初始化,也就是说初始化完成之后,返回值SUCESS。

        if(events & START_DEVICE_EVT)
        {
            // Start the Device
            GAPRole_CentralStartDevice(centralTaskId, &centralBondCB, &centralRoleCB);      
            return (events ^ START_DEVICE_EVT);
        }
    

    GAPRole_CentralStartDevice函数声明如下所示:

    extern bStatus_t GAPRole_CentralStartDevice( uint8_t taskid, gapBondCBs_t *pCB, gapCentralRoleCB_t *pAppCallbacks );
    

    函数总共三个参数。其中第一个是当前任务的ID,另外两个则是两个结构体。

    gapBondCBs_t结构体如下所示:

    typedef struct
    {
      pfnPasscodeCB_t     passcodeCB;     //!< 密码回调
      pfnPairStateCB_t    pairStateCB;    //!< 配对状态回调
    } gapBondCBs_t;
    

    该结构体中又包含了两个结构体,分别用作密码回调和配对状态回调,分别如下所示:

    typedef void (*pfnPasscodeCB_t)( uint8_t *deviceAddr, //!< address of device to pair with, and could be either public or random.
        uint16_t connectionHandle,      //!< Connection handle
        uint8_t uiInputs,              //!< Pairing User Interface Inputs - Ask user to input passcode
        uint8_t uiOutputs              //!< Pairing User Interface Outputs - Display passcode
        );
    

    deviceAddr是要配对。设备的地址,通过指针运算符,取得该地址下的设备地址;

    connectionHandle是连接处理;

    uiInputs是配对用户界面输入-要求用户输入密码;

    uiOutputs是配对用户界面输出-显示密码。

    typedef void (*pfnPairStateCB_t)
    (
      uint16 connectionHandle,              //!< Connection handle
      uint8  state,                         //!< Pairing state @ref GAPBOND_PAIRING_STATE_DEFINES
      uint8  status                         //!< Pairing statusc
    );
    

    而gapRolesCBs_t结构体中包含了三个函数

    typedef struct
    {
        gapRolesStateNotify_t pfnStateChange; //!< Whenever the device changes state
        gapRolesRssiRead_t pfnRssiRead; //!< When a valid RSSI is read from controller
        gapRolesParamUpdateCB_t pfnParamUpdate; //!< When the connection parameteres are updated
    } gapRolesCBs_t
    

    ③ESTABLISH_LINK_TIMEOUT_EVT

    建立连接超时事件,调用 GAPRole_TerminateLink(INVALID_CONNHANDLE)来终止现有连接。INVALID_CONNHANDLE为无效的连接句柄,用于没有连接句柄。

    ④START_SVC_DISCOVERY_EVT

    启动服务发现

    static void centralStartDiscovery(void)
    {
        uint8_t uuid[ATT_BT_UUID_SIZE] = {LO_UINT16(SIMPLEPROFILE_SERV_UUID), // 低八位
                                          HI_UINT16(SIMPLEPROFILE_SERV_UUID)};// 高八位
    
        // 初始化缓存句柄 
        centralSvcStartHdl = centralSvcEndHdl = centralCharHdl = 0;   // 初始化服务开始、结束和特征句柄
    
        centralDiscState = BLE_DISC_STATE_SVC;
    
        // Discovery simple BLE service
        GATT_DiscPrimaryServiceByUUID(centralConnHandle,
                                      uuid,
                                      ATT_BT_UUID_SIZE,
                                      centralTaskId);
    }
    

    其中GATT_DiscPrimaryServiceByUUID:

    extern bStatus_t GATT_DiscAllPrimaryServices( uint16_t connHandle, uint8_t taskId );
    

    当只知道服务UUID时,客户端使用此子过程来发现服务器上的特定主服务。特定主服务可能在服务器上多次存在。被发现的主服务由服务UUID标识。ATT按类型查找值请求与属性类型参数一起使用,属性类型参数设置为“主要服务”的UUID,属性值设置为16位蓝牙UUID或128位UUID,用于特定的主要服务。起始句柄应设置为0x0001,结束句柄应设置为0xFFFF。如果此函数的返回状态为SUCCESS,则调用应用程序任务将接收多条tmos_GATT_MSG EVENT消息。消息的类型将是ATT_FIND_BYtype_VALUE_RSP或ATT_ERROR_RSP(如果服务器上发生错误)。

    ⑤START_PARAM_UPDATE_EVT

    启动连接参数更新。

     if(events & START_PARAM_UPDATE_EVT)
        {
            GAPRole_UpdateLink(centralConnHandle,
                               DEFAULT_UPDATE_MIN_CONN_INTERVAL,
                               DEFAULT_UPDATE_MAX_CONN_INTERVAL,
                               DEFAULT_UPDATE_SLAVE_LATENCY,
                               DEFAULT_UPDATE_CONN_TIMEOUT);
            return (events ^ START_PARAM_UPDATE_EVT);
        }
    

    更新链接连接参数,设置为默认值。

    ⑥START_PHY_UPDATE_EVT

    启动PHY更新事件。

    当事件events中包含了START_PHY_UPDATE_EVT时,会执行物理层更新操作,并打印出PHY Update的信息。GAPRole_UpdatePHY函数的参数中,centralConnHandle是中央设备的连接句柄,GAP_PHY_BIT_LE_2M表示使用2M的LE PHY,GAP_PHY_OPTIONS_NOPRE表示不使用预先协商的PHY参数。

    if(events & START_PHY_UPDATE_EVT)
    {
        // start phy update
        PRINT("PHY Update %x...\n", GAPRole_UpdatePHY(centralConnHandle, 0, 
                    GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, GAP_PHY_OPTIONS_NOPRE));
    
        return (events ^ START_PHY_UPDATE_EVT);
    }
    

    ⑦START_READ_OR_WRITE_EVT

    一个BLE中央设备(central device)执行一个读写(read/write)操作时触发的。当该事件被触发时,代码会检查是否有中央设备的过程正在进行中,如果没有,则会执行一个写操作,否则会执行一个读操作。在写操作中,代码会分配一个attWriteReq_t结构体,并将请求发送到GATT_WriteCharValue函数进行处理。在读操作中,代码会分配一个attReadReq_t结构体,并将请求发送到GATT_ReadCharValue函数进行处理。如果操作成功,则设置centralProcedureInProgress标志为TRUE,并将centralDoWrite标志翻转。最后,代码会返回一个新的events值,其中已经将START_READ_OR_WRITE_EVT事件从中删除。

    if(events & START_READ_OR_WRITE_EVT)
        {
            if(centralProcedureInProgress == FALSE)
            {
                if(centralDoWrite)
                {
               // Do a write
                attWriteReq_t req;
    			req.cmd = FALSE;
                req.sig = FALSE;
                req.handle = centralCharHdl;
                req.len = 1;
                req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
                if(req.pValue != NULL)
                {
                    *req.pValue = centralCharVal;
    
                    if(GATT_WriteCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                    {
                        centralProcedureInProgress = TRUE;
                        centralDoWrite = !centralDoWrite;
                        tmos_start_task(centralTaskId, START_READ_OR_WRITE_EVT, DEFAULT_READ_OR_WRITE_DELAY);
                    }
                    else
                    {
                        GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
                    }
                }
            }
            else
            {
                // Do a read
                attReadReq_t req;
    
                req.handle = centralCharHdl;
                if(GATT_ReadCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                {
                    centralProcedureInProgress = TRUE;
                    centralDoWrite = !centralDoWrite;
                }
            }
        }
        return (events ^ START_READ_OR_WRITE_EVT);
    }
    

    ⑧START_WRITE_CCCD_EVT

    当客户端正在尝试写入一个特性的Client Characteristic Configuration Descriptor (CCCD)时触发的事件。CCCD用于配置特性的通知或指示属性,以便在特性值更改时通知客户端。

    这个事件通常会在BLE设备与客户端之间进行通信时触发。

    if(events & START_WRITE_CCCD_EVT)
    {
        if(centralProcedureInProgress == FALSE)
        {
            // Do a write
            attWriteReq_t req;
    
            req.cmd = FALSE;
            req.sig = FALSE;
            req.handle = centralCCCDHdl;
            req.len = 2;
            req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
            if(req.pValue != NULL)
            {
                req.pValue[0] = 1;
                req.pValue[1] = 0;
    
                if(GATT_WriteCharValue(centralConnHandle, &req, centralTaskId) == SUCCESS)
                {
                    centralProcedureInProgress = TRUE;
                }
                else
                {
                    GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
                }
            }
        }
        return (events ^ START_WRITE_CCCD_EVT);
    }
    

    ⑨START_READ_RSSI_EVT

    当设备正在尝试读取连接的远程设备的信号强度时触发的事件。RSSI代表接收信号强度指示器,可以用来测量设备之间的信号强度和距离。

    这个事件通常会在BLE设备与远程设备之间进行通信时触发。

    if(events & START_READ_RSSI_EVT)
    {
        GAPRole_ReadRssiCmd(centralConnHandle);
        tmos_start_task(centralTaskId, START_READ_RSSI_EVT, DEFAULT_RSSI_PERIOD);
        return (events ^ START_READ_RSSI_EVT);
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 沁恒蓝牙芯片CH58x系列学习与应用指南

    发表评论