├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── datasheets ├── MB1137.pdf ├── STM32F429 Reference Manual.pdf ├── rfc5905.pdf └── stm32f429zi.pdf ├── firmware ├── .gitignore ├── Makefile ├── build ├── cfg │ ├── board.c │ ├── board.h │ ├── ch_lwip_bindings.mk │ ├── chconf.h │ ├── halconf.h │ ├── lwipopts.h │ └── mcuconf.h ├── config.h ├── debug ├── debug_udp.c ├── flash ├── gnss.c ├── gnss.h ├── gnss_parse.c ├── ip_link.c ├── ip_link.h ├── main.c ├── main.h ├── ntpd.c ├── ntpd.h ├── openocd.cfg ├── tools │ ├── .gitignore │ ├── flash.wav │ ├── ntptest.c │ ├── udp_bc_rx.py │ └── udp_debug_rx.py ├── watchdog.c ├── watchdog.h └── web │ ├── .gitignore │ ├── build │ ├── htsrc │ ├── d3.v4.min.js │ ├── favicon.png │ ├── index.css │ ├── index.html │ ├── index.js │ └── mithril.min.js │ ├── web.c │ ├── web.h │ ├── web_paths.c │ └── web_paths.h ├── hardware ├── build ├── gnss_receiver.png └── gnss_receiver.yaml └── images ├── gnss_receiver.png ├── maximum_rate.png ├── ntpq.png └── web_status.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "firmware/ChibiOS"] 2 | path = firmware/ChibiOS 3 | url = https://github.com/ChibiOS/ChibiOS.git 4 | [submodule "firmware/lwIP"] 5 | path = firmware/lwIP 6 | url = https://git.savannah.gnu.org/git/lwip/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use barebones Travis image 2 | language: c 3 | 4 | # Only grab latest git commit (no need for history) 5 | git: 6 | depth: 1 7 | 8 | # Cache the compilers folder so we don't have to download and unpack it for every build 9 | cache: 10 | directories: 11 | - $HOME/compilers/ 12 | 13 | # Download and unpack the latest GNU ARM Embedded Toolchain if it's not already there 14 | # Also add the gcc/bin folder to $PATH 15 | install: 16 | - export GCC_DIR=$HOME/compilers/gcc-arm-none-eabi-9-2020-q2-update 17 | - export GCC_ARCHIVE=$HOME/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 18 | - export GCC_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 19 | - if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget -nv $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/compilers; fi 20 | - export PATH=$PATH:$GCC_DIR/bin 21 | 22 | # Command to run tests 23 | script: cd firmware/ && ./build -M -w 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NTP Server [![Build Status](https://travis-ci.org/philcrump/stm32-ntp-server.svg?branch=master)](https://travis-ci.org/philcrump/stm32-ntp-server) 2 | 3 | This project is an embedded NTPv4 server using the ChibiOS (20.3.x) and lwIP (2.1.x) software stacks. 4 | 5 | The hardware components are: 6 | 7 | * ST STM32F429ZI ARM Cortex-M4 Development Board - [Product Link](https://www.st.com/en/evaluation-tools/nucleo-f429zi.html) 8 | * ublox MAX-M8Q GNSS Receiver Breakout Board - [Product Link](https://store.uputronics.com/index.php?route=product/product&product_id=84) (+ suitable antenna) 9 | 10 | ## GNSS Time Sync 11 | 12 | The GNSS receiver is configured to send the current time over serial, and on the next rising edge of the timepulse signal the RTC is set to (time+1s). The STM32 RTC only counts in milliseconds, so there is always up to +/-1ms of jitter. (Future work may include using a GPT to measure sub-millisecond precision from last timepulse edge.) 13 | 14 | Despite this, simple observation through `ntpq -p` stats against a known-good remote Stratum 1 NTP server shows favourable characteristics: 15 | 16 |

17 | 18 |

19 | 20 | ## Clock Holdover 21 | 22 | The 32.768KHz crystal oscillator on the ST board is specified to 20ppm of tolerance. This has been measured in this application as <1 millisecond of drift per minute. The holdover time is therefore set to 10 minutes by default to allow up to 10ms of drift before the NTP server marks itself as Stratum 16 ("Unsynchronised"). (Future work may use the calibration routines of the STM32 RTC during periods of GNSS lock to improve this.) 23 | 24 | ## Status Webpage 25 | 26 | The software hosts a webpage showing the current status of the GNSS and NTP subsystems on port 80: 27 | 28 |

29 | 30 |

31 | 32 | ## NTP Request Rate Performance 33 | 34 | The firmware takes \~72µs to service each NTP request, with \~36µs of this spent in the service function. 35 | 36 | This equates to a maximum capability of \~12,000 reqs / second. No effort has been taken to optimise this so greater performance would be possible, however this is far in excess of what is required for the author's application. 37 | 38 | The firmware lights up LED2 on the ST board when in the NTP service function, this causes a single brief flash for each request, and also allows us to observe the timing performance in hardware (HIGH == in service function): 39 | 40 |

41 | 42 |

43 | 44 | If you're after a high-performance embedded NTP server appliance then I can personally recommend the [LeoNTP Time Server](https://store.uputronics.com/index.php?route=product/product&product_id=92), hand-coded in PIC assembly to capably saturate 100Mb/s ethernet at 110,000 reqs / second with sub-µs jitter. 45 | 46 | ## Software Configuration 47 | 48 | `firmware/config.h` contains config directives. 49 | 50 | * `GNSS_AID_` directives supply a position and it's standard deviation error to the GNSS Receiver to aid the initial position fix. If these are wrong then the GNSS receiver may fail to get a fix. Comment them out to disable. 51 | 52 | * `NTPD_STRATUM_DEMOTE_TIMER_PERIOD` Sets the time period after last timepulse sync that the NTP server will advertise Stratum 1 (GPS-synced) before dropping to Stratum 16 (Unsynchronized). 53 | 54 | * `NTPD_STATUS_DEMOTE_TIMER_PERIOD` Sets the time period after last timepulse sync that the Webpage will show 'In Lock' before dropping to 'Holdover'. Does not affect NTP output. 55 | 56 | ## Software Compilation 57 | 58 | This toolchain is designed for Linux. 59 | 60 | You must have `arm-none-eabi-gcc` from [GNU ARM embedded toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) in your path. 61 | 62 | Running `firmware/build -M` will pack the web files and then compile all source files in parallel. The final linker stage can take a few seconds. Running without the `-M` flag will remove parallel compilation and may help errors to be more readable. 63 | 64 | ## Flashing 65 | 66 | `firmware/flash` will flash a compiled firmware image. This first looks for a connected BlackMagic JTAG probe, and if unsuccessful will drop back to `st-flash` from [st-utils](https://github.com/stlink-org/stlink) to flash over the ST-Link USB. 67 | 68 | `firmware/build -MF` will build, and if successful also attempt to flash. 69 | 70 | ## Wiring 71 | 72 | The pins used on the STM32F429ZI are: 73 | 74 | * PC10 (TXD) 75 | * PC11 (RXD) 76 | * PC12 is configured to trigger synchronisation of the RTC on the rising edge of the GNSS Timepulse 77 | 78 | These are broken out in the first 3 pins of CN11 which is the unpopulated 0.1" pads in the upper left of the board. Power supply pins are available a few rows further down. 79 | 80 | Wiring is shown below for direct 3.3V attachment. Some GNSS receivers have an integrated 3.3V regulator and so the Vcc must be fed from a 5V pin. The IO signals are still 3.3V. 81 | 82 |

83 | 84 |

