2019-2020 Multisensor project using the X_NUCLEO_IKS01A3 sensor platform

Dependencies:   X_NUCLEO_IKS01A3

main.cpp

Committer:
martlefebvre94
Date:
2019-11-25
Revision:
16:566c4e5f090e
Parent:
15:77dec0c4ecba
Child:
17:930b91883e6b

File content as of revision 16:566c4e5f090e:

/**
 ******************************************************************************
 * @file    main.cpp
 * @author  SRA
 * @version V1.0.0
 * @date    5-March-2019
 * @brief   Simple Example application for using the X_NUCLEO_IKS01A3
 *          MEMS Inertial & Environmental Sensor Nucleo expansion board.
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2019 STMicroelectronics</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of STMicroelectronics nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
*/

/*
    LELEC2811 Multisensor IKS01A3 Project
    M. Lefebvre - 2019-2020
*/

/* Includes */
#include <stdlib.h>
#include <time.h>
#include "mbed.h"
#include "XNucleoIKS01A3.h"
#include "stm32l073xx.h"
#include "stm32l0xx_hal_flash.h"

/* Defines */
#define VDD                     3.3     // Supply voltage (V)
#define FS                      10.0    // Readout frequency (Hz) - /!\ Must be below 19Hz
#define DATA_SIZE               64      // Number of bytes to store in Flash memory
#define FLASH_WRITE_TIME        0.00328 // Flash write time (s)
#define TS                      (1/FS)-((DATA_SIZE/4)*FLASH_WRITE_TIME)

// LIS2MDL Magnetometer
#define LIS2MDL_ODR             50.0    // Output data rate (10, 20, 50 or 100 Hz)
#define LIS2MDL_LP              0       // Power mode (0 for high-resolution mode, 1 for low-power mode)
#define LIS2MDL_LPF             0       // Bandwidth (0 for ODR/2, 1 for ODR/4)
#define LIS2MDL_COMP_TEMP_EN    1       // Temperature compensation (0 disabled, 1 enabled)
#define LIS2MDL_OFF_CANC        1       // Offset cancellation (0 for no offset cancellation, 1 for offset cancellation, 2 for set pulse only at power-on)
#define LIS2MDL_DATA_SIZE       12      // Number of bytes for LIS2MDL magnetometer data

// LPS22HH Pressure sensor
#define P0                      1013.26 // Sea level pressure (hPa)
#define LPS22HH_ODR             50.0    // Output data rate (one-shot, 1, 10, 25, 50, 75, 100, 200 Hz)
#define LPS22HH_LOW_NOISE_EN    1       // Low-noise (0 disabled, 1 enabled)
#define LPS22HH_LPF_CFG         3       // Device bandwidth (0 for ODR/2, 2 for ODR/9, 3 for ODR/20)
#define LPS22HH_DATA_SIZE       8       // Number of bytes for LPS22HH pressure sensor data

// LIS2DW12 Accelerometer
#define LIS2DW12_ODR            4       // Output data rate (0 power down, 1 HP 12.5Hz/LP 1.6Hz, 2 for 12.5Hz, 3 for 25Hz, 4 for 50Hz, 5 for 100Hz, 6 for 200Hz, 7 for HP 400Hz/LP 200Hz, 8 for HP 800Hz/LP 200Hz, 9 for HP 1600Hz/LP 200Hz)
#define LIS2DW12_FS             4       // Full-scale +-(2, 4, 8 or 16 g)
#define LIS2DW12_BW_FILT        2       // Filter bandwidth (0 for ODR/2, 1 for ODR/4, 2 for ODR/10, 3 for ODR/20)
#define LIS2DW12_LP_MODE        0       // Low-power modes 1 to 4 (1 gives the max. rms noise, 4 gives the min. rms noise)
#define LIS2DW12_MODE           1       // Mode (0 for low-power, 1 for high-performance, 2 for single data conversion)
#define LIS2DW12_LOW_NOISE      1       // Low-noise (0 disabled, 1 enabled)
#define LIS2DW12_POWER_MODE     LIS2DW12_LP_MODE + (LIS2DW12_MODE << 2) + (LIS2DW12_LOW_NOISE << 4)
#define LIS2DW12_DATA_SIZE      12      // Number of bytes for LIS2DW12 accelerometer sensor data

