国外优质网站,logo设计公司简介,找项目去哪个网站,响应式网站设计教程CH585_BLE_UART_蓝牙串口透传例程详解在CH585EVT中提供的例程中#xff0c;包含BLE_UART蓝牙串口透传例程;这是实现的 UART 串口与 BLE 蓝牙之间的双向透传功能。接下来看一下完整的程序上是如何实现蓝牙串口功能。一、peripheral_main.c先看一下peripheral_main.c中#xff…CH585_BLE_UART_蓝牙串口透传例程详解在CH585EVT中提供的例程中包含BLE_UART蓝牙串口透传例程;这是实现的 UART 串口与 BLE 蓝牙之间的双向透传功能。接下来看一下完整的程序上是如何实现蓝牙串口功能。一、peripheral_main.c先看一下peripheral_main.c中主函数初始化中可以明显看出来比之前讲解的蓝牙从机的初始化中也就多了一个 app_uart_init(); 这个是对于透传串口进行初始化配置。1、app_uart_init在app_uart_init()中可以看到就是对透传串口3PA4、PA5进行初始化操作程序中一开始先是初始化app_uart_tx_fifo和app_uart_rx_fifo是对接收缓冲区和发送缓冲区FIFO进行初始化因为蓝牙发送数据的速度是不稳定的受各方面的干扰因素而串口是恒定波特率FIFO 起到了缓冲作用防止数据瞬间堆积导致丢失。还有一个是串口中断只负责快速把数据扔进 FIFO这样蓝牙TMOS任务只需要慢慢处理。但是如果没有 FIFO中断函数接收完串口之后就要被迫等待处理完成这样会造成卡死整个系统蓝牙无法正常运行下去。在全局变量定义中TX Buffer (512): 用于BLE - UART。蓝牙接收速度受连接间隔限制较慢而串口发送极快数据很快就能发走所以 512B 足够了。RX Buffer (2048): 用于UART - BLE。串口可能以 115200 甚至更高波特率疯狂灌入数据而蓝牙发送需要等待对方确认和受带宽限制容易造成拥堵。所以 RX 需要一个FIFO缓冲区(2KB) 来防止溢出丢包。然后就是对TX、RX的GPIO引脚进行配置初始化 UART3 外设硬件其中包含针对串口波特率115200的设置调用UART3_INTCfg函数来开启中断通过 PFIC_EnableIRQ(UART3_IRQn)配置中断控制器允许 UART3 的中断信号进入 CPU 核心进行处理。2、 app_uart_process()在主循环中除了本身蓝牙TMOS任务调度之外还有包含一个串口透传的调度器。这个是整个蓝牙-串口透传应用中负责“数据搬运与调度”的核心函数。它位于主循环中不停地被调用起到连接串口中断硬件层和TMOS任务应用层的构建桥梁作用。它主要处理两个任务RX 方向串口 - 蓝牙检测是否收到了数据如果收到了就安排任务让蓝牙发出去。TX 方向蓝牙 - 串口检查硬件是否空闲把缓存里的数据填入硬件发送出去。二、peripheral.c在peripheral.c中与例程中的从机程序其实没有差别无法就是多了一个串口初始化蓝牙串口传输数据的任务事件这也是蓝牙串口透传的最重要的部分这部分程序可以详细学习一下。先看一下初始化定义部分是整个蓝牙-串口透传程序的“地基”部分。它通过定义大小不对称的缓冲区、环形队列管理、中断标志位以及溢出保护机制为后面透传功能构建数据处理基础设施。在串口初始中 先是初始化软件缓冲区 (FIFO)这里设置串口3作为透传串口开启串口中断功能在 UART3_DefInit()串口3硬件外设初始化中 UART3_BaudRateCfg(115200)可以看出串口波特率设置为115200 bps同时开启硬件 FIFO设置4字节的触发。这就可以修改串口的波特率的值也可以修改硬件FIFO触发字节可以改为7字节触发。// 7字节触发 R8_UART3_FCR (3 6) | RB_FCR_TX_FIFO_CLR | RB_FCR_RX_FIFO_CLR | RB_FCR_FIFO_EN;在串口的中断服务函数中主要的部分还是在接收超时机制中。在UART_II_RECV_RDY和UART_II_RECV_TOUT中如果对方发了 100 个字节前面 96 个字节触发的是RDY中断满4个触发一次。最后剩下的 4 个字节如果不满触发水位或者数据流断了就会触发TOUT超时中断。所以这两个都要处理才能保证数据一个不漏。error app_drv_fifo_write_from_same_addr(app_uart_rx_fifo, (uint8_t *)R8_UART3_RBR, R8_UART3_RFC); if(error ! APP_DRV_FIFO_RESULT_SUCCESS) { for(uint8_t i 0; i R8_UART3_RFC; i) { //fifo full,put to fifo black hole for_uart_rx_black_hole R8_UART3_RBR; // 读出来扔掉 } } uart_rx_flag true;这个函数的使用是循环从同一个地址(R8_UART3_RBR) 读出RFC个字节顺次写入到软件缓冲区 (app_uart_rx_fifo)。还做了溢出保护的处理当软件缓冲区2048字节满了但串口还在进数据即使存不下也 必须从硬件寄存器里读出来。硬件才知道取走了数据才会清除中断标志。如果因为存不下就不读中断标志不消CPU 就会一直卡在这个中断函数里出不去的情况。最后在将uart_rx_flag置位告诉主循环中app_uart_process函数将数据发给蓝牙处理这个app_uart_process()函数是放置在main函数的主循环while(1)中被极高频率地反复调用。是整个蓝牙透传应用中的“数据调度员”。它连接了硬件层串口中断/寄存器与应用层蓝牙协议栈任务负责确保数据能够流畅地在串口与蓝牙之间能够相互通信。1、在串口接收处理UART接收 —————— 蓝牙发送先是通过关闭中断这个是防止这样的一种情况代码刚读到flag为 true准备去处理突然来了一个新中断又写了一次flag主循环接着往下执行把flag清除为false就会导致那个新中断的事件被漏处理所以在关中断保证了操作的安全性。再通过检测uart_rx_flag标志位置位后。启动TMOS一个任务事件将串口接收的数据发送到蓝牙上利用待延时的TMOS事件任务是为了拼包。串口数据是一个接一个字节流式到达的如果收到 1 个字节就马上让蓝牙发一个包效率极低且浪费带宽。延时一小会儿可以让软件 FIFO 里多攒几个字节到时候一次性打包发给手机提高吞吐量。等数据发送完成之后在将标志位清空并且恢复中断2、在串口发送处理蓝牙接收 —————— 串口发送是将手机上蓝牙的数据接收到之后在通过串口TX、RX脚发送出去//tx process // 1. 检查硬件 FIFO 是否有空位 if(R8_UART3_TFC UART_FIFO_SIZE){ // 2. 从软件 FIFO 读取数据 - 写入硬件发送寄存器 app_drv_fifo_read_to_same_addr(app_uart_tx_fifo, (uint8_t *)R8_UART3_THR, UART_FIFO_SIZE - R8_UART3_TFC); }先是通过判断R8_UART3_TFC这是一个硬件寄存器它告诉你当前 UART 硬件内部的发送缓冲区里还剩多少字节没发完。UART_FIFO_SIZE硬件缓冲区的总容量(8字节)。只有当即硬件还有空位时才进行操作app_drv_fifo_read_to_same_addr函数。这是一个高效搬运函数。它从软件缓冲区app_uart_tx_fifo取数据这里面存着蓝牙收到的数据写入目标地址(uint8_t *)R8_UART3_THR。这是UART 发送保持寄存器往这个固定地址写数据硬件就会自动把数据挤出去。搬运数量为UART_FIFO_SIZE - R8_UART3_TFC也就是硬件还能塞进多少字节就正好填多少填满为止绝不溢出。接着看一下蓝牙数据接收的回调函数on_bleuartServiceEvt(uint16_t connection_handle, ble_uart_evt_t *p_evt)当手机或其他蓝牙主机设备通过蓝牙向设备发送数据时蓝牙协议栈会自动调用这个函数来通知并把数据交给设备处理。connection_handle: 连接句柄告诉是哪个手机发的在多设备连接的情况下。p_evt: 事件结构体包含了事件类型type和具体数据data。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; // 把收到的数据 (p_evt-data.p_data) 写入 RX FIFO (本来 RX FIFO 是存串口收到的数据的) app_drv_fifo_write(app_uart_rx_fifo, (uint8_t *)p_evt-data.p_data, to_write_length); // 立刻启动发送任务把 RX FIFO 里的数据也就是刚才收到的数据通过蓝牙发回给手机 tmos_start_task(Peripheral_TaskID, UART_TO_BLE_SEND_EVT, 2); //end of nofify back test //ble to uart 把手机发来的数据通过串口发出去 app_uart_tx_data((uint8_t *)p_evt-data.p_data, p_evt-data.length);BLE_UART_EVT_BLE_DATA_RECIEVED在这个是事件中接收到蓝牙数据。先是打印接收的数据长度然后再做了一个回环测试将手机发什么数据从机设备就立刻用蓝牙回发给手机什么数据。这个是方便调试实际应用中这段可以删除。//ble to uart 把手机发来的数据通过串口发出去 app_uart_tx_data((uint8_t *)p_evt-data.p_data, p_evt-data.length);最后这个app_uart_tx_data是透传串口输出将蓝牙主机手机发送的数据都由从机的串口输出。把要发送的数据来自蓝牙接收到的数据安全地存入“发送缓冲区TX FIFO”等待主循环在空闲时把它们搬运到串口硬件发出去。app_uart_tx_fifo: 发送缓冲区。 data: 要发送的数据源。 write_length: 指向长度变量的指针。app_drv_fifo_write(app_uart_tx_fifo, data, write_length);写入软件 FIFO 这个也对应着主循环中(app_uart_process) 会在 CPU 空闲时一点一点把缓冲器的数据搬给串口硬件通过 TX 引脚将数据发出。三、UART_TO_BLE_SEND_EVT的tmos任务事件说明在peripheral.c中蓝牙的TMOS 系统中的UART_TO_BLE_SEND_EVT事件是蓝牙透传功能中最核心的部分它将周期性地/被触发地检查“串口接收缓冲区”把里面的数据取出来打包成蓝牙数据包Notification发送给手机。// 将串口收到的数据 - 发送给蓝牙 if(events UART_TO_BLE_SEND_EVT) { printf(\r\n [%s]-[%s]-[%u] \r\n, __FILE__, __FUNCTION__, __LINE__ ); static uint16_t read_length 0; uint8_t result 0xff; // 状态机判断当前是“正常发送”还是“发送失败重试” switch(send_to_ble_state) { case SEND_TO_BLE_TO_SEND: //正常状态去 FIFO 取数据发送 //notify is not enabled 检查连接状态和通知开关 // 如果手机没订阅通知Notify或者根本没连接就不能发数据 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); // 直接清空 RX FIFO防止连上新手机后收到旧垃圾数据。 } break; } read_length ATT_GetMTU(peripheralConnList.connHandle) - 3; //计算本包最大能发多少字节 (MTU - 3字节协议头) //检查 FIFO 里的数据够不够拼成一个满包 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); // 从 FIFO 读数据到 to_test_buffer uart_to_ble_send_evt_cnt 0; // 清零超时计数器 } else // 不够一个包 { // 检查是不是超过10次循环 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; // 计数器1 PRINT(NO TIME OUT\r\n); } } // 开始发送数据 从 FIFO读到了数据 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); // 把数据从临时 buffer 拷贝到蓝牙内存 result ble_uart_notify(peripheralConnList.connHandle, noti, 0); // 调用底层 ble_uart_notify 发送通知 if(result ! SUCCESS)// 发送失败 { PRINT(R1:%02x\r\n, result); send_to_ble_state SEND_TO_BLE_SEND_FAILED; // 切换状态机下次进来直接重试不再去 FIFO 取新数据 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); // 马上下一次任务事件看看 FIFO 里还有没有剩余数据要发 } } 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); }在事件任务中分为两个部分一个是SEND_TO_BLE_TO_SEND正常模式直接通过FIFO取新数据发送SEND_TO_BLE_SEND_FAILED重发模式发送失败后还捏着数据进行重发处理不取新数据。正常发送 (Case: SEND_TO_BLE_TO_SEND)第一步手机连上蓝牙从机后手机是否开启通知Notify如果没连上GAP_CONNHANDLE_INIT说明蓝牙断了。此时串口还在进数据的话为了防止堆积直接清空接收缓冲区 (app_drv_fifo_flush)然后直接退出。第二步计算蓝牙一包能发多大read_length ATT_GetMTU() - 3。能发的最大有效数据。第三步系统会检查 FIFO串口缓冲区里有多少数据如果数据足够就可以直接准备发送数据如果数据不够就得进行超时判断如何超时了就直接将数据读到多少直接发送多少。第四步如果开始发送数据 从 FIFO读到了数据(result SUCCESS)调用GATT_bm_alloc向蓝牙协议栈申请一块内存。tmos_memcpy把数据从to_test_buffer拷贝到申请的内存里。调用ble_uart_notify发送。判断发送结果发送成功状态保持为SEND_TO_BLE_TO_SEND正常。立马触发下一次任务 (tmos_start_task(..., 2))看看 FIFO 里还有没有剩下的数据接着发。发送失败切换状态为SEND_TO_BLE_SEND_FAILED。释放内存GATT_bm_free。延时重发触发任务延时一会再调用TMOS任务重新发送数据。重发模式 (Case: SEND_TO_BLE_SEND_FAILED)当发送失败且状态是 FAILED 时直接跳过 FIFO 读取步骤直接使用static read_length和to_test_buffer重新申请内存GATT_bm_alloc。重新发送ble_uart_notify。发送成功将状态改回SEND_TO_BLE_TO_SEND恢复正常并去处理下一包数据。发送失败状态不变释放内存继续等待下一次重发机制。整个蓝牙透传大体的程序是这样UART - BLE:中断将数据从串口硬件搬到rx_fifo。uart_rx_flag触发TMOS 任务。任务中将数据从rx_fifo取出根据MTU 分包调用ble_uart_notify发送。BLE - UART:蓝牙回调收到数据存入tx_fifo。主循环检测到tx_fifo有数据且硬件空闲将数据填入串口硬件寄存器发送。