1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-13 11:29:58 +03:00

Add GPS Lap Timer (#11856)

* Add gps lap timer

* change timing to GPS time instead of local time

* rebase and minor changes

* implement KarateBrot's suggestions

* follow ledvinap's suggestions, some OSD symbol changes

* move platform.h include to the top

Co-authored-by: Jan Post <Rm2k-Freak@web.de>

* fix osd elements not showing, remove useless block

* cleanup, move pg stuff to pg folder

* cleanup from review

* minor mods to gps lap timer update, add number of laps tracked

* rename time variable

* add const to timeMs

Co-authored-by: Jan Post <Rm2k-Freak@web.de>

* Update licenses, add is_sys_element macro

* update licenses

* round to nearest centisecond

Co-authored-by: Jan Post <Rm2k-Freak@web.de>

---------

Co-authored-by: Jan Post <Rm2k-Freak@web.de>
This commit is contained in:
SpencerGraffunder 2023-05-24 20:31:22 -04:00 committed by GitHub
parent 23a416b431
commit aad197f791
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 660 additions and 10 deletions

View file

@ -87,6 +87,7 @@ COMMON_SRC = \
flight/position.c \ flight/position.c \
flight/failsafe.c \ flight/failsafe.c \
flight/gps_rescue.c \ flight/gps_rescue.c \
fc/gps_lap_timer.c \
flight/dyn_notch_filter.c \ flight/dyn_notch_filter.c \
flight/imu.c \ flight/imu.c \
flight/mixer.c \ flight/mixer.c \
@ -136,7 +137,8 @@ COMMON_SRC = \
cms/cms_menu_blackbox.c \ cms/cms_menu_blackbox.c \
cms/cms_menu_failsafe.c \ cms/cms_menu_failsafe.c \
cms/cms_menu_firmware.c \ cms/cms_menu_firmware.c \
cms/cms_menu_gps_rescue.c\ cms/cms_menu_gps_rescue.c \
cms/cms_menu_gps_lap_timer.c \
cms/cms_menu_imu.c \ cms/cms_menu_imu.c \
cms/cms_menu_ledstrip.c \ cms/cms_menu_ledstrip.c \
cms/cms_menu_main.c \ cms/cms_menu_main.c \
@ -394,7 +396,8 @@ SIZE_OPTIMISED_SRC := $(SIZE_OPTIMISED_SRC) \
cms/cms_menu_blackbox.c \ cms/cms_menu_blackbox.c \
cms/cms_menu_failsafe.c \ cms/cms_menu_failsafe.c \
cms/cms_menu_firmware.c \ cms/cms_menu_firmware.c \
cms/cms_menu_gps_rescue.c\ cms/cms_menu_gps_rescue.c \
cms/cms_menu_gps_lap_timer.c \
cms/cms_menu_imu.c \ cms/cms_menu_imu.c \
cms/cms_menu_ledstrip.c \ cms/cms_menu_ledstrip.c \
cms/cms_menu_main.c \ cms/cms_menu_main.c \

View file

@ -514,6 +514,11 @@ static void printValuePointer(const char *cmdName, const clivalue_t *var, const
// uin32_t array // uin32_t array
cliPrintf("%u", ((uint32_t *)valuePointer)[i]); cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
break; break;
case VAR_INT32:
// in32_t array
cliPrintf("%d", ((int32_t *)valuePointer)[i]);
break;
} }
if (i < var->config.array.length - 1) { if (i < var->config.array.length - 1) {
@ -543,6 +548,10 @@ static void printValuePointer(const char *cmdName, const clivalue_t *var, const
case VAR_UINT32: case VAR_UINT32:
value = *(uint32_t *)valuePointer; value = *(uint32_t *)valuePointer;
break;
case VAR_INT32:
value = *(int32_t *)valuePointer;
break; break;
} }
@ -556,6 +565,13 @@ static void printValuePointer(const char *cmdName, const clivalue_t *var, const
} else if (full) { } else if (full) {
cliPrintf(" 0 %u", var->config.u32Max); cliPrintf(" 0 %u", var->config.u32Max);
} }
} else if ((var->type & VALUE_TYPE_MASK) == VAR_INT32) {
cliPrintf("%d", (int32_t)value);
if ((int32_t)value > var->config.d32Max || (int32_t)value < -var->config.d32Max) {
valueIsCorrupted = true;
} else if (full) {
cliPrintf(" 0 %u", var->config.u32Max);
}
} else { } else {
int min; int min;
int max; int max;
@ -627,6 +643,9 @@ static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const
case VAR_UINT32: case VAR_UINT32:
result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask); result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
break; break;
case VAR_INT32:
result = result && (((int32_t *)ptr)[i] & mask) == (((int32_t *)ptrDefault)[i] & mask);
break;
} }
} }
@ -792,6 +811,10 @@ static void cliPrintVarRange(const clivalue_t *var)
case VAR_UINT32: case VAR_UINT32:
cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max); cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
break;
case VAR_INT32:
cliPrintLinef("Allowed range: %d - %d", -var->config.d32Max, var->config.d32Max);
break; break;
case VAR_UINT8: case VAR_UINT8:
case VAR_UINT16: case VAR_UINT16:
@ -873,6 +896,16 @@ static void cliSetVar(const clivalue_t *var, const uint32_t value)
} }
*(uint32_t *)ptr = workValue; *(uint32_t *)ptr = workValue;
break; break;
case VAR_INT32:
mask = 1 << var->config.bitpos;
if (value) {
workValue = *(int32_t *)ptr | mask;
} else {
workValue = *(int32_t *)ptr & ~mask;
}
*(int32_t *)ptr = workValue;
break;
} }
} else { } else {
switch (var->type & VALUE_TYPE_MASK) { switch (var->type & VALUE_TYPE_MASK) {
@ -895,6 +928,10 @@ static void cliSetVar(const clivalue_t *var, const uint32_t value)
case VAR_UINT32: case VAR_UINT32:
*(uint32_t *)ptr = value; *(uint32_t *)ptr = value;
break; break;
case VAR_INT32:
*(int32_t *)ptr = value;
break;
} }
} }
} }
@ -4400,6 +4437,14 @@ STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
cliSetVar(val, value); cliSetVar(val, value);
valueChanged = true; valueChanged = true;
} }
} else if ((val->type & VALUE_TYPE_MASK) == VAR_INT32) {
int32_t value = strtol(eqptr, NULL, 10);
// INT32s are limited to being symmetric, so we test both bounds with the same magnitude
if (value <= val->config.d32Max && value >= -val->config.d32Max) {
cliSetVar(val, value);
valueChanged = true;
}
} else { } else {
int value = atoi(eqptr); int value = atoi(eqptr);
@ -4495,7 +4540,16 @@ STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i; uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
// store value // store value
*data = (uint32_t)strtoul((const char*) valPtr, NULL, 10); *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
} }
break;
case VAR_INT32:
{
// fetch data pointer
int32_t *data = (int32_t *)cliGetValuePointer(val) + i;
// store value
*data = (int32_t)strtol((const char*) valPtr, NULL, 10);
}
break; break;
} }

