STM32物联网协议栈移植:HAL库下Gizwits工程化集成指南
1. 物联网协议栈工程移植的核心逻辑与实践路径
在嵌入式物联网产品开发中,将第三方协议栈(如机智云Gizwits)集成进自有STM32工程,绝非简单的文件拷贝。其本质是一次系统级的接口适配与资源重映射过程——需要在保持协议栈业务逻辑完整性的同时,将其底层通信、时序、复位等硬件依赖项,精准嫁接到目标MCU的外设驱动模型上。本节以STM32F103系列为基准平台,系统阐述从协议栈源码解包到功能闭环的完整工程化路径,所有操作均基于HAL库标准框架,不引入任何非官方抽象层。
1.1 协议栈工程结构解析:识别可移植与不可修改模块
机智云MCU SDK生成的工程包(以
gizwits_product.c/h
为核心)采用分层设计,其文件组织严格遵循“协议逻辑”与“硬件抽象”分离原则。理解各模块职责边界,是避免移植后功能异常的前提。
| 文件/目录 | 类型 | 核心职责 | 移植要求 |
|---|---|---|---|
gizwits_product.c/h
| 协议核心 | 实现数据点(DataPoint)状态机、事件回调注册、云端指令解析、本地控制逻辑封装 | 必须移植,但禁止修改内部逻辑 |
gizwits_protocol.c/h
| 协议封装 |
完成GAgent通信协议(含包头、校验、压缩)的序列化/反序列化,提供
gizwits_uartWrite()
等API
| 必须移植,禁止修改协议格式 |
gizwits_wifi.c/h
| 硬件适配 | 提供WiFi模块串口收发、复位、状态查询等底层函数, 此处为移植关键区 | 必须重写,替换为STM32驱动 |
ring_buffer.c/h
| 公共组件 | 环形缓冲区实现,用于暂存WiFi模块突发下发的多帧数据 | 直接移植,无需修改 |
datapoint.c/h
| 工具函数 | 数据点压缩/解压、CRC校验等算法实现 | 直接移植,无需修改 |
需特别注意:
gizwits_product.c
中定义的
dataPoint_t
结构体与
gizwits_product.h
中声明的数据点枚举(
DPID_xxx
),与机智云平台创建产品时配置的数据点严格一一对应。若平台端增删数据点,必须同步更新此结构体及枚举定义,否则将导致协议解析失败或数据错位。此为工程一致性铁律,无例外。
1.2 工程目录重构:建立清晰的硬件-协议隔离层
在自有STM32工程(以STM32CubeMX生成的HAL工程为例)中,严禁将协议栈文件混入
Src/Inc
根目录。必须构建物理隔离的目录结构,明确划分职责:
Project/
├── Core/
│ ├── Inc/
│ └── Src/
├── Drivers/
│ ├── STM32F1xx_HAL_Driver/
│ └── BSP/ // 板级支持包
├── Middleware/ // 中间件层(协议栈专属)
│ └── Gizwits/
│ ├── Inc/ // gizwits_*.h, ring_buffer.h, datapoint.h
│ └── Src/ // gizwits_*.c, ring_buffer.c, datapoint.c
├── User/ // 应用层(开发者主战场)
│ ├── Inc/
│ │ └── user_app.h // 声明应用层回调函数原型
│ └── Src/
│ ├── main.c
│ ├── user_app.c // 实现gizwitsEventCallback()等回调
│ └── wifi_hal.c // **关键!** USART2收发、复位、状态检测的HAL封装
└── ...
此结构强制约束:
Middleware/Gizwits/Src/
内所有
.c
文件
仅能调用
Middleware/Gizwits/Inc/
和
User/Inc/
中的头文件,
绝对禁止
直接包含
Drivers/STM32F1xx_HAL_Driver/Inc/
或
Core/Inc/
下的HAL头文件。所有硬件访问必须通过
User/Src/wifi_hal.c
提供的统一接口(如
wifi_uart_receive()
,
wifi_uart_transmit()
)进行。此举确保协议栈逻辑与具体MCU型号解耦,未来迁移到STM32F4或GD32时,仅需重写
wifi_hal.c
即可。
1.3 头文件路径与依赖注入:构建编译时契约
将
Middleware/Gizwits/Inc/
添加至工程Include Path是基础,但更关键的是建立正确的头文件依赖链。
gizwits_protocol.c
的编译成功,依赖于三个层级的头文件正确包含:
-
协议栈自身依赖
:
gizwits_protocol.h必须包含gizwits_product.h(提供数据点定义)和ring_buffer.h(提供缓冲区操作)。 -
硬件抽象层注入
:
gizwits_wifi.h中声明的函数(如wifi_uart_transmit())其定义在User/Src/wifi_hal.c中,因此gizwits_protocol.c需包含gizwits_wifi.h,而gizwits_wifi.h本身 不得 包含任何HAL头文件。 -
HAL驱动桥接
:
User/Src/wifi_hal.c是唯一允许包含stm32f1xx_hal.h和usart.h的文件,它负责将gizwits_wifi.h声明的抽象接口,翻译为具体的HAL函数调用(如HAL_UART_Transmit())。
典型错误是直接在
gizwits_protocol.c
中
#include "stm32f1xx_hal_usart.h"
,这会破坏分层,导致协议栈无法跨平台。正确的头文件包含顺序应为:
// gizwits_protocol.c
#include "gizwits_protocol.h" // 主协议头
#include "gizwits_product.h" // 数据点定义
#include "ring_buffer.h" // 缓冲区操作
#include "gizwits_wifi.h" // 硬件抽象接口(关键!)
1.4 编译验证:首个里程碑的达成
完成文件拷贝与目录结构搭建后,执行首次编译(
Build
)。此时预期结果应为
零错误,若干警告(可忽略)
。若出现
undefined reference
错误,99%源于头文件路径缺失或
gizwits_wifi.h
未被正确包含;若出现
unknown type name
,则检查
gizwits_product.h
是否被
gizwits_protocol.h
正确引用。此阶段不追求功能运行,仅验证协议栈代码在编译器眼中是语法合法的C代码。这是工程移植的基石,必须稳固。
2. UART2接收中断适配:构建可靠的数据摄入管道
WiFi模块(如ESP8266/ESP32)与STM32的通信,本质是异步串行数据流。云端下发的指令、APP发送的控制命令,均以不定长、不定时的字节流形式经UART2抵达MCU。协议栈
gizwits_protocol.c
中的
gizwits_uartReceive()
函数,正是处理此数据流的入口。其正确工作,完全依赖于一个健壮的、永不丢包的底层接收机制。
2.1 环形缓冲区(Ring Buffer):应对数据洪峰的必然选择
为何不能在UART2中断服务程序(ISR)中直接调用
gizwits_uartReceive()
?根本原因在于协议栈解析是计算密集型操作,若在ISR中执行,将极大延长中断响应时间,导致后续接收中断被覆盖(Overrun),丢失数据。解决方案是引入环形缓冲区作为“数据蓄水池”。
ring_buffer.c
实现了一个经典的生产者-消费者模型:
*
生产者(Producer)
:UART2的接收中断服务程序(ISR)。其唯一职责是:
快速地
将接收到的单个字节(
uint8_t
)存入环形缓冲区的
write_index
位置,并原子性地递增
write_index
。此过程耗时极短(<1μs),确保中断及时退出。
*
消费者(Consumer)
:主循环(
while(1)
)或独立任务中调用的
gizwits_uartReceive()
。它从环形缓冲区的
read_index
位置
批量读取
连续字节(通常一次读取
buffer_size
字节),并递增
read_index
。读取后,协议栈立即对这批数据进行解析。
环形缓冲区的关键参数:
*
BUFFER_SIZE
:缓冲区长度,单位字节。建议值≥256。过小易溢出,过大浪费RAM。
*
write_index
,
read_index
:两个无符号整型索引,指向缓冲区首尾。利用模运算(
index % BUFFER_SIZE
)实现环形。
2.2 UART2中断服务程序(ISR)的精确注入
假设UART2已由STM32CubeMX配置为
Asynchronous
模式,
Interrupt
方式使能,且
HAL_UART_Receive_IT()
已在
MX_USART2_UART_Init()
中调用。此时,
USART2_IRQHandler
已被HAL库生成。我们需要在此ISR中,精准插入环形缓冲区写入逻辑。
标准HAL ISR模板(位于
stm32f1xx_it.c
):
void USART2_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart2); // HAL库标准处理:清除标志、触发回调
}
注入后的ISR(关键修改):
extern Ring_Buffer_t gizwits_rx_buf; // 声明全局环形缓冲区实例
void USART2_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart2); // 此调用会触发HAL_UART_RxCpltCallback()
}
// HAL库回调函数,由HAL_UART_IRQHandler()在接收完成时自动调用
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2) // 确保是USART2的回调
{
uint8_t rx_data;
// 1. 从USART2接收数据寄存器读取一字节
rx_data = (uint8_t)(huart->Instance->DR & 0xFF);
// 2. 将该字节写入环形缓冲区
ring_buffer_write(&gizwits_rx_buf, &rx_data, 1);
// 3. 立即启动下一次单字节接收,维持接收流水线
HAL_UART_Receive_IT(huart, &rx_data, 1);
}
}
核心要点解析:
*
为何用
HAL_UART_RxCpltCallback
而非直接在
USART2_IRQHandler
中写?
因为
HAL_UART_IRQHandler()
内部已完成了接收标志清除、错误处理等繁琐工作,
RxCpltCallback
是HAL推荐的、安全的用户扩展点。
*
rx_data
变量的作用域?
必须为
static
或全局,因为
HAL_UART_Receive_IT()
需要其地址。局部变量在中断返回后即失效,会导致接收数据错乱。
*
HAL_UART_Receive_IT()
的持续调用
:这是维持“永远在线”接收的关键。每次接收完一字节,立刻发起下一次接收,确保没有接收间隙。
2.3 协议栈接收入口的绑定与验证
gizwits_protocol.c
中,
gizwits_uartReceive()
函数原型为:
uint16_t gizwits_uartReceive(uint8_t *buf, uint16_t len)
其内部逻辑是:从环形缓冲区
gizwits_rx_buf
中尝试读取最多
len
个字节到
buf
,并返回实际读取长度。
在
User/Src/wifi_hal.c
中,需提供此函数的实现(或直接在
gizwits_protocol.c
中调用
ring_buffer_read()
):
// wifi_hal.c
#include "ring_buffer.h"
extern Ring_Buffer_t gizwits_rx_buf; // 同上
uint16_t gizwits_uartReceive(uint8_t *buf, uint16_t len)
{
return ring_buffer_read(&gizwits_rx_buf, buf, len);
}
验证方法:
在
main()
函数的
while(1)
循环中,周期性(如每100ms)调用
gizwits_uartReceive()
,并将返回值打印至调试串口。当WiFi模块向MCU发送AT指令或GAgent协议包时,此函数应返回非零值,证明数据已成功从UART2流入环形缓冲区,并被协议栈捕获。这是数据摄入管道贯通的第一个实证。
3. UART2发送逻辑实现:构建可控的数据输出通道
MCU向WiFi模块发送数据,是另一条关键通路。
gizwits_protocol.c
中的
gizwits_uartWrite()
函数,负责将协议栈封装好的、符合GAgent规范的完整数据包(含包头、数据、校验)发送出去。其实现必须满足两个硬性要求:
阻塞式发送保证完整性
与
协议兼容性保证正确性
。
3.1 阻塞式发送的必要性与实现原理
为何不能使用
HAL_UART_Transmit_IT()
(中断发送)或
HAL_UART_Transmit_DMA()
(DMA发送)?因为GAgent协议要求:一个完整的数据包(例如
0x55 0xAA ... 0xFF
)必须作为一个不可分割的整体发送。若采用中断或DMA发送,在发送中途若被更高优先级中断打断,或DMA传输被意外终止,将导致数据包被截断,WiFi模块无法识别,进而引发握手失败或连接中断。
因此,
gizwits_uartWrite()
必须是一个
忙等待(Busy-Waiting)
的阻塞函数。其核心逻辑是:循环查询USART2的发送数据寄存器(TDR)是否为空(
TXE
标志),一旦为空,立即将下一个字节写入TDR,直至整个
buf
发送完毕。
3.2 符合GAgent协议的发送函数实现
根据字幕提示,
gizwits_uartWrite()
的实现需特别注意一个协议细节:当待发送数据中连续出现多个
0x55
字节时,WiFi模块要求将其替换为
0x55 0x55
(即字节转义)。但字幕中提到的“第二后面的buffer都是0sf的话”存在明显口误,结合GAgent官方文档,正确的规则是:
对数据内容中的每一个
0x55
字节,均需在前面插入一个额外的
0x55
字节进行转义
。
gizwits_protocol.c
中
gizwits_uartWrite()
的典型实现如下(位于
User/Src/wifi_hal.c
中):
#include "stm32f1xx_hal.h"
#include "usart.h"
// 声明外部HAL UART句柄
extern UART_HandleTypeDef huart2;
uint16_t gizwits_uartWrite(const uint8_t *buf, uint16_t len)
{
uint16_t i = 0;
uint16_t sent = 0;
uint8_t tx_byte;
while(i < len)
{
tx_byte = buf[i];
// GAgent协议:对0x55字节进行转义,发送0x55 0x55
if(tx_byte == 0x55)
{
// 发送第一个0x55
while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE) == RESET); // 等待TDR空
huart2.Instance->DR = 0x55;
sent++;
// 发送第二个0x55(转义符)
while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE) == RESET);
huart2.Instance->DR = 0x55;
sent++;
}
else
{
// 发送原始字节
while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE) == RESET);
huart2.Instance->DR = tx_byte;
sent++;
}
i++;
}
// 等待最后一个字节完成发送(TC: Transmission Complete)
while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC) == RESET);
return sent;
}
关键点说明:
*
__HAL_UART_GET_FLAG()
:直接读取USART状态寄存器(SR),比调用
HAL_UART_GetState()
更高效,适合忙等待场景。
*
huart2.Instance->DR
:直接操作数据寄存器,绕过HAL库的开销,确保最快速度。
*
UART_FLAG_TC
等待:确保整个发送过程彻底结束,避免后续操作干扰。
3.3 调试与性能权衡
阻塞式发送的最大缺点是占用CPU时间。对于一个100字节的包,若波特率为115200bps,理论发送时间约为8.7ms。在此期间,MCU无法响应其他中断(除非更高优先级)。实践中,可通过以下方式优化:
1.
降低发送频率
:确保
gizwits_uartWrite()
只在必要时调用(如数据点变化、心跳包),避免轮询式发送。
2.
提升波特率
:在WiFi模块支持的前提下,将UART2波特率提升至921600bps或更高,可将发送时间压缩至亚毫秒级。
3.
硬件流控(可选)
:若硬件支持RTS/CTS,可在高负载时启用,让WiFi模块主动通知MCU暂停发送,但这增加了硬件复杂度。
4. 毫秒级系统定时器:为协议栈提供精准的时间刻度
GAgent协议栈的正常运转,高度依赖一个稳定、精确的毫秒级(ms)时间源。其核心用途包括:
*
心跳包(Keep-Alive)定时
:每隔固定毫秒(如30000ms)向云端发送心跳,维持长连接。
*
超时重传机制
:当发送指令后未在规定毫秒内收到ACK,触发重发。
*
数据点上报间隔控制
:传感器数据按设定毫秒周期上报。
*
事件去抖动(Debounce)
:对按键等输入信号进行毫秒级延时确认。
4.1 SysTick vs TIM2:选择最适合的定时器
STM32F103提供了多种定时器资源。选择依据是:
是否与FreeRTOS或其他系统级调度器冲突
。
*
SysTick
:是Cortex-M3内核的系统滴答定时器,FreeRTOS默认使用它作为心跳。若项目已使用FreeRTOS,则
绝对禁止
将SysTick用于GAgent协议栈,否则将导致RTOS调度紊乱。
*
TIM2
:通用定时器,资源独立,是本项目的首选。其时钟源为APB1总线时钟(通常为36MHz),通过预分频器(PSC)和自动重装载寄存器(ARR)可精确配置为1ms中断。
4.2 TIM2毫秒定时器的HAL配置与中断服务
在STM32CubeMX中配置TIM2:
*
Clock Source
: Internal Clock
*
Prescaler (PSC)
:
36000 - 1 = 35999
(若APB1=36MHz,则
36MHz / 36000 = 1kHz
)
*
Counter Period (ARR)
:
1000 - 1 = 999
(
1kHz / 1000 = 1ms
)
*
Trigger Event
: Update Event
*
NVIC Settings
: Enable TIM2 global interrupt
生成代码后,
stm32f1xx_it.c
中会自动生成
TIM2_IRQHandler
。在此中断中,只需调用
gizTimerMs()
函数即可:
extern void gizTimerMs(void); // 声明协议栈的毫秒计时器回调
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
// HAL库回调,在TIM2更新中断发生时被调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
gizTimerMs(); // 通知协议栈:又过去1ms
}
}
4.3
gizTimerMs()
函数的声明与链接
gizTimerMs()
是
gizwits_protocol.c
内部定义的函数,其作用是维护一个全局毫秒计数器
gizwitsTimerMs
。为了让
HAL_TIM_PeriodElapsedCallback()
能够调用它,必须在
gizwits_protocol.h
中声明:
// gizwits_protocol.h
#ifndef __GIZWITS_PROTOCOL_H__
#define __GIZWITS_PROTOCOL_H__
// ... 其他已有声明 ...
// 新增:声明毫秒定时器回调函数
void gizTimerMs(void);
#endif
此声明确保了编译器在链接阶段能找到
gizTimerMs
的定义。若遗漏此声明,链接器将报
undefined reference to 'gizTimerMs'
错误。这是工程移植中极易忽略的“链接时契约”,必须严格执行。
5. MCU复位函数实现:赋予协议栈对硬件的最终控制权
在物联网设备的生命周期中,“复位”是最强力的故障恢复手段。GAgent协议栈在检测到严重通信异常(如WiFi模块无响应、固件版本不匹配)时,会调用
mcuRestart()
函数,期望MCU执行一次硬件复位,重启整个系统,以期恢复连接。这是一个典型的“协议栈请求,硬件执行”的权限移交。
5.1
mcuRestart()
的语义与实现要求
mcuRestart()
函数在
gizwits_protocol.c
中通常被声明为
void mcuRestart(void)
,其内部初始实现为空(
{}
)。移植者必须将其重写为一个
真正触发MCU硬件复位
的函数。其行为必须满足:
*
不可逆性
:一旦执行,MCU必须进入复位流程,无法被软件中断或取消。
*
最小化副作用
:复位过程应尽可能干净,不依赖于任何可能已损坏的外设状态(如未初始化的GPIO)。
*
标准性
:采用ARM Cortex-M3标准复位方式,确保跨平台兼容。
5.2 基于NVIC的系统复位实现
最可靠、最标准的复位方式是触发ARM内核的
SYSRESETREQ
信号。此信号由NVIC(嵌套向量中断控制器)的
Application Interrupt and Reset Control Register (AIRCR)
控制。其优势在于:不依赖于任何外设时钟,即使在系统时钟严重异常时仍能工作。
User/Src/wifi_hal.c
中的实现如下:
#include "stm32f1xx_hal.h"
void mcuRestart(void)
{
// 1. 解锁AIRCR寄存器(写入密钥0x05FA)
SCB->AIRCR = (0x05FA << 16) | SCB_AIRCR_SYSRESETREQ;
// 2. 执行DSB(数据同步屏障)和ISB(指令同步屏障)确保指令顺序
__DSB();
__ISB();
// 3. 此后代码永不执行,MCU开始复位流程
}
关键寄存器说明:
*
SCB->AIRCR
:系统控制块(System Control Block)的AIRCR寄存器。
*
0x05FA << 16
:ARM规定的写入密钥,用于防止意外复位。
*
SCB_AIRCR_SYSRESETREQ
:宏定义,值为
0x04000000
,即设置
SYSRESETREQ
位。
5.3 验证复位功能:从协议栈发起的终极测试
验证
mcuRestart()
是否有效,最直接的方法是在
user_app.c
的应用层回调中主动触发:
// user_app.c
#include "gizwits_product.h"
// 假设有一个数据点DPID_RESET,当APP发送"1"时触发复位
bool ICACHE_FLASH_ATTR gizwitsEventProcess(eventInfo_t *info, uint8_t wifiDataPoint)
{
switch(info->event)
{
case EVENT_RESET:
if(info->data[0] == 1)
{
// APP发送了复位指令
mcuRestart(); // 立即执行复位
}
break;
// ... 其他事件
}
return true;
}
将设备接入APP,向
DPID_RESET
数据点下发值
1
。若MCU在1秒内重启(表现为LED熄灭再亮起,或调试串口输出重启日志),则证明
mcuRestart()
实现成功。这是协议栈获得对硬件最终控制权的标志性时刻。
6. 综合调试与常见问题排查:走向稳定运行的最后一步
完成上述五大步骤后,工程已具备基本通信能力,但距离稳定运行仍有距离。此阶段需借助系统化调试手段,定位并解决隐性问题。
6.1 分层调试法:从物理层到应用层逐级验证
调试应遵循“自底向上”原则,每一层验证通过后再进入上一层:
1.
物理层(UART2)
:使用USB-TTL转换器连接PC,发送AT指令给WiFi模块,确认其能正常响应。同时,用逻辑分析仪抓取MCU的USART2_TX引脚波形,验证发送的字节序列是否符合预期(特别是
0x55
转义)。
2.
驱动层(HAL)
:在
HAL_UART_RxCpltCallback()
和
gizwits_uartWrite()
中添加
printf
或LED闪烁,确认中断和发送函数被正确调用。
3.
协议栈层(Gizwits)
:启用
gizwits_protocol.c
中的
DEBUG_PRINT
宏(若存在),观察协议解析日志。重点关注
GIZWITS_LOG("Receive: %d bytes", len)
和
GIZWITS_LOG("Send: %d bytes", sent)
输出。
4.
应用层(User)
:在
gizwitsEventProcess()
中打印接收到的事件类型和数据,确认APP下发的指令能被正确解析并触发回调。
6.2 典型问题与根因分析
| 现象 | 最可能根因 | 排查路径 |
|---|---|---|
编译报
undefined reference
|
gizwits_wifi.h
未被
gizwits_protocol.c
包含;或
gizwits_protocol.h
中未声明
gizTimerMs()
| 检查头文件包含链、函数声明、链接器错误信息 |
| WiFi模块无响应 |
gizwits_uartWrite()
未正确实现,或
0x55
转义逻辑错误
| 用逻辑分析仪抓TX波形,对比协议文档 |
| 云端显示“离线” |
gizTimerMs()
未被调用,导致心跳包无法发送
|
检查TIM2中断是否使能、
HAL_TIM_PeriodElapsedCallback
是否被触发
|
| APP下发指令无反应 |
gizwits_uartReceive()
未被调用,或环形缓冲区
read_index/write_index
不同步
|
在主循环中打印
ring_buffer_get_length()
,确认有数据流入
|
| MCU频繁复位 |
mcuRestart()
被意外调用(如指针越界、堆栈溢出)
|
在
mcuRestart()
开头添加唯一标识LED闪烁,确认触发源
|
6.3 我的经验之谈:几个容易踩坑的细节
-
ring_buffer.c的临界区保护 :若gizwits_uartReceive()在主循环中调用,而ring_buffer_write()在中断中调用,两者对read_index和write_index的访问必须是原子的。ring_buffer.c通常使用__disable_irq()/__enable_irq()进行简单保护,但在FreeRTOS环境下,应改用taskENTER_CRITICAL()/taskEXIT_CRITICAL()。 -
gizwits_product.c的dataPoint_t初始化 :此结构体必须在main()中HAL_Init()之后、MX_GPIO_Init()之前进行清零(memset(¤tDataPoint, 0, sizeof(currentDataPoint)))。否则,未初始化的内存可能包含随机值,导致协议栈误判数据点状态。 -
时钟树配置陷阱
:若在CubeMX中更改了APB1或APB2总线时钟,必须重新计算TIM2的PSC和ARR值,并同步更新
gizwits_protocol.c中所有基于毫秒的常量(如心跳间隔)。最好将这些常量定义为宏,集中管理。
当
gizwits_uartReceive()
开始稳定返回非零值,
gizwits_uartWrite()
能发出合规数据包,
gizTimerMs()
被规律调用,且
mcuRestart()
能成功重启,这个移植工程便已越过最关键的门槛。后续的入网配网、数据点映射、APP联调,都将在这一坚实基础上水到渠成。
更多推荐
所有评论(0)