HeliosCube-Eleni: A Cost-Effective, Open-Source 1U CubeSat for Microgravity Research
This paper presents the design and development of HeliosCube-Eleni, a 1U CubeSat created as a demonstration satellite (demosat) for the MAPHEUS-16 mission of the German Aerospace Center (DLR). Intended for testing in microgravity conditions, the satellite emphasizes practicality and affordability by utilizing commercially available sensors and a 3D-printed structure made of carbon fiber reinforced material, complemented by aluminum panels. All designs are open source, promoting accessibility and collaboration. This work outlines the satellite’s philosophy, components, development process, and planned validation, showcasing a model for rapid, low-cost space technology development.
CubeSats are miniaturized satellites that have transformed space exploration by offering a standardized, cost-effective platform for scientific research and technology demonstrations. A 1U CubeSat, with dimensions of 10 cm × 10 cm × 10 cm, represents the fundamental building block of this class, enabling even small teams to access space. HeliosCube-Eleni, a 1U CubeSat measuring 100 mm × 100 mm × 110 mm, adheres closely to this standard and serves as a demosat for the MAPHEUS-16 mission, organized by the German Aerospace Center (DLR).The MAPHEUS (Material Physics Experiments under Microgravity) program employs sounding rockets to create short-duration microgravity environments, ideal for testing innovative technologies and conducting experiments. The MAPHEUS-16 mission provides a unique opportunity to evaluate HeliosCube-Eleni in such conditions, simulating aspects of spaceflight. Developed by mpla-ExoTech under the leadership of Petros Mand, this CubeSat is part of the HelioCube series, which prioritizes practicality, rapid production, and affordability. This paper details its design philosophy, hardware, development, and testing objectives, highlighting its role as an accessible tool for microgravity research.
The HelioCube series, including HeliosCube-Eleni, is built on a foundation of practicality and cost-efficiency. Key principles driving its design include: Use of Off-the-Shelf Sensors: Commercially available sensors reduce development time and costs compared to bespoke solutions, making the satellite accessible to a wider range of creators.
3D-Printed Structure: The satellite’s frame is crafted using a 3D printer with carbon fiber reinforced material, offering a lightweight yet durable construction that supports rapid prototyping.
Aluminum Panels: These enhance structural stability and provide mounting surfaces for internal components, ensuring reliability during operation.
Open-Source Approach: All designs, including schematics and software, are publicly available, encouraging collaboration and innovation within the global space community.
This philosophy enables fast, affordable production while maintaining functionality, making HeliosCube-Eleni a practical solution for microgravity experiments.
HeliosCube-Eleni is equipped with a diverse array of sensors to collect data during its microgravity flight: 3-Axis Accelerometer: Measures acceleration across three axes to monitor motion and vibration.
Gyroscope: Tracks orientation and angular velocity, essential for attitude determination.
Geomagnetic Sensor: Detects the Earth’s magnetic field, aiding navigation and orientation.
Barometric Pressure Sensor: Estimates altitude or environmental conditions through pressure readings.
Temperature Sensor: Monitors thermal conditions inside and outside the satellite.
GNSS Receiver: Provides precise positioning and timing data via Global Navigation Satellite Systems.
Analogue Audio Sensor: Captures sound, potentially for atmospheric or acoustic experiments.
Geiger Counter: Detects ionizing radiation, offering insights into space weather or radiation levels.
The satellite’s operations are managed by a FireBeetle 2 microcontroller, a compact and efficient platform suited for space-constrained embedded systems. This suite of components ensures comprehensive data collection in microgravity.
The creation of HeliosCube-Eleni was a collaborative, multi-site effort led by Petros Mand of mpla-ExoTech: Design and Engineering: Performed in Athens, Greece, leveraging expertise in satellite systems and integration.
Assembly and Testing: Also conducted in Athens, ensuring all components were functional and harmonized.
Support and Printing: The 3D-printed structure was supported and manufactured in Cologne, Germany, utilizing advanced facilities for precision and quality.
This international collaboration underscores the feasibility of distributed development for small satellite projects, aligning with the project’s goals of accessibility and efficiency.
As a demosat for the MAPHEUS-16 mission, HeliosCube-Eleni is designed to undergo rigorous testing in microgravity aboard a sounding rocket. The primary objectives include: Sensor Validation: Confirming the performance and accuracy of all sensors under microgravity conditions.
Structural Assessment: Evaluating the 3D-printed frame and aluminum panels for durability during launch and operation.
System Integration: Verifying the FireBeetle 2 microcontroller’s ability to coordinate data collection and processing in a space-like environment.
These tests will provide critical data to refine future HelioCube designs and enhance our understanding of CubeSat behavior in microgravity, a key focus of this project.
HeliosCube-Eleni exemplifies a new era of accessible space technology, combining off-the-shelf components, innovative 3D printing, and an open-source ethos to create a cost-effective 1U CubeSat. Designed for the MAPHEUS-16 mission, it aims to demonstrate the viability of rapid, low-budget satellite development for microgravity research. The upcoming flight will yield valuable insights into its performance, paving the way for advancements in the HelioCube series and inspiring further innovation in the CubeSat domain. This project, led by Petros Mand and mpla-ExoTech, proves that high-quality space experimentation is within reach of small teams with limited resources.
/*
#mpla-ExoTech
#DLR
HeliosCube-Eleni
-A Cost-Effective, Open-Source 1U CubeSat for Microgravity Research
@Petros Mand
-Athens, 08/12025
*/
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "DFRobot_GNSS.h"
#include "DFRobot_BNO055.h"
#include "DFRobot_BMP280.h"
#include "DFRobot_Geiger.h"
#define SD_CS_PIN D7
#define SEA_LEVEL_PRESSURE 1015.0f
#define UV_SENSOR_PIN A4
#define GEIGER_PIN D6
#define MIC_PIN A5
#define LOG_INTERVAL 100
#define FLUSH_INTERVAL 100
#define SAMPLE_RATE 48000
#define SAMPLE_PERIOD (1000000UL / SAMPLE_RATE)
#define DEBUG 1
#if DEBUG
#define DEBUG_PRINT(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#endif
DFRobot_GNSS_I2C gnss(&Wire, GNSS_DEVICE_ADDR);
typedef DFRobot_BNO055_IIC BNO;
BNO bno(&Wire, 0x28);
typedef DFRobot_BMP280_IIC BMP;
BMP bmp(&Wire, BMP::eSdoLow);
DFRobot_Geiger geiger(GEIGER_PIN);
File dataFile;
File alertFile;
File audio8k;
File audio12k;
File audio16k;
unsigned long lastLogTime = 0;
unsigned long lastFlushTime = 0;
unsigned long nextSampleTime = 0;
bool liftOffDetected = false;
float maxPressure = 0;
bool maxQLogged = false;
bool microgravityLogged = false;
bool tropopauseLogged = false;
bool stratosphereLogged = false;
void logAlert(String message, sTim_t utc) {
alertFile = SD.open("/hcelenimissiondataalert.csv", FILE_APPEND);
if (alertFile) {
alertFile.print(utc.hour); alertFile.print(":");
alertFile.print(utc.minute); alertFile.print(":");
alertFile.print(utc.second); alertFile.print(",");
alertFile.println(message);
alertFile.close();
DEBUG_PRINT("Alert: " + message);
} else {
DEBUG_PRINT("Error writing alert to SD!");
}
}
String formatCSVData(sTim_t date, sTim_t utc, sLonLat_t lat, sLonLat_t lon, double altitude, float temp, uint32_t press, float baroAltitude, BNO::sAxisAnalog_t acc, BNO::sAxisAnalog_t gyr, BNO::sAxisAnalog_t mag, float uvIndex, int cpm, float nSv, float uSv, uint8_t sats, double sog, double cog, BNO::sEulAnalog_t eul) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "%04d/%02d/%02d,%02d:%02d:%02d,%.6f,%.6f,%.2f,%.2f,%u,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%.2f,%.2f,%u,%.2f,%.2f,%.2f,%.2f,%.2f",
date.year, date.month, date.date, utc.hour, utc.minute, utc.second,
lat.latitudeDegree, lon.lonitudeDegree, altitude, temp, press, baroAltitude,
acc.x, acc.y, acc.z, gyr.x, gyr.y, gyr.z, mag.x, mag.y, mag.z,
uvIndex, cpm, nSv, uSv, sats, sog, cog, eul.head, eul.roll, eul.pitch);
return String(buffer);
}
void setup() {
Serial.begin(115200);
while (!gnss.begin()) {
DEBUG_PRINT("GNSS: No Devices!");
delay(100);
}
gnss.enablePower();
gnss.setGnss(eGPS_BeiDou);
gnss.setRgbOn();
DEBUG_PRINT("GNSS Initialized");
bno.reset();
while (bno.begin() != BNO::eStatusOK) {
DEBUG_PRINT("BNO: Initialization failed");
delay(100);
}
DEBUG_PRINT("BNO Initialized");
bmp.reset();
while (bmp.begin() != BMP::eStatusOK) {
DEBUG_PRINT("BMP280: Initialization failed");
delay(100);
}
DEBUG_PRINT("BMP280 Initialized");
geiger.start();
DEBUG_PRINT("Geiger Counter Started");
if (!SD.begin(SD_CS_PIN)) {
DEBUG_PRINT("SD Card init failed!");
while (1);
}
DEBUG_PRINT("SD Card Initialized");
dataFile = SD.open("/hcelenimissiondatafile.csv", FILE_WRITE);
if (dataFile) {
dataFile.println("Date,Time,Latitude,Longitude,Altitude,Temp,Pressure,BaroAltitude,AccX,AccY,AccZ,GyrX,GyrY,GyrZ,MagX,MagY,MagZ,UVIndex,CPM,nSv/h,uSv/h,Sats,Speed,Heading,EulHead,EulRoll,EulPitch");
dataFile.close();
} else {
DEBUG_PRINT("Error creating data file!");
}
audio8k = SD.open("/audio_8k.raw", FILE_WRITE);
if (!audio8k) DEBUG_PRINT("Error creating audio_8k file!");
audio12k = SD.open("/audio_12k.raw", FILE_WRITE);
if (!audio12k) DEBUG_PRINT("Error creating audio_12k file!");
audio16k = SD.open("/audio_16k.raw", FILE_WRITE);
if (!audio16k) DEBUG_PRINT("Error creating audio_16k file!");
nextSampleTime = micros();
lastFlushTime = millis();
lastLogTime = millis();
}
void loop() {
while (micros() < nextSampleTime);
nextSampleTime += SAMPLE_PERIOD;
int micValue = analogRead(MIC_PIN);
static int sum8k = 0, count8k = 0;
static int sum12k = 0, count12k = 0;
static int sum16k = 0, count16k = 0;
sum8k += micValue; count8k++;
sum12k += micValue; count12k++;
sum16k += micValue; count16k++;
if (count16k == 3) {
int avg = sum16k / 3;
uint8_t sample = map(avg, 0, 4095, 0, 255);
if (audio16k) audio16k.write(sample);
sum16k = 0; count16k = 0;
}
if (count12k == 4) {
int avg = sum12k / 4;
uint8_t sample = map(avg, 0, 4095, 0, 255);
if (audio12k) audio12k.write(sample);
sum12k = 0; count12k = 0;
}
if (count8k == 6) {
int avg = sum8k / 6;
uint8_t sample = map(avg, 0, 4095, 0, 255);
if (audio8k) audio8k.write(sample);
sum8k = 0; count8k = 0;
}
unsigned long currentMillis = millis();
if (currentMillis - lastFlushTime >= FLUSH_INTERVAL) {
if (audio8k) audio8k.flush();
if (audio12k) audio12k.flush();
if (audio16k) audio16k.flush();
lastFlushTime = currentMillis;
}
if (currentMillis - lastLogTime >= LOG_INTERVAL) {
lastLogTime = currentMillis;
sTim_t utc = gnss.getUTC();
sTim_t date = gnss.getDate();
sLonLat_t lat = gnss.getLat();
sLonLat_t lon = gnss.getLon();
double altitude = gnss.getAlt();
float temp = bmp.getTemperature();
uint32_t press = bmp.getPressure();
float baroAltitude = bmp.calAltitude(SEA_LEVEL_PRESSURE, press);
BNO::sAxisAnalog_t acc = bno.getAxis(BNO::eAxisAcc);
BNO::sAxisAnalog_t gyr = bno.getAxis(BNO::eAxisGyr);
BNO::sAxisAnalog_t mag = bno.getAxis(BNO::eAxisMag);
int analogValue = analogRead(UV_SENSOR_PIN);
float uvIndex = (analogValue >= 20) ? min(0.05 * analogValue - 1.0, 11.0) : 0;
int cpm = geiger.getCPM();
float nSv = geiger.getnSvh();
float uSv = geiger.getuSvh();
uint8_t sats = gnss.getNumSatUsed();
double sog = gnss.getSog();
double cog = gnss.getCog();
BNO::sEulAnalog_t eul = bno.getEul();
String csvData = formatCSVData(date, utc, lat, lon, altitude, temp, press, baroAltitude, acc, gyr, mag, uvIndex, cpm, nSv, uSv, sats, sog, cog, eul);
dataFile = SD.open("/hcelenimissiondatafile.csv", FILE_APPEND);
if (dataFile) {
dataFile.println(csvData);
dataFile.close();
} else {
DEBUG_PRINT("Error writing to data file!");
}
float accTotal = sqrt(acc.x * acc.x + acc.y * acc.y + acc.z * acc.z);
if (!liftOffDetected && accTotal > 1.5) {
logAlert("LiftOff Detected", utc);
liftOffDetected = true;
}
if (press > maxPressure) {
maxPressure = press;
} else if (!maxQLogged && press < maxPressure * 0.98) {
logAlert("MaxQ Reached", utc);
maxQLogged = true;
}
if (!microgravityLogged && accTotal < 1.0) {
logAlert("Microgravity Detected", utc);
microgravityLogged = true;
}
if (!tropopauseLogged && altitude > 11000) {
logAlert("Tropopause Entered", utc);
tropopauseLogged = true;
}
if (!stratosphereLogged && altitude > 20000) {
logAlert("Stratosphere Entered", utc);
stratosphereLogged = true;
}
}
}









