LC76G GPS模块 I2C通信不稳定

我有一批LC76G模块,用I2C进行测试时,有时能正确通过0x50和0x54通信,但是更多时候,无法向0x50写入指令或者无法从0x54读取数据。网上看到一些关于缓冲区流程导致阻塞bug的话题,但是找不到I2C官方指导文档,以及这个模块是否有固件更新?

自问自答。感谢官方给了一份文档。

全网看了一下,目前能找到的参考代码都只有读写0x50和0x54,没有写入0x58。丢失了官方文档中错误恢复的步骤,以下代码仅供参考,ESP32 S3 IDF 5.4.1 LC76G,能从一些意外场景中恢复。如果I2C通信没有按照官方文档步骤走完,例如单片机重启了,但是LC76G没重启,就有可能导致之前的读取流程没有完全结束,重启后的单片机开始新的读取,将会失败。

// FOR function vTaskDelay
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"

// FOR function ESP_LOGI ESP_LOGE ESP_LOGW
#include "esp_log.h"

// ESP32 new I2C Driver
#include "driver/i2c_master.h"

#define TAG "m_lc76g"

#define I2C_MASTER_FREQ_HZ 100000
#define DEVICE_CR_OR_CW_ADDRESS 0x50
#define DEVICE_R_ADDRESS 0x54
#define DEVICE_W_ADDRESS 0x58
#define MAX_ERROR_NUMBER 20

// Author sun0x00

static i2c_master_dev_handle_t device_cr_or_cw_handle;
static i2c_master_dev_handle_t device_r_handle;
static i2c_master_dev_handle_t device_w_handle;
static uint32_t error_count = 0;

static void add_i2c_device()
{
    // 增加device 0x50
    i2c_device_config_t device_cr_or_cw_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = DEVICE_CR_OR_CW_ADDRESS,
        .scl_speed_hz = I2C_MASTER_FREQ_HZ};
    // 增加device 0x54
    i2c_device_config_t device_r_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = DEVICE_R_ADDRESS,
        .scl_speed_hz = I2C_MASTER_FREQ_HZ};
    // 增加device 0x58
    i2c_device_config_t device_w_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = DEVICE_W_ADDRESS,
        .scl_speed_hz = I2C_MASTER_FREQ_HZ};
    // 获取muster bus 0
    static i2c_master_bus_handle_t i2c_master_bus_handle;
    ESP_ERROR_CHECK(i2c_master_get_bus_handle(0, &i2c_master_bus_handle));
    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_master_bus_handle, &device_cr_or_cw_cfg, &device_cr_or_cw_handle));
    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_master_bus_handle, &device_r_cfg, &device_r_handle));
    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_master_bus_handle, &device_w_cfg, &device_w_handle));
}
static void remove_i2c_device()
{
    // 删除device
    ESP_ERROR_CHECK(i2c_master_bus_rm_device(device_cr_or_cw_handle));
    ESP_ERROR_CHECK(i2c_master_bus_rm_device(device_r_handle));
    ESP_ERROR_CHECK(i2c_master_bus_rm_device(device_w_handle));
};

static int write_data()
{

    // 向地址0x50分别发送两个4字节数据:0xAA510004 和 0x00000004,小端模式,读取缓冲区可用长度
    uint8_t read_buffer_available_length_cmd_data[] = {0x04, 0x00, 0x51, 0xAA, 0x04, 0x00, 0x00, 0x00};

    if (i2c_master_transmit(device_cr_or_cw_handle, read_buffer_available_length_cmd_data, sizeof(read_buffer_available_length_cmd_data), -1) != ESP_OK)
    {
        error_count++;
        ESP_LOGE(TAG, "failed to write read_buffer_available_length_cmd_data to device");
        return -1;
    };
    // 延迟至少10ms 文档要求
    vTaskDelay(pdMS_TO_TICKS(10));

    uint8_t buffer_available_length_data[4] = {0};
    if (i2c_master_receive(device_r_handle, buffer_available_length_data, sizeof(buffer_available_length_data), -1))
    {
        error_count++;
        ESP_LOGE(TAG, "failed to read buffer_available_length_data from device");
        return -1;
    }
    // 延迟至少10ms 文档要求
    vTaskDelay(pdMS_TO_TICKS(10));

    uint32_t buffer_available_length = (buffer_available_length_data[0]) | (buffer_available_length_data[1] << 8) | (buffer_available_length_data[2] << 16) | (buffer_available_length_data[3] << 24);

    ESP_LOGI(TAG, "buffer available length: %lu", buffer_available_length);

    // 官方文档说缓冲区满后会I2c会休眠,发送任意数据恢复
    uint8_t dummy_data[1] = {0x00};
    // 向地址0x50分别发送两个4字节数据:0xAA531000 和 data_written_len (dummy_data==1)
    uint8_t write_data_cmd_data[] = {0x00, 0x10, 0x53, 0xAA, 0x01, 0x00, 0x00, 0x00};

    if (i2c_master_transmit(device_cr_or_cw_handle, write_data_cmd_data, sizeof(write_data_cmd_data), -1) != ESP_OK)
    {
        error_count++;
        ESP_LOGE(TAG, "failed to write write_data_cmd_data to device");
        return -1;
    };
    vTaskDelay(pdMS_TO_TICKS(10));
    // 向地址0x58发送1字节数据
    if (i2c_master_transmit(device_w_handle, dummy_data, 1, -1) == ESP_OK)
    {
        ESP_LOGE(TAG, "failed to write dummy_data to device");
        return -1;
    }

    return 0;
}