// HTS221 Relative humidity and temperature sensor
#define HTS221_ODR              1       // Output data rate (one-shot, 1Hz, 7Hz, 12.5Hz)
#define HTS221_HEATER           0       // Heater configuration (0 disabled, 1 enabled)
#define HTS221_AVGH             32      // Humidity averaging (4 to 512)
#define HTS221_AVGT             16      // Temperature averaging (2 to 256)

// LSM6DSO Accelerometer + gyroscope
#define LSM6DSO_ODR_XL          12.5    // Accelerometer output data rate (12.5, 26, 52, 104, 208, 416, 833, 1.66k, 3.33k, 6.66kHz)
#define LSM6DSO_FS_XL           4       // Accelerometer full scale (2, 4, 8, 16g)
#define LSM6DSO_XL_HM_MODE      1       // Accelerometer high-performance mode (0 enabled, 1 disabled)
#define LSM6DSO_XL_ULP_EN       0       // Accelerometer ultra-low-power configuration (0 disabled, 1 enabled)
#define LSM6DSO_LPF2_XL_EN      1       // Accelerometer high-resolution selection (0 for 1st stage of digital filtering, 1 for 2nd stage)
#define LSM6DSO_HP_SLOPE_XL_EN  0       // Accelerometer high-pass filter selection (0 for low-pass, 1 for high-pass)
#define LSM6DSO_HPCF_XL         2       // Accelerometer filter configuration and cutoff setting (0 for ODR/4, 1 for ODR/10, 2 for ODR/20, 3 for ODR/45, 4 for ODR/100, 5 for ODR/200, 6 for ODR/400, 7 for ODR/800)      
#define LSM6DSO_ODR_G           16      // Gyroscope output data rate (12.5, 26, 52, 104, 208, 416, 833, 1.66k, 3.33k, 6.66kHz)
#define LSM6DSO_FS_G            1000    // Gyroscope full scale (250, 500, 1000, 2000dps)
#define LSM6DSO_G_HM_MODE       1       // Gyroscope high-performance mode (0 enabled, 1 disabled)
#define LSM6DSO_LPF1_SEL_G      1       // Gyroscope digital LPF1 enable (0 disabled, 1 enabled)
#define LSM6DSO_FTYPE           3       // Gyroscope LPF1 bandwidth selection (0 ultra light, 1 very light, 2 light, 3 medium, 4 strong, 5 very strong, 6 aggressive, 7 xtreme)
#define LSM6DSO_HP_EN_G         1       // Gyroscope digital HPF enable (0 HPF disabled, 1 HPF enabled)
#define LSM6DSO_HPM_G           2       // Gyroscope HPF cutoff selection (0 for 16mHz, 1 for 65mHz, 10 for 260 mHz, 11 for 1.04Hz)

/* Functions definition */
bool acquisition_task(bool verbose);
void read_task();
void print_flash_info();
bool erase_flash(bool verbose);
bool write_flash(uint32_t Flash_addr, uint32_t* Flash_wdata, int32_t n_words, bool verbose);
void read_flash(uint32_t Flash_addr, uint32_t* Flash_rdata, uint32_t n_bytes);
void button1_enabled_cb(void);
void button1_onpressed_cb(void);
static char *print_double(char *str, double v);
float pressure_to_altitude(double pressure);
uint32_t FloatToUint(float n);
float UintToFloat(uint32_t n);

static void SystemClock_Config(void);
void MX_ADC_Init(void);
void HAL_ADC_MspInit(void);
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle);

/* ADC and DAC */
ADC_HandleTypeDef       hadc;
ADC_ChannelConfTypeDef  adcChannel;
uint32_t                adcValue;
AnalogOut               out(PA_4);

/* Serial link */
Serial pc(SERIAL_TX, SERIAL_RX);

/* Button */
InterruptIn button1(USER_BUTTON);
volatile bool button1_pressed = false; // Used in the main loop
volatile bool button1_enabled = true; // Used for debouncing
Timeout button1_timeout; // Used for debouncing

/* Instantiate the expansion board */
static XNucleoIKS01A3 *mems_expansion_board = XNucleoIKS01A3::instance(D14, D15, D4, D5, A3, D6, A4);