View file

@ -50,6 +50,7 @@
#include "config/config.h" #include "config/config.h"
#include "fc/controlrate_profile.h" #include "fc/controlrate_profile.h"
#include "fc/core.h" #include "fc/core.h"
#include "fc/gps_lap_timer.h"
#include "fc/parameter_names.h" #include "fc/parameter_names.h"
#include "fc/rc.h" #include "fc/rc.h"
#include "fc/rc_adjustments.h" #include "fc/rc_adjustments.h"
@ -1003,6 +1004,7 @@ const clivalue_t valueTable[] = {
{ PARAM_NAME_GPS_AUTO_BAUD, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, autoBaud) }, { PARAM_NAME_GPS_AUTO_BAUD, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, autoBaud) },
{ PARAM_NAME_GPS_UBLOX_ACQUIRE_MODEL, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_UBLOX_MODELS }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_acquire_model) }, { PARAM_NAME_GPS_UBLOX_ACQUIRE_MODEL, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_UBLOX_MODELS }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_acquire_model) },
{ PARAM_NAME_GPS_UBLOX_FLIGHT_MODEL, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_UBLOX_MODELS }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_flight_model) }, { PARAM_NAME_GPS_UBLOX_FLIGHT_MODEL, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_UBLOX_MODELS }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_flight_model) },
{ PARAM_NAME_GPS_UPDATE_RATE_HZ, VAR_UINT8 | MASTER_VALUE, .config.minmaxUnsigned = {1, 19}, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_update_rate_hz) },
{ PARAM_NAME_GPS_UBLOX_USE_GALILEO, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_use_galileo) }, { PARAM_NAME_GPS_UBLOX_USE_GALILEO, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_use_galileo) },
{ PARAM_NAME_GPS_UBLOX_FULL_POWER, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_full_power) }, { PARAM_NAME_GPS_UBLOX_FULL_POWER, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_ublox_full_power) },
{ PARAM_NAME_GPS_SET_HOME_POINT_ONCE, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_set_home_point_once) }, { PARAM_NAME_GPS_SET_HOME_POINT_ONCE, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, gps_set_home_point_once) },
@ -1048,6 +1050,14 @@ const clivalue_t valueTable[] = {
{ PARAM_NAME_GPS_RESCUE_USE_MAG, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_RESCUE, offsetof(gpsRescueConfig_t, useMag) }, { PARAM_NAME_GPS_RESCUE_USE_MAG, VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_RESCUE, offsetof(gpsRescueConfig_t, useMag) },
#endif // USE_MAG #endif // USE_MAG
#endif // USE_GPS_RESCUE #endif // USE_GPS_RESCUE
#ifdef USE_GPS_LAP_TIMER
{ PARAM_NAME_GPS_LAP_TIMER_GATE_LAT, VAR_INT32 | MASTER_VALUE, .config.d32Max = 900000000, PG_GPS_LAP_TIMER, offsetof(gpsLapTimerConfig_t, gateLat) },
{ PARAM_NAME_GPS_LAP_TIMER_GATE_LON, VAR_INT32 | MASTER_VALUE, .config.d32Max = 1800000000, PG_GPS_LAP_TIMER, offsetof(gpsLapTimerConfig_t, gateLon) },
{ PARAM_NAME_GPS_LAP_TIMER_MIN_LAP_TIME, VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, 3000 }, PG_GPS_LAP_TIMER, offsetof(gpsLapTimerConfig_t, minimumLapTimeSeconds) },
{ PARAM_NAME_GPS_LAP_TIMER_GATE_TOLERANCE, VAR_UINT8 | MASTER_VALUE, .config.minmaxUnsigned = { 1, 100 }, PG_GPS_LAP_TIMER, offsetof(gpsLapTimerConfig_t, gateToleranceM) },
#endif // USE_GPS_LAP_TIMER
#endif // USE_GPS #endif // USE_GPS
{ PARAM_NAME_DEADBAND, VAR_UINT8 | MASTER_VALUE, .config.minmaxUnsigned = { 0, 32 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, deadband) }, { PARAM_NAME_DEADBAND, VAR_UINT8 | MASTER_VALUE, .config.minmaxUnsigned = { 0, 32 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, deadband) },
@ -1381,6 +1391,11 @@ const clivalue_t valueTable[] = {
{ "osd_gps_speed_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_SPEED]) }, { "osd_gps_speed_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_SPEED]) },
{ "osd_gps_lon_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LON]) }, { "osd_gps_lon_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LON]) },
{ "osd_gps_lat_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LAT]) }, { "osd_gps_lat_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LAT]) },
#ifdef USE_GPS_LAP_TIMER
{ "osd_gps_lap_curr_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LAP_TIME_CURRENT]) },
{ "osd_gps_lap_prev_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LAP_TIME_PREVIOUS]) },
{ "osd_gps_lap_best3_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_LAP_TIME_BEST3]) },
#endif // USE_GPS_LAP_TIMER
{ "osd_gps_sats_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_SATS]) }, { "osd_gps_sats_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_GPS_SATS]) },
{ "osd_home_dir_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_HOME_DIR]) }, { "osd_home_dir_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_HOME_DIR]) },
{ "osd_home_dist_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_HOME_DIST]) }, { "osd_home_dist_pos", VAR_UINT16 | MASTER_VALUE, .config.minmaxUnsigned = { 0, OSD_POSCFG_MAX }, PG_OSD_ELEMENT_CONFIG, offsetof(osdElementConfig_t, item_pos[OSD_HOME_DIST]) },

View file

@ -164,6 +164,7 @@ typedef enum {
VAR_UINT16 = (2 << VALUE_TYPE_OFFSET), VAR_UINT16 = (2 << VALUE_TYPE_OFFSET),
VAR_INT16 = (3 << VALUE_TYPE_OFFSET), VAR_INT16 = (3 << VALUE_TYPE_OFFSET),
VAR_UINT32 = (4 << VALUE_TYPE_OFFSET), VAR_UINT32 = (4 << VALUE_TYPE_OFFSET),
VAR_INT32 = (5 << VALUE_TYPE_OFFSET),
// value section, bits 3-4 // value section, bits 3-4
MASTER_VALUE = (0 << VALUE_SECTION_OFFSET), MASTER_VALUE = (0 << VALUE_SECTION_OFFSET),
@ -219,6 +220,7 @@ typedef union {
cliStringLengthConfig_t string; // used for MODE_STRING cliStringLengthConfig_t string; // used for MODE_STRING
uint8_t bitpos; // used for MODE_BITSET uint8_t bitpos; // used for MODE_BITSET
uint32_t u32Max; // used for MODE_DIRECT with VAR_UINT32 uint32_t u32Max; // used for MODE_DIRECT with VAR_UINT32
int32_t d32Max; // used for MODE_DIRECT with VAR_INT32
} cliValueConfig_t; } cliValueConfig_t;
typedef struct clivalue_s { typedef struct clivalue_s {

View file

@ -0,0 +1,129 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include "platform.h"
#ifdef USE_CMS_GPS_LAP_TIMER_MENU
#include "cli/settings.h"
#include "cms/cms.h"
#include "cms/cms_types.h"
#include "config/config.h"
#include "fc/gps_lap_timer.h"
static uint16_t gpsLapTimerConfig_minimumLapTimeSeconds;
static uint8_t gpsLapTimerConfig_gateToleranceM;
static const void *cms_menuGpsLapTimerOnEnter(displayPort_t *pDisp)
{
UNUSED(pDisp);
gpsLapTimerConfig_minimumLapTimeSeconds = gpsLapTimerConfig()->minimumLapTimeSeconds;
gpsLapTimerConfig_gateToleranceM = gpsLapTimerConfig()->gateToleranceM;
return NULL;
}
static const void *cms_menuGpsLapTimerOnExit(displayPort_t *pDisp, const OSD_Entry *self)
{
UNUSED(pDisp);
UNUSED(self);
gpsLapTimerConfigMutable()->minimumLapTimeSeconds = gpsLapTimerConfig_minimumLapTimeSeconds;
gpsLapTimerConfigMutable()->gateToleranceM = gpsLapTimerConfig_gateToleranceM;
return NULL;
}
static const void *cmsStartSetGate(displayPort_t *pDisp)
{
UNUSED(pDisp);
gpsLapTimerStartSetGate();
return NULL;
}
static const void *cmsEndSetGate(displayPort_t *pDisp, const OSD_Entry *self)
{
UNUSED(pDisp);
UNUSED(self);
gpsLapTimerEndSetGate();
return NULL;
}
static OSD_UINT16_t setGateCMSWaitCount = { &gpsLapTimerData.numberOfSetReadings, 0, 2000, 0 };
static OSD_UINT32_t setGateCMSdistCM = { &gpsLapTimerData.distToPointCM, 0, 200, 0 };
static const OSD_Entry cmsSetGateMenuEntries[] = {
{"SETTING POSITION", OME_Label, NULL, NULL},
{"READINGS", OME_UINT16 | DYNAMIC, NULL, &setGateCMSWaitCount},
{"DISTANCE(CM)", OME_UINT32 | DYNAMIC, NULL, &setGateCMSdistCM},
{"BACK", OME_Back, NULL, NULL},
{ NULL, OME_END, NULL, NULL}
};
static CMS_Menu cmsSetGateMenu = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "GATESET",
.GUARD_type = OME_MENU,
#endif
.onEnter = cmsStartSetGate,
.onExit = cmsEndSetGate,
.onDisplayUpdate = NULL,
.entries = cmsSetGateMenuEntries,
};
const OSD_Entry cms_menuGpsLapTimerEntries[] =
{
{"--- GPS LAP TIMER ---", OME_Label, NULL, NULL},
{"SET POSITION", OME_Funcall, cmsMenuChange, &cmsSetGateMenu},
{"GATE RADIUS(M)", OME_UINT8, NULL, &(OSD_UINT8_t){&gpsLapTimerConfig_gateToleranceM, 1, 100, 1}},
{"MIN LAP SECONDS", OME_UINT16, NULL, &(OSD_UINT16_t){&gpsLapTimerConfig_minimumLapTimeSeconds, 0, 3000, 1}},
{"BACK", OME_Back, NULL, NULL},
{NULL, OME_END, NULL, NULL}};
CMS_Menu cms_menuGpsLapTimer = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "MENUGPSLAPTIMER",
.GUARD_type = OME_MENU,
#endif
.onEnter = cms_menuGpsLapTimerOnEnter,
.onExit = cms_menuGpsLapTimerOnExit,
.onDisplayUpdate = NULL,
.entries = cms_menuGpsLapTimerEntries,
};
#endif // CMS_GPS_LAP_TIMER_MENU

View file

@ -0,0 +1,24 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
extern CMS_Menu cms_menuGpsLapTimer;

View file

@ -32,6 +32,7 @@
#include "cms/cms.h" #include "cms/cms.h"
#include "cms/cms_types.h" #include "cms/cms_types.h"
#include "cms/cms_menu_gps_lap_timer.h"
#include "cms/cms_menu_ledstrip.h" #include "cms/cms_menu_ledstrip.h"
#include "common/utils.h" #include "common/utils.h"
@ -161,6 +162,9 @@ static const OSD_Entry menuMiscEntries[]=
{ "DIGITAL IDLE", OME_UINT8 | REBOOT_REQUIRED, NULL, &(OSD_UINT8_t) { &motorConfig_digitalIdleOffsetValue, 0, 200, 1 } }, { "DIGITAL IDLE", OME_UINT8 | REBOOT_REQUIRED, NULL, &(OSD_UINT8_t) { &motorConfig_digitalIdleOffsetValue, 0, 200, 1 } },
{ "FPV CAM ANGLE", OME_UINT8, NULL, &(OSD_UINT8_t) { &rxConfig_fpvCamAngleDegrees, 0, 90, 1 } }, { "FPV CAM ANGLE", OME_UINT8, NULL, &(OSD_UINT8_t) { &rxConfig_fpvCamAngleDegrees, 0, 90, 1 } },
{ "RC PREV", OME_Submenu, cmsMenuChange, &cmsx_menuRcPreview}, { "RC PREV", OME_Submenu, cmsMenuChange, &cmsx_menuRcPreview},
#ifdef USE_GPS_LAP_TIMER
{ "GPS LAP TIMER", OME_Submenu, cmsMenuChange, &cms_menuGpsLapTimer },
#endif // USE_GPS_LAP_TIMER
{ "BACK", OME_Back, NULL, NULL}, { "BACK", OME_Back, NULL, NULL},
{ NULL, OME_END, NULL, NULL} { NULL, OME_END, NULL, NULL}

View file

@ -164,6 +164,11 @@ const OSD_Entry menuOsdActiveElemsEntries[] =
{"STICK OVERLAY LEFT", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_STICK_OVERLAY_LEFT]}, {"STICK OVERLAY LEFT", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_STICK_OVERLAY_LEFT]},
{"STICK OVERLAY RIGHT",OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_STICK_OVERLAY_RIGHT]}, {"STICK OVERLAY RIGHT",OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_STICK_OVERLAY_RIGHT]},
#endif #endif
#ifdef USE_GPS_LAP_TIMER
{"LAP TIME CURRENT", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_GPS_LAP_TIME_CURRENT]},
{"LAP TIME PREVIOUS", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_GPS_LAP_TIME_PREVIOUS]},
{"LAP TIME BEST 3", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_GPS_LAP_TIME_BEST3]},
#endif // USE_GPS_LAP_TIMER
{"PILOT NAME", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_PILOT_NAME]}, {"PILOT NAME", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_PILOT_NAME]},
{"RC CHANNELS", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_RC_CHANNELS]}, {"RC CHANNELS", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_RC_CHANNELS]},
{"CAMERA FRAME", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_CAMERA_FRAME]}, {"CAMERA FRAME", OME_VISIBLE | DYNAMIC, NULL, &osdConfig_item_pos[OSD_CAMERA_FRAME]},

