You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ublox MAX M10S GPS追踪器运行1-2个月后故障排查求助

关于Ublox MAX M10S GPS模块波特率复位及设备长期稳定性的问题

问题描述

我搭建了一款搭载IMU、通过BLE 5.0传输数据的GPS追踪器,设备采用按键控制电源:短按开机,再短按关机(非拨动开关)。初始阶段设备运行完全正常,但使用1-2个月后,设备停止发送数据且无法关机。

我怀疑问题出在GPS模块(Ublox MAX M10S)上。我的代码包含逻辑:若GPS无法按预期波特率建立通信,设备将停止运行。每次开机时,模块初始配置为460800波特率,但我猜测GPS模块可能因内部复位或配置丢失,长期使用后重置为默认9600波特率。

为解决此问题,我实现了一个函数,每次开机时循环尝试三种波特率(460800、115200、9600),直到与GPS模块建立通信。目前该方法有效,但我不确定长期使用是否可靠。

请教以下几点

  1. GPS模块为何会随时间恢复到默认波特率?
  2. 确保设备长期稳定运行的最佳实践?
  3. 是否有替代方法缓解此类问题?

附:JDY-23 BLE模块数据手册(小众模块,供参考)


问题解答

1. GPS模块波特率复位的可能原因

  • 电源异常波动:设备长期使用中,电池电压下降、开机瞬间的电源尖峰或电磁干扰,可能触发Ublox模块的内部复位机制,导致存储在非易失性存储器(NVM)中的配置丢失,恢复出厂默认波特率。
  • NVM写入磨损:Ublox模块的NVM有写入次数限制,频繁调用saveConfiguration()可能导致NVM区块损坏,配置无法持久保存。
  • 模块固件bug:MAX M10S的早期固件版本可能存在配置保存的稳定性问题,建议检查官方固件更新。
  • 外部复位信号干扰:硬件布线不合理,导致模块的RESET引脚受到误触发,强制复位到默认配置。

2. 设备长期稳定运行的最佳实践

  • 优化电源设计:添加电源滤波电容(如100µF电解电容+0.1µF陶瓷电容),避免开机/关机瞬间的电压波动;确保电池供电稳定,防止欠压导致模块异常复位。
  • 减少NVM写入次数:仅在首次配置或修改关键参数时调用saveConfiguration(),避免每次开机都重复保存。可以通过检测模块当前配置是否符合预期,再决定是否保存。
  • 添加看门狗机制:利用STM32的硬件看门狗,当GPS通信中断导致设备卡死时,自动重启设备恢复运行。
  • 完善错误处理逻辑:当前代码中gpsTimerCallback()里的delay(100)可能导致定时器中断阻塞,建议改用非阻塞的按键检测(如记录按键按下时间戳),避免影响正常数据采集。
  • 固件更新:升级Ublox MAX M10S到最新官方固件,修复已知的配置稳定性问题。

3. 替代缓解方法

  • 硬件流控:为GPS模块的UART添加RTS/CTS流控引脚,避免高速波特率下的数据丢包,减少因通信异常导致的配置误判。
  • 调整波特率扫描顺序:在波特率扫描时,先尝试默认的9600波特率,再切换到高速波特率,避免模块复位后因扫描顺序问题导致初始化失败。
  • 配置备份与恢复:在设备首次配置成功后,将GPS模块的配置参数备份到STM32的Flash中,当检测到波特率异常时,直接发送备份的配置指令,无需重新扫描波特率。
  • 独立电源控制:给GPS模块设计独立的电源开关,当通信异常时,主动断电重启模块,而非仅软件复位。

更新后的代码

#include <HardwareSerial.h>
#include <SparkFun_BMI270_Arduino_Library.h>
#include <Wire.h>
#include "STM32TimerInterrupt.h"
#include "STM32_ISR_Timer.h"

SFE_UBLOX_GNSS_SERIAL myGNSS;
BMI270 imu;
const uint8_t i2cAddress = BMI2_I2C_SEC_ADDR; // 0x69

HardwareSerial gserial(USART1);
HardwareSerial bserial(USART3);

