1

我正在使用 ESP-IDF SDK 开发一个小项目来通过 UART 获取传感器数据。我按照制造商提供的数据表来解析和计算不同参数的值。但是串行的输出不正确,每次我得到不同的输出都是错误的。代码:-

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"

static const int RX_BUF_SIZE = 1024;

#define TXD_PIN (GPIO_NUM_4)
#define RXD_PIN (GPIO_NUM_5)
#define DELAY_IN_MS(t) (((portTickType)t*configTICK_RATE_HZ)/(portTickType)1000)

void init(void) {
    const uart_config_t uart_config = {
        .baud_rate = 4800,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
    };
    uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 129, 0, NULL, 0);
    uart_param_config(UART_NUM_1, &uart_config);
    uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}


static void rx_task(void *arg)
{
    static const char *RX_TASK_TAG = "RX_TASK";
    esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
    uint8_t data[7] = {0};
    uint8_t PR=0,spo2=0,temprature=0;
    while (1) {
        const int rxBytes = uart_read_bytes(UART_NUM_1, data, 7, 1);
        if (rxBytes == 7) {
            printf("The rxbytes %d and %s\n",rxBytes,data);
            PR = (data[3] & 0x7F) + ((data[2] & 0x40)<<1);
            spo2 = (data[4] & 0x7F);
            temprature = (data[5] & 0x7F);
            printf("PR is %d , Spo2 is %d , temperature is %d \n",PR,spo2,temprature);
            ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
            memset(data,0,7);
        }
    }
}

void app_main(void)
{
    init();
    xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL);
}

串行监视器上程序的输出是:- 在此处输入图像描述

制造商提供的数据表是:- https://drive.google.com/file/d/1lPATxeXXreVZkg9Ufg9BnyCrl4EsbJAj/view?usp=sharing

如果我未对齐任何数据格式来计算值,请纠正我。

4

1 回答 1

4

从不可靠的通道(串行)组装消息意味着您不能真正依赖它们总是以您期望的顺序到达而没有任何问题,因此您必须采取预防措施以免收到垃圾邮件。

该代码假定它将始终以 7 字节块的形式接收这些 7 字节消息,但它并不总是以这种方式工作。线路噪音或超时可能会导致以多个块(例如,4 个字节然后 3 个字节)接收正确的消息,或者可能导致字节丢失。

要查看这是否是问题的一部分,请在每次读取时添加日志记录,而不仅仅是您期望的读取:

static void rx_task(void *arg)
{
    ...
    while (1) {
        const int rxBytes = uart_read_bytes(UART_NUM_1, data, 7, 1);

        // Log ALL reads, not just the ones you expect
        ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
        ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

        if (rxBytes == 7) {
                ///
        }
    }
}

这可能会证实我的预感。

在任何情况下,您都不能依赖固定大小的消息,因为一旦它不同步,就永远无法恢复。这意味着您必须建立自己的保护措施。

读取传感器的数据表,它说每个 7 字节消息的第一个字节都设置了高位,因此这非常适合重新同步:在获得起始字节之前忽略所有内容,然后再读取 6 个字节,然后你有一个完整的消息。

因此,您最终需要两个缓冲区:一个用于您正在组装的消息,另一个用于从传感器执行原始 I/O,在您验证同步时复制到真实的消息缓冲区。

一个快速而肮脏的方法如下所示:

static void rx_task(void *arg)
{
    static const char *RX_TASK_TAG = "RX_TASK";
    esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);

    // sensor message we're trying to build
    uint8_t message[7] = {0};
    uint8_t *msgnext = message;

    while (1) {
        uint8_t inbuf[7];

        const int rxBytes = uart_read_bytes(UART_NUM_1, inbuf, sizeof inbuf, 1);

        ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, inbuf);
        ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, inbuf, rxBytes, ESP_LOG_INFO);

        // error/timeout? do something?
        if (rxBytes <= 0) continue;

        for (int i = 0; i < rxBytes; i++)
        {
            const uint8_t b = inbuf[i];

            if (b & 0x80)
            {
                // First byte of a message, reset the buffer
                 msgnext = message;
                *msgnext++ = b;
            }   

            else if (msgnext == message)
            {
                // not synced yet, ignore this byte
                continue;
            }
            else
            {
                *msgnext++ = b;

                if ((msgnext - message) == sizeof message)
                {
                    // WE FOUND A FULL MESSAGE
                    uint8_t PR   = (message[3] & 0x7F) + ((message[2] & 0x40)<<1);
                    uint8_t spo2 = (message[4] & 0x7F);
                    uint8_t temperature = (message[5] & 0x7F);
                    printf("PR is %d, Spo2 is %d, temperature is %d\n",
                         PR,spo2,temperature);

                    msgnext = message; // reset to empty the buffer
                }
            }
        }
    }
}

这个想法是您的原始 I/O 已完成inbuf,它首先查找同步字节(设置了高位),然后告诉您开始将数据复制到真正的传感器缓冲区message。获得 7 个字节后,它会显示结果并重置缓冲区。

即使你有几个字节的有效message数据,如果另一个SYNC 字节进来,它会假设之前的消息被搞砸了,所以它把它扔掉并开始一个新的新缓冲区。

您可以在此处添加更多内容,例如对超时的支持,或在丢弃部分消息时检测/记录,但在任何情况下都不能避免此数据帧层。

此外,I/O 缓冲区不必inbuf与消息大小相同,从 UART 读取一个字节的块可能是有意义的;在多任务操作系统中我可能不会这样做,但在 ESP 环境中它可能有意义 - 不知道。这将简化一些循环。

编辑查看您的实际数据转储,很明显您的消息没有正确构建,因为即使您有 7 个字节,SYNC 字节(设置了高位)也会在中间的某个地方找到,但每次都不是同一个地方。显然,这是一个框架问题。

显示未对齐框架的数据捕获

于 2020-11-28T15:52:45.437 回答