/* Retrieve the composing elements of the expansion board */
static LIS2MDLSensor *magnetometer = mems_expansion_board->magnetometer;
static HTS221Sensor *hum_temp = mems_expansion_board->ht_sensor;
static LPS22HHSensor *press_temp = mems_expansion_board->pt_sensor;
static LSM6DSOSensor *acc_gyro = mems_expansion_board->acc_gyro;
static LIS2DW12Sensor *accelerometer = mems_expansion_board->accelerometer;
static STTS751Sensor *temp = mems_expansion_board->t_sensor;

/* Main */
int main()
{
    // Reset of all peripherals
    HAL_Init();
    
    // Configure the system clock
    SystemClock_Config();
    
    uint8_t id;
    float read_reg, read_reg_1;
    uint8_t read_reg_int, read_reg_int_1, read_reg_int_2;
    
    bool save_data = false;
    uint32_t Flash_addr = FLASH_BANK2_BASE;

    /* Serial link configuration */
    pc.baud(115200);
    
    /* ADC and channel configuration */
    HAL_ADC_MspInit(&hadc);
    MX_ADC_Init();
    HAL_ADC_Start(&hadc);
    
    /* DAC */
    out.write(0.5);
    
    /* Button configuration */
    button1.fall(callback(button1_onpressed_cb)); // Attach ISR to handle button press event
    
    /* Reset message */
    printf("\n\r**************************************************\n\r");
    printf("LELEC2811 IKS01A3 Multisensor Program\n\r");
    printf("**************************************************\n\r");
    
    /* LIS2MDL magnetometer sensor configuration */
    magnetometer->enable();
    printf("/***** LIS2MDL magnetometer configuration *****/\r\n");
    
    magnetometer->read_id(&id);
    printf("LIS2MDL magnetometer = 0x%X\r\n", id);
    
    magnetometer->set_m_odr(LIS2MDL_ODR);
    magnetometer->get_m_odr(&read_reg);
    printf("LIS2MDL ODR = %1.1f [Hz]\r\n", read_reg);
    
    magnetometer->set_m_lp(LIS2MDL_LP);
    magnetometer->get_m_lp(&read_reg_int);
    printf("LIS2MDL LP = %1d\r\n", read_reg_int);
    
    magnetometer->set_m_lpf(LIS2MDL_LPF);
    magnetometer->get_m_lpf(&read_reg_int);
    printf("LIS2MDL LPF = %1d\r\n", read_reg_int);
    
    magnetometer->set_m_comp_temp_en(LIS2MDL_COMP_TEMP_EN);
    magnetometer->get_m_comp_temp_en(&read_reg_int);
    printf("LIS2MDL COMP_TEMP_EN = %1d\r\n", read_reg_int);
    
    magnetometer->set_m_off_canc(LIS2MDL_OFF_CANC);
    magnetometer->get_m_off_canc(&read_reg_int);
    printf("LIS2MDL OFF_CANC = %1d\r\n", read_reg_int);
    
    /* LPS22HH pressure sensor configuration */
    press_temp->enable();
    printf("/***** LPS22HH pressure sensor configuration *****/\r\n");
    
    press_temp->read_id(&id);
    printf("LPS22HH pressure = 0x%X\r\n", id);
    
    press_temp->set_odr(LPS22HH_ODR, LPS22HH_LOW_NOISE_EN);
    press_temp->get_odr(&read_reg, &read_reg_int);
    printf("LPS22HH ODR = %1.1f [Hz]\r\n", read_reg);
    printf("LPS22HH LOW_NOISE_EN = %1d\r\n", read_reg_int);
    
    press_temp->set_lpfp_cfg(LPS22HH_LPF_CFG);
    press_temp->get_lpfp_cfg(&read_reg_int);
    printf("LPS22HH LPF_CFG = %1d\r\n", read_reg_int);
    
    /* LIS2DW12 accelerometer sensor configuration */
    accelerometer->enable_x();
    printf("/***** LIS2DW12 accelerometer sensor configuration *****/\r\n");
    
    accelerometer->read_id(&id);
    printf("LIS2DW12 accelerometer = 0x%X\r\n", id);
    
    accelerometer->set_x_odr(LIS2DW12_ODR);
    accelerometer->get_x_odr(&read_reg);
    printf("LIS2DW12 ODR = %1.3f [Hz]\r\n", read_reg);
    
    accelerometer->set_x_fs(LIS2DW12_FS);
    accelerometer->get_x_fs(&read_reg);
    printf("LIS2DW12 FS = %1.3f [g]\r\n", read_reg);
    
    accelerometer->set_x_bw_filt(LIS2DW12_BW_FILT);
    accelerometer->get_x_bw_filt(&read_reg_int);
    printf("LIS2DW12 BW_FILT = %1d\r\n", read_reg_int);
    
    accelerometer->set_x_power_mode(LIS2DW12_POWER_MODE);
    accelerometer->get_x_power_mode(&read_reg_int, &read_reg_int_1, &read_reg_int_2);
    printf("LIS2DW12 LP_MODE = %1d\r\n", read_reg_int);
    printf("LIS2DW12 MODE = %1d\r\n", read_reg_int_1);
    printf("LIS2DW12 LOW_NOISE = %1d\r\n", read_reg_int_2);
    
    /* HTS221 relative humidity and temperature sensor configuration */
    hum_temp->enable();    
    printf("/***** HTS221 humidity sensor configuration *****/\r\n");
    
    hum_temp->read_id(&id);
    printf("HTS221 humidity & temperature = 0x%X\r\n", id);
    
    hum_temp->set_odr(HTS221_ODR);
    hum_temp->get_odr(&read_reg);
    printf("HTS221 ODR = %1.3f [Hz]\r\n", read_reg);
    
    hum_temp->set_heater(HTS221_HEATER);
    hum_temp->get_heater(&read_reg_int);
    printf("HTS221 HEATER = %1d\r\n", read_reg_int);
    
    hum_temp->set_avg(HTS221_AVGH, HTS221_AVGT);
    hum_temp->get_avg(&read_reg, &read_reg_1);
    printf("HTS221 AVGH = %1.0f\r\n", read_reg);
    printf("HTS221 AVGT = %1.0f\r\n", read_reg_1);
    
    /* STTS751 Temperature sensor configuration */
    temp->enable();
    printf("/***** STTS751 temperature sensor configuration *****/\r\n");
    
    temp->read_id(&id);
    printf("STTS751 temperature = 0x%X\r\n", id);
    
    /* LSM6DSO Accelerometer and gyroscope configuration */
    acc_gyro->enable_x();
    acc_gyro->enable_g();
    printf("/***** LSM6DSO accelerometer and gyroscope sensor configuration *****/\r\n");
    
    acc_gyro->read_id(&id);
    printf("LSM6DSO accelerometer & gyroscope = 0x%X\r\n", id);
    
    acc_gyro->set_x_odr(LSM6DSO_ODR_XL);
    acc_gyro->get_x_odr(&read_reg);
    printf("LSM6DSO ODR_XL = %1.3f [Hz]\r\n", read_reg);
    
    acc_gyro->set_x_fs(LSM6DSO_FS_XL);
    acc_gyro->get_x_fs(&read_reg);
    printf("LSM6DSO FS_XL = %1.3f [g]\r\n", read_reg);
    
    acc_gyro->set_x_power_mode(LSM6DSO_XL_HM_MODE, LSM6DSO_XL_ULP_EN);
    acc_gyro->get_x_power_mode(&read_reg_int, &read_reg_int_1);
    printf("LSM6DSO XL_HM_MODE = %1d\r\n", read_reg_int);
    printf("LSM6DSO XL_ULP_EN = %1d\r\n", read_reg_int_1);
    
    acc_gyro->set_x_lpf2_en(LSM6DSO_LPF2_XL_EN);
    acc_gyro->get_x_lpf2_en(&read_reg_int);
    printf("LSM6DSO LPF2_XL_EN = %1d\r\n", read_reg_int);
    
    acc_gyro->set_x_filter_config(LSM6DSO_HP_SLOPE_XL_EN, LSM6DSO_HPCF_XL);
    acc_gyro->get_x_filter_config(&read_reg_int, &read_reg_int_1);
    printf("LSM6DSO HP_SLOPE_XL_EN = %1d\r\n", read_reg_int);
    printf("LSM6DSO HPCF_XL = %1d\r\n", read_reg_int_1);
    
    acc_gyro->set_g_odr(LSM6DSO_ODR_G);
    acc_gyro->get_g_odr(&read_reg);
    printf("LSM6DSO ODR_G = %1.3f [Hz]\r\n", read_reg);
    
    acc_gyro->set_g_fs(LSM6DSO_FS_XL);
    acc_gyro->get_g_fs(&read_reg);
    printf("LSM6DSO FS_G = %1.3f [dps]\r\n", read_reg);
    
    acc_gyro->set_g_power_mode(LSM6DSO_G_HM_MODE);
    acc_gyro->get_g_power_mode(&read_reg_int);
    printf("LSM6DSO G_HM_MODE = %1d\r\n", read_reg_int);
    
    acc_gyro->set_g_lpf_config(LSM6DSO_LPF1_SEL_G, LSM6DSO_FTYPE);
    acc_gyro->get_g_lpf_config(&read_reg_int, &read_reg_int_1);
    printf("LSM6DSO LPF1_SEL_G = %1d\r\n", read_reg_int);
    printf("LSM6DSO FTYPE = %1d\r\n", read_reg_int_1);
    
    acc_gyro->set_g_hpf_config(LSM6DSO_HP_EN_G, LSM6DSO_HPM_G);
    acc_gyro->get_g_hpf_config(&read_reg_int, &read_reg_int_1);
    printf("LSM6DSO HP_EN_G = %1d\r\n", read_reg_int);
    printf("LSM6DSO HPM_G = %1d\r\n", read_reg_int_1);
    
    /* Print Flash memory information */
    print_flash_info();
    
    /* Information for the user */
    printf("Press blue button to start data acquisition\r\n");
    printf("Press 'R' to read previously measured data\r\n");
    
    /* Acquisition loop */
    while(1) {
        // Start saving data when button is pushed
        if (button1_pressed) {
            button1_pressed = false;
            save_data = true;
            erase_flash(false);
            printf("Acquiring data...\r\n");
            printf("Press blue button to stop data acquisition\r\n");
            Flash_addr = FLASH_BANK2_BASE;
        }
        
        if (save_data) {
            // Acquisition task
            save_data = acquisition_task(true);
        }
        else {
            // Read task
            read_task();
        }
    }
}