#define ADC_MIN_VALUE 80
#define ADC_MAX_VALUE 120
#define BATTERY_MAX_VOLTAGE 4.25  // Maximum battery voltage (4.25V)

// Pin configurations
const int chipSelect = PA4; // SS pin

#define DEVICE_SERIAL_NUMBER "24A1073" // VT1   -   2408   -      0A                  -           1001
                                          //  product   year/month   batch/party                        number
                                          //   name       yymm     0a,0b representing 1,2,3 ...         


// Interval in microseconds for 20Hz (50ms)
#define TIMER_INTERVAL_US      15000UL

// Create STM32 hardware timer object
STM32Timer ITimer(TIM2); // Use TIM2 or any other available timer

void setup() {

    pinMode(PA12, OUTPUT);
    pinMode(PA11, INPUT_PULLUP); 
    pinMode(PB12, OUTPUT);
    pinMode(PB13, OUTPUT);
    pinMode(PB14, OUTPUT);
    digitalWrite(PA12, HIGH);
    digitalWrite(PB12, LOW); //YELLOW
    digitalWrite(PB13, HIGH); //MAGENTA
    digitalWrite(PB14, HIGH); //CYAN
    
    bserial.begin(9600);
    changeBLEDeviceProp(); //OPEN THIS ONLY IN FIRST BUILD
    delay(600);
    bserial.begin(115200);

    Wire.begin(I2C1);
    Wire.setClock(400000); // Set I2C clock to 400kHz

    // Initialize BMI270
    while (imu.beginI2C(i2cAddress) != BMI2_OK) {
        bserial.println("Error: BMI270 not connected, check wiring and I2C address!");
        delay(100);
    }
    bserial.println("BMI270 connected!");
    delay(100);

    // Attempt GNSS initialization
    initializeGNSS();

    // Initialize ADC pin for battery measurement
    pinMode(PA0, OUTPUT);  // BAT_ADC_EN
    digitalWrite(PA0, LOW);  // Initially disable ADC
    pinMode(PA1, INPUT_ANALOG);

// Timer Interrupt Setup
    if (ITimer.attachInterruptInterval(TIMER_INTERVAL_US, gpsTimerCallback)) {
        bserial.println("Timer interrupt started.");
    } else {
        bserial.println("Failed to set timer interrupt!");
    }
}

void gpsTimerCallback() {
    imu.getSensorData();
    sendSensorData();

    if (digitalRead(PA11) == LOW) {
        delay(100);
        handleShutdownSequence();
    }
}

void loop() {
  retryGNSS();
}

void initializeGNSS() {
    const uint32_t baudRates[] = {460800,9600,115200};
    size_t baudRateCount = sizeof(baudRates) / sizeof(baudRates[0]);
    bool gnssInitialized = false;
    const uint32_t timeoutMs = 50;

    for (size_t i = 0; i < baudRateCount; i++) {
        gserial.begin(baudRates[i]);
        gserial.setTimeout(timeoutMs);
        bserial.printf("Trying GNSS baud rate: %lu\n", baudRates[i]);
        
        if (myGNSS.begin(gserial)) { // GNSS initialization attempt
            if (myGNSS.getPVT()) { // Check PVT data
                bserial.printf("GNSS initialized at baud rate: %lu\n", baudRates[i]);
                gnssInitialized = true;
                break;
            }
        }
        gserial.end(); // Close UART and try the next baud rate
    }

    if (!gnssInitialized) {
        bserial.println("Warning: GNSS not initialized. Retrying in the background...");
    } 
    else {
        // Set GNSS to a higher baud rate for performance
        if (myGNSS.setSerialRate(460800)) { 
            gserial.end();
            gserial.begin(460800);
        } else {
            bserial.println(F("Failed to change GNSS baud rate"));
        }
        // Configure GNSS settings
        myGNSS.setUART1Output(COM_TYPE_UBX);
        myGNSS.setDynamicModel(DYN_MODEL_AUTOMOTIVE);
        delay(100);
        myGNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); 
        myGNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO);
        myGNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS);  
        myGNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS);
        myGNSS.enableGNSS(false, SFE_UBLOX_GNSS_ID_GLONASS);
        myGNSS.enableGNSS(false, SFE_UBLOX_GNSS_ID_BEIDOU);
        myGNSS.setMeasurementRate(50);
        myGNSS.setNavigationFrequency(20);
        delay(50);
        myGNSS.saveConfiguration();
    }
}

