diff --git a/README.md b/README.md index 80ef618..ad21216 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,37 @@ -# RS41ng - Amateur radio firmware for Vaisala RS41 radiosonde +# RS41ng - Amateur radio firmware for Vaisala RS41 and Graw DFM-17 radiosondes -**NOTE:** While this firmware has been tested with great success on a number of high-altitude balloon +**NEW:** Experimental support for Graw DFM-17 radiosondes added! Please test and report any issues! + +**NOTE:** **DFM-17 radiosondes require a GPS lock (and clear visibility to the sky) to calibrate its internal oscillator.** +DFM-17 transmissions, especially APRS, may not decode correctly because of incorrect timing before the internal oscillator has been calibrated. + +**NOTE:** While this firmware has been tested (on RS41) 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). +## What is RS41ng? + +RS41ng is a custom, amateur radio-oriented firmware for [Vaisala RS41](https://www.vaisala.com/en/products/weather-environmental-sensors/upper-air-radiosondes-rs41-rs41-e-models) +and [Graw DFM-17](https://www.graw.de/products/radiosondes/dfm-17/) radiosondes. These radiosonde models +have very similar hardware, so that it is relatively easy to support both with the same codebase. +It is unlikely that RS41ng could support any other radiosonde hardware for now. + Some code is based on an earlier RS41 firmware project called [RS41HUP](https://github.com/df8oe/RS41HUP), 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 reporting issues -* Please use [GitHub discussions](../../discussions) for asking questions and for sharing info about your RS41-based projects +* Please use [GitHub discussions](../../discussions) for asking questions and for sharing info about your radiosonde-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 * However, please remember to post questions about usage to [GitHub discussions](../../discussions) -## What are the Vaisala RS41 radiosondes and how can I get one? +## What are radiosondes and how can I get one? -The RS41 radiosondes are used extensively for atmospheric sounding by the meteorological institutes in various countries and thus easily -available to be collected once they land, an activity called radiosonde hunting: see YouTube presentation about +Radiosondes, especially the RS41 and DFM-17, are used extensively for atmospheric sounding by the meteorological +institutes in various countries and thus easily available to be collected once they land, an activity called +radiosonde hunting: see YouTube presentation about [Tracking and Chasing Weather Balloons by Andreas Spiess](https://www.youtube.com/watch?v=vQfztG60umI) or [Chasing Radiosonde Weather Balloons used in Meteorology for Fun by Mark VK5QI](https://www.youtube.com/watch?v=fb9gNomWrAY) for more details! @@ -34,23 +46,44 @@ For your own receiver station, you will need: 3. Radiosonde tracker software: common choices are [RS41 Tracker](http://escursioni.altervista.org/Radiosonde/) for Windows and [radiosonde_auto_rx](https://github.com/projecthorus/radiosonde_auto_rx) for Linux / Raspberry Pi. -### What can I do with an RS41 radiosonde? +### What can I do with the RS41 and DFM-17 radiosondes? -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) +The [Vaisala RS41](https://www.vaisala.com/en/products/weather-environmental-sensors/upper-air-radiosondes-rs41-rs41-e-models) +and [Graw DFM-17](https://www.graw.de/products/radiosondes/dfm-17/) +radiosondes both use off-the-shelf 32-bit [STM32F100-series](https://www.st.com/en/microcontrollers-microprocessors/stm32f100-value-line.html) microcontrollers, +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). -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) -GPS chip, so it can be used as a tracker device, e.g. for high-altitude balloons. +There is detailed information about the hardware of these radiosonde models on the following pages: -The RS41ng firmware is just one example of what can be achieved with the RS41 hardware! +* https://github.com/bazjo/RS41_Hardware +* https://wiki.recessim.com/view/DFM-17_Radiosonde + +The radiosondes can be reprogrammed 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 contain Ublox +GPS chips, so it can be used as a tracker device, e.g. for high-altitude balloons. + +The RS41ng firmware is just one example of what can be achieved with the hardware of these radiosondes! + +## What are high-altitude balloon flights? + +High-altitude balloon flights arranged by hobbyists are fun technical experiments. +The flight goals are usually related to aerial photography, testing of radio tracker and transmitter hardware +and different kinds of measurements with on-board sensors. + +The following websites contain more information about planning and launching a high-altitude balloon flight: + +* https://www.overlookhorizon.com/ - Overlook Horizon high-altitude balloon flights. +* http://www.projecthorus.org/ - Australian high-altitude balloon project. Website no longer updated. +* https://www.areg.org.au/archives/category/activities/project-horus - Newer Project Horus flights. +* https://ukhas.org.uk/ - UK high-altitude society. +* http://www.daveakerman.com/ - High-altitude balloon blog of Dave Akerman. +* https://0xfeed.tech/ - High-altitude balloon / ham radio blog of Mikael Nousiainen OH3BHX. ## Why does the RS41ng firmware exist? The motivation to develop this firmware is to provide a clean, customizable and -modular codebase for developing RS41 radiosonde-based experiments. +modular codebase for developing radiosonde-based experiments. See the feature list below. @@ -60,25 +93,37 @@ The main features the RS41ng firmware are: * Support for multiple transmission modes: * Standard 1200-baud APRS - * Option to transmit APRS weather reports using readings from an external BMP280/BME280 sensor + * Option to transmit APRS weather reports using readings from an external BMP280/BME280 sensor (only RS41 supports custom sensors) * [Horus 4FSK v1 and v2 modes](https://github.com/projecthorus/horusdemodlib/wiki) that has improved performance compared to APRS or RTTY * There is an option to use continuous transmit mode (for either V1 or V2 mode), which helps with receiver frequency synchronization and improves reception. * In order to use Horus 4FSK mode on a flight, you will need to request a new Horus 4FSK payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it * Morse code (CW) - * JT65/JT9/JT4/FT8/WSPR/FSQ digital modes on HF/VHF amateur radio bands using an external Si5351 clock generator connected to the external I²C bus * "Pip" mode, which transmits a short beep generated using CW to indicate presence of the transmitter + * **RS41 only:** JT65/JT9/JT4/FT8/WSPR/FSQ digital modes on HF/VHF amateur radio bands using an external Si5351 clock generator connected to the external I²C bus * Support for transmitting multiple modes consecutively with custom, rotating comment messages (see `config.c`) * Support for GPS-based scheduling is available for transmission modes that require specific timing for transmissions +* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation +* Extensibility to allow easy addition of new transmission modes and new sensors + +Features available on RS41 hardware only: + * Support for custom sensors via the external I²C bus * Support for counting pulses on expansion header pin 2 (I2C2_SDA (PB11) / UART3 RX) for use with sensors like Geiger counters * GPS NMEA data output via the external serial port pin 3 (see below). This disables use of I²C devices as the serial port pins are shared with the I²C bus pins. * This allows using the RS41 sonde GPS data in external tracker hardware, such as Raspberry Pi or other microcontrollers. -* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation (and ultimately DMA-based symbol timing, if possible) -* Extensibility to allow easy addition of new transmission modes and new sensors + +Notes for DFM-17: + +* **DFM-17 radiosondes require a GPS lock (and clear visibility to the sky) to calibrate its internal oscillator.** + This is necessary, because the internal oscillator is not particularly accurate. + DFM-17 transmissions, especially APRS, may not decode correctly because of incorrect timing before + the internal oscillator has been calibrated. + * The RS41 radiosonde hardware uses an external oscillator, which is more stable, so RS41 does not + suffer from the same issue. ### Transmission modes -On the internal Si4032 transmitter: +On the internal Si4032 (RS41) and Si4063 (DFM-17) transmitters: * APRS (1200 baud) * Horus 4FSK v1 and v2 (100 baud) @@ -96,7 +141,7 @@ On an external Si5351 clock generator connected to the external I²C bus of the #### Notes about APRS * Bell 202 frequencies are generated via hardware PWM, but the symbol timing is created in a loop with delay -* There is also code available to use DMA transfers for symbol timing to achieve greater accuracy, but I have not been able to get the timings working correctly +* There is also code available to use DMA transfers for symbol timing to achieve greater accuracy, but the timings are not working correctly #### Notes about Horus 4FSK @@ -105,7 +150,7 @@ On an external Si5351 clock generator connected to the external I²C bus of the * See [horus-gui installation and usage instructions](https://github.com/projecthorus/horusdemodlib/wiki/1.1-Horus-GUI-Reception-Guide-(Windows-Linux-OSX)) and [horusdemodlib](https://github.com/projecthorus/horusdemodlib) library that is responsible for demodulating the signal. * In order to use Horus 4FSK mode on a flight, you will need to request a new Horus 4FSK payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it -### External sensors +### External sensors (RS41 only) It is possible to connect external sensors to the I²C bus of the RS41 radiosonde. @@ -123,7 +168,7 @@ Sensor driver code contributions are welcome! * Configurable transmission frequencies and schedules based on location / altitude * Support for more I²C sensors -* RTTY on both Si4032 (70 cm, non-standard shift) and Si5351 (HF + 2m) with configurable shift +* RTTY on both Si4032/Si4063 (70 cm, non-standard shift) and Si5351 (HF + 2m) with configurable shift * Investigate possibility to implement 1200 bps Bell 202 modulation (and possibly also 300 bps Bell 103 modulation) for APRS using Si5351, this requires special handling to make Si5351 change frequency quickly @@ -133,11 +178,20 @@ Sensor driver code contributions are welcome! 1. Configure your amateur radio call sign, transmission schedule (time sync), transmit frequencies and transmission mode parameters in `config.h` -2. Set up transmitted message templates in `config.c`, depending on the modes you use + * Select the desired radiosonde type in the beginning of the file by removing the `//` comment from either + the `#define RS41` or `#define DFM17` lines. + * Customize at least the following settings: + * `CALLSIGN` + * For RS41, the settings beginning with `RADIO_SI4032_` to select transmit power and the modes to transmit + * For DFM-17, the settings beginning with `RADIO_SI4063_` to select transmit power and the modes to transmit + * `HORUS_V2_PAYLOAD_ID` if you transmit Horus 4FSK + * `APRS_COMMENT` if you transmit APRS +2. Set up transmitted message templates in `config.c`, depending on the modes you use. + You can customize the APRS and CW messages in more detail here. ### Power consumption and power-saving features -Power consumption notes (at 3V supply voltage) by Mark VK5QI: +Power consumption notes (at 3V supply voltage) for RS41 by Mark VK5QI: - GPS in full power acquisition mode: 110-120 mA (TX off), 160-170 mA (TX on) - GPS locked (5 sats), full power: 96 - 126 mA (TX off), 170 mA (TX on) @@ -313,16 +367,21 @@ 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` -## Flashing the firmware +## Prepare the radiosonde for flashing the firmware Hardware requirements: -* A working [Vaisala RS41 radiosonde](https://www.vaisala.com/en/products/instruments-sensors-and-other-measurement-devices/soundings-products/rs41) :) -* An [ST-LINK v2 programmer for the STM32 microcontroller](https://www.st.com/en/development-tools/st-link-v2.html) in the RS41 radiosonde. - * These smaller [ST-LINK v2 USB dongles](https://www.adafruit.com/product/2548) also work well. - * [Partco sells the ST-LINK v2 USB dongles in Finland](https://www.partco.fi/en/measurement/debugging/20140-stlinkv2.html) +* A working [Vaisala RS41](https://www.vaisala.com/en/products/instruments-sensors-and-other-measurement-devices/soundings-products/rs41) + or [Graw DFM-17](https://www.graw.de/products/radiosondes/dfm-17/) radiosonde :) +* An [ST-LINK v2 programmer for the STM32 microcontroller](https://www.st.com/en/development-tools/st-link-v2.html) in the RS41 radiosonde. + * These smaller [ST-LINK v2 USB dongles](https://www.adafruit.com/product/2548) also work well. + * [Partco sells the ST-LINK v2 USB dongles in Finland](https://www.partco.fi/en/measurement/debugging/20140-stlinkv2.html) -The pinout of the RS41 connector (by DF8OE and VK5QI) is the following: +Follow the instructions below for the radiosonde model you have. + +### Vaisala RS41 programming connector + +The pinout of the RS41 programming connector (by DF8OE and VK5QI) is the following: ``` ______________________| |______________________ @@ -352,22 +411,80 @@ ______________________| |______________________ * 9 - SWDIO (PA13) * 10 - GND -### Steps to flash the firmware +#### Connect the RS41 radiosonde to the programmer -1. If your ST-LINK v2 programmer is capable of providing power (as some third-party clones are), +1. If your ST-LINK v2 programmer is capable of providing a voltage of 3.3V (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) - * GND -> Pin 1 (GND) - * 3.3V -> Pin 5 (MCU switch 3.3V) (only required when using the programmer to power the sonde) -3. Unlock the flash protection - needed only before reprogramming the sonde for the first time - * `openocd -f ./openocd_rs41.cfg -c "init; halt; flash protect 0 0 63 off; exit"` - * **NOTE:** If the above command fails with an error about erasing sectors, try replacing the number `63` with `31`: - * `openocd -f ./openocd_rs41.cfg -c "init; halt; flash protect 0 0 31 off; exit"` -4. Flash the firmware - * `openocd -f ./openocd_rs41.cfg -c "program build/src/RS41ng.elf verify reset exit"` -5. Power cycle the sonde to start running the new firmware + * SWDIO -> Pin 9 (SWDIO) + * SWCLK -> Pin 8 (SWCLK) + * GND -> Pin 1 (GND) + * 3.3V -> Pin 5 (MCU switch 3.3V) (only required when using the programmer to power the sonde) + +### Graw DFM-17 programming connector + +The DFM-17 programming connector is an unpopulated group of pads on the circuit board +between the sensor boom connector and the main STM32 microcontroller. + +The pinout of the DFM-17 programming connector is the following: + +``` +_____ +| | +| S | _____________________ +| e | | | +| n | | 1 2 | +| s | | | +| o | | 3 4 | ________________________ +| r | | | | +| | | 5 6 | | STM32 microcontroller +| b | | | | +| o | | 7 8 | | +| o | | | | +| m | | 9 10 | | +| | | | | +| | |____________________ | +| | +|___| + +(The sensor boom connector is on the left and the main microcontroller unit on the right side) +``` + +* 1 - VTRef + * This pin powers the device via 3.3V voltage from an ST-LINK programmer dongle +* 2 - SWDIO / TMS +* 3 - GND +* 4 - SWCLK / TCK +* 5 - GND +* 6 - SWO EXT TRACECTL / TDO +* 7 - KEY +* 8 - NC EXT / TDI +* 9 - GNDDetect +* 10 - nRESET + +#### Connect the DFM-17 radiosonde to the programmer + +1. Since the DFM-17 programming connector is just an unpopulated group of pads on the circuit board, + **you will need to either solder wires directly to the pads or alternatively solder a 0.05" (1.27mm) 5-by-2 pin header to the pads**. + There are suitable ribbon cables with 5x2 0.05" connectors available for this type of pin header. +2. Connect an ST-LINK v2 programmer dongle to the sonde via the following pins: + * SWDIO -> Pin 2 (SWDIO) + * SWCLK -> Pin 4 (SWCLK) + * RST -> Pin 10 (RST) + * GND -> Pin 5 (GND) + * 3.3V -> Pin 1 (VTRef) (only required when using the programmer to power the sonde) +3. If your ST-LINK v2 programmer is capable of providing a voltage of 3.3V (as some third-party clones are), + remove the batteries from the sonde. Otherwise, leave the batteries in and power on the sonde. + +## Flashing the radiosonde with the firmware (both RS41 and DFM-17) + +1. Unlock the flash protection - needed only before reprogramming the sonde for the first time + * `openocd -f ./openocd_rs41.cfg -c "init; halt; flash protect 0 0 63 off; exit"` + * **NOTE:** If the above command fails with an error about erasing sectors, try replacing the number `63` with either `31` or the number the error message suggests: + * `openocd -f ./openocd_rs41.cfg -c "init; halt; flash protect 0 0 31 off; exit"` +2. Flash the firmware + * `openocd -f ./openocd_rs41.cfg -c "program build/src/RS41ng.elf verify reset exit"` +3. Power cycle the sonde to start running the new firmware ## Developing / debugging the firmware @@ -416,6 +533,8 @@ use command `file src/RS41ng.elf`. ## Si4032 Bell FSK modulation hack for APRS +Notes by Mikael OH3BHX: + The idea behind the APRS / Bell 202 modulation implementation is based on RS41HUP project and its "ancestors" and I'm describing it here, since it has not been documented elsewhere. @@ -483,7 +602,9 @@ rtl_fm -f 432500000 -M fm -s 250k -r 48000 -g 22 - | ./aprs - # Authors -* Mikael Nousiainen OH3BHX +* Mikael Nousiainen OH3BHX +* Mike Hojnowski KD2EAT contributed significantly to Graw DFM-17 support +* Various authors with smaller contributions from GitHub pull requests * Original codebase: DF8OE and other authors of the [RS41HUP](https://github.com/df8oe/RS41HUP) project * Horus 4FSK code adapted from [darksidelemm fork of RS41HUP](https://github.com/darksidelemm/RS41HUP) project @@ -496,8 +617,26 @@ rtl_fm -f 432500000 -M fm -s 250k -r 48000 -g 22 - | ./aprs - * http://happysat.nl/RS-41/RS41.html - Vaisala RS-41 SGP Modification and info about the original firmware settings * https://destevez.net/2018/06/flashing-a-vaisala-rs41-radiosonde/ * https://destevez.net/2017/11/tracking-an-rs41-sgp-radiosonde-and-reporting-to-aprs/ +* https://github.com/digiampietro/esp8266-rs41 - A tool for reconfiguring RS41s via its serial port -## Alternative firmware projects +## Vaisala RS41 hardware datasheets + +* STM32F100x8 datasheet: https://www.st.com/resource/en/datasheet/stm32f100cb.pdf +* Si4032 datasheet: https://www.silabs.com/documents/public/data-sheets/Si4030-31-32.pdf +* Si4030/31/32 Register Descriptions: https://www.silabs.com/documents/public/application-notes/AN466.pdf + +## Graw DFM-17 hardware documentation + +* https://wiki.recessim.com/view/DFM-17_Radiosonde - Reverse-engineered documentation on the DFM-17 hardware + +## Graw DFM-17 hardware datasheets + +* STM32F100x8 datasheet: https://www.st.com/resource/en/datasheet/stm32f100cb.pdf +* Si4063 datasheet: https://www.silabs.com/documents/public/data-sheets/Si4063-60-C.pdf +* Programming Guide for EZRadioPRO Si4x6x Devices: https://www.silabs.com/documents/public/application-notes/AN633.pdf +* Si4x6x API Documentation: http://www.silabs.com/documents/public/application-notes/EZRadioPRO_REVC2_API.zip + +## Alternative RS41 firmware projects (only for RS41!) * https://github.com/df8oe/RS41HUP - The original amateur radio firmware for RS41 * https://github.com/darksidelemm/RS41HUP - A fork of the original firmware that includes support for Horus 4FSK (but omits APRS) diff --git a/docs/74LVC3G34_Q100.pdf b/docs/74LVC3G34_Q100.pdf deleted file mode 100644 index cacdd88..0000000 Binary files a/docs/74LVC3G34_Q100.pdf and /dev/null differ diff --git a/docs/Si4030-31-32.pdf b/docs/Si4030-31-32.pdf deleted file mode 100644 index 6656d75..0000000 Binary files a/docs/Si4030-31-32.pdf and /dev/null differ diff --git a/docs/Si4030_31_32_register_descriptions_AN466.pdf b/docs/Si4030_31_32_register_descriptions_AN466.pdf deleted file mode 100644 index ce56619..0000000 Binary files a/docs/Si4030_31_32_register_descriptions_AN466.pdf and /dev/null differ diff --git a/docs/stm32f100c8t6b_en.CD00251732.pdf b/docs/stm32f100c8t6b_en.CD00251732.pdf deleted file mode 100644 index 6995fc6..0000000 Binary files a/docs/stm32f100c8t6b_en.CD00251732.pdf and /dev/null differ diff --git a/src/config.c b/src/config.c index 78d21e3..ada1e77 100644 --- a/src/config.c +++ b/src/config.c @@ -27,6 +27,8 @@ * $he - Heading in degrees (up to 3 chars) * $pc - Pulse counter value (wraps to zero at 65535, 16-bit unsigned value) * $ri - Radiation intensity in µR/h (up to 5 chars) + * $ct - Clock calibration trim value (0-31, only for DFM-17) + * $cc - Clock calibration change count (only for DFM-17) * * Allowed message lengths: * @@ -56,6 +58,7 @@ volatile bool system_initialized = false; * Maximum length: 64 characters. */ char *cw_message_templates[] = { + "$cs", // "$cs $loc6 $altm $gs km/h $tiC", // "$cs $loc6", // "$alt m", @@ -83,7 +86,7 @@ char *aprs_comment_templates[] = { // " B$bu $loc12 $hh:$mm:$ss - " APRS_COMMENT, // " $loc12 - " APRS_COMMENT, // " $teC $hu% $prmb PC $pc RI $ri uR/h - " APRS_COMMENT, -// " " APRS_COMMENT, + " " APRS_COMMENT, NULL }; diff --git a/src/config.h b/src/config.h index 0056158..766a00c 100644 --- a/src/config.h +++ b/src/config.h @@ -1,9 +1,21 @@ #ifndef __CONFIG_H #define __CONFIG_H +// Define radiosonde type. Remove the "//" comment to select either RS41 or DFM17. +//#define RS41 +//#define DFM17 + +#if !defined(RS41) && !defined(DFM17) +#error "No hardware type specified. Please define RS41 or DFM17." +#endif +#if defined(RS41) && defined(DFM17) +#error "Please define either RS41 or DFM17." +#endif + + // Enable semihosting to receive debug logs during development // 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. +// NOTE: Semihosting has to be disabled when the radiosonde is not connected to an STM32 programmer dongle, otherwise the firmware will not run. //#define SEMIHOSTING_ENABLE //#define LOGGING_ENABLE @@ -25,30 +37,6 @@ // Allow powering off the sonde by pressing the button for over a second (when the sonde is not transmitting) #define ALLOW_POWER_OFF false -// Define the I²C bus clock speed in Hz. -// The default of 100000 (= 100 kHz) should be used with the Si5351 clock generator to allow fast frequency changes. -// Note that some BMP280 sensors may require decreasing the clock speed to 10000 (= 10 kHz) -#define I2C_BUS_CLOCK_SPEED 100000 - -// Enable use of an externally connected I²C BMP280/BME280 atmospheric sensor -// NOTE: Only BME280 sensors will report humidity. For BMP280 humidity readings will be zero. -#define SENSOR_BMP280_ENABLE false -// BMP280/BME280 I²C device address is usually 0x76 or 0x77. -#define SENSOR_BMP280_I2C_ADDRESS 0x77 - -// Enable use of an externally connected I²C RadSens radiation sensor -#define SENSOR_RADSENS_ENABLE false -// Expected RadSens chip ID to verify initialization of the sensor, default is 0x7D. -#define SENSOR_RADSENS_CHIP_ID 0x7D -// RadSens I²C device address, default is 0x66. -#define SENSOR_RADSENS_I2C_ADDRESS 0x66 -// Uncomment to set RadSens sensor sensitivity (imp/MKR). The default value is 105 imp/MKR. -// The value is stored in the non-volatile memory of the microcontroller. -#define SENSOR_RADSENS_SENSITIVITY 105 - -// Enable use of an externally connected I²C Si5351 clock generator chip for HF radio transmissions -#define RADIO_SI5351_ENABLE false - // Number of character pairs to include in locator #define LOCATOR_PAIR_COUNT_FULL 6 // max. 6 (12 characters WWL) @@ -82,6 +70,34 @@ #error GPS NMEA output via serial port cannot be enabled simultaneously with the I2C bus. #endif +/** + * RS41 only: Global configuration (there is no I²C bus exposed in DFM-17) + */ + +// Define the I²C bus clock speed in Hz. +// The default of 100000 (= 100 kHz) should be used with the Si5351 clock generator to allow fast frequency changes. +// Note that some BMP280 sensors may require decreasing the clock speed to 10000 (= 10 kHz) +#define I2C_BUS_CLOCK_SPEED 100000 + +// Enable use of an externally connected I²C BMP280/BME280 atmospheric sensor +// NOTE: Only BME280 sensors will report humidity. For BMP280 humidity readings will be zero. +#define SENSOR_BMP280_ENABLE false +// BMP280/BME280 I²C device address is usually 0x76 or 0x77. +#define SENSOR_BMP280_I2C_ADDRESS 0x77 + +// Enable use of an externally connected I²C RadSens radiation sensor +#define SENSOR_RADSENS_ENABLE false +// Expected RadSens chip ID to verify initialization of the sensor, default is 0x7D. +#define SENSOR_RADSENS_CHIP_ID 0x7D +// RadSens I²C device address, default is 0x66. +#define SENSOR_RADSENS_I2C_ADDRESS 0x66 +// Uncomment to set RadSens sensor sensitivity (imp/MKR). The default value is 105 imp/MKR. +// The value is stored in the non-volatile memory of the microcontroller. +#define SENSOR_RADSENS_SENSITIVITY 105 + +// Enable use of an externally connected I²C Si5351 clock generator chip for HF radio transmissions +#define RADIO_SI5351_ENABLE false + // Enable pulse counter via expansion header pin for use with devices like Geiger counters. // This disables the external I²C bus and the serial port as the expansion header pin 2 (I2C2_SDA (PB11) / UART3 RX) is used for pulse input. // Also changes the Horus 4FSK V2 data format and adds a custom data field for pulse count. @@ -110,7 +126,7 @@ #endif /** - * Built-in Si4032 radio chip transmission configuration + * RS41 only: Built-in Si4032 radio chip transmission configuration */ // Si4032 transmit power: 0..7 @@ -148,7 +164,42 @@ #define RADIO_SI4032_TX_FREQUENCY_HORUS_V2 432501000 /** - * External Si5351 radio chip transmission configuration + * DFM-17 only: Built-in Si4063 radio chip transmission configuration + */ + +// Si4063 transmit power: 0..127 +// TODO: Document Si4063 transmit power levels +#define RADIO_SI4063_TX_POWER 127 + +// Which modes to transmit using the built-in Si4063 transmitter chip +// The COUNT settings define the number of times that each type of transmission is repeated +#define RADIO_SI4063_TX_CW false +#define RADIO_SI4063_TX_CW_COUNT 1 +#define RADIO_SI4063_TX_PIP false +#define RADIO_SI4063_TX_PIP_COUNT 6 +#define RADIO_SI4063_TX_APRS true +#define RADIO_SI4063_TX_APRS_COUNT 2 +#define RADIO_SI4063_TX_HORUS_V1 false +#define RADIO_SI4063_TX_HORUS_V1_COUNT 1 +#define RADIO_SI4063_TX_HORUS_V2 true +#define RADIO_SI4063_TX_HORUS_V2_COUNT 6 + +// Continuous transmit mode can be enabled for *either* Horus V1 or V2, but not both. This disables all other transmission modes. +// The continuous mode transmits Horus 4FSK preamble between transmissions +// to allow Horus receivers to keep frequency synchronization at all times, which improves reception. +#define RADIO_SI4063_TX_HORUS_V1_CONTINUOUS false +#define RADIO_SI4063_TX_HORUS_V2_CONTINUOUS false + +// Transmit frequencies for the Si4063 transmitter modes +#define RADIO_SI4063_TX_FREQUENCY_CW 432500000 +#define RADIO_SI4063_TX_FREQUENCY_PIP 432500000 +#define RADIO_SI4063_TX_FREQUENCY_APRS_1200 432500000 +// Use a frequency offset to place FSK tones slightly above the defined frequency for SSB reception +#define RADIO_SI4063_TX_FREQUENCY_HORUS_V1 432501000 +#define RADIO_SI4063_TX_FREQUENCY_HORUS_V2 432501000 + +/** + * RS41 only: External Si5351 radio chip transmission configuration */ // Si5351 transmit power: 0..3 @@ -236,6 +287,7 @@ */ #define HORUS_FREQUENCY_OFFSET_SI4032 0 +#define HORUS_FREQUENCY_OFFSET_SI4063 0 /** * Horus V1 4FSK mode settings (deprecated, please use Horus V2 mode) @@ -246,6 +298,7 @@ // Please request a new payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it #define HORUS_V1_PAYLOAD_ID 0 #define HORUS_V1_BAUD_RATE_SI4032 100 +#define HORUS_V1_BAUD_RATE_SI4063 100 #define HORUS_V1_BAUD_RATE_SI5351 50 #define HORUS_V1_PREAMBLE_LENGTH 16 #define HORUS_V1_IDLE_PREAMBLE_LENGTH 32 @@ -265,6 +318,7 @@ // Please request a new payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it #define HORUS_V2_PAYLOAD_ID 256 #define HORUS_V2_BAUD_RATE_SI4032 100 +#define HORUS_V2_BAUD_RATE_SI4063 100 #define HORUS_V2_BAUD_RATE_SI5351 50 #define HORUS_V2_PREAMBLE_LENGTH 16 #define HORUS_V2_IDLE_PREAMBLE_LENGTH 32 diff --git a/src/config_internal.h b/src/config_internal.h index 3c9ccb0..73aa5ce 100644 --- a/src/config_internal.h +++ b/src/config_internal.h @@ -13,6 +13,9 @@ // PARIS: 50 dot durations, 20 WPM -> 60ms per unit #define MORSE_WPM_TO_SYMBOL_RATE(wpm) (1000 / (60 * 20 / wpm)) +// Experimental fast frequency change routine for Si5351, not tested +#define SI5351_FAST_ENABLE false + #include extern bool leds_enabled; diff --git a/src/drivers/si4063/si4063.c b/src/drivers/si4063/si4063.c new file mode 100644 index 0000000..a32317e --- /dev/null +++ b/src/drivers/si4063/si4063.c @@ -0,0 +1,697 @@ +/** + * The DFM17 radiosonde-compatible Si4063 driver code has been inspired by: + * - pAVAR9: https://github.com/Upuaut/pAVAR9/ + * - uTrak: https://github.com/thasti/utrak/ + * - DFM17 APRS/RTTY firmware by Derek Rowland + */ + +#include +#include +#include + +#include "hal/hal.h" +#include "hal/delay.h" +#include "hal/spi.h" +#include "si4063.h" +#include "gpio.h" +#include "log.h" + +#define SI4063_CLOCK 25600000UL + +#define GPIO_SI4063_SDN GPIOC +#define GPIO_PIN_SI4063_SDN GPIO_Pin_3 + +#define GPIO_SI4063_NSEL GPIOB +#define GPIO_PIN_SI4063_NSEL GPIO_Pin_2 + +#define GPIO_SI4063_SDI GPIOA +#define GPIO_PIN_SI4063_SDI GPIO_Pin_7 + +#define GPIO_SI4063_GPIO2 GPIOD +#define GPIO_PIN_SI4063_GPIO2 GPIO_Pin_0 + +#define GPIO_SI4063_GPIO3 GPIOA +#define GPIO_PIN_SI4063_GPIO3 GPIO_Pin_4 + +#define SI4063_COMMAND_PART_INFO 0x01 +#define SI4063_COMMAND_POWER_UP 0x02 +#define SI4063_COMMAND_SET_PROPERTY 0x11 +#define SI4063_COMMAND_GPIO_PIN_CFG 0x13 +#define SI4063_COMMAND_START_TX 0x31 +#define SI4063_COMMAND_CHANGE_STATE 0x34 +#define SI4063_COMMAND_GET_ADC_READING 0x14 +#define SI4063_COMMAND_READ_CMD_BUFF 0x44 + +#define SI4063_STATE_SLEEP 0x01 +#define SI4063_STATE_SPI_ACTIVE 0x02 +#define SI4063_STATE_READY 0x03 +#define SI4063_STATE_TX_TUNE 0x05 +#define SI4063_STATE_TX 0x07 + +/** + * Modulation settings from Derek Rowland's firmware + */ + +#define SI4063_DATA_RATE_APRS 4400 + +/** + * Filters from uTrak: https://github.com/thasti/utrak + * + * These filters consist of low-pass filters for harmonics of the square wave modulation waveform + * and a high-pass filter for APRS pre-emphasis. + */ + +/** + * 6dB@1200 Hz, 2400 Hz + */ +uint8_t si4063_filter_6db_1200_2400[9] = {0x1d, 0xe5, 0xb8, 0xaa, 0xc0, 0xf5, 0x36, 0x6b, 0x7f}; +/** + * 3db@1200 Hz, 2400 Hz + */ +uint8_t si4063_filter_3db_1200_2400[9] = {0x07, 0xde, 0xbf, 0xb9, 0xd4, 0x05, 0x40, 0x6d, 0x7f}; +/** + * LP only, 2400 Hz + */ +uint8_t si4063_filter_lp_2400[9] = {0xfa, 0xe5, 0xd8, 0xde, 0xf8, 0x21, 0x4f, 0x71, 0x7f}; +/** + * LP only, 4800 Hz + */ +uint8_t si4063_filter_lp_4800[9] = {0xd9, 0xf1, 0x0c, 0x29, 0x44, 0x5d, 0x70, 0x7c, 0x7f}; +/** + * LP only, 4400 Hz + */ +uint8_t si4063_filter_lp_4400[9] = {0xd5, 0xe9, 0x03, 0x20, 0x3d, 0x58, 0x6d, 0x7a, 0x7f}; +/** + * 6dB@1200Hz, 4400 Hz (bad stopband) + */ +uint8_t si4063_filter_6db_1200_4400[9] = {0x81, 0x9f, 0xc4, 0xee, 0x18, 0x3e, 0x5c, 0x70, 0x76}; + +uint32_t current_frequency_hz = 434000000UL; +uint32_t current_deviation_hz = 0; + +static inline void si4063_set_chip_select(bool select) +{ + spi_set_chip_select(GPIO_SI4063_NSEL, GPIO_PIN_SI4063_NSEL, select); + + // Output enable time, 20ns + for (uint32_t i = 0; i < 0xFFFF; i++); +} + +static int si4063_wait_for_cts() +{ + uint16_t timeout = 0xFFFF; + uint8_t response; + + // Poll CTS over SPI + do + { + si4063_set_chip_select(true); + spi_send(SI4063_COMMAND_READ_CMD_BUFF); + response = spi_read(); + si4063_set_chip_select(false); + } while (response != 0xFF && timeout--); + + if (timeout == 0) { + log_error("ERROR: Si4063 timeout\n"); + } + + return timeout > 0 ? HAL_OK : HAL_ERROR; +} + +static int si4063_read_response(uint8_t length, uint8_t *data) +{ + uint16_t timeout = 0xFFFF; + uint8_t response; + + // Poll CTS over SPI + do { + si4063_set_chip_select(true); + spi_send(SI4063_COMMAND_READ_CMD_BUFF); + response = spi_read(); + if (response == 0xFF) { + break; + } + si4063_set_chip_select(false); + + delay_us(10); + } while(timeout--); + + if (timeout == 0) { + log_error("ERROR: Si4063 timeout\n"); + si4063_set_chip_select(false); + return HAL_ERROR; + } + + // Read the requested data + while (length--) { + *(data++) = spi_read(); + } + + si4063_set_chip_select(false); + + return HAL_OK; +} + +static void si4063_send_command(uint8_t command, uint8_t length, uint8_t *data) +{ + si4063_wait_for_cts(); + + si4063_set_chip_select(true); + + spi_send(command); + + while (length--) { + spi_send(*(data++)); + } + + si4063_set_chip_select(false); +} + +static int si4063_power_up() +{ + si4063_wait_for_cts(); + + uint8_t data[] = { + 0x01, // 0x01 = FUNC PRO - Power the chip up into EZRadio PRO functional mode. + 0x01, // 0x01 = Reference signal is derived from an external TCXO. + (SI4063_CLOCK >> 24) & 0xFF, // VCXO frequency + (SI4063_CLOCK >> 16) & 0xFF, + (SI4063_CLOCK >> 8) & 0xFF, + SI4063_CLOCK & 0xFF + }; + + si4063_send_command(SI4063_COMMAND_POWER_UP, sizeof(data), data); + + return si4063_wait_for_cts(); +} + +static void si4603_set_shutdown(bool active) +{ + if (active) { + GPIO_SetBits(GPIO_SI4063_SDN, GPIO_PIN_SI4063_SDN); + } else { + GPIO_ResetBits(GPIO_SI4063_SDN, GPIO_PIN_SI4063_SDN); + } +} + +static void si4063_set_state(uint8_t state) +{ + log_debug("Si4063: Set state %02x\n", state); + si4063_send_command(SI4063_COMMAND_CHANGE_STATE, 1, &state); +} + +void si4063_enable_tx() +{ + log_debug("Si4063: Enable TX\n"); + si4063_set_state(SI4063_STATE_TX); +} + +void si4063_inhibit_tx() +{ + log_debug("Si4063: Inhibit TX\n"); + si4063_set_state(SI4063_STATE_READY); +} + +void si4063_disable_tx() +{ + // Is this needed? + si4063_set_state(SI4063_STATE_SLEEP); +} + +static int si4063_get_outdiv(const uint32_t frequency_hz) +{ + // Select the output divider according to the recommended ranges in the Si406x datasheet + if (frequency_hz < 177000000UL) { + return 24; + } else if (frequency_hz < 239000000UL) { + return 16; + } else if (frequency_hz < 353000000UL) { + return 12; + } else if (frequency_hz < 525000000UL) { + return 8; + } else if (frequency_hz < 705000000UL) { + return 6; + } + + return 4; +} + +static int si4063_get_band(const uint32_t frequency_hz) +{ + if (frequency_hz < 177000000UL) { + return 5; + } else if (frequency_hz < 239000000UL) { + return 4; + } else if (frequency_hz < 353000000UL) { + return 3; + } else if (frequency_hz < 525000000UL) { + return 2; + } else if (frequency_hz < 705000000UL) { + return 1; + } + + return 0; +} + +void si4063_set_tx_frequency(const uint32_t frequency_hz) +{ + uint8_t outdiv, band; + uint32_t f_pfd, n, m; + float ratio, rest; + + log_debug("Si4063: Set frequency %lu\n", frequency_hz); + + outdiv = si4063_get_outdiv(frequency_hz); + band = si4063_get_band(frequency_hz); + + f_pfd = 2 * SI4063_CLOCK / outdiv; + n = frequency_hz / f_pfd - 1; + + ratio = (float) frequency_hz / f_pfd; + rest = ratio - n; + + m = rest * 524288UL; + + // Set the frequency band + { + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x01, // Set 1 property + 0x51, // 0x51 = MODEM_CLKGEN_BAND + 0x08 + band // 0x08 = SY_SEL: High Performance mode (fixed prescaler = Div-by-2). Finer tuning. + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + // Set the PLL parameters + { + uint8_t data[] = { + 0x40, // 0x40 = Group FREQ_CONTROL + 0x06, // Set 6 properties + 0x00, // 0x00 = Start from FREQ_CONTROL_INTE + n, // 0 (FREQ_CONTROL_INTE): Frac-N PLL Synthesizer integer divide number. + (m >> 16) & 0xFF, // 1 (FREQ_CONTROL_FRAC): Frac-N PLL fraction number. + (m >> 8) & 0xFF, // 2 (FREQ_CONTROL_FRAC): Frac-N PLL fraction number. + m & 0xFF, // 3 (FREQ_CONTROL_FRAC): Frac-N PLL fraction number. + 0x00, // 4 (FREQ_CONTROL_CHANNEL_STEP_SIZE): EZ Frequency Programming channel step size. + 0x02 // 5 (FREQ_CONTROL_CHANNEL_STEP_SIZE): EZ Frequency Programming channel step size. + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + current_frequency_hz = frequency_hz; + + // Deviation depends on the frequency band + si4063_set_frequency_deviation(current_deviation_hz); +} + +void si4063_set_tx_power(uint8_t power) +{ + uint8_t data[] = { + 0x22, // 0x20 = Group PA + 0x01, // Set 1 property + 0x01, // 0x01 = PA_PWR_LVL + power & 0x7F // Power level from 00..7F + }; + + log_debug("Si4063: Set TX power %02x\n", power); + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +void si4063_set_frequency_offset(uint16_t offset) +{ + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x02, // Set 2 properties (2 bytes) + 0x0D, // 0x0D = MODEM_FREQ_OFFSET + offset >> 8, // Upper 8 bits of the offset + offset & 0xFF // Lower 8 bits of the offset + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +static uint32_t si4063_calculate_deviation(uint32_t deviation_hz) +{ + uint8_t outdiv = si4063_get_outdiv(current_frequency_hz); + + // SY_SEL = Div-by-2 + return (uint32_t) (((double) (1 << 19) * outdiv * deviation_hz) / (2 * SI4063_CLOCK)); +} + +void si4063_set_frequency_deviation(uint32_t deviation_hz) +{ + uint32_t deviation = si4063_calculate_deviation(deviation_hz); + + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x03, // Set 3 properties (3 bytes) + 0x0A, // 0x0A = MODEM_FREQ_DEV + (deviation >> 16) & 0xFF, + (deviation >> 8) & 0xFF, + deviation & 0xFF + }; + + log_info("Si4063: Set frequency deviation to value %lu with %lu Hz\n", deviation, deviation_hz); + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + + current_deviation_hz = deviation_hz; +} + +void si4063_set_modulation_type(si4063_modulation_type type) +{ + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x01, // Set 1 property + 0x00, // 0x00 = MODEM_MOD_TYPE + 0x80 | // 0x80 = Direct async mode (MCU-controlled) + 0x60 | // 0x60 = Use GPIO3 as source for direct mode modulation + 0x08 // 0x08 = Direct modulation source (MCU-controlled) + }; + + log_debug("Si4063: Set modulation type %d\n", type); + + switch (type) { + case SI4063_MODULATION_TYPE_CW: + // Pure carrier wave modulation (for modulating via frequency offset, e.g. for RTTY) + data[3] |= 0x00; + break; + case SI4063_MODULATION_TYPE_OOK: + // Direct Async Mode with OOK modulation + data[3] |= 0x01; + break; + case SI4063_MODULATION_TYPE_FSK: + // Direct Async Mode with FSK modulation + data[3] |= 0x02; + break; + default: + return; + } + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +int32_t si4063_read_temperature_celsius_100() +{ + uint8_t response[6]; + int32_t temperature; + uint8_t data[] = { + 0x10, // Measure internal temperature ADC reading only + 0x00 + }; + + si4063_send_command(SI4063_COMMAND_GET_ADC_READING, sizeof(data), data); + + si4063_read_response(sizeof(response), response); + + // Calculate the temperature in C * 10 + temperature = (response[4] << 8) | response[5]; + temperature *= 568; + temperature /= 256; + temperature -= 2970; + + return temperature * 10; +} + +uint16_t si4063_read_part_info() +{ + uint8_t response[8]; + + si4063_send_command(SI4063_COMMAND_PART_INFO, 0, NULL); + + si4063_read_response(sizeof(response), response); + + // Return part number + return response[1] << 8 | response[2]; +} + +inline void si4063_set_direct_mode_pin(bool high) +{ + if (high) { + GPIO_SetBits(GPIO_SI4063_GPIO3, GPIO_PIN_SI4063_GPIO3); + } else { + GPIO_ResetBits(GPIO_SI4063_GPIO3, GPIO_PIN_SI4063_GPIO3); + } +} + +void si4063_configure() +{ + { + uint8_t data[] = { + 0x00, // 0x00 = Group GLOBAL + 0x01, // Set 1 property + 0x00, // 0x00 = GLOBAL_XO_TUNE + 0x62 // Value determined for DFM17 radiosondes + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + uint8_t data[] = { + 0x00, // 0x00 = Group GLOBAL + 0x01, // Set 1 property + 0x01, // 0x00 = GLOBAL_CLK_CFG + 0x00 // No clock output needed + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + uint8_t data[] = { + 0x00, // 0x00 = Group GLOBAL + 0x01, // Set 1 property + 0x03, // 0x03 = GLOBAL_CONFIG + 0x40 | // 0x40 = Reserved, needs to be set to 1 + 0x20 | // 0x20 = Fast sequencer mode + 0x00 // High-performance mode + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + uint8_t data[] = { + 0x01, // 0x01 = Group INT_CTL + 0x01, // Set 1 property + 0x00, // 0x00 = INT_CTL_ENABLE + 0x00 // 0x00 = Disable all hardware interrupts + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + uint8_t data[] = { + 0x02, // 0x02 = Group FRR_CTL + 0x04, // Set 4 properties + 0x00, // 0x00 = FRR_CTL_A_MODE + 0x00, // Disable all FRR values + 0x00, + 0x00, + 0x00 + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + // Used only in synchronous mode (for GFSK modulation/filtering) + uint8_t data[] = { + 0x10, // 0x10 = Group PREAMBLE + 0x01, // Set 1 property + 0x00, // 0x00 = PREAMBLE_TX_LENGTH + 0x00 // 0x00 = Disable preamble + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + // Used only in synchronous mode (for GFSK modulation/filtering) + uint8_t data[] = { + 0x11, // 0x11 = Group SYNC + 0x01, // Set 1 property + 0x00, // 0x00 = SYNC_CONFIG + 0x80 // 0x80 = Sync word is not transmitted + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } + + { + uint8_t data[] = { + 0x22, // 0x22 = Group PA + 0x01, // Set 1 property + 0x02, // 0x02 = PA_BIAS_CLKDUTY + 0x00 // 0x00 = Complementary drive signals, 50% duty cycle. For high-power applications. + // Alternative: 0xC0 = Single-ended drive signal, 25% duty cycle. For low-power applications. + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); + } +} + +// Not used yet, for future use +void si4063_set_filter(const uint8_t *filter) +{ + uint8_t data[12] = { + 0x20, // 0x20 = Group MODEM + 0x09, // Set 9 properties + 0x0F // 0x0F = MODEM_TX_FILTER_COEFF_8 + }; + + for (uint8_t i = 0; i < 9; i++) { + data[3 + i] = filter[i]; + } + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +void si4063_configure_data_rate(uint32_t data_rate) +{ + // Used only for GFSK mode filtering + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x03, // Set 3 properties + 0x03, // 0x03 = MODEM_DATA_RATE + (data_rate >> 16) & 0xFF, + (data_rate >> 8) & 0xFF, + data_rate & 0xFF + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +// Not used yet, for future use +void si4063_configure_aprs() +{ + // For APRS in direct mode with GFSK modulation + si4063_configure_data_rate(SI4063_DATA_RATE_APRS); + + // Used only for GFSK mode filtering + uint32_t nco_mod = SI4063_CLOCK / 10; + uint8_t data[] = { + 0x20, // 0x20 = Group MODEM + 0x04, // Set 4 properties + 0x06, // 0x06 = MODEM_TX_NCO_MODE + 0x00 | // 0x00 = TX Gaussian filter oversampling ratio is 10x + ((nco_mod >> 24) & 0x03), + (nco_mod >> 16) & 0xFF, + (nco_mod >> 8) & 0xFF, + nco_mod & 0xFF + }; + + si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data); +} + +// Not used yet, for future use +void si4063_configure_rtty() +{ + // For RTTY: + // NOTE: 0x22 sets shift at about 450 Hz for RTTY + si4063_set_frequency_deviation(0x22); +} + +void si4063_configure_gpio(uint8_t gpio0, uint8_t gpio1, uint8_t gpio2, uint8_t gpio3, uint8_t drive_strength) { + uint8_t data[] = { + gpio0, + gpio1, + gpio2, + gpio3, + 0x00, // NIRQ = Do nothing + 11, // SDO 11 = Outputs the Serial Data Out (SDO) signal for the SPI bus + drive_strength + }; + + si4063_send_command(SI4063_COMMAND_GPIO_PIN_CFG, sizeof(data), data); +} + +int si4063_init() +{ + GPIO_InitTypeDef gpio_init; + + // Si4063 shutdown pin + gpio_init.GPIO_Pin = GPIO_PIN_SI4063_SDN; + gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIO_SI4063_SDN, &gpio_init); + + // Si4063 chip select pin + gpio_init.GPIO_Pin = GPIO_PIN_SI4063_NSEL; + gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIO_SI4063_NSEL, &gpio_init); + + // Si4063 GPIO3 pin for direct mode transmission + gpio_init.GPIO_Pin = GPIO_PIN_SI4063_GPIO3; + gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIO_SI4063_GPIO3, &gpio_init); + + si4063_set_direct_mode_pin(false); + + si4603_set_shutdown(false); + delay_us(50); + + si4063_wait_for_cts(); + + si4603_set_shutdown(true); + delay_us(20); + si4603_set_shutdown(false); + delay_us(50); + + if (si4063_power_up() != HAL_OK) { + log_error("ERROR: Error powering up Si4063\n"); + return HAL_ERROR; + } + + // Assume Si4063 part number + uint16_t part = si4063_read_part_info(); + if (part != 0x4063) { + log_error("ERROR: Unknown or missing Si4063 part number: 0x%04x\n", part); + return HAL_ERROR; + } + + si4063_configure(); + + si4063_configure_gpio( + 0x00, // GPIO0: Do nothing + 0x00, // GPIO1: Do nothing + 0x00, // GPIO2: Do nothing + 0x04, // GPIO3: Pin is configured as a CMOS input for direct mode transmissions. + 0x00 // Drive strength: HIGH + ); + + si4063_set_tx_power(0x00); + + si4063_set_frequency_offset(0); + + // Set deviation to zero for non-FSK modulations + si4063_set_frequency_deviation(0); + + si4063_set_modulation_type(SI4063_MODULATION_TYPE_CW); + + si4063_set_state(SI4063_STATE_READY); + + return HAL_OK; +} + +void TIM1_BRK_TIM15_IRQHandler(void) +{ + static bool pin_state = false; + + if (TIM_GetITStatus(TIM15, TIM_IT_Update) != RESET) { + TIM_ClearITPendingBit(TIM15, TIM_IT_Update); +#ifdef DFM17 + // Restrict the interrupt to DFM17 only just in case this ISR gets called on RS41 + pin_state = !pin_state; + si4063_set_direct_mode_pin(pin_state); +#endif + } +} diff --git a/src/drivers/si4063/si4063.h b/src/drivers/si4063/si4063.h new file mode 100644 index 0000000..1a09e2f --- /dev/null +++ b/src/drivers/si4063/si4063.h @@ -0,0 +1,25 @@ +#ifndef __SI4063_H +#define __SI4063_H + +#include +#include + +typedef enum _si4063_modulation_type { + SI4063_MODULATION_TYPE_CW = 0, + SI4063_MODULATION_TYPE_OOK, + SI4063_MODULATION_TYPE_FSK, +} si4063_modulation_type; + +void si4063_enable_tx(); +void si4063_inhibit_tx(); +void si4063_disable_tx(); +void si4063_set_tx_frequency(uint32_t frequency_hz); +void si4063_set_tx_power(uint8_t power); +void si4063_set_frequency_offset(uint16_t offset); +void si4063_set_frequency_deviation(uint32_t deviation); +void si4063_set_modulation_type(si4063_modulation_type type); +int32_t si4063_read_temperature_celsius_100(); +void si4063_set_direct_mode_pin(bool high); +int si4063_init(); + +#endif diff --git a/src/drivers/ubxg6010/ubxg6010.c b/src/drivers/ubxg6010/ubxg6010.c index dca001e..678e959 100644 --- a/src/drivers/ubxg6010/ubxg6010.c +++ b/src/drivers/ubxg6010/ubxg6010.c @@ -505,7 +505,7 @@ bool ubxg6010_enable_power_save_mode() ubxg6010_send_packet(&packet); success = ubxg6010_wait_for_ack(); if (!success) { - log_error("GPS: Entering power-saving mode failed\n") + log_error("GPS: Entering power-saving mode failed\n"); } return success; diff --git a/src/gpio.h b/src/gpio.h new file mode 100644 index 0000000..0bd00b7 --- /dev/null +++ b/src/gpio.h @@ -0,0 +1,104 @@ +#ifndef __GPIO_H +#define __GPIO_H + +#include +#include + +#include "config.h" + + +// GPIO definitions for devices we use + +#if defined (RS41) + +#define BANK_SHUTDOWN GPIOA +#define PIN_SHUTDOWN GPIO_Pin_12 + +#define BANK_VOLTAGE GPIOA +#define PIN_VOLTAGE GPIO_Pin_5 +#define ADC_VOLTAGE ADC1 +#define CHANNEL_VOLTAGE ADC_Channel_5 + +#define BANK_BUTTON GPIOA +#define PIN_BUTTON GPIO_Pin_6 +#define ADC_BUTTON ADC1 +#define CHANNEL_BUTTON ADC_Channel_6 + +#define BANK_RED_LED GPIOB +#define PIN_RED_LED GPIO_Pin_8 + +#define BANK_GREEN_LED GPIOB +#define PIN_GREEN_LED GPIO_Pin_7 + +#define BANK_MOSI GPIOB +#define PIN_MOSI GPIO_Pin_15 +#define BANK_SCK GPIOB +#define PIN_SCK GPIO_Pin_13 +#define BANK_MISO GPIOB +#define PIN_MISO GPIO_Pin_14 +#define APBPERIPHERAL_SPI RCC_APB1Periph_SPI2 +#define PERIPHERAL_SPI SPI2 +#define RCC_SPIPeriphClockCmd RCC_APB1PeriphClockCmd + +#define PIN_USART_TX GPIO_Pin_9 +#define BANK_USART_TX GPIOA +#define PIN_USART_RX GPIO_Pin_10 +#define BANK_USART_RX GPIOA +#define USART_IRQ USART1_IRQn +#define USART_IT USART1 +#define APBPERIPHERAL_USART RCC_APB2Periph_USART1 +#define USART_IRQ_HANDLER USART1_IRQHandler + +#elif defined (DFM17) + +#define BANK_SHUTDOWN GPIOC +#define PIN_SHUTDOWN GPIO_Pin_0 + +#define BANK_VOLTAGE GPIOA // Needs confirmation +#define PIN_VOLTAGE GPIO_Pin_0 // Needs confirmation +#define ADC_VOLTAGE ADC1 // Needs confirmation +#define CHANNEL_VOLTAGE ADC_Channel_0 // Needs confirmation + +#define BANK_BUTTON GPIOC +#define PIN_BUTTON GPIO_Pin_8 +// No ADC available on the GPIOC, so we have to use digital reads/writes for the button + +#define BANK_RED_LED GPIOB +#define PIN_RED_LED GPIO_Pin_12 + +#define BANK_GREEN_LED GPIOC +#define PIN_GREEN_LED GPIO_Pin_6 + +#define BANK_YELLOW_LED GPIOC +#define PIN_YELLOW_LED GPIO_Pin_7 + +#define BANK_MOSI GPIOA +#define PIN_MOSI GPIO_Pin_7 +#define BANK_SCK GPIOA +#define PIN_SCK GPIO_Pin_5 +#define BANK_MISO GPIOA +#define PIN_MISO GPIO_Pin_6 +#define APBPERIPHERAL_SPI RCC_APB2Periph_SPI1 +#define PERIPHERAL_SPI SPI1 +#define RCC_SPIPeriphClockCmd RCC_APB2PeriphClockCmd + +#define PIN_USART_TX GPIO_Pin_2 +#define BANK_USART_TX GPIOA +#define PIN_USART_RX GPIO_Pin_3 +#define BANK_USART_RX GPIOA +#define USART_IRQ USART2_IRQn +#define USART_IT USART2 +#define APBPERIPHERAL_USART RCC_APB1Periph_USART2 +#define USART_IRQ_HANDLER USART2_IRQHandler + +#else +Compiler error. You must define RS41 or DFM17. +#endif // RS41 or DFM17 + +// Hardware Sanity Check + +#if defined (RS41) && defined (DFM17) +Compiler error. You must define RS41 or DFM17 but not both. +#endif + +#endif // __GPIO_H diff --git a/src/hal/clock_calibration.c b/src/hal/clock_calibration.c new file mode 100644 index 0000000..8a33690 --- /dev/null +++ b/src/hal/clock_calibration.c @@ -0,0 +1,144 @@ +#include "config.h" + +#ifdef DFM17 + +#include "stm32f10x_exti.h" +#include "stm32f10x_gpio.h" +#include "stm32f10x.h" +#include "stm32f10x_rcc.h" +#include "misc.h" +#include "system.h" +#include "millis.h" +#include "clock_calibration.h" + +// The HSI (internal oscillator) trim register mask, copied from stm_lib/src/stm32f10x_rcc.c +#define CR_HSITRIM_Mask ((uint32_t)0xFFFFFF07) + +// Register definition for reading the HSI current trim out of the Calibration Register (CR). +// Resulting value will be between 0-31. +#define CURRENT_TRIM ((RCC->CR & ~CR_HSITRIM_Mask) >>3) + +/** + * On the DFM-17, GPIO PB8 is wired to the GPS Timepulse. We take advantage of this to do a + * processor speed calibration. HSITRIM[4:0] allows for 32 values to adjust the HSI clock + * speed. The center (16) value is "neutral". Each trim value above or below 16 adjusts + * the clock by approximately 40kHZ (0.5% of the 8MHZ clock speed) (per AN2868). + * 0.5% is about 5ms per second, so if we detect that we're off by more than 5 milliseconds between time pulses, + * we will suggest a recalibration. The "trim_suggestion" variable is a static that will be maintained + * by the time pulse IRQ and can be used at any time it's convenient to adjust the clock speed. +*/ + +// Defaults, will be set it in the init routine below. +int trim_suggestion = 16; +int trim_current = 16; + +uint32_t old_millis = 0; +uint16_t calibration_change_count = 0; + +bool calibration_indicator_state = true; + +uint8_t clock_calibration_get_trim() +{ + return CURRENT_TRIM; +} + +uint16_t clock_calibration_get_change_count() +{ + return calibration_change_count; +} + +void clock_calibration_adjust() +{ + if (trim_suggestion == trim_current) { + return; + } + + RCC_AdjustHSICalibrationValue(trim_suggestion); + trim_current = trim_suggestion; + + calibration_change_count++; + + calibration_indicator_state = !calibration_indicator_state; + system_set_yellow_led(calibration_indicator_state); +} + +void timepulse_init() +{ + // Initialize pin PB8 as floating input + GPIO_InitTypeDef gpio_init; + gpio_init.GPIO_Pin = GPIO_Pin_8; + gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; + gpio_init.GPIO_Speed = GPIO_Speed_10MHz; + GPIO_Init(GPIOB, &gpio_init); + + // PB8 is connected to interrupt line 8, set trigger on the configured edge and enable the interrupt + EXTI_InitTypeDef exti_init; + exti_init.EXTI_Line = EXTI_Line8; + exti_init.EXTI_Mode = EXTI_Mode_Interrupt; + exti_init.EXTI_Trigger = EXTI_Trigger_Rising; + exti_init.EXTI_LineCmd = ENABLE; + EXTI_Init(&exti_init); + + // Attach interrupt line to port B + GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource8); + + // PB8 is connected to EXTI_Line8, which has EXTI9_5_IRQn vector. Use priority 0 for now. + NVIC_InitTypeDef NVIC_InitStruct; + NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; + NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; + NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStruct); + + // Pull the current calibration to start + trim_current = CURRENT_TRIM; + trim_suggestion = trim_current; + + // Set the yellow LED to help identify calibration changes + system_set_yellow_led(calibration_indicator_state); +} + +// This handler is (at present) only being used for the GPS time pulse interrupt, +// so we shouldn't need to do additional testing for the cause of the interrupt. + +void EXTI9_5_IRQHandler(void) +{ + uint32_t current_millis = millis(); + + EXTI_ClearITPendingBit(EXTI_Line8); + + if (old_millis == 0) { + // First timepulse. Just store millis. + old_millis = current_millis; + return; + } + + if (current_millis < old_millis) { + // Milliseconds value wrapped to zero. Wait for the next interrupt. + return; + } + + // Calculate milliseconds since last timepulse. Ideally there were 1000. + uint32_t millis_delta = current_millis - old_millis; + old_millis = current_millis; + + // If too few clicks, speed up clock. If too many, slow down. + int delta = (int) (1000 - millis_delta) / 5; + + // Take one step at a time in case we had a bad clock tick + if (delta > 1) { + delta = 1; + } + if (delta < -1) { + delta = -1; + } + + // Don't allow calibration suggestion to go out of range + if (((delta + trim_current) >= 0) && + ((delta + trim_current <= 31))) { + // If the delta makes sense, apply to the suggestion. Otherwise, skip. + trim_suggestion = trim_current + delta; + } +} + +#endif diff --git a/src/hal/clock_calibration.h b/src/hal/clock_calibration.h new file mode 100644 index 0000000..1ee9c4c --- /dev/null +++ b/src/hal/clock_calibration.h @@ -0,0 +1,15 @@ +#ifndef __CLOCK_CALIBRATION_H +#define __CLOCK_CALIBRATION_H + +#include "config.h" + +#ifdef DFM17 + +extern void timepulse_init(); +extern uint8_t clock_calibration_get_trim(); +extern uint16_t clock_calibration_get_change_count(); +extern void clock_calibration_adjust(); + +#endif + +#endif diff --git a/src/hal/datatimer.c b/src/hal/datatimer.c index cdf5e95..da029cd 100644 --- a/src/hal/datatimer.c +++ b/src/hal/datatimer.c @@ -19,6 +19,7 @@ void data_timer_init(uint32_t baud_rate) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphResetCmd(RCC_APB1Periph_TIM2, DISABLE); + // The data timer assumes a 24 MHz clock source tim_init.TIM_Prescaler = 24 - 1; // tick every 1/1000000 s tim_init.TIM_CounterMode = TIM_CounterMode_Up; tim_init.TIM_Period = (uint16_t) ((1000000 / baud_rate) - 1); @@ -33,8 +34,8 @@ void data_timer_init(uint32_t baud_rate) NVIC_InitTypeDef nvic_init; nvic_init.NVIC_IRQChannel = TIM2_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 0; - nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelPreemptionPriority = 2; + nvic_init.NVIC_IRQChannelSubPriority = 2; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init); @@ -47,8 +48,8 @@ void data_timer_uninit() NVIC_InitTypeDef nvic_init; nvic_init.NVIC_IRQChannel = TIM2_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 0; - nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelPreemptionPriority = 2; + nvic_init.NVIC_IRQChannelSubPriority = 2; nvic_init.NVIC_IRQChannelCmd = DISABLE; NVIC_Init(&nvic_init); @@ -60,8 +61,6 @@ void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); - system_handle_data_timer_tick(); - } } diff --git a/src/hal/delay.c b/src/hal/delay.c index ad96d51..991ebbb 100644 --- a/src/hal/delay.c +++ b/src/hal/delay.c @@ -18,6 +18,7 @@ void delay_init() RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM3, DISABLE); + // The delay timer assumes a 24 MHz clock source tim_init.TIM_Prescaler = 24 - 1; tim_init.TIM_CounterMode = TIM_CounterMode_Up; tim_init.TIM_Period = 0; @@ -30,7 +31,7 @@ void delay_init() NVIC_InitTypeDef nvic_init; nvic_init.NVIC_IRQChannel = TIM3_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 0; + nvic_init.NVIC_IRQChannelPreemptionPriority = 1; nvic_init.NVIC_IRQChannelSubPriority = 1; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init); diff --git a/src/hal/millis.c b/src/hal/millis.c new file mode 100644 index 0000000..64f891c --- /dev/null +++ b/src/hal/millis.c @@ -0,0 +1,65 @@ +#include +#include +#include + +#include "src/hal/millis.h" + +static uint32_t millis_counter; + +void millis_timer_init(void) +{ + TIM_DeInit(TIM7); + + TIM_TimeBaseInitTypeDef tim_init; + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE); + RCC_APB2PeriphResetCmd(RCC_APB1Periph_TIM7, DISABLE); + + // The data timer assumes a 24 MHz clock source + tim_init.TIM_Prescaler = 24 - 1; // tick every 1/1000000 s + tim_init.TIM_CounterMode = TIM_CounterMode_Up; + tim_init.TIM_Period = (uint16_t) (1000 - 1); // set up period of 1 millisecond + tim_init.TIM_ClockDivision = TIM_CKD_DIV1; + tim_init.TIM_RepetitionCounter = 0; + + TIM_TimeBaseInit(TIM7, &tim_init); + + TIM_ClearITPendingBit(TIM7, TIM_IT_Update); + TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE); + + NVIC_InitTypeDef nvic_init; + nvic_init.NVIC_IRQChannel = TIM7_IRQn; + nvic_init.NVIC_IRQChannelPreemptionPriority = 0; + nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&nvic_init); + + TIM_Cmd(TIM7, ENABLE); +} + +void millis_timer_uninit() +{ + TIM_Cmd(TIM7, DISABLE); + + NVIC_InitTypeDef nvic_init; + nvic_init.NVIC_IRQChannel = TIM7_IRQn; + nvic_init.NVIC_IRQChannelPreemptionPriority = 0; + nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelCmd = DISABLE; + NVIC_Init(&nvic_init); + + TIM_ITConfig(TIM7, TIM_IT_Update, DISABLE); + TIM_ClearITPendingBit(TIM7, TIM_IT_Update); +} + +void TIM7_IRQHandler(void) +{ + if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET) { + TIM_ClearITPendingBit(TIM7, TIM_IT_Update); + millis_counter++; + } +} + +uint32_t millis(void) +{ + return millis_counter; +} diff --git a/src/hal/millis.h b/src/hal/millis.h new file mode 100644 index 0000000..2af7a9f --- /dev/null +++ b/src/hal/millis.h @@ -0,0 +1,12 @@ +#ifndef __MILLIS_H +#define __MILLIS_H + +#include + +extern void millis_timer_init(void); +extern void millis_timer_uninit(); + +extern uint32_t millis(); + +#endif + diff --git a/src/hal/pwm.c b/src/hal/pwm.c index 493b404..2f9f922 100644 --- a/src/hal/pwm.c +++ b/src/hal/pwm.c @@ -42,8 +42,8 @@ void pwm_data_timer_init() NVIC_InitTypeDef nvic_init; nvic_init.NVIC_IRQChannel = TIM2_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 0; - nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelPreemptionPriority = 2; + nvic_init.NVIC_IRQChannelSubPriority = 2; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init); */ @@ -51,12 +51,6 @@ void pwm_data_timer_init() TIM_Cmd(TIM2, ENABLE); } -void pwm_data_timer_dma_request_enable(bool enabled) -{ - // TIM2 Update DMA requests are routed to DMA1 Channel2 - TIM_DMACmd(TIM2, TIM_DMA_Update, enabled ? ENABLE : DISABLE); -} - void pwm_data_timer_uninit() { TIM_Cmd(TIM2, DISABLE); @@ -65,7 +59,9 @@ void pwm_data_timer_uninit() void pwm_timer_init(uint32_t frequency_hz_100) { TIM_DeInit(TIM15); +#ifdef RS41 GPIO_PinRemapConfig(GPIO_Remap_TIM15, DISABLE); +#endif // Not needed: AFIO->MAPR2 |= AFIO_MAPR2_TIM15_REMAP; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM15, ENABLE); @@ -83,6 +79,7 @@ void pwm_timer_init(uint32_t frequency_hz_100) TIM_TimeBaseInit(TIM15, &tim_init); +#ifdef RS41 TIM_OCInitTypeDef TIM15_OCInitStruct; TIM_OCStructInit(&TIM15_OCInitStruct); @@ -91,6 +88,7 @@ void pwm_timer_init(uint32_t frequency_hz_100) TIM15_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; TIM15_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; + // TIM15 channel 2 can be used to drive pin PB15, which is connected to RS41 Si4032 SDI pin for direct modulation TIM_OC2Init(TIM15, &TIM15_OCInitStruct); // These are not needed? @@ -104,10 +102,68 @@ void pwm_timer_init(uint32_t frequency_hz_100) TIM_OC2FastConfig(TIM15, TIM_OCFast_Enable); TIM_CtrlPWMOutputs(TIM15, DISABLE); +#endif +#ifdef DFM17 + // For DFM17 we don't have a PWM pin in the right place, so we manually toggle the pin in the ISR + TIM_ClearITPendingBit(TIM15, TIM_IT_Update); + TIM_ITConfig(TIM15, TIM_IT_Update, ENABLE); + + NVIC_InitTypeDef nvic_init; + nvic_init.NVIC_IRQChannel = TIM1_BRK_TIM15_IRQn; + nvic_init.NVIC_IRQChannelPreemptionPriority = 2; + nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&nvic_init); +#endif TIM_Cmd(TIM15, ENABLE); } +void pwm_timer_pwm_enable(bool enabled) +{ +#ifdef RS41 + TIM_CtrlPWMOutputs(TIM15, enabled ? ENABLE : DISABLE); +#endif +} + +void pwm_timer_use(bool use) +{ +#ifdef RS41 + // Remapping the TIM15 outputs will allow TIM15 channel 2 can be used to drive pin PB15, + // which is connected to RS41 Si4032 SDI pin for direct modulation + GPIO_PinRemapConfig(GPIO_Remap_TIM15, use ? ENABLE : DISABLE); +#endif +} + +void pwm_timer_uninit() +{ + TIM_CtrlPWMOutputs(TIM15, DISABLE); + TIM_Cmd(TIM15, DISABLE); + + TIM_DeInit(TIM15); + +#ifdef RS41 + GPIO_PinRemapConfig(GPIO_Remap_TIM15, DISABLE); +#endif + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM15, DISABLE); +} + +inline uint16_t pwm_calculate_period(uint32_t frequency_hz_100) +{ + return (uint16_t) (((100.0f * 1000000.0f) / (frequency_hz_100 * 2.0f))) - 1; +} + +inline void pwm_timer_set_frequency(uint32_t pwm_period) +{ + TIM_SetAutoreload(TIM15, pwm_period); +} + +/** + * Below are experimental DMA routines for supplying PWM data for APRS modulation. + * This does not work correctly, but is left for future reference. + */ + static void pwm_dma_init_channel() { DMA_InitTypeDef dma_init; @@ -146,8 +202,8 @@ void pwm_dma_init() NVIC_InitTypeDef nvic_init; nvic_init.NVIC_IRQChannel = DMA1_Channel2_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 0; - nvic_init.NVIC_IRQChannelSubPriority = 1; + nvic_init.NVIC_IRQChannelPreemptionPriority = 2; + nvic_init.NVIC_IRQChannelSubPriority = 0; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init); } @@ -172,6 +228,12 @@ void pwm_dma_stop() //pwm_dma_interrupt_enable(false); } +void pwm_data_timer_dma_request_enable(bool enabled) +{ + // TIM2 Update DMA requests are routed to DMA1 Channel2 + TIM_DMACmd(TIM2, TIM_DMA_Update, enabled ? ENABLE : DISABLE); +} + void DMA1_Channel2_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TE2)) { @@ -187,42 +249,3 @@ void DMA1_Channel2_IRQHandler(void) pwm_handle_dma_transfer_full(PWM_TIMER_DMA_BUFFER_SIZE, pwm_timer_dma_buffer); } } - -void pwm_timer_pwm_enable(bool enabled) -{ - TIM_CtrlPWMOutputs(TIM15, enabled ? ENABLE : DISABLE); -} - -void pwm_timer_use(bool use) -{ - GPIO_PinRemapConfig(GPIO_Remap_TIM15, use ? ENABLE : DISABLE); -} - -void pwm_timer_uninit() -{ - TIM_CtrlPWMOutputs(TIM15, DISABLE); - TIM_Cmd(TIM15, DISABLE); - - TIM_DeInit(TIM15); - - GPIO_PinRemapConfig(GPIO_Remap_TIM15, DISABLE); - - RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM15, DISABLE); -} - -inline uint16_t pwm_calculate_period(uint32_t frequency_hz_100) -{ - return (uint16_t) (((100.0f * 1000000.0f) / (frequency_hz_100 * 2.0f))) - 1; -} - -inline void pwm_timer_set_frequency(uint32_t pwm_period) -{ - // TIM_CtrlPWMOutputs(TIM15, DISABLE); - // TIM_Cmd(TIM15, DISABLE); - - TIM_SetAutoreload(TIM15, pwm_period); - // TIM_SetCompare2(TIM15, pwm_period / 2); - - // TIM_Cmd(TIM15, ENABLE); - // TIM_CtrlPWMOutputs(TIM15, ENABLE); -} diff --git a/src/hal/spi.c b/src/hal/spi.c index 0154150..fcaf2c5 100644 --- a/src/hal/spi.c +++ b/src/hal/spi.c @@ -3,85 +3,152 @@ #include #include "spi.h" +#include "gpio.h" void spi_init() { GPIO_InitTypeDef gpio_init; - // SPI2_SCK & SPI2_MOSI - gpio_init.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; + // SCK + gpio_init.GPIO_Pin = PIN_SCK; gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOB, &gpio_init); + GPIO_Init(BANK_SCK, &gpio_init); - // SPI2_MISO - gpio_init.GPIO_Pin = GPIO_Pin_14; - gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; + // MOSI + gpio_init.GPIO_Pin = PIN_MOSI; + gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOB, &gpio_init); + GPIO_Init(BANK_MOSI, &gpio_init); - RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); + // MISO + gpio_init.GPIO_Pin = PIN_MISO; +#ifdef RS41 + gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; +#endif +#ifdef DFM17 + gpio_init.GPIO_Mode = GPIO_Mode_IPU; +#endif + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(BANK_MISO, &gpio_init); + + RCC_SPIPeriphClockCmd(APBPERIPHERAL_SPI, ENABLE); SPI_InitTypeDef spi_init; SPI_StructInit(&spi_init); spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex; spi_init.SPI_Mode = SPI_Mode_Master; +#ifdef RS41 spi_init.SPI_DataSize = SPI_DataSize_16b; +#endif +#ifdef DFM17 + spi_init.SPI_DataSize = SPI_DataSize_8b; +#endif spi_init.SPI_CPOL = SPI_CPOL_Low; spi_init.SPI_CPHA = SPI_CPHA_1Edge; +#ifdef RS41 spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; +#endif +#ifdef DFM17 + spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; +#endif spi_init.SPI_FirstBit = SPI_FirstBit_MSB; +#ifdef RS41 spi_init.SPI_CRCPolynomial = 7; - SPI_Init(SPI2, &spi_init); +#endif +#ifdef DFM17 + spi_init.SPI_CRCPolynomial = 10; + spi_init.SPI_NSS = SPI_NSS_Soft; +#endif + SPI_Init(PERIPHERAL_SPI, &spi_init); - SPI_SSOutputCmd(SPI2, ENABLE); +#ifdef RS41 + SPI_SSOutputCmd(PERIPHERAL_SPI, ENABLE); +#endif +#ifdef DFM17 + SPI_CalculateCRC(PERIPHERAL_SPI, DISABLE); +#endif - SPI_Cmd(SPI2, ENABLE); - SPI_Init(SPI2, &spi_init); + SPI_Cmd(PERIPHERAL_SPI, ENABLE); +#ifdef RS41 + // TODO: Why is this call even here? + SPI_Init(PERIPHERAL_SPI, &spi_init); +#endif } void spi_uninit() { - SPI_I2S_DeInit(SPI2); - SPI_Cmd(SPI2, DISABLE); - SPI_SSOutputCmd(SPI2, DISABLE); - RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, DISABLE); + SPI_I2S_DeInit(PERIPHERAL_SPI); + SPI_Cmd(PERIPHERAL_SPI, DISABLE); + SPI_SSOutputCmd(PERIPHERAL_SPI, DISABLE); + RCC_SPIPeriphClockCmd(APBPERIPHERAL_SPI, DISABLE); GPIO_InitTypeDef gpio_init; - gpio_init.GPIO_Pin = GPIO_Pin_14; + gpio_init.GPIO_Pin = PIN_MISO; gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOB, &gpio_init); + GPIO_Init(BANK_MISO, &gpio_init); - gpio_init.GPIO_Pin = GPIO_Pin_15; + gpio_init.GPIO_Pin = PIN_MOSI; gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; // was: GPIO_Mode_Out_PP; // GPIO_Mode_AF_PP gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOB, &gpio_init); + GPIO_Init(BANK_MOSI, &gpio_init); } -inline void spi_send(uint16_t data) +void spi_send(uint16_t data) { // Wait for TX buffer - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); - SPI_I2S_SendData(SPI2, data); + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_TXE) == RESET); + SPI_I2S_SendData(PERIPHERAL_SPI, data); +#ifdef DFM17 + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_TXE) == RESET); + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_BSY) == SET); + + // Reset the overrun error by reading the data and status registers + // NOTE: It seems this sequence is required to make Si4063 SPI communication work on DFM17 radiosondes + SPI_I2S_ReceiveData(PERIPHERAL_SPI); + SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_OVR); +#endif } -inline uint8_t spi_receive() +uint8_t spi_receive() { // Wait for data in RX buffer - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); - return (uint8_t) SPI_I2S_ReceiveData(SPI2); +#ifdef DFM17 + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_BSY) == SET); +#endif + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_RXNE) == RESET); + return (uint8_t) SPI_I2S_ReceiveData(PERIPHERAL_SPI); +} + +uint8_t spi_read() +{ + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_BSY) == SET); + // Send dummy data to read in bidirectional mode + SPI_I2S_SendData(PERIPHERAL_SPI, 0xFF); + // Wait for data in RX buffer + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_RXNE) == RESET); + return (uint8_t) SPI_I2S_ReceiveData(PERIPHERAL_SPI); +} + +void spi_set_chip_select(GPIO_TypeDef *gpio_cs, uint16_t pin_cs, bool select) +{ + if (select) { + GPIO_ResetBits(gpio_cs, pin_cs); + } else { + GPIO_SetBits(gpio_cs, pin_cs); + } } uint8_t spi_send_and_receive(GPIO_TypeDef *gpio_cs, uint16_t pin_cs, uint16_t data) { GPIO_ResetBits(gpio_cs, pin_cs); - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); - SPI_I2S_SendData(SPI2, data); + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_TXE) == RESET); + SPI_I2S_SendData(PERIPHERAL_SPI, data); - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); + while (SPI_I2S_GetFlagStatus(PERIPHERAL_SPI, SPI_I2S_FLAG_RXNE) == RESET); GPIO_SetBits(gpio_cs, pin_cs); - return (uint8_t) SPI_I2S_ReceiveData(SPI2); + return (uint8_t) SPI_I2S_ReceiveData(PERIPHERAL_SPI); } diff --git a/src/hal/spi.h b/src/hal/spi.h index 109eccc..a00a905 100644 --- a/src/hal/spi.h +++ b/src/hal/spi.h @@ -1,6 +1,7 @@ #ifndef __SPI_H #define __SPI_H +#include #include #include @@ -12,6 +13,10 @@ void spi_send(uint16_t data); uint8_t spi_receive(); +uint8_t spi_read(); + +void spi_set_chip_select(GPIO_TypeDef *gpio_cs, uint16_t pin_cs, bool select); + uint8_t spi_send_and_receive(GPIO_TypeDef *gpio_cs, uint16_t pin_cs, uint16_t data); #endif diff --git a/src/hal/system.c b/src/hal/system.c index 896c248..152f69f 100644 --- a/src/hal/system.c +++ b/src/hal/system.c @@ -11,6 +11,8 @@ #include "system.h" #include "delay.h" #include "log.h" +#include "gpio.h" +#include "millis.h" #define BUTTON_PRESS_LONG_COUNT SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND @@ -38,6 +40,8 @@ static void nvic_init() static void rcc_init() { RCC_DeInit(); +#ifdef RS41 + // The RS41 hardware uses an external clock at 24 MHz RCC_HSEConfig(RCC_HSE_ON); ErrorStatus hse_status = RCC_WaitForHSEStartUp(); @@ -45,6 +49,17 @@ static void rcc_init() // If HSE fails to start up, the application will have incorrect clock configuration. while (true) {} } +#endif +#ifdef DFM17 + // The DFM17 hardware uses the internal clock + RCC_AdjustHSICalibrationValue(0x10U); + // Set up a 24 MHz PLL for 24 MHz SYSCLK (8 MHz / 2) * 6 = 24 MHz + RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_6); + RCC_HSICmd(ENABLE); + RCC_PLLCmd(ENABLE); + while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); +#endif + //SystemInit(); FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); @@ -52,12 +67,21 @@ static void rcc_init() // TODO: Check what the delay timer TIM3 settings really should be and WTF the clock tick really is!?!?!? - RCC_HCLKConfig(RCC_SYSCLK_Div1); // Was: RCC_SYSCLK_Div4 - RCC_PCLK2Config(RCC_HCLK_Div1); // Was: 4 - RCC_PCLK1Config(RCC_HCLK_Div1); // Was: 2 + RCC_HCLKConfig(RCC_SYSCLK_Div1); + RCC_PCLK2Config(RCC_HCLK_Div1); + RCC_PCLK1Config(RCC_HCLK_Div1); +#ifdef RS41 + // Use the 24 MHz external clock as SYSCLK RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); while (RCC_GetSYSCLKSource() != 0x04); +#endif +#ifdef DFM17 + // Use the 24 MHz PLL as SYSCLK + RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); + + while (RCC_GetSYSCLKSource() != 0x08); +#endif } static void gpio_init() @@ -72,28 +96,45 @@ static void gpio_init() GPIO_InitTypeDef gpio_init; // Shutdown request - gpio_init.GPIO_Pin = GPIO_Pin_12; + gpio_init.GPIO_Pin = PIN_SHUTDOWN; gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOA, &gpio_init); + GPIO_Init(BANK_SHUTDOWN, &gpio_init); +#ifdef DFM17 + GPIO_SetBits(BANK_SHUTDOWN, PIN_SHUTDOWN); // Pull high to keep BMS from removing battery power after startup +#endif // Battery voltage (analog) - gpio_init.GPIO_Pin = GPIO_Pin_5; + gpio_init.GPIO_Pin = PIN_VOLTAGE; gpio_init.GPIO_Mode = GPIO_Mode_AIN; gpio_init.GPIO_Speed = GPIO_Speed_10MHz; - GPIO_Init(GPIOA, &gpio_init); + GPIO_Init(BANK_VOLTAGE, &gpio_init); // Button state (analog) - gpio_init.GPIO_Pin = GPIO_Pin_6; + gpio_init.GPIO_Pin = PIN_BUTTON; gpio_init.GPIO_Mode = GPIO_Mode_AIN; gpio_init.GPIO_Speed = GPIO_Speed_10MHz; - GPIO_Init(GPIOA, &gpio_init); + GPIO_Init(BANK_BUTTON, &gpio_init); - // LEDs - gpio_init.GPIO_Pin = GPIO_PIN_LED_GREEN | GPIO_PIN_LED_RED; + // Green LED + gpio_init.GPIO_Pin = PIN_GREEN_LED; gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOB, &gpio_init); + GPIO_Init(BANK_GREEN_LED, &gpio_init); + + // Red LED + gpio_init.GPIO_Pin = PIN_RED_LED; + gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(BANK_RED_LED, &gpio_init); + +#ifdef DFM17 + // Yellow LED (only in DFM-17) + gpio_init.GPIO_Pin = PIN_YELLOW_LED; + gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; + gpio_init.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(BANK_YELLOW_LED, &gpio_init); +#endif } /** @@ -108,7 +149,12 @@ static void dma_adc_init() RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); +#ifdef RS41 dma_init.DMA_BufferSize = 2; +#endif +#ifdef DFM17 + dma_init.DMA_BufferSize = 1; +#endif dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; dma_init.DMA_M2M = DMA_M2M_Disable; dma_init.DMA_MemoryBaseAddr = (uint32_t) &dma_buffer_adc; @@ -132,11 +178,21 @@ static void dma_adc_init() adc_init.ADC_ContinuousConvMode = ENABLE; adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; adc_init.ADC_DataAlign = ADC_DataAlign_Right; +#ifdef RS41 adc_init.ADC_NbrOfChannel = 2; +#endif +#ifdef DFM17 + adc_init.ADC_NbrOfChannel = 1; +#endif ADC_Init(ADC1, &adc_init); - ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_28Cycles5); - ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 2, ADC_SampleTime_28Cycles5); + ADC_RegularChannelConfig(ADC1, CHANNEL_VOLTAGE, 1, ADC_SampleTime_28Cycles5); +#ifdef RS41 + ADC_RegularChannelConfig(ADC1, CHANNEL_BUTTON, 2, ADC_SampleTime_28Cycles5); +#endif +#ifdef DFM17 +// Not using ADC for button on DFM17 +#endif // ADC1 DMA requests are routed to DMA1 Channel1 ADC_DMACmd(ADC1, ENABLE); @@ -160,12 +216,23 @@ uint16_t system_get_battery_voltage_millivolts() uint16_t system_get_button_adc_value() { +#ifdef RS41 return (uint16_t) dma_buffer_adc[1]; +#endif +#ifdef DFM17 + // Fake being an ADC. Take the binary value and if non-zero, make it trigger button-down + return ( ((int) GPIO_ReadInputDataBit(BANK_BUTTON,PIN_BUTTON)) * 2100); +#endif } void system_shutdown() { - GPIO_SetBits(GPIOA, GPIO_Pin_12); +#ifdef RS41 + GPIO_SetBits(BANK_SHUTDOWN, PIN_SHUTDOWN); +#endif +#ifdef DFM17 + GPIO_ResetBits(BANK_SHUTDOWN, PIN_SHUTDOWN); +#endif } void system_handle_button() @@ -251,20 +318,50 @@ void system_enable_tick() void system_set_green_led(bool enabled) { +#ifdef RS41 if (enabled) { - GPIO_ResetBits(GPIOB, GPIO_PIN_LED_GREEN); + GPIO_ResetBits(BANK_GREEN_LED, PIN_GREEN_LED); } else { - GPIO_SetBits(GPIOB, GPIO_PIN_LED_GREEN); + GPIO_SetBits(BANK_GREEN_LED, PIN_GREEN_LED); } +#endif +#ifdef DFM17 + if (enabled) { + GPIO_SetBits(BANK_GREEN_LED, PIN_GREEN_LED); + } else { + GPIO_ResetBits(BANK_GREEN_LED, PIN_GREEN_LED); + } +#endif } void system_set_red_led(bool enabled) { +#ifdef RS41 if (enabled) { - GPIO_ResetBits(GPIOB, GPIO_PIN_LED_RED); + GPIO_ResetBits(BANK_RED_LED, PIN_RED_LED); } else { - GPIO_SetBits(GPIOB, GPIO_PIN_LED_RED); + GPIO_SetBits(BANK_RED_LED, PIN_RED_LED); } +#endif +#ifdef DFM17 + if (enabled) { + GPIO_SetBits(BANK_RED_LED, PIN_RED_LED); + } else { + GPIO_ResetBits(BANK_RED_LED, PIN_RED_LED); + } +#endif +} + +void system_set_yellow_led(bool enabled) +{ +#ifdef DFM17 + // Only DFM-17 has a yellow LED + if (enabled) { + GPIO_SetBits(BANK_YELLOW_LED, PIN_YELLOW_LED); + } else { + GPIO_ResetBits(BANK_YELLOW_LED, PIN_YELLOW_LED); + } +#endif } void system_disable_irq() @@ -284,7 +381,10 @@ void system_init() gpio_init(); dma_adc_init(); delay_init(); - +#ifdef DFM17 + // The millis timer is used for clock calibration on DFM-17 only + millis_timer_init(); +#endif system_scheduler_timer_init(); RCC_ClocksTypeDef RCC_Clocks; diff --git a/src/hal/system.h b/src/hal/system.h index 2f29315..10682c1 100644 --- a/src/hal/system.h +++ b/src/hal/system.h @@ -1,11 +1,9 @@ #ifndef __HAL_SYSTEM_H #define __HAL_SYSTEM_H +#include "config.h" #include "hal.h" -#define GPIO_PIN_LED_GREEN GPIO_Pin_7 -#define GPIO_PIN_LED_RED GPIO_Pin_8 - #define SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND 10000 void system_init(); @@ -17,6 +15,9 @@ void system_disable_irq(); void system_enable_irq(); void system_set_green_led(bool enabled); void system_set_red_led(bool enabled); +#ifdef DFM17 +void system_set_yellow_led(bool enabled); +#endif uint16_t system_get_battery_voltage_millivolts(); uint16_t system_get_button_adc_value(); diff --git a/src/hal/usart_gps.c b/src/hal/usart_gps.c index dbd8cf7..96f4769 100644 --- a/src/hal/usart_gps.c +++ b/src/hal/usart_gps.c @@ -6,6 +6,7 @@ #include #include "usart_gps.h" +#include "gpio.h" void (*usart_gps_handle_incoming_byte)(uint8_t data) = NULL; @@ -13,25 +14,29 @@ void usart_gps_init(uint32_t baud_rate, bool enable_irq) { GPIO_InitTypeDef gpio_init; - // USART1 TX - gpio_init.GPIO_Pin = GPIO_Pin_9; + // USART TX + gpio_init.GPIO_Pin = PIN_USART_TX; gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; - GPIO_Init(GPIOA, &gpio_init); + GPIO_Init(BANK_USART_TX, &gpio_init); - // USART1 RX - gpio_init.GPIO_Pin = GPIO_Pin_10; + // USART RX + gpio_init.GPIO_Pin = PIN_USART_RX; gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; - GPIO_Init(GPIOA, &gpio_init); + GPIO_Init(BANK_USART_RX, &gpio_init); - NVIC_DisableIRQ(USART1_IRQn); - USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); - USART_ClearITPendingBit(USART1, USART_IT_RXNE); - USART_ClearITPendingBit(USART1, USART_IT_ORE); + NVIC_DisableIRQ(USART_IRQ); + USART_ITConfig(USART_IT, USART_IT_RXNE, DISABLE); + USART_ClearITPendingBit(USART_IT, USART_IT_RXNE); + USART_ClearITPendingBit(USART_IT, USART_IT_ORE); - USART_Cmd(USART1, DISABLE); + USART_Cmd(USART_IT, DISABLE); - RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); +#if defined(RS41) + RCC_APB2PeriphClockCmd(APBPERIPHERAL_USART, ENABLE); +#elif defined(DFM17) + RCC_APB1PeriphClockCmd(APBPERIPHERAL_USART, ENABLE); +#endif USART_InitTypeDef usart_init; usart_init.USART_BaudRate = baud_rate; @@ -40,65 +45,69 @@ void usart_gps_init(uint32_t baud_rate, bool enable_irq) 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(USART1, &usart_init); + USART_Init(USART_IT, &usart_init); NVIC_InitTypeDef nvic_init; - nvic_init.NVIC_IRQChannel = USART1_IRQn; - nvic_init.NVIC_IRQChannelPreemptionPriority = 15; + nvic_init.NVIC_IRQChannel = USART_IRQ; + nvic_init.NVIC_IRQChannelPreemptionPriority = 3; nvic_init.NVIC_IRQChannelSubPriority = 2; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init); - USART_Cmd(USART1, ENABLE); + USART_Cmd(USART_IT, ENABLE); if (enable_irq) { - USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); - NVIC_EnableIRQ(USART1_IRQn); + USART_ITConfig(USART_IT, USART_IT_RXNE, ENABLE); + NVIC_EnableIRQ(USART_IRQ); } } static void usart_gps_enable_irq(bool enabled) { if (enabled) { - USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); - NVIC_EnableIRQ(USART1_IRQn); + USART_ITConfig(USART_IT, USART_IT_RXNE, ENABLE); + NVIC_EnableIRQ(USART_IRQ); } else { - NVIC_DisableIRQ(USART1_IRQn); - USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); + NVIC_DisableIRQ(USART_IRQ); + USART_ITConfig(USART_IT, USART_IT_RXNE, DISABLE); } - USART_ClearITPendingBit(USART1, USART_IT_RXNE); - USART_ClearITPendingBit(USART1, USART_IT_ORE); + USART_ClearITPendingBit(USART_IT, USART_IT_RXNE); + USART_ClearITPendingBit(USART_IT, USART_IT_ORE); } void usart_gps_uninit() { usart_gps_enable_irq(false); - USART_Cmd(USART1, DISABLE); - USART_DeInit(USART1); - RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE); + USART_Cmd(USART_IT, DISABLE); + USART_DeInit(USART_IT); +#if defined(RS41) + RCC_APB2PeriphClockCmd(APBPERIPHERAL_USART, DISABLE); +#elif defined(DFM17) + RCC_APB1PeriphClockCmd(APBPERIPHERAL_USART, DISABLE); +#endif } void usart_gps_enable(bool enabled) { usart_gps_enable_irq(enabled); - USART_Cmd(USART1, enabled ? ENABLE : DISABLE); + USART_Cmd(USART_IT, enabled ? ENABLE : DISABLE); } void usart_gps_send_byte(uint8_t data) { - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} - USART_SendData(USART1, data); - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} + while (USART_GetFlagStatus(USART_IT, USART_FLAG_TC) == RESET) {} + USART_SendData(USART_IT, data); + while (USART_GetFlagStatus(USART_IT, USART_FLAG_TC) == RESET) {} } -void USART1_IRQHandler(void) +void USART_IRQ_HANDLER(void) { - if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { - USART_ClearITPendingBit(USART1, USART_IT_RXNE); - uint8_t data = (uint8_t) USART_ReceiveData(USART1); + if (USART_GetITStatus(USART_IT, USART_IT_RXNE) != RESET) { + USART_ClearITPendingBit(USART_IT, USART_IT_RXNE); + uint8_t data = (uint8_t) USART_ReceiveData(USART_IT); usart_gps_handle_incoming_byte(data); - } else if (USART_GetITStatus(USART1, USART_IT_ORE) != RESET) { - USART_ClearITPendingBit(USART1, USART_IT_ORE); - USART_ReceiveData(USART1); + } else if (USART_GetITStatus(USART_IT, USART_IT_ORE) != RESET) { + USART_ClearITPendingBit(USART_IT, USART_IT_ORE); + USART_ReceiveData(USART_IT); } else { - USART_ReceiveData(USART1); + USART_ReceiveData(USART_IT); } } diff --git a/src/main.c b/src/main.c index 988a3ad..caf7d8a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,12 +1,10 @@ #include "hal/system.h" -#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" -#include "drivers/si4032/si4032.h" #include "drivers/pulse_counter/pulse_counter.h" #include "bmp280_handler.h" #include "radsens_handler.h" @@ -15,6 +13,16 @@ #include "config.h" #include "log.h" +#ifdef RS41 +#include "hal/i2c.h" +#include "drivers/si4032/si4032.h" +#endif + +#ifdef DFM17 +#include "hal/clock_calibration.h" +#include "drivers/si4063/si4063.h" +#endif + uint32_t counter = 0; bool led_state = true; @@ -67,6 +75,17 @@ void set_red_led(bool enabled) system_set_red_led(enabled); } +#ifdef DFM17 +void set_yellow_led(bool enabled) +{ + if ((LEDS_DISABLE_ALTITUDE_METERS > 0) && (current_gps_data.altitude_mm / 1000 > LEDS_DISABLE_ALTITUDE_METERS)) { + enabled = false; + } + + system_set_yellow_led(enabled); +} +#endif + int main(void) { bool success; @@ -89,8 +108,11 @@ int main(void) log_info("Pulse counter init\n"); pulse_counter_init(PULSE_COUNTER_PIN_MODE, PULSE_COUNTER_INTERRUPT_EDGE); } else { +#ifdef RS41 + // Only RS41 uses the I2C bus log_info("I2C init: clock speed %d kHz\n", I2C_BUS_CLOCK_SPEED / 1000); i2c_init(I2C_BUS_CLOCK_SPEED); +#endif } log_info("SPI init\n"); @@ -105,8 +127,18 @@ int main(void) goto gps_init; } +#ifdef DFM17 + log_info("Timepulse init\n"); + timepulse_init(); +#endif + +#if defined(RS41) log_info("Si4032 init\n"); si4032_init(); +#elif defined(DFM17) + log_info("Si4063 init\n"); + si4063_init(); +#endif if (bmp280_enabled) { for (int i = 0; i < 3; i++) { @@ -160,6 +192,9 @@ int main(void) while (true) { radio_handle_main_loop(); +#ifdef DFM17 + clock_calibration_adjust(); +#endif //NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, DISABLE); //__WFI(); } diff --git a/src/radio.c b/src/radio.c index e047689..443f2cb 100644 --- a/src/radio.c +++ b/src/radio.c @@ -13,7 +13,12 @@ #include "codecs/jtencode/jtencode.h" #include "drivers/ubxg6010/ubxg6010.h" #include "radio_internal.h" +#ifdef RS41 #include "radio_si4032.h" +#endif +#ifdef DFM17 +#include "radio_si4063.h" +#endif #include "radio_si5351.h" #include "radio_payload_cw.h" #include "radio_payload_aprs_position.h" @@ -25,6 +30,8 @@ #include "radio_payload_fsq.h" radio_transmit_entry radio_transmit_schedule[] = { +#ifdef RS41 +// Si4032 #if RADIO_SI4032_TX_HORUS_V1_CONTINUOUS { .enabled = RADIO_SI4032_TX_HORUS_V1, @@ -155,6 +162,145 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &mfsk_fsk_encoder_api, }, #endif +#endif +#endif + +#ifdef DFM17 +// Si4063 +#if RADIO_SI4063_TX_HORUS_V1_CONTINUOUS + { + .enabled = RADIO_SI4063_TX_HORUS_V1, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V1, + .time_sync_seconds = HORUS_V1_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V1_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V1, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V1_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v1_payload_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, + { + .enabled = RADIO_SI4063_TX_HORUS_V1, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V1, + .time_sync_seconds = HORUS_V1_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V1_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V1, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V1_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v1_idle_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, +#elif RADIO_SI4063_TX_HORUS_V2_CONTINUOUS + { + .enabled = RADIO_SI4063_TX_HORUS_V2, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V2, + .time_sync_seconds = HORUS_V2_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V2_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V2, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V2_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v2_payload_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, + { + .enabled = RADIO_SI4063_TX_HORUS_V2, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V2, + .time_sync_seconds = HORUS_V2_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V2_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V2, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V2_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v2_idle_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, +#else +#if RADIO_SI4063_TX_PIP + { + .enabled = RADIO_SI4063_TX_PIP, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_PIP, + .transmit_count = RADIO_SI4063_TX_PIP_COUNT, + .time_sync_seconds = PIP_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = PIP_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_PIP, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = MORSE_WPM_TO_SYMBOL_RATE(PIP_SPEED_WPM), + .payload_encoder = &radio_cw_payload_encoder, + .fsk_encoder_api = &morse_fsk_encoder_api, + }, +#endif +#if RADIO_SI4063_TX_CW + { + .enabled = RADIO_SI4063_TX_CW, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_CW, + .transmit_count = RADIO_SI4063_TX_CW_COUNT, + .time_sync_seconds = CW_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = CW_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_CW, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = MORSE_WPM_TO_SYMBOL_RATE(CW_SPEED_WPM), + .payload_encoder = &radio_cw_payload_encoder, + .fsk_encoder_api = &morse_fsk_encoder_api, + }, +#endif +#if RADIO_SI4063_TX_APRS + { + .enabled = RADIO_SI4063_TX_APRS, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_APRS_1200, + .transmit_count = RADIO_SI4063_TX_APRS_COUNT, + .time_sync_seconds = APRS_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = APRS_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_APRS_1200, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = 1200, +#if APRS_WEATHER_REPORT_ENABLE + .payload_encoder = &radio_aprs_weather_report_payload_encoder, +#else + .payload_encoder = &radio_aprs_position_payload_encoder, +#endif + .fsk_encoder_api = &bell_fsk_encoder_api, + }, +#endif +#if RADIO_SI4063_TX_HORUS_V1 + { + .enabled = RADIO_SI4063_TX_HORUS_V1, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V1, + .transmit_count = RADIO_SI4063_TX_HORUS_V1_COUNT, + .time_sync_seconds = HORUS_V1_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V1_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V1, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V1_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v1_payload_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, +#endif +#if RADIO_SI4063_TX_HORUS_V2 + { + .enabled = RADIO_SI4063_TX_HORUS_V2, + .radio_type = RADIO_TYPE_SI4063, + .data_mode = RADIO_DATA_MODE_HORUS_V2, + .transmit_count = RADIO_SI4063_TX_HORUS_V2_COUNT, + .time_sync_seconds = HORUS_V2_TIME_SYNC_SECONDS, + .time_sync_seconds_offset = HORUS_V2_TIME_SYNC_OFFSET_SECONDS, + .frequency = RADIO_SI4063_TX_FREQUENCY_HORUS_V2, + .tx_power = RADIO_SI4063_TX_POWER, + .symbol_rate = HORUS_V2_BAUD_RATE_SI4063, + .payload_encoder = &radio_horus_v2_payload_encoder, + .fsk_encoder_api = &mfsk_fsk_encoder_api, + }, +#endif +#endif +#endif + +// Si5351 #if RADIO_SI5351_ENABLE #if RADIO_SI5351_TX_PIP { @@ -300,7 +446,6 @@ radio_transmit_entry radio_transmit_schedule[] = { .fsk_encoder_api = &jtencode_fsk_encoder_api, }, #endif -#endif #endif { .end = true, @@ -329,6 +474,8 @@ uint16_t radio_current_payload_length = 0; uint8_t radio_current_symbol_data[RADIO_SYMBOL_DATA_MAX_LENGTH]; +uint32_t precalculated_pwm_periods[FSK_TONE_COUNT_MAX]; + static volatile uint32_t start_tick = 0, end_tick = 0; telemetry_data current_telemetry_data; @@ -515,9 +662,16 @@ static bool radio_start_transmit(radio_transmit_entry *entry) } switch (entry->radio_type) { +#ifdef RS41 case RADIO_TYPE_SI4032: success = radio_start_transmit_si4032(entry, &radio_shared_state); break; +#endif +#ifdef DFM17 + case RADIO_TYPE_SI4063: + success = radio_start_transmit_si4063(entry, &radio_shared_state); + break; +#endif case RADIO_TYPE_SI5351: success = radio_start_transmit_si5351(entry, &radio_shared_state); break; @@ -562,9 +716,16 @@ static bool radio_stop_transmit(radio_transmit_entry *entry) bool success; switch (entry->radio_type) { +#ifdef RS41 case RADIO_TYPE_SI4032: success = radio_stop_transmit_si4032(entry, &radio_shared_state); break; +#endif +#ifdef DFM17 + case RADIO_TYPE_SI4063: + success = radio_stop_transmit_si4063(entry, &radio_shared_state); + break; +#endif case RADIO_TYPE_SI5351: success = radio_stop_transmit_si5351(entry, &radio_shared_state); break; @@ -620,9 +781,16 @@ static bool radio_transmit_symbol(radio_transmit_entry *entry) bool success; switch (entry->radio_type) { +#ifdef RS41 case RADIO_TYPE_SI4032: success = radio_transmit_symbol_si4032(entry, &radio_shared_state); break; +#endif +#ifdef DFM17 + case RADIO_TYPE_SI4063: + success = radio_transmit_symbol_si4063(entry, &radio_shared_state); + break; +#endif case RADIO_TYPE_SI5351: success = radio_transmit_symbol_si5351(entry, &radio_shared_state); break; @@ -687,7 +855,12 @@ void radio_handle_timer_tick() void radio_handle_data_timer_tick() { +#ifdef RS41 radio_handle_data_timer_si4032(); +#endif +#ifdef DFM17 + radio_handle_data_timer_si4063(); +#endif radio_handle_data_timer_si5351(); } @@ -788,7 +961,13 @@ void radio_handle_main_loop() radio_start_transmit_entry = radio_current_transmit_entry; } +#ifdef RS41 radio_handle_main_loop_si4032(radio_current_transmit_entry, &radio_shared_state); +#endif +#ifdef DFM17 + radio_handle_main_loop_si4063(radio_current_transmit_entry, &radio_shared_state); +#endif + radio_handle_main_loop_si5351(radio_current_transmit_entry, &radio_shared_state); bool first_symbol = false; @@ -901,7 +1080,12 @@ void radio_init() radio_current_transmit_entry = &radio_transmit_schedule[radio_current_transmit_entry_index]; } +#ifdef RS41 radio_init_si4032(); +#endif +#ifdef DFM17 + radio_init_si4063(); +#endif radio_module_initialized = true; } diff --git a/src/radio_internal.h b/src/radio_internal.h index df134b8..8ee3cd5 100644 --- a/src/radio_internal.h +++ b/src/radio_internal.h @@ -10,6 +10,7 @@ typedef enum _radio_type { RADIO_TYPE_SI4032 = 1, RADIO_TYPE_SI5351, + RADIO_TYPE_SI4063, } radio_type; typedef enum _radio_data_mode { @@ -80,5 +81,6 @@ typedef struct _radio_module_state { extern radio_transmit_entry *radio_current_transmit_entry; extern radio_module_state radio_shared_state; +extern uint32_t precalculated_pwm_periods[]; #endif diff --git a/src/radio_si4032.c b/src/radio_si4032.c index effac50..b8f2f93 100644 --- a/src/radio_si4032.c +++ b/src/radio_si4032.c @@ -1,4 +1,6 @@ -#include +#include "config.h" + +#ifdef RS41 #include #include "hal/system.h" @@ -27,14 +29,13 @@ static bool si4032_use_dma = false; // TODO: Add support for multiple APRS baud rates -uint16_t symbol_delay_bell_202_1200bps_us = 823; +// This delay is for RS41 radiosondes +#define symbol_delay_bell_202_1200bps_us 823 static volatile bool radio_si4032_state_change = false; static volatile uint32_t radio_si4032_freq = 0; static volatile int8_t radio_dma_transfer_stop_after_counter = -1; -uint32_t precalculated_pwm_periods[FSK_TONE_COUNT_MAX]; - uint16_t radio_si4032_fill_pwm_buffer(uint16_t offset, uint16_t length, uint16_t *buffer); bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state) @@ -258,7 +259,7 @@ inline void radio_handle_data_timer_si4032() tone_index = fsk_encoder_api->next_tone(fsk_enc); if (tone_index < 0) { - log_info("Horus V1 TX finished\n"); + log_info("Horus TX finished\n"); radio_shared_state.radio_interrupt_transmit_active = false; radio_shared_state.radio_transmission_finished = true; system_enable_tick(); @@ -405,3 +406,4 @@ void radio_init_si4032() pwm_dma_init(); } } +#endif diff --git a/src/radio_si4063.c b/src/radio_si4063.c new file mode 100644 index 0000000..0e0fe1b --- /dev/null +++ b/src/radio_si4063.c @@ -0,0 +1,307 @@ +#include "config.h" + +#ifdef DFM17 +#include "hal/system.h" +#include "hal/spi.h" +#include "hal/pwm.h" +#include "hal/delay.h" +#include "hal/datatimer.h" +#include "drivers/si4063/si4063.h" +#include "log.h" + +#include "radio_si4063.h" +#include "codecs/mfsk/mfsk.h" + +#define SI4063_DEVIATION_HZ_RTTY 200.0 +#define SI4063_DEVIATION_HZ_APRS 2600.0 + +#define CW_SYMBOL_RATE_MULTIPLIER 4 + +// TODO: Add support for multiple APRS baud rates +// This delay is for DFM-17 radiosondes +#define symbol_delay_bell_202_1200bps_us 821 + +static volatile bool radio_si4063_state_change = false; +static volatile uint32_t radio_si4063_freq = 0; + +bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + uint16_t frequency_offset; + uint32_t frequency_deviation = 0; + si4063_modulation_type modulation_type; + bool use_direct_mode; + + switch (entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: + frequency_offset = 1; + modulation_type = SI4063_MODULATION_TYPE_OOK; + use_direct_mode = false; + + data_timer_init(entry->symbol_rate * CW_SYMBOL_RATE_MULTIPLIER); + break; + case RADIO_DATA_MODE_RTTY: + frequency_offset = 0; + frequency_deviation = SI4063_DEVIATION_HZ_RTTY; + modulation_type = SI4063_MODULATION_TYPE_CW; + use_direct_mode = false; + break; + case RADIO_DATA_MODE_APRS_1200: + frequency_offset = 0; + frequency_deviation = SI4063_DEVIATION_HZ_APRS; + modulation_type = SI4063_MODULATION_TYPE_FSK; + use_direct_mode = true; + break; + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: { + fsk_tone *idle_tone = mfsk_get_idle_tone(&entry->fsk_encoder); + frequency_offset = (uint16_t) idle_tone->index + HORUS_FREQUENCY_OFFSET_SI4063; + modulation_type = SI4063_MODULATION_TYPE_CW; + use_direct_mode = false; + + data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder)); + break; + } + default: + return false; + } + + si4063_set_tx_frequency(entry->frequency); + si4063_set_tx_power(entry->tx_power); + si4063_set_frequency_offset(frequency_offset); + si4063_set_modulation_type(modulation_type); + si4063_set_frequency_deviation(frequency_deviation); + + si4063_enable_tx(); + + if (use_direct_mode) { + spi_uninit(); + pwm_timer_init(100 * 100); // TODO: Idle tone + pwm_timer_use(true); + pwm_timer_pwm_enable(true); + } + + switch (entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: + spi_uninit(); + system_disable_tick(); + shared_state->radio_interrupt_transmit_active = true; + break; + case RADIO_DATA_MODE_APRS_1200: + shared_state->radio_manual_transmit_active = true; + break; + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: + system_disable_tick(); + shared_state->radio_interrupt_transmit_active = true; + break; + default: + break; + } + + return true; +} + +static uint32_t radio_next_symbol_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + switch (entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: + return 0; + case RADIO_DATA_MODE_RTTY: + return 0; + case RADIO_DATA_MODE_APRS_1200: { + int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder); + if (next_tone_index < 0) { + return 0; + } + + return shared_state->radio_current_fsk_tones[next_tone_index].frequency_hz_100; + } + default: + return 0; + } +} + +bool radio_transmit_symbol_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + uint32_t frequency = radio_next_symbol_si4063(entry, shared_state); + + if (frequency == 0) { + return false; + } + + radio_si4063_freq = frequency; + radio_si4063_state_change = true; + + return true; +} + +static void radio_handle_main_loop_manual_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + fsk_encoder_api *fsk_encoder_api = entry->fsk_encoder_api; + fsk_encoder *fsk_enc = &entry->fsk_encoder; + + for (uint8_t i = 0; i < shared_state->radio_current_fsk_tone_count; i++) { + precalculated_pwm_periods[i] = pwm_calculate_period(shared_state->radio_current_fsk_tones[i].frequency_hz_100); + } + + system_disable_tick(); + + switch (entry->data_mode) { + case RADIO_DATA_MODE_APRS_1200: { + int8_t tone_index; + + while ((tone_index = fsk_encoder_api->next_tone(fsk_enc)) >= 0) { + pwm_timer_set_frequency(precalculated_pwm_periods[tone_index]); + shared_state->radio_symbol_count_loop++; + delay_us(symbol_delay_bell_202_1200bps_us); + } + + radio_si4063_state_change = false; + shared_state->radio_transmission_finished = true; + break; + } + default: + break; + } + + system_enable_tick(); +} + +void radio_handle_main_loop_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + if (entry->radio_type != RADIO_TYPE_SI4063 || shared_state->radio_interrupt_transmit_active) { + return; + } + + if (shared_state->radio_manual_transmit_active) { + radio_handle_main_loop_manual_si4063(entry, shared_state); + return; + } + + if (radio_si4063_state_change) { + radio_si4063_state_change = false; + pwm_timer_set_frequency(radio_si4063_freq); + shared_state->radio_symbol_count_loop++; + } +} + +inline void radio_handle_data_timer_si4063() +{ + static int cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER; + + if (radio_current_transmit_entry->radio_type != RADIO_TYPE_SI4063 || !radio_shared_state.radio_interrupt_transmit_active) { + return; + } + + switch (radio_current_transmit_entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: { + cw_symbol_rate_multiplier--; + if (cw_symbol_rate_multiplier > 0) { + break; + } + + cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER; + + fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api; + fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder; + int8_t tone_index; + + tone_index = fsk_encoder_api->next_tone(fsk_enc); + if (tone_index < 0) { + si4063_set_direct_mode_pin(false); + log_info("CW TX finished\n"); + radio_shared_state.radio_interrupt_transmit_active = false; + radio_shared_state.radio_transmission_finished = true; + system_enable_tick(); + break; + } + + si4063_set_direct_mode_pin(tone_index == 0 ? false : true); + + radio_shared_state.radio_symbol_count_interrupt++; + break; + } + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: { + fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api; + fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder; + int8_t tone_index; + + tone_index = fsk_encoder_api->next_tone(fsk_enc); + if (tone_index < 0) { + log_info("Horus TX finished\n"); + radio_shared_state.radio_interrupt_transmit_active = false; + radio_shared_state.radio_transmission_finished = true; + system_enable_tick(); + break; + } + + // NOTE: The factor of 23 will produce a tone spacing of about 270 Hz, which is the standard spacing for Horus 4FSK + si4063_set_frequency_offset(tone_index * 23 + HORUS_FREQUENCY_OFFSET_SI4063); + + radio_shared_state.radio_symbol_count_interrupt++; + break; + } + default: + break; + } +} + +bool radio_stop_transmit_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) +{ + bool use_direct_mode = false; + + switch (entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: + data_timer_uninit(); + spi_init(); + break; + case RADIO_DATA_MODE_RTTY: + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: + data_timer_uninit(); + break; + case RADIO_DATA_MODE_APRS_1200: + use_direct_mode = true; + break; + default: + break; + } + + if (use_direct_mode) { + pwm_timer_pwm_enable(false); + pwm_timer_use(false); + pwm_timer_uninit(); + spi_init(); + } + + si4063_inhibit_tx(); + + switch (entry->data_mode) { + case RADIO_DATA_MODE_CW: + case RADIO_DATA_MODE_PIP: + system_enable_tick(); + break; + case RADIO_DATA_MODE_APRS_1200: + break; + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: + system_enable_tick(); + break; + default: + break; + } + + return true; +} + + +void radio_init_si4063() +{ +} +#endif diff --git a/src/radio_si4063.h b/src/radio_si4063.h new file mode 100644 index 0000000..2292dcb --- /dev/null +++ b/src/radio_si4063.h @@ -0,0 +1,13 @@ +#ifndef __RADIO_SI4063_H +#define __RADIO_SI4063_H +#ifdef DFM17 +#include "radio_internal.h" + +bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state *shared_state); +bool radio_transmit_symbol_si4063(radio_transmit_entry *entry, radio_module_state *shared_state); +void radio_handle_main_loop_si4063(radio_transmit_entry *entry, radio_module_state *shared_state); +void radio_handle_data_timer_si4063(); +bool radio_stop_transmit_si4063(radio_transmit_entry *entry, radio_module_state *shared_state); +void radio_init_si4063(); +#endif +#endif diff --git a/src/radio_si5351.c b/src/radio_si5351.c index d37615b..800827e 100644 --- a/src/radio_si5351.c +++ b/src/radio_si5351.c @@ -8,8 +8,6 @@ #define CW_SYMBOL_RATE_MULTIPLIER 4 -static bool use_fast_si5351 = false; - static volatile bool radio_si5351_state_change = false; static volatile uint64_t radio_si5351_freq = 0; static volatile bool radio_si5351_frequency_not_set = false; @@ -27,26 +25,18 @@ bool radio_start_transmit_si5351(radio_transmit_entry *entry, radio_module_state set_frequency_early = false; break; case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder)); - //use_fast_si5351 = true; break; default: break; } // TODO: handle Si5351 errors - if (use_fast_si5351) { - si5351_set_drive_strength_fast(SI5351_CLOCK_CLK0, entry->tx_power); - if (set_frequency_early) { - si5351_set_frequency_fast(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL); - si5351_output_enable_fast(SI5351_CLOCK_CLK0, true); - } - } else { - si5351_set_drive_strength(SI5351_CLOCK_CLK0, entry->tx_power); - if (set_frequency_early) { - si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL); - si5351_output_enable(SI5351_CLOCK_CLK0, true); - } + si5351_set_drive_strength(SI5351_CLOCK_CLK0, entry->tx_power); + if (set_frequency_early) { + si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL); + si5351_output_enable(SI5351_CLOCK_CLK0, true); } switch (entry->data_mode) { @@ -58,6 +48,7 @@ bool radio_start_transmit_si5351(radio_transmit_entry *entry, radio_module_state radio_si5351_frequency_not_set = true; break; case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: system_disable_tick(); shared_state->radio_interrupt_transmit_active = true; break; @@ -73,8 +64,8 @@ bool radio_transmit_symbol_si5351(radio_transmit_entry *entry, radio_module_stat switch (entry->data_mode) { case RADIO_DATA_MODE_CW: case RADIO_DATA_MODE_PIP: - return false; case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: return false; default: { int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder); @@ -152,14 +143,15 @@ inline void radio_handle_data_timer_si5351() radio_shared_state.radio_symbol_count_interrupt++; break; } - case RADIO_DATA_MODE_HORUS_V1: { + case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: { fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api; fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder; int8_t tone_index; tone_index = fsk_encoder_api->next_tone(fsk_enc); if (tone_index < 0) { - log_info("Horus V1 TX finished\n"); + log_info("Horus TX finished\n"); radio_shared_state.radio_interrupt_transmit_active = false; radio_shared_state.radio_transmission_finished = true; system_enable_tick(); @@ -181,30 +173,13 @@ inline void radio_handle_data_timer_si5351() bool radio_stop_transmit_si5351(radio_transmit_entry *entry, radio_module_state *shared_state) { - switch (entry->data_mode) { - case RADIO_DATA_MODE_CW: - case RADIO_DATA_MODE_PIP: - break; - case RADIO_DATA_MODE_HORUS_V1: - // use_fast_si5351 = true; - break; - default: - break; - } - - if (use_fast_si5351) { - si5351_output_enable_fast(SI5351_CLOCK_CLK0, false); - } else { - si5351_output_enable(SI5351_CLOCK_CLK0, false); - } + si5351_output_enable(SI5351_CLOCK_CLK0, false); switch (entry->data_mode) { case RADIO_DATA_MODE_CW: case RADIO_DATA_MODE_PIP: - data_timer_uninit(); - system_enable_tick(); - break; case RADIO_DATA_MODE_HORUS_V1: + case RADIO_DATA_MODE_HORUS_V2: data_timer_uninit(); system_enable_tick(); break; diff --git a/src/si5351_handler.cpp b/src/si5351_handler.cpp index 4e688ba..76dad93 100644 --- a/src/si5351_handler.cpp +++ b/src/si5351_handler.cpp @@ -1,10 +1,64 @@ -#include "drivers/si5351/si5351.h" +#include "config.h" + +#if SI5351_FAST_ENABLE #include "drivers/si5351fast/si5351mcu.h" +#else +#include "drivers/si5351/si5351.h" +#endif #include "si5351_handler.h" -Si5351 *si5351; +#if SI5351_FAST_ENABLE Si5351mcu si5351_fast; +#else +Si5351 *si5351; +#endif +#if SI5351_FAST_ENABLE +bool si5351_handler_init() +{ + si5351_fast.init(&DEFAULT_I2C_PORT, SIADDR); + return true; +} + +bool si5351_set_frequency(si5351_clock_id clock, uint64_t frequency_hz_100) +{ + si5351_fast.setFreq((uint8_t) clock, frequency_hz_100 / 100L); + return true; +} + +void si5351_output_enable(si5351_clock_id clock, bool enabled) +{ + if (enabled) { + si5351_fast.enable((uint8_t) clock); + } else { + si5351_fast.disable((uint8_t) clock); + } +} + +void si5351_set_drive_strength(si5351_clock_id clock, uint8_t drive) +{ + int si5351_drive; + + switch (drive) { + case 0: + si5351_drive = SIOUT_2mA; + break; + case 1: + si5351_drive = SIOUT_4mA; + break; + case 2: + si5351_drive = SIOUT_6mA; + break; + case 3: + si5351_drive = SIOUT_8mA; + break; + default: + si5351_drive = SIOUT_2mA; + } + + si5351_fast.setPower(si5351_drive, (uint8_t) clock); +} +#else bool si5351_handler_init() { si5351 = new Si5351(&DEFAULT_I2C_PORT); @@ -14,9 +68,6 @@ bool si5351_handler_init() if (!si5351_found) { return si5351_found; } - - // si5351_fast.init(&DEFAULT_I2C_PORT, SIADDR); - return si5351_found; } @@ -53,42 +104,4 @@ void si5351_set_drive_strength(si5351_clock_id clock, uint8_t drive) si5351->drive_strength((enum si5351_clock) clock, si5351_drive); } - -bool si5351_set_frequency_fast(si5351_clock_id clock, uint64_t frequency_hz_100) -{ - si5351_fast.setFreq((uint8_t) clock, frequency_hz_100 / 100L); - return true; -} - -void si5351_output_enable_fast(si5351_clock_id clock, bool enabled) -{ - if (enabled) { - si5351_fast.enable((uint8_t) clock); - } else { - si5351_fast.disable((uint8_t) clock); - } -} - -void si5351_set_drive_strength_fast(si5351_clock_id clock, uint8_t drive) -{ - int si5351_drive; - - switch (drive) { - case 0: - si5351_drive = SIOUT_2mA; - break; - case 1: - si5351_drive = SIOUT_4mA; - break; - case 2: - si5351_drive = SIOUT_6mA; - break; - case 3: - si5351_drive = SIOUT_8mA; - break; - default: - si5351_drive = SIOUT_2mA; - } - - si5351_fast.setPower(si5351_drive, (uint8_t) clock); -} +#endif diff --git a/src/si5351_handler.h b/src/si5351_handler.h index 6bb5ddf..fdbe480 100644 --- a/src/si5351_handler.h +++ b/src/si5351_handler.h @@ -23,9 +23,6 @@ bool si5351_handler_init(); bool si5351_set_frequency(si5351_clock_id clock, uint64_t frequency_hz_100); void si5351_output_enable(si5351_clock_id clock, bool enabled); void si5351_set_drive_strength(si5351_clock_id clock, uint8_t drive); -bool si5351_set_frequency_fast(si5351_clock_id clock, uint64_t frequency_hz_100); -void si5351_output_enable_fast(si5351_clock_id clock, bool enabled); -void si5351_set_drive_strength_fast(si5351_clock_id clock, uint8_t drive); #ifdef __cplusplus } diff --git a/src/telemetry.c b/src/telemetry.c index fedfce9..640c362 100644 --- a/src/telemetry.c +++ b/src/telemetry.c @@ -1,6 +1,5 @@ #include "telemetry.h" #include "hal/system.h" -#include "drivers/si4032/si4032.h" #include "drivers/ubxg6010/ubxg6010.h" #include "drivers/pulse_counter/pulse_counter.h" #include "bmp280_handler.h" @@ -9,6 +8,14 @@ #include "config.h" #include "log.h" +#ifdef RS41 +#include "drivers/si4032/si4032.h" +#endif +#ifdef DFM17 +#include "hal/clock_calibration.h" +#include "drivers/si4063/si4063.h" +#endif + // Initialize leap seconds with a known good value int8_t gps_time_leap_seconds = GPS_TIME_LEAP_SECONDS; @@ -20,7 +27,12 @@ void telemetry_collect(telemetry_data *data) data->button_adc_value = system_get_button_adc_value(); data->battery_voltage_millivolts = system_get_battery_voltage_millivolts(); +#ifdef RS41 data->internal_temperature_celsius_100 = si4032_read_temperature_celsius_100(); +#endif +#ifdef DFM17 + data->internal_temperature_celsius_100 = si4063_read_temperature_celsius_100(); +#endif if (bmp280_enabled) { bmp280_read_telemetry(data); @@ -60,6 +72,11 @@ void telemetry_collect(telemetry_data *data) data->gps.climb_cm_per_second = 0; } +#ifdef DFM17 + data->clock_calibration_trim = clock_calibration_get_trim(); + data->clock_calibration_count = clock_calibration_get_change_count(); +#endif + locator_from_lonlat(data->gps.longitude_degrees_1000000, data->gps.latitude_degrees_1000000, LOCATOR_PAIR_COUNT_FULL, data->locator); diff --git a/src/telemetry.h b/src/telemetry.h index f7c6f10..6a7c209 100644 --- a/src/telemetry.h +++ b/src/telemetry.h @@ -21,6 +21,9 @@ typedef struct _telemetry_data { gps_data gps; char locator[LOCATOR_PAIR_COUNT_FULL * 2 + 1]; + + int clock_calibration_trim; + uint16_t clock_calibration_count; } telemetry_data; void telemetry_collect(telemetry_data *data); diff --git a/src/template.c b/src/template.c index f6fd65c..e237410 100644 --- a/src/template.c +++ b/src/template.c @@ -111,6 +111,14 @@ size_t template_replace(char *dest, size_t dest_len, char *src, telemetry_data * strlcpy(temp, dest, dest_len); str_replace(dest, dest_len, temp, "$ri", replacement); + snprintf(replacement, sizeof(replacement), "%d", (int) data->clock_calibration_trim); + strlcpy(temp, dest, dest_len); + str_replace(dest, dest_len, temp, "$ct", replacement); + + snprintf(replacement, sizeof(replacement), "%d", (int) data->clock_calibration_count); + strlcpy(temp, dest, dest_len); + str_replace(dest, dest_len, temp, "$cc", replacement); + free(temp); return len;