/* Acquisition task */
bool acquisition_task(bool verbose)
{
    int32_t m_axes[3];
    int32_t acc_axes[3];
    int32_t acc_axes_1[3];
    int32_t gyro_axes[3];
    float pressure_value, hum_value, temp_value, temp_value_1;
    int32_t data_buffer[DATA_SIZE/4];
    
    uint32_t Flash_addr = FLASH_BANK2_BASE;

    while (Flash_addr <= FLASH_BANK2_END-DATA_SIZE+1) {
        // Read sensors data
        magnetometer->get_m_axes(m_axes);
        press_temp->get_pressure(&pressure_value);
        accelerometer->get_x_axes(acc_axes);
        hum_temp->get_temperature(&temp_value);
        hum_temp->get_humidity(&hum_value);
        temp->get_temperature(&temp_value_1);
        acc_gyro->get_x_axes(acc_axes_1);
        acc_gyro->get_g_axes(gyro_axes);
        
        // Save data to Flash memory
        data_buffer[0] = m_axes[0];
        data_buffer[1] = m_axes[1];
        data_buffer[2] = m_axes[2];
        data_buffer[3] = acc_axes[0];
        data_buffer[4] = acc_axes[1];
        data_buffer[5] = acc_axes[2];
        data_buffer[6] = acc_axes_1[0];
        data_buffer[7] = acc_axes_1[1];
        data_buffer[8] = acc_axes_1[2];
        data_buffer[9] = gyro_axes[0];
        data_buffer[10] = gyro_axes[1];
        data_buffer[11] = gyro_axes[2];
        data_buffer[12] = (int32_t) FloatToUint(pressure_value);
        data_buffer[13] = (int32_t) FloatToUint(hum_value);
        data_buffer[14] = (int32_t) FloatToUint(temp_value);
        if (HAL_ADC_PollForConversion(&hadc, 5) == HAL_OK)
        {
            adcValue = HAL_ADC_GetValue(&hadc);
        }
        data_buffer[15] = (int32_t) FloatToUint(((float) adcValue)/4096 * VDD);
        
        write_flash(Flash_addr, (uint32_t*) &data_buffer[0], DATA_SIZE/4, false);
        
        // Increase Flash address
        Flash_addr += DATA_SIZE;
        
        // Print data in terminal
        if (verbose) {
            printf("LIS2MDL: [mag/mgauss] %6d, %6d, %6d\r\n", ((uint32_t) m_axes[0]), ((uint32_t) m_axes[1]), ((uint32_t) m_axes[2]));
            printf("LPS22HH: [press/mbar] %1.3f, [alt/m] %1.3f\r\n", pressure_value, pressure_to_altitude(pressure_value));
            printf("HTS221: [temp/deg C] %1.3f, [hum/%%] %1.3f\r\n", temp_value, hum_value);
            printf("STTS751 [temp/deg C] %1.3f\r\n", temp_value_1);
            printf("LIS2DW12: [acc/mg] %6d, %6d, %6d\r\n", ((uint32_t) acc_axes[0]), ((uint32_t) acc_axes[1]), ((uint32_t) acc_axes[2]));
            printf("LSM6DSO: [acc/mg] %6d, %6d, %6d\r\n", ((uint32_t) acc_axes_1[0]), ((uint32_t) acc_axes_1[1]), ((uint32_t) acc_axes_1[2]));
            printf("LSM6DSO: [gyro/mdps] %6d, %6d, %6d\r\n", ((uint32_t) gyro_axes[0]), ((uint32_t) gyro_axes[1]), ((uint32_t) gyro_axes[2]));
        }
        
        // Wait for acquisition period
        wait(TS);
        
        // Stop saving data when button is pushed
        if (button1_pressed) {
            button1_pressed = false;
            printf("Data acquisition stopped\r\n");
            printf("Press 'R' to read the data\r\n");
            return false;
        }
    }
    printf("Data acquisition stopped\r\n");
    printf("Press 'R' to read the data\r\n");
    return false;
}

