点击上方[物联网思考],点击关注,第一时间查看物联网精彩分享!
1、整体框图
实现思路:CH579作为外部设备(Peripheral),串口接收(RX)来自外部mcu的数据,通过蓝牙(ble4.2)发送到中心设备(Center)(上图中是手机),通过蓝牙接收来自中心设备的数据,然后通过串口发送(TX)到外部mcu。
2、串口透传服务
ble是通过特征值传输数据的,因此串口透传服务至少需要两个特征值;一个用于发送数据,一个用于接收数据;为了提高数据吞吐量,发送和接收都不使用ack;因此CH579主动发送数据到手机,可以用notify,手机发送数据到CH579,可以用write no rsp的方式。
3、应用串口
这里使用串口3,用接收中断和时间超时的方法接收串口不定长数据。
代码语言:javascript复制 UART3_ByteTrigCfg( UART_1BYTE_TRIG );
UART3_INTCfg( ENABLE, RB_IER_RECV_RDY|RB_IER_LINE_STAT );
NVIC_EnableIRQ( UART3_IRQn );
NVIC_SetPriority(UART3_IRQn,5);
由于CH579串口设计上使用的是FIFO的形式,所以设置为1字节触发。
4、串口透传属性表
代码语言:javascript复制gattAttribute_t uarttransAttrTb[]=
{
//uart服务
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 *)&uarttransService /* pValue */
},
//rx特征
{
{ATT_BT_UUID_SIZE,characterUUID},
GATT_PERMIT_READ,
0,
&uartrxProps
},
//rx特征值
{
{ATT_BT_UUID_SIZE,uartrxUUID},
GATT_PERMIT_WRITE,
0,
&uartrxchar
},
//tx特征
{
{ATT_BT_UUID_SIZE,characterUUID},
GATT_PERMIT_READ,
0,
&uarttxProps
},
//tx特征值
{
{ATT_BT_UUID_SIZE,uarttxUUID},
0,
0,
&uarttxchar
},
//cccd
{
{ATT_BT_UUID_SIZE,clientCharCfgUUID},
GATT_PERMIT_READ|GATT_PERMIT_WRITE,
0,
(uint8 *)uarttxcharConfig
},
};
可以看出属性表里面,包含6条属性,声明了一个服务、一个tx特征、tx特征值、rx特征、rx特征值、以及一个客户特征配置(用于客户端配置是否接收服务器的notify)。
5、RX特征实现
属性写回调函数,将蓝牙RX的特征值传到应用层。
代码语言:javascript复制uint8 uarttrans_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint16 len, uint16 offset,
uint8 method )
{
uint8 status = SUCCESS;
if(pAttr->type.len==ATT_BT_UUID_SIZE)
{
uint16 uuid= BUILD_UINT16(pAttr->type.uuid[0],pAttr->type.uuid[1]);
switch(uuid)
{
case GATT_CLIENT_CHAR_CFG_UUID:
status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,offset,GATT_CLIENT_CFG_NOTIFY);
break;
case UART_RX_CHAR_UUID:
if(UartTransChangeCBs)
{
UartTransChangeCBs(0,pValue,len);
//将蓝牙接收到的数据,传向应用层,应用层再通过串口3发送到外部mcu
}
break;
default:
status=ATT_ERR_ATTR_NOT_FOUND;
break;
}
}
else
{
status= ATT_ERR_INVALID_HANDLE;
}
return status;
}
6、TX特征实现
CH579接收到外部mcu的串口数据后,通过notify的方式发送出去。
代码语言:javascript复制#define SBP_DATA_READY_EVT 0x0008
#define SBP_DATA_TX_EVT 0x0010
#define SBP_RX_TIMEOUT_EVT 0x0020
定义了三个事件,SBP_DATA_READY_EVT用于检测串口接收数据是否就绪,SBP_RX_TIMEOUT_EVT用于串口接收超时,SBP_DATA_TX_EVT用于蓝牙发送。
代码语言:javascript复制static void PollingTx(void)
{
uint8 result=0xff;
attHandleValueNoti_t noti;
uint16 i=0;
static uint8 retry=0;
if(AppTxCount>=0)
{
uint8 mtulen = ATT_GetMTU(peripheralConnList.connHandle)-3;
//获取当前MTU的大小,实际能发送的数据长度,还要减去notify的数据头
if(AppTxCount>mtulen)
{
noti.len=mtulen;
noti.pValue=GATT_bm_alloc(peripheralConnList.connHandle,ATT_HANDLE_VALUE_NOTI,noti.len,NULL,0);
tmos_memcpy(noti.pValue,AppTxData,noti.len);
result=UartTrans_Notify(peripheralConnList.connHandle, noti);
if(result != SUCCESS)
{
GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI );
retry ;
if(retry>=3)
{
retry=0;
AppTxCount=0;
tmos_stop_task(Peripheral_TaskID,SBP_DATA_TX_EVT);
tmos_start_task(Peripheral_TaskID,SBP_DATA_READY_EVT,SBP_DATA_READY_EVT_PERIOD);
PRINT("Send fail...rn");
return;
}
}
else
{
retry=0;
AppTxCount=AppTxCount-mtulen;
#if 1
for( i=0;i<AppTxCount;i )
{
AppTxData[i]=AppTxData[i mtulen];
}
#else
tmos_memcpy(AppTxData,&AppTxData[mtulen],AppTxCount);
#endif
}
tmos_start_task(Peripheral_TaskID,SBP_DATA_TX_EVT,SBP_DATA_TX_EVT_PERIOD);
}
else
{
noti.len=AppTxCount;
noti.pValue=GATT_bm_alloc(peripheralConnList.connHandle,ATT_HANDLE_VALUE_NOTI,noti.len,NULL,0);
tmos_memcpy(noti.pValue,AppTxData,noti.len);
result=UartTrans_Notify(peripheralConnList.connHandle, noti);
if(result!=SUCCESS)
{
GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI );
retry ;
if(retry>=3)
{
retry=0;
AppTxCount=0;
tmos_stop_task(Peripheral_TaskID,SBP_DATA_TX_EVT);
tmos_start_task(Peripheral_TaskID,SBP_DATA_READY_EVT,SBP_DATA_READY_EVT_PERIOD);
PRINT("Send fail...rn");
return;
}
else
{
tmos_start_task(Peripheral_TaskID,SBP_DATA_TX_EVT,SBP_DATA_TX_EVT_PERIOD);
}
}
else
{
retry=0;
AppTxCount=0;
tmos_stop_task(Peripheral_TaskID,SBP_DATA_TX_EVT);
tmos_start_task(Peripheral_TaskID,SBP_DATA_READY_EVT,SBP_DATA_READY_EVT_PERIOD);
}
}
}
}
这里要注意,当前MTU的大小,如果需要发送的数据大于MTU的大小,则需要分包发送;还加入了重发,如果3次都失败,则打印“Send fail…”。
代码语言:javascript复制uint8 UartTrans_Notify(uint16 connhandle,attHandleValueNoti_t noti)
{
uint16 value=GATTServApp_ReadCharCfg(connhandle,uarttxcharConfig);
//获取CCCD配置
if(value & GATT_CLIENT_CFG_NOTIFY)
{
noti.handle=uarttransAttrTb[4].handle;
return GATT_Notification(connhandle,¬i,FALSE);
}
return bleIncorrectMode;
}
notify发送,需要先获取客户端是否使能了发送,如果使能了则发送,否则返回错误。
7、主函数
上述增加的事件都是基于TMOS的,因此只需要在主函数在初始化应用串口即可,
代码语言:javascript复制int main( void )
{
#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("%sn",VER_LIB);
CH57X_BLEInit( );
HAL_Init( );
GAPRole_PeripheralInit( );
AppUartInit();//应用串口
Peripheral_Init( );
while(1){
TMOS_SystemProcess( );//TMOS运行
}
}
8、运行测试
8.1、使用ble调试助手连接,如下:
可以看到自定义的串口透传服务,在串口透传服务下有两个特征,一个支持Write No Response(写),一个支持Notify(通知)。
8.2、接收测试
点击Write No Response右边的箭头,手机发送数据到CH579,CH5789通过串口打印出来,如下:
这里为了方便测试,在手机端周期发送,可以看出CH579也周期性的接收到了数据。
8.3发送测试
CH579发送数据到手机,如下:
篇幅限制,文中只列出了部分代码,如需完整工程,后台联系。
——————END——————
相关推荐:
专辑->蓝牙BLE4.2
专辑->玩转ESP32
专辑->从0到1搭建LoRa物联网
专辑->mcu系列