add astro-tests

This commit is contained in:
Edward Emelianov
2026-09-08 22:42:06 +03:00
parent 290a8757f6
commit 8200366090
8 changed files with 338 additions and 34 deletions

241
G4:G431/CORDIC/Readme.md Normal file
View File

@@ -0,0 +1,241 @@
# STM32 Astronomy & CORDIC Test Project
## Introduction
This project is designed for **STM32G4** microcontrollers (specifically the STM32G431) and provides a
framework for:
- **Astronomical coordinate transformations** (Alt-Az vs HA-Dec, RA/HA, Local Sidereal Time).
- **Atmospheric refraction** modelling using a single-precision adaptation of SOFA/ERFA algorithms.
- **Performance comparison** between standard `math.h` functions and the hardware-accelerated **CORDIC** unit
built into the STM32G4.
- **Interactive control** via a serial (UART) command interface.
- **Precise timing** measurements using a 32-bit timer.
All calculations are performed in **single-precision floating-point** for speed on microcontrollers without
hardware double-precision support.
---
## Hardware Platform
- **MCU**: STM32G431 (or any STM32G4 with CORDIC).
- **Clock**: HSE oscillator, system frequency defined by `SysFreq` (usually 170 MHz).
- **GPIO**:
- **PC6** - LED (active high).
- **PC13** - User button (pull-down, pressed = high).
- **USART1**:
- **PA9** - TX (alternate function 7).
- **PA10** - RX (alternate function 7).
- **Timers**:
- **SysTick** - 1-ms system tick (`Tms`).
- **TIM2** - 32-bit counter running at 1-MHz for microsecond measurements.
---
## Building and Flashing
The project is intended for use with **GCC ARM** toolchain. Key build definitions:
- `SysFreq` - system clock frequency in Hz (e.g., 170000000).
- `BUILD_NUMBER` and `BUILD_DATE` are defined in `version.inc` (generated by `make`).
Flashing can be done via SWD using st-link (`make flash`), via DFU (`make dfuboot`) or via USART1 bootloader
(`make boot`).
---
## Software Architecture
The system runs an infinite loop in `main()` that:
- Services the UART (receiving and sending data).
- Parses incoming commands via `parse_cmd()`.
- Blinks the LED every 500 ms.
- Detects button presses and measures press/release duration using TIM2.
---
## Module Descriptions
### 1. `hardware.c/h` - Low-level initialisation
- `gpio_setup()`: configures GPIOs, enables SysTick (1 ms interrupt), and initialises TIM2 for microsecond counting.
- Provides macros for LED and key handling:
- `LED_ON()`, `LED_OFF()`, `LED_TOGG()`
- `KEY_PRESSED()` - returns true if PC13 is high.
- `timer_start()`, `timer_stop()`, `timer_read()` - control TIM2 for precise time measurements.
### 2. `usart.c/h` - UART1 with DMA and ring buffers
- Uses **circular DMA** for RX and **normal DMA** for TX.
- RX buffer size: 256 bytes (DMA) + 512 bytes (ring buffer).
- TX buffer size: 512 bytes (ring buffer) + 256 bytes (DMA).
- Interrupts:
- USART1 interrupt - detects end-of-line (`\n`) and triggers line processing.
- DMA1 Channel 1 (RX) - handles receive error (buffer overflow).
- DMA1 Channel 2 (TX) - signals transmit completion.
- Functions:
- `usart_setup(uint32_t baud)` - initialises UART at the given baud rate.
- `usart_sendstr(const char*)` - queues a string for transmission.
- `usart_getline()` - returns a pointer to a null-terminated buffer containing one line (ends with `\n`), or `NULL` if no complete line.
- `usart_process()` - must be called frequently to feed the TX buffer and move RX data from DMA to the ring buffer; returns status flags.
### 3. `commproto.cpp` - Command protocol
- Parses commands of the form:
```
command [args] [= value]
```
- Built-in commands and variables (see **Usage** below).
- Uses a **hash table** (compile-time hashing) to dispatch commands efficiently.
- Supports **getter** and **setter** for floating-point variables (az, dec, ha, ra, zd, humid, press, temp).
- Command results are sent back via the UART send function.
- Error codes are defined in `commproto.h`.
### 4. `astro.c/h` - Astronomical calculations
All angles are in **degrees** unless otherwise noted.
- **Time**:
- `MJD_from_unix(uint32_t t)` - converts Unix time (seconds since 1970-01-01) to Modified Julian Date (MJD).
- `LST_from_unix(uint32_t t)` - computes Local Sidereal Time (in hours) for the observer's longitude (`long_hrs`).
- **Coordinate transformations**:
- `altaz_to_hadec()` - convert Alt-Az to HA-Dec.
- `hadec_to_altaz()` - convert HA-Dec to Alt-Az.
- `ha_to_ra()` and `ra_to_ha()` - convert between Hour Angle and Right Ascension given LST.
- **Refraction** (single-precision adaptation of ERFA `eraRefco`):
- `refco_f32(float P, float T, float RH, float wavelength, float *refa, float *refb)` - computes A and B coefficients for the refraction model:
`dz = A*tan(z) + B*tan(z)^3` (z = zenith distance).
- `refraction(float P, float T, float RH, float zd)` - returns the refraction correction in degrees.
- The observer's **latitude** and **longitude** are fixed in the code (`lat_rad` and `long_hrs`). They may be changed by editing `astro.c`.
### 5. `cordic.c/h` - Hardware CORDIC wrappers
- Interfaces with the STM32G4 CORDIC coprocessor.
- Functions:
- `cordic_sincos(float angle, float *sin, float *cos)` - computes sine and cosine in one call.
- `cordic_sin(float angle)`, `cordic_cos(float angle)` - individual functions.
- `cordic_atan(float x)` - returns atan(x), x in [-1,1].
- `cordic_sqrt(float x)` - returns sqrt(x) for x > 0.
- `cordic_log(float x)` - returns natural logarithm of x (x > 0).
- **Precision**: Q1.31 fixed-point (32-bit) with 5 iterations (`CORDIC_CSR_PRECISION = 5`).
- Input scaling and normalisation are handled inside each function.
### 6. `strfunc.c/h` - String utilities
- Number formatting:
- `u2str()`, `i2str()`, `uhex2str()` - convert integers to strings.
- `float2str(float x, uint8_t prec)` - convert float to a compact string with optional scientific notation.
- Number parsing:
- `getnum()`, `getint()`, `getfloat()` - parse numbers (decimal, hex, binary, signed, floating with exponent).
- `omit_spaces()` - skip leading whitespace.
### 7. `ringbuffer.c/h` - Circular buffer
- Generic ring buffer for arbitrary data.
- Functions:
- `RB_write()`, `RB_read()`, `RB_readto()` (read until a delimiter), `RB_hasbyte()`, `RB_datalen()`, `RB_clearbuf()`.
### 8. `test.c/h` - Performance tests
- Runs a fixed number of iterations (`N_TESTS = 1000`) on random data.
- Measures execution time using TIM2 (microseconds).
- Tests available:
- `math` functions: `sinf`, `cosf`, `atanf`, `sqrtf`, `logf`.
- `CORDIC` functions: `sincos`, `sin`, `cos`, `atan`, `sqrt`, `log`.
- **Astronomy** tests:
- Coordinate transformations (Alt-Az vs HA-Dec) - measures time per conversion.
- Refraction cycle (ha-dec to alt-az, apply refraction and convert back) - includes `refraction()`.
- LST calculation - measures time per LST computation.
---
## Usage: UART Commands
Connect to the USART1 (default baud rate **115200**, 8N1) using a terminal program. The device echoes
responses to commands.
General syntax:
```
command [args] [= value]
```
- Without `= value`, the command acts as a **getter** (prints current value) or command that don't needs
argument.
- With `= value`, it acts as a **setter**.
All floating-point values are printed with 7 decimal places.
### Available Commands
| Command | Description |
|---------|-------------|
| `help` | Show this help message. |
| `testtimer` | Test the microsecond timer for 1 second (prints elapsed us). |
| `testc = <func>` | Test CORDIC function: `sincos`, `sin`, `cos`, `atan`, `sqrt`, `log`. Prints time in us. |
| `testm = <func>` | Test `math.h` function: `sin`, `cos`, `atan`, `sqrt`, `log`. Prints time in us. |
| `sincos = <angle>` | Compute sin/cos for given angle (degrees) using both `math.h` and CORDIC, print results. |
| `testastro = <func>` | Test astronomy functions: `coords` (transform cycle), `lst` (LST calculation), `refr` (refraction cycle). Prints time in us and ms. |
| `sets [= 0/1]` | Choose sine/cos implementation: `0` = `math.h`, `1` = CORDIC. |
| `time [= unix_sec]` | Show current Unix time, MJD, and LST. If `= unix_sec` is given, sets the system time. |
| `az [= value]` | Azimuth (degrees, -180..180). |
| `dec [= value]` | Declination (degrees, -90..90). |
| `ha [= value]` | Hour Angle (degrees, -180..180). |
| `ra [= value]` | Right Ascension (degrees, 0..360). |
| `zd [= value]` | Zenith Distance (degrees, 0..90). |
| `humid [= value]` | Relative humidity (0..1). |
| `press [= value]` | Atmospheric pressure (hPa, 0..1200). |
| `temp [= value]` | Temperature (degC, -273.15..100). |
| `hara = 0/1` | Convert HA to RA (`0`) or RA to HA (`1`) using current LST. Updates the corresponding variable. |
| `azhd = 0/1` | Convert Alt-Az to HA-Dec (`0`) or HA-Dec to Alt-Az (`1`). Updates relevant variables (az, zd or ha, dec, ra). |
| `refr` | Compute refraction (arcseconds) for current `zd`, pressure, temperature, humidity. Also prints A/B coefficients. |
**Note**: The `azhd` and `hara` commands automatically use the current system time (from the last `time` setter) to compute LST.
---
## Testing and Performance
The test commands (`testc`, `testm`, `testastro`) measure execution time over 1000 iterations on random input
data. This allows you to compare the speed of hardware CORDIC against software `math.h` and evaluate the
overall efficiency of the astronomical pipeline.
Example outputs (typical for STM32G4 @ 170 MHz):
```
testc = sincos
TIMEus=425
```
```
testm = sin
TIMEus=884
```
```
testastro = coords
TIMEus=159680
TIMEms=159
```
These numbers help in optimising real-time applications such as telescope control.
---
## Notes and Customisation
- **Observer coordinates**: The latitude (43.6535278deg) and longitude (41.44143375deg) are hard-coded in
`astro.c` as `lat_rad` and `long_hrs`. Modify these for your location.
- **CORDIC precision**: The number of iterations is set to 5 (`CORDIC_CSR_DEF`). You can increase it for
better accuracy at the cost of speed.
- **Refraction model**: The model works best for zenith distances up to ~80deg. It uses a wavelength of
0.55um (optical) by default.
- **Baud rate**: Default is 115200. Change the argument to `usart_setup()` in `main()` if needed.
- **DMA buffers**: Sizes are defined in `usart.h`. Adjust if you experience overruns.
---
## License
This project is released under the **GNU General Public License v3**. See the header of each source file for
details.