void retryGNSS() {
    static unsigned long lastRetryTime = 0;
    const unsigned long retryInterval = 5000; // Retry every 5 seconds

    if (!myGNSS.getPVT() && (millis() - lastRetryTime > retryInterval)) {
        bserial.println("Retrying GNSS initialization...");
        initializeGNSS();
        lastRetryTime = millis();
    }
}


// Send sensor data over BLE
void sendSensorData() {
    String data;
    data.reserve(128);

    float latitude = myGNSS.getLatitude() / 10000000.0;
    float longitude = myGNSS.getLongitude() / 10000000.0;
    float altitude = myGNSS.getAltitude() / 1000.0; // Convert mm to meters
    float speed = myGNSS.getGroundSpeed() * 0.0036;
    byte SIV = myGNSS.getSIV();
    byte fixType = myGNSS.getFixType();
    int fixStatus = (fixType == 3) ? 2 : (fixType == 2) ? 1 : 0;
    float pdop = myGNSS.getPDOP() / 100.0;
    float heading = myGNSS.getHeading() / 100000.0;
    float batteryPercentage = getBatteryPercentage();
    int32_t horizontalAcc = myGNSS.getHorizontalAccEst();  // in millimeters
    int32_t verticalAcc = myGNSS.getVerticalAccEst();      // in millimeters
    float positionAccuracy = sqrt(sq(horizontalAcc) + sq(verticalAcc)) / 1000.0;  // Convert to meters

    data += "A:" + String(DEVICE_SERIAL_NUMBER);
    data += ";B:" + String(latitude, 7);
    data += ";C:" + String(longitude, 7);
    data += ";D:" + String(altitude,1);
    data += ";E:" + String(speed, 1);
    data += ";F:" + String(SIV);
    data += ";G:" + String(fixStatus);
    data += ";H:" + String(imu.data.accelX, 2);
    data += ";I:" + String(imu.data.accelY, 2);
    data += ";J:" + String(imu.data.accelZ, 2);
    data += ";K:" + String(imu.data.gyroX, 0);
    data += ";L:" + String(imu.data.gyroY, 0);
    data += ";M:" + String(imu.data.gyroZ, 0);
    data += ";N:" + String(batteryPercentage, 0);
    data += ";O:" + String(heading,0);
    data += ";P:" + String(positionAccuracy, 1) + ";";

    bserial.println(data);
    bserial.flush();
}

// Handle device shutdown
void handleShutdownSequence() {
    static bool shuttingDown = false;
    if (!shuttingDown) {
        shuttingDown = true;
        digitalWrite(PB14, LOW);
        digitalWrite(PB12, HIGH);
        digitalWrite(PB13, HIGH);
        delay(1000);
        digitalWrite(PA12, LOW);
        shuttingDown = false;
    }
}

// Get battery percentage
float getBatteryPercentage() {
    static const float ADC_SCALE = BATTERY_MAX_VOLTAGE / ADC_MAX_VALUE;
    digitalWrite(PA0, HIGH);
    int adcValue = analogRead(PA1);
    digitalWrite(PA0, LOW);
    float voltage = adcValue * ADC_SCALE;
    float percentage = ((voltage - 3.0) / (BATTERY_MAX_VOLTAGE - 3.0)) * 100.0;
    return constrain(percentage, 0, 100);
}


void changeBLEDeviceProp() {
    bserial.print("AT\r\n");
    delay(500);
    bserial.print("AT+NAMEGPS-ILKER\r\n");
    delay(500);
    bserial.print("AT+MTU2\r\n");  // Set MTU size TO 128b
    delay(500);
    bserial.print("AT+BAUD0\r\n");
    delay(500);
}

内容的提问来源于stack exchange,提问作者ilker çetinkaya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:42:03