85 | 86 | # Copyright 87 | 88 | GPLv3 licensed. © Phil Crump - phil@philcrump.co.uk 89 | 90 | Derivations from other license-compatible works are acknowledged in the source. -------------------------------------------------------------------------------- /datasheets/MB1137.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/datasheets/MB1137.pdf -------------------------------------------------------------------------------- /datasheets/STM32F429 Reference Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/datasheets/STM32F429 Reference Manual.pdf -------------------------------------------------------------------------------- /datasheets/rfc5905.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/datasheets/rfc5905.pdf -------------------------------------------------------------------------------- /datasheets/stm32f429zi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/datasheets/stm32f429zi.pdf -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | builds/ 2 | -------------------------------------------------------------------------------- /firmware/Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Build global options 3 | # NOTE: Can be overridden externally. 4 | # 5 | 6 | # Compiler options here. 7 | ifeq ($(USE_OPT),) 8 | USE_OPT = -Og -ggdb -fomit-frame-pointer -falign-functions=16 -specs=nosys.specs 9 | endif 10 | 11 | # C specific options here (added to USE_OPT). 12 | ifeq ($(USE_COPT),) 13 | USE_COPT = 14 | endif 15 | 16 | # C++ specific options here (added to USE_OPT). 17 | ifeq ($(USE_CPPOPT),) 18 | USE_CPPOPT = -fno-rtti 19 | endif 20 | 21 | # Enable this if you want the linker to remove unused code and data. 22 | ifeq ($(USE_LINK_GC),) 23 | USE_LINK_GC = yes 24 | endif 25 | 26 | # Linker extra options here. 27 | ifeq ($(USE_LDOPT),) 28 | USE_LDOPT = 29 | endif 30 | 31 | # Enable this if you want link time optimizations (LTO). 32 | ifeq ($(USE_LTO),) 33 | USE_LTO = yes 34 | endif 35 | 36 | # Enable this if you want to see the full log while compiling. 37 | ifeq ($(USE_VERBOSE_COMPILE),) 38 | USE_VERBOSE_COMPILE = no 39 | endif 40 | 41 | # If enabled, this option makes the build process faster by not compiling 42 | # modules not used in the current configuration. 43 | ifeq ($(USE_SMART_BUILD),) 44 | USE_SMART_BUILD = yes 45 | endif 46 | 47 | # 48 | # Build global options 49 | ############################################################################## 50 | 51 | ############################################################################## 52 | # Architecture or project specific options 53 | # 54 | 55 | # Stack size to be allocated to the Cortex-M process stack. This stack is 56 | # the stack used by the main() thread. 57 | ifeq ($(USE_PROCESS_STACKSIZE),) 58 | USE_PROCESS_STACKSIZE = 0x400 59 | endif 60 | 61 | # Stack size to the allocated to the Cortex-M main/exceptions stack. This 62 | # stack is used for processing interrupts and exceptions. 63 | ifeq ($(USE_EXCEPTIONS_STACKSIZE),) 64 | USE_EXCEPTIONS_STACKSIZE = 0x400 65 | endif 66 | 67 | # Enables the use of FPU (no, softfp, hard). 68 | ifeq ($(USE_FPU),) 69 | USE_FPU = no 70 | endif 71 | 72 | # FPU-related options. 73 | ifeq ($(USE_FPU_OPT),) 74 | USE_FPU_OPT = -mfloat-abi=$(USE_FPU) -mfpu=fpv4-sp-d16 75 | endif 76 | 77 | # 78 | # Architecture or project specific options 79 | ############################################################################## 80 | 81 | ############################################################################## 82 | # Project, target, sources and paths 83 | # 84 | 85 | # Define project name here 86 | PROJECT = $(TARGET_FILENAME) 87 | 88 | # Target settings. 89 | MCU = cortex-m4 90 | 91 | # Imported source files and paths. 92 | CHIBIOS := ChibiOS 93 | LWIP := lwIP 94 | CONFDIR := ./cfg 95 | BUILDDIR := ./builds 96 | DEPDIR := $(BUILDDIR)/.dep 97 | 98 | # Board files 99 | BOARDSRC = $(CONFDIR)/board.c 100 | BOARDINC = $(CONFDIR) 101 | 102 | # Licensing files. 103 | include $(CHIBIOS)/os/license/license.mk 104 | # Startup files. 105 | include $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/mk/startup_stm32f4xx.mk 106 | # HAL-OSAL files (optional). 107 | include $(CHIBIOS)/os/hal/hal.mk 108 | include $(CHIBIOS)/os/hal/ports/STM32/STM32F4xx/platform.mk 109 | include $(CHIBIOS)/os/hal/osal/rt-nil/osal.mk 110 | # RTOS files (optional). 111 | include $(CHIBIOS)/os/rt/rt.mk 112 | include $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/port_v7m.mk 113 | # chprintf 114 | include $(CHIBIOS)/os/hal/lib/streams/streams.mk 115 | # lwIP 116 | # include $(CHIBIOS)/os/various/lwip_bindings/lwip.mk 117 | include $(CONFDIR)/ch_lwip_bindings.mk 118 | 119 | # Define linker script file here 120 | LDSCRIPT= $(STARTUPLD)/STM32F429xI.ld 121 | 122 | # C sources that can be compiled in ARM or THUMB mode depending on the global 123 | # setting. 124 | CSRC = $(ALLCSRC) \ 125 | $(BOARDSRC) \ 126 | $(CHIBIOS)/os/various/evtimer.c \ 127 | watchdog.c \ 128 | ip_link.c \ 129 | ntpd.c \ 130 | gnss.c \ 131 | gnss_parse.c \ 132 | web/web.c \ 133 | web/web_paths.c \ 134 | main.c 135 | 136 | # C++ sources that can be compiled in ARM or THUMB mode depending on the global 137 | # setting. 138 | CPPSRC = $(ALLCPPSRC) 139 | 140 | # List ASM source files here. 141 | ASMSRC = $(ALLASMSRC) 142 | 143 | # List ASM with preprocessor source files here. 144 | ASMXSRC = $(ALLXASMSRC) 145 | 146 | # Inclusion directories. 147 | INCDIR = $(CONFDIR) $(ALLINC) $(TESTINC) 148 | 149 | # Define C warning options here. 150 | CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes 151 | 152 | # Define C++ warning options here. 153 | CPPWARN = -Wall -Wextra -Wundef 154 | 155 | # 156 | # Project, target, sources and paths 157 | ############################################################################## 158 | 159 | ############################################################################## 160 | # Start of user section 161 | # 162 | 163 | # List all user C define here, like -D_DEBUG=1 164 | UDEFS = 165 | 166 | # Define ASM defines here 167 | UADEFS = 168 | 169 | # List all user directories here 170 | UINCDIR = 171 | 172 | # List the user directory to look for the libraries here 173 | ULIBDIR = 174 | 175 | # List all user libraries here 176 | ULIBS = -lm 177 | 178 | # 179 | # End of user section 180 | ############################################################################## 181 | 182 | ############################################################################## 183 | # Common rules 184 | # 185 | 186 | RULESPATH = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/mk 187 | include $(RULESPATH)/arm-none-eabi.mk 188 | include $(RULESPATH)/rules.mk 189 | 190 | # 191 | # Common rules 192 | ############################################################################## 193 | 194 | ############################################################################## 195 | # Custom rules 196 | # 197 | 198 | # 199 | # Custom rules 200 | ############################################################################## 201 | -------------------------------------------------------------------------------- /firmware/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source_dir="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)" 4 | cd "${source_dir}" 5 | 6 | COLOUR_GREEN='\033[0;32m' 7 | COLOUR_YELLOW='\033[1;33m' 8 | COLOUR_PURPLE='\033[0;35m' 9 | COLOUR_RED='\033[0;31m' 10 | COLOUR_OFF='\033[0;00m' 11 | CLEAR_LINE='\033[2K' 12 | 13 | _ERROR_="${COLOUR_RED}[ERROR]${COLOUR_OFF}" 14 | _INFO_="${COLOUR_PURPLE}[INFO]${COLOUR_OFF}" 15 | _DEBUG_="${COLOUR_YELLOW}[DEBUG]${COLOUR_OFF}" 16 | 17 | opt_verbose=false 18 | opt_flash=false 19 | opt_debug=false 20 | opt_asm=false 21 | opt_werror=false 22 | opt_sound=false 23 | opt_mthread=false 24 | 25 | hyphenjoin() { 26 | str="" 27 | while [ $# -gt 0 ] 28 | do 29 | str="$str$1-" 30 | shift 31 | done 32 | if [ ! -z "$str" ] 33 | then 34 | str="${str::-1}" 35 | fi 36 | echo "$str" 37 | } 38 | 39 | gccversion() { 40 | echo "$(arm-none-eabi-gcc -dumpversion)" 41 | } 42 | 43 | githash() { 44 | githash="$(git describe --dirty --always)" 45 | # Mark as dirty if untracked files exist 46 | if [ -z "$(echo "${githash}" | grep "dirty")" ] && [ ! -z "$(git status --porcelain | grep "^??")" ]; then 47 | githash="${githash}-dirty" 48 | fi 49 | echo $githash 50 | } 51 | 52 | githash_x32() { 53 | githash_x32="0x$(git describe --always)" 54 | # Set lowest nibble to 0xf if dirty, 0x0 otherwise. 55 | if [ ! -z "$(githash | grep "dirty")" ]; then 56 | githash_x32="${githash_x32}f" 57 | else 58 | githash_x32="${githash_x32}0" 59 | fi 60 | echo $githash_x32 61 | } 62 | 63 | #device_name="STM32F042K6" 64 | #device_ram=$((6*1024)) 65 | #device_flash=$((32*1024)) 66 | 67 | #device_name="STM32F746NG" 68 | #device_ram=$((240*1024)) # Main bank 69 | #device_flash=$((1024*1024)) 70 | 71 | device_name="STM32F429ZI" 72 | device_ram=$((112*1024)) # Main bank 73 | device_flash=$((1024*1024)) 74 | 75 | size_binary() { 76 | size=`arm-none-eabi-size -A -d $1`; 77 | 78 | size_text=`echo "$size" | grep '\.text' | grep -o -E '[0-9]+' | sed -n 1p`; 79 | size_rodata=`echo "$size" | grep '\.rodata' | grep -o -E '[0-9]+' | sed -n 1p`; 80 | size_data=`echo "$size" | grep '\.data' | grep -o -E '[0-9]+' | sed -n 1p`; 81 | size_bss=`echo "$size" | grep '\.bss' | grep -o -E '[0-9]+' | sed -n 1p`; 82 | size_heap=`echo "$size" | grep '\.heap' | grep -o -E '[0-9]+' | sed -n 1p`; 83 | 84 | size_flash=$(($size_text+$size_data+$size_rodata)); 85 | size_static=$(($size_data+$size_bss)); 86 | size_ram=$(($size_static+$size_heap)); 87 | size_fdata=$(($size_data+$size_rodata)); 88 | 89 | printf "$_INFO_ Device Resources (%s)\n" ${device_name}; 90 | printf " RAM: % 6s B / % 6s B\n" ${size_ram} ${device_ram}; 91 | printf " - Static: % 6s B\n" ${size_static}; 92 | printf " - Heap: % 6s B\n" ${size_heap}; 93 | printf " Flash: % 6s B / % 6s B (%2s%%)\n" ${size_flash} ${device_flash} $(((100*$size_flash)/$device_flash)); 94 | printf " - Code: % 6s B\n" ${size_text}; 95 | printf " - Data: % 6s B\n" ${size_fdata}; 96 | } 97 | 98 | asmdump() { 99 | arm-none-eabi-objdump -DSlx ${1} > ${1}.asm 100 | printf "$_INFO_$COLOUR_GREEN Extracted assembly$COLOUR_OFF [${1}.asm]\n" 101 | } 102 | 103 | ## Read Flags 104 | OPTIND=1 105 | while getopts ":vDFmwSM" opt; do 106 | case "$opt" in 107 | v) # Verbose 108 | opt_verbose=true 109 | ;; 110 | D) # Debug 111 | opt_debug=true 112 | ;; 113 | F) # Flash 114 | opt_flash=true 115 | ;; 116 | m) # ASM 117 | opt_asm=true 118 | ;; 119 | w) # Werror 120 | opt_werror=true 121 | ;; 122 | S) # Flash Sound 123 | opt_sound=true 124 | ;; 125 | M) # Multithread Compile 126 | opt_mthread=true 127 | ;; 128 | ?) # Illegal Option 129 | echo -e "$_ERROR_ Illegal option '$OPTARG'" 130 | exit 3 131 | ;; 132 | esac 133 | done 134 | for i in `seq 2 $OPTIND`; do shift; done 135 | 136 | githash_var=`githash` 137 | filename="$(hyphenjoin "stm32ntpd" "${githash_var}")" 138 | mkdir -p "builds/" 139 | 140 | flags="TARGET_FILENAME=\"${filename}\" GITVERSION=\"${githash_var}\" GITVERSION_X32=\"`githash_x32`\"" 141 | printf "$_INFO_ Using ARM-GCC version: `gccversion`\n"; 142 | printf "$_INFO_ Firmware version: ${githash_var}\n"; 143 | 144 | if $opt_verbose; then 145 | printf "$_INFO_ Compile Verbosely:$COLOUR_YELLOW ON $COLOUR_OFF\n"; 146 | flags+=" USE_VERBOSE_COMPILE=yes"; 147 | fi 148 | 149 | if $opt_werror; then 150 | printf "$_INFO_ Compiler treat Warnings as Errors:$COLOUR_RED ON $COLOUR_OFF\n"; 151 | flags+=" WERROR=1"; 152 | fi 153 | 154 | if $opt_mthread; then 155 | mthreads=`nproc` 156 | printf "$_INFO_ Multithread build enabled, threads: ${mthreads}$COLOUR_OFF\n"; 157 | flags+=" -j${mthreads}"; 158 | fi 159 | 160 | if [ -f web/build ]; then 161 | web/build; 162 | fi 163 | 164 | printf "$_INFO_ Compiling..\n"; 165 | make ${flags} \ 166 | && { 167 | printf "$_INFO_$COLOUR_GREEN Build successful$COLOUR_OFF [builds/${filename}.elf]\n" 168 | } || { 169 | printf "$_ERROR_$COLOUR_RED There were errors in the build process$COLOUR_OFF\n" \ 170 | && exit 1 171 | } 172 | size_binary "builds/${filename}.elf" 173 | 174 | if $opt_asm; then asmdump "builds/${filename}.elf"; fi 175 | 176 | if $opt_debug; 177 | then 178 | ./debug "builds/${filename}"; 179 | elif $opt_flash; 180 | then 181 | fflags="" 182 | if $opt_sound; then fflags+=" -S"; fi 183 | ./flash ${fflags} "builds/${filename}"; 184 | fi 185 | -------------------------------------------------------------------------------- /firmware/cfg/board.c: -------------------------------------------------------------------------------- 1 | /* 2 | ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | * This file has been automatically generated using ChibiStudio board 19 | * generator plugin. Do not edit manually. 20 | */ 21 | 22 | #include "hal.h" 23 | #include "stm32_gpio.h" 24 | 25 | /*===========================================================================*/ 26 | /* Driver local definitions. */ 27 | /*===========================================================================*/ 28 | 29 | /*===========================================================================*/ 30 | /* Driver exported variables. */ 31 | /*===========================================================================*/ 32 | 33 | /*===========================================================================*/ 34 | /* Driver local variables and types. */ 35 | /*===========================================================================*/ 36 | 37 | /** 38 | * @brief Type of STM32 GPIO port setup. 39 | */ 40 | typedef struct { 41 | uint32_t moder; 42 | uint32_t otyper; 43 | uint32_t ospeedr; 44 | uint32_t pupdr; 45 | uint32_t odr; 46 | uint32_t afrl; 47 | uint32_t afrh; 48 | } gpio_setup_t; 49 | 50 | /** 51 | * @brief Type of STM32 GPIO initialization data. 52 | */ 53 | typedef struct { 54 | #if STM32_HAS_GPIOA || defined(__DOXYGEN__) 55 | gpio_setup_t PAData; 56 | #endif 57 | #if STM32_HAS_GPIOB || defined(__DOXYGEN__) 58 | gpio_setup_t PBData; 59 | #endif 60 | #if STM32_HAS_GPIOC || defined(__DOXYGEN__) 61 | gpio_setup_t PCData; 62 | #endif 63 | #if STM32_HAS_GPIOD || defined(__DOXYGEN__) 64 | gpio_setup_t PDData; 65 | #endif 66 | #if STM32_HAS_GPIOE || defined(__DOXYGEN__) 67 | gpio_setup_t PEData; 68 | #endif 69 | #if STM32_HAS_GPIOF || defined(__DOXYGEN__) 70 | gpio_setup_t PFData; 71 | #endif 72 | #if STM32_HAS_GPIOG || defined(__DOXYGEN__) 73 | gpio_setup_t PGData; 74 | #endif 75 | #if STM32_HAS_GPIOH || defined(__DOXYGEN__) 76 | gpio_setup_t PHData; 77 | #endif 78 | #if STM32_HAS_GPIOI || defined(__DOXYGEN__) 79 | gpio_setup_t PIData; 80 | #endif 81 | #if STM32_HAS_GPIOJ || defined(__DOXYGEN__) 82 | gpio_setup_t PJData; 83 | #endif 84 | #if STM32_HAS_GPIOK || defined(__DOXYGEN__) 85 | gpio_setup_t PKData; 86 | #endif 87 | } gpio_config_t; 88 | 89 | /** 90 | * @brief STM32 GPIO static initialization data. 91 | */ 92 | static const gpio_config_t gpio_default_config = { 93 | #if STM32_HAS_GPIOA 94 | {VAL_GPIOA_MODER, VAL_GPIOA_OTYPER, VAL_GPIOA_OSPEEDR, VAL_GPIOA_PUPDR, 95 | VAL_GPIOA_ODR, VAL_GPIOA_AFRL, VAL_GPIOA_AFRH}, 96 | #endif 97 | #if STM32_HAS_GPIOB 98 | {VAL_GPIOB_MODER, VAL_GPIOB_OTYPER, VAL_GPIOB_OSPEEDR, VAL_GPIOB_PUPDR, 99 | VAL_GPIOB_ODR, VAL_GPIOB_AFRL, VAL_GPIOB_AFRH}, 100 | #endif 101 | #if STM32_HAS_GPIOC 102 | {VAL_GPIOC_MODER, VAL_GPIOC_OTYPER, VAL_GPIOC_OSPEEDR, VAL_GPIOC_PUPDR, 103 | VAL_GPIOC_ODR, VAL_GPIOC_AFRL, VAL_GPIOC_AFRH}, 104 | #endif 105 | #if STM32_HAS_GPIOD 106 | {VAL_GPIOD_MODER, VAL_GPIOD_OTYPER, VAL_GPIOD_OSPEEDR, VAL_GPIOD_PUPDR, 107 | VAL_GPIOD_ODR, VAL_GPIOD_AFRL, VAL_GPIOD_AFRH}, 108 | #endif 109 | #if STM32_HAS_GPIOE 110 | {VAL_GPIOE_MODER, VAL_GPIOE_OTYPER, VAL_GPIOE_OSPEEDR, VAL_GPIOE_PUPDR, 111 | VAL_GPIOE_ODR, VAL_GPIOE_AFRL, VAL_GPIOE_AFRH}, 112 | #endif 113 | #if STM32_HAS_GPIOF 114 | {VAL_GPIOF_MODER, VAL_GPIOF_OTYPER, VAL_GPIOF_OSPEEDR, VAL_GPIOF_PUPDR, 115 | VAL_GPIOF_ODR, VAL_GPIOF_AFRL, VAL_GPIOF_AFRH}, 116 | #endif 117 | #if STM32_HAS_GPIOG 118 | {VAL_GPIOG_MODER, VAL_GPIOG_OTYPER, VAL_GPIOG_OSPEEDR, VAL_GPIOG_PUPDR, 119 | VAL_GPIOG_ODR, VAL_GPIOG_AFRL, VAL_GPIOG_AFRH}, 120 | #endif 121 | #if STM32_HAS_GPIOH 122 | {VAL_GPIOH_MODER, VAL_GPIOH_OTYPER, VAL_GPIOH_OSPEEDR, VAL_GPIOH_PUPDR, 123 | VAL_GPIOH_ODR, VAL_GPIOH_AFRL, VAL_GPIOH_AFRH}, 124 | #endif 125 | #if STM32_HAS_GPIOI 126 | {VAL_GPIOI_MODER, VAL_GPIOI_OTYPER, VAL_GPIOI_OSPEEDR, VAL_GPIOI_PUPDR, 127 | VAL_GPIOI_ODR, VAL_GPIOI_AFRL, VAL_GPIOI_AFRH}, 128 | #endif 129 | #if STM32_HAS_GPIOJ 130 | {VAL_GPIOJ_MODER, VAL_GPIOJ_OTYPER, VAL_GPIOJ_OSPEEDR, VAL_GPIOJ_PUPDR, 131 | VAL_GPIOJ_ODR, VAL_GPIOJ_AFRL, VAL_GPIOJ_AFRH}, 132 | #endif 133 | #if STM32_HAS_GPIOK 134 | {VAL_GPIOK_MODER, VAL_GPIOK_OTYPER, VAL_GPIOK_OSPEEDR, VAL_GPIOK_PUPDR, 135 | VAL_GPIOK_ODR, VAL_GPIOK_AFRL, VAL_GPIOK_AFRH} 136 | #endif 137 | }; 138 | 139 | /*===========================================================================*/ 140 | /* Driver local functions. */ 141 | /*===========================================================================*/ 142 | 143 | static void gpio_init(stm32_gpio_t *gpiop, const gpio_setup_t *config) { 144 | 145 | gpiop->OTYPER = config->otyper; 146 | gpiop->OSPEEDR = config->ospeedr; 147 | gpiop->PUPDR = config->pupdr; 148 | gpiop->ODR = config->odr; 149 | gpiop->AFRL = config->afrl; 150 | gpiop->AFRH = config->afrh; 151 | gpiop->MODER = config->moder; 152 | } 153 | 154 | static void stm32_gpio_init(void) { 155 | 156 | /* Enabling GPIO-related clocks, the mask comes from the 157 | registry header file.*/ 158 | rccResetAHB1(STM32_GPIO_EN_MASK); 159 | rccEnableAHB1(STM32_GPIO_EN_MASK, true); 160 | 161 | /* Initializing all the defined GPIO ports.*/ 162 | #if STM32_HAS_GPIOA 163 | gpio_init(GPIOA, &gpio_default_config.PAData); 164 | #endif 165 | #if STM32_HAS_GPIOB 166 | gpio_init(GPIOB, &gpio_default_config.PBData); 167 | #endif 168 | #if STM32_HAS_GPIOC 169 | gpio_init(GPIOC, &gpio_default_config.PCData); 170 | #endif 171 | #if STM32_HAS_GPIOD 172 | gpio_init(GPIOD, &gpio_default_config.PDData); 173 | #endif 174 | #if STM32_HAS_GPIOE 175 | gpio_init(GPIOE, &gpio_default_config.PEData); 176 | #endif 177 | #if STM32_HAS_GPIOF 178 | gpio_init(GPIOF, &gpio_default_config.PFData); 179 | #endif 180 | #if STM32_HAS_GPIOG 181 | gpio_init(GPIOG, &gpio_default_config.PGData); 182 | #endif 183 | #if STM32_HAS_GPIOH 184 | gpio_init(GPIOH, &gpio_default_config.PHData); 185 | #endif 186 | #if STM32_HAS_GPIOI 187 | gpio_init(GPIOI, &gpio_default_config.PIData); 188 | #endif 189 | #if STM32_HAS_GPIOJ 190 | gpio_init(GPIOJ, &gpio_default_config.PJData); 191 | #endif 192 | #if STM32_HAS_GPIOK 193 | gpio_init(GPIOK, &gpio_default_config.PKData); 194 | #endif 195 | } 196 | 197 | /*===========================================================================*/ 198 | /* Driver interrupt handlers. */ 199 | /*===========================================================================*/ 200 | 201 | /*===========================================================================*/ 202 | /* Driver exported functions. */ 203 | /*===========================================================================*/ 204 | 205 | /** 206 | * @brief Early initialization code. 207 | * @details GPIO ports and system clocks are initialized before everything 208 | * else. 209 | */ 210 | void __early_init(void) { 211 | 212 | stm32_gpio_init(); 213 | stm32_clock_init(); 214 | } 215 | 216 | #if HAL_USE_SDC || defined(__DOXYGEN__) 217 | /** 218 | * @brief SDC card detection. 219 | */ 220 | bool sdc_lld_is_card_inserted(SDCDriver *sdcp) { 221 | 222 | (void)sdcp; 223 | /* TODO: Fill the implementation.*/ 224 | return true; 225 | } 226 | 227 | /** 228 | * @brief SDC card write protection detection. 229 | */ 230 | bool sdc_lld_is_write_protected(SDCDriver *sdcp) { 231 | 232 | (void)sdcp; 233 | /* TODO: Fill the implementation.*/ 234 | return false; 235 | } 236 | #endif /* HAL_USE_SDC */ 237 | 238 | #if HAL_USE_MMC_SPI || defined(__DOXYGEN__) 239 | /** 240 | * @brief MMC_SPI card detection. 241 | */ 242 | bool mmc_lld_is_card_inserted(MMCDriver *mmcp) { 243 | 244 | (void)mmcp; 245 | /* TODO: Fill the implementation.*/ 246 | return true; 247 | } 248 | 249 | /** 250 | * @brief MMC_SPI card write protection detection. 251 | */ 252 | bool mmc_lld_is_write_protected(MMCDriver *mmcp) { 253 | 254 | (void)mmcp; 255 | /* TODO: Fill the implementation.*/ 256 | return false; 257 | } 258 | #endif 259 | 260 | /** 261 | * @brief Board-specific initialization code. 262 | * @todo Add your board-specific code, if any. 263 | */ 264 | void boardInit(void) { 265 | 266 | } 267 | -------------------------------------------------------------------------------- /firmware/cfg/ch_lwip_bindings.mk: -------------------------------------------------------------------------------- 1 | # List of the required lwIP files. 2 | #LWIPDIR = $(CHIBIOS)/ext/lwip/src 3 | LWIPDIR = $(LWIP)/src 4 | 5 | # The various blocks of files are outlined in Filelists.mk. 6 | include $(LWIPDIR)/Filelists.mk 7 | 8 | LWBINDSRC = \ 9 | $(CHIBIOS)/os/various/lwip_bindings/lwipthread.c \ 10 | $(CHIBIOS)/os/various/lwip_bindings/arch/sys_arch.c 11 | 12 | 13 | # Add blocks of files from Filelists.mk as required for enabled options 14 | LWSRC_REQUIRED = $(COREFILES) $(CORE4FILES) $(APIFILES) $(LWBINDSRC) $(NETIFFILES) 15 | LWSRC_EXTRAS ?= $(SNTPFILES) 16 | 17 | LWINC = \ 18 | $(CHIBIOS)/os/various/lwip_bindings \ 19 | $(LWIPDIR)/include 20 | 21 | # Shared variables 22 | ALLCSRC += $(LWSRC_REQUIRED) $(LWSRC_EXTRAS) 23 | ALLINC += $(LWINC) \ 24 | $(CHIBIOS)/os/various 25 | -------------------------------------------------------------------------------- /firmware/cfg/chconf.h: -------------------------------------------------------------------------------- 1 | /* 2 | ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /** 18 | * @file rt/templates/chconf.h 19 | * @brief Configuration file template. 20 | * @details A copy of this file must be placed in each project directory, it 21 | * contains the application specific kernel settings. 22 | * 23 | * @addtogroup config 24 | * @details Kernel related settings and hooks. 25 | * @{ 26 | */ 27 | 28 | #ifndef CHCONF_H 29 | #define CHCONF_H 30 | 31 | #define _CHIBIOS_RT_CONF_ 32 | #define _CHIBIOS_RT_CONF_VER_6_1_ 33 | 34 | /*===========================================================================*/ 35 | /* Phil's Additions */ 36 | 37 | #if defined(CHPRINTF_USE_FLOAT) 38 | #undef CHPRINTF_USE_FLOAT 39 | #endif 40 | #define CHPRINTF_USE_FLOAT TRUE 41 | 42 | 43 | /*===========================================================================*/ 44 | /** 45 | * @name System timers settings 46 | * @{ 47 | */ 48 | /*===========================================================================*/ 49 | 50 | /** 51 | * @brief System time counter resolution. 52 | * @note Allowed values are 16 or 32 bits. 53 | */ 54 | #if !defined(CH_CFG_ST_RESOLUTION) 55 | #define CH_CFG_ST_RESOLUTION 32 56 | #endif 57 | 58 | /** 59 | * @brief System tick frequency. 60 | * @details Frequency of the system timer that drives the system ticks. This 61 | * setting also defines the system tick time unit. 62 | */ 63 | #if !defined(CH_CFG_ST_FREQUENCY) 64 | #define CH_CFG_ST_FREQUENCY 10000 65 | #endif 66 | 67 | /** 68 | * @brief Time intervals data size. 69 | * @note Allowed values are 16, 32 or 64 bits. 70 | */ 71 | #if !defined(CH_CFG_INTERVALS_SIZE) 72 | #define CH_CFG_INTERVALS_SIZE 32 73 | #endif 74 | 75 | /** 76 | * @brief Time types data size. 77 | * @note Allowed values are 16 or 32 bits. 78 | */ 79 | #if !defined(CH_CFG_TIME_TYPES_SIZE) 80 | #define CH_CFG_TIME_TYPES_SIZE 32 81 | #endif 82 | 83 | /** 84 | * @brief Time delta constant for the tick-less mode. 85 | * @note If this value is zero then the system uses the classic 86 | * periodic tick. This value represents the minimum number 87 | * of ticks that is safe to specify in a timeout directive. 88 | * The value one is not valid, timeouts are rounded up to 89 | * this value. 90 | */ 91 | #if !defined(CH_CFG_ST_TIMEDELTA) 92 | #define CH_CFG_ST_TIMEDELTA 2 93 | #endif 94 | 95 | /** @} */ 96 | 97 | /*===========================================================================*/ 98 | /** 99 | * @name Kernel parameters and options 100 | * @{ 101 | */ 102 | /*===========================================================================*/ 103 | 104 | /** 105 | * @brief Round robin interval. 106 | * @details This constant is the number of system ticks allowed for the 107 | * threads before preemption occurs. Setting this value to zero 108 | * disables the preemption for threads with equal priority and the 109 | * round robin becomes cooperative. Note that higher priority 110 | * threads can still preempt, the kernel is always preemptive. 111 | * @note Disabling the round robin preemption makes the kernel more compact 112 | * and generally faster. 113 | * @note The round robin preemption is not supported in tickless mode and 114 | * must be set to zero in that case. 115 | */ 116 | #if !defined(CH_CFG_TIME_QUANTUM) 117 | #define CH_CFG_TIME_QUANTUM 0 118 | #endif 119 | 120 | /** 121 | * @brief Idle thread automatic spawn suppression. 122 | * @details When this option is activated the function @p chSysInit() 123 | * does not spawn the idle thread. The application @p main() 124 | * function becomes the idle thread and must implement an 125 | * infinite loop. 126 | */ 127 | #if !defined(CH_CFG_NO_IDLE_THREAD) 128 | #define CH_CFG_NO_IDLE_THREAD FALSE 129 | #endif 130 | 131 | /** @} */ 132 | 133 | /*===========================================================================*/ 134 | /** 135 | * @name Performance options 136 | * @{ 137 | */ 138 | /*===========================================================================*/ 139 | 140 | /** 141 | * @brief OS optimization. 142 | * @details If enabled then time efficient rather than space efficient code 143 | * is used when two possible implementations exist. 144 | * 145 | * @note This is not related to the compiler optimization options. 146 | * @note The default is @p TRUE. 147 | */ 148 | #if !defined(CH_CFG_OPTIMIZE_SPEED) 149 | #define CH_CFG_OPTIMIZE_SPEED TRUE 150 | #endif 151 | 152 | /** @} */ 153 | 154 | /*===========================================================================*/ 155 | /** 156 | * @name Subsystem options 157 | * @{ 158 | */ 159 | /*===========================================================================*/ 160 | 161 | /** 162 | * @brief Time Measurement APIs. 163 | * @details If enabled then the time measurement APIs are included in 164 | * the kernel. 165 | * 166 | * @note The default is @p TRUE. 167 | */ 168 | #if !defined(CH_CFG_USE_TM) 169 | #define CH_CFG_USE_TM TRUE 170 | #endif 171 | 172 | /** 173 | * @brief Threads registry APIs. 174 | * @details If enabled then the registry APIs are included in the kernel. 175 | * 176 | * @note The default is @p TRUE. 177 | */ 178 | #if !defined(CH_CFG_USE_REGISTRY) 179 | #define CH_CFG_USE_REGISTRY TRUE 180 | #endif 181 | 182 | /** 183 | * @brief Threads synchronization APIs. 184 | * @details If enabled then the @p chThdWait() function is included in 185 | * the kernel. 186 | * 187 | * @note The default is @p TRUE. 188 | */ 189 | #if !defined(CH_CFG_USE_WAITEXIT) 190 | #define CH_CFG_USE_WAITEXIT TRUE 191 | #endif 192 | 193 | /** 194 | * @brief Semaphores APIs. 195 | * @details If enabled then the Semaphores APIs are included in the kernel. 196 | * 197 | * @note The default is @p TRUE. 198 | */ 199 | #if !defined(CH_CFG_USE_SEMAPHORES) 200 | #define CH_CFG_USE_SEMAPHORES TRUE 201 | #endif 202 | 203 | /** 204 | * @brief Semaphores queuing mode. 205 | * @details If enabled then the threads are enqueued on semaphores by 206 | * priority rather than in FIFO order. 207 | * 208 | * @note The default is @p FALSE. Enable this if you have special 209 | * requirements. 210 | * @note Requires @p CH_CFG_USE_SEMAPHORES. 211 | */ 212 | #if !defined(CH_CFG_USE_SEMAPHORES_PRIORITY) 213 | #define CH_CFG_USE_SEMAPHORES_PRIORITY FALSE 214 | #endif 215 | 216 | /** 217 | * @brief Mutexes APIs. 218 | * @details If enabled then the mutexes APIs are included in the kernel. 219 | * 220 | * @note The default is @p TRUE. 221 | */ 222 | #if !defined(CH_CFG_USE_MUTEXES) 223 | #define CH_CFG_USE_MUTEXES TRUE 224 | #endif 225 | 226 | /** 227 | * @brief Enables recursive behavior on mutexes. 228 | * @note Recursive mutexes are heavier and have an increased 229 | * memory footprint. 230 | * 231 | * @note The default is @p FALSE. 232 | * @note Requires @p CH_CFG_USE_MUTEXES. 233 | */ 234 | #if !defined(CH_CFG_USE_MUTEXES_RECURSIVE) 235 | #define CH_CFG_USE_MUTEXES_RECURSIVE FALSE 236 | #endif 237 | 238 | /** 239 | * @brief Conditional Variables APIs. 240 | * @details If enabled then the conditional variables APIs are included 241 | * in the kernel. 242 | * 243 | * @note The default is @p TRUE. 244 | * @note Requires @p CH_CFG_USE_MUTEXES. 245 | */ 246 | #if !defined(CH_CFG_USE_CONDVARS) 247 | #define CH_CFG_USE_CONDVARS TRUE 248 | #endif 249 | 250 | /** 251 | * @brief Conditional Variables APIs with timeout. 252 | * @details If enabled then the conditional variables APIs with timeout 253 | * specification are included in the kernel. 254 | * 255 | * @note The default is @p TRUE. 256 | * @note Requires @p CH_CFG_USE_CONDVARS. 257 | */ 258 | #if !defined(CH_CFG_USE_CONDVARS_TIMEOUT) 259 | #define CH_CFG_USE_CONDVARS_TIMEOUT TRUE 260 | #endif 261 | 262 | /** 263 | * @brief Events Flags APIs. 264 | * @details If enabled then the event flags APIs are included in the kernel. 265 | * 266 | * @note The default is @p TRUE. 267 | */ 268 | #if !defined(CH_CFG_USE_EVENTS) 269 | #define CH_CFG_USE_EVENTS TRUE 270 | #endif 271 | 272 | /** 273 | * @brief Events Flags APIs with timeout. 274 | * @details If enabled then the events APIs with timeout specification 275 | * are included in the kernel. 276 | * 277 | * @note The default is @p TRUE. 278 | * @note Requires @p CH_CFG_USE_EVENTS. 279 | */ 280 | #if !defined(CH_CFG_USE_EVENTS_TIMEOUT) 281 | #define CH_CFG_USE_EVENTS_TIMEOUT TRUE 282 | #endif 283 | 284 | /** 285 | * @brief Synchronous Messages APIs. 286 | * @details If enabled then the synchronous messages APIs are included 287 | * in the kernel. 288 | * 289 | * @note The default is @p TRUE. 290 | */ 291 | #if !defined(CH_CFG_USE_MESSAGES) 292 | #define CH_CFG_USE_MESSAGES TRUE 293 | #endif 294 | 295 | /** 296 | * @brief Synchronous Messages queuing mode. 297 | * @details If enabled then messages are served by priority rather than in 298 | * FIFO order. 299 | * 300 | * @note The default is @p FALSE. Enable this if you have special 301 | * requirements. 302 | * @note Requires @p CH_CFG_USE_MESSAGES. 303 | */ 304 | #if !defined(CH_CFG_USE_MESSAGES_PRIORITY) 305 | #define CH_CFG_USE_MESSAGES_PRIORITY FALSE 306 | #endif 307 | 308 | /** 309 | * @brief Dynamic Threads APIs. 310 | * @details If enabled then the dynamic threads creation APIs are included 311 | * in the kernel. 312 | * 313 | * @note The default is @p TRUE. 314 | * @note Requires @p CH_CFG_USE_WAITEXIT. 315 | * @note Requires @p CH_CFG_USE_HEAP and/or @p CH_CFG_USE_MEMPOOLS. 316 | */ 317 | #if !defined(CH_CFG_USE_DYNAMIC) 318 | #define CH_CFG_USE_DYNAMIC TRUE 319 | #endif 320 | 321 | /** @} */ 322 | 323 | /*===========================================================================*/ 324 | /** 325 | * @name OSLIB options 326 | * @{ 327 | */ 328 | /*===========================================================================*/ 329 | 330 | /** 331 | * @brief Mailboxes APIs. 332 | * @details If enabled then the asynchronous messages (mailboxes) APIs are 333 | * included in the kernel. 334 | * 335 | * @note The default is @p TRUE. 336 | * @note Requires @p CH_CFG_USE_SEMAPHORES. 337 | */ 338 | #if !defined(CH_CFG_USE_MAILBOXES) 339 | #define CH_CFG_USE_MAILBOXES TRUE 340 | #endif 341 | 342 | /** 343 | * @brief Core Memory Manager APIs. 344 | * @details If enabled then the core memory manager APIs are included 345 | * in the kernel. 346 | * 347 | * @note The default is @p TRUE. 348 | */ 349 | #if !defined(CH_CFG_USE_MEMCORE) 350 | #define CH_CFG_USE_MEMCORE TRUE 351 | #endif 352 | 353 | /** 354 | * @brief Managed RAM size. 355 | * @details Size of the RAM area to be managed by the OS. If set to zero 356 | * then the whole available RAM is used. The core memory is made 357 | * available to the heap allocator and/or can be used directly through 358 | * the simplified core memory allocator. 359 | * 360 | * @note In order to let the OS manage the whole RAM the linker script must 361 | * provide the @p __heap_base__ and @p __heap_end__ symbols. 362 | * @note Requires @p CH_CFG_USE_MEMCORE. 363 | */ 364 | #if !defined(CH_CFG_MEMCORE_SIZE) 365 | #define CH_CFG_MEMCORE_SIZE 0 366 | #endif 367 | 368 | /** 369 | * @brief Heap Allocator APIs. 370 | * @details If enabled then the memory heap allocator APIs are included 371 | * in the kernel. 372 | * 373 | * @note The default is @p TRUE. 374 | * @note Requires @p CH_CFG_USE_MEMCORE and either @p CH_CFG_USE_MUTEXES or 375 | * @p CH_CFG_USE_SEMAPHORES. 376 | * @note Mutexes are recommended. 377 | */ 378 | #if !defined(CH_CFG_USE_HEAP) 379 | #define CH_CFG_USE_HEAP TRUE 380 | #endif 381 | 382 | /** 383 | * @brief Memory Pools Allocator APIs. 384 | * @details If enabled then the memory pools allocator APIs are included 385 | * in the kernel. 386 | * 387 | * @note The default is @p TRUE. 388 | */ 389 | #if !defined(CH_CFG_USE_MEMPOOLS) 390 | #define CH_CFG_USE_MEMPOOLS TRUE 391 | #endif 392 | 393 | /** 394 | * @brief Objects FIFOs APIs. 395 | * @details If enabled then the objects FIFOs APIs are included 396 | * in the kernel. 397 | * 398 | * @note The default is @p TRUE. 399 | */ 400 | #if !defined(CH_CFG_USE_OBJ_FIFOS) 401 | #define CH_CFG_USE_OBJ_FIFOS TRUE 402 | #endif 403 | 404 | /** 405 | * @brief Pipes APIs. 406 | * @details If enabled then the pipes APIs are included 407 | * in the kernel. 408 | * 409 | * @note The default is @p TRUE. 410 | */ 411 | #if !defined(CH_CFG_USE_PIPES) 412 | #define CH_CFG_USE_PIPES TRUE 413 | #endif 414 | 415 | /** 416 | * @brief Objects Caches APIs. 417 | * @details If enabled then the objects caches APIs are included 418 | * in the kernel. 419 | * 420 | * @note The default is @p TRUE. 421 | */ 422 | #if !defined(CH_CFG_USE_OBJ_CACHES) 423 | #define CH_CFG_USE_OBJ_CACHES TRUE 424 | #endif 425 | 426 | /** 427 | * @brief Delegate threads APIs. 428 | * @details If enabled then the delegate threads APIs are included 429 | * in the kernel. 430 | * 431 | * @note The default is @p TRUE. 432 | */ 433 | #if !defined(CH_CFG_USE_DELEGATES) 434 | #define CH_CFG_USE_DELEGATES TRUE 435 | #endif 436 | 437 | /** 438 | * @brief Jobs Queues APIs. 439 | * @details If enabled then the jobs queues APIs are included 440 | * in the kernel. 441 | * 442 | * @note The default is @p TRUE. 443 | */ 444 | #if !defined(CH_CFG_USE_JOBS) 445 | #define CH_CFG_USE_JOBS TRUE 446 | #endif 447 | 448 | /** @} */ 449 | 450 | /*===========================================================================*/ 451 | /** 452 | * @name Objects factory options 453 | * @{ 454 | */ 455 | /*===========================================================================*/ 456 | 457 | /** 458 | * @brief Objects Factory APIs. 459 | * @details If enabled then the objects factory APIs are included in the 460 | * kernel. 461 | * 462 | * @note The default is @p FALSE. 463 | */ 464 | #if !defined(CH_CFG_USE_FACTORY) 465 | #define CH_CFG_USE_FACTORY TRUE 466 | #endif 467 | 468 | /** 469 | * @brief Maximum length for object names. 470 | * @details If the specified length is zero then the name is stored by 471 | * pointer but this could have unintended side effects. 472 | */ 473 | #if !defined(CH_CFG_FACTORY_MAX_NAMES_LENGTH) 474 | #define CH_CFG_FACTORY_MAX_NAMES_LENGTH 8 475 | #endif 476 | 477 | /** 478 | * @brief Enables the registry of generic objects. 479 | */ 480 | #if !defined(CH_CFG_FACTORY_OBJECTS_REGISTRY) 481 | #define CH_CFG_FACTORY_OBJECTS_REGISTRY TRUE 482 | #endif 483 | 484 | /** 485 | * @brief Enables factory for generic buffers. 486 | */ 487 | #if !defined(CH_CFG_FACTORY_GENERIC_BUFFERS) 488 | #define CH_CFG_FACTORY_GENERIC_BUFFERS TRUE 489 | #endif 490 | 491 | /** 492 | * @brief Enables factory for semaphores. 493 | */ 494 | #if !defined(CH_CFG_FACTORY_SEMAPHORES) 495 | #define CH_CFG_FACTORY_SEMAPHORES TRUE 496 | #endif 497 | 498 | /** 499 | * @brief Enables factory for mailboxes. 500 | */ 501 | #if !defined(CH_CFG_FACTORY_MAILBOXES) 502 | #define CH_CFG_FACTORY_MAILBOXES TRUE 503 | #endif 504 | 505 | /** 506 | * @brief Enables factory for objects FIFOs. 507 | */ 508 | #if !defined(CH_CFG_FACTORY_OBJ_FIFOS) 509 | #define CH_CFG_FACTORY_OBJ_FIFOS TRUE 510 | #endif 511 | 512 | /** 513 | * @brief Enables factory for Pipes. 514 | */ 515 | #if !defined(CH_CFG_FACTORY_PIPES) || defined(__DOXYGEN__) 516 | #define CH_CFG_FACTORY_PIPES TRUE 517 | #endif 518 | 519 | /** @} */ 520 | 521 | /*===========================================================================*/ 522 | /** 523 | * @name Debug options 524 | * @{ 525 | */ 526 | /*===========================================================================*/ 527 | 528 | /** 529 | * @brief Debug option, kernel statistics. 530 | * 531 | * @note The default is @p FALSE. 532 | */ 533 | #if !defined(CH_DBG_STATISTICS) 534 | #define CH_DBG_STATISTICS FALSE 535 | #endif 536 | 537 | /** 538 | * @brief Debug option, system state check. 539 | * @details If enabled the correct call protocol for system APIs is checked 540 | * at runtime. 541 | * 542 | * @note The default is @p FALSE. 543 | */ 544 | #if !defined(CH_DBG_SYSTEM_STATE_CHECK) 545 | #define CH_DBG_SYSTEM_STATE_CHECK FALSE 546 | #endif 547 | 548 | /** 549 | * @brief Debug option, parameters checks. 550 | * @details If enabled then the checks on the API functions input 551 | * parameters are activated. 552 | * 553 | * @note The default is @p FALSE. 554 | */ 555 | #if !defined(CH_DBG_ENABLE_CHECKS) 556 | #define CH_DBG_ENABLE_CHECKS FALSE 557 | #endif 558 | 559 | /** 560 | * @brief Debug option, consistency checks. 561 | * @details If enabled then all the assertions in the kernel code are 562 | * activated. This includes consistency checks inside the kernel, 563 | * runtime anomalies and port-defined checks. 564 | * 565 | * @note The default is @p FALSE. 566 | */ 567 | #if !defined(CH_DBG_ENABLE_ASSERTS) 568 | #define CH_DBG_ENABLE_ASSERTS FALSE 569 | #endif 570 | 571 | /** 572 | * @brief Debug option, trace buffer. 573 | * @details If enabled then the trace buffer is activated. 574 | * 575 | * @note The default is @p CH_DBG_TRACE_MASK_DISABLED. 576 | */ 577 | #if !defined(CH_DBG_TRACE_MASK) 578 | #define CH_DBG_TRACE_MASK CH_DBG_TRACE_MASK_DISABLED 579 | #endif 580 | 581 | /** 582 | * @brief Trace buffer entries. 583 | * @note The trace buffer is only allocated if @p CH_DBG_TRACE_MASK is 584 | * different from @p CH_DBG_TRACE_MASK_DISABLED. 585 | */ 586 | #if !defined(CH_DBG_TRACE_BUFFER_SIZE) 587 | #define CH_DBG_TRACE_BUFFER_SIZE 128 588 | #endif 589 | 590 | /** 591 | * @brief Debug option, stack checks. 592 | * @details If enabled then a runtime stack check is performed. 593 | * 594 | * @note The default is @p FALSE. 595 | * @note The stack check is performed in a architecture/port dependent way. 596 | * It may not be implemented or some ports. 597 | * @note The default failure mode is to halt the system with the global 598 | * @p panic_msg variable set to @p NULL. 599 | */ 600 | #if !defined(CH_DBG_ENABLE_STACK_CHECK) 601 | #define CH_DBG_ENABLE_STACK_CHECK FALSE 602 | #endif 603 | 604 | /** 605 | * @brief Debug option, stacks initialization. 606 | * @details If enabled then the threads working area is filled with a byte 607 | * value when a thread is created. This can be useful for the 608 | * runtime measurement of the used stack. 609 | * 610 | * @note The default is @p FALSE. 611 | */ 612 | #if !defined(CH_DBG_FILL_THREADS) 613 | #define CH_DBG_FILL_THREADS FALSE 614 | #endif 615 | 616 | /** 617 | * @brief Debug option, threads profiling. 618 | * @details If enabled then a field is added to the @p thread_t structure that 619 | * counts the system ticks occurred while executing the thread. 620 | * 621 | * @note The default is @p FALSE. 622 | * @note This debug option is not currently compatible with the 623 | * tickless mode. 624 | */ 625 | #if !defined(CH_DBG_THREADS_PROFILING) 626 | #define CH_DBG_THREADS_PROFILING FALSE 627 | #endif 628 | 629 | /** @} */ 630 | 631 | /*===========================================================================*/ 632 | /** 633 | * @name Kernel hooks 634 | * @{ 635 | */ 636 | /*===========================================================================*/ 637 | 638 | /** 639 | * @brief System structure extension. 640 | * @details User fields added to the end of the @p ch_system_t structure. 641 | */ 642 | #define CH_CFG_SYSTEM_EXTRA_FIELDS \ 643 | /* Add threads custom fields here.*/ 644 | 645 | /** 646 | * @brief System initialization hook. 647 | * @details User initialization code added to the @p chSysInit() function 648 | * just before interrupts are enabled globally. 649 | */ 650 | #define CH_CFG_SYSTEM_INIT_HOOK() { \ 651 | /* Add threads initialization code here.*/ \ 652 | } 653 | 654 | /** 655 | * @brief Threads descriptor structure extension. 656 | * @details User fields added to the end of the @p thread_t structure. 657 | */ 658 | #define CH_CFG_THREAD_EXTRA_FIELDS \ 659 | /* Add threads custom fields here.*/ 660 | 661 | /** 662 | * @brief Threads initialization hook. 663 | * @details User initialization code added to the @p _thread_init() function. 664 | * 665 | * @note It is invoked from within @p _thread_init() and implicitly from all 666 | * the threads creation APIs. 667 | */ 668 | #define CH_CFG_THREAD_INIT_HOOK(tp) { \ 669 | /* Add threads initialization code here.*/ \ 670 | } 671 | 672 | /** 673 | * @brief Threads finalization hook. 674 | * @details User finalization code added to the @p chThdExit() API. 675 | */ 676 | #define CH_CFG_THREAD_EXIT_HOOK(tp) { \ 677 | /* Add threads finalization code here.*/ \ 678 | } 679 | 680 | /** 681 | * @brief Context switch hook. 682 | * @details This hook is invoked just before switching between threads. 683 | */ 684 | #define CH_CFG_CONTEXT_SWITCH_HOOK(ntp, otp) { \ 685 | /* Context switch code here.*/ \ 686 | } 687 | 688 | /** 689 | * @brief ISR enter hook. 690 | */ 691 | #define CH_CFG_IRQ_PROLOGUE_HOOK() { \ 692 | /* IRQ prologue code here.*/ \ 693 | } 694 | 695 | /** 696 | * @brief ISR exit hook. 697 | */ 698 | #define CH_CFG_IRQ_EPILOGUE_HOOK() { \ 699 | /* IRQ epilogue code here.*/ \ 700 | } 701 | 702 | /** 703 | * @brief Idle thread enter hook. 704 | * @note This hook is invoked within a critical zone, no OS functions 705 | * should be invoked from here. 706 | * @note This macro can be used to activate a power saving mode. 707 | */ 708 | #define CH_CFG_IDLE_ENTER_HOOK() { \ 709 | /* Idle-enter code here.*/ \ 710 | } 711 | 712 | /** 713 | * @brief Idle thread leave hook. 714 | * @note This hook is invoked within a critical zone, no OS functions 715 | * should be invoked from here. 716 | * @note This macro can be used to deactivate a power saving mode. 717 | */ 718 | #define CH_CFG_IDLE_LEAVE_HOOK() { \ 719 | /* Idle-leave code here.*/ \ 720 | } 721 | 722 | /** 723 | * @brief Idle Loop hook. 724 | * @details This hook is continuously invoked by the idle thread loop. 725 | */ 726 | #define CH_CFG_IDLE_LOOP_HOOK() { \ 727 | /* Idle loop code here.*/ \ 728 | } 729 | 730 | /** 731 | * @brief System tick event hook. 732 | * @details This hook is invoked in the system tick handler immediately 733 | * after processing the virtual timers queue. 734 | */ 735 | #define CH_CFG_SYSTEM_TICK_HOOK() { \ 736 | /* System tick event code here.*/ \ 737 | } 738 | 739 | /** 740 | * @brief System halt hook. 741 | * @details This hook is invoked in case to a system halting error before 742 | * the system is halted. 743 | */ 744 | #define CH_CFG_SYSTEM_HALT_HOOK(reason) { \ 745 | /* System halt code here.*/ \ 746 | } 747 | 748 | /** 749 | * @brief Trace hook. 750 | * @details This hook is invoked each time a new record is written in the 751 | * trace buffer. 752 | */ 753 | #define CH_CFG_TRACE_HOOK(tep) { \ 754 | /* Trace code here.*/ \ 755 | } 756 | 757 | /** @} */ 758 | 759 | /*===========================================================================*/ 760 | /* Port-specific settings (override port settings defaulted in chcore.h). */ 761 | /*===========================================================================*/ 762 | 763 | #endif /* CHCONF_H */ 764 | 765 | /** @} */ 766 | -------------------------------------------------------------------------------- /firmware/cfg/halconf.h: -------------------------------------------------------------------------------- 1 | /* 2 | ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /** 18 | * @file templates/halconf.h 19 | * @brief HAL configuration header. 20 | * @details HAL configuration file, this file allows to enable or disable the 21 | * various device drivers from your application. You may also use 22 | * this file in order to override the device drivers default settings. 23 | * 24 | * @addtogroup HAL_CONF 25 | * @{ 26 | */ 27 | 28 | #ifndef HALCONF_H 29 | #define HALCONF_H 30 | 31 | #define _CHIBIOS_HAL_CONF_ 32 | #define _CHIBIOS_HAL_CONF_VER_7_1_ 33 | 34 | #include "mcuconf.h" 35 | 36 | /** 37 | * @brief Enables the PAL subsystem. 38 | */ 39 | #if !defined(HAL_USE_PAL) || defined(__DOXYGEN__) 40 | #define HAL_USE_PAL TRUE 41 | #endif 42 | 43 | /** 44 | * @brief Enables the ADC subsystem. 45 | */ 46 | #if !defined(HAL_USE_ADC) || defined(__DOXYGEN__) 47 | #define HAL_USE_ADC FALSE 48 | #endif 49 | 50 | /** 51 | * @brief Enables the CAN subsystem. 52 | */ 53 | #if !defined(HAL_USE_CAN) || defined(__DOXYGEN__) 54 | #define HAL_USE_CAN FALSE 55 | #endif 56 | 57 | /** 58 | * @brief Enables the cryptographic subsystem. 59 | */ 60 | #if !defined(HAL_USE_CRY) || defined(__DOXYGEN__) 61 | #define HAL_USE_CRY FALSE 62 | #endif 63 | 64 | /** 65 | * @brief Enables the DAC subsystem. 66 | */ 67 | #if !defined(HAL_USE_DAC) || defined(__DOXYGEN__) 68 | #define HAL_USE_DAC FALSE 69 | #endif 70 | 71 | /** 72 | * @brief Enables the EFlash subsystem. 73 | */ 74 | #if !defined(HAL_USE_EFL) || defined(__DOXYGEN__) 75 | #define HAL_USE_EFL FALSE 76 | #endif 77 | 78 | /** 79 | * @brief Enables the GPT subsystem. 80 | */ 81 | #if !defined(HAL_USE_GPT) || defined(__DOXYGEN__) 82 | #define HAL_USE_GPT FALSE 83 | #endif 84 | 85 | /** 86 | * @brief Enables the I2C subsystem. 87 | */ 88 | #if !defined(HAL_USE_I2C) || defined(__DOXYGEN__) 89 | #define HAL_USE_I2C FALSE 90 | #endif 91 | 92 | /** 93 | * @brief Enables the I2S subsystem. 94 | */ 95 | #if !defined(HAL_USE_I2S) || defined(__DOXYGEN__) 96 | #define HAL_USE_I2S FALSE 97 | #endif 98 | 99 | /** 100 | * @brief Enables the ICU subsystem. 101 | */ 102 | #if !defined(HAL_USE_ICU) || defined(__DOXYGEN__) 103 | #define HAL_USE_ICU FALSE 104 | #endif 105 | 106 | /** 107 | * @brief Enables the MAC subsystem. 108 | */ 109 | #if !defined(HAL_USE_MAC) || defined(__DOXYGEN__) 110 | #define HAL_USE_MAC TRUE 111 | #endif 112 | 113 | /** 114 | * @brief Enables the MMC_SPI subsystem. 115 | */ 116 | #if !defined(HAL_USE_MMC_SPI) || defined(__DOXYGEN__) 117 | #define HAL_USE_MMC_SPI FALSE 118 | #endif 119 | 120 | /** 121 | * @brief Enables the PWM subsystem. 122 | */ 123 | #if !defined(HAL_USE_PWM) || defined(__DOXYGEN__) 124 | #define HAL_USE_PWM FALSE 125 | #endif 126 | 127 | /** 128 | * @brief Enables the RTC subsystem. 129 | */ 130 | #if !defined(HAL_USE_RTC) || defined(__DOXYGEN__) 131 | #define HAL_USE_RTC TRUE 132 | #endif 133 | 134 | /** 135 | * @brief Enables the SDC subsystem. 136 | */ 137 | #if !defined(HAL_USE_SDC) || defined(__DOXYGEN__) 138 | #define HAL_USE_SDC FALSE 139 | #endif 140 | 141 | /** 142 | * @brief Enables the SERIAL subsystem. 143 | */ 144 | #if !defined(HAL_USE_SERIAL) || defined(__DOXYGEN__) 145 | #define HAL_USE_SERIAL TRUE 146 | #endif 147 | 148 | /** 149 | * @brief Enables the SERIAL over USB subsystem. 150 | */ 151 | #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) 152 | #define HAL_USE_SERIAL_USB FALSE 153 | #endif 154 | 155 | /** 156 | * @brief Enables the SIO subsystem. 157 | */ 158 | #if !defined(HAL_USE_SIO) || defined(__DOXYGEN__) 159 | #define HAL_USE_SIO FALSE 160 | #endif 161 | 162 | /** 163 | * @brief Enables the SPI subsystem. 164 | */ 165 | #if !defined(HAL_USE_SPI) || defined(__DOXYGEN__) 166 | #define HAL_USE_SPI FALSE 167 | #endif 168 | 169 | /** 170 | * @brief Enables the TRNG subsystem. 171 | */ 172 | #if !defined(HAL_USE_TRNG) || defined(__DOXYGEN__) 173 | #define HAL_USE_TRNG FALSE 174 | #endif 175 | 176 | /** 177 | * @brief Enables the UART subsystem. 178 | */ 179 | #if !defined(HAL_USE_UART) || defined(__DOXYGEN__) 180 | #define HAL_USE_UART FALSE 181 | #endif 182 | 183 | /** 184 | * @brief Enables the USB subsystem. 185 | */ 186 | #if !defined(HAL_USE_USB) || defined(__DOXYGEN__) 187 | #define HAL_USE_USB FALSE 188 | #endif 189 | 190 | /** 191 | * @brief Enables the WDG subsystem. 192 | */ 193 | #if !defined(HAL_USE_WDG) || defined(__DOXYGEN__) 194 | #define HAL_USE_WDG TRUE 195 | #endif 196 | 197 | /** 198 | * @brief Enables the WSPI subsystem. 199 | */ 200 | #if !defined(HAL_USE_WSPI) || defined(__DOXYGEN__) 201 | #define HAL_USE_WSPI FALSE 202 | #endif 203 | 204 | /*===========================================================================*/ 205 | /* PAL driver related settings. */ 206 | /*===========================================================================*/ 207 | 208 | /** 209 | * @brief Enables synchronous APIs. 210 | * @note Disabling this option saves both code and data space. 211 | */ 212 | #if !defined(PAL_USE_CALLBACKS) || defined(__DOXYGEN__) 213 | #define PAL_USE_CALLBACKS TRUE 214 | #endif 215 | 216 | /** 217 | * @brief Enables synchronous APIs. 218 | * @note Disabling this option saves both code and data space. 219 | */ 220 | #if !defined(PAL_USE_WAIT) || defined(__DOXYGEN__) 221 | #define PAL_USE_WAIT FALSE 222 | #endif 223 | 224 | /*===========================================================================*/ 225 | /* ADC driver related settings. */ 226 | /*===========================================================================*/ 227 | 228 | /** 229 | * @brief Enables synchronous APIs. 230 | * @note Disabling this option saves both code and data space. 231 | */ 232 | #if !defined(ADC_USE_WAIT) || defined(__DOXYGEN__) 233 | #define ADC_USE_WAIT TRUE 234 | #endif 235 | 236 | /** 237 | * @brief Enables the @p adcAcquireBus() and @p adcReleaseBus() APIs. 238 | * @note Disabling this option saves both code and data space. 239 | */ 240 | #if !defined(ADC_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 241 | #define ADC_USE_MUTUAL_EXCLUSION TRUE 242 | #endif 243 | 244 | /*===========================================================================*/ 245 | /* CAN driver related settings. */ 246 | /*===========================================================================*/ 247 | 248 | /** 249 | * @brief Sleep mode related APIs inclusion switch. 250 | */ 251 | #if !defined(CAN_USE_SLEEP_MODE) || defined(__DOXYGEN__) 252 | #define CAN_USE_SLEEP_MODE TRUE 253 | #endif 254 | 255 | /** 256 | * @brief Enforces the driver to use direct callbacks rather than OSAL events. 257 | */ 258 | #if !defined(CAN_ENFORCE_USE_CALLBACKS) || defined(__DOXYGEN__) 259 | #define CAN_ENFORCE_USE_CALLBACKS FALSE 260 | #endif 261 | 262 | /*===========================================================================*/ 263 | /* CRY driver related settings. */ 264 | /*===========================================================================*/ 265 | 266 | /** 267 | * @brief Enables the SW fall-back of the cryptographic driver. 268 | * @details When enabled, this option, activates a fall-back software 269 | * implementation for algorithms not supported by the underlying 270 | * hardware. 271 | * @note Fall-back implementations may not be present for all algorithms. 272 | */ 273 | #if !defined(HAL_CRY_USE_FALLBACK) || defined(__DOXYGEN__) 274 | #define HAL_CRY_USE_FALLBACK FALSE 275 | #endif 276 | 277 | /** 278 | * @brief Makes the driver forcibly use the fall-back implementations. 279 | */ 280 | #if !defined(HAL_CRY_ENFORCE_FALLBACK) || defined(__DOXYGEN__) 281 | #define HAL_CRY_ENFORCE_FALLBACK FALSE 282 | #endif 283 | 284 | /*===========================================================================*/ 285 | /* DAC driver related settings. */ 286 | /*===========================================================================*/ 287 | 288 | /** 289 | * @brief Enables synchronous APIs. 290 | * @note Disabling this option saves both code and data space. 291 | */ 292 | #if !defined(DAC_USE_WAIT) || defined(__DOXYGEN__) 293 | #define DAC_USE_WAIT TRUE 294 | #endif 295 | 296 | /** 297 | * @brief Enables the @p dacAcquireBus() and @p dacReleaseBus() APIs. 298 | * @note Disabling this option saves both code and data space. 299 | */ 300 | #if !defined(DAC_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 301 | #define DAC_USE_MUTUAL_EXCLUSION TRUE 302 | #endif 303 | 304 | /*===========================================================================*/ 305 | /* I2C driver related settings. */ 306 | /*===========================================================================*/ 307 | 308 | /** 309 | * @brief Enables the mutual exclusion APIs on the I2C bus. 310 | */ 311 | #if !defined(I2C_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 312 | #define I2C_USE_MUTUAL_EXCLUSION TRUE 313 | #endif 314 | 315 | /*===========================================================================*/ 316 | /* MAC driver related settings. */ 317 | /*===========================================================================*/ 318 | 319 | /** 320 | * @brief Enables the zero-copy API. 321 | */ 322 | #if !defined(MAC_USE_ZERO_COPY) || defined(__DOXYGEN__) 323 | #define MAC_USE_ZERO_COPY FALSE 324 | #endif 325 | 326 | /** 327 | * @brief Enables an event sources for incoming packets. 328 | */ 329 | #if !defined(MAC_USE_EVENTS) || defined(__DOXYGEN__) 330 | #define MAC_USE_EVENTS TRUE 331 | #endif 332 | 333 | /*===========================================================================*/ 334 | /* MMC_SPI driver related settings. */ 335 | /*===========================================================================*/ 336 | 337 | /** 338 | * @brief Delays insertions. 339 | * @details If enabled this options inserts delays into the MMC waiting 340 | * routines releasing some extra CPU time for the threads with 341 | * lower priority, this may slow down the driver a bit however. 342 | * This option is recommended also if the SPI driver does not 343 | * use a DMA channel and heavily loads the CPU. 344 | */ 345 | #if !defined(MMC_NICE_WAITING) || defined(__DOXYGEN__) 346 | #define MMC_NICE_WAITING TRUE 347 | #endif 348 | 349 | /*===========================================================================*/ 350 | /* SDC driver related settings. */ 351 | /*===========================================================================*/ 352 | 353 | /** 354 | * @brief Number of initialization attempts before rejecting the card. 355 | * @note Attempts are performed at 10mS intervals. 356 | */ 357 | #if !defined(SDC_INIT_RETRY) || defined(__DOXYGEN__) 358 | #define SDC_INIT_RETRY 100 359 | #endif 360 | 361 | /** 362 | * @brief Include support for MMC cards. 363 | * @note MMC support is not yet implemented so this option must be kept 364 | * at @p FALSE. 365 | */ 366 | #if !defined(SDC_MMC_SUPPORT) || defined(__DOXYGEN__) 367 | #define SDC_MMC_SUPPORT FALSE 368 | #endif 369 | 370 | /** 371 | * @brief Delays insertions. 372 | * @details If enabled this options inserts delays into the MMC waiting 373 | * routines releasing some extra CPU time for the threads with 374 | * lower priority, this may slow down the driver a bit however. 375 | */ 376 | #if !defined(SDC_NICE_WAITING) || defined(__DOXYGEN__) 377 | #define SDC_NICE_WAITING TRUE 378 | #endif 379 | 380 | /** 381 | * @brief OCR initialization constant for V20 cards. 382 | */ 383 | #if !defined(SDC_INIT_OCR_V20) || defined(__DOXYGEN__) 384 | #define SDC_INIT_OCR_V20 0x50FF8000U 385 | #endif 386 | 387 | /** 388 | * @brief OCR initialization constant for non-V20 cards. 389 | */ 390 | #if !defined(SDC_INIT_OCR) || defined(__DOXYGEN__) 391 | #define SDC_INIT_OCR 0x80100000U 392 | #endif 393 | 394 | /*===========================================================================*/ 395 | /* SERIAL driver related settings. */ 396 | /*===========================================================================*/ 397 | 398 | /** 399 | * @brief Default bit rate. 400 | * @details Configuration parameter, this is the baud rate selected for the 401 | * default configuration. 402 | */ 403 | #if !defined(SERIAL_DEFAULT_BITRATE) || defined(__DOXYGEN__) 404 | #define SERIAL_DEFAULT_BITRATE 38400 405 | #endif 406 | 407 | /** 408 | * @brief Serial buffers size. 409 | * @details Configuration parameter, you can change the depth of the queue 410 | * buffers depending on the requirements of your application. 411 | * @note The default is 16 bytes for both the transmission and receive 412 | * buffers. 413 | */ 414 | #if !defined(SERIAL_BUFFERS_SIZE) || defined(__DOXYGEN__) 415 | #define SERIAL_BUFFERS_SIZE 16 416 | #endif 417 | 418 | /*===========================================================================*/ 419 | /* SERIAL_USB driver related setting. */ 420 | /*===========================================================================*/ 421 | 422 | /** 423 | * @brief Serial over USB buffers size. 424 | * @details Configuration parameter, the buffer size must be a multiple of 425 | * the USB data endpoint maximum packet size. 426 | * @note The default is 256 bytes for both the transmission and receive 427 | * buffers. 428 | */ 429 | #if !defined(SERIAL_USB_BUFFERS_SIZE) || defined(__DOXYGEN__) 430 | #define SERIAL_USB_BUFFERS_SIZE 256 431 | #endif 432 | 433 | /** 434 | * @brief Serial over USB number of buffers. 435 | * @note The default is 2 buffers. 436 | */ 437 | #if !defined(SERIAL_USB_BUFFERS_NUMBER) || defined(__DOXYGEN__) 438 | #define SERIAL_USB_BUFFERS_NUMBER 2 439 | #endif 440 | 441 | /*===========================================================================*/ 442 | /* SPI driver related settings. */ 443 | /*===========================================================================*/ 444 | 445 | /** 446 | * @brief Enables synchronous APIs. 447 | * @note Disabling this option saves both code and data space. 448 | */ 449 | #if !defined(SPI_USE_WAIT) || defined(__DOXYGEN__) 450 | #define SPI_USE_WAIT TRUE 451 | #endif 452 | 453 | /** 454 | * @brief Enables circular transfers APIs. 455 | * @note Disabling this option saves both code and data space. 456 | */ 457 | #if !defined(SPI_USE_CIRCULAR) || defined(__DOXYGEN__) 458 | #define SPI_USE_CIRCULAR FALSE 459 | #endif 460 | 461 | /** 462 | * @brief Enables the @p spiAcquireBus() and @p spiReleaseBus() APIs. 463 | * @note Disabling this option saves both code and data space. 464 | */ 465 | #if !defined(SPI_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 466 | #define SPI_USE_MUTUAL_EXCLUSION TRUE 467 | #endif 468 | 469 | /** 470 | * @brief Handling method for SPI CS line. 471 | * @note Disabling this option saves both code and data space. 472 | */ 473 | #if !defined(SPI_SELECT_MODE) || defined(__DOXYGEN__) 474 | #define SPI_SELECT_MODE SPI_SELECT_MODE_PAD 475 | #endif 476 | 477 | /*===========================================================================*/ 478 | /* UART driver related settings. */ 479 | /*===========================================================================*/ 480 | 481 | /** 482 | * @brief Enables synchronous APIs. 483 | * @note Disabling this option saves both code and data space. 484 | */ 485 | #if !defined(UART_USE_WAIT) || defined(__DOXYGEN__) 486 | #define UART_USE_WAIT FALSE 487 | #endif 488 | 489 | /** 490 | * @brief Enables the @p uartAcquireBus() and @p uartReleaseBus() APIs. 491 | * @note Disabling this option saves both code and data space. 492 | */ 493 | #if !defined(UART_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 494 | #define UART_USE_MUTUAL_EXCLUSION FALSE 495 | #endif 496 | 497 | /*===========================================================================*/ 498 | /* USB driver related settings. */ 499 | /*===========================================================================*/ 500 | 501 | /** 502 | * @brief Enables synchronous APIs. 503 | * @note Disabling this option saves both code and data space. 504 | */ 505 | #if !defined(USB_USE_WAIT) || defined(__DOXYGEN__) 506 | #define USB_USE_WAIT FALSE 507 | #endif 508 | 509 | /*===========================================================================*/ 510 | /* WSPI driver related settings. */ 511 | /*===========================================================================*/ 512 | 513 | /** 514 | * @brief Enables synchronous APIs. 515 | * @note Disabling this option saves both code and data space. 516 | */ 517 | #if !defined(WSPI_USE_WAIT) || defined(__DOXYGEN__) 518 | #define WSPI_USE_WAIT TRUE 519 | #endif 520 | 521 | /** 522 | * @brief Enables the @p wspiAcquireBus() and @p wspiReleaseBus() APIs. 523 | * @note Disabling this option saves both code and data space. 524 | */ 525 | #if !defined(WSPI_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) 526 | #define WSPI_USE_MUTUAL_EXCLUSION TRUE 527 | #endif 528 | 529 | #endif /* HALCONF_H */ 530 | 531 | /** @} */ 532 | -------------------------------------------------------------------------------- /firmware/cfg/mcuconf.h: -------------------------------------------------------------------------------- 1 | /* 2 | ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef MCUCONF_H 18 | #define MCUCONF_H 19 | 20 | /* 21 | * STM32F4xx drivers configuration. 22 | * The following settings override the default settings present in 23 | * the various device driver implementation headers. 24 | * Note that the settings for each driver only have effect if the whole 25 | * driver is enabled in halconf.h. 26 | * 27 | * IRQ priorities: 28 | * 15...0 Lowest...Highest. 29 | * 30 | * DMA priorities: 31 | * 0...3 Lowest...Highest. 32 | */ 33 | 34 | #define STM32F4xx_MCUCONF 35 | 36 | /* 37 | * HAL driver system settings. 38 | */ 39 | #define STM32_NO_INIT FALSE 40 | #define STM32_HSI_ENABLED TRUE 41 | #define STM32_LSI_ENABLED TRUE 42 | #define STM32_HSE_ENABLED TRUE 43 | #define STM32_LSE_ENABLED TRUE 44 | #define STM32_CLOCK48_REQUIRED TRUE 45 | #define STM32_SW STM32_SW_PLL 46 | #define STM32_PLLSRC STM32_PLLSRC_HSE 47 | #define STM32_PLLM_VALUE 8 48 | #define STM32_PLLN_VALUE 336 49 | #define STM32_PLLP_VALUE 2 50 | #define STM32_PLLQ_VALUE 7 51 | #define STM32_HPRE STM32_HPRE_DIV1 52 | #define STM32_PPRE1 STM32_PPRE1_DIV4 53 | #define STM32_PPRE2 STM32_PPRE2_DIV2 54 | #define STM32_RTCSEL STM32_RTCSEL_LSE 55 | #define STM32_RTCPRE_VALUE 8 56 | #define STM32_MCO1SEL STM32_MCO1SEL_HSI 57 | #define STM32_MCO1PRE STM32_MCO1PRE_DIV1 58 | #define STM32_MCO2SEL STM32_MCO2SEL_SYSCLK 59 | #define STM32_MCO2PRE STM32_MCO2PRE_DIV5 60 | #define STM32_I2SSRC STM32_I2SSRC_CKIN 61 | #define STM32_PLLI2SN_VALUE 192 62 | #define STM32_PLLI2SR_VALUE 5 63 | #define STM32_PVD_ENABLE FALSE 64 | #define STM32_PLS STM32_PLS_LEV0 65 | #define STM32_BKPRAM_ENABLE FALSE 66 | 67 | /* 68 | * IRQ system settings. 69 | */ 70 | #define STM32_IRQ_EXTI0_PRIORITY 6 71 | #define STM32_IRQ_EXTI1_PRIORITY 6 72 | #define STM32_IRQ_EXTI2_PRIORITY 6 73 | #define STM32_IRQ_EXTI3_PRIORITY 6 74 | #define STM32_IRQ_EXTI4_PRIORITY 6 75 | #define STM32_IRQ_EXTI5_9_PRIORITY 6 76 | #define STM32_IRQ_EXTI10_15_PRIORITY 6 77 | #define STM32_IRQ_EXTI16_PRIORITY 6 78 | #define STM32_IRQ_EXTI17_PRIORITY 15 79 | #define STM32_IRQ_EXTI18_PRIORITY 6 80 | #define STM32_IRQ_EXTI19_PRIORITY 6 81 | #define STM32_IRQ_EXTI20_PRIORITY 6 82 | #define STM32_IRQ_EXTI21_PRIORITY 15 83 | #define STM32_IRQ_EXTI22_PRIORITY 15 84 | 85 | /* 86 | * ADC driver system settings. 87 | */ 88 | #define STM32_ADC_ADCPRE ADC_CCR_ADCPRE_DIV4 89 | #define STM32_ADC_USE_ADC1 FALSE 90 | #define STM32_ADC_USE_ADC2 FALSE 91 | #define STM32_ADC_USE_ADC3 FALSE 92 | #define STM32_ADC_ADC1_DMA_STREAM STM32_DMA_STREAM_ID(2, 4) 93 | #define STM32_ADC_ADC2_DMA_STREAM STM32_DMA_STREAM_ID(2, 2) 94 | #define STM32_ADC_ADC3_DMA_STREAM STM32_DMA_STREAM_ID(2, 1) 95 | #define STM32_ADC_ADC1_DMA_PRIORITY 2 96 | #define STM32_ADC_ADC2_DMA_PRIORITY 2 97 | #define STM32_ADC_ADC3_DMA_PRIORITY 2 98 | #define STM32_ADC_IRQ_PRIORITY 6 99 | #define STM32_ADC_ADC1_DMA_IRQ_PRIORITY 6 100 | #define STM32_ADC_ADC2_DMA_IRQ_PRIORITY 6 101 | #define STM32_ADC_ADC3_DMA_IRQ_PRIORITY 6 102 | 103 | /* 104 | * CAN driver system settings. 105 | */ 106 | #define STM32_CAN_USE_CAN1 FALSE 107 | #define STM32_CAN_USE_CAN2 FALSE 108 | #define STM32_CAN_CAN1_IRQ_PRIORITY 11 109 | #define STM32_CAN_CAN2_IRQ_PRIORITY 11 110 | 111 | /* 112 | * DAC driver system settings. 113 | */ 114 | #define STM32_DAC_DUAL_MODE FALSE 115 | #define STM32_DAC_USE_DAC1_CH1 FALSE 116 | #define STM32_DAC_USE_DAC1_CH2 FALSE 117 | #define STM32_DAC_DAC1_CH1_IRQ_PRIORITY 10 118 | #define STM32_DAC_DAC1_CH2_IRQ_PRIORITY 10 119 | #define STM32_DAC_DAC1_CH1_DMA_PRIORITY 2 120 | #define STM32_DAC_DAC1_CH2_DMA_PRIORITY 2 121 | #define STM32_DAC_DAC1_CH1_DMA_STREAM STM32_DMA_STREAM_ID(1, 5) 122 | #define STM32_DAC_DAC1_CH2_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) 123 | 124 | /* 125 | * GPT driver system settings. 126 | */ 127 | #define STM32_GPT_USE_TIM1 FALSE 128 | #define STM32_GPT_USE_TIM2 FALSE 129 | #define STM32_GPT_USE_TIM3 FALSE 130 | #define STM32_GPT_USE_TIM4 FALSE 131 | #define STM32_GPT_USE_TIM5 FALSE 132 | #define STM32_GPT_USE_TIM6 FALSE 133 | #define STM32_GPT_USE_TIM7 FALSE 134 | #define STM32_GPT_USE_TIM8 FALSE 135 | #define STM32_GPT_USE_TIM9 FALSE 136 | #define STM32_GPT_USE_TIM11 FALSE 137 | #define STM32_GPT_USE_TIM12 FALSE 138 | #define STM32_GPT_USE_TIM14 FALSE 139 | #define STM32_GPT_TIM1_IRQ_PRIORITY 7 140 | #define STM32_GPT_TIM2_IRQ_PRIORITY 7 141 | #define STM32_GPT_TIM3_IRQ_PRIORITY 7 142 | #define STM32_GPT_TIM4_IRQ_PRIORITY 7 143 | #define STM32_GPT_TIM5_IRQ_PRIORITY 7 144 | #define STM32_GPT_TIM6_IRQ_PRIORITY 7 145 | #define STM32_GPT_TIM7_IRQ_PRIORITY 7 146 | #define STM32_GPT_TIM8_IRQ_PRIORITY 7 147 | #define STM32_GPT_TIM9_IRQ_PRIORITY 7 148 | #define STM32_GPT_TIM11_IRQ_PRIORITY 7 149 | #define STM32_GPT_TIM12_IRQ_PRIORITY 7 150 | #define STM32_GPT_TIM14_IRQ_PRIORITY 7 151 | 152 | /* 153 | * I2C driver system settings. 154 | */ 155 | #define STM32_I2C_USE_I2C1 FALSE 156 | #define STM32_I2C_USE_I2C2 FALSE 157 | #define STM32_I2C_USE_I2C3 FALSE 158 | #define STM32_I2C_BUSY_TIMEOUT 50 159 | #define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) 160 | #define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) 161 | #define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) 162 | #define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) 163 | #define STM32_I2C_I2C3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) 164 | #define STM32_I2C_I2C3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) 165 | #define STM32_I2C_I2C1_IRQ_PRIORITY 5 166 | #define STM32_I2C_I2C2_IRQ_PRIORITY 5 167 | #define STM32_I2C_I2C3_IRQ_PRIORITY 5 168 | #define STM32_I2C_I2C1_DMA_PRIORITY 3 169 | #define STM32_I2C_I2C2_DMA_PRIORITY 3 170 | #define STM32_I2C_I2C3_DMA_PRIORITY 3 171 | #define STM32_I2C_DMA_ERROR_HOOK(i2cp) osalSysHalt("DMA failure") 172 | 173 | /* 174 | * I2S driver system settings. 175 | */ 176 | #define STM32_I2S_USE_SPI2 FALSE 177 | #define STM32_I2S_USE_SPI3 FALSE 178 | #define STM32_I2S_SPI2_IRQ_PRIORITY 10 179 | #define STM32_I2S_SPI3_IRQ_PRIORITY 10 180 | #define STM32_I2S_SPI2_DMA_PRIORITY 1 181 | #define STM32_I2S_SPI3_DMA_PRIORITY 1 182 | #define STM32_I2S_SPI2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 3) 183 | #define STM32_I2S_SPI2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) 184 | #define STM32_I2S_SPI3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) 185 | #define STM32_I2S_SPI3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) 186 | #define STM32_I2S_DMA_ERROR_HOOK(i2sp) osalSysHalt("DMA failure") 187 | 188 | /* 189 | * ICU driver system settings. 190 | */ 191 | #define STM32_ICU_USE_TIM1 FALSE 192 | #define STM32_ICU_USE_TIM2 FALSE 193 | #define STM32_ICU_USE_TIM3 FALSE 194 | #define STM32_ICU_USE_TIM4 FALSE 195 | #define STM32_ICU_USE_TIM5 FALSE 196 | #define STM32_ICU_USE_TIM8 FALSE 197 | #define STM32_ICU_USE_TIM9 FALSE 198 | #define STM32_ICU_TIM1_IRQ_PRIORITY 7 199 | #define STM32_ICU_TIM2_IRQ_PRIORITY 7 200 | #define STM32_ICU_TIM3_IRQ_PRIORITY 7 201 | #define STM32_ICU_TIM4_IRQ_PRIORITY 7 202 | #define STM32_ICU_TIM5_IRQ_PRIORITY 7 203 | #define STM32_ICU_TIM8_IRQ_PRIORITY 7 204 | #define STM32_ICU_TIM9_IRQ_PRIORITY 7 205 | 206 | /* 207 | * MAC driver system settings. 208 | */ 209 | #define STM32_MAC_TRANSMIT_BUFFERS 2 210 | #define STM32_MAC_RECEIVE_BUFFERS 4 211 | #define STM32_MAC_BUFFERS_SIZE 1522 212 | #define STM32_MAC_PHY_TIMEOUT 100 213 | #define STM32_MAC_ETH1_CHANGE_PHY_STATE TRUE 214 | #define STM32_MAC_ETH1_IRQ_PRIORITY 13 215 | #define STM32_MAC_IP_CHECKSUM_OFFLOAD 0 216 | 217 | /* 218 | * PWM driver system settings. 219 | */ 220 | #define STM32_PWM_USE_ADVANCED FALSE 221 | #define STM32_PWM_USE_TIM1 FALSE 222 | #define STM32_PWM_USE_TIM2 FALSE 223 | #define STM32_PWM_USE_TIM3 FALSE 224 | #define STM32_PWM_USE_TIM4 FALSE 225 | #define STM32_PWM_USE_TIM5 FALSE 226 | #define STM32_PWM_USE_TIM8 FALSE 227 | #define STM32_PWM_USE_TIM9 FALSE 228 | #define STM32_PWM_TIM1_IRQ_PRIORITY 7 229 | #define STM32_PWM_TIM2_IRQ_PRIORITY 7 230 | #define STM32_PWM_TIM3_IRQ_PRIORITY 7 231 | #define STM32_PWM_TIM4_IRQ_PRIORITY 7 232 | #define STM32_PWM_TIM5_IRQ_PRIORITY 7 233 | #define STM32_PWM_TIM8_IRQ_PRIORITY 7 234 | #define STM32_PWM_TIM9_IRQ_PRIORITY 7 235 | 236 | /* 237 | * SDC driver system settings. 238 | */ 239 | #define STM32_SDC_SDIO_DMA_PRIORITY 3 240 | #define STM32_SDC_SDIO_IRQ_PRIORITY 9 241 | #define STM32_SDC_WRITE_TIMEOUT_MS 1000 242 | #define STM32_SDC_READ_TIMEOUT_MS 1000 243 | #define STM32_SDC_CLOCK_ACTIVATION_DELAY 10 244 | #define STM32_SDC_SDIO_UNALIGNED_SUPPORT TRUE 245 | #define STM32_SDC_SDIO_DMA_STREAM STM32_DMA_STREAM_ID(2, 3) 246 | 247 | /* 248 | * SERIAL driver system settings. 249 | */ 250 | #define STM32_SERIAL_USE_USART1 FALSE 251 | #define STM32_SERIAL_USE_USART2 FALSE 252 | #define STM32_SERIAL_USE_USART3 TRUE 253 | #define STM32_SERIAL_USE_UART4 FALSE 254 | #define STM32_SERIAL_USE_UART5 FALSE 255 | #define STM32_SERIAL_USE_USART6 FALSE 256 | #define STM32_SERIAL_USE_UART7 FALSE 257 | #define STM32_SERIAL_USE_UART8 FALSE 258 | #define STM32_SERIAL_USART1_PRIORITY 12 259 | #define STM32_SERIAL_USART2_PRIORITY 12 260 | #define STM32_SERIAL_USART3_PRIORITY 12 261 | #define STM32_SERIAL_UART4_PRIORITY 12 262 | #define STM32_SERIAL_UART5_PRIORITY 12 263 | #define STM32_SERIAL_USART6_PRIORITY 12 264 | #define STM32_SERIAL_UART7_PRIORITY 12 265 | #define STM32_SERIAL_UART8_PRIORITY 12 266 | 267 | /* 268 | * SPI driver system settings. 269 | */ 270 | #define STM32_SPI_USE_SPI1 FALSE 271 | #define STM32_SPI_USE_SPI2 FALSE 272 | #define STM32_SPI_USE_SPI3 FALSE 273 | #define STM32_SPI_USE_SPI4 FALSE 274 | #define STM32_SPI_USE_SPI5 FALSE 275 | #define STM32_SPI_USE_SPI6 FALSE 276 | #define STM32_SPI_SPI1_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 0) 277 | #define STM32_SPI_SPI1_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 3) 278 | #define STM32_SPI_SPI2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 3) 279 | #define STM32_SPI_SPI2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) 280 | #define STM32_SPI_SPI3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) 281 | #define STM32_SPI_SPI3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) 282 | #define STM32_SPI_SPI4_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 0) 283 | #define STM32_SPI_SPI4_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 1) 284 | #define STM32_SPI_SPI5_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 3) 285 | #define STM32_SPI_SPI5_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 4) 286 | #define STM32_SPI_SPI6_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 6) 287 | #define STM32_SPI_SPI6_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 5) 288 | #define STM32_SPI_SPI1_DMA_PRIORITY 1 289 | #define STM32_SPI_SPI2_DMA_PRIORITY 1 290 | #define STM32_SPI_SPI3_DMA_PRIORITY 1 291 | #define STM32_SPI_SPI4_DMA_PRIORITY 1 292 | #define STM32_SPI_SPI5_DMA_PRIORITY 1 293 | #define STM32_SPI_SPI6_DMA_PRIORITY 1 294 | #define STM32_SPI_SPI1_IRQ_PRIORITY 10 295 | #define STM32_SPI_SPI2_IRQ_PRIORITY 10 296 | #define STM32_SPI_SPI3_IRQ_PRIORITY 10 297 | #define STM32_SPI_SPI4_IRQ_PRIORITY 10 298 | #define STM32_SPI_SPI5_IRQ_PRIORITY 10 299 | #define STM32_SPI_SPI6_IRQ_PRIORITY 10 300 | #define STM32_SPI_DMA_ERROR_HOOK(spip) osalSysHalt("DMA failure") 301 | 302 | /* 303 | * ST driver system settings. 304 | */ 305 | #define STM32_ST_IRQ_PRIORITY 8 306 | #define STM32_ST_USE_TIMER 2 307 | 308 | /* 309 | * UART driver system settings. 310 | */ 311 | #define STM32_UART_USE_USART1 FALSE 312 | #define STM32_UART_USE_USART2 FALSE 313 | #define STM32_UART_USE_USART3 FALSE 314 | #define STM32_UART_USE_UART4 FALSE 315 | #define STM32_UART_USE_UART5 FALSE 316 | #define STM32_UART_USE_USART6 FALSE 317 | #define STM32_UART_USART1_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 5) 318 | #define STM32_UART_USART1_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 7) 319 | #define STM32_UART_USART2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5) 320 | #define STM32_UART_USART2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) 321 | #define STM32_UART_USART3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 1) 322 | #define STM32_UART_USART3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 3) 323 | #define STM32_UART_UART4_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) 324 | #define STM32_UART_UART4_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) 325 | #define STM32_UART_UART5_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) 326 | #define STM32_UART_UART5_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) 327 | #define STM32_UART_USART6_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 2) 328 | #define STM32_UART_USART6_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 7) 329 | #define STM32_UART_USART1_IRQ_PRIORITY 12 330 | #define STM32_UART_USART2_IRQ_PRIORITY 12 331 | #define STM32_UART_USART3_IRQ_PRIORITY 12 332 | #define STM32_UART_UART4_IRQ_PRIORITY 12 333 | #define STM32_UART_UART5_IRQ_PRIORITY 12 334 | #define STM32_UART_USART6_IRQ_PRIORITY 12 335 | #define STM32_UART_USART1_DMA_PRIORITY 0 336 | #define STM32_UART_USART2_DMA_PRIORITY 0 337 | #define STM32_UART_USART3_DMA_PRIORITY 0 338 | #define STM32_UART_UART4_DMA_PRIORITY 0 339 | #define STM32_UART_UART5_DMA_PRIORITY 0 340 | #define STM32_UART_USART6_DMA_PRIORITY 0 341 | #define STM32_UART_DMA_ERROR_HOOK(uartp) osalSysHalt("DMA failure") 342 | 343 | /* 344 | * USB driver system settings. 345 | */ 346 | #define STM32_USB_USE_OTG1 FALSE 347 | #define STM32_USB_USE_OTG2 FALSE 348 | #define STM32_USB_OTG1_IRQ_PRIORITY 14 349 | #define STM32_USB_OTG2_IRQ_PRIORITY 14 350 | #define STM32_USB_OTG1_RX_FIFO_SIZE 512 351 | #define STM32_USB_OTG2_RX_FIFO_SIZE 1024 352 | #define STM32_USB_OTG_THREAD_PRIO LOWPRIO 353 | #define STM32_USB_OTG_THREAD_STACK_SIZE 128 354 | #define STM32_USB_OTGFIFO_FILL_BASEPRI 0 355 | 356 | /* 357 | * WDG driver system settings. 358 | */ 359 | #define STM32_WDG_USE_IWDG TRUE 360 | 361 | #endif /* MCUCONF_H */ 362 | -------------------------------------------------------------------------------- /firmware/config.h: -------------------------------------------------------------------------------- 1 | #ifndef __NTPD_CONFIG_H__ 2 | #define __NTPD_CONFIG_H__ 3 | 4 | #define GNSS_AID_POSITION 5 | #define GNSS_AID_POS_LATITUDE (51.2500 / 1e-7) 6 | #define GNSS_AID_POS_LONGITUDE (-0.5946 / 1e-7) 7 | #define GNSS_AID_POS_ALTITUDE (60 / 1e-2) // 60m 8 | #define GNSS_AID_POS_STDDEV (199 / 1e-5) // 199km 9 | 10 | #define NTPD_STRATUM_DEMOTE_TIMER_PERIOD TIME_S2I(10*60) // 10 minutes 11 | 12 | #define NTPD_STATUS_DEMOTE_TIMER_PERIOD TIME_MS2I(1200) // 10 minutes 13 | 14 | #endif /* __NTPD_CONFIG_H__ */ -------------------------------------------------------------------------------- /firmware/debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source_dir="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)" 4 | cd "$source_dir" 5 | 6 | COLOUR_GREEN='\033[0;32m' 7 | COLOUR_YELLOW='\033[1;33m' 8 | COLOUR_PURPLE='\033[0;35m' 9 | COLOUR_RED='\033[0;31m' 10 | COLOUR_OFF='\033[0;00m' 11 | CLEAR_LINE='\033[2K' 12 | 13 | _ERROR_="${COLOUR_RED}[ERROR]${COLOUR_OFF}" 14 | _INFO_="${COLOUR_PURPLE}[INFO]${COLOUR_OFF}" 15 | _DEBUG_="${COLOUR_YELLOW}[DEBUG]${COLOUR_OFF}" 16 | 17 | flashtool="arm-none-eabi-gdb" 18 | programmer=false 19 | 20 | locate_black_magic() { 21 | probe="`ls 2>/dev/null -Ub1 -- /dev/serial/by-id/usb-Black_Sphere_Technologies_Black_Magic_Probe_*-if00 | head -n 1`" 22 | if ! [ -z "$probe" ] 23 | then 24 | echo "$probe" 25 | return 0 26 | fi 27 | return 1 28 | } 29 | debug_target() { 30 | $flashtool -q \ 31 | -ex 'file '$binary \ 32 | -ex 'target extended-remote '$programmer \ 33 | -ex 'monitor tpwr $programmer_power' \ 34 | -ex 'monitor swdp_scan' \ 35 | -ex 'attach 1' \ 36 | -ex 'set mem inaccessible-by-default off' \ 37 | -ex 'load '$binary \ 38 | -ex 'compare-sections' 39 | } 40 | 41 | debug_target_st-util() { 42 | $flashtool -q \ 43 | -ex 'file '$binary \ 44 | -ex 'set remote hardware-breakpoint-limit 8'\ 45 | -ex 'set remote hardware-watchpoint-limit 4'\ 46 | -ex 'set remotetimeout 10000' \ 47 | -ex 'set remote interrupt-on-connect off' \ 48 | -ex 'set mem inaccessible-by-default off' \ 49 | -ex 'target extended-remote localhost:4242' \ 50 | -ex 'load '$binary 51 | } 52 | 53 | debug_target_openocd() { 54 | $flashtool -q \ 55 | -ex 'file '$binary \ 56 | -ex 'set remote hardware-breakpoint-limit 8'\ 57 | -ex 'set remote hardware-watchpoint-limit 4'\ 58 | -ex 'set remotetimeout 10000' \ 59 | -ex 'set remote interrupt-on-connect off' \ 60 | -ex 'set mem inaccessible-by-default off' \ 61 | -ex 'target extended-remote localhost:3333' \ 62 | -ex 'load '$binary 63 | } 64 | 65 | binary="${1}.elf" 66 | 67 | if [ "$binary" == ".elf" ]; then 68 | binary="$(ls -t builds/*.elf | head -1)" 69 | echo -e "$_INFO_ Most recent build selected: ${binary}" 70 | fi 71 | 72 | if [[ ! -f $binary ]]; then 73 | echo -e "$_ERROR_ Binary $binary not found!" 74 | exit 1 75 | fi 76 | 77 | if programmer="`locate_black_magic`"; 78 | then 79 | debug_target; 80 | else 81 | debug_target_openocd; 82 | fi 83 | -------------------------------------------------------------------------------- /firmware/debug_udp.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | typedef struct { 8 | bool waiting; 9 | uint8_t rawFrame[64]; 10 | mutex_t mutex; 11 | condition_variable_t condition; 12 | } udp_tx_queue_t; 13 | 14 | static udp_tx_queue_t udp_tx_queue = 15 | { 16 | .waiting = false, 17 | .mutex = _MUTEX_DATA(udp_tx_queue.mutex), 18 | .condition = _CONDVAR_DATA(udp_tx_queue.condition) 19 | }; 20 | 21 | static struct udp_pcb *debug_udp_tx_pcb_ptr = NULL; 22 | static struct pbuf *debug_udp_pbuf_ptr; 23 | 24 | static void _udp_tx(void *arg) 25 | { 26 | (void)arg; 27 | 28 | if(debug_udp_tx_pcb_ptr == NULL) 29 | { 30 | debug_udp_tx_pcb_ptr = udp_new(); 31 | } 32 | 33 | udp_sendto(debug_udp_tx_pcb_ptr, debug_udp_pbuf_ptr, IP_ADDR_BROADCAST, 11234); 34 | } 35 | 36 | 37 | THD_FUNCTION(debug_udp_thread, arg) 38 | { 39 | (void)arg; 40 | 41 | chRegSetThreadName("debug_udp"); 42 | 43 | while(true) 44 | { 45 | watchdog_feed(WATCHDOG_DOG_IPSRVS); 46 | chMtxLock(&udp_tx_queue.mutex); 47 | 48 | while(!udp_tx_queue.waiting) 49 | { 50 | watchdog_feed(WATCHDOG_DOG_IPSRVS); 51 | 52 | if(chCondWaitTimeout(&udp_tx_queue.condition, TIME_MS2I(100)) == MSG_TIMEOUT) 53 | { 54 | /* Re-acquire Mutex */ 55 | chMtxLock(&udp_tx_queue.mutex); 56 | } 57 | } 58 | 59 | debug_pbuf_payload_ptr[4] = 0xFF; 60 | memcpy(&(debug_pbuf_payload_ptr[5]), udp_tx_queue.rawFrame, 64); 61 | 62 | udp_tx_queue.waiting = false; 63 | chMtxUnlock(&udp_tx_queue.mutex); 64 | 65 | if(app_ip_link_status() != APP_IP_LINK_STATUS_BOUND) 66 | { 67 | /* Discard packet */ 68 | continue; 69 | } 70 | 71 | rtcGetTime(&RTCD1, &debug_datetime); 72 | *(uint32_t *)(&debug_pbuf_payload_ptr[0]) = debug_datetime.millisecond; 73 | 74 | tcpip_callback(_udp_tx, NULL); 75 | 76 | watchdog_feed(WATCHDOG_DOG_IPSRVS); 77 | chThdSleepMilliseconds(10); 78 | } 79 | }; -------------------------------------------------------------------------------- /firmware/flash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source_dir="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)" 4 | cd "$source_dir" 5 | 6 | COLOUR_GREEN='\033[0;32m' 7 | COLOUR_YELLOW='\033[1;33m' 8 | COLOUR_PURPLE='\033[0;35m' 9 | COLOUR_RED='\033[0;31m' 10 | COLOUR_OFF='\033[0;00m' 11 | CLEAR_LINE='\033[2K' 12 | 13 | _ERROR_="${COLOUR_RED}[ERROR]${COLOUR_OFF}" 14 | _INFO_="${COLOUR_PURPLE}[INFO]${COLOUR_OFF}" 15 | _DEBUG_="${COLOUR_YELLOW}[DEBUG]${COLOUR_OFF}" 16 | 17 | opt_sound=false; 18 | opt_power=false; 19 | 20 | flashtool="arm-none-eabi-gdb" 21 | programmer=false 22 | 23 | locate_black_magic() { 24 | probe="`ls 2>/dev/null -Ub1 -- /dev/serial/by-id/usb-Black_Sphere_Technologies_Black_Magic_Probe_*-if00 | head -n 1`"; 25 | if ! [ -z "$probe" ]; 26 | then 27 | echo "$probe"; 28 | return 0; 29 | fi 30 | return 1; 31 | } 32 | 33 | flash_target() { 34 | programmer_power="disable" 35 | if $opt_power; then 36 | programmer_power="enable"; 37 | fi 38 | 39 | flash_cmd="$flashtool -q --batch \ 40 | -ex 'file '$binary \ 41 | -ex 'target extended-remote '$programmer \ 42 | -ex 'monitor tpwr '$programmer_power \ 43 | -ex 'monitor swdp_scan' \ 44 | -ex 'attach 1' \ 45 | -ex 'load '$binary \ 46 | -ex 'compare-sections'"; 47 | 48 | flash_out=`eval $flash_cmd 2>&1`; 49 | echo ${flash_out} | grep -vqE 'warning|fail|Error|MIS-MATCHED|"monitor" command not supported by this target'; 50 | if [[ $? -eq 0 ]]; then 51 | printf "${flash_out}\n" 52 | return 0; 53 | else 54 | printf "${flash_out}\n" 55 | return 1; 56 | fi 57 | } 58 | 59 | playsound() { 60 | if hash aplay 2>/dev/null 61 | then 62 | aplay -q $@; 63 | elif hash play 2>/dev/null 64 | then 65 | play -q $@; 66 | else 67 | printf "$_ERROR_ No WAV player detected\n"; 68 | fi 69 | } 70 | 71 | check_linux_serial_permissions() { 72 | # For Linux - check user has permissions to the serial port (required for Black Magic Probe) 73 | if [ ${host_platform} == "linux" ]; then 74 | groups $USER | grep -q 'dialout' 75 | if [[ ! $? -eq 0 ]]; then 76 | printf "$_ERROR_ Current user is not in dialout group, you'll need this to use the programmer!\n"; 77 | printf " * eg. for Ubuntu/Debian: 'sudo gpasswd --add "$USER" dialout'\n"; 78 | exit 1 79 | fi 80 | fi 81 | } 82 | 83 | ## Read Flags 84 | OPTIND=1 85 | while getopts ":SP" opt; do 86 | case "$opt" in 87 | S) # Sound 88 | opt_sound=true 89 | ;; 90 | P) # Sound 91 | opt_power=true 92 | ;; 93 | ?) # Illegal Option 94 | echo -e "$_ERROR_ Illegal option '$OPTARG'" 95 | exit 3 96 | ;; 97 | esac 98 | done 99 | for i in `seq 2 $OPTIND`; do shift; done 100 | 101 | binary="${1}.elf" 102 | 103 | if [ "$binary" == ".elf" ]; 104 | then 105 | binary="$(ls -t builds/*.elf | head -1)" 106 | if [[ ! -f $binary ]]; then 107 | echo -e "$_ERROR_ No suitable builds found!" 108 | exit 1 109 | fi 110 | echo -e "$_INFO_ Most recent build selected" 111 | else 112 | if [[ ! -f $binary ]]; then 113 | echo -e "$_ERROR_ Binary $binary not found!" 114 | exit 1 115 | fi 116 | fi 117 | 118 | echo -e "$_INFO_ - File: ${binary}" 119 | 120 | if [ `date "+%Y-%m-%d"` == `date -r ${binary} "+%Y-%m-%d"` ]; 121 | then 122 | echo -e "$_INFO_ - Compiled today `date -r ${binary} "+%H:%M:%S"`"; 123 | else 124 | echo -e "$_INFO_ - Compiled `date -r ${binary} "+%Y-%m-%d %H:%M:%S"`"; 125 | fi 126 | 127 | if programmer="`locate_black_magic`"; 128 | then 129 | printf "$_INFO_ Black Magic Probe found. Flashing..\n"; 130 | flash_target \ 131 | && { 132 | printf "$_INFO_$COLOUR_GREEN Device Flash successful$COLOUR_OFF\n" 133 | if $opt_sound; then playsound tools/flash.wav; fi 134 | } || { 135 | printf "$_ERROR_$COLOUR_RED Device Flash Failed$COLOUR_OFF\n" \ 136 | && exit 1 137 | } 138 | else 139 | printf "$_INFO_ ${COLOUR_YELLOW}Black Magic Probe not found, trying ST-Link..${COLOUR_OFF}\n" 140 | binary=builds/"$(basename "$binary" .elf).bin" 141 | printf "$_INFO_ Using bin file with st-flash: ${binary}\n" 142 | st-flash write $binary 0x8000000 \ 143 | && { 144 | printf "$_INFO_$COLOUR_GREEN Device Flash successful$COLOUR_OFF\n" 145 | if $opt_sound; then playsound tools/flash.wav; fi 146 | } || { 147 | printf "$_ERROR_$COLOUR_RED Device Flash Failed$COLOUR_OFF\n" \ 148 | && exit 1 149 | } 150 | fi 151 | -------------------------------------------------------------------------------- /firmware/gnss.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | gnss_status_t gnss_status = { 0 }; 9 | 10 | int32_t gnss_aid_position_latitude = GNSS_AID_POS_LATITUDE; 11 | int32_t gnss_aid_position_longitude = GNSS_AID_POS_LONGITUDE; 12 | int32_t gnss_aid_position_altitude = GNSS_AID_POS_ALTITUDE; 13 | int32_t gnss_aid_position_stddev = GNSS_AID_POS_STDDEV; 14 | 15 | void gnss_parse(uint8_t *buffer); 16 | 17 | static const SerialConfig serial_gnss_config = { 18 | .speed = 9600, 19 | .cr2 = USART_CR2_CPOL 20 | }; 21 | 22 | static uint16_t ubx_crc(const uint8_t *input, uint16_t len) 23 | { 24 | uint8_t a = 0; 25 | uint8_t b = 0; 26 | uint32_t i; 27 | for(i = 0; i < len; i++) 28 | { 29 | a = a + input[i]; 30 | b = b + a; 31 | } 32 | return (a << 8) | b; 33 | } 34 | 35 | static bool ubx_crc_verify(const uint8_t *buffer, int32_t buffer_size) 36 | { 37 | uint16_t ck = ubx_crc(&buffer[2], (buffer_size-4)); 38 | 39 | return (((ck >> 8) & 0xFF) == buffer[buffer_size-2]) && ((ck & 0xFF) == buffer[buffer_size-1]); 40 | } 41 | 42 | static void uart_send_blocking_len_ubx(const uint8_t *_buff, uint16_t len) 43 | { 44 | uint16_t checksum; 45 | uint8_t checksum_u8[2]; 46 | checksum = ubx_crc(&_buff[2], (len-2)); 47 | checksum_u8[0] = (checksum >> 8) & 0xFF; 48 | checksum_u8[1] = checksum & 0xFF; 49 | 50 | sdWrite(&SD3, _buff, len); 51 | sdWrite(&SD3, checksum_u8, 2); 52 | } 53 | 54 | const uint8_t enable_galileo[] = { 0xb5, 0x62, 55 | 0x06, 0x3e, 56 | 0x3c, 0x00, // Length: 60 bytes 57 | 0x00, 0x00, 0xff, 0x07, 58 | // GPS min max res x1 x2 x3, x4 59 | 0x00, 0x0A, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, 60 | // SBAS min max res x1 x2 x3 x4 61 | 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x01, 62 | // GAL min max res x1 x2 x3, x4 63 | 0x02, 0x04, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 64 | // BEI min max res x1 x2 x3, x4 65 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 66 | // IMES min max res x1 x2 x3, x4 67 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 68 | // QZSS min max res x1 x2 x3, x4 69 | 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 70 | // GLO min max res x1 x2 x3, x4 71 | 0x06, 0x0A, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, 72 | }; 73 | 74 | const uint8_t disable_nmea_gpgga[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 75 | const uint8_t disable_nmea_gpgll[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 76 | const uint8_t disable_nmea_gpgsa[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 77 | const uint8_t disable_nmea_gpgsv[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 78 | const uint8_t disable_nmea_gprmc[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 79 | const uint8_t disable_nmea_gpvtg[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 80 | 81 | const uint8_t enable_navpvt[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0x01, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; 82 | const uint8_t enable_navsat[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0x01, 0x35, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; 83 | 84 | uint8_t gnss_aid_position_msg[] = 85 | { 86 | 0x13, 0x40, // UBX-MGA-INI 87 | 0x14, 0x00, // Length 88 | 0x01, // Type: UBX-MGA-INI-POS_LLH 89 | 0x00, // Version: 0 90 | 0x00, 0x00, // Reserved 91 | 0x00, 0x00, 0x00, 0x00, // Latitude 1e-7 92 | 0x00, 0x00, 0x00, 0x00, // Longitude 1e-7 93 | 0x00, 0x00, 0x00, 0x00, // Altitude cm 94 | 0x00, 0x00, 0x00, 0x00 // Std Dev cm 95 | }; 96 | 97 | static void gnss_configure(void) 98 | { 99 | /* Enable Galileo */ 100 | uart_send_blocking_len_ubx(enable_galileo, sizeof(enable_galileo)); 101 | 102 | /* Disable NMEA Outputs */ 103 | uart_send_blocking_len_ubx(disable_nmea_gpgga, sizeof(disable_nmea_gpgga)); 104 | uart_send_blocking_len_ubx(disable_nmea_gpgll, sizeof(disable_nmea_gpgll)); 105 | uart_send_blocking_len_ubx(disable_nmea_gpgsa, sizeof(disable_nmea_gpgsa)); 106 | uart_send_blocking_len_ubx(disable_nmea_gpgsv, sizeof(disable_nmea_gpgsv)); 107 | uart_send_blocking_len_ubx(disable_nmea_gprmc, sizeof(disable_nmea_gprmc)); 108 | uart_send_blocking_len_ubx(disable_nmea_gpvtg, sizeof(disable_nmea_gpvtg)); 109 | 110 | /* Enable UBX Outputs */ 111 | uart_send_blocking_len_ubx(enable_navpvt, sizeof(enable_navpvt)); 112 | uart_send_blocking_len_ubx(enable_navsat, sizeof(enable_navsat)); 113 | 114 | #ifdef GNSS_AID_POSITION 115 | memcpy(&gnss_aid_position_msg[8], &gnss_aid_position_latitude, sizeof(int32_t)); 116 | memcpy(&gnss_aid_position_msg[12], &gnss_aid_position_longitude, sizeof(int32_t)); 117 | memcpy(&gnss_aid_position_msg[16], &gnss_aid_position_altitude, sizeof(int32_t)); 118 | memcpy(&gnss_aid_position_msg[20], &gnss_aid_position_stddev, sizeof(int32_t)); 119 | 120 | uart_send_blocking_len_ubx(gnss_aid_position_msg, sizeof(gnss_aid_position_msg)); 121 | #endif 122 | } 123 | 124 | static uint8_t buffer[1024]; 125 | 126 | static const uint8_t msg_header[2] = { 0xb5, 0x62 }; 127 | static uint32_t response_index = 0; 128 | static uint32_t response_length = 0; 129 | static void gnss_ubx_rx(uint8_t response_byte) 130 | { 131 | if(response_index < 2 && response_byte == msg_header[response_index]) 132 | { 133 | /* Header */ 134 | buffer[response_index] = response_byte; 135 | response_index++; 136 | } 137 | else if(response_index >= 2 && response_index < 5) 138 | { 139 | /* Message Class, ID, first byte of length */ 140 | buffer[response_index] = response_byte; 141 | response_index++; 142 | } 143 | else if(response_index == 5) 144 | { 145 | /* Final Length byte */ 146 | buffer[response_index] = response_byte; 147 | response_length = buffer[5] << 8 | buffer[4]; 148 | response_index++; 149 | } 150 | else if(response_index > 5 && response_index < (6+2+response_length-1)) 151 | { 152 | /* Data payload and first byte of checksum */ 153 | buffer[response_index] = response_byte; 154 | response_index++; 155 | } 156 | else if(response_index == (6+2+response_length-1)) 157 | { 158 | /* Last byte of checksum */ 159 | buffer[response_index] = response_byte; 160 | 161 | if(ubx_crc_verify(buffer, (6+2+response_length))) 162 | { 163 | gnss_parse(buffer); 164 | } 165 | 166 | response_index = 0; 167 | } 168 | else 169 | { 170 | response_index = 0; 171 | } 172 | } 173 | 174 | bool pps_set_time = false; 175 | RTCDateTime time_set_timespec; 176 | 177 | /* To be copied over on PPS pulse */ 178 | struct tm time_ref_candidate_tm; 179 | uint32_t time_ref_candidate_tm_ms; 180 | uint32_t time_ref_candidate_s; 181 | uint32_t time_ref_candidate_f; 182 | 183 | /* Copied over to on PPS pulse */ 184 | struct tm time_ref_tm = { 0 }; 185 | uint32_t time_ref_tm_ms = 0; 186 | uint32_t time_ref_s = 0; 187 | uint32_t time_ref_f = 0; 188 | 189 | extern RTCDateTime time_ref_timespec; 190 | 191 | static virtual_timer_t status_demote_timer; 192 | static virtual_timer_t stratum_demote_timer; 193 | 194 | static void cb_status_demote(void *arg) 195 | { 196 | (void)arg; 197 | 198 | ntpd_status.status = NTPD_IN_HOLDOVER; 199 | } 200 | 201 | static void cb_stratum_demote(void *arg) 202 | { 203 | (void)arg; 204 | 205 | ntpd_status.status = NTPD_DEGRADED; 206 | ntpd_status.stratum = 16; 207 | } 208 | 209 | void pps_set_time_cb(void *arg) 210 | { 211 | (void)arg; 212 | 213 | if(!pps_set_time) 214 | { 215 | return; 216 | } 217 | 218 | rtcSetTime(&RTCD1, &time_set_timespec); 219 | 220 | RTCD1.rtc->CR &= ~RTC_CR_ALRAIE; 221 | RTCD1.rtc->CR &= ~RTC_CR_ALRAE; 222 | 223 | pps_set_time = false; 224 | 225 | time_ref_tm = time_ref_candidate_tm; 226 | time_ref_tm_ms = time_ref_candidate_tm_ms; 227 | time_ref_s = time_ref_candidate_s; 228 | time_ref_f = time_ref_candidate_f; 229 | 230 | ntpd_status.status = NTPD_IN_LOCK; 231 | ntpd_status.stratum = 1; 232 | 233 | /* Reset stratum demote timer */ 234 | chSysLockFromISR(); 235 | chVTSetI(&status_demote_timer, NTPD_STATUS_DEMOTE_TIMER_PERIOD, cb_status_demote, NULL); 236 | chVTSetI(&stratum_demote_timer, NTPD_STRATUM_DEMOTE_TIMER_PERIOD, cb_stratum_demote, NULL); 237 | chSysUnlockFromISR(); 238 | } 239 | 240 | 241 | static uint8_t gnss_input_buffer[512]; 242 | 243 | THD_FUNCTION(gnss_thread, arg) 244 | { 245 | (void)arg; 246 | 247 | chRegSetThreadName("gnss"); 248 | 249 | /* Start GNSS UART */ 250 | sdStart(&SD3, &serial_gnss_config); 251 | 252 | /* Register for DATA IN event */ 253 | event_listener_t serial_gnss_listener; 254 | chEvtRegisterMaskWithFlags(chnGetEventSource(&SD3), 255 | &serial_gnss_listener, 256 | EVENT_MASK(1), 257 | CHN_INPUT_AVAILABLE); 258 | 259 | gnss_configure(); 260 | 261 | /* Set up timer to drop stratum */ 262 | chVTSet(&stratum_demote_timer, NTPD_STRATUM_DEMOTE_TIMER_PERIOD, cb_stratum_demote, NULL); 263 | 264 | palEnableLineEvent(LINE_GNSS_PPS, PAL_EVENT_MODE_RISING_EDGE); 265 | palSetLineCallback(LINE_GNSS_PPS, pps_set_time_cb, NULL); 266 | 267 | uint32_t i; 268 | uint32_t read_length; 269 | 270 | eventflags_t flags; 271 | while(true) 272 | { 273 | if(0 != chEvtWaitOneTimeout(EVENT_MASK(1), TIME_MS2I(100))) 274 | { 275 | /* Event Received */ 276 | flags = chEvtGetAndClearFlags(&serial_gnss_listener); 277 | if (flags & CHN_INPUT_AVAILABLE) 278 | { 279 | /* Data available read here.*/ 280 | read_length = sdReadTimeout(&SD3, gnss_input_buffer, sizeof(gnss_input_buffer), TIME_IMMEDIATE); 281 | 282 | i = 0; 283 | while(read_length--) 284 | { 285 | gnss_ubx_rx(gnss_input_buffer[i++]); 286 | } 287 | } 288 | } 289 | 290 | //watchdog_feed(WATCHDOG_DOG_GNSS); 291 | } 292 | }; -------------------------------------------------------------------------------- /firmware/gnss.h: -------------------------------------------------------------------------------- 1 | #ifndef __GNSS_H__ 2 | #define __GNSS_H__ 3 | 4 | typedef struct { 5 | uint8_t gnss_id; 6 | uint8_t sv_id; 7 | uint8_t cn0; 8 | int8_t elevation; 9 | int16_t azimuth; 10 | int16_t psr_res; 11 | uint32_t flags; 12 | } __attribute__((packed)) gnss_status_sv_t; 13 | /* packet to allow memcpy from ubx */ 14 | 15 | typedef struct { 16 | bool fix; 17 | uint32_t time_accuracy_ns; 18 | int32_t time_nanosecond; 19 | 20 | uint64_t gnss_timestamp; 21 | int32_t lat; 22 | int32_t lon; 23 | int32_t alt; 24 | uint32_t h_acc; 25 | uint32_t v_acc; 26 | 27 | gnss_status_sv_t svs[32]; 28 | uint8_t svs_count; 29 | uint8_t svs_acquired_count; 30 | uint8_t svs_locked_count; 31 | uint8_t svs_nav_count; 32 | } gnss_status_t; 33 | 34 | extern gnss_status_t gnss_status; 35 | 36 | THD_FUNCTION(gnss_thread, arg); 37 | 38 | #endif /* __GNSS_H__ */ -------------------------------------------------------------------------------- /firmware/gnss_parse.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include "lwip/def.h" 4 | 5 | #include 6 | 7 | typedef struct 8 | { 9 | uint32_t itow; 10 | uint16_t year; 11 | uint8_t month; // jan = 1 12 | uint8_t day; 13 | uint8_t hour; // 24 14 | uint8_t min; 15 | uint8_t sec; 16 | uint8_t valid; 17 | uint32_t tAcc; 18 | int32_t nano; 19 | uint8_t fixtype; 20 | uint8_t flags; 21 | uint8_t flags2; 22 | uint8_t numsv; 23 | // 1e-7 mm mm 24 | int32_t lon, lat, height, hMSL; 25 | // mm mm 26 | uint32_t hAcc, vAcc; 27 | // mm/s mm/s mm/s mm/s 28 | int32_t velN, velE, velD, gSpeed; // millimeters 29 | } __attribute__((packed)) nav_pvt_t; 30 | 31 | typedef struct 32 | { 33 | uint32_t itow; 34 | uint8_t version; 35 | uint8_t num_svs; 36 | uint8_t _reserved1; 37 | uint8_t _reserved2; 38 | } __attribute__((packed)) nav_sat_header_t; 39 | 40 | typedef struct 41 | { 42 | uint8_t gnss_id; 43 | uint8_t sv_id; 44 | uint8_t cn0; 45 | int8_t elevation; 46 | int16_t azimuth; 47 | int16_t psr_res; 48 | uint32_t flags; 49 | } __attribute__((packed)) nav_sat_sv_t; 50 | 51 | extern bool pps_set_time; 52 | extern RTCDateTime time_set_timespec; 53 | 54 | extern struct tm time_ref_candidate_tm; 55 | extern uint32_t time_ref_candidate_tm_ms; 56 | extern uint32_t time_ref_candidate_s; 57 | extern uint32_t time_ref_candidate_f; 58 | 59 | static virtual_timer_t cancel_pps_set_timer; 60 | 61 | static void cb_cancel_pps_set(void *arg) 62 | { 63 | (void)arg; 64 | 65 | pps_set_time = false; 66 | } 67 | 68 | void gnss_parse(uint8_t *buffer) 69 | { 70 | if(buffer[2] == 0x01 && buffer[3] == 0x07) /* NAV-PVT */ 71 | { 72 | nav_pvt_t *pvt = (nav_pvt_t *)(&buffer[6]); 73 | 74 | struct tm tm; 75 | memset(&tm, 0, sizeof(tm)); 76 | tm.tm_year = pvt->year - 1900; 77 | tm.tm_mon = pvt->month - 1; 78 | tm.tm_mday = pvt->day; 79 | tm.tm_hour = pvt->hour; 80 | tm.tm_min = pvt->min; 81 | tm.tm_sec = pvt->sec; 82 | 83 | gnss_status.fix = (pvt->fixtype == 0x03) || (pvt->fixtype == 0x04); 84 | gnss_status.time_accuracy_ns = pvt->tAcc; 85 | 86 | gnss_status.gnss_timestamp = mktime(&tm); 87 | gnss_status.lat = pvt->lat; 88 | gnss_status.lon = pvt->lon; 89 | gnss_status.alt = pvt->height; 90 | gnss_status.h_acc = pvt->hAcc; 91 | gnss_status.v_acc = pvt->vAcc; 92 | 93 | if(gnss_status.fix) 94 | { 95 | pps_set_time = false; 96 | rtcConvertStructTmToDateTime(&tm, 0, &time_set_timespec); 97 | /* Don't set time in the 1.5s before midnight - TODO: Handle rollover properly instead */ 98 | if(time_set_timespec.millisecond < 86398500) 99 | { 100 | /* Next second */ 101 | time_set_timespec.millisecond += 1000; 102 | 103 | /* Set candidate reference timestamps */ 104 | rtcConvertDateTimeToStructTm(&time_set_timespec, &time_ref_candidate_tm, &time_ref_candidate_tm_ms); 105 | time_ref_candidate_s = htonl((uint32_t)mktime(&time_ref_candidate_tm) - DIFF_SEC_1970_2036); 106 | time_ref_candidate_f = htonl((NTP_MS_TO_FS_U32 * time_ref_candidate_tm_ms)); 107 | 108 | pps_set_time = true; 109 | /* Set up timer to cancel if the PPS is overdue (>1000ms after we've received this) */ 110 | chVTSet(&cancel_pps_set_timer, TIME_MS2I(1000), cb_cancel_pps_set, NULL); 111 | } 112 | } 113 | } 114 | else if(buffer[2] == 0x01 && buffer[3] == 0x35) /* NAV-SAT */ 115 | { 116 | nav_sat_header_t *sat_header = (nav_sat_header_t *)(&buffer[6]); 117 | 118 | /* Copy SVs into status struct */ 119 | memcpy((void *)gnss_status.svs, &buffer[6+8], (sat_header->num_svs * 12)); 120 | gnss_status.svs_count = sat_header->num_svs; 121 | 122 | nav_sat_sv_t *sat_sv; 123 | 124 | gnss_status.svs_acquired_count = 0; 125 | gnss_status.svs_locked_count = 0; 126 | gnss_status.svs_nav_count = 0; 127 | 128 | for(uint32_t i = 0; i < sat_header->num_svs; i++) 129 | { 130 | sat_sv = (nav_sat_sv_t *)(&buffer[6+8+(12*i)]); 131 | 132 | if((sat_sv->flags & 0x7) >= 2) 133 | { 134 | /* SV acquired */ 135 | gnss_status.svs_acquired_count++; 136 | } 137 | 138 | if((sat_sv->flags & 0x7) >= 4) 139 | { 140 | /* SV locked (note: acquired are included) */ 141 | gnss_status.svs_locked_count++; 142 | } 143 | 144 | if(((sat_sv->flags & 0x8) >> 3) == 1) 145 | { 146 | /* Used in navigation solution */ 147 | gnss_status.svs_nav_count++; 148 | } 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /firmware/ip_link.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | 5 | static struct netif* _ip_netif_ptr = NULL; 6 | 7 | static bool _ip_link_is_up = false; 8 | 9 | uint32_t app_ip_link_status(void) 10 | { 11 | if(_ip_link_is_up && _ip_netif_ptr != NULL) 12 | { 13 | if(netif_dhcp_data(_ip_netif_ptr)->state == 10) // DHCP_STATE_BOUND = 10 14 | { 15 | return APP_IP_LINK_STATUS_BOUND; 16 | } 17 | else 18 | { 19 | return APP_IP_LINK_STATUS_UPBUTNOIP; 20 | } 21 | } 22 | else 23 | { 24 | return APP_IP_LINK_STATUS_DOWN; 25 | } 26 | } 27 | 28 | void ip_link_up_cb(void *p) 29 | { 30 | _ip_netif_ptr = (struct netif*)p; 31 | 32 | dhcp_start(_ip_netif_ptr); 33 | 34 | _ip_link_is_up = true; 35 | palSetLine(LINE_LED1); 36 | } 37 | 38 | void ip_link_down_cb(void *p) 39 | { 40 | _ip_netif_ptr = (struct netif*)p; 41 | 42 | dhcp_stop(_ip_netif_ptr); 43 | 44 | _ip_link_is_up = false; 45 | palClearLine(LINE_LED1); 46 | } -------------------------------------------------------------------------------- /firmware/ip_link.h: -------------------------------------------------------------------------------- 1 | #ifndef __IP_LINK_H__ 2 | #define __IP_LINK_H__ 3 | 4 | #define APP_IP_LINK_STATUS_DOWN 0 5 | #define APP_IP_LINK_STATUS_UPBUTNOIP 1 6 | #define APP_IP_LINK_STATUS_BOUND 2 7 | 8 | uint32_t app_ip_link_status(void); 9 | 10 | void ip_link_up_cb(void *p); 11 | void ip_link_down_cb(void *p); 12 | 13 | #endif /* __IP_LINK_H__ */ -------------------------------------------------------------------------------- /firmware/main.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | 5 | #include "lwipthread.h" 6 | 7 | static THD_WORKING_AREA(watchdog_service_wa, 128); 8 | static THD_WORKING_AREA(ntpd_wa, 4096); 9 | static THD_WORKING_AREA(gnss_wa, 128); 10 | 11 | static uint8_t _macAddress[] = {0xC2, 0xAF, 0x51, 0x03, 0xCF, 0x47}; 12 | static const lwipthread_opts_t lwip_opts = { 13 | .macaddress = _macAddress, 14 | .address = 0, 15 | .netmask = 0, 16 | .gateway = 0, 17 | .addrMode = NET_ADDRESS_DHCP, 18 | .ourHostName = "stm32ntpd", 19 | .link_up_cb = ip_link_up_cb, 20 | .link_down_cb = ip_link_down_cb 21 | }; 22 | 23 | int main(void) 24 | { 25 | halInit(); 26 | chSysInit(); 27 | 28 | /* For some really weird reason the ethernet peripheral requires the PLL SAI clock to be on (both STM32F4 & STM32F7) */ 29 | // Enable PLLSAI 30 | RCC->CR |= RCC_CR_PLLSAION; 31 | // Wait for PLLSAI to lock 32 | while(!(RCC->CR & RCC_CR_PLLSAIRDY)); // wait for PLLSAI to lock 33 | 34 | /* Set up watchdog */ 35 | watchdog_init(); 36 | chThdCreateStatic(watchdog_service_wa, sizeof(watchdog_service_wa), HIGHPRIO, watchdog_service_thread, NULL); 37 | 38 | /* Set up IP stack */ 39 | lwipInit(&lwip_opts); 40 | 41 | web_init(); 42 | 43 | /* NTPD Thread */ 44 | chThdCreateStatic(ntpd_wa, sizeof(ntpd_wa), NORMALPRIO, ntpd_thread, NULL); 45 | 46 | /* GNSS Thread */ 47 | chThdCreateStatic(gnss_wa, sizeof(gnss_wa), LOWPRIO, gnss_thread, NULL); 48 | 49 | while(true) 50 | { 51 | watchdog_feed(WATCHDOG_DOG_MAIN); 52 | chThdSleepMilliseconds(100); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /firmware/main.h: -------------------------------------------------------------------------------- 1 | #ifndef __MAIN_H__ 2 | #define __MAIN_H__ 3 | 4 | #include "ch.h" 5 | #include "hal.h" 6 | 7 | #define MIN(x,y) ((x) < (y) ? (x) : (y)) 8 | #define MAX(x,y) ((x) > (y) ? (x) : (y)) 9 | 10 | /* Round up N to the next multiple of S */ 11 | #define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S)) 12 | 13 | /* Mark binary constants as known GCC extension to suppress warning */ 14 | #define GCC_BINARY(x) (__extension__ (x)) 15 | 16 | #define _token_concat(x,y) x##y 17 | #define token_concat(x,y) _token_concat(x,y) 18 | 19 | #define nibble_to_hex(i) ((i) <= 9 ? '0' + (i) : 'A' - 10 + (i)) 20 | 21 | #define ITERATOR_DEF() uint8_t iterator = 0 22 | #define ITERATOR_VAR() iterator < 1 ? "/" : iterator < 2 ? "-" : "\\" 23 | #define ITERATOR_INC() iterator = iterator < 2 ? iterator + 1 : 0 24 | 25 | #include "config.h" 26 | 27 | #include "watchdog.h" 28 | #include "ip_link.h" 29 | #include "ntpd.h" 30 | #include "gnss.h" 31 | #include "web/web.h" 32 | 33 | #endif /* __MAIN_H__ */ -------------------------------------------------------------------------------- /firmware/ntpd.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include "lwip/api.h" 4 | #include "lwip/netif.h" 5 | 6 | #include 7 | 8 | ntpd_status_t ntpd_status = { 9 | .status = NTPD_UNSYNC, 10 | .requests_count = 0, 11 | .stratum = 16 12 | }; 13 | 14 | typedef struct 15 | { 16 | uint8_t li_vn_mode; 17 | 18 | uint8_t stratum; 19 | uint8_t poll; 20 | uint8_t precision; 21 | 22 | uint32_t rootDelay; 23 | 24 | uint16_t rootDispersion_s; 25 | uint16_t rootDispersion_f; 26 | 27 | uint32_t refId; 28 | 29 | uint32_t refTm_s; 30 | uint32_t refTm_f; 31 | 32 | uint32_t origTm_s; 33 | uint32_t origTm_f; 34 | 35 | uint32_t rxTm_s; 36 | uint32_t rxTm_f; 37 | 38 | uint32_t txTm_s; 39 | uint32_t txTm_f; 40 | 41 | } ntp_packet_t; 42 | 43 | /* From GNSS PPS */ 44 | extern uint32_t time_ref_s; 45 | extern uint32_t time_ref_f; 46 | 47 | THD_FUNCTION(ntpd_thread, arg) 48 | { 49 | (void)arg; 50 | struct netconn *conn; 51 | struct netbuf *buf; 52 | err_t err; 53 | uint16_t buf_data_len; 54 | 55 | ntp_packet_t *ntp_packet_ptr; 56 | RTCDateTime ntpd_datetime; 57 | struct tm tm_; 58 | uint32_t tm_ms_; 59 | 60 | chRegSetThreadName("ntpd"); 61 | 62 | /* Create a new UDP connection handle */ 63 | conn = netconn_new(NETCONN_UDP); 64 | if(conn == NULL) 65 | { 66 | chThdExit(MSG_RESET); 67 | } 68 | 69 | netconn_bind(conn, IP_ADDR_ANY, 123); 70 | 71 | while (true) 72 | { 73 | err = netconn_recv(conn, &buf); 74 | if (err == ERR_OK) 75 | { 76 | palSetLine(LINE_LED2); 77 | 78 | netbuf_data(buf, (void **)&ntp_packet_ptr, &buf_data_len); 79 | 80 | if(buf_data_len < 48 || buf_data_len > 2048) 81 | { 82 | netbuf_delete(buf); 83 | continue; 84 | } 85 | 86 | ntp_packet_ptr->li_vn_mode = (0 << 6) | (4 << 3) | (4); // Leap Warning: None, Version: NTPv4, Mode: 4 - Server 87 | 88 | ntp_packet_ptr->stratum = ntpd_status.stratum; 89 | ntp_packet_ptr->poll = 5; // 32s 90 | ntp_packet_ptr->precision = -10; // ~1ms 91 | 92 | ntp_packet_ptr->rootDelay = 0; // Delay from GPS clock is ~zero 93 | ntp_packet_ptr->rootDispersion_s = 0; 94 | ntp_packet_ptr->rootDispersion_f = htonl(NTP_MS_TO_FS_U16 * 1.0); // 1ms 95 | ntp_packet_ptr->refId = ('G') | ('P' << 8) | ('S' << 16) | ('\0' << 24); 96 | 97 | /* Move client's transmit timestamp into origin fields */ 98 | ntp_packet_ptr->origTm_s = ntp_packet_ptr->txTm_s; 99 | ntp_packet_ptr->origTm_f = ntp_packet_ptr->txTm_f; 100 | 101 | ntp_packet_ptr->refTm_s = time_ref_s; 102 | ntp_packet_ptr->refTm_f = time_ref_f; 103 | 104 | rtcGetTime(&RTCD1, &ntpd_datetime); 105 | rtcConvertDateTimeToStructTm(&ntpd_datetime, &tm_, &tm_ms_); 106 | 107 | ntp_packet_ptr->rxTm_s = htonl(mktime(&tm_) - DIFF_SEC_1970_2036); 108 | ntp_packet_ptr->rxTm_f = htonl((NTP_MS_TO_FS_U32 * tm_ms_)); 109 | 110 | /* Copy into transmit timestamp fields */ 111 | ntp_packet_ptr->txTm_s = ntp_packet_ptr->rxTm_s; 112 | ntp_packet_ptr->txTm_f = ntp_packet_ptr->rxTm_f; 113 | 114 | netconn_send(conn, buf); 115 | netbuf_delete(buf); 116 | 117 | ntpd_status.requests_count++; 118 | 119 | palClearLine(LINE_LED2); 120 | } 121 | } 122 | }; -------------------------------------------------------------------------------- /firmware/ntpd.h: -------------------------------------------------------------------------------- 1 | #ifndef __NTPD_H__ 2 | #define __NTPD_H__ 3 | 4 | #define NTP_MS_TO_FS_U32 (4294967296.0 / 1000.0) 5 | #define NTP_MS_TO_FS_U16 (65536.0 / 1000.0) 6 | 7 | /* Number of seconds between 1970 and Feb 7, 2036 06:28:16 UTC (epoch 1) */ 8 | #define DIFF_SEC_1970_2036 ((uint32_t)2085978496L) 9 | 10 | typedef enum { 11 | NTPD_UNSYNC = 0, 12 | NTPD_IN_LOCK = 1, 13 | NTPD_IN_HOLDOVER = 2, 14 | NTPD_DEGRADED = 3 15 | } ntpd_status_status_t; 16 | 17 | typedef struct { 18 | ntpd_status_status_t status; 19 | uint32_t requests_count; 20 | uint8_t stratum; 21 | } ntpd_status_t; 22 | 23 | extern ntpd_status_t ntpd_status; 24 | 25 | THD_FUNCTION(ntpd_thread, arg); 26 | 27 | #endif /* __NTPD_H__ */ -------------------------------------------------------------------------------- /firmware/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2-1.cfg] 2 | source [find target/stm32f4x.cfg] 3 | init 4 | $_TARGETNAME configure -rtos ChibiOS -------------------------------------------------------------------------------- /firmware/tools/.gitignore: -------------------------------------------------------------------------------- 1 | ntptest 2 | -------------------------------------------------------------------------------- /firmware/tools/flash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/firmware/tools/flash.wav -------------------------------------------------------------------------------- /firmware/tools/ntptest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * To compile: $ gcc ntptest.c -lm -o ntptest 4 | * 5 | * (C) 2014 David Lettier. http://www.lettier.com/ 6 | * (C) 2020 Phil Crump 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define SHORTNTPFS_TO_MS (1000.0 / 65536.0) 24 | 25 | #define NTPFS_TO_MS (1000.0 / 4294967296.0) 26 | #define NTPFS_TO_US (1000000.0 / 4294967296.0) 27 | #define NTP_NS_TO_FS (4294967296.0 / 1000000000.0 ) 28 | #define DIFF_SEC_1970_2036 ((uint32_t)2085978496L) 29 | 30 | #define NTP_SEC_FRAC_TO_S64(s, f) ((int64_t)(((uint64_t)(s) << 32) | (uint32_t)(f))) 31 | #define NTP_TIMESTAMP_TO_S64(t) NTP_SEC_FRAC_TO_S64(ntohl((t).sec), ntohl((t).frac)) 32 | 33 | #define NTP_TIMESTAMP_DELTA 2208988800ull 34 | 35 | #define LI(packet) (uint8_t) ((packet.li_vn_mode & 0xC0) >> 6) // (li & 11 000 000) >> 6 36 | #define VN(packet) (uint8_t) ((packet.li_vn_mode & 0x38) >> 3) // (vn & 00 111 000) >> 3 37 | #define MODE(packet) (uint8_t) ((packet.li_vn_mode & 0x07) >> 0) // (mode & 00 000 111) >> 0 38 | 39 | int portno = 123; // NTP UDP port number. 40 | 41 | // Structure that defines the 48 byte NTP packet protocol. 42 | 43 | typedef struct 44 | { 45 | 46 | uint8_t li_vn_mode; // Eight bits. li, vn, and mode. 47 | // li. Two bits. Leap indicator. 48 | // vn. Three bits. Version number of the protocol. 49 | // mode. Three bits. Client will pick mode 3 for client. 50 | 51 | uint8_t stratum; // Eight bits. Stratum level of the local clock. 52 | uint8_t poll; // Eight bits. Maximum interval between successive messages. 53 | int8_t precision; // Eight bits. Precision of the local clock. 54 | 55 | uint16_t rootDelay_s; // 16 bits. Total round trip delay time seconds. 56 | uint16_t rootDelay_f; // 16 bits. Total round trip delay time fraction of a second. 57 | 58 | uint16_t rootDispersion_s; // 16 bits. Max error aloud from primary clock source seconds. 59 | uint16_t rootDispersion_f; // 16 bits. Max error aloud from primary clock source fraction of a second. 60 | 61 | uint32_t refId; // 32 bits. Reference clock identifier. 62 | 63 | uint32_t refTm_s; // 32 bits. Reference time-stamp seconds. 64 | uint32_t refTm_f; // 32 bits. Reference time-stamp fraction of a second. 65 | 66 | uint32_t origTm_s; // 32 bits. Originate time-stamp seconds. 67 | uint32_t origTm_f; // 32 bits. Originate time-stamp fraction of a second. 68 | 69 | uint32_t rxTm_s; // 32 bits. Received time-stamp seconds. 70 | uint32_t rxTm_f; // 32 bits. Received time-stamp fraction of a second. 71 | 72 | uint32_t txTm_s; // 32 bits and the most important field the client cares about. Transmit time-stamp seconds. 73 | uint32_t txTm_f; // 32 bits. Transmit time-stamp fraction of a second. 74 | 75 | } ntp_packet; // Total: 384 bits or 48 bytes. 76 | 77 | void error( char* msg ) 78 | { 79 | perror( msg ); // Print the error message to stderr. 80 | exit( 0 ); // Quit the process. 81 | } 82 | 83 | int main( int argc, char* argv[ ] ) 84 | { 85 | int sockfd, n; // Socket file descriptor and the n return result from writing/reading from the socket. 86 | char target_name[128]; 87 | 88 | if(argc != 2) 89 | { 90 | printf("Usage: ./ntptest \n"); 91 | return 1; 92 | } 93 | 94 | strncpy(target_name, argv[1], 127); 95 | 96 | if(strlen(target_name) < 1) 97 | { 98 | printf("Usage: ./ntptest \n"); 99 | return 1; 100 | } 101 | 102 | printf("Target: %s\n", target_name); 103 | 104 | ntp_packet packet; 105 | memset( &packet, 0, sizeof( ntp_packet ) ); 106 | //li = 0, vn = 3, and mode = 3. 107 | *((char *)&packet + 0) = 0x1b; // Represents 27 in base 10 or 00011011 in base 2. 108 | 109 | // Create a UDP socket, convert the host-name to an IP address, set the port number, 110 | // connect to the server, send the packet, and then read in the return packet. 111 | 112 | struct sockaddr_in serv_addr; // Server address data structure. 113 | struct hostent *server; // Server data structure. 114 | 115 | sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // Create a UDP socket. 116 | 117 | if ( sockfd < 0 ) 118 | error( "ERROR opening socket" ); 119 | 120 | server = gethostbyname(target_name); // Convert URL to IP. 121 | 122 | if ( server == NULL ) 123 | error( "ERROR, no such host" ); 124 | 125 | bzero( ( char* ) &serv_addr, sizeof( serv_addr ) ); 126 | serv_addr.sin_family = AF_INET; 127 | 128 | // Copy the server's IP address to the server address structure. 129 | bcopy( ( char* )server->h_addr, ( char* ) &serv_addr.sin_addr.s_addr, server->h_length ); 130 | 131 | // Convert the port number integer to network big-endian style and save it to the server address structure. 132 | serv_addr.sin_port = htons(123); 133 | 134 | // Call up the server using its IP address and port number. 135 | if ( connect( sockfd, ( struct sockaddr * ) &serv_addr, sizeof( serv_addr) ) < 0 ) 136 | error( "ERROR connecting" ); 137 | 138 | // Set Origin timestamp in packet 139 | struct timespec tv; 140 | clock_gettime(CLOCK_REALTIME, &tv); 141 | packet.txTm_s = htonl( (int32_t)(tv.tv_sec - DIFF_SEC_1970_2036) ); 142 | packet.txTm_f = htonl( tv.tv_nsec * NTP_NS_TO_FS ); 143 | 144 | // Send it the NTP packet it wants. If n == -1, it failed. 145 | n = write( sockfd, ( char* ) &packet, sizeof( ntp_packet ) ); 146 | 147 | if ( n < 0 ) 148 | error( "ERROR writing to socket" ); 149 | 150 | // Wait and receive the packet back from the server. If n == -1, it failed. 151 | n = read( sockfd, ( char* ) &packet, sizeof( ntp_packet ) ); 152 | 153 | // Set Received Timestamp 154 | clock_gettime(CLOCK_REALTIME, &tv); 155 | 156 | if ( n < 0 ) 157 | error( "ERROR reading from socket" ); 158 | 159 | // Convert all fields from network byte order back to host byte order 160 | packet.refId = ntohl( packet.refId ); 161 | 162 | packet.refTm_s = ntohl( packet.refTm_s ); 163 | packet.refTm_f = ntohl( packet.refTm_f ); 164 | 165 | packet.origTm_s = ntohl( packet.origTm_s ); 166 | packet.origTm_f = ntohl( packet.origTm_f ); 167 | 168 | packet.rxTm_s = ntohl( packet.rxTm_s ); 169 | packet.rxTm_f = ntohl( packet.rxTm_f ); 170 | 171 | packet.txTm_s = ntohl( packet.txTm_s ); 172 | packet.txTm_f = ntohl( packet.txTm_f ); 173 | 174 | packet.rootDelay_s = ntohl( packet.rootDelay_s ); 175 | packet.rootDelay_f = ntohl( packet.rootDelay_f ); 176 | 177 | packet.rootDispersion_s = ntohl( packet.rootDispersion_s ); 178 | packet.rootDispersion_f = ntohl( packet.rootDispersion_f ); 179 | 180 | packet.refId = ntohl( packet.refId ); 181 | 182 | // Extract the 32 bits that represent the time-stamp seconds (since NTP epoch) from when the packet left the server. 183 | // Subtract 70 years worth of seconds from the seconds since 1900. 184 | // This leaves the seconds since the UNIX epoch of 1970. 185 | // (1900)------------------(1970)**************************************(Time Packet Left the Server) 186 | 187 | time_t txTm = ( time_t ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA ); 188 | 189 | char gpsRefId[5]; 190 | strncpy(gpsRefId, (char *)&(packet.refId), 4); 191 | gpsRefId[4] = '\0'; 192 | 193 | printf(" - Server Stratum: %"PRIu8"\n", packet.stratum); 194 | printf(" - Reference Identifier: %s\n", gpsRefId); 195 | printf(" - Reference Sync Timestamp: %"PRIu32".%"PRIu32"\n", packet.refTm_s, packet.refTm_f); 196 | float precision_raw = pow(2, packet.precision); 197 | printf(" - Server Clock Precision: "); 198 | if(precision_raw < 1e-7) 199 | { 200 | printf("%.1fns\n", precision_raw * 1e9); 201 | } 202 | else if(precision_raw < 1e-4) 203 | { 204 | printf("%.3fus\n", precision_raw * 1e6); 205 | } 206 | else 207 | { 208 | printf("%.3fms\n", precision_raw * 1e3); 209 | } 210 | printf(" - Server Clock Root Delay: %.2fms\n", (1000 * packet.rootDelay_s) + (SHORTNTPFS_TO_MS * packet.rootDelay_f)); 211 | printf(" - Server Clock Dispersion: %.2fms\n", (1000 * packet.rootDispersion_s) + (SHORTNTPFS_TO_MS * packet.rootDispersion_f)); 212 | printf("\n"); 213 | printf(" - Client Origin Timestamp: %"PRIu32".%"PRIu32"\n", packet.origTm_s, packet.origTm_f); 214 | printf(" - Server Received Timestamp: %"PRIu32".%"PRIu32"\n", packet.rxTm_s, packet.rxTm_f); 215 | printf(" - Server Transmitted Timestamp: %"PRIu32".%"PRIu32"\n", packet.txTm_s, packet.txTm_f); 216 | printf(" - [Client Received Timestamp]: %"PRIu32".%"PRIu32"\n", (int32_t)(tv.tv_sec - DIFF_SEC_1970_2036), (uint32_t)(tv.tv_nsec * NTP_NS_TO_FS )); 217 | printf("\n"); 218 | printf(" - - Client -> Server: %+.3fms\n", (1000 * (packet.rxTm_s - packet.origTm_s)) + NTPFS_TO_MS * (packet.rxTm_f - packet.origTm_f)); 219 | printf(" - - Server Internal Delay: %+.3fms\n", NTPFS_TO_MS * (packet.txTm_f - packet.rxTm_f)); 220 | printf(" - - Server -> Client: %+.3fms\n", NTPFS_TO_MS * ((int64_t)(tv.tv_nsec * NTP_NS_TO_FS ) - packet.txTm_f)); 221 | printf("\n"); 222 | 223 | int64_t t1, t2, t3, t4, offset; 224 | 225 | t2 = NTP_SEC_FRAC_TO_S64(packet.rxTm_s, packet.rxTm_f); 226 | t1 = NTP_SEC_FRAC_TO_S64(packet.origTm_s, packet.origTm_f); 227 | t3 = NTP_SEC_FRAC_TO_S64((int32_t)(tv.tv_sec - DIFF_SEC_1970_2036), (uint32_t)(tv.tv_nsec * NTP_NS_TO_FS )); 228 | t4 = NTP_SEC_FRAC_TO_S64(packet.txTm_s, packet.txTm_f); 229 | /* Clock offset calculation according to RFC 4330 */ 230 | offset = ((t2 - t1) + (t3 - t4)) / 2; 231 | 232 | int32_t sec = (int32_t)((uint64_t)offset >> 32); 233 | int32_t frac = (uint32_t)((uint64_t)offset); 234 | 235 | printf(" - SNTP Calculated Clock Offset: %+.3fms\n", (sec * 1000) + (NTPFS_TO_MS * frac)); 236 | 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /firmware/tools/udp_bc_rx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket 4 | from time import time, sleep 5 | 6 | #UDP_IP = '192.168.100.195' 7 | UDP_IP = '' 8 | UDP_PORT = 11236 9 | 10 | class bcolors: 11 | HEADER = '\033[95m' 12 | OKBLUE = '\033[94m' 13 | OKGREEN = '\033[92m' 14 | WARNING = '\033[93m' 15 | FAIL = '\033[91m' 16 | OFF = '\033[0m' 17 | BOLD = '\033[1m' 18 | UNDERLINE = '\033[4m' 19 | 20 | send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 21 | #send_sock.settimeout(1.0) 22 | 23 | #sock = socket.socket(socket.AF_INET, # Internet 24 | # socket.SOCK_DGRAM) # UDP 25 | send_sock.bind((UDP_IP, UDP_PORT)) 26 | 27 | start_time = 0 28 | az_received = 0 29 | 30 | while True: 31 | 32 | data, addr = send_sock.recvfrom(1024) # buffer size is 1024 bytes 33 | 34 | print("RX!", "".join("%02x" % b for b in data)) 35 | print("".join("%c" % b for b in data)) 36 | #ts = int.from_bytes(data[0:4], byteorder='little', signed=False) 37 | #print(f"TS: %d" % (ts)) 38 | 39 | #if data[4] == 0xFF: 40 | # # ADC Packet 41 | # joystick_el_raw = int.from_bytes(data[5:9], byteorder='little', signed=False) 42 | # joystick_az_raw = int.from_bytes(data[9:13], byteorder='little', signed=False) 43 | 44 | # print(f"[Joystick] Elevation: 0x%08X, Azimuth: 0x%08X" % (joystick_el_raw, joystick_az_raw)) 45 | -------------------------------------------------------------------------------- /firmware/tools/udp_debug_rx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket 4 | from time import time, sleep 5 | 6 | #UDP_IP = '192.168.100.195' 7 | UDP_IP = '10.42.0.10' 8 | UDP_PORT = 123 9 | 10 | class bcolors: 11 | HEADER = '\033[95m' 12 | OKBLUE = '\033[94m' 13 | OKGREEN = '\033[92m' 14 | WARNING = '\033[93m' 15 | FAIL = '\033[91m' 16 | OFF = '\033[0m' 17 | BOLD = '\033[1m' 18 | UNDERLINE = '\033[4m' 19 | 20 | send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 21 | send_sock.settimeout(1.0) 22 | 23 | #sock = socket.socket(socket.AF_INET, # Internet 24 | # socket.SOCK_DGRAM) # UDP 25 | #sock.bind((UDP_IP, UDP_PORT)) 26 | 27 | start_time = 0 28 | az_received = 0 29 | 30 | while True: 31 | 32 | MESSAGE = bytearray.fromhex('0102030405') 33 | #MESSAGE.extend((SPEED.to_bytes(2, byteorder='little', signed=True))) 34 | send_sock.sendto(MESSAGE, (UDP_IP, UDP_PORT)) 35 | 36 | data, addr = send_sock.recvfrom(1024) # buffer size is 1024 bytes 37 | 38 | print("RX!", "".join("%02x" % b for b in data)) 39 | ts = int.from_bytes(data[0:4], byteorder='little', signed=False) 40 | print(f"TS: %d" % (ts)) 41 | 42 | if data[4] == 0xFF: 43 | # ADC Packet 44 | joystick_el_raw = int.from_bytes(data[5:9], byteorder='little', signed=False) 45 | joystick_az_raw = int.from_bytes(data[9:13], byteorder='little', signed=False) 46 | 47 | print(f"[Joystick] Elevation: 0x%08X, Azimuth: 0x%08X" % (joystick_el_raw, joystick_az_raw)) 48 | 49 | #else: 50 | # print(hex(sid)) 51 | 52 | #send_sock.close() 53 | sleep(1) -------------------------------------------------------------------------------- /firmware/watchdog.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | static const WDGConfig wdg_cfg = { 4 | // 40 KHz input clock from LSI 5 | .pr = STM32_IWDG_PR_256, // Prescaler = 256 => 156.25Hz 6 | .rlr = STM32_IWDG_RL(79), // Reload value = 79 (slightly over 500ms), 7 | }; 8 | 9 | static bool wdg_initialised = false; 10 | static uint32_t mask = WATCHDOG_MASK; 11 | static uint32_t fed = 0; 12 | 13 | void watchdog_init(void) 14 | { 15 | if(wdg_initialised) 16 | { 17 | return; 18 | } 19 | 20 | wdgStart(&WDGD1, &wdg_cfg); 21 | wdg_initialised = true; 22 | } 23 | 24 | THD_FUNCTION(watchdog_service_thread, arg) 25 | { 26 | (void)arg; 27 | 28 | /* Should have been init-ed by main(), but let's make sure */ 29 | watchdog_init(); 30 | 31 | while(1) 32 | { 33 | if((mask & fed) == mask) 34 | { 35 | fed = 0; 36 | 37 | // Feed the hardware dog 38 | wdgReset(&WDGD1); 39 | } 40 | 41 | chThdSleepMilliseconds(20); 42 | } 43 | } 44 | 45 | /* To be called by other threads */ 46 | void watchdog_feed(uint32_t dog) 47 | { 48 | fed |= ((1 << dog) & 0xFFFFFFFF); 49 | } -------------------------------------------------------------------------------- /firmware/watchdog.h: -------------------------------------------------------------------------------- 1 | #ifndef __WATCHDOG_H__ 2 | #define __WATCHDOG_H__ 3 | 4 | #define WATCHDOG_DOG_MAIN 0 5 | #define WATCHDOG_DOG_NTPD 1 6 | 7 | 8 | #define WATCHDOG_MASK ((1 << WATCHDOG_DOG_MAIN) \ 9 | | (0 << WATCHDOG_DOG_NTPD)) 10 | 11 | void watchdog_init(void); 12 | 13 | THD_FUNCTION(watchdog_service_thread, arg); 14 | 15 | void watchdog_feed(uint32_t dog); 16 | 17 | #endif /* __WATCHDOG_H__ */ -------------------------------------------------------------------------------- /firmware/web/.gitignore: -------------------------------------------------------------------------------- 1 | htdist/ 2 | -------------------------------------------------------------------------------- /firmware/web/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source_dir="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)" 4 | cd $source_dir 5 | 6 | COLOUR_GREEN='\033[0;32m' 7 | COLOUR_YELLOW='\033[1;33m' 8 | COLOUR_PURPLE='\033[0;35m' 9 | COLOUR_RED='\033[0;31m' 10 | COLOUR_OFF='\033[0;00m' 11 | CLEAR_LINE='\033[2K' 12 | 13 | _ERROR_="${COLOUR_RED}[ERROR]${COLOUR_OFF}" 14 | _INFO_="${COLOUR_PURPLE}[INFO]${COLOUR_OFF}" 15 | _DEBUG_="${COLOUR_YELLOW}[DEBUG]${COLOUR_OFF}" 16 | 17 | opt_verbose=false 18 | 19 | printf_v() { 20 | if $opt_verbose; 21 | then 22 | printf $1; 23 | fi 24 | } 25 | printf_nv() { 26 | if ! $opt_verbose; 27 | then 28 | printf $1; 29 | fi 30 | } 31 | 32 | ## Read Flags 33 | OPTIND=1 34 | while getopts ":v" opt; do 35 | case "$opt" in 36 | v) # Debug 37 | opt_verbose=true 38 | ;; 39 | ?) # Illegal Option 40 | echo -e "$_ERROR_ Illegal option '$OPTARG'" 41 | exit 3 42 | ;; 43 | esac 44 | done 45 | for i in `seq 2 $OPTIND`; do shift; done 46 | 47 | printf "$_INFO_ Packing web files.. "; 48 | printf_v "\n" 49 | 50 | printf_v "Clearing htdist/..\n" 51 | rm -fr htdist/ 52 | cp -r htsrc/ htdist/ 53 | 54 | printf_v "Compressing files..\n" 55 | for file in $(find htdist/ -type f) 56 | do 57 | gzip -9 $file 58 | done 59 | 60 | printf_v "Packing headers..\n" 61 | for file in $(find htdist/ -type f -name *.gz) 62 | do 63 | file_path=${file%/*} 64 | file_basename=$(basename $file) 65 | file_headername=${file_basename//./_}.h 66 | pushd $file_path > /dev/null 67 | xxd -i $file_basename > "${file_headername}" 68 | rm $file_basename 69 | popd > /dev/null 70 | printf_v " - ${file_basename}\n" 71 | done 72 | printf_nv "${COLOUR_GREEN}OK${COLOUR_OFF}\n"; -------------------------------------------------------------------------------- /firmware/web/htsrc/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/firmware/web/htsrc/favicon.png -------------------------------------------------------------------------------- /firmware/web/htsrc/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Open Sans", Arial, sans-serif; 3 | font-size: 16px; 4 | } 5 | h1, #footer { 6 | text-align: center; 7 | } 8 | #footer { 9 | padding-top: 14px; 10 | padding-bottom: 14px; 11 | font-size: 14px; 12 | font-weight: 500; 13 | color: #666; 14 | } 15 | #gnss-ui-table { 16 | width: 100%; 17 | } 18 | #gnss-svs-table { 19 | font-size: 11px; 20 | } 21 | #gnss-svs-table tr td { 22 | padding-left: 3px; 23 | padding-right: 3px; 24 | padding-bottom: 0px; 25 | } 26 | .Orbits { 27 | background: #FFF; 28 | margin-top:1em; 29 | margin-left:2em; 30 | margin-bottom:1em; 31 | } 32 | 33 | .Orbits .Variables { 34 | display:inline-block; 35 | vertical-align:top; 36 | width:28em; 37 | height:44em; 38 | overflow:auto; 39 | position:relative; 40 | } 41 | 42 | .Orbits .Variables table { 43 | font-family:'Roboto Mono',monospace; 44 | color:#000; 45 | border-spacing:.5em; 46 | display:block; 47 | position:absolute; 48 | left:50%; 49 | transform:translateX(-50%); 50 | } 51 | 52 | .Orbits .Variables table tr { 53 | text-align:center; 54 | } 55 | 56 | .Orbits .Variables table td { 57 | font-size:.85em; 58 | } 59 | 60 | #Orbits-Drawing { 61 | display:inline-block; 62 | vertical-align:top; 63 | margin-top:.5em; 64 | width: 350px; 65 | height: 350px; 66 | } 67 | 68 | #Orbits-Drawing .sats-label { 69 | font-size:12px; 70 | fill:#454545; 71 | font-weight:bold; 72 | stroke:transparent; 73 | } 74 | 75 | #Orbits-Drawing .satellite circle { 76 | stroke-width:2px; 77 | } 78 | 79 | #Orbits-Drawing svg { 80 | font-family:sans-serif; 81 | font-size:10px; 82 | text-anchor:middle; 83 | fill:none; 84 | stroke:#000; 85 | width:100%; 86 | height:100%; 87 | } -------------------------------------------------------------------------------- /firmware/web/htsrc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | STM32 NTP Server 10 | 11 | 12 | 13 | 14 | 15 | 16 |

