【全能小网关|CH32V208】--6.蓝牙串口服务
本帖最后由 dirty 于 2024-7-7 11:10 编辑<p> 本篇讲述CH32V208蓝牙串口透传。</p>
<p><strong><span style="color:#0000ff;">一.了解准备</span></strong></p>
<p> 沁恒低功耗蓝牙协议栈以及应用均基于 TMOS(Task Management Operating System),TMOS是一个控制循环,通过 TMOS 可设置事件的执行方式。TMOS 作为调度核心,BLE 协议栈、profile 定义、所有的应用都围绕它来实现。TMOS 不是传统意义上的操作系统,而是一种以实现多任务为核心的系统资源管理机制。TMOS 通过轮询的方式进行调度,系统时钟一般来源于 RTC。<br />
对于一个任务,独一无二的任务 ID,任务的初始化以及任务下可执行的事件都是不可或缺的。每个Task 用户最多可以自定义 15 个事件,0x8000 为系统预留的SYS_EVENT_MSG<br />
事件,即系统消息传递事件,不可被定义。</p>
<p> 更多关于TMOS及协议栈使用可参考阅读官方文档。</p>
<p><strong><span style="color:#0000ff;">二.代码准备</span></strong></p>
<p>1.蓝牙协议栈初始化。包括协议栈起始地址、大小、时钟、发送功率、MAC、协议栈库初始化等。</p>
<pre>
<code>/*******************************************************************************
* @fn WCHBLE_Init
*
* <a href="home.php?mod=space&uid=159083" target="_blank">@brief </a> BLE library initialization
*
* @param None.
*
* <a href="home.php?mod=space&uid=784970" target="_blank">@return </a> None.
*/
void WCHBLE_Init(void)
{
uint8_t i;
bleConfig_t cfg;
g_LLE_IRQLibHandlerLocation = (uint32_t)LLE_IRQLibHandler;
if(!tmos_memcmp(VER_LIB, VER_FILE, strlen(VER_FILE)))
{
PRINT("head file error...\n");
while(1);
}
// 32M crystal capacitance and current
OSC->HSE_CAL_CTRL &= ~(0x07<<28);
OSC->HSE_CAL_CTRL |= 0x03<<28;
OSC->HSE_CAL_CTRL |= 3<<24;
tmos_memset(&cfg, 0, sizeof(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.SNVNum = (uint32_t)BLE_SNV_NUM;
cfg.readFlashCB = Lib_Read_Flash;
cfg.writeFlashCB = Lib_Write_Flash;
#endif
cfg.ClockFrequency = CAB_LSIFQ/2;
#if(CLK_OSC32K==0)
cfg.ClockAccuracy = 50;
#else
cfg.ClockAccuracy = 1000;
#endif
cfg.ConnectNumber = (PERIPHERAL_MAX_CONNECTION & 3) | (CENTRAL_MAX_CONNECTION << 2);
#if(defined TEM_SAMPLE) && (TEM_SAMPLE == TRUE)
// Calibrate RF and internal RC according to temperature changes (greater than 7 degrees Celsius)
cfg.tsCB = HAL_GetInterTempValue;
#if(CLK_OSC32K)
cfg.rcCB = Lib_Calibration_LSI; // Internal 32K clock calibration
#endif
#endif
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
cfg.idleCB = BLE_LowPower; // Enable sleep
#endif
#if(defined(BLE_MAC)) && (BLE_MAC == TRUE)
for(i = 0; i < 6; i++)
{
cfg.MacAddr = MacAddr;
}
#else
{
uint8_t MacAddr;
FLASH_GetMACAddress(MacAddr);
for(i = 0; i < 6; i++)
{
cfg.MacAddr = MacAddr; // Use chip mac address
}
}
#endif
if(!cfg.MEMAddr || cfg.MEMLen < 4 * 1024)
{
while(1);
}
i = BLE_LibInit(&cfg);
if(i)
{
PRINT("LIB init error code: %x ...\n", i);
while(1);
}
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE );
NVIC_EnableIRQ( BB_IRQn );
NVIC_EnableIRQ( LLE_IRQn );
}</code></pre>
<p>2.外设初始化。这里面用到了事件注册函数TMOS_ProcessEventRegister,校准任务函数tmos_start_task</p>
<pre>
<code>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)
// Add a calibration task, and a single calibration takes less than 10ms
tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD));
#endif
// tmos_start_task(halTaskID, HAL_TEST_EVENT, MS1_TO_SYSTEM_TIME(1000)); // Add a test task
}</code></pre>
<p>3.低功耗蓝牙初始化。包含GAP参数设置(广播参数、连接参数、绑定参数等),GATT层服务的注册以及回调函数的注册</p>
<pre>
<code>//扫描回包数据
static uint8_t scanRspData[] = {
// complete name
13, // length of this data
GAP_ADTYPE_LOCAL_NAME_COMPLETE,
'w', 'c', 'h','_', 'b', 'l', 'e', '_', 'u', 'a', 'r', 't',
// connection interval range
0x05, // length of this data
GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE,
LO_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL), // 100ms
HI_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL),
LO_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL), // 1s
HI_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL),
// Tx power level
0x02, // length of this data
GAP_ADTYPE_POWER_LEVEL,
0 // 0dBm
};
//蓝牙串口服务回调
/*********************************************************************
* @fn on_bleuartServiceEvt
*
* @brief ble uart service callback handler
*
* @returnNULL
*/
void on_bleuartServiceEvt(uint16_t connection_handle, ble_uart_evt_t *p_evt)
{
switch(p_evt->type)
{
case BLE_UART_EVT_TX_NOTI_DISABLED:
PRINT("%02x:bleuart_EVT_TX_NOTI_DISABLED\r\n", connection_handle);
break;
case BLE_UART_EVT_TX_NOTI_ENABLED:
PRINT("%02x:bleuart_EVT_TX_NOTI_ENABLED\r\n", connection_handle);
break;
case BLE_UART_EVT_BLE_DATA_RECIEVED:
PRINT("BLE RX DATA len:%d\r\n", p_evt->data.length);
//for notify back test
//to ble
uint16_t to_write_length = p_evt->data.length;
app_drv_fifo_write(&app_uart_rx_fifo, (uint8_t *)p_evt->data.p_data, &to_write_length);
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
//end of nofify back test
PRINT("\r\n");
for(uint16_t i=0;i<to_write_length;i++)
{
PRINT("%02x ", app_uart_rx_fifo.data);
}
PRINT("\r\nble to uart\r\n");
//ble to uart
app_uart_tx_data((uint8_t *)p_evt->data.p_data, p_evt->data.length);
break;
default:
break;
}
}
void Peripheral_Init()
{
Peripheral_TaskID = TMOS_ProcessEventRegister(Peripheral_ProcessEvent);
// Setup the GAP Peripheral Role Profile
{
uint8_tinitial_advertising_enable = TRUE;
uint16_t desired_min_interval = 6;
uint16_t desired_max_interval = 1000;
// Set the GAP Role Parameters
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &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_t), &desired_min_interval);
GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(uint16_t), &desired_max_interval);
}
// Set advertising interval
{
uint16_t 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_t passkey = 0; // passkey "000000"
uint8_tpairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;
uint8_tmitm = TRUE;
uint8_tbonding = TRUE;
uint8_tioCap = GAPBOND_IO_CAP_DISPLAY_ONLY;
GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey);
GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8_t), &pairMode);
GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8_t), &mitm);
GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);
GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8_t), &bonding);
}
// Initialize GATT attributes
GGS_AddService(GATT_ALL_SERVICES); // GAP
GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes
DevInfo_AddService(); // Device Information Service
ble_uart_add_service(on_bleuartServiceEvt);
// Set the GAP Characteristics
GGS_SetParameter(GGS_DEVICE_NAME_ATT, sizeof(attDeviceName), attDeviceName);
// Init Connection Item
peripheralInitConnItem(&peripheralConnList);
// Register receive scan request callback
GAPRole_BroadcasterSetCB(&Broadcaster_BroadcasterCBs);
// Setup a delayed profile startup
tmos_set_event(Peripheral_TaskID, SBP_START_DEVICE_EVT);
}</code></pre>
<p>4.事件处理回调函数。这里面有实现串口接收数据蓝牙notify发送</p>
<pre>
<code>/*********************************************************************
* @fn Peripheral_ProcessEvent
*
* @brief Peripheral Application Task event processor.This function
* is called to process all events for the task.Events
* include timers, messages and any other user defined events.
*
* @param task_id - The TMOS assigned task ID.
* @param events - events to process.This is a bit map and can
* contain more than one event.
*
* @returnevents not processed
*/
uint16_t Peripheral_ProcessEvent(uint8_t task_id, uint16_t events)
{
static attHandleValueNoti_t noti;
//VOID task_id; // TMOS required parameter that isn't used in this function
if(events & SYS_EVENT_MSG)
{
uint8_t *pMsg;
if((pMsg = tmos_msg_receive(Peripheral_TaskID)) != NULL)
{
Peripheral_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg);
// Release the TMOS message
tmos_msg_deallocate(pMsg);
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
if(events & SBP_START_DEVICE_EVT)
{
// Start the Device
GAPRole_PeripheralStartDevice(Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs);
return (events ^ SBP_START_DEVICE_EVT);
}
if(events & SBP_PARAM_UPDATE_EVT)
{
// Send connect param update request
GAPRole_PeripheralConnParamUpdateReq(peripheralConnList.connHandle,
DEFAULT_DESIRED_MIN_CONN_INTERVAL,
DEFAULT_DESIRED_MAX_CONN_INTERVAL,
DEFAULT_DESIRED_SLAVE_LATENCY,
DEFAULT_DESIRED_CONN_TIMEOUT,
Peripheral_TaskID);
// GAPRole_PeripheralConnParamUpdateReq( peripheralConnList.connHandle,
// 10,
// 20,
// 0,
// 400,
// Peripheral_TaskID);
return (events ^ SBP_PARAM_UPDATE_EVT);
}
if(events & UART_TO_BLE_SEND_EVT)
{
static uint16_t read_length = 0;
;
uint8_t result = 0xff;
switch(send_to_ble_state)
{
case SEND_TO_BLE_TO_SEND:
//notify is not enabled
if(!ble_uart_notify_is_ready(peripheralConnList.connHandle))
{
if(peripheralConnList.connHandle == GAP_CONNHANDLE_INIT)
{
//connection lost, flush rx fifo here
app_drv_fifo_flush(&app_uart_rx_fifo);
}
break;
}
read_length = ATT_GetMTU(peripheralConnList.connHandle) - 3;
if(app_drv_fifo_length(&app_uart_rx_fifo) >= read_length)
{
PRINT("FIFO_LEN:%d\r\n", app_drv_fifo_length(&app_uart_rx_fifo));
result = app_drv_fifo_read(&app_uart_rx_fifo, to_test_buffer, &read_length);
uart_to_ble_send_evt_cnt = 0;
}
else
{
if(uart_to_ble_send_evt_cnt > 10)
{
result = app_drv_fifo_read(&app_uart_rx_fifo, to_test_buffer, &read_length);
uart_to_ble_send_evt_cnt = 0;
}
else
{
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 4);
uart_to_ble_send_evt_cnt++;
//PRINT("NO TIME OUT\r\n");
}
}
if(APP_DRV_FIFO_RESULT_SUCCESS == result)
{
noti.len = read_length;
noti.pValue = GATT_bm_alloc(peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0);
if(noti.pValue != NULL)
{
tmos_memcpy(noti.pValue, to_test_buffer, noti.len);
PRINT("uart to ble notify\r\n");
for(uint16_t i=0;i<read_length;i++)
{
PRINT("%02x ",to_test_buffer);
}
PRINT("\r\n");
result = ble_uart_notify(peripheralConnList.connHandle, &noti, 0);
if(result != SUCCESS)
{
PRINT("R1:%02x\r\n", result);
send_to_ble_state = SEND_TO_BLE_SEND_FAILED;
GATT_bm_free((gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI);
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
else
{
send_to_ble_state = SEND_TO_BLE_TO_SEND;
//app_fifo_write(&app_uart_tx_fifo,to_test_buffer,&read_length);
//app_drv_fifo_write(&app_uart_tx_fifo,to_test_buffer,&read_length);
read_length = 0;
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
}
else
{
send_to_ble_state = SEND_TO_BLE_ALLOC_FAILED;
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
}
else
{
//send_to_ble_state = SEND_TO_BLE_FIFO_EMPTY;
}
break;
case SEND_TO_BLE_ALLOC_FAILED:
case SEND_TO_BLE_SEND_FAILED:
noti.len = read_length;
noti.pValue = GATT_bm_alloc(peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0);
if(noti.pValue != NULL)
{
tmos_memcpy(noti.pValue, to_test_buffer, noti.len);
result = ble_uart_notify(peripheralConnList.connHandle, &noti, 0);
if(result != SUCCESS)
{
PRINT("R2:%02x\r\n", result);
send_to_ble_state = SEND_TO_BLE_SEND_FAILED;
GATT_bm_free((gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI);
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
else
{
send_to_ble_state = SEND_TO_BLE_TO_SEND;
//app_drv_fifo_write(&app_uart_tx_fifo,to_test_buffer,&read_length);
read_length = 0;
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
}
else
{
send_to_ble_state = SEND_TO_BLE_ALLOC_FAILED;
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
}
break;
default:
break;
}
return (events ^ UART_TO_BLE_SEND_EVT);
}
// Discard unknown events
return 0;
}</code></pre>
<p>5.数据收发应用串口初始化。这里使用到UART3.</p>
<pre>
<code>/*********************************************************************
* @fn app_uart_init
*
* @brief init uart
*
* @returnNULL
*/
void app_uart_init()
{
//tx fifo and tx fifo
//The buffer length should be a power of 2
app_drv_fifo_init(&app_uart_tx_fifo, app_uart_tx_buffer, APP_UART_TX_BUFFER_LENGTH);
app_drv_fifo_init(&app_uart_rx_fifo, app_uart_rx_buffer, APP_UART_RX_BUFFER_LENGTH);
GPIO_InitTypeDefGPIO_InitStructure = {0};
USART_InitTypeDef USART_InitStructure = {0};
NVIC_InitTypeDefNVIC_InitStructure = {0};
//uart3 init
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/* USART3 TX-->B.10RX-->B.11 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART3, ENABLE);
}
</code></pre>
<p>6.主函数。这里main()的最后循环执行 TMOS_SystemProcess(),保证 TMOS 持续运行。app_uart_process处理串口接收及发送,并将接收到数据蓝牙发送。</p>
<pre>
<code>void app_uart_process(void)
{
uint8_t data;
__disable_irq();
if(uart_rx_flag)
{
tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2);
uart_rx_flag = false;
}
__enable_irq();
while(app_drv_fifo_length(&app_uart_tx_fifo))
{
app_drv_fifo_read_to_same_addr(&app_uart_tx_fifo, (uint8_t *)&data, 1);
USART_SendData(USART3, data);
while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET) /* waiting for sending finish */
{
}
}
}
/*******************************************************************************
* Function Name: main
* Description : Main function
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main(void)
{
SystemCoreClockUpdate();
Delay_Init();
#ifdef DEBUG
USART_Printf_Init( 115200 );
#endif
PRINT("%s\n", VER_LIB);
WCHBLE_Init();
HAL_Init();
GAPRole_PeripheralInit();
Peripheral_Init();
app_uart_init();
while(1)
{
TMOS_SystemProcess();
app_uart_process();
}
}
</code></pre>
<p> </p>
<p><strong><span style="color:#0000ff;">三.测验</span></strong></p>
<p> 编译烧录后运行。蓝牙广播如下:</p>
<div style="text-align: center;"></div>
<div style="text-align: center;">图1:蓝牙广播</div>
<p> 连接后,蓝牙串口服务如下:</p>
<div style="text-align: center;"></div>
<div style="text-align: center;">图2:蓝牙串口服务</div>
<p> 在nrf connect工具端打开消息通知使能,发送数据,串口端(UART3,图3右侧)将收到数据打印出来。在串口发送数据(UART3),可以看到左侧打印出调试日志并收到蓝牙数据通知如图4.</p>
<div style="text-align: center;"></div>
<div style="text-align: center;">图3:串口日志。左侧调试串口,右侧数据通讯接收发送串口</div>
<div style="text-align: center;">
<div style="text-align: center;"></div>
<div style="text-align: center;">图4:蓝牙串口服务调试</div>
<p> </p>
</div>
<p> 至此,实现WCH蓝牙串口服务功能,熟悉了对沁恒蓝牙功能的使用。</p>
<p>更多关于TMOS及协议栈使用可参考阅读官方文档。</p>
<p>他有中文文档吗?</p>
lugl4313820 发表于 2024-7-14 07:04
更多关于TMOS及协议栈使用可参考阅读官方文档。
他有中文文档吗?
<p>有的,SDK路径下,目前是\CH32V20xEVT\EVT\EXAM\BLE\沁恒低功耗蓝牙软件开发参考手册.PDF</p>
<p>蓝牙和freertos能否同时使用?</p>
页:
[1]