diff --git a/README.md b/README.md index d64765f..0e008cd 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ Some of the code is based on an earlier RS41 firmware project called [RS41HUP](h but most of it has been rewritten from scratch. The Horus 4FSK code has been adapted from the [darksidelemm fork of RS41HUP](https://github.com/darksidelemm/RS41HUP). -## Asking questions, filing feature requests and issues +## Asking questions, filing feature requests and reporting issues * Please use [GitHub discussions](../../discussions) for asking questions and for sharing info about your RS41-based projects * For example, questions about firmware configuration and connecting of external chips to the sonde belong here * Please use [GitHub issues](../../issues) to file new feature requests or issues that you have already identified with the firmware - * Please remember to post questions about usage to [GitHub discussions](../../discussions) + * However, please remember to post questions about usage to [GitHub discussions](../../discussions) ## What are the Vaisala RS41 radiosondes and how can I get one? @@ -37,7 +37,7 @@ For your own receiver station, you will need: The [Vaisala RS41 radiosondes](https://www.vaisala.com/en/products/instruments-sensors-and-other-measurement-devices/soundings-products/rs41) uses an off-the-shelf [STM32F100C8](https://www.st.com/en/microcontrollers-microprocessors/stm32f100c8.html) 32-bit microcontroller, which can be reprogrammed using an [ST-LINK v2 programmer](https://www.st.com/en/development-tools/st-link-v2.html) -or a smaller [ST-LINK v2 USB dongle]((https://www.adafruit.com/product/2548). +or a smaller [ST-LINK v2 USB dongle](https://www.adafruit.com/product/2548). The RS41 hardware can be programmed to transmit different kinds of RF modulations (morse code, APRS and different FSK modulations) on the 70 cm (~433 MHz) amateur radio band. The radiosonde contains a [UBX-G6010](https://www.u-blox.com/en/product/ubx-g6010-st-chip) diff --git a/src/config.c b/src/config.c index 794025b..691f4f2 100644 --- a/src/config.c +++ b/src/config.c @@ -43,6 +43,7 @@ bool leds_enabled = LEDS_ENABLE; bool bmp280_enabled = SENSOR_BMP280_ENABLE; bool si5351_enabled = RADIO_SI5351_ENABLE; +bool gps_nmea_output_enabled = GPS_NMEA_OUTPUT_VIA_SERIAL_PORT_ENABLE; volatile bool system_initialized = false; diff --git a/src/config.h b/src/config.h index 0973912..eb3b64f 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,13 @@ // Threshold for time-synchronized modes regarding how far from scheduled transmission time the transmission is still allowed #define RADIO_TIME_SYNC_THRESHOLD_MS 2000 +// 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 + +#if (GPS_NMEA_OUTPUT_VIA_SERIAL_PORT_ENABLE) && ((RADIO_SI5351_ENABLE) || (SENSOR_BMP280_ENABLE)) +#error GPS NMEA output via serial port cannot be enabled simultaneously with the I2C bus. +#endif + /** * Built-in Si4032 radio chip transmission configuration */ diff --git a/src/config_internal.h b/src/config_internal.h index 8b123bb..1a39e18 100644 --- a/src/config_internal.h +++ b/src/config_internal.h @@ -1,6 +1,11 @@ #ifndef __CONFIG_INTERNAL_H #define __CONFIG_INTERNAL_H +#define GPS_SERIAL_PORT_BAUD_RATE 38400 + +// The external serial port baud rate must be higher than the GPS serial port baud rate (38400) +#define EXTERNAL_SERIAL_PORT_BAUD_RATE 115200 + #define RADIO_PAYLOAD_MAX_LENGTH 256 #define RADIO_SYMBOL_DATA_MAX_LENGTH 512 #define RADIO_PAYLOAD_MESSAGE_MAX_LENGTH 128 @@ -11,6 +16,7 @@ #include extern bool leds_enabled; +extern bool gps_nmea_output_enabled; extern bool bmp280_enabled; extern bool si5351_enabled; diff --git a/src/drivers/ubxg6010/ubxg6010.c b/src/drivers/ubxg6010/ubxg6010.c index dedc6de..585fc17 100644 --- a/src/drivers/ubxg6010/ubxg6010.c +++ b/src/drivers/ubxg6010/ubxg6010.c @@ -2,11 +2,14 @@ #include "hal/system.h" #include "hal/usart_gps.h" +#include "hal/usart_ext.h" #include "hal/delay.h" #include "ubxg6010.h" #include "log.h" +#define GPS_INITIAL_BAUD_RATE 9600 + typedef struct __attribute__((packed)) { uint8_t sc1; // 0xB5 uint8_t sc2; // 0x62 @@ -204,6 +207,13 @@ typedef struct { uint16_t timeRef; //Alignment to reference time: 0 = UTC time, 1 = GPS time [- -] } uBloxCFGRATEPayload; +typedef struct { + uint8_t filter; // Filter flags + uint8_t version; // 0x23 = NMEA version 2.3, 0x21 = NMEA version 2.1 + uint8_t numSV; // Maximum Number of SVs to report in NMEA protocol. + uint8_t flags; // Flags: 0x01 = enable compatibility mode, 0x02 = enable considering mode +} uBloxCFGNMEA; + typedef union { uBloxNAVPVTPayload navpvt; uBloxCFGPRTPayload cfgprt; @@ -220,6 +230,7 @@ typedef union { uBloxCFGRSTPayload cfgrst; uBloxCFGRXMPayload cfgrxm; uBloxCFGRATEPayload cfgrate; + uBloxCFGNMEA cfgnmea; } ubloxPacketData; typedef struct __attribute__((packed)) { @@ -233,6 +244,9 @@ volatile bool gps_initialized = false; volatile bool ack_received = false; volatile bool nack_received = false; +volatile static uint8_t sync_ubx = 0; +volatile static uint8_t sync_nmea = 0; + static uBloxChecksum ubxg6010_calculate_checksum(const uint8_t msgClass, const uint8_t msgId, const uint8_t *message, uint16_t size) { @@ -458,6 +472,23 @@ uBloxPacket msgcfgnav5 = { }, }; +// Configure NMEA protocol version 2.3 +uBloxPacket msgcfgnmea = { + .header = { + 0xb5, + 0x62, + .messageClass=0x06, + .messageId=0x17, + .payloadSize=sizeof(uBloxCFGNMEA) + }, + .data.cfgnmea = { + .filter = 0x00, + .version = 0x23, + .numSV = 24, + .flags = 0x00, + } +}; + bool ubxg6010_init() { bool success; @@ -466,28 +497,38 @@ bool ubxg6010_init() gps_initialized = false; - log_info("GPS: Initializing USART with baud rate 38400\n"); - usart_gps_init(38400, true); + log_info("GPS: Initializing USART with baud rate %d\n", GPS_SERIAL_PORT_BAUD_RATE); + usart_gps_init(GPS_SERIAL_PORT_BAUD_RATE, true); delay_ms(100); log_info("GPS: Resetting GPS chip\n"); ubxg6010_send_packet(&msgcfgrst); delay_ms(1000); - log_info("GPS: Initializing USART with baud rate 9600\n"); - usart_gps_init(9600, true); + log_info("GPS: Initializing USART with baud rate %d\n", GPS_INITIAL_BAUD_RATE); + usart_gps_init(GPS_INITIAL_BAUD_RATE, true); delay_ms(100); log_info("GPS: Resetting GPS chip\n"); ubxg6010_send_packet(&msgcfgrst); delay_ms(1000); - log_info("GPS: Configuring GPS chip serial port for baud rate 38400\n"); + if (gps_nmea_output_enabled) { + log_info("GPS: Configuring GPS NMEA output settings\n"); + ubxg6010_send_packet(&msgcfgnmea); + delay_ms(100); + } + + log_info("GPS: Configuring GPS chip I/O port settings\n"); + if (gps_nmea_output_enabled) { + // Enable both UBX and NMEA protocols + msgcfgprt.data.cfgprt.outProtoMask = 0x03; + } ubxg6010_send_packet(&msgcfgprt); delay_ms(100); - log_info("GPS: Initializing USART with baud rate 38400\n"); - usart_gps_init(38400, true); + log_info("GPS: Initializing USART with baud rate %d\n", GPS_SERIAL_PORT_BAUD_RATE); + usart_gps_init(GPS_SERIAL_PORT_BAUD_RATE, true); delay_ms(100); log_info("GPS: Setting GPS chip power mode\n"); @@ -690,36 +731,78 @@ static void ubxg6010_handle_packet(uBloxPacket *pkt) } } +void ubxg6010_reset_parser() +{ + sync_ubx = 0; + sync_nmea = 0; +} + +static void ubxg6010_handle_nmea_sentence_start(uint8_t data) +{ + if (sync_nmea == 0 && data == '$') { + sync_nmea = 1; + } else if (sync_nmea == 1) { + if (data == 'G') { + sync_nmea = 2; + } else { + sync_nmea = 0; + } + } else if (sync_nmea == 2) { + if (data >= 'A' && data <= 'Z') { + usart_ext_send_byte('$'); + usart_ext_send_byte('G'); + usart_ext_send_byte(data); + sync_nmea = 3; + } else { + sync_nmea = 0; + } + } +} + +static void ubxg6010_handle_nmea_output(uint8_t data) +{ + usart_ext_send_byte(data); + if (data == '\r') { + sync_nmea = 3; + } else if (sync_nmea == 3 && data == '\n') { + sync_nmea = 0; + } +} + void ubxg6010_handle_incoming_byte(uint8_t data) { - static uint8_t sync = 0; static uint8_t buffer_pos = 0; static uint8_t incoming_packet_buffer[sizeof(uBloxPacket) + sizeof(uBloxChecksum)]; static uBloxPacket *incoming_packet = (uBloxPacket *) incoming_packet_buffer; - if (!sync) { + if (!sync_ubx && (sync_nmea < 3)) { if (!buffer_pos && data == 0xB5) { buffer_pos = 1; incoming_packet->header.sc1 = data; } else if (buffer_pos == 1 && data == 0x62) { - sync = 1; + sync_ubx = 1; buffer_pos = 2; incoming_packet->header.sc2 = data; } else { + if (gps_nmea_output_enabled) { + ubxg6010_handle_nmea_sentence_start(data); + } buffer_pos = 0; } + } else if (gps_nmea_output_enabled && sync_nmea >= 3) { + ubxg6010_handle_nmea_output(data); } else { ((uint8_t *) incoming_packet)[buffer_pos] = data; if ((buffer_pos >= sizeof(uBloxHeader) - 1) && (buffer_pos - 1 == (incoming_packet->header.payloadSize + sizeof(uBloxHeader) + sizeof(uBloxChecksum)))) { ubxg6010_handle_packet((uBloxPacket *) incoming_packet); buffer_pos = 0; - sync = 0; + sync_ubx = 0; } else { buffer_pos++; if (buffer_pos >= sizeof(uBloxPacket) + sizeof(uBloxChecksum)) { buffer_pos = 0; - sync = 0; + sync_ubx = 0; } } } diff --git a/src/drivers/ubxg6010/ubxg6010.h b/src/drivers/ubxg6010/ubxg6010.h index 804572a..02302bc 100644 --- a/src/drivers/ubxg6010/ubxg6010.h +++ b/src/drivers/ubxg6010/ubxg6010.h @@ -13,4 +13,6 @@ bool ubxg6010_get_current_gps_data(gps_data *data); void ubxg6010_handle_incoming_byte(uint8_t data); +void ubxg6010_reset_parser(); + #endif diff --git a/src/hal/usart_ext.c b/src/hal/usart_ext.c index 49ba950..2714073 100644 --- a/src/hal/usart_ext.c +++ b/src/hal/usart_ext.c @@ -2,7 +2,11 @@ #include #include -void usart_ext_init() +#include "usart_ext.h" + +// USART3 is the external serial port, which shares TX/RX pins with the external I²C bus. + +void usart_ext_init(uint32_t baud_rate) { GPIO_InitTypeDef gpio_init; @@ -23,12 +27,12 @@ void usart_ext_init() RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); USART_InitTypeDef usart_init; - usart_init.USART_BaudRate = 19200; //0x9c4; + usart_init.USART_BaudRate = baud_rate; usart_init.USART_WordLength = USART_WordLength_8b; usart_init.USART_StopBits = USART_StopBits_1; usart_init.USART_Parity = USART_Parity_No; usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; - usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; + usart_init.USART_Mode = USART_Mode_Tx; // Enable only transmit for now | USART_Mode_Rx; USART_Init(USART3, &usart_init); USART_Cmd(USART3, ENABLE); @@ -40,3 +44,15 @@ void usart_ext_uninit() USART_DeInit(USART3); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, DISABLE); } + +void usart_ext_enable(bool enabled) +{ + USART_Cmd(USART3, enabled ? ENABLE : DISABLE); +} + +void usart_ext_send_byte(uint8_t data) +{ + while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) {} + USART_SendData(USART3, data); + while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) {} +} diff --git a/src/hal/usart_ext.h b/src/hal/usart_ext.h new file mode 100644 index 0000000..88cc7a7 --- /dev/null +++ b/src/hal/usart_ext.h @@ -0,0 +1,12 @@ +#ifndef __USART_EXT_H +#define __USART_EXT_H + +#include +#include + +void usart_ext_init(uint32_t baud_rate); +void usart_ext_uninit(); +void usart_ext_enable(bool enabled); +void usart_ext_send_byte(uint8_t data); + +#endif diff --git a/src/main.c b/src/main.c index 16a0b32..16de59d 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,7 @@ #include "hal/i2c.h" #include "hal/spi.h" #include "hal/usart_gps.h" +#include "hal/usart_ext.h" #include "hal/delay.h" #include "hal/datatimer.h" #include "drivers/ubxg6010/ubxg6010.h" @@ -61,8 +62,13 @@ int main(void) system_set_green_led(false); system_set_red_led(true); - log_info("I2C init\n"); - i2c_init(); + if (gps_nmea_output_enabled) { + log_info("External USART init\n"); + usart_ext_init(EXTERNAL_SERIAL_PORT_BAUD_RATE); + } else { + log_info("I2C init\n"); + i2c_init(); + } log_info("SPI init\n"); spi_init(); diff --git a/src/radio.c b/src/radio.c index cc599db..a3b359a 100644 --- a/src/radio.c +++ b/src/radio.c @@ -23,7 +23,7 @@ #include "radio_payload_fsq.h" radio_transmit_entry radio_transmit_schedule[] = { -#if RADIO_SI4032_TX_HORUS_V1_CONTINUOUS == true +#if RADIO_SI4032_TX_HORUS_V1_CONTINUOUS { .enabled = RADIO_SI4032_TX_HORUS_V1, .radio_type = RADIO_TYPE_SI4032, @@ -48,7 +48,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .payload_encoder = &radio_horus_v1_idle_encoder, .fsk_encoder_api = &mfsk_fsk_encoder_api, }, -#elif RADIO_SI4032_TX_HORUS_V2_CONTINUOUS == true +#elif RADIO_SI4032_TX_HORUS_V2_CONTINUOUS { .enabled = RADIO_SI4032_TX_HORUS_V2, .radio_type = RADIO_TYPE_SI4032, @@ -122,8 +122,8 @@ radio_transmit_entry radio_transmit_schedule[] = { .payload_encoder = &radio_horus_v2_payload_encoder, .fsk_encoder_api = &mfsk_fsk_encoder_api, }, -#if RADIO_SI5351_ENABLE == true -#if RADIO_SI5351_TX_CW == true +#if RADIO_SI5351_ENABLE +#if RADIO_SI5351_TX_CW { .enabled = RADIO_SI5351_TX_CW, .radio_type = RADIO_TYPE_SI5351, @@ -137,7 +137,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &morse_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_HORUS_V1 == true +#if RADIO_SI5351_TX_HORUS_V1 { .enabled = RADIO_SI5351_TX_HORUS_V1, .radio_type = RADIO_TYPE_SI5351, @@ -151,7 +151,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &mfsk_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_HORUS_V2 == true +#if RADIO_SI5351_TX_HORUS_V2 { .enabled = RADIO_SI5351_TX_HORUS_V2, .radio_type = RADIO_TYPE_SI5351, @@ -165,7 +165,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &mfsk_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_WSPR == true +#if RADIO_SI5351_TX_WSPR { .enabled = RADIO_SI5351_TX_WSPR, .radio_type = RADIO_TYPE_SI5351, @@ -178,7 +178,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_FT8 == true +#if RADIO_SI5351_TX_FT8 { .enabled = RADIO_SI5351_TX_FT8, .radio_type = RADIO_TYPE_SI5351, @@ -191,7 +191,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_JT9 == true +#if RADIO_SI5351_TX_JT9 { .enabled = RADIO_SI5351_TX_JT9, .radio_type = RADIO_TYPE_SI5351, @@ -204,7 +204,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_JT4 == true +#if RADIO_SI5351_TX_JT4 { .enabled = RADIO_SI5351_TX_JT4, .radio_type = RADIO_TYPE_SI5351, @@ -217,7 +217,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_JT65 == true +#if RADIO_SI5351_TX_JT65 { .enabled = RADIO_SI5351_TX_JT65, .radio_type = RADIO_TYPE_SI5351, @@ -230,7 +230,7 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#if RADIO_SI5351_TX_FSQ == true +#if RADIO_SI5351_TX_FSQ { .enabled = RADIO_SI5351_TX_FSQ, .radio_type = RADIO_TYPE_SI5351, @@ -366,8 +366,14 @@ static bool radio_start_transmit(radio_transmit_entry *entry) log_info("\n"); #endif + // USART interrupts may interfere with transmission timing + bool enable_gps_during_transmit = false; + switch (entry->data_mode) { case RADIO_DATA_MODE_CW: + // CW timing is not as critical + enable_gps_during_transmit = true; + morse_encoder_new(&entry->fsk_encoder, entry->symbol_rate); radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder); entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count, @@ -385,6 +391,9 @@ static bool radio_start_transmit(radio_transmit_entry *entry) entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload); break; case RADIO_DATA_MODE_HORUS_V1: + // GPS should not disturb the timing of Horus modes + enable_gps_during_transmit = true; + mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, HORUS_V1_TONE_SPACING_HZ_SI5351 * 100); radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder); entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count, @@ -394,6 +403,9 @@ static bool radio_start_transmit(radio_transmit_entry *entry) entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload); break; case RADIO_DATA_MODE_HORUS_V2: + // GPS should not disturb the timing of Horus modes + enable_gps_during_transmit = true; + mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, HORUS_V2_TONE_SPACING_HZ_SI5351 * 100); radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder); entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count, @@ -411,6 +423,9 @@ static bool radio_start_transmit(radio_transmit_entry *entry) case RADIO_DATA_MODE_FSQ_3: case RADIO_DATA_MODE_FSQ_4_5: case RADIO_DATA_MODE_FSQ_6: { + // Timing of these slow modes is not as critical + enable_gps_during_transmit = true; + char locator[5]; jtencode_mode_type jtencode_mode = radio_jtencode_mode_type_for(entry->data_mode); @@ -436,8 +451,10 @@ static bool radio_start_transmit(radio_transmit_entry *entry) return false; } - // USART interrupts may interfere with transmission timing - usart_gps_enable(false); + usart_gps_enable(enable_gps_during_transmit); + if (!enable_gps_during_transmit) { + ubxg6010_reset_parser(); + } switch (entry->radio_type) { case RADIO_TYPE_SI4032: