在嵌入式开发中,通过UART串口让MCU与传感器模块通信是一项基本功。然而,从连接好硬件到真正读出有意义的数据,这个过程往往充满了各种意想不到的“坑”。本文将完整复盘一次使用STM32F4系列单片机调试维特智能HWT101陀螺仪的全过程,记录我们如何从最初的“角度始终为0”,一步步定位问题,最终成功读取数据的完整心路历程。

最初的目标与困境

我们的目标很简单:通过STM32的USART2接口,接收HWT101陀螺仪发送的角度数据,并将其打印出来。

但现实是残酷的,程序下载后,串口监视器上打印出的 global_angle 始终是 0.00。这是每一个嵌入式工程师都遇到过的经典“噩梦开局”。

问题1:中断到底触发了吗?

  • 诊断:数据处理的核心在 HAL_UART_RxCpltCallback 中断回调函数里。如果这个函数从未被执行,一切都无从谈起。

  • 验证方法:在回调函数的第一行加入一个 printf 语句,例如 printf("Interrupt Fired!\n");。

  • 结果:串口监视器没有任何输出。这100%确定了问题在于中断没有被触发

问题2:中断为什么不触发?
中断不触发,无外乎三个原因:

  1. 硬件接线错误:TX/RX接反,或者没有共地(GND)。

  2. CubeMX配置错误:最常见的是在NVIC设置里,忘记勾选使能 USART2 global interrupt。

  3. 软件逻辑错误:使用了中断接收 HAL_UART_Receive_IT,但从未在 main 函数中调用它来开启第一次接收!这是一个“鸡生蛋”的逻辑链,必须有人先踢出第一脚。

我们通过检查,发现是第3个原因。于是在 main 函数的 while(1) 之前,添加了 HAL_UART_Receive_IT(&huart2, &RxByte, 1);。

问题3:中断触发了,但收到的数据全是 0x00

  • 现象:printf 终于开始打印 Interrupt Fired! Received: 0x00。

  • 诊断:这是波特率不匹配的经典现象。发送方和接收方的时钟不同步,导致接收方在错误的时间点采样,将有效信号误读为 0x00 或其他乱码。

  • 解决方案:查阅HWT101的数据手册!手册第4页明确指出,模块的默认波特率是 9600。我们将CubeMX中USART2的波特率从115200修改为9600。

问题4:收到真实数据了,但角度依然为0

  • 现象:接收到的数据不再是 0x00,而是 0x55, 0x52, 0x53 等看起来有意义的数据流,但角度依然不更新。

  • 诊断:这说明单个字节的接收已经正确,但将字节拼接成完整数据包的逻辑(状态机)不够健壮。它可能因为偶尔的坏数据或数据帧粘连而导致“失步”,后续再也无法正确拼出一个完整的11字节数据包。

  • 解决方案:重写一个更简单、更鲁棒的状态机。核心逻辑是:只有当接收到包头 0x55 时,才开始填充数据包缓冲区;当缓冲区满11个字节后,立刻进行解析,然后将索引清零,等待下一个 0x55 包头。这可以保证即使中间出错,也能在下一个包头到来时自动重新同步。

至此,所有问题都已解决,角度数据成功打印!

最终的正确代码

以下是经过层层调试后,最终可以稳定工作的核心代码文件。

HWT101.h (头文件)
#ifndef __HWT101_H
#define __HWT101_H
 
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "main.h"

// 使用 extern 声明全局变量,供其他文件使用
extern volatile float global_angle;
extern volatile uint8_t new_data_received;
extern volatile float angular_velocity_y;
extern volatile float angular_velocity_z;

// 函数原型声明
void ParseAndPrintData(uint8_t *data, uint16_t length);
void HWT101_Init(void);

#endif
HWT101.c (驱动及解析)
#include "stm32f4xx.h"
#include "HWT101.h"
#include "main.h"

// 定义全局变量实体
volatile float global_angle = 0.0f;
volatile uint8_t new_data_received = 0;
volatile float angular_velocity_y = 0.0f;
volatile float angular_velocity_z = 0.0f;

// 外部引用在main.c中定义的huart2句柄
extern UART_HandleTypeDef huart2;

// 重置Z轴角度的指令
uint8_t reset_z_axis[] = {0xFF, 0xAA, 0x76, 0x00, 0x00};

void HWT101_Init(void)
{
    // 通过串口2发送重置Z轴的指令
    HAL_UART_Transmit_IT(&huart2, reset_z_axis, sizeof(reset_z_axis));
}

// 计算校验和
uint8_t CalculateChecksum(uint8_t *data, uint16_t length) 
{
    uint8_t checksum = 0;
    // 注意:校验和计算不包含最后一个字节(SUM本身)
    for (uint16_t i = 0; i < length - 1; i++) {
        checksum += data[i];
    }
    return checksum;
}

// 解析并打印数据
void ParseAndPrintData(uint8_t *data, uint16_t length) {
    if (length == 11 && data[0] == 0x55) {
        
        uint8_t checksum = CalculateChecksum(data, length);
        if (checksum != data[length - 1]) {
            // printf("Checksum error!\n");
            return; // 校验和错误,直接丢弃
        }
 
        // 检查数据包类型为角度输出
        if (data[1] == 0x53) {
            uint8_t yaw_l = data[6];
            uint8_t yaw_h = data[7];
            int16_t yaw = (int16_t)((yaw_h << 8) | yaw_l);
 
            float angle = ((float)yaw / 32768.0f) * 180.0f;
            global_angle = angle;
            new_data_received = 1;
        }
    }
}
main.c (主逻辑与中断回调)
#include "main.h"
#include "stdio.h" 
#include "HWT101.h"
// ... 其他 include ...

// ... CubeMX生成的代码 ...

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
uint8_t RxByte; // 用于接收单个字节的全局变量
/* USER CODE END PV */

// ... fputc重定向等代码 ...

int main(void)
{
  // ... 系统初始化代码 ...
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  // ★★★ 关键:启动第一次串口中断接收 ★★★
  HAL_UART_Receive_IT(&huart2, &RxByte, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    printf("Angle: %.2f\n", global_angle);
    HAL_Delay(100); // 增加延时,避免刷屏
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

// ... 系统时钟配置等函数 ...

/* USER CODE BEGIN 4 */

/**
  * @brief  串口接收完成回调函数
  * @param  huart: UART handle
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 判断是否是USART2的中断
    if (huart->Instance == USART2)
    {
        static uint8_t rx_buffer[11];
        static uint8_t rx_index = 0;

        // 步骤1:只在寻找包头时关心0x55
        if (rx_index == 0)
        {
            if (RxByte == 0x55)
            {
                rx_buffer[rx_index] = RxByte;
                rx_index++;
            }
        }
        // 步骤2:如果已经找到包头,则继续填充缓冲区
        else
        {
            rx_buffer[rx_index] = RxByte;
            rx_index++;

            // 步骤3:如果缓冲区已满11个字节
            if (rx_index >= 11)
            {
                // 调用解析函数
                ParseAndPrintData(rx_buffer, 11);
                
                // 步骤4:无论解析成功与否,都必须清零索引,准备接收下一个新包
                rx_index = 0; 
            }
        }
        
        // 步骤5:重新开启下一次中断接收
        HAL_UART_Receive_IT(&huart2, &RxByte, 1);
    }
}

/* USER CODE END 4 */

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