View file

@ -139,6 +139,10 @@
#define SYM_ON_M 0x9B #define SYM_ON_M 0x9B
#define SYM_FLY_M 0x9C #define SYM_FLY_M 0x9C
// Lap Timer
#define SYM_CHECKERED_FLAG 0x24
#define SYM_PREV_LAP_TIME 0x79
// Speed // Speed
#define SYM_SPEED 0x70 #define SYM_SPEED 0x70
#define SYM_KPH 0x9E #define SYM_KPH 0x9E

154
src/main/fc/gps_lap_timer.c Normal file
View file

@ -0,0 +1,154 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <math.h>
#include "platform.h"
#include "common/maths.h"
#include "drivers/time.h"
#include "fc/rc_modes.h"
#include "io/gps.h"
#include "gps_lap_timer.h"
#define MAX_GATE_SET_READINGS 1000
// if 1000 readings of 32-bit values are used, the total (for use in calculating the averate) would need 42 bits max.
static int64_t gateSetLatReadings;
static int64_t gateSetLonReadings;
static bool settingGate = false;
static uint32_t minDistance = UINT32_MAX;
static uint32_t minDistanceTime = 0;
static bool wasInCircle = false;
gpsLapTimerData_t gpsLapTimerData;
void gpsLapTimerInit(void)
{
gpsLapTimerData.gateLocation.lat = gpsLapTimerConfig()->gateLat;
gpsLapTimerData.gateLocation.lon = gpsLapTimerConfig()->gateLon;
gpsLapTimerData.numberOfSetReadings = 0;
gpsLapTimerData.bestLapTime = 0;
gpsLapTimerData.best3Consec = 0;
for (uint8_t i = 0; i < MAX_N_RECORDED_PREVIOUS_LAPS; i++) {
gpsLapTimerData.previousLaps[i] = 0;
}
gpsLapTimerData.timerRunning = false;
gpsLapTimerData.timeOfLastLap = 0;
gpsLapTimerData.numberOfLapsRecorded = 0;
}
void gpsLapTimerStartSetGate(void)
{
settingGate = true;
gateSetLatReadings = 0;
gateSetLonReadings = 0;
gpsLapTimerData.numberOfSetReadings = 0;
}
void gpsLapTimerProcessSettingGate(void)
{
if (gpsLapTimerData.numberOfSetReadings < MAX_GATE_SET_READINGS){
gateSetLatReadings += gpsSol.llh.lat;
gateSetLonReadings += gpsSol.llh.lon;
gpsLapTimerData.numberOfSetReadings++;
}
gpsLapTimerData.gateLocation.lat = gateSetLatReadings / gpsLapTimerData.numberOfSetReadings;
gpsLapTimerData.gateLocation.lon = gateSetLonReadings / gpsLapTimerData.numberOfSetReadings;
}
void gpsLapTimerEndSetGate(void)
{
settingGate = false;
gpsLapTimerConfigMutable()->gateLat = gpsLapTimerData.gateLocation.lat;
gpsLapTimerConfigMutable()->gateLon = gpsLapTimerData.gateLocation.lon;
}
void gpsLapTimerNewGpsData(void)
{
GPS_distance_cm_bearing(&gpsSol.llh.lat, &gpsSol.llh.lon, &gpsLapTimerData.gateLocation.lat, &gpsLapTimerData.gateLocation.lon, &gpsLapTimerData.distToPointCM, &gpsLapTimerData.dirToPoint);
if (settingGate) {
gpsLapTimerProcessSettingGate();
return;
}
// Reset all lap timer data if reset mode is active
if (IS_RC_MODE_ACTIVE(BOXLAPTIMERRESET)) {
gpsLapTimerInit();
return;
}
// Current lap time is at least the min lap timer or timer not running, so we need to get ready to record a gate crossing
if (!gpsLapTimerData.timerRunning || gpsSol.time - gpsLapTimerData.timeOfLastLap > (gpsLapTimerConfig()->minimumLapTimeSeconds * 1000)) {
// Within radius of gate, record the closest point we get before leaving
if (gpsLapTimerData.distToPointCM < (gpsLapTimerConfig()->gateToleranceM * 100)) {
// Either just entered the circle or were already in circle but are the closest we've been to the center this lap
if (!wasInCircle || gpsLapTimerData.distToPointCM < minDistance) {
// Track the closest we've been to the center
minDistance = gpsLapTimerData.distToPointCM;
// Track the time we were the closest to the center, which will be used to determine the actual lap time
minDistanceTime = gpsSol.time;
}
wasInCircle = true;
} else {
// Just left the circle, so record the lap time
if (wasInCircle) {
// Not the first time through the gate
if (gpsLapTimerData.timerRunning) {
uint32_t lapTime = minDistanceTime - gpsLapTimerData.timeOfLastLap;
// Update best lap time
if (gpsLapTimerData.numberOfLapsRecorded >= 1 && (lapTime < gpsLapTimerData.bestLapTime || gpsLapTimerData.bestLapTime == 0)) {
gpsLapTimerData.bestLapTime = lapTime;
}
// Shift array of previously recorded laps and add latest
for (unsigned i = MAX_N_RECORDED_PREVIOUS_LAPS - 1; i > 0; i--) {
gpsLapTimerData.previousLaps[i] = gpsLapTimerData.previousLaps[i-1];
}
gpsLapTimerData.previousLaps[0] = lapTime;
// Check if we're able to calculate a best N consec time yet, and add them up just in case
uint32_t sumLastNLaps = 0;
if (gpsLapTimerData.numberOfLapsRecorded >= MAX_N_RECORDED_PREVIOUS_LAPS) {
for (int i = 0; i < MAX_N_RECORDED_PREVIOUS_LAPS; i++) {
sumLastNLaps += gpsLapTimerData.previousLaps[i];
}
}
// Check if this is better than the previous best
if (sumLastNLaps > 0 && (sumLastNLaps < gpsLapTimerData.best3Consec || gpsLapTimerData.best3Consec == 0)) {
gpsLapTimerData.best3Consec = sumLastNLaps;
}
}
gpsLapTimerData.timeOfLastLap = minDistanceTime;
gpsLapTimerData.timerRunning = true;
}
wasInCircle = false;
}
}
}

