diff --git a/.gitignore b/.gitignore
index 87128e6426d25b53d2044f0a00fe805d8e571e8d..9afc659eb12e7c32954abc4372b73d9f23812ebd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
 
 !.gitignore
 !main.c
+!ble.c
+!imu.c
 !template.eww
 
 !ex_2/pca10040/blank/ses/*
diff --git a/project/ble.c b/project/ble.c
new file mode 100644
index 0000000000000000000000000000000000000000..2afc9dc19173d60b60c73c8c75640b465920873a
--- /dev/null
+++ b/project/ble.c
@@ -0,0 +1,632 @@
+// Flag to keep track of when an indication confirmation is pending
+static bool m_hts_meas_ind_conf_pending = false;
+volatile uint32_t hts_counter; // hold dummy hts data
+
+// Function declarations
+static void on_hts_evt(ble_hts_t * p_hts, ble_hts_evt_t * p_evt);
+static void temperature_measurement_send(void);
+
+// YOUR_JOB: Use UUIDs for service(s) used in your application.
+static ble_uuid_t m_adv_uuids[] =                                               /**< Universally unique service identifiers. */
+{
+    {BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE}
+};
+
+static void advertising_start(bool erase_bonds);
+
+/*
+* Function for generating a dummy temperature information packet.
+*/
+static void generate_temperature(ble_hts_meas_t * p_meas) {
+    static ble_date_time_t time_stamp = { 2018, 16, 10, 16, 15, 0 };
+
+    uint32_t celciusX100;
+
+    p_meas->temp_in_fahr_units = false;
+    p_meas->time_stamp_present = true;
+    p_meas->temp_type_present = (TEMP_TYPE_AS_CHARACTERISTIC ? false : true);
+
+    celciusX100 = 2000+hts_counter++; // one unit is 0.01 Celcius
+
+    p_meas->temp_in_celcius.exponent = -2;
+    p_meas->temp_in_celcius.mantissa = celciusX100;
+    p_meas->temp_in_fahr.exponent = -2;
+    p_meas->temp_in_fahr.mantissa = (32 * 100) + ((celciusX100 * 9) / 5);
+    p_meas->time_stamp = time_stamp;
+    p_meas->temp_type = BLE_HTS_TEMP_TYPE_FINGER;
+
+    // update simulated time stamp
+    time_stamp.seconds += 27;
+    if (time_stamp.seconds > 59){
+        time_stamp.seconds -= 60;
+        time_stamp.minutes++;
+        if (time_stamp.minutes > 59){
+            time_stamp.minutes = 0;
+        }
+    }
+}
+
+/*
+* Function for handling the Health Thermometer Service events.
+*/
+static void on_hts_evt(ble_hts_t * p_hts, ble_hts_evt_t * p_evt) {
+    switch (p_evt->evt_type) {
+    case BLE_HTS_EVT_INDICATION_ENABLED:
+        // Indication has been enabled, send a single temperature measurement
+        temperature_measurement_send();
+        break;
+    case BLE_HTS_EVT_INDICATION_CONFIRMED:
+        m_hts_meas_ind_conf_pending = false;
+        break;
+    default:
+        // No implementation needed.
+        break;
+    }
+}
+
+static void temperature_measurement_send(void) {
+    ble_hts_meas_t hts_meas; //Health Thermometer Service measurement structure
+    ret_code_t
+    err_code;
+
+    if (!m_hts_meas_ind_conf_pending) {
+        generate_temperature(&hts_meas);
+        err_code = ble_hts_measurement_send(&m_hts, &hts_meas);
+
+        switch (err_code) {
+            case NRF_SUCCESS:
+            // Measurement was successfully sent, wait for confirmation.
+            m_hts_meas_ind_conf_pending = true;
+            break;
+            case NRF_ERROR_INVALID_STATE:
+            // Ignore error.
+            break;
+            default:
+            APP_ERROR_HANDLER(err_code);
+            break;
+        }
+    }
+}
+
+/**@brief Callback function for asserts in the SoftDevice.
+ *
+ * @details This function will be called in case of an assert in the SoftDevice.
+ *
+ * @warning This handler is an example only and does not fit a final product. You need to analyze
+ *          how your product is supposed to react in case of Assert.
+ * @warning On assert from the SoftDevice, the system can only recover on reset.
+ *
+ * @param[in] line_num   Line number of the failing ASSERT call.
+ * @param[in] file_name  File name of the failing ASSERT call.
+ */
+void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) {
+    app_error_handler(DEAD_BEEF, line_num, p_file_name);
+}
+
+
+/**@brief Function for handling Peer Manager events.
+ *
+ * @param[in] p_evt  Peer Manager event.
+ */
+static void pm_evt_handler(pm_evt_t const * p_evt) {
+    pm_handler_on_pm_evt(p_evt);
+    pm_handler_flash_clean(p_evt);
+
+    switch (p_evt->evt_id) {
+        case PM_EVT_PEERS_DELETE_SUCCEEDED:
+            advertising_start(false);
+            break;
+
+        default:
+            break;
+    }
+}
+
+
+/**@brief Function for the Timer initialization.
+ *
+ * @details Initializes the timer module. This creates and starts application timers.
+ */
+static void timers_init(void) {
+    // Initialize timer module.
+    ret_code_t err_code = app_timer_init();
+    APP_ERROR_CHECK(err_code);
+
+    // Create timers.
+
+    /* YOUR_JOB: Create any timers to be used by the application.
+                    Below is an example of how to create a timer.
+                    For every new timer needed, increase the value of the macro APP_TIMER_MAX_TIMERS by
+                    one.
+        ret_code_t err_code;
+        err_code = app_timer_create(&m_app_timer_id, APP_TIMER_MODE_REPEATED, timer_timeout_handler);
+        APP_ERROR_CHECK(err_code); */
+}
+
+
+/**@brief Function for the GAP initialization.
+ *
+ * @details This function sets up all the necessary GAP (Generic Access Profile) parameters of the
+ *          device including the device name, appearance, and the preferred connection parameters.
+ */
+static void gap_params_init(void) {
+    ret_code_t              err_code;
+    ble_gap_conn_params_t   gap_conn_params;
+    ble_gap_conn_sec_mode_t sec_mode;
+
+    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
+
+    err_code = sd_ble_gap_device_name_set(&sec_mode,
+                                            (const uint8_t *)DEVICE_NAME,
+                                            strlen(DEVICE_NAME));
+    APP_ERROR_CHECK(err_code);
+
+    /* YOUR_JOB: Use an appearance value matching the application's use case.
+        err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_);
+        APP_ERROR_CHECK(err_code); */
+
+    memset(&gap_conn_params, 0, sizeof(gap_conn_params));
+
+    gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
+    gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
+    gap_conn_params.slave_latency     = SLAVE_LATENCY;
+    gap_conn_params.conn_sup_timeout  = CONN_SUP_TIMEOUT;
+
+    err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for initializing the GATT module.
+ */
+static void gatt_init(void) {
+    ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for handling Queued Write Module errors.
+ *
+ * @details A pointer to this function will be passed to each service which may need to inform the
+ *          application about an error.
+ *
+ * @param[in]   nrf_error   Error code containing information about what went wrong.
+ */
+static void nrf_qwr_error_handler(uint32_t nrf_error) {
+    APP_ERROR_HANDLER(nrf_error);
+}
+
+
+/**@brief Function for handling the YYY Service events.
+ * YOUR_JOB implement a service handler function depending on the event the service you are using can generate
+ *
+ * @details This function will be called for all YY Service events which are passed to
+ *          the application.
+ *
+ * @param[in]   p_yy_service   YY Service structure.
+ * @param[in]   p_evt          Event received from the YY Service.
+ *
+ *
+static void on_yys_evt(ble_yy_service_t     * p_yy_service,
+                       ble_yy_service_evt_t * p_evt)
+{
+    switch (p_evt->evt_type)
+    {
+        case BLE_YY_NAME_EVT_WRITE:
+            APPL_LOG("[APPL]: charact written with value %s. ", p_evt->params.char_xx.value.p_str);
+            break;
+
+        default:
+            // No implementation needed.
+            break;
+    }
+}
+*/
+
+/**@brief Function for initializing services that will be used by the application.
+ */
+static void services_init(void) {
+    ret_code_t         err_code;
+    nrf_ble_qwr_init_t qwr_init = {0};
+
+    // Initialize Queued Write Module.
+    qwr_init.error_handler = nrf_qwr_error_handler;
+
+    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
+    APP_ERROR_CHECK(err_code);
+
+    ble_hts_init_t hts_init;
+    ble_bas_init_t bas_init;
+
+    // Initialize Health Thermometer Service
+    memset(&hts_init, 0, sizeof(hts_init));
+    hts_init.evt_handler = on_hts_evt;
+    hts_init.temp_type_as_characteristic = TEMP_TYPE_AS_CHARACTERISTIC;
+    hts_init.temp_type = BLE_HTS_TEMP_TYPE_BODY;
+
+    // Here the sec level for the Health Thermometer Service can be changed/increased.
+    hts_init.ht_meas_cccd_wr_sec = SEC_JUST_WORKS;
+    hts_init.ht_type_rd_sec = SEC_OPEN;
+    err_code = ble_hts_init(&m_hts, &hts_init);
+    APP_ERROR_CHECK(err_code);
+
+    // Initialize Battery Service.
+    memset(&bas_init, 0, sizeof(bas_init));
+
+    // Here the sec level for the Battery Service can be changed/increased.
+    bas_init.bl_rd_sec = SEC_OPEN;
+    bas_init.bl_cccd_wr_sec = SEC_OPEN;
+    bas_init.bl_report_rd_sec = SEC_OPEN;
+
+    bas_init.evt_handler = NULL;
+    bas_init.support_notification = true;
+    bas_init.p_report_ref = NULL;
+    bas_init.initial_batt_level = 100;
+
+    err_code = ble_bas_init(&m_bas, &bas_init);
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for handling the Connection Parameters Module.
+ *
+ * @details This function will be called for all events in the Connection Parameters Module which
+ *          are passed to the application.
+ *          @note All this function does is to disconnect. This could have been done by simply
+ *                setting the disconnect_on_fail config parameter, but instead we use the event
+ *                handler mechanism to demonstrate its use.
+ *
+ * @param[in] p_evt  Event received from the Connection Parameters Module.
+ */
+static void on_conn_params_evt(ble_conn_params_evt_t * p_evt) {
+    ret_code_t err_code;
+
+    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED) {
+        err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
+        APP_ERROR_CHECK(err_code);
+    }
+}
+
+
+/**@brief Function for handling a Connection Parameters error.
+ *
+ * @param[in] nrf_error  Error code containing information about what went wrong.
+ */
+static void conn_params_error_handler(uint32_t nrf_error) {
+    APP_ERROR_HANDLER(nrf_error);
+}
+
+
+/**@brief Function for initializing the Connection Parameters module.
+ */
+static void conn_params_init(void) {
+    ret_code_t             err_code;
+    ble_conn_params_init_t cp_init;
+
+    memset(&cp_init, 0, sizeof(cp_init));
+
+    cp_init.p_conn_params                  = NULL;
+    cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
+    cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
+    cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
+    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
+    cp_init.disconnect_on_fail             = false;
+    cp_init.evt_handler                    = on_conn_params_evt;
+    cp_init.error_handler                  = conn_params_error_handler;
+
+    err_code = ble_conn_params_init(&cp_init);
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for starting timers.
+ */
+static void application_timers_start(void) {
+    /* YOUR_JOB: Start your timers. below is an example of how to start a timer.
+       ret_code_t err_code;
+       err_code = app_timer_start(m_app_timer_id, TIMER_INTERVAL, NULL);
+       APP_ERROR_CHECK(err_code); */
+
+}
+
+
+/**@brief Function for putting the chip into sleep mode.
+ *
+ * @note This function will not return.
+ */
+static void sleep_mode_enter(void) {
+    ret_code_t err_code;
+
+    err_code = bsp_indication_set(BSP_INDICATE_IDLE);
+    APP_ERROR_CHECK(err_code);
+
+    // Prepare wakeup buttons.
+    err_code = bsp_btn_ble_sleep_mode_prepare();
+    APP_ERROR_CHECK(err_code);
+
+    // Go to system-off mode (this function will not return; wakeup will cause a reset).
+    err_code = sd_power_system_off();
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for handling advertising events.
+ *
+ * @details This function will be called for advertising events which are passed to the application.
+ *
+ * @param[in] ble_adv_evt  Advertising event.
+ */
+static void on_adv_evt(ble_adv_evt_t ble_adv_evt) {
+    ret_code_t err_code;
+
+    switch (ble_adv_evt) {
+        case BLE_ADV_EVT_FAST:
+            NRF_LOG_INFO("Fast advertising.");
+            err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
+            APP_ERROR_CHECK(err_code);
+            break;
+
+        case BLE_ADV_EVT_IDLE:
+            sleep_mode_enter();
+            break;
+
+        default:
+            break;
+    }
+}
+
+
+/**@brief Function for handling BLE events.
+ *
+ * @param[in]   p_ble_evt   Bluetooth stack event.
+ * @param[in]   p_context   Unused.
+ */
+static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) {
+    ret_code_t err_code = NRF_SUCCESS;
+
+    switch (p_ble_evt->header.evt_id) {
+        case BLE_GAP_EVT_DISCONNECTED:
+            NRF_LOG_INFO("Disconnected.");
+            // LED indication will be changed when advertising starts.
+            break;
+
+        case BLE_GAP_EVT_CONNECTED:
+            NRF_LOG_INFO("Connected.");
+            err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
+            APP_ERROR_CHECK(err_code);
+            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
+            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
+            APP_ERROR_CHECK(err_code);
+            break;
+
+        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
+        {
+            NRF_LOG_DEBUG("PHY update request.");
+            ble_gap_phys_t const phys =
+            {
+                .rx_phys = BLE_GAP_PHY_AUTO,
+                .tx_phys = BLE_GAP_PHY_AUTO,
+            };
+            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
+            APP_ERROR_CHECK(err_code);
+        } break;
+
+        case BLE_GATTC_EVT_TIMEOUT:
+            // Disconnect on GATT Client timeout event.
+            NRF_LOG_DEBUG("GATT Client Timeout.");
+            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
+                                                BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
+            APP_ERROR_CHECK(err_code);
+            break;
+
+        case BLE_GATTS_EVT_TIMEOUT:
+            // Disconnect on GATT Server timeout event.
+            NRF_LOG_DEBUG("GATT Server Timeout.");
+            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
+                                                BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
+            APP_ERROR_CHECK(err_code);
+            break;
+
+        default:
+            // No implementation needed.
+            break;
+    }
+}
+
+
+/**@brief Function for initializing the BLE stack.
+ *
+ * @details Initializes the SoftDevice and the BLE event interrupt.
+ */
+static void ble_stack_init(void) {
+    ret_code_t err_code;
+
+    err_code = nrf_sdh_enable_request();
+    APP_ERROR_CHECK(err_code);
+
+    // Configure the BLE stack using the default settings.
+    // Fetch the start address of the application RAM.
+    uint32_t ram_start = 0;
+    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
+    APP_ERROR_CHECK(err_code);
+
+    // Enable BLE stack.
+    err_code = nrf_sdh_ble_enable(&ram_start);
+    APP_ERROR_CHECK(err_code);
+
+    // Register a handler for BLE events.
+    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
+}
+
+
+/**@brief Function for the Peer Manager initialization.
+ */
+static void peer_manager_init(void) {
+    ble_gap_sec_params_t sec_param;
+    ret_code_t           err_code;
+
+    err_code = pm_init();
+    APP_ERROR_CHECK(err_code);
+
+    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));
+
+    // Security parameters to be used for all security procedures.
+    sec_param.bond           = SEC_PARAM_BOND;
+    sec_param.mitm           = SEC_PARAM_MITM;
+    sec_param.lesc           = SEC_PARAM_LESC;
+    sec_param.keypress       = SEC_PARAM_KEYPRESS;
+    sec_param.io_caps        = SEC_PARAM_IO_CAPABILITIES;
+    sec_param.oob            = SEC_PARAM_OOB;
+    sec_param.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
+    sec_param.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
+    sec_param.kdist_own.enc  = 1;
+    sec_param.kdist_own.id   = 1;
+    sec_param.kdist_peer.enc = 1;
+    sec_param.kdist_peer.id  = 1;
+
+    err_code = pm_sec_params_set(&sec_param);
+    APP_ERROR_CHECK(err_code);
+
+    err_code = pm_register(pm_evt_handler);
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Clear bond information from persistent storage.
+ */
+static void delete_bonds(void) {
+    ret_code_t err_code;
+
+    NRF_LOG_INFO("Erase bonds!");
+
+    err_code = pm_peers_delete();
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for handling events from the BSP module.
+ *
+ * @param[in]   event   Event generated when button is pressed.
+ */
+static void bsp_event_handler(bsp_event_t event) {
+    ret_code_t err_code;
+
+    switch (event) {
+        case BSP_EVENT_SLEEP:
+            sleep_mode_enter();
+            break; // BSP_EVENT_SLEEP
+
+        case BSP_EVENT_DISCONNECT:
+            err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
+            if (err_code != NRF_ERROR_INVALID_STATE) {
+                APP_ERROR_CHECK(err_code);
+            }
+            break; // BSP_EVENT_DISCONNECT
+
+        case BSP_EVENT_WHITELIST_OFF:
+            if (m_conn_handle == BLE_CONN_HANDLE_INVALID) {
+                err_code = ble_advertising_restart_without_whitelist(&m_advertising);
+                if (err_code != NRF_ERROR_INVALID_STATE) {
+                    APP_ERROR_CHECK(err_code);
+                }
+            }
+            break; // BSP_EVENT_KEY_0
+        case BSP_EVENT_KEY_0:
+            if (m_conn_handle != BLE_CONN_HANDLE_INVALID) {
+                temperature_measurement_send();
+            }
+            break;
+
+        default:
+            break;
+    }
+}
+
+
+/**@brief Function for initializing the Advertising functionality.
+ */
+static void advertising_init(void) {
+    ret_code_t             err_code;
+    ble_advertising_init_t init;
+
+    memset(&init, 0, sizeof(init));
+
+    init.advdata.name_type               = BLE_ADVDATA_FULL_NAME;
+    init.advdata.include_appearance      = true;
+    init.advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
+    init.advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
+    init.advdata.uuids_complete.p_uuids  = m_adv_uuids;
+
+    init.config.ble_adv_fast_enabled  = true;
+    init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
+    init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;
+
+    init.evt_handler = on_adv_evt;
+
+    err_code = ble_advertising_init(&m_advertising, &init);
+    APP_ERROR_CHECK(err_code);
+
+    ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
+}
+
+
+/**@brief Function for initializing buttons and leds.
+ *
+ * @param[out] p_erase_bonds  Will be true if the clear bonding button was pressed to wake the application up.
+ */
+static void buttons_leds_init(bool * p_erase_bonds) {
+    ret_code_t err_code;
+    bsp_event_t startup_event;
+
+    err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);
+    APP_ERROR_CHECK(err_code);
+
+    err_code = bsp_btn_ble_init(NULL, &startup_event);
+    APP_ERROR_CHECK(err_code);
+
+    *p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
+}
+
+
+/**@brief Function for initializing the nrf log module.
+ */
+static void log_init(void) {
+    ret_code_t err_code = NRF_LOG_INIT(NULL);
+    APP_ERROR_CHECK(err_code);
+
+    NRF_LOG_DEFAULT_BACKENDS_INIT();
+}
+
+
+/**@brief Function for initializing power management.
+ */
+static void power_management_init(void) {
+    ret_code_t err_code;
+    err_code = nrf_pwr_mgmt_init();
+    APP_ERROR_CHECK(err_code);
+}
+
+
+/**@brief Function for handling the idle state (main loop).
+ *
+ * @details If there is no pending log operation, then sleep until next the next event occurs.
+ */
+static void idle_state_handle(void) {
+    if (NRF_LOG_PROCESS() == false) {
+        nrf_pwr_mgmt_run();
+    }
+}
+
+
+/**@brief Function for starting advertising.
+ */
+static void advertising_start(bool erase_bonds) {
+    if (erase_bonds == true) {
+        delete_bonds();
+        // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event
+    } else {
+        ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
+
+        APP_ERROR_CHECK(err_code);
+    }
+}
diff --git a/project/imu.c b/project/imu.c
new file mode 100644
index 0000000000000000000000000000000000000000..7452480b888422e3614366de853e3e5af49a959f
--- /dev/null
+++ b/project/imu.c
@@ -0,0 +1,231 @@
+/**
+* Function for initializing the FIR filter instance
+*/
+void dsp_config() {
+    // Note that the init function requires the address of the coefficient table B as an input
+    arm_fir_init_f32(&fir_lpf, NUM_TAPS, (float32_t *)&B[0], &firStateF32[0], BLOCK_SIZE);
+}
+
+/**
+* Function for computing the filter response
+*/
+void compute_fir() {
+
+  // Each axis is processed on its own in 5 blocks of data
+
+  // x-axis
+  for (uint8_t i = 0; i < 5; i++) {
+    arm_fir_f32(&fir_lpf, &acc_x_in_buf[0] + i * BLOCK_SIZE, &acc_x_out_buf[0] + i * BLOCK_SIZE, BLOCK_SIZE);
+  }
+
+  // y-axis
+  for (uint8_t i = 0; i < 5; i++) {
+    arm_fir_f32(&fir_lpf, &acc_y_in_buf[0] + i * BLOCK_SIZE, &acc_y_out_buf[0] + i * BLOCK_SIZE, BLOCK_SIZE);
+  }
+
+  // z-axis
+  for (uint8_t i = 0; i < 5; i++) {
+    arm_fir_f32(&fir_lpf, &acc_z_in_buf[0] + i * BLOCK_SIZE, &acc_z_out_buf[0] + i * BLOCK_SIZE, BLOCK_SIZE);
+  }
+
+  block_cnt = 0; // Reset block counting
+}
+
+/**
+* Function for reading FIFO data
+*/
+int8_t get_bmi160_fifo_data() {
+    int8_t rslt = BMI160_OK;
+    uint8_t acc_frames_req = 28;
+    // Read the fifo buffer using SPI
+    rslt = bmi160_get_fifo_data(&sensor);
+    // Parse the data and extract 28 accelerometer frames
+    rslt = bmi160_extract_accel(acc_data, &acc_frames_req, &sensor);
+
+    // Copy the contents of each axis to a FIR input buffer
+    for (uint8_t i = 0; i < acc_frames_req; i++) {
+        acc_x_in_buf[acc_frames_req*block_cnt + i] = acc_data[i].x;
+        acc_y_in_buf[acc_frames_req*block_cnt + i] = acc_data[i].y;
+        acc_z_in_buf[acc_frames_req*block_cnt + i] = acc_data[i].z;
+    }
+
+    // Increase block count after each sensor read
+    block_cnt++;
+
+    // After 5 reads the buffer is almost full and the data is ready to be processed
+    if (block_cnt == 5) {
+        compute_fir();
+    }
+
+    return rslt;
+}
+
+/**
+* SPI user event handler.
+*/
+void spi_event_handler(nrf_drv_spi_evt_t const * p_event, void *p_context) {
+    spi_xfer_done = true; // Set a flag when transfer is done
+}
+
+void gpio_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
+    get_bmi160_fifo_data();
+}
+
+/**
+* Function for setting up the SPI communication.
+*/
+uint32_t spi_config() {
+    uint32_t err_code;
+
+    // Use nRF's default configurations
+    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
+
+    // Define each GPIO pin
+    spi_config.ss_pin = SPI_SS_PIN;
+    spi_config.miso_pin = SPI_MISO_PIN;
+    spi_config.mosi_pin = SPI_MOSI_PIN;
+    spi_config.sck_pin = SPI_SCK_PIN;
+
+    // Initialize the SPI peripheral and give it a function pointer to
+    // it’s event handler
+    err_code = nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL);
+
+    return err_code;
+}
+
+/**
+* Function for writing to the BMI160 via SPI.
+*/
+int8_t bmi160_spi_bus_write(uint8_t hw_addr, uint8_t reg_addr, uint8_t *reg_data, uint16_t cnt) {
+    spi_xfer_done = false; // set the flag down during transfer
+
+    int32_t error = 0;
+
+    // Allocate array, which lenght is address + number of data bytes to be sent
+    uint8_t tx_buff[cnt+1];
+    uint16_t stringpos;
+
+    // AND address with 0111 1111; set msb to '0' (write operation)
+    tx_buff[0] = reg_addr & 0x7F;
+    for (stringpos = 0; stringpos < cnt; stringpos++) {
+        tx_buff[stringpos+1] = *(reg_data + stringpos);
+    }
+
+    // Do the actual SPI transfer
+    nrf_drv_spi_transfer(&spi, tx_buff, cnt+1, NULL, 0);
+    while (!spi_xfer_done) {}; // Loop until the transfer is complete
+
+    return (int8_t)error;
+}
+
+/**
+* Function for reading from the BMI160 via SPI.
+*/
+int8_t bmi160_spi_bus_read(uint8_t hw_addr, uint8_t reg_addr, uint8_t *reg_data, uint16_t len) {
+  spi_xfer_done = false; // set the flag down during transfer
+
+  int32_t error = 0;
+
+  uint8_t tx_buff = reg_addr | 0x80; // OR address with 1000 0000; Read -> set msb to '1';
+  uint8_t * rx_buff_pointer;
+  uint16_t stringpos;
+
+  rx_buff_pointer = (uint8_t *) (SPI_RX_Buffer);
+
+  // Do the actual SPI transfer
+  nrf_drv_spi_transfer(&spi, &tx_buff, 1, rx_buff_pointer, len+1);
+
+  while (!spi_xfer_done) {} // Loop until the transfer is complete
+
+  // Copy received bytes to reg_data
+  for (stringpos = 0; stringpos < len; stringpos++)
+  *(reg_data + stringpos) = SPI_RX_Buffer[stringpos + 1];
+
+  return (int8_t)error;
+}
+
+/**
+* Function for configuring the sensor
+*/
+int8_t sensor_config() {
+    int8_t rslt = BMI160_OK;
+
+    sensor.id = 0; // We use SPI so id == 0
+    sensor.interface = BMI160_SPI_INTF;
+
+    // Give the driver the correct interfacing functions
+    sensor.read = bmi160_spi_bus_read;
+    sensor.write = bmi160_spi_bus_write;
+    sensor.delay_ms = nrf_delay_ms;
+
+    // Initialize the sensor and check if everything went ok
+    rslt = bmi160_init(&sensor);
+
+    // Configure the accelerometer's sampling freq, range and modes
+    sensor.accel_cfg.odr = BMI160_ACCEL_ODR_100HZ;
+    sensor.accel_cfg.range = BMI160_ACCEL_RANGE_8G;
+    sensor.accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4;
+    sensor.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;
+    // Set the configurations
+    rslt = bmi160_set_sens_conf(&sensor);
+    // Some fifo settings
+    fifo_frame.data = fifo_buff;
+    fifo_frame.length = 200;
+    sensor.fifo = &fifo_frame;
+
+    // Configure the sensor's FIFO settings
+    rslt = bmi160_set_fifo_config(BMI160_FIFO_ACCEL, BMI160_ENABLE, &sensor);
+    // Create an instance for interrupt settings
+    struct bmi160_int_settg int_config;
+    // Interrupt channel/pin 1
+    int_config.int_channel = BMI160_INT_CHANNEL_1;
+    // Choosing fifo watermark interrupt
+    int_config.int_type = BMI160_ACC_GYRO_FIFO_WATERMARK_INT;
+    // Set fifo watermark level to 180
+    rslt = bmi160_set_fifo_wm((uint8_t) 180, &sensor);
+    // Enabling interrupt pins to act as output pin
+    int_config.int_pin_settg.output_en = BMI160_ENABLE;
+    // Choosing push-pull mode for interrupt pin
+    int_config.int_pin_settg.output_mode = BMI160_DISABLE;
+    // Choosing active high output
+    int_config.int_pin_settg.output_type = BMI160_ENABLE;
+    // Choosing edge triggered output
+    int_config.int_pin_settg.edge_ctrl = BMI160_ENABLE;
+    // Disabling interrupt pin to act as input
+    int_config.int_pin_settg.input_en = BMI160_DISABLE;
+    // Non-latched output
+    int_config.int_pin_settg.latch_dur = BMI160_LATCH_DUR_NONE;
+    // Enabling FIFO watermark interrupt
+    int_config.fifo_WTM_int_en = BMI160_ENABLE;
+    // Set interrupt configurations
+    rslt = bmi160_set_int_config(&int_config, &sensor);
+
+    return rslt;
+}
+
+/**
+* Function for configuring General Purpose I/O.
+*/
+uint32_t config_gpio() {
+    uint32_t err_code = NRF_SUCCESS;
+
+    if(!nrf_drv_gpiote_is_init()) {
+        err_code = nrf_drv_gpiote_init();
+    }
+
+    // Set which clock edge triggers the interrupt
+    nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
+
+    // Configure the internal pull up resistor
+    //config.pull = NRF_GPIO_PIN_PULLUP;
+
+    // Configure the pin as input
+    err_code = nrf_drv_gpiote_in_init(INT_PIN, &config, gpio_event_handler);
+    if (err_code != NRF_SUCCESS) {
+    // handle error condition
+    }
+
+    // Enable events
+    nrf_drv_gpiote_in_event_enable(INT_PIN, true);
+    return err_code;
+}
diff --git a/project/main.c b/project/main.c
index b82caf1c13f01a3c3bb0371eb62ca3616ce9702b..1682b52ebeb5c0b234b3469a3ec99ffa587445ab 100644
--- a/project/main.c
+++ b/project/main.c
@@ -90,6 +90,127 @@
 #include "fdacoefs.h"
 #include "arm_math.h"
 