Binary file not shown.

View File

@@ -1,3 +1,4 @@
Readme.md
astro.c astro.c
astro.h astro.h
commproto.cpp commproto.cpp

View File

@@ -28,24 +28,12 @@ void sys_tick_handler(){
} }
static void timer_setup(){ static void timer_setup(){
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM3EN; RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN;
__DSB(); __DSB();
TIM3->CR1 = 0; // disable counter TIM2->CR1 = 0; // disable counter
TIM3->PSC = 84; // 85 MHz / 85 = 1 MHz TIM2->PSC = 84; // 85 MHz / 85 = 1 MHz
TIM3->ARR = 0xFFFFFFFF; // 32-bit auto-reload (maximum) TIM2->ARR = 0xFFFFFFFF; // 32-bit auto-reload (maximum)
} TIM2->EGR = TIM_EGR_UG;
void timer_start(){
TIM3->CNT = 0;
TIM3->CR1 = TIM_CR1_CEN;
}
void timer_stop(){
TIM3->CR1 = 0;
}
uint32_t timer_read(){
return TIM3->CNT;
} }
void gpio_setup(){ void gpio_setup(){
@@ -62,7 +50,7 @@ void gpio_setup(){
// count milliseconds // count milliseconds
SysTick_Config(SysFreq / 1000); SysTick_Config(SysFreq / 1000);
// Setup TIM3 for microsecond counting // Setup 32-bit TIM2 for microsecond counting
timer_setup(); timer_setup();
} }

View File

@@ -18,6 +18,8 @@
#pragma once #pragma once
#include <stm32g4.h>
// KEY (intpullup->0) - PC13 // KEY (intpullup->0) - PC13
// LED - PC6 // LED - PC6
#define KEY_PORT GPIOC #define KEY_PORT GPIOC
@@ -38,6 +40,15 @@ extern volatile uint32_t Tms;
void gpio_setup(); void gpio_setup();
void timer_start(); TRUE_INLINE void timer_start(){
void timer_stop(); TIM2->CNT = 0;
uint32_t timer_read(); TIM2->CR1 = TIM_CR1_CEN;
}
TRUE_INLINE void timer_stop(){
TIM2->CR1 = 0;
}
TRUE_INLINE uint32_t timer_read(){
return TIM2->CNT;
}

View File

@@ -20,6 +20,7 @@
#include <stdint.h> #include <stdint.h>
#include <stm32g4.h> #include <stm32g4.h>
#include "astro.h"
#include "test.h" #include "test.h"
#include "hardware.h" #include "hardware.h"
#include "cordic.h" #include "cordic.h"
@@ -90,38 +91,95 @@ static uint32_t run_test2(void (*gen)(), void (*func)(float, float*, float*)){
} }
// ------------- math.h tests ------------- // ------------- math.h tests -------------
uint32_t test_math_sin(){ uint32_t test_math_sin(){ // 0.9us per cycle
return run_test(fill_random_sin_cos, sinf); return run_test(fill_random_sin_cos, sinf);
} }
uint32_t test_math_cos(){ uint32_t test_math_cos(){ // 0.9us per cycle
return run_test(fill_random_sin_cos, cosf); return run_test(fill_random_sin_cos, cosf);
} }
uint32_t test_math_atan(){ uint32_t test_math_atan(){ // 0.9us per cycle
return run_test(fill_random_atan, atanf); return run_test(fill_random_atan, atanf);
} }
uint32_t test_math_sqrt(){ uint32_t test_math_sqrt(){ // 0.16us per cycle
return run_test(fill_random_sqrt, sqrtf); return run_test(fill_random_sqrt, sqrtf);
} }
uint32_t test_math_log(){ uint32_t test_math_log(){ // 0.9us per cycle
return run_test(fill_random_log, logf); return run_test(fill_random_log, logf);
} }
// ------------- CORDIC tests ------------- // ------------- CORDIC tests -------------
uint32_t test_cordic_sincos(){ uint32_t test_cordic_sincos(){ // 0.4us per cycle
return run_test2(fill_random_sin_cos, cordic_sincos); return run_test2(fill_random_sin_cos, cordic_sincos);
} }
uint32_t test_cordic_sin(){ uint32_t test_cordic_sin(){ // 0.4us per cycle
return run_test(fill_random_sin_cos, cordic_sin); return run_test(fill_random_sin_cos, cordic_sin);
} }
uint32_t test_cordic_cos(){ uint32_t test_cordic_cos(){ // 0.4us per cycle
return run_test(fill_random_sin_cos, cordic_cos); return run_test(fill_random_sin_cos, cordic_cos);
} }
uint32_t test_cordic_atan(){ uint32_t test_cordic_atan(){ // 0.12us per cycle
return run_test(fill_random_atan, cordic_atan); return run_test(fill_random_atan, cordic_atan);
} }
uint32_t test_cordic_sqrt(){ uint32_t test_cordic_sqrt(){ // 0.4us per cycle
return run_test(fill_random_sqrt, cordic_sqrt); return run_test(fill_random_sqrt, cordic_sqrt);
} }
uint32_t test_cordic_log(){ uint32_t test_cordic_log(){ // 0.4us per cycle
return run_test(fill_random_log, cordic_log); return run_test(fill_random_log, cordic_log);
} }
// ------------- Astronomy tests -------------
// test coordinates transformation: hor2eq and eq2hor
uint32_t test_astro_coordsTransform(){
volatile float az = 11.3f, alt = 89.3f, ha, dec;
timer_start();
for(int i = 0; i < N_TESTS; ++i){
// 160us per cycle for math.h sin/cos
// 15us per cycle for CORDIC sin/cos
altaz_to_hadec(alt, az, (float*)&ha, (float*)&dec);
hadec_to_altaz(ha, dec, (float*)&alt, (float*)&az);
if((az += 9.51f) > 359.99f) az -= 359.9f;
if((alt -= 1.76f) < 9.9f) alt += 79.f;
(void) ha;
(void) dec;
}
timer_stop();
return timer_read();
}
// test refraction correction for HA-DEC (eq2hor->refr->hor2eq)
uint32_t test_astro_refraction(){
float az = 11.3f, alt = 89.3f, ha, dec;
float phpa = 800.f, tc = 10.f, rh = 0.7f;
volatile float newha, newdec, newalt;
timer_start();
for(int i = 0; i < N_TESTS; ++i){
// 260us per cycle for math.h sin/cos
// 34us per cycle for CORDIC sin/cos
altaz_to_hadec(alt, az, &ha, &dec);
hadec_to_altaz(ha, dec, &alt, &az);
newalt = alt + refraction(phpa, tc, rh, 90.f-alt);
altaz_to_hadec(newalt, az, (float*)&newha, (float*)&newdec);
(void) newha;
(void) newdec;
if((az += 9.51f) > 359.99f) az -= 359.9f;
if((alt -= 1.76f) < 9.9f) alt += 79.f;
}
timer_stop();
return timer_read();
}
// test LST calculation
uint32_t test_astro_LST(){
uint32_t *uarr = (uint32_t*) arr;
volatile float result;
for(int i = 0; i < N_TESTS; ++i){ // fill random data in 21th century
uarr[i] = next_rand() % 3155673599 + 978307200;
}
timer_start();
for(int i = 0; i < N_TESTS; ++i){ // 1.5us per cycle
result = LST_from_unix(uarr[i]);
(void) result;
}
timer_stop();
return timer_read();
}

View File

@@ -34,3 +34,8 @@ uint32_t test_cordic_cos();
uint32_t test_cordic_atan(); uint32_t test_cordic_atan();
uint32_t test_cordic_sqrt(); uint32_t test_cordic_sqrt();
uint32_t test_cordic_log(); uint32_t test_cordic_log();
// Astronomy tests
uint32_t test_astro_coordsTransform();
uint32_t test_astro_refraction();
uint32_t test_astro_LST();

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "57" #define BUILD_NUMBER "75"
#define BUILD_DATE "2026-09-05" #define BUILD_DATE "2026-09-08"