/* Read task */
void read_task()
{
    char pc_input;
    uint32_t Flash_rdata[DATA_SIZE/4];
    bool flash_empty = false;
    
    // Read terminal input
    if (pc.readable()) {
        pc_input = pc.getc();
    }
    else {
        pc_input = ' ';
    }
    
    // Read Flash memory if 'R' is pressed
    if ((pc_input == 'r') || (pc_input == 'R')) {
        // Data labels
        printf("mag_X\tmag_Y\tmag_Z\tacc_X\tacc_Y\tacc_Z\tacc_X_1\tacc_Y_1\tacc_Z_1\tgyr_X\tgyr_Y\tgyr_Z\tpress\thum\ttemp\ttemp_1\r\n");
        
        // Read 1st Flash data
        uint32_t Flash_addr_temp = FLASH_BANK2_BASE;
        read_flash(Flash_addr_temp, &Flash_rdata[0], DATA_SIZE);
        
        // Read Flash data
        while ((Flash_addr_temp <= FLASH_BANK2_END-DATA_SIZE+1) && !flash_empty) {
            // Print read data in the terminal
            printf("%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%6d\t%1.6f\t%1.6f\t%1.6f\t%1.6f\r\n", Flash_rdata[0], Flash_rdata[1], Flash_rdata[2], Flash_rdata[3], Flash_rdata[4], Flash_rdata[5], Flash_rdata[6], Flash_rdata[7], Flash_rdata[8], Flash_rdata[9], Flash_rdata[10], Flash_rdata[11], UintToFloat(Flash_rdata[12]), UintToFloat(Flash_rdata[13]), UintToFloat(Flash_rdata[14]), UintToFloat(Flash_rdata[15]));
            Flash_addr_temp += DATA_SIZE;
            
            // Check if the next address is not empty (erased Flash only contains 0)
            if (Flash_addr_temp <= FLASH_BANK2_END-DATA_SIZE+1) {
                read_flash(Flash_addr_temp, &Flash_rdata[0], DATA_SIZE);
                if ((Flash_rdata[0] == 0) && (Flash_rdata[1] == 0) && (Flash_rdata[2] == 0)) {
                    flash_empty = true;
                }
            }
        }
    }
}