View file

@ -0,0 +1,49 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "io/gps.h"
#include "pg/gps_lap_timer.h"
#define MAX_N_RECORDED_PREVIOUS_LAPS 3
typedef struct gpsLapTimerData_s {
gpsLocation_t gateLocation;
uint32_t previousLaps[MAX_N_RECORDED_PREVIOUS_LAPS];
uint32_t currentLapTime;
uint32_t bestLapTime;
uint32_t best3Consec;
uint32_t distToPointCM;
uint32_t timeOfLastLap;
int32_t dirToPoint;
uint16_t numberOfSetReadings;
uint16_t numberOfLapsRecorded;
bool timerRunning;
} gpsLapTimerData_t;
extern gpsLapTimerData_t gpsLapTimerData;
void gpsLapTimerInit(void);
void gpsLapTimerNewGpsData(void);
void gpsLapTimerStartSetGate(void);
void gpsLapTimerEndSetGate(void);

View file

@ -86,6 +86,7 @@
#include "fc/board_info.h" #include "fc/board_info.h"
#include "fc/dispatch.h" #include "fc/dispatch.h"
#include "fc/gps_lap_timer.h"
#include "fc/init.h" #include "fc/init.h"
#include "fc/rc_controls.h" #include "fc/rc_controls.h"
#include "fc/runtime_config.h" #include "fc/runtime_config.h"
@ -758,6 +759,9 @@ void init(void)
#ifdef USE_GPS_RESCUE #ifdef USE_GPS_RESCUE
gpsRescueInit(); gpsRescueInit();
#endif #endif
#ifdef USE_GPS_LAP_TIMER
gpsLapTimerInit();
#endif // USE_GPS_LAP_TIMER
} }
#endif #endif

