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

沁恒的蓝牙芯片例程已经将各个函数集合的很高,不同主从机例程的主函数中,往往只有一至两个的函数调用的区别,但是在这一到两个函数中,差别却是很大的。

以沁恒的外设从机例程Peripheral为例,进行学习与应用。
但是不懂的地方还有很多,本次学习只是大概梳理了下整体程序的顺序,很多地方的了解只局限于程序本身的注释,也希望能够和大家讨论学习。
注:我是用的是沁恒的CH573F芯片,用的软件是Mounriver Studio。

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

int main( void )
{SetSysClock( CLK_SOURCE_PLL_32MHz );//时钟初始化
#ifdef DEBUGGPIOA_SetBits(bTXD1);GPIOA_ModeCfg(bTXD1, GPIO_ModeOut_PP_5mA);UART1_DefInit( );//串口初始化和引脚配置
#endif   PRINT("%s\n",VER_LIB);CH57X_BLEInit( );//BLE库初始化HAL_Init( );//硬件初始化GAPRole_PeripheralInit( );//外围设备配置Peripheral_Init( ); //外围应用设备初始化while(1){TMOS_SystemProcess( );//执行系统处理}
}

其中,和沁恒的主机例程Central的主函数相比只有两个函数的区别:

Peripheral例程中,对从机和外设的配置的主要函数如下所示:

GAPRole_PeripheralInit( );//外围设备配置
Peripheral_Init( ); //外围应用设备初始化

Central例程中,对主机和外设的配置的主要函数如下所示:

GAPRole_CentralInit( );
Central_Init( );
1.CH57X_BLEInit

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

void CH57X_BLEInit( void )
{uint8 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) );cfg.MEMAddr = ( u32 ) MEM_BUF;cfg.MEMLen = ( u32 ) BLE_MEMHEAP_SIZE;cfg.BufMaxLen = ( u32 ) BLE_BUFF_MAX_LEN;cfg.BufNumber = ( u32 ) BLE_BUFF_NUM;cfg.TxNumEvent = ( u32 ) BLE_TX_NUM_EVENT;cfg.TxPower = ( u32 ) BLE_TX_POWER;
#if (defined (BLE_SNV)) && (BLE_SNV == TRUE)cfg.SNVAddr = ( u32 ) BLE_SNV_ADDR;cfg.readFlashCB = Lib_Read_Flash;cfg.writeFlashCB = Lib_Write_Flash;
#endif
#if( CLK_OSC32K )   cfg.SelRTCClock = (u32)CLK_OSC32K;
#endifcfg.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 = CH57X_LowPower;    // 启用睡眠
#endif
#if (defined (BLE_MAC)) && (BLE_MAC == TRUE)for(i=0;i<6;i++) cfg.MacAddr[i] = MacAddr[5-i];
#else{uint8 MacAddr[6];GetMACAddress( MacAddr );for(i=0;i<6;i++) cfg.MacAddr[i] = MacAddr[i];   // 使用芯片mac地址}
#endifif ( !cfg.MEMAddr || cfg.MEMLen < 4 * 1024 )while( 1 );i = BLE_LibInit( &cfg );if ( i ){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) );//对cfg结构体中的变量进行清零操作cfg.MEMAddr = ( u32 ) MEM_BUF;cfg.MEMLen = ( u32 ) BLE_MEMHEAP_SIZE;cfg.BufMaxLen = ( u32 ) BLE_BUFF_MAX_LEN;cfg.BufNumber = ( u32 ) BLE_BUFF_NUM;cfg.TxNumEvent = ( u32 ) BLE_TX_NUM_EVENT;cfg.TxPower = ( u32 ) BLE_TX_POWER;
#if (defined (BLE_SNV)) && (BLE_SNV == TRUE)cfg.SNVAddr = ( u32 ) BLE_SNV_ADDR;cfg.readFlashCB = Lib_Read_Flash;cfg.writeFlashCB = Lib_Write_Flash;
#endifcfg.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摄氏度 )

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

  u32 MEMAddr; //库内存起始地址__attribute __((at(0x20003800)))u16 MEMLen; //库内存大小u32 SNVAddr; // SNV闪存启动地址,必须为dataflash区域或NULL(绑定信息将不被保存)u16 SNVBlock; // SNV闪存块大小(默认512)u8 SNVNum; // SNV闪存块号(默认为1)u8 BufMaxLen; //支持的最大八位字节,范围27-251,ATT_MTU = BufMaxLen-4(默认27)u8 BufNumber; //控制器缓存的最大发送和接收包数(默认为10)//必须大于连接数。u8 TxNumEvent; //连接事件中的最大TX数据数(默认为1)u8 TxPower; //发射功率(defautl LL_TX_POWEER_0_DBM(0dBm))u8 WakeUpTime; //一个RTC计数中的唤醒时间值(默认值为80)u8 SelRTCClock; // RTC时钟选择LSE,LSI(32768Hz或32000Hz)(默认值:0 LSE,1:LSI(32000Hz),2:LSI(32768Hz))u8 BLEIrqOff; // resv(默认:0)u8 MacAddr [6]; // MAC地址,小端(出厂默认)u8 ConnectNumber; //连接号,后两位为外设号,后跟中心号u8 WindowWidening; //等待射频启动窗口u8 WaiteWindow; //等待连接事件到达窗口pfnSrandCB srandCB; //注册生成随机种子的程序pfnSleepCB sleepCB; //注册设置空闲模式的程序pfnTempSampleCB tsCB; //注册一个读取当前温度的程序,确定是否需要校准pfnLSECalibrationCB rcCB; //注册一个程序以进行RC32K时钟校准pfnLibStatusErrorCB staCB; //注册一个程序库状态回调pfnFlashReadCB readFlashCB; //注册一个读取flash的程序pfnFlashWriteCB writeFlashCB; //注册写闪存的程序

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

