kopia lustrzana https://github.com/mikaelnousiainen/RS41ng
Implement simple GPS power saving. Read leap seconds from GPS data when available. Improve documentation.
rodzic
da2743e4d1
commit
fda9cef76a
70
README.md
70
README.md
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
15
src/config.h
15
src/config.h
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -25,4 +25,6 @@ typedef struct _telemetry_data {
|
|||
|
||||
void telemetry_collect(telemetry_data *data);
|
||||
|
||||
extern int8_t gps_time_leap_seconds;
|
||||
|
||||
#endif
|
||||
|
|
Ładowanie…
Reference in New Issue