+#define SPI_INSTANCE 0 // SPI instance index. We use SPI master 0
+#define SPI_SS_PIN 26
+#define SPI_MISO_PIN 23
+#define SPI_MOSI_PIN 24
+#define SPI_SCK_PIN 22
+#define INT_PIN 27
+
+#define NUM_TAPS 58
+#define BLOCK_SIZE 28
+
+#define DEVICE_NAME                     "nRF_Aapo"                       /**< Name of device. Will be included in the advertising data. */
+#define MANUFACTURER_NAME               "NordicSemiconductor"                   /**< Manufacturer. Will be passed to Device Information Service. */
+#define APP_ADV_INTERVAL                300                                     /**< The advertising interval (in units of 0.625 ms. This value corresponds to 187.5 ms). */
+
+#define APP_ADV_DURATION                18000                                   /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
+#define APP_BLE_OBSERVER_PRIO           3                                       /**< Application's BLE observer priority. You shouldn't need to modify this value. */
+#define APP_BLE_CONN_CFG_TAG            1                                       /**< A tag identifying the SoftDevice BLE configuration. */
+
+#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(100, UNIT_1_25_MS)        /**< Minimum acceptable connection interval (0.1 seconds). */
+#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(200, UNIT_1_25_MS)        /**< Maximum acceptable connection interval (0.2 second). */
+#define SLAVE_LATENCY                   0                                       /**< Slave latency. */
+#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)         /**< Connection supervisory timeout (4 seconds). */
+
+#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                   /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
+#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                  /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
+#define MAX_CONN_PARAMS_UPDATE_COUNT    3                                       /**< Number of attempts before giving up the connection parameter negotiation. */
+
+#define SEC_PARAM_BOND                  1                                       /**< Perform bonding. */
+#define SEC_PARAM_MITM                  0                                       /**< Man In The Middle protection not required. */
+#define SEC_PARAM_LESC                  0                                       /**< LE Secure Connections not enabled. */
+#define SEC_PARAM_KEYPRESS              0                                       /**< Keypress notifications not enabled. */
+#define SEC_PARAM_IO_CAPABILITIES       BLE_GAP_IO_CAPS_NONE                    /**< No I/O capabilities. */
+#define SEC_PARAM_OOB                   0                                       /**< Out Of Band data not available. */
+#define SEC_PARAM_MIN_KEY_SIZE          7                                       /**< Minimum encryption key size. */
+#define SEC_PARAM_MAX_KEY_SIZE          16                                      /**< Maximum encryption key size. */
+
+#define DEAD_BEEF                       0xDEADBEEF                              /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */
+
+// Determines if temperature type is given as characteristic (1) or as a field of measurement (0)
+#define TEMP_TYPE_AS_CHARACTERISTIC 0
+
+// Declare a state array
+static float32_t firStateF32[BLOCK_SIZE + NUM_TAPS - 1];
+// Declare an instance for the low-pass FIR filter
+arm_fir_instance_f32 fir_lpf;
+
+float32_t acc_x_in_buf[140];
+float32_t acc_y_in_buf[140];
+float32_t acc_z_in_buf[140];
+float32_t acc_x_out_buf[140];
+float32_t acc_y_out_buf[140];
+float32_t acc_z_out_buf[140];
+
+int in_array[16];
+int out_array[16];
+
+int block_cnt = 0;
+
+//SPI instance
+static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
+//Flag used to indicate that SPI instance completed the transfer
+static volatile bool spi_xfer_done;
+
+static uint8_t SPI_RX_Buffer[201]; // Allocate a buffer for SPI reads
+struct bmi160_dev sensor; // An instance of bmi160 sensor
+
+// Declare memory to store the raw FIFO buffer information
+uint8_t fifo_buff[200];
+// Modify the FIFO buffer instance and link to the device instance
+struct bmi160_fifo_frame fifo_frame;
+
+// 200 bytes -> ~7bytes per frame -> ~28 data frames
+struct bmi160_sensor_data acc_data[28];
+
+NRF_BLE_GATT_DEF(m_gatt);                                                       /**< GATT module instance. */
+NRF_BLE_QWR_DEF(m_qwr);                                                         /**< Context for the Queued Write module.*/
+BLE_ADVERTISING_DEF(m_advertising);                                             /**< Advertising module instance. */
+
+static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;                        /**< Handle of the current connection. */
+
+BLE_HTS_DEF(m_hts); // Macro for defining a ble_hts instance
+BLE_BAS_DEF(m_bas); // Macro for defining a ble_bas instance
+
+#include "imu.c"
+#include "ble.c"
+
 int main(void) {
 
+    bool erase_bonds;
+
+    dsp_config();
+    spi_config();
+    config_gpio();
+    sensor_config();
+
+    while(true) {
+        __WFE(); // sleep until an event wakes us up
+        __SEV(); // sleep until an event wakes us up
+        __WFE(); // sleep until an event wakes us up
+    }
+
+    log_init();
+    timers_init();
+    buttons_leds_init(&erase_bonds);
+    power_management_init();
+    ble_stack_init();
+    gap_params_init();
+    gatt_init();
+    advertising_init();
+    services_init();
+    conn_params_init();
+    peer_manager_init();
+
+    // Start execution.
+    NRF_LOG_INFO("Template example started.");
+    application_timers_start();
+
+    advertising_start(erase_bonds);
+
+    // Enter main loop.
+    for (;;) {
+        idle_state_handle();
+    }
 }