/* Print Flash memory info */
void print_flash_info()
{
    printf("**************************************************\n\r");
    printf("/***** Flash memory info *****/\r\n");
    printf("Flash size: %d [B]\r\n", FLASH_SIZE);
    printf("Flash page size: %d [B]\r\n", FLASH_PAGE_SIZE);
    printf("Flash nb of pages: %d \r\n", FLASH_SIZE/FLASH_PAGE_SIZE);
    printf("Flash bank 1 base address: 0x%X\r\n", FLASH_BASE);
    printf("Flash bank 1 end address: 0x%X\r\n", FLASH_BANK1_END);
    printf("Flash bank 2 base address: 0x%X\r\n", FLASH_BANK2_BASE);
    printf("Flash bank 2 end address: 0x%X\r\n", FLASH_BANK2_END);
    printf("**************************************************\n\r");
}

/* Erase content of Flash memory */
bool erase_flash(bool verbose)
{
    printf("Erasing Flash memory...\r\n");
    
    // Unlock Flash memory
    HAL_FLASH_Unlock();

    // Erase Flash memory
    FLASH_EraseInitTypeDef eraser;
    uint32_t Flash_addr = FLASH_BANK2_BASE;
    uint32_t page_error = 0;
    int32_t page = 1;
    
    while (Flash_addr < FLASH_BANK2_END) {
        eraser.TypeErase = FLASH_TYPEERASE_PAGES;
        eraser.PageAddress = Flash_addr;
        eraser.NbPages = 1;
        if(HAL_OK != HAL_FLASHEx_Erase(&eraser, &page_error)) {
            if (verbose) {printf("Flash erase failed!\r\n");}
            printf("Error 0x%X\r\n", page_error);
            HAL_FLASH_Lock();
            return false;
        }
        if (verbose) {printf("Erased page %d at address: 0x%X\r\n", page, Flash_addr);}
        Flash_addr += FLASH_PAGE_SIZE;
        page++;
    }
    
    if (verbose) {printf("Flash erase succesful!\r\n");}
    return true;
}

