Project Background
This project is meant to be an extension of the previous Altimeter project which was built to measure the maximum height reached by a model rocket. This version of the altimeter is built on a different platform and utilizes different and more sensors to collect data during the ascent of the rocket. The display from the original project is gone and replaced with a webinterface which can be accessed over a WiFi connection.
Usage
This section describes how to use the altimeter.
Image of the webinterface (as of 03.03.2024)
WiFi Credential setup
One set of WiFi credentials can be embedded into the firmware during flashing. On startup, the microcontroller will attempt to connect to the WiFi network provided by those credentials and if that fails will open its own network.
The file containing the credentials should be named wifi_credentials.h
and
be located in the src
folder. It will automatically be excluded from
version control to avoid leaking of credentials.
The contents of the file should look like the following
#ifndef WIFI_CREDENTIALS_H
#define WIFI_CREDENTIALS_H
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
#endif // WIFI_CREDENTIALS_H
Starting/Stopping measurements
Before starting the sampling process, ensure that the measured reference values are sensible by checking the webinterface, as the height calculation is relative to those values. If they do not make sense, a recalibration can be triggered from the webinterface. In general you should open the webinterface once before launching the altimeter as this will set the clock of the microcontroller to the current time.
The sampling process can be started by pressing the user button connected to pin D8 on the controller or by using the start button in the webinterface.
When the statemachine is in the sampling state, pressing the user button or the start button in the webinterface again will restart the measurements and discard existing samples.
There are 3 ways for the sampling process to end:
- The current measured height falls a configurable distance below the maximum height measured during this sampling cycle.
- The stop button in the webinterface is pressed.
- The maximum configured time for sampling has been reached.
After the statemachine enters the idle stop state the sampling process can be restarted by pressing the start buttons again. This will however discard the previous measurement results.
Downloading results
The measured sensor values and the reference measurements on ground level which are established during startup can be downloaded in csv format using the corresponding buttons in the webinterface.
Measurement results stay available, even after a reboot, until the measurement process is started again.
Visualization
A python script has been provided in the folder plotter
which can use the
downloaded samples and reference values to plot a height over time graph.
Technical information
Hardware used
The microcontroller used is the Seeed Studio XIAO ESP32C3. The sensor board is called GY-86 and is comprised of 3 different sensors which can all be accessed over I2C.
Name | Description | I2C address |
---|---|---|
HMC5883L | 3-Axis digital compass | 0x3C |
MPU-6050 | 3-axis gyroscope and 3-axis accelerometer | 0x68 |
MS5611 | Altimeter | 0x77 |
Libraries used
- Adafruit HMC5883L Driver (3-Axis Magnetometer)
- Adafruit MPU6050
- MS5611
- ESPAsyncWebServer
- ArduinoJson
Wiring
ESP32 to sensor:
XIAO ESO32C3 | GY-86 |
---|---|
NC | VCC_IN |
3V3 | 3.3V |
GND | GND |
D5 | SCL |
D4 | SDA |
NC | FSYNC |
NC | INTA |
NC | DRDY |
An additional user button was added connecting pin D8 directly to the 3.3V pin. This button is used as the main user button in the program and is also relevant for correctly entering debug mode with this board.
Debugging
The ESP32 can only be powered from the 5V pin or USB. The GY-86 requires 3V3 volt though so don't use the same supply for both. Also do not connect the battery without a diode while powering the ESP32 from the 5V pin.
The ESP32C3 has a builtin debugger.
To allow gdb debug connection to succeed, D8 has to be pulled to 3.3V during boot.
Otherwise a Failed to get flash maps
error will be triggered.
See also the additional resources for information on setting up the debugger and utilizing it.
FreeRTOS tasks
Main task (Statemachine task)
This task is responsible for running the altimeter statemachine. It is implemented in the main function.
Sensor task
The backbone of the system. This task is responsible for sampling the sensors in a fixed interval and sending the data to the statemachine task using a FreeRTOS queue. If the statemachine is not in the measuring state, this task is suspended.
Clock task
The simplest task is that of the clock task, which will in regular intervals increment the system clock to keep track of time in a simple human readable format.
Webserver
The AsyncWebserver library implements its own tasks to asynchronously handle webserver requests.
Statemachine
The statemachine is implemented using transition tables to keep track of which event triggers which transition and what action to take during that transition.
There are 2 kinds of transition tables used for this.
- Inner transition table: Each state has exactly one inner transition table. Each entry in this table defines the event triggering an outgoing transition, the target state of the transition, an optional transition guard and an optional transition action.
- Outer transition table: This transition table assigns the definitions of inner transition tables to their corresponding statemachine states.
The processEvent()
function checks a new event and finds whether the current
state has a corresponding transition which is not currently blocked by a
transition guard. If a valid transition is found, the optional transition action
is executed and the statemachine transitions into the new state.
st_Initialization
The default state upon starting the controller. This state will take some reference measurements to determine the relative height of later measurements.
st_Measuring
The main state of the statemachine. While in this state,
sensor data will be pulled in as provided by the sensor task and stored in
the global dataStore
object.
Additionally the current height will be calculated and checked against the
maximum measured height to be able to trigger the ev_Falling
event.
When transitioning to the stopped state, the measured sensor values will be stored to a file in flash to save them in case of a reboot.
st_StoppedMeasuring
Idle state. Allows restarting (and thereby overwriting!!!) the measurements.
Component diagram
Filesystem
Note: LittleFS requires absolute paths (filenames must start with a /
).
It also cannot use files which are stored in folders other than the current one.
The filesystem is implemented using a self-written abstraction layer to allow testing of certain components in a desktop environment. On a desktop machine standard C++ library functions are used for file accesses. On the microcontroller LittleFS is used instead. LittleFS is used to persist a single measurement run in flash. The saving is done automatically at the end of the measurement cycle.
REST API
All API requests have to be directed to various subdirectories of the /api
path depending
on their intended purpose.
/api/sensors
HTTP GET Requests:
Parameters | Expected response | Example request |
---|---|---|
None (always returned) | Returns current sensor values. These are not read from the buffer but instead freshly requested from the sensors. | <url>/api/sensors |
references |
Returns the current references and offsets values used for calibrating the sensor measurements | <url>/api/sensors?references |
This endpoint allows chaining of multiple parameters listed in the table. For example, sending a request <url>/api/sensors?sampleCount&references
will return the data from both of those parameters and a current sensor value. Adding the from=
and to=
parameters to this request,
will return a chunk of measured sensor values instead of a current sensor value.
/api/log
HTTP GET Requests:
Parameters | Expected response | Example request |
---|---|---|
None | Returns last log message | <url>/api/log |
from= , to=
|
Returns chunk of log messages. Can be negative or positive indexed, e.g. from=-1 and to=-3 will return the 3 newest messages while from=0 and to=2 will return the 3 oldest. Requesting more messages than exist will not cause an error and only return the maximum currently available. |
<url>/api/log?from=-1&to=-3 |
/api/system
HTTP GET Requests:
Parameters | Expected response | Example request |
---|---|---|
None | Returns general system information such as current system time | <url>/api/system |
clock-state |
Returns the state of the system clock | <url>/api/system?clock-state |
HTTP POST:
Parameters | Result |
---|---|
h= , m= , s=
|
Sets the hours, minutes and seconds of the controllers internal clock. Parameters must all be used and must be in valid range or HTTP 400 will be returned. |
restart=true |
Restarts the controller. |
rezero=true |
Recalibrates the reference temperature and pressure for the relative height calculation |
start=true |
Triggers start event for state machine. Response of controller depends on current state. Cannot be sent along with stop=true . |
stop=true |
Triggers stop event for state machine. Response of controller depends on current state. Cannot be sent along with start=true . |
/api/file
HTTP GET Requests:
Parameters | Expected response | Example request |
---|---|---|
filename= |
Tries to open a file with the given filename and returns it on success. Filename must start with a /
|
<url>/api/file?filename=/style.css |
data |
Returns the file storing the persisted measurement data from the previous measurement run. | <url>/api/file?data |
references |
Returns the file storing the reference measurements | <url>/api/file?references |
Unit tests
Some of the software components used in the altimeter have unit tests written for them using the googletest
framework. Those are located in the test
folder and separated into the common
and embedded
tests and
can be executed through the PlatformIO UI.
The common
tests are tests of software components that have been written in a way as to not depend on
the features present on the microcontroller and can therefore be tested on a desktop machine or the
microcontroller directly.
The embedded
tests on the other hand can only be executed on the microcontroller.
To add a new set of tests, simply create a new .cpp
file in the respective testfolder and prefix it with
test_
. Testcases written in this file will automatically be picked up and executed. Tests executed on a
desktop machine may require adding the tested source code to the build_src_filter
in the platformio.ini
file.
Known Issues
-
Sensor task dropping samples while old values are written to filesystem:
The sensor task often drops individual values due to missed deadlines when the statemachine task is busy writing old sensor values to the filesystem. This is likely caused by the filesystem operations taking too long. Theoretically the sensor task should not be impacted by this as it is explicitly executed on another processor core and has a higher priority than any other task. -
wifiScan function prevents webinterface log messages:
ThewifiScan()
function (defined inmain.cpp
) can prevent the webinterface from displaying log messages when used. This is caused by the function finding networks with non-ASCII symbols in their name, printing them to the log and the JSON parser in the webinterface failing to interpret those characters. -
WiFi SoftAP signal extremely weak:
The WiFi network hosted by the controller when no other network can be connected to is extremely short range and often drops the connection to client devices. -
HMC5883 sensor does not work at all:
When using the HMC5883 sensor (compass), only[Wire.cpp:499] requestFrom(): i2cWriteReadNonStop returned Error -1
is logged to the UART. This is likely a driver issue. For the time being requests to this sensor have been completely disabled in thegetSensorValues()
function insensors.cpp
. -
[ 20758][E][vfs_api.cpp:105] open(): /littlefs/persistData.csv does not exist, no permits for creation:
This error indicates the LittleFS library is trying to open a file that does not exist. To check whether a file exists with LittleFS, the program has to try and open it. If the open command fails, the file does not exist. This error is caused by the webserver periodically checking whether a file containing data to download is available, to indicate this state to the user. Once a file has been created due to the program entering the sampling state, it goes away.
Future Improvements
Hardware improvement ideas:
- Onboard SMD LED for simple status indication
- Onboard switch for correctly entering debug mode
Software improvement ideas:
- Fix the known issues
- Expand plotting script to perform post processing of data before plotting, such as filtering out measurement spikes.
- Expand plotting script to also plot the flight path. This requires measuring the orientation of the controller during flight though for which the HMC5883 sensor issue has to be resolved.
- Improve webinterface design and use Server-Sent events for more responsiveness instead of the currently used AJAX.
Additional resources/guides
General information
- Getting Started with Seeed Studio XIAO ESP32C3: Getting started guide for MCU. Includes pinout and general information.
- Seeed Studio XIAO ESP32C3: PlatformIO docs for using the XIAO ESP32C3.
- First Steps with a GY-86 10DOF Sensor: MPU6050, HMC5883L and MS5611: Getting started YouTube video.
Debugging
- Debug an ESP32-C3 via its JTAG interface: Setup of JTAG debugging using on-board JTAG interface.
- JTAG Debugging Tips and Quirks: Tips and quirks for jtag debugging on an ESP32.
- Use the PlatformIO Debugger on the ESP32 Using an ESP-prog: Guide to using the esp-prog debug probe in PlatformIO.
- ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically): Example on how to automatically update information on a webpage from the controller side.