Implement simple GPS power saving. Read leap seconds from GPS data when available. Improve documentation.

pull/54/head
Mikael Nousiainen 2023-08-09 18:28:00 +03:00
rodzic da2743e4d1
commit fda9cef76a
10 zmienionych plików z 129 dodań i 28 usunięć

Wyświetl plik

@ -1,6 +1,8 @@
# RS41ng - Amateur radio firmware for Vaisala RS41 radiosonde
**NOTE:** This firmware is a work in progress and some features might not work as expected yet!
**NOTE:** While this firmware has been tested with great success on a number of high-altitude balloon
flights, it is still a work in progress and some features might not work as expected yet!
In particular, the time sync (scheduling) features and use of an external Si5351 as a transmitter need more testing.
This is a custom, amateur radio-oriented firmware for [Vaisala RS41 radiosondes](https://www.vaisala.com/en/products/instruments-sensors-and-other-measurement-devices/soundings-products/rs41).
Some code is based on an earlier RS41 firmware project called [RS41HUP](https://github.com/df8oe/RS41HUP),
@ -133,6 +135,15 @@ Sensor driver code contributions are welcome!
transmit frequencies and transmission mode parameters in `config.h`
2. Set up transmitted message templates in `config.c`, depending on the modes you use
### Power consumption and power-saving features
Power consumption notes (at 3V supply voltage) by Mark VK5QI:
- GPS in default (max performance) mode, transmitting with Si4032 @ 13 dBm = ~150 mA
- GPS in default (max performance) mode, not transmitting = 70-90 mA
- GPS in power-saving mode, transmitting with Si4032 @ 13 dBm = ~120 mA
- GPS in power-saving mode, not transmitting = 30-50 mA, depending on GPS state.
### Time sync settings
The time sync feature is a simple way to activate the transmissions every N seconds, delayed by the `TIME_SYNC_OFFSET_SECONDS` setting.
@ -242,6 +253,39 @@ Payload 3:
## Building the firmware
The easiest and the recommended method to build the firmware is using Docker.
If you have a Linux environment -- Windows Subsystem for Linux (WSL) or macOS might work too -- and
you feel adventurous, you can try to build using the Linux-based instructions.
### Building the firmware with Docker
Using Docker to build the firmware is usually the easiest option, because it provides a stable Fedora Linux-based
build environment on any platform. It should work on Windows and Mac operating systems too.
The Docker environment can also help address issues with the build process.
1. Install Docker if not already installed
2. Set the current directory to the RS41ng source directory
3. Build the RS41ng compiler Docker image using the following command. It is necessary to build the Docker image only once.
```
docker build -t rs41ng_compiler .
```
4. Build the firmware using the following command. If you need to rebuild the firmware, simply run the command again.
On Linux/macOS, run:
```
docker run --rm -it -v $(pwd):/usr/local/src/RS41ng rs41ng_compiler
```
On Windows, run:
```
docker run --rm -it -v %cd%:/usr/local/src/RS41ng rs41ng_compiler
```
5. The firmware will be stored in file `build/src/RS41ng.elf`
Now you can flash the firmware using instructions below (skip the build instructions for Linux).
### Building the firmware in a Linux environment
Software requirements:
* [GNU GCC toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads/9-2-2019-12)
@ -254,7 +298,7 @@ On a Red Hat/Fedora Linux installation, the following packages can be installed:
dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ arm-none-eabi-binutils-cs arm-none-eabi-newlib cmake openocd
```
### Steps to build the firmware
#### Steps to build the firmware on Linux
1. Install the required software dependencies listed above
2. Build the firmware using the following commands
@ -266,25 +310,6 @@ dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ arm-none-eabi-binutils
```
3. The firmware will be stored in file `build/src/RS41ng.elf`
### Building the firmware with Docker
Using Docker to build the firmware is usually the easiest option, because it provides a stable Fedora Linux-based
build environment on any platform. It should work on Windows and Mac operating systems too.
The Docker environment can also help address issues with the build process, including the `strlcpy()` errors observed on certain Linux distributions.
1. Install Docker if not already installed
2. Set the current directory to the RS41ng source directory
3. Build the RS41ng compiler Docker image using the following command. It is necessary to build the Docker image only once.
```
docker build -t rs41ng_compiler .
```
4. Build the firmware using the following command. If you need to rebuild the firmware, simply run the command again.
```
docker run --rm -it -v $(pwd):/usr/local/src/RS41ng rs41ng_compiler
```
5. The firmware will be stored in file `build/src/RS41ng.elf`
## Flashing the firmware
Hardware requirements:
@ -326,7 +351,8 @@ ______________________| |______________________
### Steps to flash the firmware
1. If your ST-LINK v2 programmer is capable of providing power (as some third-party clones are), remove the batteries from the sonde. Otherwise, leave the battiers in and power on the sonde.
1. If your ST-LINK v2 programmer is capable of providing power (as some third-party clones are),
remove the batteries from the sonde. Otherwise, leave the batteries in and power on the sonde.
2. Connect an ST-LINK v2 programmer dongle to the sonde via the following pins:
* SWDIO -> Pin 9 (SWDIO)
* SWCLK -> Pin 8 (SWCLK)

Wyświetl plik

@ -48,6 +48,8 @@ size_t horus_packet_v1_create(uint8_t *payload, size_t length, telemetry_data *d
horus_packet.Sats += 100;
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING) {
horus_packet.Sats += 200;
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_INACTIVE) {
horus_packet.Sats += 300;
}
horus_packet.Checksum = (uint16_t) calculate_crc16_checksum((char *) &horus_packet, sizeof(horus_packet) - 2);

Wyświetl plik

@ -43,6 +43,8 @@ size_t horus_packet_v2_create(uint8_t *payload, size_t length, telemetry_data *d
horus_packet.Sats += 100;
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING) {
horus_packet.Sats += 200;
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_INACTIVE) {
horus_packet.Sats += 300;
}
memset(horus_packet.CustomData, 0, sizeof(horus_packet.CustomData));

Wyświetl plik

@ -2,7 +2,8 @@
#define __CONFIG_H
// Enable semihosting to receive debug logs during development
// NOTE: Semihosting has to be disabled when the RS41 radiosonde is not connected to the STM32 programmer dongle, otherwise the firmware will not run.
// See the README for details on how to set up debugging and debug logs with GDB
// NOTE: Semihosting has to be disabled when the RS41 radiosonde is not connected to an STM32 programmer dongle, otherwise the firmware will not run.
//#define SEMIHOSTING_ENABLE
//#define LOGGING_ENABLE
@ -58,11 +59,22 @@
#define RADIO_TIME_SYNC_THRESHOLD_MS 2000
// Number of leap seconds to add to the raw GPS time reported by the GPS chip (see https://timetoolsltd.com/gps/what-is-gps-time/ for more info)
// This value is used by default, but if the received GPS data contains indication about leap seconds, that one is used instead.
#define GPS_TIME_LEAP_SECONDS 18
// Enable this setting to require 3D fix (altitude required, enable for airborne use), otherwise 2D fix is enough
#define GPS_REQUIRE_3D_FIX true
// Enable power-saving features of the GPS chip to save power.
// This option should be safe to enable, as it enters a selective power saving mode.
// If the GPS chip loses fix, it will enter a higher power state automatically.
// Note that power saving mode is only enabled after the GPS chip has acquired good GPS fix for the first time.
// It is not necessary to use power saving on short flights (e.g. less than 6 hours).
// Based on measurements Mark VK5QI, enabling this reduces power consumption by about 30-40 mA (~50%) to around 30-50 mA,
// where the consumption is 70-90 mA when power saving is not enabled and any radio transmitters are idle.
// See the README for details about power consumption.
#define GPS_POWER_SAVING_ENABLE false
// Enable NMEA output from GPS via external serial port. This disables use of I²C bus (Si5351 and sensors) because the pins are shared.
#define GPS_NMEA_OUTPUT_VIA_SERIAL_PORT_ENABLE false
@ -105,6 +117,7 @@
// 0 = -1dBm, 1 = 2dBm, 2 = 5dBm (~3 mW), 3 = 8dBm (~6 mW), 4 = 11dBm (~12 mW), 5 = 14dBm (25 mW), 6 = 17dBm (50 mW), 7 = 20dBm (100 mW)
// This defaults to 5 (14 dBm, 25 mW), which is a good setting for Horus 4FSK transmissions and it saves power.
// For APRS usage, you might want to use maximum power setting of 7 (20 dBm, 100 mW). Note that this setting reduces battery life.
// See the README for details about power consumption.
#define RADIO_SI4032_TX_POWER 5
// Which modes to transmit using the built-in Si4032 transmitter chip

Wyświetl plik

@ -391,7 +391,7 @@ uBloxPacket msgcfgrxm = {
},
.data.cfgrxm = {
.reserved1=8, // Always set to 8
.lpMode=0 // Low power mode: Eco mode -- TODO: set back to Eco mode
.lpMode=0 // Enable max performance mode at start-up time to acquire GPS fix quickly
}
};
@ -489,6 +489,28 @@ uBloxPacket msgcfgnmea = {
}
};
bool ubxg6010_enable_power_save_mode()
{
bool success;
uBloxPacket packet;
// Copy the packet header
memcpy(&packet, &msgcfgrxm, sizeof(uBloxPacket));
// The default power-save settings should be OK (1 second cyclic)
// Enable power-saving mode
packet.data.cfgrxm.lpMode = 1;
log_info("GPS: Entering power-saving mode\n");
ubxg6010_send_packet(&packet);
success = ubxg6010_wait_for_ack();
if (!success) {
log_error("GPS: Entering power-saving mode failed\n")
}
return success;
}
bool ubxg6010_init()
{
bool success;
@ -714,6 +736,11 @@ static void ubxg6010_handle_packet(uBloxPacket *pkt)
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navtimegps.iTOW;
ubxg6010_current_gps_data.week = pkt->data.navtimegps.week;
if (pkt->data.navtimegps.valid & 0x04) {
// Flag set if leap seconds are valid
ubxg6010_current_gps_data.leap_seconds = pkt->data.navtimegps.leapS;
}
ubxg6010_current_gps_data.updated = true;
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x21) {
ubxg6010_current_gps_data.year = pkt->data.navtimeutc.year;

Wyświetl plik

@ -7,6 +7,8 @@
bool ubxg6010_init();
bool ubxg6010_enable_power_save_mode();
void ubxg6010_request_gpstime();
bool ubxg6010_get_current_gps_data(gps_data *data);

Wyświetl plik

@ -5,11 +5,19 @@
#include <stdbool.h>
#include "config.h"
// Acquisition state: The receiver actively searches for and acquires signals. Maximum power consumption.
#define POWER_SAFE_MODE_STATE_ACQUISITION 0
// Tracking state: The receiver continuously tracks and downloads data. Less power consumption than in Acquisition state.
#define POWER_SAFE_MODE_STATE_TRACKING 1
// POT state: The receiver repeatedly loops through a sequence of tracking (TRK), calculating the position fix
// (Calc), and entering an idle period (Idle). No new signals are acquired and no data is downloaded. Much less
// power consumption than in Tracking state.
#define POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING 2
// Inactive state: Most parts of the receiver are switched off.
#define POWER_SAFE_MODE_STATE_INACTIVE 3
#define GPS_IS_POWER_SAVING_ACTIVE(gps_data) (gps_data.power_safe_mode_state != POWER_SAFE_MODE_STATE_ACQUISITION)
#define GPS_FIX_NO_FIX 0
#define GPS_FIX_DEAD_RECKONING_ONLY 1
#define GPS_FIX_2D 2
@ -34,6 +42,7 @@ typedef struct _gps_data {
uint8_t seconds;
uint8_t minutes;
uint8_t hours;
int8_t leap_seconds;
int32_t latitude_degrees_1000000;
int32_t longitude_degrees_1000000;

Wyświetl plik

@ -712,7 +712,7 @@ bool radio_handle_time_sync()
return false;
}
uint32_t time_millis = gps.time_of_week_millis - (GPS_TIME_LEAP_SECONDS * 1000);
uint32_t time_millis = gps.time_of_week_millis - (gps_time_leap_seconds * 1000);
if (time_millis == radio_previous_time_sync_scheduled) {
// The GPS chip has not provided an updated time yet for some reason

Wyświetl plik

@ -9,6 +9,11 @@
#include "config.h"
#include "log.h"
// Initialize leap seconds with a known good value
int8_t gps_time_leap_seconds = GPS_TIME_LEAP_SECONDS;
static bool gps_power_saving_enabled = false;
void telemetry_collect(telemetry_data *data)
{
log_info("Collecting telemetry...\n");
@ -31,9 +36,22 @@ void telemetry_collect(telemetry_data *data)
ubxg6010_get_current_gps_data(&data->gps);
// Zero out position data if we don't have a valid GPS fix.
// This is done to avoid transmitting invalid position information.
if (!GPS_HAS_FIX(data->gps)) {
if (GPS_HAS_FIX(data->gps)) {
// If we have a good fix, we can enter power-saving mode
if ((data->gps.satellites_visible >= 6) && !gps_power_saving_enabled) {
#ifdef GPS_POWER_SAVING_ENABLE
ubxg6010_enable_power_save_mode();
gps_power_saving_enabled = true;
#endif
}
// If we get the number of leap seconds from GPS data, use it
if (data->gps.leap_seconds > 0) {
gps_time_leap_seconds = data->gps.leap_seconds;
}
} else {
// Zero out position data if we don't have a valid GPS fix.
// This is done to avoid transmitting invalid position information.
data->gps.latitude_degrees_1000000 = 0;
data->gps.longitude_degrees_1000000 = 0;
data->gps.altitude_mm = 0;

Wyświetl plik

@ -25,4 +25,6 @@ typedef struct _telemetry_data {
void telemetry_collect(telemetry_data *data);
extern int8_t gps_time_leap_seconds;
#endif