STM32 NTP Server

17 | 18 | 19 | 20 | 26 | 28 | 35 | 36 |
21 |

GNSS Receiver

22 |
23 |

NTP Server

24 |
25 |
27 | 29 |
30 |
31 |
32 |
33 |
34 |
37 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /firmware/web/htsrc/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const doc_gnss_ui = document.getElementById("gnss-ui"); 4 | const doc_gnss_svs_ui = document.getElementById("gnss-svs-ui"); 5 | const doc_ntpd_ui = document.getElementById("ntpd-ui"); 6 | 7 | var status_svs = []; 8 | var status_gnss = {}; 9 | var status_ntpd = {}; 10 | 11 | const gnss_lookup_letter = { 12 | 0: "G", 13 | 2: "E", 14 | 3: "C", 15 | 6: "R" 16 | } 17 | 18 | const gnss_lookup_name = { 19 | 0: "GPS", 20 | 1: "SBAS", 21 | 2: "GAL", 22 | 3: "BDS", 23 | 4: "IMES", 24 | 5: "QZSS", 25 | 6: "GLO" 26 | } 27 | 28 | const gnss_lookup_colour = { 29 | 0: "green", 30 | 2: "blue", 31 | 3: "red", 32 | 6: "yellow" 33 | } 34 | 35 | function ts_ms_to_string(ts, ts_ms) 36 | { 37 | return (new Date((ts * 1000) + ts_ms)).toLocaleString(); 38 | } 39 | 40 | var gnss_ui = { 41 | view: function() 42 | { 43 | return m("div", [ 44 | m("p", m("b", "Status: "), `${status_gnss.fix ? "Locked" : "Searching.."}`), 45 | m("p", m("b", "Time: "), `${ts_ms_to_string(status_gnss.ts, status_gnss.ts_ms)}`), 46 | m("p", m("b", "Position: "), `${status_gnss.lat}°, ${status_gnss.lon}°, ${status_gnss.alt}m`), 47 | m("p", m("b", "SVs: "), `${status_gnss.svs_locked} locked, ${status_gnss.svs_nav} used in soln.`), 48 | m("p", m("b", "Timepulse Accuracy: "), `${status_gnss.fix ? `+/- ${status_gnss.time_accuracy_ns}ns` : "N/A"}`), 49 | ]); 50 | } 51 | } 52 | 53 | m.mount(doc_gnss_ui, gnss_ui); 54 | 55 | 56 | var gnss_svs_ui = { 57 | view: function() 58 | { 59 | return m("table", {id: 'gnss-svs-table'}, 60 | m("tr", [ 61 | m("th", "GNSS"), 62 | m("th", "SV"), 63 | m("th", "C/N0"), 64 | m("th", "Used in soln.") 65 | ]), 66 | status_svs.map(function(sv_obj) { 67 | return m("tr", [ 68 | m("td", gnss_lookup_name[sv_obj.gnss] || "??"), 69 | m("td", `${gnss_lookup_letter[sv_obj.gnss] || ""}${sv_obj.sv}`), 70 | m("td", sv_obj.cn0), 71 | m("td", `${sv_obj.nav ? "Y" : "N"}`) 72 | ]); 73 | }) 74 | ); 75 | } 76 | } 77 | 78 | m.mount(doc_gnss_svs_ui, gnss_svs_ui); 79 | 80 | const ntpd_status_lookup_string = { 81 | 0: "Unsynchronized", 82 | 1: "In Lock", 83 | 2: "In Holdover", 84 | 3: "Degraded (Holdover expired)" 85 | } 86 | 87 | var ntpd_ui = { 88 | view: function() 89 | { 90 | return m("div", [ 91 | m("p", m("b", "Status: "), `${ntpd_status_lookup_string[status_ntpd.status] || "??"}`), 92 | m("p", m("b", "Reported Stratum: "), `${status_ntpd.stratum}`), 93 | m("p", m("b", "Requests: "), `${status_ntpd.requests_count}`), 94 | ]); 95 | } 96 | } 97 | 98 | m.mount(doc_ntpd_ui, ntpd_ui); 99 | 100 | function status_update() 101 | { 102 | m.request({ 103 | method: "GET", 104 | url: "/api/status" 105 | }) 106 | .then(function(result) 107 | { 108 | //console.log(result); 109 | status_gnss = result.gnss; 110 | status_svs = result.svs; 111 | status_ntpd = result.ntpd; 112 | 113 | drawSVsPolar(); 114 | }); 115 | } 116 | 117 | status_update(); 118 | setInterval(status_update, 1000); 119 | 120 | 121 | /* SVs Polar Projection derived from https://github.com/berthubert/galmon */ 122 | /* (C) AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/ */ 123 | 124 | function flippedStereographic(x, y) { 125 | var cx = Math.cos(x), cy = Math.cos(y), k = 1 / (1 + cx * cy); 126 | return [k * cy * Math.sin(x), -k * Math.sin(y)]; 127 | } 128 | 129 | function drawSVsPolar() 130 | { 131 | var sats = d3.select("#Orbits-Drawing").html("").append("svg"); 132 | 133 | const width = 350; //sats.clientWidth; 134 | const height = 350; //sats.clientHeight; 135 | 136 | sats.attr("width", width); 137 | sats.attr("height", height); 138 | 139 | var projection = d3.geoProjection(flippedStereographic) 140 | .scale(width * 0.40) 141 | .clipAngle(130) 142 | .rotate([0, -90]) 143 | .translate([width / 2 + 0.5, height / 2 + 0.5]) 144 | .precision(1); 145 | 146 | var path = d3.geoPath().projection(projection); 147 | 148 | sats.append("path") 149 | .datum(d3.geoCircle().center([0, 90]).radius(90)) 150 | .attr("stroke-width", 1.5) 151 | .attr("d", path); 152 | 153 | sats.append("path") 154 | .datum(d3.geoGraticule()) 155 | .attr("stroke-width", 0.15) 156 | .attr("d", path); 157 | // .attr("fill", "none").attr("stroke", "black").attr("width", "100%").attr("height", "100%"); 158 | 159 | 160 | sats.append("g") 161 | .selectAll("line") 162 | .data(d3.range(360)) 163 | .enter().append("line") 164 | .each(function(d) { 165 | var p0 = projection([d, 0]), 166 | p1 = projection([d, d % 10 ? -1 : -2]); 167 | 168 | d3.select(this) 169 | .attr("x1", p0[0]) 170 | .attr("y1", p0[1]) 171 | .attr("x2", p1[0]) 172 | .attr("y2", p1[1]); 173 | }); 174 | 175 | /* Azimuth Labels */ 176 | sats.append("g") 177 | .attr("fill", "black") 178 | .attr("stroke", "none") 179 | .selectAll("text") 180 | .data(d3.range(0, 360, 90)) 181 | .enter().append("text") 182 | .each(function(d) { 183 | var p = projection([d, -4]); 184 | d3.select(this).attr("x", p[0]).attr("y", p[1]); 185 | }) 186 | .attr("dy", "0.35em") 187 | .text(function(d) { return d === 0 ? "N" : d === 90 ? "E" : d === 180 ? "S" : d === 270 ? "W" : d + "°"; }) 188 | .attr("font-weight", "bold") 189 | .attr("font-size", 14); 190 | 191 | /* Elevation Labels */ 192 | sats.append("g") 193 | .attr("fill", "#A3ACA9") 194 | .attr("stroke", "none") 195 | .selectAll("text") 196 | .data(d3.range(10, 91, 10)) 197 | .enter().append("text") 198 | .each(function(d) { 199 | var p = projection([0, d]); 200 | d3.select(this).attr("x", p[0]).attr("y", p[1]); 201 | }) 202 | .attr("dy", "-0.4em") 203 | .text(function(d) { return d + "°"; }); 204 | 205 | sats.select('g.satellites').remove(); 206 | 207 | let points = sats 208 | .insert("g") 209 | .attr("class", "satellites") 210 | .selectAll('g.satellite') 211 | .data(status_svs) 212 | .enter() 213 | .append('g') 214 | .attr("transform", function(d) { 215 | var p = projection([d.az, d.el]); 216 | return 'translate(' + p[0] + ', ' + p[1] + ')'; 217 | }); 218 | 219 | points 220 | .attr('class', 'satellite') 221 | .append("circle") 222 | .attr("stroke", function(d) { 223 | return d.cn0 > 0 ? "transparent" : d[5]; 224 | }) 225 | .attr("r", function(d) { 226 | return d.cn0 > 0 ? d.cn0*0.125 : 3; 227 | }) 228 | .attr("fill", function(d) { 229 | return d.cn0 > 0 ? (gnss_lookup_colour[d.gnss] || "transparent") : "transparent"; 230 | }); 231 | 232 | points 233 | .attr("r", 50) 234 | .append("text") 235 | .attr("class", "sats-label") 236 | .attr('dy', function(d) { 237 | return d.cn0 > 0 ? `${10+(d.cn0/8)}px` : "1.3em"; 238 | }) 239 | .attr('dx', function(d) { 240 | return d.cn0 > 0 ? `${3+(d.cn0/8)}px` : "0.7em"; 241 | }) 242 | .text(function(d){return `${gnss_lookup_letter[d.gnss] || "?"}${d.sv}`}); 243 | } -------------------------------------------------------------------------------- /firmware/web/htsrc/mithril.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function e(e,t,n,r,o,l){return{tag:e,key:t,attrs:n,children:r,text:o,dom:l,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null==t||"boolean"==typeof t?null:"object"==typeof t?t:e("#",void 0,void 0,String(t),void 0,void 0)},e.normalizeChildren=function(t){var n=[];if(t.length){for(var r=null!=t[0]&&null!=t[0].key,o=1;o0&&(i.className=l.join(" ")),r[e]={tag:o,attrs:i}}(i),a):(a.tag=i,a)}if(i.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},i.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n},(a=function(e){if(!(this instanceof a))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=s(n,!0),l=s(r,!1),i=t._instance={resolvers:n,rejectors:r},u="function"==typeof setImmediate?setImmediate:setTimeout;function s(e,o){return function a(s){var f;try{if(!o||null==s||"object"!=typeof s&&"function"!=typeof s||"function"!=typeof(f=s.then))u(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",s);for(var t=0;t0||e(n)}}var r=n(l);try{e(n(o),r)}catch(e){r(e)}}c(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function l(e,t,l,i){t.push(function(t){if("function"!=typeof e)l(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&i===o.state&&o.retry()}var i=new a(function(e,t){n=e,r=t});return l(e,o.resolvers,n,!0),l(t,o.rejectors,r,!1),i},a.prototype.catch=function(e){return this.then(null,e)},a.prototype.finally=function(e){return this.then(function(t){return a.resolve(e()).then(function(){return t})},function(t){return a.resolve(e()).then(function(){return a.reject(t)})})},a.resolve=function(e){return e instanceof a?e:new a(function(t){t(e)})},a.reject=function(e){return new a(function(t,n){n(e)})},a.all=function(e){return new a(function(t,n){var r=e.length,o=0,l=[];if(0===e.length)t([]);else for(var i=0;i'+t.children+"",i=i.firstChild):i.innerHTML=t.children,t.dom=i.firstChild,t.domSize=i.childNodes.length,t.instance=[];for(var a,u=r.createDocumentFragment();a=i.firstChild;)t.instance.push(a),u.appendChild(a);w(e,u,o)}function h(e,t,n,r,o,l){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,l);else if(null==n||0===n.length)x(e,t,0,t.length);else{var i=null!=t[0]&&null!=t[0].key,a=null!=n[0]&&null!=n[0].key,u=0,f=0;if(!i)for(;f=f&&C>=u&&(w=t[S],b=n[C],w.key===b.key);)w!==b&&p(e,w,b,r,o,l),null!=b.dom&&(o=b.dom),S--,C--;for(;S>=f&&C>=u&&(d=t[f],h=n[u],d.key===h.key);)f++,u++,d!==h&&p(e,d,h,r,y(t,f,o),l);for(;S>=f&&C>=u&&u!==C&&d.key===b.key&&w.key===h.key;)g(e,w,E=y(t,f,o)),w!==h&&p(e,w,h,r,E,l),++u<=--C&&g(e,d,o),d!==b&&p(e,d,b,r,o,l),null!=b.dom&&(o=b.dom),f++,w=t[--S],b=n[C],d=t[f],h=n[u];for(;S>=f&&C>=u&&w.key===b.key;)w!==b&&p(e,w,b,r,o,l),null!=b.dom&&(o=b.dom),C--,w=t[--S],b=n[C];if(u>C)x(e,t,f,S+1);else if(f>S)s(e,n,u,C+1,r,o,l);else{var j,z,A=o,O=C-u+1,N=new Array(O),T=0,P=0,$=2147483647,I=0;for(P=0;P=u;P--){null==j&&(j=v(t,f,S+1));var L=j[(b=n[P]).key];null!=L&&($=L<$?L:-1,N[P-u]=L,w=t[L],t[L]=null,w!==b&&p(e,w,b,r,o,l),null!=b.dom&&(o=b.dom),I++)}if(o=A,I!==S-f+1&&x(e,t,f,S+1),0===I)s(e,n,u,C+1,r,o,l);else if(-1===$)for(T=(z=function(e){for(var t=[0],n=0,r=0,o=0,l=m.length=e.length,o=0;o>>1)+(r>>>1)+(n&r&1);e[t[a]]0&&(m[o]=t[n-1]),t[n]=o)}}for(n=t.length,r=t[n-1];n-- >0;)t[n]=r,r=m[r];return m.length=0,t}(N)).length-1,P=C;P>=u;P--)h=n[P],-1===N[P-u]?c(e,h,r,l,o):z[T]===P-u?T--:g(e,h,o),null!=h.dom&&(o=n[P].dom);else for(P=C;P>=u;P--)h=n[P],-1===N[P-u]&&c(e,h,r,l,o),null!=h.dom&&(o=n[P].dom)}}else{var R=t.lengthR&&x(e,t,u,t.length),n.length>R&&s(e,n,u,n.length,r,o,l)}}}function p(t,n,r,o,i,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,e.attrs=t.attrs,e.children=t.children,e.text=t.text,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&_(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children),t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(E(e,t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize,n.instance=t.instance)}(t,n,r,u,i);break;case"[":!function(e,t,n,r,o,l){h(e,t.children,n.children,r,o,l);var i=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var N=/[A-Z]/g;function T(e){return"-"+e.toLowerCase()}function P(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(N,T)}function $(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n)null!=(o=n[r])&&e.style.setProperty(P(r),String(o));else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(P(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(P(r))}}function I(){this._=n}function L(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new I,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function R(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function _(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return I.prototype=Object.create(null),I.prototype.handleEvent=function(e){var t,n=this["on"+e.type];"function"==typeof n?t=n.call(e.currentTarget,e):"function"==typeof n.handleEvent&&n.handleEvent(e),this._&&!1!==e.redraw&&(0,this._)(),!1===t&&(e.preventDefault(),e.stopPropagation())},function(t,r,o){if(!t)throw new TypeError("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var l=[],i=u(),a=t.namespaceURI;null==t.vnodes&&(t.textContent=""),r=e.normalizeChildren(Array.isArray(r)?r:[r]);var s=n;try{n="function"==typeof o?o:void 0,h(t,t.vnodes,r,l,null,"http://www.w3.org/1999/xhtml"===a?void 0:a)}finally{n=s}t.vnodes=r,null!=i&&u()!==i&&"function"==typeof i.focus&&i.focus();for(var c=0;c=0&&(o.splice(l,2),t(n,[],u)),null!=r&&(o.push(n,r),t(n,e(r),u))},redraw:u}}(u,requestAnimationFrame,console),c=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o=0&&(v+=e.slice(n,o)),s>=0&&(v+=(n<0?"?":"&")+u.slice(s,h));var m=c(a);return m&&(v+=(n<0&&s<0?"?":"&")+m),r>=0&&(v+=e.slice(r)),d>=0&&(v+=(r<0?"":"&")+u.slice(d)),v},h=function(e,t,n){var r=0;function o(e){return new t(e)}function l(e){return function(r,l){"string"!=typeof r?(l=r,r=r.url):null==l&&(l={});var i=new t(function(t,n){e(d(r,l.params),l,function(e){if("function"==typeof l.type)if(Array.isArray(e))for(var n=0;n=200&&e.target.status<300||304===e.target.status||/^file:\/\//i.test(t),a=e.target.response;if("json"===c?e.target.responseType||"function"==typeof n.extract||(a=JSON.parse(e.target.responseText)):c&&"text"!==c||null==a&&(a=e.target.responseText),"function"==typeof n.extract?(a=n.extract(e.target,n),i=!0):"function"==typeof n.deserialize&&(a=n.deserialize(a)),i)r(a);else{try{l=e.target.responseText}catch(e){l=a}var u=new Error(l);u.code=e.target.status,u.response=a,o(u)}}catch(e){o(e)}},"function"==typeof n.config&&(f=n.config(f,n,t)||f)!==h&&(l=f.abort,f.abort=function(){d=!0,l.call(this)}),null==u?f.send():"function"==typeof n.serialize?f.send(n.serialize(u)):u instanceof e.FormData?f.send(u):f.send(JSON.stringify(u))}),jsonp:l(function(t,n,o,l){var i=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,a=e.document.createElement("script");e[i]=function(t){delete e[i],a.parentNode.removeChild(a),o(t)},a.onerror=function(){delete e[i],a.parentNode.removeChild(a),l(new Error("JSONP request failed"))},a.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(i),e.document.documentElement.appendChild(a)})}}(window,a,s.redraw),p=s,v=function(){return i.apply(this,arguments)};v.m=i,v.trust=i.trust,v.fragment=i.fragment,v.mount=p.mount;var m=i,y=a,g=function(e){if(""===e||null==e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var t=e.split("&"),n={},r={},o=0;o-1&&u.pop();for(var c=0;c1&&"/"===l[l.length-1]&&(l=l.slice(0,-1))):l="/",{path:l,params:t<0?{}:g(e.slice(t+1,r))}},b=function(e){var t=w(e),n=Object.keys(t.params),r=[],o=new RegExp("^"+t.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,function(e,t,n){return null==t?"\\"+e:(r.push({k:t,r:"..."===n}),"..."===n?"(.*)":"."===n?"([^/]+)\\.":"([^/]+)"+(n||""))})+"$");return function(e){for(var l=0;l 15 | 16 | static char packet_buffer[WEB_MAX_PACKET_SIZE]; 17 | static char url_buffer[WEB_MAX_PATH_SIZE]; 18 | /** 19 | * @brief Decodes an URL sting. 20 | * @note The string is terminated by a zero or a separator. 21 | * 22 | * @param[in] url encoded URL string 23 | * @param[out] buf buffer for the processed string 24 | * @param[in] max max number of chars to copy into the buffer 25 | * @return The conversion status. 26 | * @retval false string converted. 27 | * @retval true the string was not valid or the buffer overflowed 28 | * 29 | * @notapi 30 | */ 31 | #define HEXTOI(x) (isdigit(x) ? (x) - '0' : (x) - 'a' + 10) 32 | static bool decode_url(const char *url, char *buf, size_t max) 33 | { 34 | while (true) { 35 | int h, l; 36 | unsigned char c = *url++; 37 | 38 | switch (c) { 39 | case 0: 40 | case '\r': 41 | case '\n': 42 | case '\t': 43 | case ' ': 44 | case '?': 45 | *buf = 0; 46 | return true; 47 | case '.': 48 | if (max <= 1) 49 | return false; 50 | 51 | h = *(url + 1); 52 | if (h == '.') 53 | return false; 54 | 55 | break; 56 | case '%': 57 | if (max <= 1) 58 | return false; 59 | 60 | h = tolower((int)*url++); 61 | if (h == 0) 62 | return false; 63 | if (!isxdigit(h)) 64 | return false; 65 | 66 | l = tolower((int)*url++); 67 | if (l == 0) 68 | return false; 69 | if (!isxdigit(l)) 70 | return false; 71 | 72 | c = (char)((HEXTOI(h) << 4) | HEXTOI(l)); 73 | break; 74 | default: 75 | if (max <= 1) 76 | return false; 77 | 78 | if (!isalnum(c) && (c != '_') && (c != '-') && (c != '+') && 79 | (c != '/')) 80 | return false; 81 | 82 | break; 83 | } 84 | 85 | *buf++ = c; 86 | max--; 87 | } 88 | } 89 | 90 | static void http_server_serve(struct netconn *conn) 91 | { 92 | struct netbuf *inbuf; 93 | err_t err; 94 | u16_t packetlen; 95 | 96 | /* Read the data from the port, blocking if nothing yet there. 97 | We assume the request (the part we care about) is in one netbuf */ 98 | err = netconn_recv(conn, &inbuf); 99 | 100 | if(err != ERR_OK) 101 | { 102 | netconn_close(conn); 103 | netbuf_delete(inbuf); 104 | return; 105 | } 106 | 107 | packetlen = netbuf_len(inbuf); 108 | /* Check we've got room for the packet */ 109 | if(packetlen >= WEB_MAX_PACKET_SIZE) 110 | { 111 | /* We haven't, fail */ 112 | netconn_close(conn); 113 | netbuf_delete(inbuf); 114 | return; 115 | } 116 | netbuf_copy(inbuf, packet_buffer, WEB_MAX_PACKET_SIZE); 117 | 118 | /* Is this an HTTP GET command? (only check the first 5 chars, since 119 | there are other formats for GET, and we're keeping it very simple )*/ 120 | if(packetlen>=5 && (0 == memcmp("GET /", packet_buffer, 5))) 121 | { 122 | if(!decode_url(packet_buffer + (4 * sizeof(char)), url_buffer, WEB_MAX_PATH_SIZE)) 123 | { 124 | /* URL decode failed.*/ 125 | netconn_close(conn); 126 | netbuf_delete(inbuf); 127 | return; 128 | } 129 | 130 | web_paths_get(conn, url_buffer); 131 | } 132 | 133 | /* Close the connection (server closes in HTTP) */ 134 | netconn_close(conn); 135 | 136 | /* Delete the buffer (netconn_recv gives us ownership, 137 | so we have to make sure to deallocate the buffer) */ 138 | netbuf_delete(inbuf); 139 | } 140 | 141 | /** 142 | * Stack area for the http thread. 143 | */ 144 | THD_WORKING_AREA(wa_http_server, WEB_THREAD_STACK_SIZE); 145 | 146 | /** 147 | * HTTP server thread. 148 | */ 149 | THD_FUNCTION(http_server, p) { 150 | struct netconn *conn, *newconn; 151 | err_t err; 152 | 153 | (void)p; 154 | chRegSetThreadName("http"); 155 | 156 | conn = netconn_new(NETCONN_TCP); 157 | if(conn == NULL) 158 | { 159 | chThdExit(MSG_RESET); 160 | } 161 | 162 | netconn_bind(conn, IP_ADDR_ANY, WEB_THREAD_PORT); 163 | 164 | netconn_listen(conn); 165 | 166 | /* Goes to the final priority after initialization.*/ 167 | chThdSetPriority(WEB_THREAD_PRIORITY); 168 | 169 | while (true) { 170 | err = netconn_accept(conn, &newconn); 171 | if (err != ERR_OK) 172 | continue; 173 | http_server_serve(newconn); 174 | netconn_delete(newconn); 175 | } 176 | } 177 | 178 | void web_init(void) 179 | { 180 | chThdCreateStatic(wa_http_server, sizeof(wa_http_server), NORMALPRIO + 1, http_server, NULL); 181 | } 182 | 183 | /** @} */ 184 | -------------------------------------------------------------------------------- /firmware/web/web.h: -------------------------------------------------------------------------------- 1 | /* 2 | ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /** 18 | * @file web.h 19 | * @brief HTTP server wrapper thread macros and structures. 20 | * @addtogroup WEB_THREAD 21 | * @{ 22 | */ 23 | 24 | #ifndef WEB_H 25 | #define WEB_H 26 | 27 | #ifndef WEB_THREAD_STACK_SIZE 28 | #define WEB_THREAD_STACK_SIZE 1024 29 | #endif 30 | 31 | #ifndef WEB_THREAD_PORT 32 | #define WEB_THREAD_PORT 80 33 | #endif 34 | 35 | #ifndef WEB_THREAD_PRIORITY 36 | #define WEB_THREAD_PRIORITY (LOWPRIO + 2) 37 | #endif 38 | 39 | #ifndef WEB_MAX_PACKET_SIZE 40 | #define WEB_MAX_PACKET_SIZE 2048 41 | #endif 42 | 43 | #ifndef WEB_MAX_PATH_SIZE 44 | #define WEB_MAX_PATH_SIZE 128 45 | #endif 46 | 47 | #ifndef WEB_MAX_POSTBODY_SIZE 48 | #define WEB_MAX_POSTBODY_SIZE 128 49 | #endif 50 | 51 | void web_init(void); 52 | 53 | #endif /* WEB_H */ 54 | 55 | /** @} */ 56 | -------------------------------------------------------------------------------- /firmware/web/web_paths.c: -------------------------------------------------------------------------------- 1 | #include "../main.h" 2 | 3 | #include "lwip/api.h" 4 | #include "lwip/netif.h" 5 | 6 | #include 7 | #include 8 | #include "chprintf.h" 9 | 10 | static char http_response[4096]; 11 | 12 | static const char http_robots_txt_hdr[] = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n"; 13 | static const char http_robots_txt_body[] = "User-agent: *\r\nDisallow: /"; 14 | static void web_path_robots_txt(struct netconn *conn); 15 | 16 | /* 403 - Forbidden */ 17 | //static const char http_403_json_hdr[] = "HTTP/1.0 HTTP/1.0 403 Forbidden\r\nContent-type: application/javascript\r\n\r\n"; 18 | //static const char http_403_hdr[] = "HTTP/1.0 HTTP/1.0 403 Forbidden\r\nContent-type: text/html\r\n\r\n"; 19 | //static const char http_403_body[] = "

Forbidden

"; 20 | 21 | /* 404 - File Not Found */ 22 | static const char http_404_hdr[] = "HTTP/1.0 404 Not Found\r\nContent-type: text/html\r\n\r\n"; 23 | static const char http_404_body[] = "

Path not found

"; 24 | static void web_path_404(struct netconn *conn); 25 | 26 | /* HTML */ 27 | //static const char http_html_hdr[] = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n"; 28 | static const char http_html_gz_hdr[] = "HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-type: text/html\r\n\r\n"; 29 | #include "htdist/index_html_gz.h" 30 | static void web_path_index_html(struct netconn *conn); 31 | 32 | /* CSS */ 33 | //static const char http_css_hdr[] = "HTTP/1.0 200 OK\r\nContent-type: text/css\r\n\r\n"; 34 | static const char http_css_gz_hdr[] = "HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-type: text/css\r\n\r\n"; 35 | #include "htdist/index_css_gz.h" 36 | static void web_path_index_css(struct netconn *conn); 37 | 38 | /* Javascript */ 39 | //static const char http_javascript_hdr[] = "HTTP/1.0 200 OK\r\nContent-type: application/javascript\r\n\r\n"; 40 | static const char http_javascript_gz_hdr[] = "HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-type: application/javascript\r\n\r\n"; 41 | #include "htdist/index_js_gz.h" 42 | static void web_path_index_js(struct netconn *conn); 43 | #include "htdist/mithril_min_js_gz.h" 44 | static void web_path_mithril_min_js(struct netconn *conn); 45 | #include "htdist/d3_v4_min_js_gz.h" 46 | static void web_path_d3_v4_min_js(struct netconn *conn); 47 | 48 | /* JSON API */ 49 | static const char http_json_hdr[] = "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n"; 50 | static void web_path_api_status(struct netconn *conn); 51 | 52 | /* PNG Image */ 53 | static const char http_png_gz_hdr[] = "HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-type: image/png\r\n\r\n"; 54 | #include "htdist/favicon_png_gz.h" 55 | static void web_path_favicon_png(struct netconn *conn); 56 | 57 | /* Binary Files */ 58 | //static const char http_binary_hdr[] = "HTTP/1.0 200 OK\r\nContent-Type:application/octet-stream\r\n\r\n"; 59 | //static const char http_binary_gz_hdr[] = "HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Type:application/octet-stream\r\n\r\n"; 60 | 61 | void web_paths_get(struct netconn *conn, char *url_buffer) 62 | { 63 | if(strcmp("/", url_buffer) == 0 || strcmp("/index.html", url_buffer) == 0) 64 | { 65 | web_path_index_html(conn); 66 | } 67 | else if(strcmp("/api/status", url_buffer) == 0) 68 | { 69 | web_path_api_status(conn); 70 | } 71 | else if(strcmp("/index.css", url_buffer) == 0) 72 | { 73 | web_path_index_css(conn); 74 | } 75 | else if(strcmp("/index.js", url_buffer) == 0) 76 | { 77 | web_path_index_js(conn); 78 | } 79 | else if(strcmp("/mithril.min.js", url_buffer) == 0) 80 | { 81 | web_path_mithril_min_js(conn); 82 | } 83 | else if(strcmp("/d3.v4.min.js", url_buffer) == 0) 84 | { 85 | web_path_d3_v4_min_js(conn); 86 | } 87 | else if(strcmp("/favicon.png", url_buffer) == 0) 88 | { 89 | web_path_favicon_png(conn); 90 | } 91 | else if(strcmp("/robots.txt", url_buffer) == 0) 92 | { 93 | web_path_robots_txt(conn); 94 | } 95 | else 96 | { 97 | web_path_404(conn); 98 | } 99 | } 100 | 101 | static void web_path_404(struct netconn *conn) 102 | { 103 | netconn_write(conn, http_404_hdr, sizeof(http_404_hdr)-1, NETCONN_NOCOPY); 104 | netconn_write(conn, http_404_body, sizeof(http_404_body), NETCONN_NOCOPY); 105 | } 106 | 107 | static void web_path_index_html(struct netconn *conn) 108 | { 109 | netconn_write(conn, http_html_gz_hdr, sizeof(http_html_gz_hdr)-1, NETCONN_NOCOPY); 110 | netconn_write(conn, index_html_gz, index_html_gz_len, NETCONN_NOCOPY); 111 | } 112 | 113 | static void web_path_robots_txt(struct netconn *conn) 114 | { 115 | netconn_write(conn, http_robots_txt_hdr, sizeof(http_robots_txt_hdr)-1, NETCONN_NOCOPY); 116 | netconn_write(conn, http_robots_txt_body, sizeof(http_robots_txt_body)-1, NETCONN_NOCOPY); 117 | } 118 | 119 | static void web_path_index_css(struct netconn *conn) 120 | { 121 | netconn_write(conn, http_css_gz_hdr, sizeof(http_css_gz_hdr)-1, NETCONN_NOCOPY); 122 | netconn_write(conn, index_css_gz, index_css_gz_len, NETCONN_NOCOPY); 123 | } 124 | 125 | static void web_path_index_js(struct netconn *conn) 126 | { 127 | netconn_write(conn, http_javascript_gz_hdr, sizeof(http_javascript_gz_hdr)-1, NETCONN_NOCOPY); 128 | netconn_write(conn, index_js_gz, index_js_gz_len, NETCONN_NOCOPY); 129 | } 130 | 131 | static void web_path_mithril_min_js(struct netconn *conn) 132 | { 133 | netconn_write(conn, http_javascript_gz_hdr, sizeof(http_javascript_gz_hdr)-1, NETCONN_NOCOPY); 134 | netconn_write(conn, mithril_min_js_gz, mithril_min_js_gz_len, NETCONN_NOCOPY); 135 | } 136 | 137 | static void web_path_d3_v4_min_js(struct netconn *conn) 138 | { 139 | netconn_write(conn, http_javascript_gz_hdr, sizeof(http_javascript_gz_hdr)-1, NETCONN_NOCOPY); 140 | netconn_write(conn, d3_v4_min_js_gz, d3_v4_min_js_gz_len, NETCONN_NOCOPY); 141 | } 142 | 143 | static void web_path_favicon_png(struct netconn *conn) 144 | { 145 | netconn_write(conn, http_png_gz_hdr, sizeof(http_png_gz_hdr)-1, NETCONN_NOCOPY); 146 | netconn_write(conn, favicon_png_gz, favicon_png_gz_len, NETCONN_NOCOPY); 147 | } 148 | 149 | 150 | static void web_path_api_status(struct netconn *conn) 151 | { 152 | int str_ptr; 153 | 154 | netconn_write(conn, http_json_hdr, sizeof(http_json_hdr)-1, NETCONN_NOCOPY); 155 | 156 | RTCDateTime time_get_timespec; 157 | rtcGetTime(&RTCD1, &time_get_timespec); 158 | struct tm _tm; 159 | uint32_t _tm_ms; 160 | rtcConvertDateTimeToStructTm(&time_get_timespec, &_tm, &_tm_ms); 161 | uint32_t _ts = (uint32_t)mktime(&_tm); 162 | 163 | str_ptr = chsnprintf(http_response, 4096, 164 | "{" 165 | ); 166 | 167 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 168 | "\"gnss\": { \ 169 | \"ts\": %ld \ 170 | ,\"ts_ms\": %ld ", 171 | _ts, 172 | _tm_ms 173 | ); 174 | 175 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 176 | ",\"fix\": %s \ 177 | ,\"time_accuracy_ns\": %ld \ 178 | ,\"lat\": %.4f \ 179 | ,\"lon\": %.4f \ 180 | ,\"alt\": %.1f \ 181 | ,\"svs_locked\": %d \ 182 | ,\"svs_nav\": %d }", 183 | gnss_status.fix ? "true" : "false", 184 | gnss_status.time_accuracy_ns, 185 | ((float)gnss_status.lat * 1e-7), 186 | ((float)gnss_status.lon * 1e-7), 187 | ((float)gnss_status.alt * 1e-3), 188 | gnss_status.svs_locked_count, 189 | gnss_status.svs_nav_count 190 | ); 191 | 192 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 193 | ",\"svs\": [" 194 | ); 195 | 196 | for(uint32_t i = 0; i < gnss_status.svs_count; i++) 197 | { 198 | if((gnss_status.svs[i].flags & 0x7) < 4 // Not time-synchronized 199 | || gnss_status.svs[i].cn0 < 5) 200 | { 201 | continue; 202 | } 203 | 204 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 205 | "{ \"gnss\": %d, \"sv\": %d, \"cn0\": %d, \"el\": %d, \"az\": %d, \"nav\": %s },", 206 | gnss_status.svs[i].gnss_id, 207 | gnss_status.svs[i].sv_id, 208 | gnss_status.svs[i].cn0, 209 | gnss_status.svs[i].elevation, 210 | gnss_status.svs[i].azimuth, 211 | (((gnss_status.svs[i].flags & 0x8) >> 3) == 1) ? "true" : "false" 212 | ); 213 | } 214 | 215 | if(http_response[str_ptr-1] == ',') 216 | { 217 | /* Overwrite tailing comma */ 218 | http_response[str_ptr-1] = ']'; 219 | } 220 | else 221 | { 222 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 223 | "]" 224 | ); 225 | } 226 | 227 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 228 | ",\"ntpd\": { \ 229 | \"status\": %ld, \ 230 | \"requests_count\": %ld, \ 231 | \"stratum\": %d \ 232 | }", 233 | ntpd_status.status, 234 | ntpd_status.requests_count, 235 | ntpd_status.stratum 236 | ); 237 | 238 | str_ptr += chsnprintf(&http_response[str_ptr], (4096 - str_ptr), 239 | "}" 240 | ); 241 | 242 | netconn_write(conn, http_response, str_ptr, NETCONN_NOFLAG); 243 | } 244 | -------------------------------------------------------------------------------- /firmware/web/web_paths.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_PATHS_H 2 | #define WEB_PATHS_H 3 | 4 | void web_paths_get(struct netconn *conn, char *url_buffer); 5 | 6 | void web_paths_post(struct netconn *conn, char *url_buffer, char *postbody_buffer); 7 | 8 | #endif /* WEB_PATHS_H */ -------------------------------------------------------------------------------- /hardware/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | wireviz *.yaml; 4 | rm *.html *.bom.tsv *.gv *.svg; 5 | -------------------------------------------------------------------------------- /hardware/gnss_receiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/hardware/gnss_receiver.png -------------------------------------------------------------------------------- /hardware/gnss_receiver.yaml: -------------------------------------------------------------------------------- 1 | connectors: 2 | X1: 3 | type: STM32F429ZI Nucleo Board 4 | subtype: CN11 5 | pinout: [PC10 (TXD), PC11 (RXD), PC12 (TP), PD2, VDD, E5V, BT0, GND, PF6, NC, PF7, 3V3, PA13, NRST, PA14, 3V3, PA15, 5V, GND, GND, "..."] 6 | X2: 7 | type: Uputronics MAX-M8Q GNSS Breakout Board 8 | pinout: [SDA, SCL, TXD, RXD, TP, "-", GND, VCC] 9 | 10 | cables: 11 | W1: 12 | length: 0.25 13 | colors: [BU, WH, YE, RD, BK] 14 | 15 | connections: 16 | - 17 | - X1: [1,2,3,19,16] 18 | - W1: [1,2,3,5,4] 19 | - X2: [4,3,5,7,8] -------------------------------------------------------------------------------- /images/gnss_receiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/images/gnss_receiver.png -------------------------------------------------------------------------------- /images/maximum_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/images/maximum_rate.png -------------------------------------------------------------------------------- /images/ntpq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/images/ntpq.png -------------------------------------------------------------------------------- /images/web_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philcrump/stm32-ntp-server/0ff2c3581a8918b2f62c3641bfe897db4cfd1f44/images/web_status.png --------------------------------------------------------------------------------