static int read_data()
{

    // 向地址0x50分别发送两个4字节数据:0xAA510008 和 0x00000004,小端模式,读取缓冲区长度
    uint8_t read_length_cmd_data[] = {0x08, 0x00, 0x51, 0xAA, 0x04, 0x00, 0x00, 0x00};

    if (i2c_master_transmit(device_cr_or_cw_handle, read_length_cmd_data, sizeof(read_length_cmd_data), -1) != ESP_OK)
    {
        error_count++;
        ESP_LOGE(TAG, "failed to write read_length_cmd_data to device");
        return -1;
    };
    // 延迟至少10ms 文档要求
    vTaskDelay(pdMS_TO_TICKS(10));

    uint8_t data_length_return[4] = {0};
    if (i2c_master_receive(device_r_handle, data_length_return, sizeof(data_length_return), -1))
    {
        error_count++;
        ESP_LOGE(TAG, "failed to read data_length_return from device");
        return -1;
    }
    // 延迟至少10ms 文档要求
    vTaskDelay(pdMS_TO_TICKS(10));

    uint32_t data_length = (data_length_return[0]) | (data_length_return[1] << 8) | (data_length_return[2] << 16) | (data_length_return[3] << 24);

    // 数据长度可能为0,0则不处理
    if (data_length == 0)
    {
        error_count++;
        ESP_LOGW(TAG, "invalid data length: %lu", data_length);
        return -1;
    }
    else
    {
        ESP_LOGI(TAG, "data length: %lu", data_length);
    }

    // 向地址0x50分别发送两个4字节数据:0xAA512000 和 data_read_len
    uint8_t read_data_cmd_data[] = {0x00, 0x20, 0x51, 0xAA, 0x00, 0x00, 0x00, 0x00};
    // 拼接读取回来的data_read_len
    for (int i = 0; i < sizeof(data_length_return); i++)
    {
        read_data_cmd_data[i + 4] = data_length_return[i];
    }
    if (i2c_master_transmit(device_cr_or_cw_handle, read_data_cmd_data, sizeof(read_data_cmd_data), -1) != ESP_OK)
    {
        error_count++;
        ESP_LOGE(TAG, "failed to write read_data_cmd_data to device");
        return -1;
    };
    vTaskDelay(pdMS_TO_TICKS(10));
    uint8_t *dynamic_data = (uint8_t *)malloc(data_length);
    if (!dynamic_data)
    {
        error_count++;
        ESP_LOGE(TAG, "memory allocation failed length %lu", data_length);
        return -1;
    }
    vTaskDelay(pdMS_TO_TICKS(10));

    if (i2c_master_receive(device_r_handle, dynamic_data, data_length, -1) != ESP_OK)
    {
        error_count++;
        ESP_LOGE(TAG, "failed to read dynamic_data from device");
        return -1;
    }

    ESP_LOGE(TAG, "data: \n %s \n", dynamic_data);

    error_count = 0;
    free(dynamic_data);
    return 0;
}

void lc76g_init()
{
    ESP_LOGI(TAG, "lc76g_init");
}

void main()
{
    while (1)
    {
        add_i2c_device();
        if (error_count > MAX_ERROR_NUMBER)
        {   /*
            如果错误累加到一定值,说明I2C可能已经休眠了。
            
            according to official documents,
            maybe read too slow then lc76g i2c buffer is full,
            or abnormal interruption of the i2c process,
            anyway, gps device I2C may have gone into hibernation,
            try to write something RECOVERY
             */
            ESP_LOGW(TAG, "try to recovery lc76g i2c");
            write_data();
            error_count = 0;
        }
        // 延迟至少10ms 文档要求
        vTaskDelay(pdMS_TO_TICKS(10));
        read_data();
        remove_i2c_device();
    }
}

