SMARTWATCH 学习手册有 C 基础 · 嵌入式从零开始
支线二 / CH582M 开发与蓝牙桥接 / 05

手机写入之后,字节怎样到达 STM32

从一次 FFE2 写入出发,依次跟过库回调、事件回调、软件队列与串口寄存器。这里不增加新的业务协议,沿用主线的消息格式。

接续上一篇:手机找到的服务和特征,怎样变成代码

1 · 先问这一层应该处理什么

手机可能发设置时间、用户资料或原型传输命令。CH582M 的桥接层只负责转交收到的字节,不根据 0xAA 去解释业务帧。应用帧的长度、命令和校验由 STM32 与 App 按协议处理。

每一步都明确字节当前存在哪里;桥接过程不修改业务内容。
每一步都明确字节当前存在哪里;桥接过程不修改业务内容。 · 教学示意

2 · 库回调已经告诉我们写到了哪个位置

ble_uart_WriteAttrCB 先区分描述符写入与业务值写入。命中接收特征的 handle 后,把指针与长度放入 evt,调用应用回调。这里的 RX 以 CH 的蓝牙服务为观察点,表示从手机收到数据。

服务写入回调中的片段 · 事件名沿用源码拼写

evt.type = BLE_UART_EVT_BLE_DATA_RECIEVED;
evt.status = SUCCESS;
evt.data.length = (uint16_t)len;
evt.data.p_data = pValue;
ble_uart_AppCBs(connHandle, &evt);
status = evt.status;

数据指针来自本次调用,回调结束后不能假定仍可长期使用。项目下一步把数据复制进自己的队列,解决了数据需要稍后发送的保存问题。

3 · 回调先交给队列,让串口慢慢发送

on_bleuartServiceEvt 的接收分支

case BLE_UART_EVT_BLE_DATA_RECIEVED:
    p_evt->status = app_uart_tx_data(
        p_evt->data.p_data, p_evt->data.length);
    break;

app_uart_tx_data 调用 app_drv_fifo_write_all,要求这一整块都能放入。如果空间不足,它增加拒绝计数并返回资源不足。这样不会只收下半块,却向上层表示整块已接受。已有的字节仍由主循环继续发送。

4 · 发送队列不等于串口硬件缓冲

软件队列在 RAM 数组中,串口硬件还有自己的发送缓冲。app_uart_process 读取 R8_UART1_TFC,确认硬件还能接纳多少字节,然后把对应数量写入 R8_UART1_THR。同一个寄存器被反复写,每次写入一个待发送字节。

因此主循环需要不断执行。长时间堵在蓝牙回调中,会拖慢这一步,软件队列也更容易积压。串口接收方还必须把分次收到的字节持续交给帧解析器,不能把一次中断当作消息边界。

5 · 接受写入与业务执行成功,分开观察

有响应写入可以报告属性写入失败;无响应写入没有同样的属性响应可依靠。即使写入已接受,也只说明某层接收了字节,不说明 STM32 已经执行设置。需要可靠确认的命令仍应按应用协议等待对应响应。

动手观察

在 服务回调 与 串口交接 中分别找到同一段数据的指针和长度。联调时发送一条主线已定义的命令,在 STM32 接收侧核对原始字节,再观察业务响应;不要在 CH 上添加回声,混淆真正响应。

资料与代码