#if (defined (BLE_MAC)) && (BLE_MAC == TRUE)for(i=0;i<6;i++) cfg.MacAddr[i] = MacAddr[5-i];
#else{uint8 MacAddr[6];GetMACAddress( MacAddr );for(i=0;i<6;i++) cfg.MacAddr[i] = MacAddr[i];   // 使用芯片mac地址}
#endifif ( !cfg.MEMAddr || cfg.MEMLen < 4 * 1024 )while( 1 );

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

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

  i = BLE_LibInit( &cfg );if ( i ){PRINT( "LIB init error code: %x ...\n", i );while( 1 );}
2.HAL_Init

HAL_Init()函数是硬件初始化函数。

函数主体如下所示:

void HAL_Init()
{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 ,1000 ); // 添加一个测试任务
}

由于Peripheral例程并未用到诸如蓝牙休眠,LED和Key等功能,所以HAL_Init()函数主要只执行了系统时间配置和校准任务的添加。

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

void HAL_TimeInit( void )
{#if( CLK_OSC32K )Calibration_LSI();
#elseR8_SAFE_ACCESS_SIG = 0x57;R8_SAFE_ACCESS_SIG = 0xa8;R8_CK32K_CONFIG |= RB_CLK_OSC32K_XT | RB_CLK_INT32K_PON | RB_CLK_XT32K_PON;R8_SAFE_ACCESS_SIG = 0;
#endifRTC_InitTime( 23, 59, 58 );    //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 * 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_LEDreturn events ^ LED_BLINK_EVENT;}if ( events & HAL_KEY_EVENT ){#if (defined HAL_KEY) && (HAL_KEY == TRUE)HAL_KeyPoll(); /* Check for keys */if (!Hal_KeyIntEnable){ 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)  // 校准任务,单次校准耗时小于10msBLE_RegInit();    // 校准RF
#if( CLK_OSC32K )   Lib_Calibration_LSI();    // 校准内部RC
#endiftmos_start_task( halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME( BLE_CALIBRATION_PERIOD ) );return events ^ HAL_REG_INIT_EVENT;
#endif}if ( events & HAL_TEST_EVENT ){PRINT( "* " );tmos_start_task( halTaskID, HAL_TEST_EVENT, MS1_TO_SYSTEM_TIME( 10000 ) );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_LEDreturn 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 */if (!Hal_KeyIntEnable){ 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)  // 校准任务,单次校准耗时小于10msBLE_RegInit();    // 校准RF
#if( CLK_OSC32K )   Lib_Calibration_LSI();    // 校准内部RC
#endiftmos_start_task( halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME( BLE_CALIBRATION_PERIOD ) );return events ^ HAL_REG_INIT_EVENT;
#endif}

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。

3.GAPRole_PeripheralInit

GAPRole_PeripheralInit()函数是应用于外围设备配置,也就是说,对于不同的应用需要配置不同的外围设备函数。

4.Peripheral_Init

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

void Peripheral_Init( )
{Peripheral_TaskID = TMOS_ProcessEventRegister( Peripheral_ProcessEvent );// Setup the GAP Peripheral Role Profile{uint8 initial_advertising_enable = TRUE;uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;// Set the GAP Role ParametersGAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( scanRspData ), scanRspData );GAPRole_SetParameter( GAPROLE_ADVERT_DATA, sizeof( advertData ), advertData );GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );}// Set the GAP CharacteristicsGGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName );// Set advertising interval{uint16 advInt = DEFAULT_ADVERTISING_INTERVAL;GAP_SetParamValue( TGAP_DISC_ADV_INT_MIN, advInt );GAP_SetParamValue( TGAP_DISC_ADV_INT_MAX, advInt );}// Setup the GAP Bond Manager{uint32 passkey = 0; // passkey "000000"uint8 pairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;uint8 mitm = TRUE;uint8 bonding = TRUE;uint8 ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY;GAPBondMgr_SetParameter( GAPBOND_PERI_DEFAULT_PASSCODE, sizeof ( uint32 ), &passkey );GAPBondMgr_SetParameter( GAPBOND_PERI_PAIRING_MODE, sizeof ( uint8 ), &pairMode );GAPBondMgr_SetParameter( GAPBOND_PERI_MITM_PROTECTION, sizeof ( uint8 ), &mitm );GAPBondMgr_SetParameter( GAPBOND_PERI_IO_CAPABILITIES, sizeof ( uint8 ), &ioCap );GAPBondMgr_SetParameter( GAPBOND_PERI_BONDING_ENABLED, sizeof ( uint8 ), &bonding );}// Initialize GATT attributesGGS_AddService( GATT_ALL_SERVICES );            // GAPGATTServApp_AddService( GATT_ALL_SERVICES );    // GATT attributesDevInfo_AddService();                           // Device Information ServiceSimpleProfile_AddService( GATT_ALL_SERVICES );  // Simple GATT Profile// Setup the SimpleProfile Characteristic Values{uint8 charValue1[SIMPLEPROFILE_CHAR1_LEN] = { 1 };uint8 charValue2[SIMPLEPROFILE_CHAR2_LEN] = { 2 };uint8 charValue3[SIMPLEPROFILE_CHAR3_LEN] = { 3 };uint8 charValue4[SIMPLEPROFILE_CHAR4_LEN] = { 4 };uint8 charValue5[SIMPLEPROFILE_CHAR5_LEN] = { 1, 2, 3, 4, 5 };SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR1, SIMPLEPROFILE_CHAR1_LEN, charValue1 );SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR2, SIMPLEPROFILE_CHAR2_LEN, charValue2 );SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR3, SIMPLEPROFILE_CHAR3_LEN, charValue3 );SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR4, SIMPLEPROFILE_CHAR4_LEN, charValue4 );SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5 );}// Init Connection ItemperipheralInitConnItem( &peripheralConnList );// Register callback with SimpleGATTprofileSimpleProfile_RegisterAppCBs( &Peripheral_SimpleProfileCBs );// Register receive scan request callbackGAPRole_BroadcasterSetCB( &Broadcaster_BroadcasterCBs );// Setup a delayed profile startuptmos_set_event( Peripheral_TaskID, SBP_START_DEVICE_EVT );
}

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

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

uint16 Peripheral_ProcessEvent( uint8 task_id, uint16 events )
{//  VOID task_id; // TMOS required parameter that isn't used in this functionif ( events & SYS_EVENT_MSG ){uint8 *pMsg;if ( (pMsg = tmos_msg_receive( Peripheral_TaskID )) != NULL ){Peripheral_ProcessTMOSMsg( (tmos_event_hdr_t *)pMsg );// Release the TMOS messagetmos_msg_deallocate( pMsg );}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}if ( events & SBP_START_DEVICE_EVT ){// Start the DeviceGAPRole_PeripheralStartDevice( Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs );return ( events ^ SBP_START_DEVICE_EVT );}if ( events & SBP_PERIODIC_EVT ){// Restart timerif ( SBP_PERIODIC_EVT_PERIOD ){tmos_start_task( Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );}// Perform periodic application taskperformPeriodicTask();return (events ^ SBP_PERIODIC_EVT);}if ( events & SBP_PARAM_UPDATE_EVT ){// Send connect param update requestGAPRole_PeripheralConnParamUpdateReq( peripheralConnList.connHandle,DEFAULT_DESIRED_MIN_CONN_INTERVAL,DEFAULT_DESIRED_MAX_CONN_INTERVAL,DEFAULT_DESIRED_SLAVE_LATENCY,DEFAULT_DESIRED_CONN_TIMEOUT,Peripheral_TaskID);return (events ^ SBP_PARAM_UPDATE_EVT);}if ( events & SBP_READ_RSSI_EVT ){GAPRole_ReadRssiCmd(peripheralConnList.connHandle);tmos_start_task( Peripheral_TaskID, SBP_READ_RSSI_EVT, SBP_READ_RSSI_EVT_PERIOD );      return (events ^ SBP_READ_RSSI_EVT);}   // Discard unknown eventsreturn 0;
}

①SYS_EVENT_MSG

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

  if ( events & SYS_EVENT_MSG ){uint8 *pMsg;if ( (pMsg = tmos_msg_receive( Peripheral_TaskID )) != NULL ){Peripheral_ProcessTMOSMsg( (tmos_event_hdr_t *)pMsg );// Release the TMOS messagetmos_msg_deallocate( pMsg );}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}

②SBP_START_DEVICE_EVT

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

  if ( events & SBP_START_DEVICE_EVT ){// Start the DeviceGAPRole_PeripheralStartDevice( Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs );return ( events ^ SBP_START_DEVICE_EVT );}

GAPRole_PeripheralStartDevice函数声明如下所示:

extern bStatus_t GAPRole_PeripheralStartDevice( uint8 taskid, gapBondCBs_t *pCB,gapRolesCBs_t *pAppCallbacks );

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

gapBondCBs_t结构体如下所示:

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

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

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

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

connectionHandle是连接处理;

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

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

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

而调用的Peripheral_BondMgrCBs结构体,则是把所有的结构体中的变量都置零,相当于初始化。

static gapBondCBs_t Peripheral_BondMgrCBs =
{NULL,                     // Passcode callback (not used by application)NULL                      // Pairing / Bonding state Callback (not used by application)
};

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

static gapRolesCBs_t Peripheral_PeripheralCBs =
{peripheralStateNotificationCB,  // Profile State Change CallbacksperipheralRssiCB,  // When a valid RSSI is read from controller (not used by application)peripheralParamUpdateCB
};

首先来看peripheralStateNotificationCB()函数:

static void peripheralStateNotificationCB( gapRole_States_t newState, gapRoleEvent_t * pEvent )
{switch ( newState ){case GAPROLE_STARTED:PRINT( "Initialized..\n" );break;case GAPROLE_ADVERTISING:if( pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT ){Peripheral_LinkTerminated( pEvent );PRINT( "Disconnected.. Reason:%x\n",pEvent->linkTerminate.reason );}PRINT( "Advertising..\n" );break;case GAPROLE_CONNECTED:if( pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT ){Peripheral_LinkEstablished( pEvent );}PRINT( "Connected..\n" );break;case GAPROLE_CONNECTED_ADV:PRINT( "Connected Advertising..\n" );break;      case GAPROLE_WAITING:if( pEvent->gap.opcode == GAP_END_DISCOVERABLE_DONE_EVENT ){PRINT( "Waiting for advertising..\n" );}else if( pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT ){Peripheral_LinkTerminated( pEvent );PRINT( "Disconnected.. Reason:%x\n",pEvent->linkTerminate.reason );}else if( pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT ){if( pEvent->gap.hdr.status != SUCCESS ){PRINT( "Waiting for advertising..\n" );}else{PRINT( "Error..\n" );}}else{PRINT( "Error..%x\n",pEvent->gap.opcode );}break;case GAPROLE_ERROR:PRINT( "Error..\n" );break;default:break;}
}

peripheralStateNotificationCB()函数共有两个参数,分别是当前状态newState和中心时间结构pEvent。

首先来看第一种状态:

case GAPROLE_STARTED:PRINT( "Initialized..\n" );break;

GAPROLE_STARTED状态表示的是已经开始但是没有进行广播,通过串口打印表示已经初始化结束。

第二种状态:

case GAPROLE_ADVERTISING:if( pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT ){Peripheral_LinkTerminated( pEvent );PRINT( "Disconnected.. Reason:%x\n",pEvent->linkTerminate.reason );}PRINT( "Advertising..\n" );break;

GAPROLE_ADVERTISING状态表示的是正在进行广播,同时如果 pEvent结构中的gap.opcode变量与表示连接终止标志GAP_LINK_TERMINATED_EVENT相等的时候,则执行Peripheral_LinkTerminated()函数,并通过串口打印通报无法连接和无法连接的原因。其中pEvent结构体中的linkTerminate结构体中的reason寄存的是连接终止的原因。

其中Peripheral_LinkTerminated()函数是当进程终止时,进行处理,具体函数如下所示:

static void Peripheral_LinkTerminated( gapRoleEvent_t * pEvent )
{gapTerminateLinkEvent_t *event = (gapTerminateLinkEvent_t *) pEvent;if( event->connectionHandle == peripheralConnList.connHandle ){peripheralConnList.connHandle = GAP_CONNHANDLE_INIT;peripheralConnList.connInterval = 0;peripheralConnList.connSlaveLatency = 0;peripheralConnList.connTimeout = 0;tmos_stop_task( Peripheral_TaskID, SBP_PERIODIC_EVT );tmos_stop_task( Peripheral_TaskID, SBP_READ_RSSI_EVT );// Restart advertising{uint8 advertising_enable = TRUE;GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &advertising_enable );}}else{PRINT("ERR..\n");}
}

如果没有出现连接终止的标志,则通过串口进行打印正在广播进行通报。

第三种状态:

case GAPROLE_CONNECTED:if( pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT ){Peripheral_LinkEstablished( pEvent );}PRINT( "Connected..\n" );break;

GAPROLE_CONNECTED状态表示的是在连接中。

当pEvent结构体中的gap结构体中的opcode变量与表示建立连接请求发送完成的标志位GAP_LINK_ESTABLISHED_EVENT相等的时候,则执行Peripheral_LinkEstablished()函数,从而对主从机进行连接操作。

其中Peripheral_LinkEstablished()函数具体如下所示:

static void Peripheral_LinkEstablished( gapRoleEvent_t * pEvent )
{gapEstLinkReqEvent_t *event = (gapEstLinkReqEvent_t *) pEvent;// See if already connectedif( peripheralConnList.connHandle != GAP_CONNHANDLE_INIT ){GAPRole_TerminateLink( pEvent->linkCmpl.connectionHandle );PRINT( "Connection max...\n" );}else{peripheralConnList.connHandle = event->connectionHandle;peripheralConnList.connInterval = event->connInterval;peripheralConnList.connSlaveLatency = event->connLatency;peripheralConnList.connTimeout = event->connTimeout;// Set timer for periodic eventtmos_start_task( Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );// Set timer for param update eventtmos_start_task( Peripheral_TaskID, SBP_PARAM_UPDATE_EVT, SBP_PARAM_UPDATE_DELAY );// Start read rssitmos_start_task( Peripheral_TaskID, SBP_READ_RSSI_EVT, SBP_READ_RSSI_EVT_PERIOD );PRINT("Conn %x - Int %x \n", event->connectionHandle, event->connInterval);                   }
}

在Peripheral_LinkEstablished()函数中启动了三个事件,分别是SBP_PERIODIC_EVT、

SBP_PARAM_UPDATE_EVT和SBP_READ_RSSI_EVT。三个事件的功能分别是为定期事件设置定时器、设置参数更新事件的计时器和开始读蓝牙强度RSSI。

第四种状态

GAPROLE_CONNECTED_ADV表示的是正在连接和广播,通过串口打印进行通报。

case GAPROLE_CONNECTED_ADV:PRINT( "Connected Advertising..\n" );break;

第五种状态

GAPROLE_WAITING表示的是设备已经启动,处于等待广播的状态。

case GAPROLE_WAITING:if( pEvent->gap.opcode == GAP_END_DISCOVERABLE_DONE_EVENT ){PRINT( "Waiting for advertising..\n" );}else if( pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT ){Peripheral_LinkTerminated( pEvent );PRINT( "Disconnected.. Reason:%x\n",pEvent->linkTerminate.reason );}else if( pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT ){if( pEvent->gap.hdr.status != SUCCESS ){PRINT( "Waiting for advertising..\n" );}else{PRINT( "Error..\n" );}}else{PRINT( "Error..%x\n",pEvent->gap.opcode );}break;

当gap.opcode标志位与表示广播结束的GAP_END_DISCOVERABLE_DONE_EVENT相等时,通过串口打印表示广播结束,等待下次广播。

当gap.opcode标志位与表示连接终止的GAP_LINK_TERMINATED_EVENT相等时,执行Peripheral_LinkTerminated()函数,并通过串口通报无法连接以及无法连接的原因。

当gap.opcode标志位与表示连接请求完成时的GAP_LINK_ESTABLISHED_EVENT相等时,再对status变量进行判断,如果是SUCESS,则打印表示等待下次广播,否则通过串口进行通报错误。

第六种状态

GAPROLE_ERROR表示发生错误,通过串口打印进行通报。

case GAPROLE_ERROR:PRINT( "Error..\n" );break;

其中peripheralRssiCB()函数具体如下所示,主要功能是从控制器中读取有效的蓝牙强度值RSSI。

static void peripheralRssiCB( uint16 connHandle, int8 rssi )
{PRINT( "RSSI -%d dB Conn  %x \n", -rssi, connHandle);
}

其中peripheralParamUpdateCB()函数具体如下所示,主要功能是更新外设参数,分别是connInterval-连接间隔,connSlaveLatency-连接从属延迟和connTimeout-连接超时。

static void peripheralParamUpdateCB( uint16 connHandle, uint16 connInterval, uint16 connSlaveLatency, uint16 connTimeout )
{if( connHandle == peripheralConnList.connHandle ){peripheralConnList.connInterval = connInterval;peripheralConnList.connSlaveLatency = connSlaveLatency;peripheralConnList.connTimeout = connTimeout;PRINT("Update %x - Int %x \n", connHandle, connInterval);}else{PRINT("ERR..\n");}
}

③SBP_PERIODIC_EVT

先是利用tmos_start_task()函数来实现本个事件的时间配置。

  if ( events & SBP_PERIODIC_EVT ){// Restart timerif ( SBP_PERIODIC_EVT_PERIOD ){tmos_start_task( Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );}// Perform periodic application taskperformPeriodicTask();return (events ^ SBP_PERIODIC_EVT);}

Perform periodic application task()函数主要功能是执行申请任务,具体函数如下所示:

static void performPeriodicTask( void )
{uint8 notiData[SIMPLEPROFILE_CHAR4_LEN] = { 0x99 };peripheralChar4Notify( notiData, SIMPLEPROFILE_CHAR4_LEN );
}

通过设置notiData[]数组中的值,可以实现从机向主机传送数据的改变。

其中peripheralChar4Notify()函数具体如下所示:

static void peripheralChar4Notify( uint8 *pValue, uint16 len )
{attHandleValueNoti_t noti;noti.len = len;noti.pValue = GATT_bm_alloc( peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0 );tmos_memcpy( noti.pValue, pValue, noti.len );if( simpleProfile_Notify( peripheralConnList.connHandle, &noti ) != SUCCESS ){GATT_bm_free( (gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI );}
}

得到的变量先通过tmos_memcpy()函数进行内存复制,再通过simpleProfile_Notify()函数,simpleProfile_Notify()函数如下所示:

bStatus_t simpleProfile_Notify( uint16 connHandle, attHandleValueNoti_t *pNoti )
{uint16 value = GATTServApp_ReadCharCfg( connHandle, simpleProfileChar4Config );// If notifications enabledif ( value & GATT_CLIENT_CFG_NOTIFY ){// Set the handlepNoti->handle = simpleProfileAttrTbl[SIMPLEPROFILE_CHAR4_VALUE_POS].handle;// Send the notificationreturn GATT_Notification( connHandle, pNoti, FALSE );}return bleIncorrectMode;
}

首先simpleProfile_Notify()函数执行了一个GATTServApp_ReadCharCfg()函数。之后由分别执行了simpleProfileAttrTbl()函数和GATT_Notification()函数。

其中利用simpleProfileAttrTbl()实现配置文件属性表的查询:

static gattAttribute_t simpleProfileAttrTbl[] =
{// Simple Profile Service{ { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */GATT_PERMIT_READ,                         /* permissions */0,                                        /* handle */(uint8 *)&simpleProfileService            /* pValue */},// Characteristic 1 Declaration{ { ATT_BT_UUID_SIZE, characterUUID },GATT_PERMIT_READ, 0,&simpleProfileChar1Props },// Characteristic Value 1{ { ATT_BT_UUID_SIZE, simpleProfilechar1UUID },GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, simpleProfileChar1 },// Characteristic 1 User Description{ { ATT_BT_UUID_SIZE, charUserDescUUID },GATT_PERMIT_READ, 0, simpleProfileChar1UserDesp },      // Characteristic 2 Declaration{ { ATT_BT_UUID_SIZE, characterUUID },GATT_PERMIT_READ, 0,&simpleProfileChar2Props },// Characteristic Value 2{ { ATT_BT_UUID_SIZE, simpleProfilechar2UUID },GATT_PERMIT_READ, 0, simpleProfileChar2 },// Characteristic 2 User Description{ { ATT_BT_UUID_SIZE, charUserDescUUID },GATT_PERMIT_READ, 0, simpleProfileChar2UserDesp },           // Characteristic 3 Declaration{ { ATT_BT_UUID_SIZE, characterUUID },GATT_PERMIT_READ, 0,&simpleProfileChar3Props },// Characteristic Value 3{ { ATT_BT_UUID_SIZE, simpleProfilechar3UUID },GATT_PERMIT_WRITE, 0, simpleProfileChar3 },// Characteristic 3 User Description{ { ATT_BT_UUID_SIZE, charUserDescUUID },GATT_PERMIT_READ, 0, simpleProfileChar3UserDesp },// Characteristic 4 Declaration{ { ATT_BT_UUID_SIZE, characterUUID },GATT_PERMIT_READ, 0,&simpleProfileChar4Props },// Characteristic Value 4{ { ATT_BT_UUID_SIZE, simpleProfilechar4UUID },0, 0, simpleProfileChar4 },// Characteristic 4 configuration{ { ATT_BT_UUID_SIZE, clientCharCfgUUID },GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8 *)simpleProfileChar4Config },// Characteristic 4 User Description{ { ATT_BT_UUID_SIZE, charUserDescUUID },GATT_PERMIT_READ, 0, simpleProfileChar4UserDesp },// Characteristic 5 Declaration{ { ATT_BT_UUID_SIZE, characterUUID },GATT_PERMIT_READ, 0,&simpleProfileChar5Props },// Characteristic Value 5{ { ATT_BT_UUID_SIZE, simpleProfilechar5UUID },GATT_PERMIT_AUTHEN_READ, 0, simpleProfileChar5 },// Characteristic 5 User Description{ { ATT_BT_UUID_SIZE, charUserDescUUID },GATT_PERMIT_READ, 0, simpleProfileChar5UserDesp },};

而GATT_Notification()函数则是发送数据的函数,将之前peripheralChar4Notify()函数存储的数据发送出去。

③SBP_PARAM_UPDATE_EVT

SBP_PARAM_UPDATE_EVT事件是发送连接参数更新请求

  if ( events & SBP_PARAM_UPDATE_EVT ){// Send connect param update requestGAPRole_PeripheralConnParamUpdateReq( peripheralConnList.connHandle,DEFAULT_DESIRED_MIN_CONN_INTERVAL,DEFAULT_DESIRED_MAX_CONN_INTERVAL,DEFAULT_DESIRED_SLAVE_LATENCY,DEFAULT_DESIRED_CONN_TIMEOUT,Peripheral_TaskID);return (events ^ SBP_PARAM_UPDATE_EVT);}

其中,GAPRole_PeripheralConnParamUpdateReq()函数需要更新的主要有连接处理,新的连接间隔,新的从属延迟。监控超时值等

④SBP_READ_RSSI_EVT

SBP_READ_RSSI_EVT事件是最后一个事件,他的功能很明显,是读蓝牙强度的这么一个事件任务。

  if ( events & SBP_READ_RSSI_EVT ){GAPRole_ReadRssiCmd(peripheralConnList.connHandle);tmos_start_task( Peripheral_TaskID, SBP_READ_RSSI_EVT, SBP_READ_RSSI_EVT_PERIOD );      return (events ^ SBP_READ_RSSI_EVT);}

主要执行函数就是tmos_start_task()函数,通过设置SBP_READ_RSSI_EVT事件和间隔时间完成配置。

后续Peripheral_Init()函数的内容主要是进行初始化和配置,像外设角色配置,主要配置使能广播,扫描响应,广播数据,连接间隔最大值和最小值和设置密码等。

最后再Peripheral_Init()函数中,还有以下几个初始化。

  // 初始化连接项peripheralInitConnItem( &peripheralConnList );// 使用SimpleGATTprofile注册回调SimpleProfile_RegisterAppCBs( &Peripheral_SimpleProfileCBs );// 注册接收扫描请求回调GAPRole_BroadcasterSetCB( &Broadcaster_BroadcasterCBs );// 设置延迟的配置文件启动tmos_set_event( Peripheral_TaskID, SBP_START_DEVICE_EVT );

沁恒蓝牙芯片CH57x系列学习与应用相关推荐

  1. 沁恒蓝牙芯片CH58X蓝牙从机的使用

    CH583 是集成 BLE 无线通讯的 32 位 RISC 微控制器.片上集成 2Mbps 低功耗蓝牙 BLE 通讯模块.2 个全速 USB 主机和设备控制器及收发器.2 个 SPI.4 个串口.AD ...

  2. 沁恒蓝牙芯片CH58X蓝牙主机的使用

    在做的项目本身只是需要用到蓝牙从机就行,从机项目开发已基本要收尾了.考虑到要配合产线系统做自动化测试,因此需要一个配合从机的主机dongle供自动化测试系统使用. 蓝牙主机的使用只要按主机和从机的通信 ...

  3. 第十七届全国大学生智能汽车竞赛 沁恒微电子芯片推荐

    简 介: 南京沁恒为电子公司继续赞助第十七届全国大学生智能车.今年他们不仅提供性能更加优越的RISV-V MCU芯片,同时也推荐使用公司具有特色的无线蓝牙芯片,用于运动智能车作品的调试与跟踪. 关键词 ...

  4. 沁恒CH582M开发板-1-点亮LED

    CH582-1-点亮LED 硬件准备 沁恒CH582M-R0-1V0开发板 USB-TTL模块 软件准备 CH582M是wch自研的 青稞RISC 处理器 WCH RISC-V4A,所用的开发环境也是 ...

  5. 沁恒RISC-V MCU 为全国大学生智能汽车竞赛加速

    §01 沁恒RISC-V   第十七届(2022年)全国大学生智能汽车竞赛规则已发布,沁恒微电子很荣幸继续为大赛提供赞助.其中多车编队组的头车限定使用沁恒微电子的MCU作为主控,跟随车之一可以选用沁恒 ...

  6. 第十六届全国大学智能汽车竞赛全向组沁恒芯片申请统计情况

    全向行进组沁恒CH32V103 免费芯片申请情况汇总 §01 申请情况说明   各参赛学校的参赛同学们你们好,截止到2021年6月1日,沁恒CH32V103样片申请审批通过的学校名单如后面申请一览表所 ...

  7. 全国大学生智能车竞赛申请沁恒RISC-V MCU样品说明

      第十六届(2021 年)全国大学生智能汽车竞赛规则已发布,沁恒微电子很荣幸成为大赛的赞助商之一.本次大赛推荐使用的WCH 微控制器CH32V103 为沁恒微电子自主研发的32 位通用RISC-V架 ...

  8. 普大喜奔:沁恒单片机免费样品申请开始啦!

    亲爱的参赛同学: 第十六届全国大学生智能汽车竞赛规则初稿已发布,沁恒微电子很荣幸成为大赛的赞助商之一,为更好的支持同学们参与全国大学生智能汽车竞赛,沁恒微电子为每个参赛学校免费提供: 1个下载调试器W ...

  9. 沁恒CH573开发板上手

    概述 CH573是集成BLE无线通讯的32位RISC-V内核微控制器.片上集成低功耗蓝牙BLE通讯模块.全速USB主机和设备控制器及收发器.SPI.4个串口.ADC.触摸按键检测模块.RTC等丰富的外 ...

最新文章

  1. linux进程间通信:命名管道FIFO
  2. silverlight、wpf中 dispatcher和timer区别
  3. pptv网络电视android,PP视频(原聚力视频)
  4. java 连接sqlserver2005_JAVA用jdbc连接SQLServer2005
  5. spring-DAO
  6. Linux C 算法与数据结构 --二叉树
  7. 《SpringCloud超级入门》Spring Cloud Eureka是什么?《八》
  8. Windows下80端口被进程SystemPID=4占用的解决方法
  9. Silverlight 5 强袭 !! 圣临王者之三端大一统
  10. 分享打造爆款书的方法,同时聊聊出版图书中的哪些事和哪些坑
  11. 2022届网易校招提前批笔试直播笔记
  12. vue中的事件修饰符.self、.capture和.passive
  13. 发票管理系统java_企业发票管理系统.doc
  14. python输出时怎么保留两位小数_python输出怎么保留两位小数-Python教程
  15. 当你从美梦中惊醒的时候,你该做什么?
  16. ESP-IDF遇到的关于环境变量的问题
  17. Fecshop 开源B2C电商系统,php Yii2框架,支持多语言多货币
  18. Servlet服务器端程序
  19. 如何删除带有密码的赛门铁克企业版客户端?
  20. CSDN博客:在非登录状态下通过百度搜索引擎查看自己的博客

热门文章

  1. python教程app下载地址_Python爬取APP下载链接的实现方法
  2. 如何重装windows10系统(超详细图文版)
  3. 用原生JS和CSS3做一个有趣的cube相册
  4. 按键精灵 android版运行异常,按键精灵安卓版 tap、touch命令 不好用的解决办法!...
  5. 老菜鸟致青春,程序员应该选择java 还是 c#-
  6. Minecraft作弊端介绍:PYRO CLIENT-一个平凡但神秘的存在
  7. 1分钟学会PS背景虚化
  8. 小程序利用background-image设置背景
  9. 视觉SLAM小知识——叉乘的物理意义
  10. python如何裁剪图像