View file

@ -149,6 +149,7 @@
#define PARAM_NAME_GPS_SET_HOME_POINT_ONCE "gps_set_home_point_once" #define PARAM_NAME_GPS_SET_HOME_POINT_ONCE "gps_set_home_point_once"
#define PARAM_NAME_GPS_USE_3D_SPEED "gps_use_3d_speed" #define PARAM_NAME_GPS_USE_3D_SPEED "gps_use_3d_speed"
#define PARAM_NAME_GPS_NMEA_CUSTOM_COMMANDS "gps_nmea_custom_commands" #define PARAM_NAME_GPS_NMEA_CUSTOM_COMMANDS "gps_nmea_custom_commands"
#define PARAM_NAME_GPS_UPDATE_RATE_HZ "gps_update_rate_hz"
#ifdef USE_GPS_RESCUE #ifdef USE_GPS_RESCUE
#define PARAM_NAME_GPS_RESCUE_MIN_START_DIST "gps_rescue_min_start_dist" #define PARAM_NAME_GPS_RESCUE_MIN_START_DIST "gps_rescue_min_start_dist"
@ -187,4 +188,11 @@
#define PARAM_NAME_GPS_RESCUE_USE_MAG "gps_rescue_use_mag" #define PARAM_NAME_GPS_RESCUE_USE_MAG "gps_rescue_use_mag"
#endif #endif
#endif #endif
#ifdef USE_GPS_LAP_TIMER
#define PARAM_NAME_GPS_LAP_TIMER_GATE_LAT "gps_lap_timer_gate_lat"
#define PARAM_NAME_GPS_LAP_TIMER_GATE_LON "gps_lap_timer_gate_lon"
#define PARAM_NAME_GPS_LAP_TIMER_MIN_LAP_TIME "gps_lap_timer_min_lap_time_s"
#define PARAM_NAME_GPS_LAP_TIMER_GATE_TOLERANCE "gps_lap_timer_gate_tolerance_m"
#endif // USE_GPS_LAP_TIMER
#endif #endif

View file

@ -78,6 +78,7 @@ typedef enum {
BOXSTICKCOMMANDDISABLE, BOXSTICKCOMMANDDISABLE,
BOXBEEPERMUTE, BOXBEEPERMUTE,
BOXREADY, BOXREADY,
BOXLAPTIMERRESET,
CHECKBOX_ITEM_COUNT CHECKBOX_ITEM_COUNT
} boxId_e; } boxId_e;

View file