/* Write Flash memory */
bool write_flash(uint32_t Flash_addr, uint32_t* Flash_wdata, int32_t n_words, bool verbose)
{
    clock_t time;
    if (verbose) {time = clock();}
    
    // Unlock Flash memory
    HAL_FLASH_Unlock();
    
    // Write Flash memory
    for (int i=0; i<n_words; i++) {
        if (HAL_OK != HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Flash_addr, Flash_wdata[i])) {
            if (verbose) {printf("Flash write failed!\r\n");}
            HAL_FLASH_Lock();
            return false;
        }
        Flash_addr += 4;
    }
    if (verbose) {printf("Flash write succesful!\r\n");}
    
    HAL_FLASH_Lock();
    
    if (verbose) {
        time = clock() - time;
        printf("Time to write: %1.6f [s]\r\n", (((double) time)/CLOCKS_PER_SEC));
    }
    
    return true;
}

/* Read Flash memory */
void read_flash(uint32_t Flash_addr, uint32_t* Flash_rdata, uint32_t n_bytes)
{
    memcpy(Flash_rdata, (uint32_t*) Flash_addr, n_bytes);
}

/* Enables button when bouncing is over */
void button1_enabled_cb(void)
{
    button1_enabled = true;
}

/* ISR handling button pressed event */
void button1_onpressed_cb(void)
{
    if (button1_enabled) { // Disabled while the button is bouncing
        button1_enabled = false;
        button1_pressed = true; // To be read by the main loop
        button1_timeout.attach(callback(button1_enabled_cb), 0.3); // Debounce time 300 ms
    }
}

/* Helper function for printing floats & doubles */
static char *print_double(char *str, double v)
{
    int decimalDigits = 6;
    int i = 1;
    int intPart, fractPart;
    int len;
    char *ptr;

    /* prepare decimal digits multiplicator */
    for (; decimalDigits != 0; i *= 10, decimalDigits--);

    /* calculate integer & fractinal parts */
    intPart = (int)v;
    fractPart = (int)((v - (double)(int)v) * i);

    /* fill in integer part */
    sprintf(str, "%i.", intPart);

    /* prepare fill in of fractional part */
    len = strlen(str);
    ptr = &str[len];

    /* fill in leading fractional zeros */
    for (i /= 10; i > 1; i /= 10, ptr++) {
        if (fractPart >= i) {
            break;
        }
        *ptr = '0';
    }

    /* fill in (rest of) fractional part */
    sprintf(ptr, "%i", fractPart);

    return str;
}