LC76G I2C常用NMEA指令,关闭不必要的输出,只输出GNGGA和GNRMC,每定位一次输出1次。这两项能够解析出日期、UTC时间、经纬度、高度。定位时间间隔改为500ms。

/**
b’$PAIR062,0,1*3F\r\n’ 打开GGA
24504149523036322C302C312A33460D0A
[‘0x24’, ‘0x50’, ‘0x41’, ‘0x49’, ‘0x52’, ‘0x30’, ‘0x36’, ‘0x32’, ‘0x2C’, ‘0x30’, ‘0x2C’, ‘0x31’, ‘0x2A’, ‘0x33’, ‘0x46’, ‘0x0D’, ‘0x0A’]

    b'$PAIR062,1,0*3F\r\n' 关闭GLL
    24504149523036322C312C302A33460D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x36', '0x32', '0x2C', '0x31', '0x2C', '0x30', '0x2A', '0x33', '0x46', '0x0D', '0x0A']

    b'$PAIR062,2,0*3C\r\n' 关闭GSA
    24504149523036322C322C302A33430D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x36', '0x32', '0x2C', '0x32', '0x2C', '0x30', '0x2A', '0x33', '0x43', '0x0D', '0x0A']

    b'$PAIR062,3,0*3D\r\n' 关闭GSV
    24504149523036322C332C302A33440D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x36', '0x32', '0x2C', '0x33', '0x2C', '0x30', '0x2A', '0x33', '0x44', '0x0D', '0x0A']

    b'$PAIR062,4,1*3B\r\n' 打开RMC
    24504149523036322C342C312A33415D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x36', '0x32', '0x2C', '0x34', '0x2C', '0x31', '0x2A', '0x33', '0x42', '0x0D', '0x0A']

    b'$PAIR062,5,0*3B\r\n' 关闭 VTG8 GST
    24504149523036322C352C302A33415D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x36', '0x32', '0x2C', '0x35', '0x2C', '0x30', '0x2A', '0x33', '0x42', '0x0D', '0x0A']

    b'$PAIR050,500*26\r\n' 定位时间间隔改为500ms(每秒2次)
    24504149523035302C3530302A32360D0A
    ['0x24', '0x50', '0x41', '0x49', '0x52', '0x30', '0x35', '0x30', '0x2C', '0x35', '0x30', '0x30', '0x2A', '0x32', '0x36', '0x0D', '0x0A']

*/

// 合并长度119 0x77,可以一次性发送(不用改为小端模式)
uint8_t instruction_data[119] = {
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x30, 0x2C, 0x31, 0x2A, 0x33, 0x46, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x31, 0x2C, 0x30, 0x2A, 0x33, 0x46, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x32, 0x2C, 0x30, 0x2A, 0x33, 0x43, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x33, 0x2C, 0x30, 0x2A, 0x33, 0x44, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x34, 0x2C, 0x31, 0x2A, 0x33, 0x42, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x36, 0x32, 0x2C, 0x35, 0x2C, 0x30, 0x2A, 0x33, 0x42, 0x0D, 0x0A,
    0x24, 0x50, 0x41, 0x49, 0x52, 0x30, 0x35, 0x30, 0x2C, 0x35, 0x30, 0x30, 0x2A, 0x32, 0x36, 0x0D, 0x0A};

// 向地址0x50分别发送两个4字节数据:0xAA531000 和 data_written_len (instruction_data==119(0x77)),小端模式
uint8_t write_data_cmd_data[] = {0x00, 0x10, 0x53, 0xAA, 0x77, 0x00, 0x00, 0x00};

// 然后向地址0x58发送instruction_data

==========================
-1 Reset the output rates of all types of sentences to default values.
如果需要还原,指令如下
b’$PAIR062,-1,1*13\r\n’
24504149523036322C2D312C312A31330D0A
[‘0x24’, ‘0x50’, ‘0x41’, ‘0x49’, ‘0x52’, ‘0x30’, ‘0x36’, ‘0x32’, ‘0x2C’, ‘0x2D’, ‘0x31’, ‘0x2C’, ‘0x31’, ‘0x2A’, ‘0x31’, ‘0x33’, ‘0x0D’, ‘0x0A’]