@ -45,6 +45,8 @@
#include "io/serial.h" #include "io/serial.h"
#include "config/config.h" #include "config/config.h"
#include "fc/gps_lap_timer.h"
#include "fc/runtime_config.h" #include "fc/runtime_config.h"
#include "flight/imu.h" #include "flight/imu.h"
@ -577,7 +579,12 @@ static void ubloxSetMessageRate(uint8_t messageClass, uint8_t messageID, uint8_t
static void ubloxSetNavRate(uint16_t measRate, uint16_t navRate, uint16_t timeRef) static void ubloxSetNavRate(uint16_t measRate, uint16_t navRate, uint16_t timeRef)
{ {
ubxMessage_t tx_buffer; ubxMessage_t tx_buffer;
tx_buffer.payload.cfg_rate.measRate = measRate;
uint16_t measRateMilliseconds = 1000 / measRate;
// Testing has revealed this is the max rate common modules can achieve
if (measRateMilliseconds < 53) measRateMilliseconds = 53;
tx_buffer.payload.cfg_rate.measRate = measRateMilliseconds;
tx_buffer.payload.cfg_rate.navRate = navRate; tx_buffer.payload.cfg_rate.navRate = navRate;
tx_buffer.payload.cfg_rate.timeRef = timeRef; tx_buffer.payload.cfg_rate.timeRef = timeRef;
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_RATE, sizeof(ubxCfgRate_t)); ubloxSendConfigMessage(&tx_buffer, MSG_CFG_RATE, sizeof(ubxCfgRate_t));
@ -743,7 +750,7 @@ void gpsInitUblox(void)
ubloxSetMessageRate(CLASS_NAV, MSG_DOP, 1); // set DOP MSG rate ubloxSetMessageRate(CLASS_NAV, MSG_DOP, 1); // set DOP MSG rate
break; break;
case UBLOX_SET_NAV_RATE: case UBLOX_SET_NAV_RATE:
ubloxSetNavRate(0x64, 1, 1); // set rate to 10Hz (measurement period: 100ms, navigation rate: 1 cycle) (for 5Hz use 0xC8) ubloxSetNavRate(gpsConfig()->gps_update_rate_hz, 1, 1);
break; break;
case UBLOX_SET_SBAS: case UBLOX_SET_SBAS:
ubloxSetSbas(); ubloxSetSbas();
@ -1579,6 +1586,7 @@ static bool UBLOX_parse_gps(void)
gpsSol.llh.lon = _buffer.posllh.longitude; gpsSol.llh.lon = _buffer.posllh.longitude;
gpsSol.llh.lat = _buffer.posllh.latitude; gpsSol.llh.lat = _buffer.posllh.latitude;
gpsSol.llh.altCm = _buffer.posllh.altitudeMslMm / 10; //alt in cm gpsSol.llh.altCm = _buffer.posllh.altitudeMslMm / 10; //alt in cm
gpsSol.time = _buffer.posllh.time;
gpsSetFixState(next_fix); gpsSetFixState(next_fix);
_new_position = true; _new_position = true;
break; break;
@ -1999,6 +2007,9 @@ void onGpsNewData(void)
#ifdef USE_GPS_RESCUE #ifdef USE_GPS_RESCUE
gpsRescueNewGpsData(); gpsRescueNewGpsData();
#endif #endif
#ifdef USE_GPS_LAP_TIMER
gpsLapTimerNewGpsData();
#endif // USE_GPS_LAP_TIMER
} }
void gpsSetFixState(bool state) void gpsSetFixState(bool state)

View file

@ -129,6 +129,7 @@ typedef struct gpsSolutionData_s {
uint16_t groundSpeed; // speed in 0.1m/s uint16_t groundSpeed; // speed in 0.1m/s
uint16_t groundCourse; // degrees * 10 uint16_t groundCourse; // degrees * 10
uint8_t numSat; uint8_t numSat;
uint32_t time; // GPS msToW
} gpsSolutionData_t; } gpsSolutionData_t;
typedef struct gpsData_s { typedef struct gpsData_s {

View file

@ -100,7 +100,8 @@ static const box_t boxes[CHECKBOX_ITEM_COUNT] = {
{ .boxId = BOXMSPOVERRIDE, .boxName = "MSP OVERRIDE", .permanentId = 50}, { .boxId = BOXMSPOVERRIDE, .boxName = "MSP OVERRIDE", .permanentId = 50},
{ .boxId = BOXSTICKCOMMANDDISABLE, .boxName = "STICK COMMANDS DISABLE", .permanentId = 51}, { .boxId = BOXSTICKCOMMANDDISABLE, .boxName = "STICK COMMANDS DISABLE", .permanentId = 51},
{ .boxId = BOXBEEPERMUTE, .boxName = "BEEPER MUTE", .permanentId = 52}, { .boxId = BOXBEEPERMUTE, .boxName = "BEEPER MUTE", .permanentId = 52},
{ .boxId = BOXREADY, .boxName = "READY", .permanentId = 53} { .boxId = BOXREADY, .boxName = "READY", .permanentId = 53},
{ .boxId = BOXLAPTIMERRESET, .boxName = "LAP TIMER RESET", .permanentId = 54},
}; };
// mask of enabled IDs, calculated on startup based on enabled features. boxId_e is used as bit index // mask of enabled IDs, calculated on startup based on enabled features. boxId_e is used as bit index
@ -338,6 +339,10 @@ void initActiveBoxIds(void)
BME(BOXSTICKCOMMANDDISABLE); BME(BOXSTICKCOMMANDDISABLE);
BME(BOXREADY); BME(BOXREADY);
#if defined(USE_GPS_LAP_TIMER)
BME(BOXLAPTIMERRESET);
#endif
#undef BME #undef BME
// check that all enabled IDs are in boxes array (check may be skipped when using findBoxById() functions) // check that all enabled IDs are in boxes array (check may be skipped when using findBoxById() functions)
for (boxId_e boxId = 0; boxId < CHECKBOX_ITEM_COUNT; boxId++) for (boxId_e boxId = 0; boxId < CHECKBOX_ITEM_COUNT; boxId++)

View file

@ -61,6 +61,7 @@
#include "drivers/time.h" #include "drivers/time.h"
#include "fc/core.h" #include "fc/core.h"
#include "fc/gps_lap_timer.h"
#include "fc/rc_controls.h" #include "fc/rc_controls.h"
#include "fc/rc_modes.h" #include "fc/rc_modes.h"
#include "fc/runtime_config.h" #include "fc/runtime_config.h"
@ -193,6 +194,8 @@ const osd_stats_e osdStatsDisplayOrder[OSD_STAT_COUNT] = {
OSD_STAT_TOTAL_TIME, OSD_STAT_TOTAL_TIME,
OSD_STAT_TOTAL_DIST, OSD_STAT_TOTAL_DIST,
OSD_STAT_WATT_HOURS_DRAWN, OSD_STAT_WATT_HOURS_DRAWN,
OSD_STAT_BEST_3_CONSEC_LAPS,
OSD_STAT_BEST_LAP,
}; };
// Group elements in a number of groups to reduce task scheduling overhead // Group elements in a number of groups to reduce task scheduling overhead
@ -545,6 +548,19 @@ void osdInit(displayPort_t *osdDisplayPortToUse, osdDisplayPortDevice_e displayP
} }
} }
#ifdef USE_GPS_LAP_TIMER
void printLapTime(char *buffer, const uint32_t timeMs) {
if (timeMs != 0) {
const uint32_t timeRoundMs = timeMs + 5; // round value in division by 10
const int timeSeconds = timeRoundMs / 1000;
const int timeDecimals = (timeRoundMs % 1000) / 10;
tfp_sprintf(buffer, "%3u.%02u", timeSeconds, timeDecimals);
} else {
tfp_sprintf(buffer, " -.--");
}
}
#endif // USE_GPS_LAP_TIMER
static void osdResetStats(void) static void osdResetStats(void)
{ {
stats.max_current = 0; stats.max_current = 0;
@ -951,6 +967,20 @@ static bool osdDisplayStat(int statistic, uint8_t displayRow)
return true; return true;
#endif #endif
#ifdef USE_GPS_LAP_TIMER
case OSD_STAT_BEST_3_CONSEC_LAPS: {
printLapTime(buff, gpsLapTimerData.best3Consec);
osdDisplayStatisticLabel(midCol, displayRow, "BEST 3 CON", buff);
return true;
}
case OSD_STAT_BEST_LAP: {
printLapTime(buff, gpsLapTimerData.bestLapTime);
osdDisplayStatisticLabel(midCol, displayRow, "BEST LAP", buff);
return true;
}
#endif // USE_GPS_LAP_TIMER
#ifdef USE_PERSISTENT_STATS #ifdef USE_PERSISTENT_STATS
case OSD_STAT_TOTAL_FLIGHTS: case OSD_STAT_TOTAL_FLIGHTS:
itoa(statsConfig()->stats_total_flights, buff, 10); itoa(statsConfig()->stats_total_flights, buff, 10);

View file

@ -186,6 +186,9 @@ typedef enum {
OSD_SYS_WARNINGS, OSD_SYS_WARNINGS,
OSD_SYS_VTX_TEMP, OSD_SYS_VTX_TEMP,
OSD_SYS_FAN_SPEED, OSD_SYS_FAN_SPEED,
OSD_GPS_LAP_TIME_CURRENT,
OSD_GPS_LAP_TIME_PREVIOUS,
OSD_GPS_LAP_TIME_BEST3,
OSD_ITEM_COUNT // MUST BE LAST OSD_ITEM_COUNT // MUST BE LAST
} osd_items_e; } osd_items_e;
@ -227,6 +230,8 @@ typedef enum {
OSD_STAT_MIN_RSSI_DBM, OSD_STAT_MIN_RSSI_DBM,
OSD_STAT_WATT_HOURS_DRAWN, OSD_STAT_WATT_HOURS_DRAWN,
OSD_STAT_MIN_RSNR, OSD_STAT_MIN_RSNR,
OSD_STAT_BEST_3_CONSEC_LAPS,
OSD_STAT_BEST_LAP,
OSD_STAT_COUNT // MUST BE LAST OSD_STAT_COUNT // MUST BE LAST
} osd_stats_e; } osd_stats_e;

