Graw DFM-17 radiosonde support (#61)

* Initial plumbing for DFM17 (#54)

* Implementing Si4063 radio chip support for DFM17 sonde. Work in progress.

* Add an error to indicate that the code does not work yet

* Removed copyrighted docs from documentation and provided links instead (#55)

* Fix SPI bus initialization for DFM17. Trying to make Si4063 SPI communication work (in progress). Fix LED bit polarity.

* Set DFM17 clocks correctly to 24 MHz

* Still trying to fix Si4063 SPI communication, no luck

* Add clearing of SPI overrun status flag to make Si4063 comm work properly in DFM17. Not sure about the reason this is required.

* Fix bug when setting Si4063 frequency. Remove unnecessary GPS debug and delays. Add Si4063 debug printout. Find a suitable frequency offset multiplier to achieve 270 Hz tone spacing for Horus 4FSK.

* Add some known good Si4063 configuration values. Implement direct GPIO-based modulation for Si4063 that allows support for CW now.

* DFM-17 APRS work (#60)

* Modify for APRS on DFM17

* Clean up Si4063 APRS work and make APRS deviation configurable

* Add documentation for DFM-17

* Add a note about the DFM-17 connector

* More work on docs and config for DFM-17

* Docs

* Mor work on config

* Fix RS41 URL

* Added info about high-altitude balloon flights

* Fix typo

* More code cleanup

* Update authors

* Improve DFM-17 docs

* Added a timepulse routine for DFM17 and also a millis() routine.  Working toward clock calibration.

* More refinements to the HCI calibration

* Cleaned up clock calibration code and integrated into the main radio loop.  Also added APRS telemetry for calibration.

* Added logic to (hopefully) avoid over-calibration if there is an errant timepulse.

* Clean up DFM-17 clock calibration implementation and make RS41 compile properly

* Adjust README

* Add notes about DFM-17 clock calibration

* Fix typo

* Add DFM-17 note about clock calibration to the top of the README

* Text style

* Working on README

* Working on README

* Move datasheet links to main README

* Markdown styling

* Improve config file structure

* Remove unnecessary comments

---------

Co-authored-by: Mike Hojnowski <kd2eat@gmail.com>
pull/79/head
Mikael Nousiainen 2023-10-15 12:38:05 +03:00 zatwierdzone przez GitHub
rodzic 18b6e1ffb4
commit 76e2fd1fae
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
36 zmienionych plików z 2339 dodań i 317 usunięć

233
README.md
Wyświetl plik

@ -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 <oh3bhx@sral.fi>
* Mikael Nousiainen OH3BHX <mikael@0xfeed.tech>
* 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)

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -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
};

Wyświetl plik

@ -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

Wyświetl plik

@ -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 <stdbool.h>
extern bool leds_enabled;

Wyświetl plik

@ -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 <stdbool.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_tim.h>
#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
}
}

Wyświetl plik

@ -0,0 +1,25 @@
#ifndef __SI4063_H
#define __SI4063_H
#include <stdint.h>
#include <stdbool.h>
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

Wyświetl plik

@ -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;

104
src/gpio.h 100644
Wyświetl plik

@ -0,0 +1,104 @@
#ifndef __GPIO_H
#define __GPIO_H
#include <system_stm32f10x.h>
#include <stm32f10x_gpio.h>
#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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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);

65
src/hal/millis.c 100644
Wyświetl plik

@ -0,0 +1,65 @@
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>
#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;
}

12
src/hal/millis.h 100644
Wyświetl plik

@ -0,0 +1,12 @@
#ifndef __MILLIS_H
#define __MILLIS_H
#include <stdint.h>
extern void millis_timer_init(void);
extern void millis_timer_uninit();
extern uint32_t millis();
#endif

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -3,85 +3,152 @@
#include <stm32f10x_spi.h>
#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);
}

Wyświetl plik

@ -1,6 +1,7 @@
#ifndef __SPI_H
#define __SPI_H
#include <stdbool.h>
#include <stdint.h>
#include <stm32f10x.h>
@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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();

Wyświetl plik

@ -6,6 +6,7 @@
#include <misc.h>
#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);
}
}

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -1,4 +1,6 @@
#include <stdint.h>
#include "config.h"
#ifdef RS41
#include <string.h>
#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

307
src/radio_si4063.c 100644
Wyświetl plik

@ -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

13
src/radio_si4063.h 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;