/* Pressure to altitude conversion */
float pressure_to_altitude(double pressure)
{
    return 44330.77 * (1-pow(pressure/P0, 0.1902632));
}

uint32_t FloatToUint(float n)
{
   return (uint32_t)(*(uint32_t*)&n);
}
 
float UintToFloat(uint32_t n)
{
   return (float)(*(float*)&n);
}

static void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct;
    RCC_ClkInitTypeDef RCC_ClkInitStruct;
    RCC_PeriphCLKInitTypeDef PeriphClkInit;
    
    /* Configure the main internal regulator output voltage */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
    /* Initializes the CPU, AHB and APB busses clocks */
    RCC_OscInitStruct.OscillatorType  = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState        = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = 16;
    RCC_OscInitStruct.PLL.PLLState    = RCC_PLL_NONE;
    //RCC_OscInitStruct.PLL.PLLState    = RCC_PLL_ON;
    //RCC_OscInitStruct.PLL.PLLSource   = RCC_PLLSOURCE_HSI;
    //RCC_OscInitStruct.PLL.PLLMUL      = RCC_PLL_MUL8;
    //RCC_OscInitStruct.PLL.PLLDIV      = RCC_PLL_DIV2;
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        printf("Error in oscillator configuration\r\n");
    }
    
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    //RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
    {
        printf("Error in clock configuration\r\n");
    }
    
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
    PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
        printf("Error in peripherals clock configuration\r\n");
    }
}

void MX_ADC_Init(void)
{
    // Instance
    hadc.Instance = ADC1;
    
    // ADC oversampling parameters
    hadc.Init.OversamplingMode = DISABLE;
    hadc.Init.Oversample.Ratio = ADC_OVERSAMPLING_RATIO_16;
    hadc.Init.Oversample.RightBitShift = ADC_RIGHTBITSHIFT_4;
    hadc.Init.Oversample.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
    
    // ADC parameters
    hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
    hadc.Init.Resolution = ADC_RESOLUTION_12B;
    hadc.Init.SamplingTime = ADC_SAMPLETIME_3CYCLES_5;
    hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
    hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc.Init.ContinuousConvMode = ENABLE;
    hadc.Init.DiscontinuousConvMode = DISABLE;
    hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc.Init.DMAContinuousRequests = DISABLE;
    //hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc.Init.LowPowerAutoWait = DISABLE;
    hadc.Init.LowPowerFrequencyMode = DISABLE;
    hadc.Init.LowPowerAutoPowerOff = DISABLE;
    
    // ADC initialization
    if (HAL_ADC_Init(&hadc) != HAL_OK)
    {
        printf("Error during ADC configuration");
    }
    
    if (HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED) != HAL_OK)
    {
        printf("Error during ADC calibration");
    }
    
    // ADC channel parameters
    adcChannel.Channel = ADC_CHANNEL_0;
    
    // ADC channel configuration
    if (HAL_ADC_ConfigChannel(&hadc, &adcChannel) != HAL_OK)
    {
        printf("Error during ADC channel configuration!");
    }
}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    
    if (adcHandle->Instance == ADC1)
    {
        /* ADC1 clock enable */
        __HAL_RCC_ADC1_CLK_ENABLE();
    
        /* ADC GPIO Configuration PA0 ------> ADC_IN0 */
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        /* NVIC configuration for ADC EOC interrupt */
        HAL_NVIC_SetPriority(ADC1_COMP_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(ADC1_COMP_IRQn);
    }
}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{

  if(adcHandle->Instance==ADC1)
  {
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();
  
    /* ADC GPIO Configuration PA0 ------> ADC_IN0 */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);

    /* NVIC configuration for ADC EOC interrupt */
    HAL_NVIC_DisableIRQ(ADC1_COMP_IRQn);
  }
}