View file

@ -138,6 +138,7 @@
#include "fc/controlrate_profile.h" #include "fc/controlrate_profile.h"
#include "fc/core.h" #include "fc/core.h"
#include "fc/gps_lap_timer.h"
#include "fc/rc_adjustments.h" #include "fc/rc_adjustments.h"
#include "fc/rc_controls.h" #include "fc/rc_controls.h"
#include "fc/runtime_config.h" #include "fc/runtime_config.h"
@ -234,6 +235,9 @@ static uint32_t blinkBits[(OSD_ITEM_COUNT + 31) / 32];
#define IS_BLINK(item) (blinkBits[(item) / 32] & (1 << ((item) % 32))) #define IS_BLINK(item) (blinkBits[(item) / 32] & (1 << ((item) % 32)))
#define BLINK(item) (IS_BLINK(item) && blinkState) #define BLINK(item) (IS_BLINK(item) && blinkState)
// Return whether element is a SYS element and needs special handling
#define IS_SYS_OSD_ELEMENT(item) (item >= OSD_SYS_GOGGLE_VOLTAGE) && (item <= OSD_SYS_FAN_SPEED)
enum {UP, DOWN}; enum {UP, DOWN};
static int osdDisplayWrite(osdElementParms_t *element, uint8_t x, uint8_t y, uint8_t attr, const char *s) static int osdDisplayWrite(osdElementParms_t *element, uint8_t x, uint8_t y, uint8_t attr, const char *s)
@ -1040,8 +1044,14 @@ static void osdElementGpsHomeDirection(osdElementParms_t *element)
{ {
if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) {
if (GPS_distanceToHome > 0) { if (GPS_distanceToHome > 0) {
const int h = DECIDEGREES_TO_DEGREES(GPS_directionToHome - attitude.values.yaw); int direction = GPS_directionToHome;
element->buff[0] = osdGetDirectionSymbolFromHeading(h); #ifdef USE_GPS_LAP_TIMER
// Override the "home" point to the start/finish location if the lap timer is running
if (gpsLapTimerData.timerRunning) {
direction = lrintf(gpsLapTimerData.dirToPoint * 0.1f); // Convert from centidegree to degree and round to nearest
}
#endif
element->buff[0] = osdGetDirectionSymbolFromHeading(DECIDEGREES_TO_DEGREES(direction - attitude.values.yaw));
} else { } else {
element->buff[0] = SYM_OVER_HOME; element->buff[0] = SYM_OVER_HOME;
} }
@ -1057,7 +1067,14 @@ static void osdElementGpsHomeDirection(osdElementParms_t *element)
static void osdElementGpsHomeDistance(osdElementParms_t *element) static void osdElementGpsHomeDistance(osdElementParms_t *element)
{ {
if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) {
osdFormatDistanceString(element->buff, GPS_distanceToHome, SYM_HOMEFLAG); int distance = GPS_distanceToHome;
#ifdef USE_GPS_LAP_TIMER
// Change the "home" point to the start/finish location if the lap timer is running
if (gpsLapTimerData.timerRunning) {
distance = lrintf(gpsLapTimerData.distToPointCM * 0.01f); // Round to nearest natural number
}
#endif
osdFormatDistanceString(element->buff, distance, SYM_HOMEFLAG);
} else { } else {
element->buff[0] = SYM_HOMEFLAG; element->buff[0] = SYM_HOMEFLAG;
// We use this symbol when we don't have a FIX // We use this symbol when we don't have a FIX
@ -1129,6 +1146,35 @@ static void osdElementEfficiency(osdElementParms_t *element)
} }
#endif // USE_GPS #endif // USE_GPS
#ifdef USE_GPS_LAP_TIMER
static void osdFormatLapTime(osdElementParms_t *element, uint32_t timeMs, uint8_t symbol)
{
timeMs += 5; // round to nearest centisecond (+/- 5ms)
uint32_t seconds = timeMs / 1000;
uint32_t decimals = (timeMs % 1000) / 10;
tfp_sprintf(element->buff, "%c%3u.%02u", symbol, seconds, decimals);
}
static void osdElementGpsLapTimeCurrent(osdElementParms_t *element)
{
if (gpsLapTimerData.timerRunning) {
osdFormatLapTime(element, gpsSol.time - gpsLapTimerData.timeOfLastLap, SYM_TOTAL_DISTANCE);
} else {
osdFormatLapTime(element, 0, SYM_TOTAL_DISTANCE);
}
}
static void osdElementGpsLapTimePrevious(osdElementParms_t *element)
{
osdFormatLapTime(element, gpsLapTimerData.previousLaps[0], SYM_PREV_LAP_TIME);
}
static void osdElementGpsLapTimeBest3(osdElementParms_t *element)
{
osdFormatLapTime(element, gpsLapTimerData.best3Consec, SYM_CHECKERED_FLAG);
}
#endif // GPS_LAP_TIMER
static void osdBackgroundHorizonSidebars(osdElementParms_t *element) static void osdBackgroundHorizonSidebars(osdElementParms_t *element)
{ {
// Draw AH sides // Draw AH sides
@ -1852,6 +1898,11 @@ const osdElementDrawFn osdElementDrawFunction[OSD_ITEM_COUNT] = {
#ifdef USE_GPS #ifdef USE_GPS
[OSD_EFFICIENCY] = osdElementEfficiency, [OSD_EFFICIENCY] = osdElementEfficiency,
#endif #endif
#ifdef USE_GPS_LAP_TIMER
[OSD_GPS_LAP_TIME_CURRENT] = osdElementGpsLapTimeCurrent,
[OSD_GPS_LAP_TIME_PREVIOUS] = osdElementGpsLapTimePrevious,
[OSD_GPS_LAP_TIME_BEST3] = osdElementGpsLapTimeBest3,
#endif // GPS_LAP_TIMER
#ifdef USE_PERSISTENT_STATS #ifdef USE_PERSISTENT_STATS
[OSD_TOTAL_FLIGHTS] = osdElementTotalFlights, [OSD_TOTAL_FLIGHTS] = osdElementTotalFlights,
#endif #endif
@ -1932,6 +1983,14 @@ void osdAddActiveElements(void)
} }
#endif #endif
#ifdef USE_GPS_LAP_TIMER
if (sensors(SENSOR_GPS)) {
osdAddActiveElement(OSD_GPS_LAP_TIME_CURRENT);
osdAddActiveElement(OSD_GPS_LAP_TIME_PREVIOUS);
osdAddActiveElement(OSD_GPS_LAP_TIME_BEST3);
}
#endif // GPS_LAP_TIMER
#ifdef USE_PERSISTENT_STATS #ifdef USE_PERSISTENT_STATS
osdAddActiveElement(OSD_TOTAL_FLIGHTS); osdAddActiveElement(OSD_TOTAL_FLIGHTS);
#endif #endif
@ -1962,7 +2021,7 @@ static void osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
element.attr = DISPLAYPORT_SEVERITY_NORMAL; element.attr = DISPLAYPORT_SEVERITY_NORMAL;
// Call the element drawing function // Call the element drawing function
if ((item >= OSD_SYS_GOGGLE_VOLTAGE) && (item < OSD_ITEM_COUNT)) { if (IS_SYS_OSD_ELEMENT(item)) {
displaySys(osdDisplayPort, elemPosX, elemPosY, (displayPortSystemElement_e)(item - OSD_SYS_GOGGLE_VOLTAGE + DISPLAYPORT_SYS_GOGGLE_VOLTAGE)); displaySys(osdDisplayPort, elemPosX, elemPosY, (displayPortSystemElement_e)(item - OSD_SYS_GOGGLE_VOLTAGE + DISPLAYPORT_SYS_GOGGLE_VOLTAGE));
} else { } else {
osdElementDrawFunction[item](&element); osdElementDrawFunction[item](&element);

View file

@ -38,6 +38,7 @@ PG_RESET_TEMPLATE(gpsConfig_t, gpsConfig,
.autoBaud = GPS_AUTOBAUD_OFF, .autoBaud = GPS_AUTOBAUD_OFF,
.gps_ublox_acquire_model = UBLOX_MODEL_STATIONARY, .gps_ublox_acquire_model = UBLOX_MODEL_STATIONARY,
.gps_ublox_flight_model = UBLOX_MODEL_AIRBORNE_4G, .gps_ublox_flight_model = UBLOX_MODEL_AIRBORNE_4G,
.gps_update_rate_hz = 10,
.gps_ublox_use_galileo = false, .gps_ublox_use_galileo = false,
.gps_ublox_full_power = true, .gps_ublox_full_power = true,
.gps_set_home_point_once = false, .gps_set_home_point_once = false,

View file

@ -34,6 +34,7 @@ typedef struct gpsConfig_s {
uint8_t autoBaud; uint8_t autoBaud;
uint8_t gps_ublox_acquire_model; uint8_t gps_ublox_acquire_model;
uint8_t gps_ublox_flight_model; uint8_t gps_ublox_flight_model;
uint8_t gps_update_rate_hz;
bool gps_ublox_use_galileo; bool gps_ublox_use_galileo;
bool gps_ublox_full_power; bool gps_ublox_full_power;
bool gps_set_home_point_once; bool gps_set_home_point_once;

View file

@ -0,0 +1,40 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "platform.h"
#ifdef USE_GPS_LAP_TIMER
#include "pg/pg.h"
#include "pg/pg_ids.h"
#include "gps_lap_timer.h"
PG_REGISTER_WITH_RESET_TEMPLATE(gpsLapTimerConfig_t, gpsLapTimerConfig, PG_GPS_LAP_TIMER, 1);
PG_RESET_TEMPLATE(gpsLapTimerConfig_t, gpsLapTimerConfig,
.gateLat = 0,
.gateLon = 0,
.minimumLapTimeSeconds = 10,
.gateToleranceM = 9,
);
#endif // USE_GPS_LAP_TIMER

View file

@ -0,0 +1,35 @@
/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "pg/pg.h"
typedef struct gpsLapTimerConfig_s {
int32_t gateLat;
int32_t gateLon;
uint16_t minimumLapTimeSeconds;
uint8_t gateToleranceM;
} gpsLapTimerConfig_t;
PG_DECLARE(gpsLapTimerConfig_t, gpsLapTimerConfig);

View file

@ -81,6 +81,7 @@
#define PG_GPS_RESCUE 55 // struct OK #define PG_GPS_RESCUE 55 // struct OK
#define PG_POSITION 56 #define PG_POSITION 56
#define PG_VTX_IO_CONFIG 57 #define PG_VTX_IO_CONFIG 57
#define PG_GPS_LAP_TIMER 58
// Driver configuration // Driver configuration
#define PG_DRIVER_PWM_RX_CONFIG 100 // does not exist in betaflight #define PG_DRIVER_PWM_RX_CONFIG 100 // does not exist in betaflight

View file

@ -579,4 +579,9 @@ extern uint8_t __config_end;
#ifndef USE_GPS #ifndef USE_GPS
#undef USE_GPS_PLUS_CODES #undef USE_GPS_PLUS_CODES
#undef USE_GPS_LAP_TIMER
#endif
#ifdef USE_GPS_LAP_TIMER
#define USE_CMS_GPS_LAP_TIMER_MENU
#endif #endif